Skip to content

Commit 0bfe36d

Browse files
committed
epic: improve invoice and interface state APIs
1 parent 624e84c commit 0bfe36d

14 files changed

+406
-312
lines changed

Diff for: Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: invoice/src/builder.rs

+94-13
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
use std::str::FromStr;
2323

24-
use rgb::{AttachId, ContractId, State};
24+
use rgb::{AttachId, ContractId, State, StateData};
2525
use strict_encoding::{FieldName, StrictSerialize, TypeName};
2626

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

53-
pub fn rgb20(contract_id: ContractId, beneficiary: impl Into<XChainNet<Beneficiary>>) -> Self {
54-
Self::with(contract_id, beneficiary).set_interface("RGB20")
55-
}
56-
57-
pub fn rgb20_anything(beneficiary: impl Into<XChainNet<Beneficiary>>) -> Self {
58-
Self::new(beneficiary).set_interface("RGB20")
59-
}
60-
6153
pub fn set_contract(mut self, contract_id: ContractId) -> Self {
6254
self.0.contract = Some(contract_id);
6355
self
6456
}
6557

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

81-
pub fn set_state(mut self, state: impl StrictSerialize) -> Self {
82-
self.0.owned_state = InvoiceState::Specific(State::new(state));
75+
/// Set the invoiced state, which includes both state data and an optional attachment
76+
/// information.
77+
///
78+
/// # Panics
79+
///
80+
/// If any state information or attachment requirements are already present in the invoice.
81+
///
82+
/// # See also
83+
///
84+
/// - [`Self::add_state_data`], adding just state data information, not affecting attachment
85+
/// requirements;
86+
/// - [`Self::serialize_state_data`], for adding state data by serializing them from a state
87+
/// object;
88+
/// - [`Self::add_attachment`], for adding attachment requirement to an existing invoiced state
89+
/// information.
90+
pub fn set_state(mut self, state: State) -> Self {
91+
if !self.0.owned_state.is_any() {
92+
panic!("invoice already has state information");
93+
}
94+
self.0.owned_state = InvoiceState::Specific(state);
95+
self
96+
}
97+
98+
/// Add state data to the invoice.
99+
///
100+
/// NB: This keeps existing attachment requirements.
101+
///
102+
/// # Panics
103+
///
104+
/// If the invoice already have any state information (excluding attachment requirements).
105+
///
106+
/// # See also
107+
///
108+
/// - [`Self::set_state`], for redefining the whole of the invoiced state, including attachment
109+
/// requirements;
110+
/// - [`Self::serialize_state_data`], for adding state data by serializing them from a state
111+
/// object;
112+
/// - [`Self::add_attachment`], for adding attachment requirement to an existing invoiced state
113+
/// information.
114+
pub fn add_state_data(mut self, data: StateData) -> Self {
115+
self.0.owned_state = match self.0.owned_state {
116+
InvoiceState::Any => InvoiceState::Specific(State::from(data)),
117+
InvoiceState::Specific(_) => panic!("invoice already has state information"),
118+
InvoiceState::Attach(attach_id) => InvoiceState::Specific(State::with(data, attach_id)),
119+
};
120+
self
121+
}
122+
123+
/// Add state data to the invoice by strict-serializing the provided object.
124+
///
125+
/// NB: This keeps existing attachment requirements.
126+
///
127+
/// Use the function carefully, since the common pitfall here is to perform double serialization
128+
/// of an already serialized data type, like `SmallBlob`. This produces an invalid state object
129+
/// which can't be properly parsed later.
130+
///
131+
/// # Panics
132+
///
133+
/// If the invoice already has any state information (excluding attachment requirements).
134+
///
135+
/// # See also
136+
///
137+
/// - [`Self::set_state`], for redefining the whole of the invoiced state, including attachment
138+
/// requirements;
139+
/// - [`Self::add_state_data`], adding just state data information, not affecting attachment
140+
/// requirements;
141+
/// - [`Self::add_attachment`], for adding attachment requirement to an existing invoiced state
142+
/// information.
143+
pub fn serialize_state_data(mut self, data: impl StrictSerialize) -> Self {
144+
self.0.owned_state = InvoiceState::Specific(State::from_serialized(data));
83145
self
84146
}
85147

86-
pub fn set_attachment(mut self, attach_id: AttachId) -> Result<Self, Self> {
148+
/// Add attachment requirements to an invoice, keeping the rest of the invoice state information
149+
/// unchanged.
150+
///
151+
/// # Panics
152+
///
153+
/// If the invoice already has attachment requirements defined.
154+
///
155+
/// # See also
156+
///
157+
/// - [`Self::set_state`], for redefining the whole of the invoiced state, including attachment
158+
/// requirements;
159+
/// - [`Self::add_state_data`], adding just state data information, not affecting attachment
160+
/// requirements;
161+
/// - [`Self::serialize_state_data`], for adding state data by serializing them from a state
162+
/// object;
163+
pub fn add_attachment(mut self, attach_id: AttachId) -> Result<Self, Self> {
87164
self.0.owned_state = match self.0.owned_state {
88-
InvoiceState::Any | InvoiceState::Attach(_) => InvoiceState::Attach(attach_id),
165+
InvoiceState::Any => InvoiceState::Attach(attach_id),
166+
InvoiceState::Attach(_)
167+
| InvoiceState::Specific(State {
168+
attach: Some(_), ..
169+
}) => panic!("invoice already has attachment requirements"),
89170
InvoiceState::Specific(mut state) => {
90171
state.attach = Some(attach_id);
91172
InvoiceState::Specific(state)

Diff for: invoice/src/parse.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ impl Display for InvoiceState {
181181
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
182182
match self {
183183
InvoiceState::Any => Ok(()),
184-
InvoiceState::Specific(state) => f.write_str(&state.value.to_base58()),
184+
InvoiceState::Specific(state) => f.write_str(&state.data.to_base58()),
185185
// TODO: Support attachment through invoice params
186186
InvoiceState::Attach(_) => Ok(()),
187187
}

Diff for: src/interface/builder.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,7 @@ impl<Seal: ExposedSeal> OperationBuilder<Seal> {
653653
.assignments_type(&name)
654654
.ok_or(BuilderError::AssignmentNotFound(name))?;
655655

656-
self.add_owned_state_raw(type_id, seal, State::new(value))
656+
self.add_owned_state_raw(type_id, seal, State::from_serialized(value))
657657
}
658658

659659
fn complete(self) -> (Schema, Iface, IfaceImpl, GlobalState, Assignments<Seal>, TypeSystem) {

Diff for: src/interface/calc.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ impl StateCalc {
106106
self.vm.registers.set_n(RegA::A8, Reg32::Reg0, Some(0u8));
107107
self.vm
108108
.registers
109-
.set_s(RegS::from(0), Some(ByteStr::with(&state.value)));
109+
.set_s(RegS::from(0), Some(ByteStr::with(&state.data)));
110110
self.vm.registers.set_n(
111111
RegR::R256,
112112
Reg32::Reg0,
@@ -119,7 +119,7 @@ impl StateCalc {
119119
ty: AssignmentType,
120120
idx: Reg16,
121121
) -> Result<Option<rgb::State>, StateCalcError> {
122-
let Some(value) = self.vm.registers.get_s(RegS::from(u4::from(idx))) else {
122+
let Some(data) = self.vm.registers.get_s(RegS::from(u4::from(idx))) else {
123123
return Ok(None);
124124
};
125125
let reserved = self
@@ -138,7 +138,7 @@ impl StateCalc {
138138
}
139139
Ok(Some(rgb::State {
140140
reserved: none!(),
141-
value: StateData::from_checked(value.to_vec()),
141+
data: StateData::from_checked(data.to_vec()),
142142
attach,
143143
}))
144144
}

Diff for: src/interface/contract.rs

+39-26
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ use std::borrow::Borrow;
2323
use std::collections::{BTreeSet, HashMap, HashSet};
2424

2525
use amplify::confinement;
26-
use amplify::confinement::SmallBlob;
2726
use rgb::validation::Scripts;
2827
use rgb::{
29-
AssignmentType, AttachId, ContractId, OpId, Opout, Schema, State, XOutputSeal, XWitnessId,
28+
AssignmentType, AttachId, ContractId, OpId, Opout, Schema, State, StateData, XOutputSeal,
29+
XWitnessId,
3030
};
3131
use strict_encoding::{FieldName, SerializeError, StrictSerialize};
3232
use strict_types::{typify, SemId, StrictVal, TypeSystem};
@@ -155,33 +155,52 @@ impl<S: ContractStateRead> ContractIface<S> {
155155
.sem_id
156156
}
157157

158-
fn assignment_value(&self, ty: AssignmentType, value: &SmallBlob) -> StrictVal {
159-
self.types
160-
.strict_deserialize_type(self.assignment_sem_id(ty), value.as_slice())
161-
.expect("invalid contract state")
162-
.unbox()
163-
}
164-
165158
fn allocation_to_output(&self, a: &Allocation) -> Output {
166159
Output {
167160
opout: a.opout,
168161
seal: a.seal,
169-
state: self.assignment_value(a.opout.ty, &a.state.value),
162+
state: self.value_from_state_raw(a.opout.ty, &a.state),
170163
attach_id: a.state.attach,
171164
witness: a.witness,
172165
}
173166
}
174167

175-
fn state_convert(
168+
pub fn value_from_state_raw(&self, ty: AssignmentType, state: &State) -> StrictVal {
169+
self.types
170+
.strict_deserialize_type(self.assignment_sem_id(ty), state.data.as_slice())
171+
.expect("invalid contract state")
172+
.unbox()
173+
}
174+
175+
pub fn value_from_state(
176+
&self,
177+
name: impl Into<FieldName>,
178+
state: &State,
179+
) -> Result<StrictVal, ContractError> {
180+
let type_id = self.assignment_type(name)?;
181+
Ok(self.value_from_state_raw(type_id, state))
182+
}
183+
184+
pub fn value_to_state_raw(
176185
&self,
177186
ty: AssignmentType,
178187
value: StrictVal,
179-
) -> Result<SmallBlob, ContractError> {
188+
) -> Result<StateData, ContractError> {
180189
let t = self.types.typify(value, self.assignment_sem_id(ty))?;
181-
Ok(self
190+
let value = self
182191
.types
183192
.strict_serialize_type::<{ confinement::U16 }>(&t)?
184-
.to_strict_serialized()?)
193+
.to_strict_serialized()?;
194+
Ok(value.into())
195+
}
196+
197+
pub fn value_to_state(
198+
&self,
199+
name: impl Into<FieldName>,
200+
value: StrictVal,
201+
) -> Result<StateData, ContractError> {
202+
let type_id = self.assignment_type(name)?;
203+
self.value_to_state_raw(type_id, value)
185204
}
186205

187206
pub fn contract_id(&self) -> ContractId { self.state.contract_id() }
@@ -248,9 +267,8 @@ impl<S: ContractStateRead> ContractIface<S> {
248267
&'c self,
249268
name: impl Into<FieldName>,
250269
filter: impl AssignmentsFilter + 'c,
251-
value: StrictVal,
252-
attach: Option<AttachId>,
253270
sorting: impl FnMut(&&Allocation) -> K,
271+
state: &'c State,
254272
) -> Result<impl Iterator<Item = Output> + 'c, ContractError> {
255273
let type_id = self.assignment_type(name)?;
256274
let mut selected = self
@@ -259,18 +277,13 @@ impl<S: ContractStateRead> ContractIface<S> {
259277
.collect::<Vec<_>>();
260278
selected.sort_by_key(sorting);
261279
let mut calc = StateCalc::new(self.scripts.clone(), self.iface.state_abi);
262-
let state = State {
263-
reserved: none!(),
264-
value: self.state_convert(type_id, value)?.into(),
265-
attach,
266-
};
267280
Ok(selected
268281
.into_iter()
269282
.take_while(move |a| {
270283
if calc.reg_input(a.opout.ty, &a.state).is_err() {
271284
return false;
272285
}
273-
calc.is_sufficient_for(a.opout.ty, &state)
286+
calc.is_sufficient_for(a.opout.ty, state)
274287
})
275288
.map(|a| self.allocation_to_output(a)))
276289
}
@@ -314,7 +327,7 @@ impl<S: ContractStateRead> ContractIface<S> {
314327
// add allocations with no witness to the beginning of the history
315328
if let Some(genesis_state) = allocations_our_outpoint.remove(&None) {
316329
for assignment in genesis_state {
317-
let value = self.assignment_value(assignment.opout.ty, &assignment.state.value);
330+
let value = self.value_from_state_raw(assignment.opout.ty, &assignment.state);
318331
ops.push(ContractOp::issued(assignment, value))
319332
}
320333
}
@@ -340,15 +353,15 @@ impl<S: ContractStateRead> ContractIface<S> {
340353
}
341354
for assignment in ext_assignments {
342355
let value =
343-
self.assignment_value(assignment.opout.ty, &assignment.state.value);
356+
self.value_from_state_raw(assignment.opout.ty, &assignment.state);
344357
ops.push(ContractOp::sent(assignment, value, witness_info))
345358
}
346359
}
347360
// the same as above, but the payment has no change
348361
(None, Some(ext_assignments)) => {
349362
for assignment in ext_assignments {
350363
let value =
351-
self.assignment_value(assignment.opout.ty, &assignment.state.value);
364+
self.value_from_state_raw(assignment.opout.ty, &assignment.state);
352365
ops.push(ContractOp::sent(assignment, value, witness_info))
353366
}
354367
}
@@ -357,7 +370,7 @@ impl<S: ContractStateRead> ContractIface<S> {
357370
(Some(our_assignments), None) => {
358371
for assignment in our_assignments {
359372
let value =
360-
self.assignment_value(assignment.opout.ty, &assignment.state.value);
373+
self.value_from_state_raw(assignment.opout.ty, &assignment.state);
361374
ops.push(ContractOp::received(assignment, value, witness_info))
362375
}
363376
}

Diff for: src/stl.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ pub const LIB_NAME_RGB_STORAGE: &str = "RGBStorage";
3737
/// Strict types id for the library providing standard data types which may be
3838
/// used in RGB smart contracts.
3939
pub const LIB_ID_RGB_STORAGE: &str =
40-
"stl:lnl6QOG0-EYfOLKP-MHdEyA3-$cyUNuc-F3XmU!W-0glc1M0#alaska-phone-bagel";
40+
"stl:dYzM3Ct9-SJ8PljY-C!6hB6y-VlWvDE7-DAtNg3y-zanf6hE#media-fresh-cadet";
4141

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

4646
#[allow(dead_code)]
4747
#[derive(Debug, From)]

0 commit comments

Comments
 (0)