From 1a1ad23a0ee94e6b9ea02a3568bc5742f95a105c Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Thu, 17 Oct 2024 15:09:08 +0200 Subject: [PATCH] further invoice and interface state improvements --- Cargo.lock | 10 +-- invoice/Cargo.toml | 1 - invoice/src/builder.rs | 111 ++++------------------------ invoice/src/invoice.rs | 32 +------- invoice/src/lib.rs | 3 +- invoice/src/parse.rs | 155 ++++++++++++++------------------------- src/interface/builder.rs | 149 +++++++++++++++++++++++++------------ src/persistence/stock.rs | 25 +++---- 8 files changed, 190 insertions(+), 296 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 80e12cd3..2420f764 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -149,12 +149,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "base58" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" - [[package]] name = "base64" version = "0.22.1" @@ -674,11 +668,12 @@ dependencies = [ [[package]] name = "rgb-core" version = "0.11.0-beta.9" -source = "git+https://github.com/RGB-WG/rgb-core?branch=feat/fungible-nonconf#75424505e4f24df90d9ee1d284c4ee25103c185b" +source = "git+https://github.com/RGB-WG/rgb-core?branch=feat/fungible-nonconf#b59f1ca4c13c62ce53152f7f4c34094e435a1be4" dependencies = [ "aluvm", "amplify", "baid64", + "base64", "bp-core", "chrono", "commit_verify", @@ -696,7 +691,6 @@ version = "0.11.0-beta.9" dependencies = [ "amplify", "baid64", - "base58", "bp-core", "bp-invoice", "fluent-uri", diff --git a/invoice/Cargo.toml b/invoice/Cargo.toml index ff70bc69..60607596 100644 --- a/invoice/Cargo.toml +++ b/invoice/Cargo.toml @@ -17,7 +17,6 @@ name = "rgbinvoice" [dependencies] amplify = { workspace = true } -base58 = "0.2.0" baid64 = { workspace = true } strict_encoding = { workspace = true } bp-core = { workspace = true } diff --git a/invoice/src/builder.rs b/invoice/src/builder.rs index 1805ee9e..9d84fb61 100644 --- a/invoice/src/builder.rs +++ b/invoice/src/builder.rs @@ -21,11 +21,10 @@ use std::str::FromStr; -use rgb::{AttachId, ContractId, State, StateData}; -use strict_encoding::{FieldName, StrictSerialize, TypeName}; +use rgb::{ContractId, StateData}; +use strict_encoding::{FieldName, SerializeError, StrictSerialize, TypeName}; -use crate::invoice::{Beneficiary, InvoiceState, RgbInvoice, RgbTransport, XChainNet}; -use crate::TransportParseError; +use crate::{Beneficiary, RgbInvoice, RgbTransport, TransportParseError, XChainNet}; #[derive(Clone, Eq, PartialEq, Debug)] pub struct RgbInvoiceBuilder(RgbInvoice); @@ -40,7 +39,7 @@ impl RgbInvoiceBuilder { operation: None, assignment: None, beneficiary: beneficiary.into(), - owned_state: InvoiceState::Any, + state: None, expiry: None, unknown_query: none!(), }) @@ -72,106 +71,26 @@ impl RgbInvoiceBuilder { self } - /// Set the invoiced state, which includes both state data and an optional attachment - /// information. - /// - /// # Panics - /// - /// If any state information or attachment requirements are already present in the invoice. - /// - /// # See also - /// - /// - [`Self::add_state_data`], adding just state data information, not affecting attachment - /// requirements; - /// - [`Self::serialize_state_data`], for adding state data by serializing them from a state - /// object; - /// - [`Self::add_attachment`], for adding attachment requirement to an existing invoiced state - /// information. - pub fn set_state(mut self, state: State) -> Self { - if !self.0.owned_state.is_any() { - panic!("invoice already has state information"); - } - self.0.owned_state = InvoiceState::Specific(state); - self - } - /// Add state data to the invoice. /// - /// NB: This keeps existing attachment requirements. - /// - /// # Panics - /// - /// If the invoice already have any state information (excluding attachment requirements). - /// - /// # See also - /// - /// - [`Self::set_state`], for redefining the whole of the invoiced state, including attachment - /// requirements; - /// - [`Self::serialize_state_data`], for adding state data by serializing them from a state - /// object; - /// - [`Self::add_attachment`], for adding attachment requirement to an existing invoiced state - /// information. - pub fn add_state_data(mut self, data: StateData) -> Self { - self.0.owned_state = match self.0.owned_state { - InvoiceState::Any => InvoiceState::Specific(State::from(data)), - InvoiceState::Specific(_) => panic!("invoice already has state information"), - InvoiceState::Attach(attach_id) => InvoiceState::Specific(State::with(data, attach_id)), - }; + /// See also [`Self::serialize_state_data`], which adds state data by serializing them from a + /// state object. + pub fn set_state(mut self, data: StateData) -> Self { + self.0.state = Some(data); self } /// Add state data to the invoice by strict-serializing the provided object. /// - /// NB: This keeps existing attachment requirements. - /// /// Use the function carefully, since the common pitfall here is to perform double serialization /// of an already serialized data type, like `SmallBlob`. This produces an invalid state object - /// which can't be properly parsed later. - /// - /// # Panics - /// - /// If the invoice already has any state information (excluding attachment requirements). - /// - /// # See also - /// - /// - [`Self::set_state`], for redefining the whole of the invoiced state, including attachment - /// requirements; - /// - [`Self::add_state_data`], adding just state data information, not affecting attachment - /// requirements; - /// - [`Self::add_attachment`], for adding attachment requirement to an existing invoiced state - /// information. - pub fn serialize_state_data(mut self, data: impl StrictSerialize) -> Self { - self.0.owned_state = InvoiceState::Specific(State::from_serialized(data)); - self - } - - /// Add attachment requirements to an invoice, keeping the rest of the invoice state information - /// unchanged. - /// - /// # Panics - /// - /// If the invoice already has attachment requirements defined. - /// - /// # See also - /// - /// - [`Self::set_state`], for redefining the whole of the invoiced state, including attachment - /// requirements; - /// - [`Self::add_state_data`], adding just state data information, not affecting attachment - /// requirements; - /// - [`Self::serialize_state_data`], for adding state data by serializing them from a state - /// object; - pub fn add_attachment(mut self, attach_id: AttachId) -> Result { - self.0.owned_state = match self.0.owned_state { - InvoiceState::Any => InvoiceState::Attach(attach_id), - InvoiceState::Attach(_) - | InvoiceState::Specific(State { - attach: Some(_), .. - }) => panic!("invoice already has attachment requirements"), - InvoiceState::Specific(mut state) => { - state.attach = Some(attach_id); - InvoiceState::Specific(state) - } - }; + /// which can't be properly parsed later. See also [`Self::set_state`], which sets state data + /// directly with no serialization. + pub fn serialize_state_data( + mut self, + data: &impl StrictSerialize, + ) -> Result { + self.0.state = Some(StateData::from_serialized(data)?); Ok(self) } diff --git a/invoice/src/invoice.rs b/invoice/src/invoice.rs index f48f5b75..41f1720d 100644 --- a/invoice/src/invoice.rs +++ b/invoice/src/invoice.rs @@ -24,7 +24,7 @@ use bp::seals::txout::CloseMethod; use bp::{InvalidPubkey, OutputPk, PubkeyHash, ScriptHash, WPubkeyHash, WScriptHash}; use indexmap::IndexMap; use invoice::{AddressNetwork, AddressPayload, Network}; -use rgb::{AttachId, ContractId, Layer1, SecretSeal, State}; +use rgb::{ContractId, Layer1, SecretSeal, StateData}; use strict_encoding::{FieldName, TypeName}; #[derive(Clone, Eq, PartialEq, Hash, Debug)] @@ -37,33 +37,6 @@ pub enum RgbTransport { UnspecifiedMeans, } -#[derive(Clone, Eq, PartialEq, Hash, Debug)] -pub enum InvoiceState { - Any, - Specific(State), - Attach(AttachId), -} - -impl InvoiceState { - pub fn is_any(&self) -> bool { matches!(self, InvoiceState::Any) } - - pub fn state(&self) -> Option<&State> { - match self { - InvoiceState::Any => None, - InvoiceState::Specific(s) => Some(s), - InvoiceState::Attach(_) => None, - } - } - - pub fn attach_id(&self) -> Option { - match self { - InvoiceState::Any => None, - InvoiceState::Specific(s) => s.attach, - InvoiceState::Attach(id) => Some(*id), - } - } -} - #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)] #[non_exhaustive] pub enum ChainNet { @@ -237,9 +210,10 @@ pub struct RgbInvoice { pub operation: Option, pub assignment: Option, pub beneficiary: XChainNet, - pub owned_state: InvoiceState, + pub state: Option, /// UTC unix timestamp pub expiry: Option, + // Attachment requirements should go here pub unknown_query: IndexMap, } diff --git a/invoice/src/lib.rs b/invoice/src/lib.rs index d2820bc8..9ffe2999 100644 --- a/invoice/src/lib.rs +++ b/invoice/src/lib.rs @@ -37,6 +37,5 @@ pub use builder::RgbInvoiceBuilder; pub use parse::{InvoiceParseError, TransportParseError}; pub use crate::invoice::{ - Beneficiary, ChainNet, InvoiceState, Pay2Vout, Pay2VoutError, RgbInvoice, RgbTransport, - XChainNet, + Beneficiary, ChainNet, Pay2Vout, Pay2VoutError, RgbInvoice, RgbTransport, XChainNet, }; diff --git a/invoice/src/parse.rs b/invoice/src/parse.rs index 49a49dca..3ae6b546 100644 --- a/invoice/src/parse.rs +++ b/invoice/src/parse.rs @@ -24,21 +24,16 @@ use std::io::{Cursor, Write}; use std::num::ParseIntError; use std::str::FromStr; -use amplify::confinement::{self, SmallBlob}; -use amplify::Wrapper; use baid64::{Baid64ParseError, DisplayBaid64, FromBaid64Str}; -use base58::{FromBase58, ToBase58}; use fluent_uri::enc::EStr; use fluent_uri::Uri; use indexmap::IndexMap; use invoice::{AddressPayload, UnknownNetwork}; use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; -use rgb::{ContractId, SecretSeal, State, StateData}; +use rgb::{ContractId, SecretSeal, StateData, StateParseError}; use strict_encoding::{InvalidRString, TypeName}; -use crate::invoice::{ - Beneficiary, ChainNet, InvoiceState, Pay2Vout, RgbInvoice, RgbTransport, XChainNet, -}; +use crate::invoice::{Beneficiary, ChainNet, Pay2Vout, RgbInvoice, RgbTransport, XChainNet}; const OMITTED: &str = "~"; const EXPIRY: &str = "expiry"; @@ -68,22 +63,6 @@ pub enum TransportParseError { InvalidTransportHost(String), } -#[derive(Debug, Display, Error, From)] -#[display(doc_comments)] -pub enum InvoiceStateError { - #[from] - /// invalid invoice state Base58 encoding. - Base58(base58::FromBase58Error), - - #[from] - /// invoice state size exceeded. - Len(confinement::Error), - - #[from] - /// invalid invoice state encoding - {0} - Deserialize(strict_encoding::DeserializeError), -} - #[derive(Debug, Display, Error, From)] #[display(doc_comments)] pub enum InvoiceParseError { @@ -112,7 +91,7 @@ pub enum InvoiceParseError { #[from] #[display(inner)] - InvalidState(InvoiceStateError), + InvalidState(StateParseError), /// no invoice transport has been provided. NoTransport, @@ -177,30 +156,6 @@ impl RgbInvoice { } } -impl Display for InvoiceState { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - InvoiceState::Any => Ok(()), - InvoiceState::Specific(state) => f.write_str(&state.data.to_base58()), - // TODO: Support attachment through invoice params - InvoiceState::Attach(_) => Ok(()), - } - } -} - -impl FromStr for InvoiceState { - type Err = InvoiceStateError; - fn from_str(s: &str) -> Result { - if s.is_empty() { - return Ok(InvoiceState::Any); - } - let data = s.from_base58()?; - let data = SmallBlob::try_from(data)?; - let data = StateData::from_inner(data); - Ok(InvoiceState::Specific(State::from(data))) - } -} - impl Display for RgbTransport { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { @@ -337,7 +292,6 @@ impl FromStr for XChainNet { impl Display for RgbInvoice { fn fmt(&self, f: &mut Formatter) -> fmt::Result { // TODO: Support attachment through invoice params - let amt = self.owned_state.to_string(); if let Some(contract) = self.contract { let id = if f.alternate() { contract.to_string().replace('-', "") @@ -359,8 +313,8 @@ impl Display for RgbInvoice { if let Some(ref assignment_name) = self.assignment { write!(f, "{assignment_name}/")?; } - if !amt.is_empty() { - write!(f, "{amt}+")?; + if let Some(state) = self.state.as_ref() { + write!(f, "{state}+")?; } let beneficiary = if f.alternate() { self.beneficiary.to_string().replace('-', "") @@ -442,9 +396,9 @@ impl FromStr for RgbInvoice { .split_once('+') .map(|(a, b)| (Some(a), Some(b))) .unwrap_or((Some(assignment.as_str()), None)); - let (beneficiary_str, value) = match (beneficiary, state) { - (Some(b), Some(a)) => (b, InvoiceState::from_str(a)?), - (None, Some(b)) => (b, InvoiceState::Any), + let (beneficiary_str, state) = match (beneficiary, state) { + (Some(b), Some(a)) => (b, Some(StateData::from_str(a)?)), + (None, Some(b)) => (b, None), _ => unreachable!(), }; @@ -480,7 +434,7 @@ impl FromStr for RgbInvoice { operation: None, assignment: None, beneficiary, - owned_state: value, + state, expiry, unknown_query: query_params, }) @@ -518,31 +472,22 @@ mod test { fn parse() { // rgb20/rgb25 parameters let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/\ - T5FhUZEHbQu4B+bc:utxob:\ + y----------+bc:utxob:\ zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F"; let invoice = RgbInvoice::from_str(invoice_str).unwrap(); - assert_eq!( - invoice.owned_state, - InvoiceState::Specific(State::from(StateData::from_checked(vec![ - 8, 0, 100, 0, 0, 0, 0, 0, 0, 0 - ]))) - ); + assert_eq!(invoice.state, Some(StateData::from_checked(vec![100, 0, 0, 0, 0, 0, 0, 0]))); assert_eq!(invoice.to_string(), invoice_str); - assert_eq!(format!("{invoice:#}"), invoice_str.replace('-', "")); // rgb21 parameters let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB21/\ - 5QsfkEcyanohXadePHZ+bc:utxob:\ + -p----p---------+bc:utxob:\ zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F"; let invoice = RgbInvoice::from_str(invoice_str).unwrap(); assert_eq!( - invoice.owned_state, - InvoiceState::Specific(State::from(StateData::from_checked(vec![ - 12, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 - ]))) + invoice.state, + Some(StateData::from_checked(vec![1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0])) ); assert_eq!(invoice.to_string(), invoice_str); - assert_eq!(format!("{invoice:#}"), invoice_str.replace('-', "")); // no amount let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/bc:utxob:\ @@ -584,47 +529,51 @@ mod test { Err(InvoiceParseError::InvalidContractId(c)) if c == invalid_contract_id)); // with expiration - let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/BF+bc:utxob:\ - zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?\ - expiry=1682086371"; + let invoice_str = + "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/y----------+bc:utxob:\ + zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?expiry=1682086371"; let invoice = RgbInvoice::from_str(invoice_str).unwrap(); assert_eq!(invoice.to_string(), invoice_str); // bad expiration - let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/BF+bc:utxob:\ + let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/\ + y----------+bc:utxob:\ zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?expiry=six"; let result = RgbInvoice::from_str(invoice_str); assert!(matches!(result, Err(InvoiceParseError::InvalidExpiration(_)))); // with bad query parameter - let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/BF+bc:utxob:\ + let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/\ + y----------+bc:utxob:\ zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?expiry"; let result = RgbInvoice::from_str(invoice_str); assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); // with an unknown query parameter - let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/BF+bc:utxob:\ + let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/\ + y----------+bc:utxob:\ zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?unknown=new"; let invoice = RgbInvoice::from_str(invoice_str).unwrap(); assert_eq!(invoice.to_string(), invoice_str); // with two unknown query parameters - let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/BF+bc:utxob:\ - zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?unknown=new&\ - another=new"; + let invoice_str = + "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/y----------+bc:utxob:\ + zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?unknown=new&another=new"; let invoice = RgbInvoice::from_str(invoice_str).unwrap(); assert_eq!(invoice.to_string(), invoice_str); // with expiration and an unknown query parameter - let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/BF+bc:utxob:\ - zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?\ - expiry=1682086371&unknown=new"; + let invoice_str = + "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/y----------+bc:utxob:\ + zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?expiry=1682086371&unknown=new"; let invoice = RgbInvoice::from_str(invoice_str).unwrap(); assert_eq!(invoice.to_string(), invoice_str); // with an unknown query parameter containing percent-encoded text - let invoice_base = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/BF+bc:\ - utxob:zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?"; + let invoice_base = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/\ + y----------+bc:utxob:\ + zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?"; let query_key_encoded = ":@-%20%23"; let query_key_decoded = ":@- #"; let query_val_encoded = "?/.%26%3D"; @@ -652,26 +601,30 @@ mod test { assert!(matches!(result, Err(InvoiceParseError::InvalidScheme(_)))); // empty transport endpoint specification - let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/BF+bc:utxob:\ + let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/\ + y----------+bc:utxob:\ zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?endpoints="; let result = RgbInvoice::from_str(invoice_str); assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); // invalid transport endpoint specification - let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/BF+bc:utxob:\ + let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/\ + y----------+bc:utxob:\ zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?endpoints=bad"; let result = RgbInvoice::from_str(invoice_str); assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); // invalid transport variant - let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/BF+bc:utxob:\ + let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/\ + y----------+bc:utxob:\ zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?endpoints=rpca:/\ /host.example.com"; let result = RgbInvoice::from_str(invoice_str); assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); // rgb-rpc variant - let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/BF+bc:utxob:\ + let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/\ + y----------+bc:utxob:\ zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?endpoints=rpc://\ host.example.com"; let invoice = RgbInvoice::from_str(invoice_str).unwrap(); @@ -682,7 +635,8 @@ mod test { assert_eq!(invoice.to_string(), invoice_str); // rgb-rpc variant, host containing authentication, "-" characters and port - let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/BF+bc:utxob:\ + let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/\ + y----------+bc:utxob:\ zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?endpoints=rpcs:/\ /user:pass@host-1.ex-ample.com:1234"; let invoice = RgbInvoice::from_str(invoice_str).unwrap(); @@ -693,7 +647,8 @@ mod test { assert_eq!(invoice.to_string(), invoice_str); // rgb-rpc variant, IPv6 host - let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/BF+bc:utxob:\ + let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/\ + y----------+bc:utxob:\ zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?endpoints=rpcs:/\ /%5B2001:db8::1%5D:1234"; let invoice = RgbInvoice::from_str(invoice_str).unwrap(); @@ -704,29 +659,30 @@ mod test { assert_eq!(invoice.to_string(), invoice_str); // rgb-rpc variant with missing host - let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/BF+bc:utxob:\ + let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/\ + y----------+bc:utxob:\ zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?endpoints=rpc://"; let result = RgbInvoice::from_str(invoice_str); assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); // rgb-rpc variant with invalid separator - let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/BF+bc:utxob:\ - zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?endpoints=rpc/\ - host.example.com"; + let invoice_str = + "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/y----------+bc:utxob:\ + zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?endpoints=rpc/host.example.com"; let result = RgbInvoice::from_str(invoice_str); assert!(matches!(result, Err(InvoiceParseError::InvalidQueryParam(_)))); // rgb-rpc variant with invalid transport host specification - let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/BF+bc:utxob:\ - zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?endpoints=rpc://\ - ho]t"; + let invoice_str = + "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/y----------+bc:utxob:\ + zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?endpoints=rpc://ho]t"; let result = RgbInvoice::from_str(invoice_str); assert!(matches!(result, Err(InvoiceParseError::Uri(_)))); // rgb+http variant let invoice_str = "rgb:\ 11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/\ - BF+bc:utxob:zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?endpoints=https://\ + y----------+bc:utxob:zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?endpoints=https://\ host.example.com"; let invoice = RgbInvoice::from_str(invoice_str).unwrap(); let transports = vec![RgbTransport::RestHttp { @@ -737,7 +693,8 @@ mod test { assert_eq!(invoice.to_string(), invoice_str); // rgb+ws variant - let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/BF+bc:utxob:\ + let invoice_str = "rgb:11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/\ + y----------+bc:utxob:\ zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?endpoints=wss://\ host.example.com"; let invoice = RgbInvoice::from_str(invoice_str).unwrap(); @@ -753,7 +710,7 @@ mod test { // multiple transports let invoice_str = "rgb:\ 11Fa!$Dk-rUWXhy8-7H35qXm-pLGGLOo-txBWUgj-tbOaSbI/RGB20/\ - BF+bc:utxob:zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?endpoints=rpcs://\ + y----------+bc:utxob:zlVS28Rb-amM5lih-ONXGACC-IUWD0Y$-0JXcnWZ-MQn8VEI-B39!F?endpoints=rpcs://\ host1.example.com,http://host2.example.com,ws://host3.example.com"; let invoice = RgbInvoice::from_str(invoice_str).unwrap(); let transports = vec![ diff --git a/src/interface/builder.rs b/src/interface/builder.rs index e794dad5..5f444a82 100644 --- a/src/interface/builder.rs +++ b/src/interface/builder.rs @@ -27,13 +27,14 @@ use amplify::confinement::{self, Confined, SmallOrdSet, TinyOrdMap}; use chrono::Utc; use rgb::validation::Scripts; use rgb::{ - validation, AltLayer1, AltLayer1Set, AssignmentType, Assignments, ContractId, ExposedSeal, - Genesis, GenesisSeal, GlobalState, GraphSeal, Identity, Input, Layer1, MetadataError, Opout, - Schema, State, Transition, TransitionType, TypedAssigns, XChain, XOutpoint, + validation, AltLayer1, AltLayer1Set, AssignmentType, Assignments, AttachId, ContractId, + ExposedSeal, Genesis, GenesisSeal, GlobalState, GraphSeal, Identity, Input, Layer1, + MetadataError, Opout, Schema, State, StateData, Transition, TransitionType, TypedAssigns, + XChain, XOutpoint, }; use rgbcore::{GlobalStateSchema, GlobalStateType, MetaType, Metadata, ValencyType}; use strict_encoding::{FieldName, SerializeError, StrictSerialize}; -use strict_types::{decode, SemId, TypeSystem}; +use strict_types::{decode, typify, SemId, StrictVal, TypeSystem}; use crate::containers::{BuilderSeal, ContainerVer, Contract, ValidConsignment}; use crate::interface::resolver::DumbResolver; @@ -59,15 +60,12 @@ pub enum BuilderError { /// assignment `{0}` is not known to the schema. AssignmentNotFound(FieldName), + /// valency `{0}` is not known to the schema. + ValencyNotFound(FieldName), + /// transition `{0}` is not known to the schema. TransitionNotFound(FieldName), - /// unknown owned state name `{0}`. - InvalidStateField(FieldName), - - /// state `{0}` provided to the builder has invalid type. - InvalidStateType(AssignmentType), - /// interface doesn't specify default operation name, thus an explicit /// operation type must be provided with `set_operation_type` method. NoOperationSubtype, @@ -90,6 +88,10 @@ pub enum BuilderError { #[display(inner)] Reify(decode::Error), + #[from] + #[display(inner)] + Typify(typify::Error), + #[from] #[display(inner)] Confinement(confinement::Error), @@ -179,23 +181,28 @@ impl ContractBuilder { } #[inline] - pub fn global_type(&self, name: &FieldName) -> Option { + pub fn meta_type(&self, name: impl Into) -> Result { + self.builder.meta_type(name) + } + + #[inline] + pub fn global_type(&self, name: impl Into) -> Result { self.builder.global_type(name) } #[inline] - pub fn valency_type(&self, name: &FieldName) -> Option { + pub fn valency_type(&self, name: impl Into) -> Result { self.builder.valency_type(name) } + #[inline] + pub fn meta_name(&self, type_id: MetaType) -> &FieldName { self.builder.meta_name(type_id) } + #[inline] pub fn valency_name(&self, type_id: ValencyType) -> &FieldName { self.builder.valency_name(type_id) } - #[inline] - pub fn meta_name(&self, type_id: MetaType) -> &FieldName { self.builder.meta_name(type_id) } - #[inline] pub fn add_metadata( mut self, @@ -232,11 +239,27 @@ impl ContractBuilder { mut self, name: impl Into, seal: impl Into>, - value: impl StrictSerialize, + state: StrictVal, + attach: Option, ) -> Result { let seal = seal.into(); self.check_layer1(seal.layer1())?; - self.builder = self.builder.add_owned_state(name, seal, value)?; + self.builder = self.builder.add_owned_state(name, seal, state, attach)?; + Ok(self) + } + + pub fn serialize_owned_state( + mut self, + name: impl Into, + seal: impl Into>, + value: &impl StrictSerialize, + attach: Option, + ) -> Result { + let seal = seal.into(); + self.check_layer1(seal.layer1())?; + self.builder = self + .builder + .serialize_owned_state(name, seal, value, attach)?; Ok(self) } @@ -419,17 +442,20 @@ impl TransitionBuilder { } #[inline] - pub fn assignments_type(&self, name: &FieldName) -> Option { + pub fn assignments_type( + &self, + name: impl Into, + ) -> Result { self.builder.assignments_type(name) } #[inline] - pub fn global_type(&self, name: &FieldName) -> Option { + pub fn global_type(&self, name: impl Into) -> Result { self.builder.global_type(name) } #[inline] - pub fn valency_type(&self, name: &FieldName) -> Option { + pub fn valency_type(&self, name: impl Into) -> Result { self.builder.valency_type(name) } @@ -440,8 +466,9 @@ impl TransitionBuilder { pub fn meta_name(&self, type_id: MetaType) -> &FieldName { self.builder.meta_name(type_id) } - /// NB: Doesn't process the state with VM - pub fn add_owned_state_raw( + // TODO: We won't need this once we will have Blank Transition builder + /// NB: This does not process the state with VM + pub fn add_owned_state_blank( mut self, type_id: AssignmentType, seal: impl Into>, @@ -546,22 +573,36 @@ impl OperationBuilder { .expect("internal inconsistency") } - fn assignments_type(&self, name: &FieldName) -> Option { - self.iimpl.assignments_type(name) + fn assignments_type(&self, name: impl Into) -> Result { + let name = name.into(); + self.iimpl + .assignments_type(&name) + .ok_or(BuilderError::AssignmentNotFound(name)) } - fn meta_type(&self, name: &FieldName) -> Option { self.iimpl.meta_type(name) } + fn meta_type(&self, name: impl Into) -> Result { + let name = name.into(); + self.iimpl + .meta_type(&name) + .ok_or(BuilderError::MetadataNotFound(name)) + } fn meta_name(&self, ty: MetaType) -> &FieldName { self.iimpl.meta_name(ty).expect("internal inconsistency") } - fn global_type(&self, name: &FieldName) -> Option { - self.iimpl.global_type(name) + fn global_type(&self, name: impl Into) -> Result { + let name = name.into(); + self.iimpl + .global_type(&name) + .ok_or(BuilderError::GlobalNotFound(name)) } - fn valency_type(&self, name: &FieldName) -> Option { - self.iimpl.valency_type(name) + fn valency_type(&self, name: impl Into) -> Result { + let name = name.into(); + self.iimpl + .valency_type(&name) + .ok_or(BuilderError::ValencyNotFound(name)) } fn valency_name(&self, ty: ValencyType) -> &FieldName { @@ -589,13 +630,8 @@ impl OperationBuilder { name: impl Into, value: impl StrictSerialize, ) -> Result { - let name = name.into(); + let type_id = self.meta_type(name)?; let serialized = value.to_strict_serialized::<{ u16::MAX as usize }>()?; - - let Some(type_id) = self.meta_type(&name) else { - return Err(BuilderError::MetadataNotFound(name)); - }; - let sem_id = self.meta_schema(type_id); self.types.strict_deserialize_type(*sem_id, &serialized)?; self.meta.add_value(type_id, serialized.into())?; @@ -607,13 +643,9 @@ impl OperationBuilder { name: impl Into, value: impl StrictSerialize, ) -> Result { - let name = name.into(); + let type_id = self.global_type(name)?; let serialized = value.to_strict_serialized::<{ u16::MAX as usize }>()?; - // Check value matches type requirements - let Some(type_id) = self.global_type(&name) else { - return Err(BuilderError::GlobalNotFound(name)); - }; let sem_id = self.global_schema(type_id).sem_id; self.types.strict_deserialize_type(sem_id, &serialized)?; @@ -645,15 +677,40 @@ impl OperationBuilder { self, name: impl Into, seal: impl Into>, - value: impl StrictSerialize, + value: StrictVal, + attach: Option, ) -> Result { - let name = name.into(); + let type_id = self.assignments_type(name)?; - let type_id = self - .assignments_type(&name) - .ok_or(BuilderError::AssignmentNotFound(name))?; + let types = self.type_system(); + let sem_id = self + .schema + .owned_types + .get(&type_id) + .expect("schema-interface inconsistence") + .sem_id; + let value = types.typify(value, sem_id)?; + let data = types + .strict_serialize_type::<{ confinement::U16 }>(&value)? + .to_strict_serialized()?; + + let mut state = State::from(StateData::from(data)); + state.attach = attach; + self.add_owned_state_raw(type_id, seal, state) + } + + fn serialize_owned_state( + self, + name: impl Into, + seal: impl Into>, + value: &impl StrictSerialize, + attach: Option, + ) -> Result { + let type_id = self.assignments_type(name)?; - self.add_owned_state_raw(type_id, seal, State::from_serialized(value)) + let mut state = State::from_serialized(value)?; + state.attach = attach; + self.add_owned_state_raw(type_id, seal, state) } fn complete(self) -> (Schema, Iface, IfaceImpl, GlobalState, Assignments, TypeSystem) { diff --git a/src/persistence/stock.rs b/src/persistence/stock.rs index 273af067..2793c156 100644 --- a/src/persistence/stock.rs +++ b/src/persistence/stock.rs @@ -885,7 +885,7 @@ impl Stock { #[allow(clippy::result_large_err)] pub fn compose( &self, - invoice: &RgbInvoice, + invoice: RgbInvoice, prev_outputs: impl IntoIterator>, method: CloseMethod, beneficiary_vout: Option>, @@ -908,7 +908,7 @@ impl Stock { #[allow(clippy::too_many_arguments, clippy::result_large_err)] pub fn compose_deterministic( &self, - invoice: &RgbInvoice, + invoice: RgbInvoice, prev_outputs: impl IntoIterator>, method: CloseMethod, beneficiary_vout: Option>, @@ -917,11 +917,8 @@ impl Stock { seal_blinder: impl Fn(ContractId, AssignmentType) -> u64, ) -> Result> { let layer1 = invoice.layer1(); - let invoice_state = invoice - .owned_state - .state() - .ok_or(ComposeError::NoInvoiceState)? - .clone(); + let state_data = invoice.state.ok_or(ComposeError::NoInvoiceState)?; + let state = rgb::State::from(state_data); // TODO: Take account attachements when they will be supported by invoices let prev_outputs = prev_outputs .into_iter() .map(|o| o.into()) @@ -970,9 +967,7 @@ impl Stock { .or_else(|| main_builder.default_assignment().ok()) .ok_or(BuilderError::NoDefaultAssignment)? .clone(); - let assignment_id = main_builder - .assignments_type(&assignment_name) - .ok_or(BuilderError::InvalidStateField(assignment_name.clone()))?; + let assignment_id = main_builder.assignments_type(assignment_name)?; // If there are inputs which are using different seal closing method from our // wallet (and thus main state transition) we need to put them aside and @@ -1019,9 +1014,9 @@ impl Stock { if opout.ty != assignment_id { let seal = output_for_assignment(contract_id, opout.ty)?; if output.method() == method { - main_builder = main_builder.add_owned_state_raw(opout.ty, seal, state)?; + main_builder = main_builder.add_owned_state_blank(opout.ty, seal, state)?; } else { - alt_builder = alt_builder.add_owned_state_raw(opout.ty, seal, state)?; + alt_builder = alt_builder.add_owned_state_blank(opout.ty, seal, state)?; } } } @@ -1029,7 +1024,7 @@ impl Stock { // Add payments to beneficiary let (builder, partial_state) = - main_builder.fulfill_owned_state(assignment_id, beneficiary, invoice_state)?; + main_builder.fulfill_owned_state(assignment_id, beneficiary, state)?; main_builder = builder; if let Some(partial_state) = partial_state { let (builder, remaining_state) = @@ -1080,12 +1075,12 @@ impl Stock { Method::TapretFirst => { blank_builder_tapret = blank_builder_tapret .add_input(opout, state.clone())? - .add_owned_state_raw(opout.ty, seal, state)? + .add_owned_state_blank(opout.ty, seal, state)? } Method::OpretFirst => { blank_builder_opret = blank_builder_opret .add_input(opout, state.clone())? - .add_owned_state_raw(opout.ty, seal, state)? + .add_owned_state_blank(opout.ty, seal, state)? } } }