Skip to content

Commit

Permalink
Merge pull request #134 from crisdut/review/rgb11
Browse files Browse the repository at this point in the history
  • Loading branch information
dr-orlovsky authored Jan 27, 2024
2 parents e2bbd76 + 59a183e commit fa8d1ad
Show file tree
Hide file tree
Showing 10 changed files with 343 additions and 23 deletions.
15 changes: 11 additions & 4 deletions invoice/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,8 @@ use std::str::FromStr;
use rgb::ContractId;
use strict_encoding::{FieldName, TypeName};

use super::{
Amount, Beneficiary, InvoiceState, Precision, RgbInvoice, RgbTransport, TransportParseError,
};
use crate::invoice::XChainNet;
use crate::invoice::{Beneficiary, InvoiceState, RgbInvoice, RgbTransport, XChainNet};
use crate::{Allocation, Amount, NonFungible, Precision, TransportParseError};

#[derive(Clone, Eq, PartialEq, Debug)]
pub struct RgbInvoiceBuilder(RgbInvoice);
Expand Down Expand Up @@ -107,6 +105,15 @@ impl RgbInvoiceBuilder {
Ok(self.set_amount_raw(amount))
}

pub fn set_allocation_raw(mut self, allocation: impl Into<Allocation>) -> Self {
self.0.owned_state = InvoiceState::Data(NonFungible::RGB21(allocation.into()));
self
}

pub fn set_allocation(self, token_index: u32, fraction: u64) -> Result<Self, Self> {
Ok(self.set_allocation_raw(Allocation::with(token_index, fraction)))
}

/// # Safety
///
/// The function may cause the loss of the information about the precise
Expand Down
107 changes: 107 additions & 0 deletions invoice/src/data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use std::str::FromStr;

use rgb::{DataState, KnownState, RevealedData};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use strict_encoding::{StrictDeserialize, StrictSerialize};

use crate::LIB_NAME_RGB_CONTRACT;

#[derive(Clone, Eq, PartialEq, Hash, Debug, Display)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub enum NonFungible {
#[display(inner)]
RGB21(Allocation),
}

impl FromStr for NonFungible {
type Err = AllocationParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let allocation = Allocation::from_str(s)?;
Ok(NonFungible::RGB21(allocation))
}
}

#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)]
#[display(inner)]
pub enum AllocationParseError {
#[display(doc_comments)]
/// invalid token index {0}.
InvalidIndex(String),

#[display(doc_comments)]
/// invalid fraction {0}.
InvalidFraction(String),

#[display(doc_comments)]
/// allocation must have format <fraction>@<token_index>.
WrongFormat,
}

#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, From, Display)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB_CONTRACT)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))]
#[display("{1}@{0}")]
pub struct Allocation(u32, u64);

impl StrictSerialize for Allocation {}
impl StrictDeserialize for Allocation {}

impl KnownState for Allocation {}

impl From<RevealedData> for Allocation {
fn from(data: RevealedData) -> Self {
Allocation::from_strict_serialized(data.value.into()).expect("invalid allocation data")
}
}

impl From<DataState> for Allocation {
fn from(state: DataState) -> Self {
Allocation::from_strict_serialized(state.into()).expect("invalid allocation data")
}
}

impl From<Allocation> for DataState {
fn from(allocation: Allocation) -> Self {
DataState::from(
allocation
.to_strict_serialized()
.expect("invalid allocation data"),
)
}
}

impl FromStr for Allocation {
type Err = AllocationParseError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
if !s.contains('@') {
return Err(AllocationParseError::WrongFormat);
}

match s.split_once('@') {
Some((fraction, token_index)) => Ok(Allocation::with(
token_index
.parse()
.map_err(|_| AllocationParseError::InvalidIndex(token_index.to_owned()))?,
fraction
.parse()
.map_err(|_| AllocationParseError::InvalidFraction(fraction.to_lowercase()))?,
)),
None => Err(AllocationParseError::WrongFormat),
}
}
}

impl Allocation {
pub fn with(token_index: u32, fraction: u64) -> Self { Self(token_index, fraction) }

pub fn token_index(self) -> u32 { self.0 }

pub fn fraction(self) -> u64 { self.1 }
}
33 changes: 30 additions & 3 deletions invoice/src/invoice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use std::str::FromStr;

use indexmap::IndexMap;
use invoice::{AddressNetwork, AddressPayload, Network};

Check failure on line 25 in invoice/src/invoice.rs

View workflow job for this annotation

GitHub Actions / testing

multiple candidates for `rlib` dependency `invoice` found

Check failure on line 25 in invoice/src/invoice.rs

View workflow job for this annotation

GitHub Actions / codecov

multiple candidates for `rlib` dependency `invoice` found
use rgb::{AttachId, ContractId, Layer1, SecretSeal};
use strict_encoding::{FieldName, TypeName};

use crate::Amount;
use crate::{Amount, NonFungible};

Check failure on line 29 in invoice/src/invoice.rs

View workflow job for this annotation

GitHub Actions / testing

unresolved imports `crate::Amount`, `crate::NonFungible`, `crate::Allocation`, `crate::Amount`, `crate::NonFungible`, `crate::Precision`, `crate::TransportParseError`, `crate::LIB_NAME_RGB_CONTRACT`, `crate::LIB_NAME_RGB_CONTRACT`

Check failure on line 29 in invoice/src/invoice.rs

View workflow job for this annotation

GitHub Actions / codecov

unresolved imports `crate::Amount`, `crate::NonFungible`, `crate::Allocation`, `crate::Amount`, `crate::NonFungible`, `crate::Precision`, `crate::TransportParseError`, `crate::LIB_NAME_RGB_CONTRACT`, `crate::LIB_NAME_RGB_CONTRACT`

#[derive(Clone, Eq, PartialEq, Hash, Debug)]
#[non_exhaustive]
Expand All @@ -36,18 +38,43 @@ pub enum RgbTransport {
UnspecifiedMeans,
}

#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)]
#[display(inner)]
pub enum InvoiceStateError {
#[display(doc_comments)]
/// could not parse as amount, data, or attach: {0}.
ParseError(String),
}

#[derive(Clone, Eq, PartialEq, Hash, Debug, Display)]
pub enum InvoiceState {
#[display("")]
Void,
#[display("{0}")]
Amount(Amount),
#[display("...")] // TODO
Data(Vec<u8> /* StrictVal */),
#[display(inner)]
Data(NonFungible),
#[display(inner)]
Attach(AttachId),
}

impl FromStr for InvoiceState {
type Err = InvoiceStateError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
Ok(InvoiceState::Void)
} else if let Ok(amount) = Amount::from_str(s) {
Ok(InvoiceState::Amount(amount))
} else if let Ok(data) = NonFungible::from_str(s) {
Ok(InvoiceState::Data(data))
} else if let Ok(attach) = AttachId::from_str(s) {
Ok(InvoiceState::Attach(attach))
} else {
Err(InvoiceStateError::ParseError(s.to_owned()))
}
}
}

#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
#[non_exhaustive]
pub enum ChainNet {
Expand Down
2 changes: 2 additions & 0 deletions invoice/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ mod invoice;
mod parse;
mod builder;
mod amount;
mod data;

pub use amount::{Amount, CoinAmount, Precision};
pub use builder::RgbInvoiceBuilder;
pub use data::{Allocation, NonFungible};
pub use parse::{InvoiceParseError, TransportParseError};

pub use crate::invoice::{
Expand Down
32 changes: 25 additions & 7 deletions invoice/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
use rgb::{ContractId, SecretSeal};
use strict_encoding::{InvalidIdent, TypeName};

use super::{Amount, Beneficiary, InvoiceState, RgbInvoice, RgbTransport};
use crate::invoice::{ChainNet, XChainNet};
use crate::invoice::{Beneficiary, ChainNet, InvoiceState, RgbInvoice, RgbTransport, XChainNet};

const OMITTED: &str = "~";
const EXPIRY: &str = "expiry";
Expand Down Expand Up @@ -118,6 +117,10 @@ pub enum InvoiceParseError {
#[display(inner)]
Num(ParseIntError),

/// can't recognize amount "{0}": it should be valid rgb21 allocation
/// data.
Data(String),

#[from]
/// invalid interface name.
IfaceName(InvalidIdent),
Expand Down Expand Up @@ -352,14 +355,16 @@ impl FromStr for RgbInvoice {
.map(|(a, b)| (Some(a), Some(b)))
.unwrap_or((Some(assignment.as_str()), None));
// TODO: support other state types
let (beneficiary_str, value) = match (amount, beneficiary) {
(Some(a), Some(b)) => (b, InvoiceState::Amount(a.parse::<Amount>()?)),
(Some(b), None) => (b, InvoiceState::Void),
let (beneficiary_str, value) = match (beneficiary, amount) {
(Some(b), Some(a)) => (
b,
InvoiceState::from_str(a).map_err(|_| InvoiceParseError::Data(a.to_string()))?,
),
(None, Some(b)) => (b, InvoiceState::Void),
_ => unreachable!(),
};

let beneficiary = XChainNet::<Beneficiary>::from_str(beneficiary_str)?;

let mut query_params = map_query_params(&uri)?;

let transports = if let Some(endpoints) = query_params.remove(ENDPOINTS) {
Expand Down Expand Up @@ -427,19 +432,32 @@ mod test {

#[test]
fn parse() {
// all path parameters
// rgb20/rgb25 parameters
let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/\
100+bc:utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb";
let invoice = RgbInvoice::from_str(invoice_str).unwrap();
assert_eq!(invoice.to_string(), invoice_str);
assert_eq!(format!("{invoice:#}"), invoice_str.replace('-', ""));

// rgb21 parameters
let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB21/1@\
1+bc:utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb";
let invoice = RgbInvoice::from_str(invoice_str).unwrap();
assert_eq!(invoice.to_string(), invoice_str);
assert_eq!(format!("{invoice:#}"), invoice_str.replace('-', ""));

// no amount
let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB20/bc:\
utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb";
let invoice = RgbInvoice::from_str(invoice_str).unwrap();
assert_eq!(invoice.to_string(), invoice_str);

// no allocation
let invoice_str = "rgb:2WBcas9-yjzEvGufY-9GEgnyMj7-beMNMWA8r-sPHtV1nPU-TMsGMQX/RGB21/bc:\
utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb";
let invoice = RgbInvoice::from_str(invoice_str).unwrap();
assert_eq!(invoice.to_string(), invoice_str);

// no contract ID
let invoice_str =
"rgb:~/RGB20/bc:utxob:egXsFnw-5Eud7WKYn-7DVQvcPbc-rR69YmgmG-veacwmUFo-uMFKFb";
Expand Down
14 changes: 13 additions & 1 deletion src/interface/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use std::collections::{HashMap, HashSet};

use amplify::confinement::{Confined, TinyOrdMap, TinyOrdSet, U16};
use amplify::{confinement, Wrapper};
use invoice::Amount;
use invoice::{Allocation, Amount};
use rgb::{
AltLayer1, AltLayer1Set, AssetTag, Assign, AssignmentType, Assignments, BlindingFactor,
ContractId, DataState, ExposedSeal, FungibleType, Genesis, GenesisSeal, GlobalState, GraphSeal,
Expand Down Expand Up @@ -452,6 +452,18 @@ impl TransitionBuilder {
Ok(self)
}

pub fn add_data_raw(
mut self,
type_id: AssignmentType,
seal: impl Into<BuilderSeal<GraphSeal>>,
allocation: impl Into<Allocation>,
blinding: u64,
) -> Result<Self, BuilderError> {
let revelead_state = RevealedData::with_salt(allocation.into(), blinding.into());
self.builder = self.builder.add_data_raw(type_id, seal, revelead_state)?;
Ok(self)
}

pub fn add_data_default(
self,
seal: impl Into<BuilderSeal<GraphSeal>>,
Expand Down
20 changes: 19 additions & 1 deletion src/interface/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
use std::collections::HashMap;

use amplify::confinement::{SmallOrdSet, SmallVec};
use invoice::Amount;
use invoice::{Allocation, Amount};
use rgb::{
AssignmentWitness, AttachId, ContractId, ContractState, DataState, KnownState, MediaType, OpId,
OutputAssignment, RevealedAttach, RevealedData, RevealedValue, VoidState, WitnessId, XOutpoint,
Expand Down Expand Up @@ -69,6 +69,7 @@ pub enum AllocatedState {

#[from]
#[from(RevealedData)]
#[from(Allocation)]
#[strict_type(tag = 2)]
Data(DataState),

Expand Down Expand Up @@ -372,6 +373,23 @@ impl ContractIface {
))
}

pub fn data_ops<C: StateChange<State = DataState>>(
&self,
name: impl Into<FieldName>,
witness_filter: impl WitnessFilter + Copy,
outpoint_filter: impl OutpointFilter + Copy,
) -> Result<HashMap<WitnessId, IfaceOp<C>>, ContractError> {
Ok(self.operations(
self.state
.data()
.iter()
.cloned()
.map(OutputAssignment::transmute),
self.data(name, outpoint_filter)?,
witness_filter,
))
}

pub fn wrap<W: IfaceWrapper>(self) -> W { W::from(self) }
}

Expand Down
Loading

0 comments on commit fa8d1ad

Please sign in to comment.