From c3ef1e3f79eccabc9f8e938c130f931e0013262c Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Tue, 15 Oct 2024 23:22:35 +0200 Subject: [PATCH] iface: make invoice state calculable with AluVM --- src/containers/file.rs | 2 - src/interface/builder.rs | 115 ++++++++++++----------- src/interface/calc.rs | 192 ++++++++++++++++++++++++++++++++++++++ src/interface/contract.rs | 11 --- src/interface/iimpl.rs | 3 +- src/interface/mod.rs | 2 + src/persistence/memory.rs | 18 ++-- src/persistence/stash.rs | 9 ++ src/persistence/stock.rs | 131 ++++++-------------------- 9 files changed, 303 insertions(+), 180 deletions(-) create mode 100644 src/interface/calc.rs diff --git a/src/containers/file.rs b/src/containers/file.rs index 4cd722c0..02a567b8 100644 --- a/src/containers/file.rs +++ b/src/containers/file.rs @@ -273,7 +273,6 @@ mod test { issuer: Default::default(), testnet: Default::default(), alt_layers1: Default::default(), - asset_tags: Default::default(), metadata: Default::default(), globals: Default::default(), assignments: Default::default(), @@ -371,7 +370,6 @@ mod test { issuer: Default::default(), testnet: Default::default(), alt_layers1: Default::default(), - asset_tags: Default::default(), metadata: Default::default(), globals: Default::default(), assignments: Default::default(), diff --git a/src/interface/builder.rs b/src/interface/builder.rs index 7fc6c13e..2ba1d8c3 100644 --- a/src/interface/builder.rs +++ b/src/interface/builder.rs @@ -29,7 +29,7 @@ use rgb::validation::Scripts; use rgb::{ validation, AltLayer1, AltLayer1Set, AssignmentType, Assignments, ContractId, ExposedSeal, Genesis, GenesisSeal, GlobalState, GraphSeal, Identity, Input, Layer1, MetadataError, Opout, - OwnedStateSchema, Schema, State, Transition, TransitionType, TypedAssigns, XChain, XOutpoint, + Schema, State, Transition, TransitionType, TypedAssigns, XChain, XOutpoint, }; use rgbcore::{GlobalStateSchema, GlobalStateType, MetaType, Metadata, ValencyType}; use strict_encoding::{FieldName, SerializeError, StrictSerialize}; @@ -37,7 +37,7 @@ use strict_types::{decode, SemId, TypeSystem}; use crate::containers::{BuilderSeal, ContainerVer, Contract, ValidConsignment}; use crate::interface::resolver::DumbResolver; -use crate::interface::{Iface, IfaceImpl, TransitionIface}; +use crate::interface::{Iface, IfaceImpl, StateCalc, StateCalcError, TransitionIface}; use crate::Outpoint; #[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)] @@ -68,19 +68,7 @@ pub enum BuilderError { /// state `{0}` provided to the builder has invalid type. InvalidStateType(AssignmentType), - /// asset tag for state `{0}` must be added before any fungible state of - /// the same type. - AssetTagMissed(AssignmentType), - - /// asset tag for state `{0}` was already automatically created. Please call - /// `add_asset_tag` before adding any fungible state to the builder. - AssetTagAutomatic(AssignmentType), - - /// state data for state type `{0}` are invalid: asset tag doesn't match the - /// tag defined by the contract. - AssetTagInvalid(AssignmentType), - - /// interface doesn't specifies default operation name, thus an explicit + /// interface doesn't specify default operation name, thus an explicit /// operation type must be provided with `set_operation_type` method. NoOperationSubtype, @@ -90,6 +78,10 @@ pub enum BuilderError { /// {0} is not supported by the contract genesis. InvalidLayer1(Layer1), + #[from] + #[display(inner)] + Calc(StateCalcError), + #[from] #[display(inner)] StrictEncode(SerializeError), @@ -226,13 +218,13 @@ impl ContractBuilder { pub fn add_owned_state_raw( mut self, - name: impl Into, + type_id: AssignmentType, seal: impl Into>, state: State, ) -> Result { let seal = seal.into(); self.check_layer1(seal.layer1())?; - self.builder = self.builder.add_owned_state_raw(name, seal, state)?; + self.builder = self.builder.add_owned_state_raw(type_id, seal, state)?; Ok(self) } @@ -306,13 +298,15 @@ impl ContractBuilder { } } -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct TransitionBuilder { contract_id: ContractId, builder: OperationBuilder, nonce: u64, transition_type: TransitionType, inputs: TinyOrdMap, + // TODO: Remove option once we have blank builder + calc: Option, } impl TransitionBuilder { @@ -323,7 +317,7 @@ impl TransitionBuilder { iimpl: IfaceImpl, types: TypeSystem, ) -> Self { - Self::with(contract_id, iface, schema, iimpl, TransitionType::BLANK, types) + Self::with(contract_id, iface, schema, iimpl, TransitionType::BLANK, types, None) } pub fn default_transition( @@ -332,13 +326,15 @@ impl TransitionBuilder { schema: Schema, iimpl: IfaceImpl, types: TypeSystem, + scripts: Scripts, ) -> Result { let transition_type = iface .default_operation .as_ref() .and_then(|name| iimpl.transition_type(name)) .ok_or(BuilderError::NoOperationSubtype)?; - Ok(Self::with(contract_id, iface, schema, iimpl, transition_type, types)) + let calc = StateCalc::new(scripts, iimpl.state_abi); + Ok(Self::with(contract_id, iface, schema, iimpl, transition_type, types, Some(calc))) } pub fn named_transition( @@ -348,12 +344,14 @@ impl TransitionBuilder { iimpl: IfaceImpl, transition_name: impl Into, types: TypeSystem, + scripts: Scripts, ) -> Result { let transition_name = transition_name.into(); let transition_type = iimpl .transition_type(&transition_name) .ok_or(BuilderError::TransitionNotFound(transition_name))?; - Ok(Self::with(contract_id, iface, schema, iimpl, transition_type, types)) + let calc = StateCalc::new(scripts, iimpl.state_abi); + Ok(Self::with(contract_id, iface, schema, iimpl, transition_type, types, Some(calc))) } fn with( @@ -363,6 +361,7 @@ impl TransitionBuilder { iimpl: IfaceImpl, transition_type: TransitionType, types: TypeSystem, + calc: Option, ) -> Self { Self { contract_id, @@ -370,6 +369,7 @@ impl TransitionBuilder { nonce: u64::MAX, transition_type, inputs: none!(), + calc, } } @@ -403,6 +403,9 @@ impl TransitionBuilder { } pub fn add_input(mut self, opout: Opout, state: State) -> Result { + if let Some(calc) = &mut self.calc { + calc.reg_input(opout.ty, &state)?; + } self.inputs.insert(Input::with(opout), state)?; Ok(self) } @@ -430,39 +433,54 @@ impl TransitionBuilder { self.builder.valency_type(name) } + #[inline] pub fn valency_name(&self, type_id: ValencyType) -> &FieldName { self.builder.valency_name(type_id) } 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( mut self, - name: impl Into, + type_id: AssignmentType, seal: impl Into>, state: State, ) -> Result { - self.builder = self.builder.add_owned_state_raw(name, seal, state)?; + self.builder = self.builder.add_owned_state_raw(type_id, seal, state)?; Ok(self) } - pub fn add_owned_state( + pub fn fulfill_owned_state( mut self, - name: impl Into, + type_id: AssignmentType, seal: impl Into>, - value: impl StrictSerialize, - ) -> Result { - self.builder = self.builder.add_owned_state(name, seal, value)?; - Ok(self) - } - - pub fn add_owned_state_default( - self, + state: State, + ) -> Result<(Self, Option), BuilderError> { + let calc = self + .calc + .as_mut() + .expect("you must not call fulfill_owned_state for the blank transition builder"); + let state = calc.calc_output(type_id, &state)?; + self.builder = self + .builder + .add_owned_state_raw(type_id, seal, state.sufficient)?; + Ok((self, state.insufficient)) + } + + pub fn add_owned_state_change( + mut self, + type_id: AssignmentType, seal: impl Into>, - value: impl StrictSerialize, ) -> Result { - let assignment_name = self.default_assignment()?.clone(); - self.add_owned_state(assignment_name, seal.into(), value) + let calc = self + .calc + .as_mut() + .expect("you must not call add_owned_state_change for the blank transition builder"); + if let Some(state) = calc.calc_change(type_id)? { + self.builder = self.builder.add_owned_state_raw(type_id, seal, state)?; + } + Ok(self) } pub fn has_inputs(&self) -> bool { !self.inputs.is_empty() } @@ -514,7 +532,6 @@ impl OperationBuilder { global: none!(), assignments: none!(), meta: none!(), - types, } } @@ -551,14 +568,6 @@ impl OperationBuilder { self.iimpl.valency_name(ty).expect("internal inconsistency") } - #[inline] - fn state_schema(&self, type_id: AssignmentType) -> &OwnedStateSchema { - self.schema - .owned_types - .get(&type_id) - .expect("schema should match interface: must be checked by the constructor") - } - #[inline] fn meta_schema(&self, type_id: MetaType) -> &SemId { self.schema @@ -615,16 +624,10 @@ impl OperationBuilder { fn add_owned_state_raw( mut self, - name: impl Into, + type_id: AssignmentType, seal: impl Into>, state: State, ) -> Result { - let name = name.into(); - - let type_id = self - .assignments_type(&name) - .ok_or(BuilderError::AssignmentNotFound(name))?; - let assignment = seal.into().assignment(state); match self.assignments.entry(type_id)? { @@ -644,7 +647,13 @@ impl OperationBuilder { seal: impl Into>, value: impl StrictSerialize, ) -> Result { - self.add_owned_state_raw(name, seal, State::new(value)) + let name = name.into(); + + let type_id = self + .assignments_type(&name) + .ok_or(BuilderError::AssignmentNotFound(name))?; + + self.add_owned_state_raw(type_id, seal, State::new(value)) } fn complete(self) -> (Schema, Iface, IfaceImpl, GlobalState, Assignments, TypeSystem) { diff --git a/src/interface/calc.rs b/src/interface/calc.rs new file mode 100644 index 00000000..50a6fd50 --- /dev/null +++ b/src/interface/calc.rs @@ -0,0 +1,192 @@ +// RGB standard library for working with smart contracts on Bitcoin & Lightning +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2024 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use aluvm::data::ByteStr; +use aluvm::library::{LibId, LibSite}; +use aluvm::reg::{Reg16, Reg32, RegA, RegR, RegS}; +use amplify::num::{u256, u4}; +use amplify::{ByteArray, Wrapper}; +use rgb::validation::Scripts; +use rgb::{AssignmentType, AttachId, StateData}; + +use crate::LIB_NAME_RGB_STD; + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display, Error)] +#[display(doc_comments)] +pub enum StateCalcError { + /// reserved byte value {0} is not 0x00 for assignment type {1}. + InvalidReserved(AssignmentType, u8), + /// error registering input state of type {0} - {1}. + InputReg(AssignmentType, String), + /// error registering output state of type {0} - {1}. + OutputReg(AssignmentType, String), + /// error computing output state of type {0} - {1}. + OutputCalc(AssignmentType, String), + /// error computing change state of type {0} - {1}. + ChangeCalc(AssignmentType, String), + /// failed script for calculating output state of type {0}; please update interface + /// implementation for the schema + InsufficientState(AssignmentType), +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_RGB_STD)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +pub struct StateAbi { + pub reg_input: LibSite, + pub reg_output: LibSite, + pub calc_output: LibSite, + pub calc_change: LibSite, +} + +impl StateAbi { + pub fn lib_ids(&self) -> impl Iterator { + [self.reg_input, self.reg_output, self.calc_output, self.calc_change] + .into_iter() + .map(|site| site.lib) + } +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct AllocatedState { + pub sufficient: rgb::State, + pub insufficient: Option, +} + +#[derive(Debug)] +pub struct StateCalc { + vm: aluvm::Vm, + abi: StateAbi, + scripts: Scripts, +} + +impl StateCalc { + pub fn new(scripts: Scripts, abi: StateAbi) -> Self { + let vm = aluvm::Vm::new(); + Self { vm, abi, scripts } + } + + fn run(&mut self, site: LibSite) -> Result<(), String> { + if !self.vm.exec(site, |id| self.scripts.get(&id), &()) { + if let Some(err) = self.vm.registers.get_s(RegS::from(15)).cloned() { + return Err(err.to_string()); + } + } + Ok(()) + } + + fn put_state(&mut self, ty: AssignmentType, state: &rgb::State) { + self.vm + .registers + .set_n(RegA::A16, Reg32::Reg0, Some(ty.to_inner())); + assert_eq!(state.reserved, none!()); + self.vm.registers.set_n(RegA::A8, Reg32::Reg0, Some(0u8)); + self.vm + .registers + .set_s(RegS::from(0), Some(ByteStr::with(&state.value))); + self.vm.registers.set_n( + RegR::R256, + Reg32::Reg0, + state.attach.map(|a| u256::from_le_bytes(a.to_byte_array())), + ); + } + + fn fetch_state( + &self, + ty: AssignmentType, + idx: Reg16, + ) -> Result, StateCalcError> { + let Some(value) = self.vm.registers.get_s(RegS::from(u4::from(idx))) else { + return Ok(None); + }; + let reserved = self + .vm + .registers + .get_n(RegA::A8, idx) + .map(|n| u8::from(n)) + .unwrap_or_default(); + let attach = self + .vm + .registers + .get_n(RegR::R256, idx) + .map(|n| AttachId::from_byte_array(u256::from(n).to_le_bytes())); + if reserved != 0x00 { + return Err(StateCalcError::InvalidReserved(ty, reserved)); + } + Ok(Some(rgb::State { + reserved: none!(), + value: StateData::from_checked(value.to_vec()), + attach, + })) + } + + pub fn reg_input( + &mut self, + ty: AssignmentType, + state: &rgb::State, + ) -> Result<(), StateCalcError> { + self.put_state(ty, state); + self.run(self.abi.reg_input) + .map_err(|err| StateCalcError::InputReg(ty, err)) + } + + pub fn reg_output( + &mut self, + ty: AssignmentType, + state: &rgb::State, + ) -> Result<(), StateCalcError> { + self.put_state(ty, state); + self.run(self.abi.reg_output) + .map_err(|err| StateCalcError::OutputReg(ty, err)) + } + + pub fn calc_output( + &mut self, + ty: AssignmentType, + state: &rgb::State, + ) -> Result { + self.put_state(ty, state); + self.run(self.abi.calc_output) + .map_err(|err| StateCalcError::OutputCalc(ty, err))?; + let Some(sufficient) = self.fetch_state(ty, Reg16::Reg0)? else { + return Err(StateCalcError::InsufficientState(ty)); + }; + let insufficient = self.fetch_state(ty, Reg16::Reg1)?; + Ok(AllocatedState { + sufficient, + insufficient, + }) + } + + pub fn calc_change( + &mut self, + ty: AssignmentType, + ) -> Result, StateCalcError> { + self.run(self.abi.calc_change) + .map_err(|err| StateCalcError::ChangeCalc(ty, err))?; + self.fetch_state(ty, Reg16::Reg0) + } +} diff --git a/src/interface/contract.rs b/src/interface/contract.rs index 5f03b6d5..a5113fdf 100644 --- a/src/interface/contract.rs +++ b/src/interface/contract.rs @@ -66,17 +66,6 @@ pub struct ContractOp { pub witness: Option, } -fn reduce_to_ty(allocations: impl IntoIterator) -> AssignmentType { - allocations - .into_iter() - .map(|a| a.opout.ty) - .reduce(|ty1, ty2| { - assert_eq!(ty1, ty2); - ty1 - }) - .expect("empty list of allocations") -} - impl ContractOp { fn genesis(our_allocations: HashSet) -> impl ExactSizeIterator { our_allocations.into_iter().map(|a| Self { diff --git a/src/interface/iimpl.rs b/src/interface/iimpl.rs index 1e528ed1..69f9eb76 100644 --- a/src/interface/iimpl.rs +++ b/src/interface/iimpl.rs @@ -36,7 +36,7 @@ use strict_encoding::{FieldName, StrictDumb, VariantName}; use strict_types::encoding::{StrictDecode, StrictEncode, StrictType}; use crate::interface::iface::IfaceId; -use crate::interface::{Iface, VerNo}; +use crate::interface::{Iface, StateAbi, VerNo}; use crate::{ReservedBytes, LIB_NAME_RGB_STD}; pub trait SchemaTypeIndex: @@ -228,6 +228,7 @@ pub struct IfaceImpl { pub extensions: TinyOrdSet>, pub errors: TinyOrdSet>, pub developer: Identity, + pub state_abi: StateAbi, } impl IfaceImpl { diff --git a/src/interface/mod.rs b/src/interface/mod.rs index e4bd759f..4e0edd21 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -31,8 +31,10 @@ mod filter; pub(crate) mod resolver; mod contractum; mod inheritance; +mod calc; pub use builder::{BuilderError, ContractBuilder, TransitionBuilder, TxOutpoint}; +pub use calc::{AllocatedState, StateAbi, StateCalc, StateCalcError}; pub use contract::{ContractError, ContractIface, ContractOp, OpDirection}; pub use contractum::IfaceDisplay; pub use filter::{AssignmentsFilter, FilterExclude, FilterIncludeAll}; diff --git a/src/persistence/memory.rs b/src/persistence/memory.rs index 1bd3098e..cb14ee79 100644 --- a/src/persistence/memory.rs +++ b/src/persistence/memory.rs @@ -29,8 +29,8 @@ use std::{iter, mem}; use aluvm::library::{Lib, LibId}; use amplify::confinement::{ - self, Confined, LargeOrdMap, LargeOrdSet, MediumBlob, MediumOrdMap, MediumOrdSet, SmallBlob, - SmallOrdMap, TinyOrdMap, TinyOrdSet, + self, Confined, LargeOrdMap, LargeOrdSet, MediumBlob, MediumOrdMap, MediumOrdSet, SmallOrdMap, + TinyOrdMap, TinyOrdSet, }; use amplify::num::u24; use bp::dbc::tapret::TapretCommitment; @@ -44,8 +44,8 @@ use rgb::vm::{ use rgb::{ Assign, AssignmentType, Assignments, AssignmentsRef, AttachId, BundleId, ContractId, ExposedSeal, Extension, Genesis, GenesisSeal, GlobalStateType, GraphSeal, Identity, OpId, - Operation, Opout, Schema, SchemaId, SecretSeal, Transition, TransitionBundle, XChain, - XOutpoint, XOutputSeal, XWitnessId, + Operation, Opout, Schema, SchemaId, SecretSeal, StateData, Transition, TransitionBundle, + XChain, XOutpoint, XOutputSeal, XWitnessId, }; use strict_encoding::{StrictDeserialize, StrictSerialize}; use strict_types::TypeSystem; @@ -680,7 +680,7 @@ impl StateWriteProvider for MemState { #[strict_type(lib = LIB_NAME_RGB_STORAGE)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))] pub struct MemGlobalState { - known: LargeOrdMap, + known: LargeOrdMap, limit: u24, } @@ -850,12 +850,12 @@ impl> ContractStateAccess for MemContract { &self, ty: GlobalStateType, ) -> Result, UnknownGlobalStateType> { - type Src<'a> = &'a BTreeMap; - type FilteredIter<'a> = Box + 'a>; + type Src<'a> = &'a BTreeMap; + type FilteredIter<'a> = Box + 'a>; struct Iter<'a> { src: Src<'a>, iter: FilteredIter<'a>, - last: Option<(GlobalOrd, &'a SmallBlob)>, + last: Option<(GlobalOrd, &'a StateData)>, depth: u24, constructor: Box) -> FilteredIter<'a> + 'a>, } @@ -867,7 +867,7 @@ impl> ContractStateAccess for MemContract { } } impl<'a> GlobalStateIter for Iter<'a> { - type Data = &'a SmallBlob; + type Data = &'a StateData; fn size(&mut self) -> u24 { let iter = self.swap(); // TODO: Consuming iterator just to count items is highly inefficient, but I do diff --git a/src/persistence/stash.rs b/src/persistence/stash.rs index c1b3e083..bd0dba10 100644 --- a/src/persistence/stash.rs +++ b/src/persistence/stash.rs @@ -312,6 +312,7 @@ impl Stash

{ let lib = self.provider.lib(id)?; scripts.insert(id, lib.clone()); } + // TODO: Make sure we have all the libs including their dependencies let scripts = Scripts::try_from(scripts) .map_err(|_| StashDataError::TooManyLibs(schema.schema_id()))?; @@ -358,6 +359,12 @@ impl Stash

{ .ok_or(StashDataError::NoIfaceImpl(schema.schema_id(), iface.iface_id()))?; let (types, _) = self.extract(&schema_ifaces.schema, [iface])?; + let mut scripts = BTreeMap::new(); + for id in iimpl.state_abi.lib_ids() { + let lib = self.provider.lib(id)?; + scripts.insert(id, lib.clone()); + } + let scripts = Scripts::from_checked(scripts); let builder = if let Some(transition_name) = transition_name { TransitionBuilder::named_transition( @@ -367,6 +374,7 @@ impl Stash

{ iimpl.clone(), transition_name.into(), types, + scripts, ) } else { TransitionBuilder::default_transition( @@ -375,6 +383,7 @@ impl Stash

{ schema.clone(), iimpl.clone(), types, + scripts, ) } .expect("internal inconsistency"); diff --git a/src/persistence/stock.rs b/src/persistence/stock.rs index a295c74a..1d8191a7 100644 --- a/src/persistence/stock.rs +++ b/src/persistence/stock.rs @@ -31,7 +31,7 @@ use bp::dbc::Method; use bp::seals::txout::CloseMethod; use bp::Vout; use chrono::Utc; -use invoice::{Beneficiary, InvoiceState, RgbInvoice}; +use invoice::{Beneficiary, RgbInvoice}; use nonasync::persistence::{CloneNoPersistence, PersistenceError, PersistenceProvider}; use rgb::validation::{DbcProof, ResolveWitness, WitnessResolverError}; use rgb::{ @@ -230,6 +230,9 @@ pub enum ComposeError { /// the invoice contains no interface information. NoIface, + /// the invoice lacks specific state information. + NoInvoiceState, + /// the invoice requirements can't be fulfilled using available assets or /// smart contract state. InsufficientState, @@ -904,6 +907,11 @@ 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 prev_outputs = prev_outputs .into_iter() .map(|o| o.into()) @@ -961,7 +969,6 @@ impl Stock { // allocate a different state transition spending them as a change. let mut alt_builder = self.transition_builder(contract_id, iface.clone(), invoice.operation.clone())?; - let mut alt_inputs = Vec::::new(); let layer1 = invoice.beneficiary.chain_network().layer1(); let beneficiary = match (invoice.beneficiary.into_inner(), beneficiary_vout) { @@ -983,16 +990,7 @@ impl Stock { // 2. Prepare transition let mut main_inputs = Vec::::new(); - let mut sum_inputs = Amount::ZERO; - let mut sum_alt = Amount::ZERO; - let mut data_inputs = vec![]; - let mut data_main = true; - let lookup_state = - if let InvoiceState::Data(NonFungible::RGB21(allocation)) = &invoice.owned_state { - Some(DataState::from(*allocation)) - } else { - None - }; + let mut alt_inputs = Vec::::new(); for (output, list) in self.contract_assignments_for(contract_id, prev_outputs.iter().copied())? @@ -1002,7 +1000,7 @@ impl Stock { } else { alt_inputs.push(output) }; - for (opout, mut state) in list { + for (opout, state) in list { if output.method() == method { main_builder = main_builder.add_input(opout, state.clone())?; } else { @@ -1010,109 +1008,34 @@ impl Stock { } if opout.ty != assignment_id { let seal = output_for_assignment(contract_id, opout.ty)?; - state.update_blinding(pedersen_blinder(contract_id, assignment_id)); if output.method() == method { main_builder = main_builder.add_owned_state_raw(opout.ty, seal, state)?; } else { alt_builder = alt_builder.add_owned_state_raw(opout.ty, seal, state)?; } - } else if let PersistedState::Amount(value, _, _) = state { - sum_inputs += value; - if output.method() != method { - sum_alt += value; - } - } else if let PersistedState::Data(value, _) = state { - if lookup_state.as_ref() == Some(&value) && output.method() != method { - data_main = false; - } - data_inputs.push(value); } } } - // Add payments to beneficiary and change - match invoice.owned_state.clone() { - InvoiceState::Amount(amt) => { - // Pay beneficiary - if sum_inputs < amt { - return Err(ComposeError::InsufficientState.into()); - } - - let sum_main = sum_inputs - sum_alt; - let (paid_main, paid_alt) = - if sum_main < amt { (sum_main, amt - sum_main) } else { (amt, Amount::ZERO) }; - let blinding_beneficiary = pedersen_blinder(contract_id, assignment_id); - - if paid_main > Amount::ZERO { - main_builder = main_builder.add_fungible_state_raw( - assignment_id, - beneficiary, - paid_main, - blinding_beneficiary, - )?; - } - if paid_alt > Amount::ZERO { - alt_builder = alt_builder.add_fungible_state_raw( - assignment_id, - beneficiary, - paid_alt, - blinding_beneficiary, - )?; - } - let blinding_change = pedersen_blinder(contract_id, assignment_id); - let change_seal = output_for_assignment(contract_id, assignment_id)?; - - // Pay change - if sum_main > paid_main { - main_builder = main_builder.add_fungible_state_raw( - assignment_id, - change_seal, - sum_main - paid_main, - blinding_change, - )?; - } - if sum_alt > paid_alt { - alt_builder = alt_builder.add_fungible_state_raw( - assignment_id, - change_seal, - sum_alt - paid_alt, - blinding_change, - )?; - } - } - InvoiceState::Data(data) => match data { - NonFungible::RGB21(allocation) => { - let lookup_state = DataState::from(allocation); - if !data_inputs.into_iter().any(|x| x == lookup_state) { - return Err(ComposeError::InsufficientState.into()); - } - - let seal = seal_blinder(contract_id, assignment_id); - if data_main { - main_builder = main_builder.add_data_raw( - assignment_id, - beneficiary, - allocation, - seal, - )?; - } else { - alt_builder = alt_builder.add_data_raw( - assignment_id, - beneficiary, - allocation, - seal, - )?; - } - } - }, - _ => { - todo!( - "only PersistedState::Amount and PersistedState::Allocation are currently \ - supported" - ) + // Add payments to beneficiary + let (builder, partial_state) = + main_builder.fulfill_owned_state(assignment_id, beneficiary, invoice_state)?; + main_builder = builder; + if let Some(partial_state) = partial_state { + let (builder, remaining_state) = + alt_builder.fulfill_owned_state(assignment_id, beneficiary, partial_state)?; + alt_builder = builder; + if let Some(_) = remaining_state { + // TODO: Add information about remaining state to the error + return Err(ComposeError::InsufficientState.into()); } } + // Pay change + let change_seal = output_for_assignment(contract_id, assignment_id)?; + main_builder = main_builder.add_owned_state_change(assignment_id, change_seal)?; + alt_builder = alt_builder.add_owned_state_change(assignment_id, change_seal)?; + // 3. Prepare other transitions // Enumerate state let mut spent_state =