Skip to content

Commit

Permalink
epic: improve invoice and interface state APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
dr-orlovsky committed Oct 17, 2024
1 parent 624e84c commit 0bfe36d
Show file tree
Hide file tree
Showing 14 changed files with 406 additions and 312 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

107 changes: 94 additions & 13 deletions invoice/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

use std::str::FromStr;

use rgb::{AttachId, ContractId, State};
use rgb::{AttachId, ContractId, State, StateData};
use strict_encoding::{FieldName, StrictSerialize, TypeName};

use crate::invoice::{Beneficiary, InvoiceState, RgbInvoice, RgbTransport, XChainNet};
Expand Down Expand Up @@ -50,19 +50,13 @@ impl RgbInvoiceBuilder {
Self::new(beneficiary).set_contract(contract_id)
}

pub fn rgb20(contract_id: ContractId, beneficiary: impl Into<XChainNet<Beneficiary>>) -> Self {
Self::with(contract_id, beneficiary).set_interface("RGB20")
}

pub fn rgb20_anything(beneficiary: impl Into<XChainNet<Beneficiary>>) -> Self {
Self::new(beneficiary).set_interface("RGB20")
}

pub fn set_contract(mut self, contract_id: ContractId) -> Self {
self.0.contract = Some(contract_id);
self
}

/// Sets interface for the invoice. Interface can be a concrete interface (name or id), or a
/// name of an interface standard, like `RGB20`, `RGB21` etc.
pub fn set_interface(mut self, name: impl Into<TypeName>) -> Self {
self.0.iface = Some(name.into());
self
Expand All @@ -78,14 +72,101 @@ impl RgbInvoiceBuilder {
self
}

pub fn set_state(mut self, state: impl StrictSerialize) -> Self {
self.0.owned_state = InvoiceState::Specific(State::new(state));
/// 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)),
};
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
}

pub fn set_attachment(mut self, attach_id: AttachId) -> Result<Self, 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, Self> {
self.0.owned_state = match self.0.owned_state {
InvoiceState::Any | InvoiceState::Attach(_) => InvoiceState::Attach(attach_id),
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)
Expand Down
2 changes: 1 addition & 1 deletion invoice/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ impl Display for InvoiceState {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
InvoiceState::Any => Ok(()),
InvoiceState::Specific(state) => f.write_str(&state.value.to_base58()),
InvoiceState::Specific(state) => f.write_str(&state.data.to_base58()),
// TODO: Support attachment through invoice params
InvoiceState::Attach(_) => Ok(()),
}
Expand Down
2 changes: 1 addition & 1 deletion src/interface/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ impl<Seal: ExposedSeal> OperationBuilder<Seal> {
.assignments_type(&name)
.ok_or(BuilderError::AssignmentNotFound(name))?;

self.add_owned_state_raw(type_id, seal, State::new(value))
self.add_owned_state_raw(type_id, seal, State::from_serialized(value))
}

fn complete(self) -> (Schema, Iface, IfaceImpl, GlobalState, Assignments<Seal>, TypeSystem) {
Expand Down
6 changes: 3 additions & 3 deletions src/interface/calc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ impl StateCalc {
self.vm.registers.set_n(RegA::A8, Reg32::Reg0, Some(0u8));
self.vm
.registers
.set_s(RegS::from(0), Some(ByteStr::with(&state.value)));
.set_s(RegS::from(0), Some(ByteStr::with(&state.data)));
self.vm.registers.set_n(
RegR::R256,
Reg32::Reg0,
Expand All @@ -119,7 +119,7 @@ impl StateCalc {
ty: AssignmentType,
idx: Reg16,
) -> Result<Option<rgb::State>, StateCalcError> {
let Some(value) = self.vm.registers.get_s(RegS::from(u4::from(idx))) else {
let Some(data) = self.vm.registers.get_s(RegS::from(u4::from(idx))) else {
return Ok(None);
};
let reserved = self
Expand All @@ -138,7 +138,7 @@ impl StateCalc {
}
Ok(Some(rgb::State {
reserved: none!(),
value: StateData::from_checked(value.to_vec()),
data: StateData::from_checked(data.to_vec()),
attach,
}))
}
Expand Down
65 changes: 39 additions & 26 deletions src/interface/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ use std::borrow::Borrow;
use std::collections::{BTreeSet, HashMap, HashSet};

use amplify::confinement;
use amplify::confinement::SmallBlob;
use rgb::validation::Scripts;
use rgb::{
AssignmentType, AttachId, ContractId, OpId, Opout, Schema, State, XOutputSeal, XWitnessId,
AssignmentType, AttachId, ContractId, OpId, Opout, Schema, State, StateData, XOutputSeal,
XWitnessId,
};
use strict_encoding::{FieldName, SerializeError, StrictSerialize};
use strict_types::{typify, SemId, StrictVal, TypeSystem};
Expand Down Expand Up @@ -155,33 +155,52 @@ impl<S: ContractStateRead> ContractIface<S> {
.sem_id
}

fn assignment_value(&self, ty: AssignmentType, value: &SmallBlob) -> StrictVal {
self.types
.strict_deserialize_type(self.assignment_sem_id(ty), value.as_slice())
.expect("invalid contract state")
.unbox()
}

fn allocation_to_output(&self, a: &Allocation) -> Output {
Output {
opout: a.opout,
seal: a.seal,
state: self.assignment_value(a.opout.ty, &a.state.value),
state: self.value_from_state_raw(a.opout.ty, &a.state),
attach_id: a.state.attach,
witness: a.witness,
}
}

fn state_convert(
pub fn value_from_state_raw(&self, ty: AssignmentType, state: &State) -> StrictVal {
self.types
.strict_deserialize_type(self.assignment_sem_id(ty), state.data.as_slice())
.expect("invalid contract state")
.unbox()
}

pub fn value_from_state(
&self,
name: impl Into<FieldName>,
state: &State,
) -> Result<StrictVal, ContractError> {
let type_id = self.assignment_type(name)?;
Ok(self.value_from_state_raw(type_id, state))
}

pub fn value_to_state_raw(
&self,
ty: AssignmentType,
value: StrictVal,
) -> Result<SmallBlob, ContractError> {
) -> Result<StateData, ContractError> {
let t = self.types.typify(value, self.assignment_sem_id(ty))?;
Ok(self
let value = self
.types
.strict_serialize_type::<{ confinement::U16 }>(&t)?
.to_strict_serialized()?)
.to_strict_serialized()?;
Ok(value.into())
}

pub fn value_to_state(
&self,
name: impl Into<FieldName>,
value: StrictVal,
) -> Result<StateData, ContractError> {
let type_id = self.assignment_type(name)?;
self.value_to_state_raw(type_id, value)
}

pub fn contract_id(&self) -> ContractId { self.state.contract_id() }
Expand Down Expand Up @@ -248,9 +267,8 @@ impl<S: ContractStateRead> ContractIface<S> {
&'c self,
name: impl Into<FieldName>,
filter: impl AssignmentsFilter + 'c,
value: StrictVal,
attach: Option<AttachId>,
sorting: impl FnMut(&&Allocation) -> K,
state: &'c State,
) -> Result<impl Iterator<Item = Output> + 'c, ContractError> {
let type_id = self.assignment_type(name)?;
let mut selected = self
Expand All @@ -259,18 +277,13 @@ impl<S: ContractStateRead> ContractIface<S> {
.collect::<Vec<_>>();
selected.sort_by_key(sorting);
let mut calc = StateCalc::new(self.scripts.clone(), self.iface.state_abi);
let state = State {
reserved: none!(),
value: self.state_convert(type_id, value)?.into(),
attach,
};
Ok(selected
.into_iter()
.take_while(move |a| {
if calc.reg_input(a.opout.ty, &a.state).is_err() {
return false;
}
calc.is_sufficient_for(a.opout.ty, &state)
calc.is_sufficient_for(a.opout.ty, state)
})
.map(|a| self.allocation_to_output(a)))
}
Expand Down Expand Up @@ -314,7 +327,7 @@ impl<S: ContractStateRead> ContractIface<S> {
// add allocations with no witness to the beginning of the history
if let Some(genesis_state) = allocations_our_outpoint.remove(&None) {
for assignment in genesis_state {
let value = self.assignment_value(assignment.opout.ty, &assignment.state.value);
let value = self.value_from_state_raw(assignment.opout.ty, &assignment.state);
ops.push(ContractOp::issued(assignment, value))
}
}
Expand All @@ -340,15 +353,15 @@ impl<S: ContractStateRead> ContractIface<S> {
}
for assignment in ext_assignments {
let value =
self.assignment_value(assignment.opout.ty, &assignment.state.value);
self.value_from_state_raw(assignment.opout.ty, &assignment.state);
ops.push(ContractOp::sent(assignment, value, witness_info))
}
}
// the same as above, but the payment has no change
(None, Some(ext_assignments)) => {
for assignment in ext_assignments {
let value =
self.assignment_value(assignment.opout.ty, &assignment.state.value);
self.value_from_state_raw(assignment.opout.ty, &assignment.state);
ops.push(ContractOp::sent(assignment, value, witness_info))
}
}
Expand All @@ -357,7 +370,7 @@ impl<S: ContractStateRead> ContractIface<S> {
(Some(our_assignments), None) => {
for assignment in our_assignments {
let value =
self.assignment_value(assignment.opout.ty, &assignment.state.value);
self.value_from_state_raw(assignment.opout.ty, &assignment.state);
ops.push(ContractOp::received(assignment, value, witness_info))
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/stl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ pub const LIB_NAME_RGB_STORAGE: &str = "RGBStorage";
/// Strict types id for the library providing standard data types which may be
/// used in RGB smart contracts.
pub const LIB_ID_RGB_STORAGE: &str =
"stl:lnl6QOG0-EYfOLKP-MHdEyA3-$cyUNuc-F3XmU!W-0glc1M0#alaska-phone-bagel";
"stl:dYzM3Ct9-SJ8PljY-C!6hB6y-VlWvDE7-DAtNg3y-zanf6hE#media-fresh-cadet";

/// Strict types id for the library representing of RGB StdLib data types.
pub const LIB_ID_RGB_STD: &str =
"stl:yMGmidPl-LcWFyh!-W6sQ3K5-JQ8evpO-BGuI!lA-0htx!kg#chemist-enjoy-sound";
"stl:3QsSNDHr-YPIKTR9-5O!fLuv-xhcxKT2-0LWrO9b-pG15Nvw#mango-inside-shelf";

#[allow(dead_code)]
#[derive(Debug, From)]
Expand Down
Loading

0 comments on commit 0bfe36d

Please sign in to comment.