From 0fc15c9f2bb24b013b351d1c43f3c633c0b26c7f Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sat, 7 Sep 2024 14:44:03 +0200 Subject: [PATCH 01/21] contract: derive Hash for OutputAssignment --- src/contract/assignments.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contract/assignments.rs b/src/contract/assignments.rs index 8d66b25a..edc293a5 100644 --- a/src/contract/assignments.rs +++ b/src/contract/assignments.rs @@ -50,7 +50,7 @@ impl KnownState for RevealedValue {} impl KnownState for RevealedData {} impl KnownState for RevealedAttach {} -#[derive(Copy, Clone, Eq, Debug)] +#[derive(Copy, Clone, Eq, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB_STD)] #[cfg_attr( From ef4679f6401c60d9080115649da5d208e66a0fad Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Wed, 11 Sep 2024 12:32:18 +0200 Subject: [PATCH 02/21] contract: add ContractStateRead::witness_info allowing wallets to access mining status --- src/persistence/memory.rs | 5 +++++ src/persistence/state.rs | 1 + 2 files changed, 6 insertions(+) diff --git a/src/persistence/memory.rs b/src/persistence/memory.rs index 87749342..09dc944d 100644 --- a/src/persistence/memory.rs +++ b/src/persistence/memory.rs @@ -1082,6 +1082,11 @@ impl> ContractStateRead for MemContract { #[inline] fn schema_id(&self) -> SchemaId { self.unfiltered.borrow().schema_id } + #[inline] + fn witness_info(&self, witness_id: XWitnessId) -> Option { + self.filter.get(&witness_id).copied() + } + #[inline] fn rights_all(&self) -> impl Iterator> { self.unfiltered diff --git a/src/persistence/state.rs b/src/persistence/state.rs index 866b5fea..6c82d6d5 100644 --- a/src/persistence/state.rs +++ b/src/persistence/state.rs @@ -307,6 +307,7 @@ pub trait StateWriteProvider: StoreTransaction { pub trait ContractStateRead: ContractStateAccess { fn contract_id(&self) -> ContractId; fn schema_id(&self) -> SchemaId; + fn witness_info(&self, witness_id: XWitnessId) -> Option; fn rights_all(&self) -> impl Iterator>; fn fungible_all(&self) -> impl Iterator>; fn data_all(&self) -> impl Iterator>; From d1d134df6d97d13a63c27c8c0aeb994be2c96822 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Wed, 11 Sep 2024 12:41:38 +0200 Subject: [PATCH 03/21] iface: add contract state accessing methods to IfaceWrapper --- src/interface/iface.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/interface/iface.rs b/src/interface/iface.rs index c6cbfaaf..26fa99f9 100644 --- a/src/interface/iface.rs +++ b/src/interface/iface.rs @@ -30,7 +30,8 @@ use amplify::{ByteArray, Bytes32}; use baid64::{Baid64ParseError, DisplayBaid64, FromBaid64Str}; use chrono::{DateTime, TimeZone, Utc}; use commit_verify::{CommitId, CommitmentId, DigestExt, Sha256}; -use rgb::{Identity, Occurrences}; +use rgb::vm::WitnessOrd; +use rgb::{ContractId, Identity, Occurrences, SchemaId, XWitnessId}; use strict_encoding::{ FieldName, StrictDecode, StrictDeserialize, StrictDumb, StrictEncode, StrictSerialize, StrictType, TypeName, VariantName, @@ -355,6 +356,15 @@ pub trait IfaceWrapper { /// Constructs information object describing a specific class in terms of /// the interface class. fn info(&self) -> Self::Info; + + /// Returns contract id. + fn contract_id(&self) -> ContractId; + + /// Returns schema id of the contract. + fn schema_id(&self) -> SchemaId; + + /// Returns information about a witness, if it is known to the contract state. + fn witness_info(&self, witness_id: XWitnessId) -> Option; } /// Interface definition. From 427931f000e7e0415cf7533d8b7dee3f43341b00 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Wed, 11 Sep 2024 12:48:52 +0200 Subject: [PATCH 04/21] chore: fix clippy lint --- src/contract/assignments.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/contract/assignments.rs b/src/contract/assignments.rs index edc293a5..1c0a920d 100644 --- a/src/contract/assignments.rs +++ b/src/contract/assignments.rs @@ -50,6 +50,7 @@ impl KnownState for RevealedValue {} impl KnownState for RevealedData {} impl KnownState for RevealedAttach {} +#[allow(clippy::derived_hash_with_manual_eq)] #[derive(Copy, Clone, Eq, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB_STD)] From 1ba9a8b6de118d824ef4b812501567f8ab0c6adf Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 20 Sep 2024 10:58:38 +0200 Subject: [PATCH 05/21] iface: remove IfaceOp, which should be handled at RGB wallet --- src/interface/contract.rs | 161 +------------------------------------- src/interface/mod.rs | 2 +- 2 files changed, 3 insertions(+), 160 deletions(-) diff --git a/src/interface/contract.rs b/src/interface/contract.rs index b54e45d2..720caacc 100644 --- a/src/interface/contract.rs +++ b/src/interface/contract.rs @@ -21,13 +21,11 @@ use std::borrow::Borrow; use std::cmp::Ordering; -use std::collections::HashMap; -use amplify::confinement::SmallOrdSet; use invoice::{Allocation, Amount}; use rgb::{ - AttachState, ContractId, DataState, OpId, RevealedAttach, RevealedData, RevealedValue, Schema, - VoidState, XOutpoint, XOutputSeal, XWitnessId, + AttachState, ContractId, DataState, RevealedAttach, RevealedData, RevealedValue, Schema, + VoidState, XOutpoint, }; use strict_encoding::{FieldName, StrictDecode, StrictDumb, StrictEncode}; use strict_types::{StrictVal, TypeSystem}; @@ -148,59 +146,6 @@ impl StateChange for AmountChange { } } -#[derive(Clone, Eq, PartialEq, 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 IfaceOp { - pub opids: SmallOrdSet, // may come from multiple bundles - pub inputs: SmallOrdSet, // may come from multiple bundles - pub state_change: S, - pub payers: SmallOrdSet, - pub beneficiaries: SmallOrdSet, -} - -impl IfaceOp { - fn from_spent(alloc: OutputAssignment) -> Self { - Self { - opids: none!(), - inputs: small_bset![alloc.opout.op], - state_change: C::from_spent(alloc.state), - payers: none!(), - // TODO: Do something with beneficiary info - beneficiaries: none!(), - } - } - fn from_received(alloc: OutputAssignment) -> Self { - Self { - opids: small_bset![alloc.opout.op], - inputs: none!(), - state_change: C::from_received(alloc.state), - // TODO: Do something with payer info - payers: none!(), - beneficiaries: none!(), - } - } - fn merge_spent(&mut self, alloc: OutputAssignment) { - self.inputs - .push(alloc.opout.op) - .expect("internal inconsistency of stash data"); - self.state_change.merge_spent(alloc.state); - // TODO: Do something with beneficiary info - } - fn merge_received(&mut self, alloc: OutputAssignment) { - self.opids - .push(alloc.opout.op) - .expect("internal inconsistency of stash data"); - self.state_change.merge_received(alloc.state); - // TODO: Do something with payer info - } -} - /// Contract state is an in-memory structure providing API to read structured /// data from the [`rgb::ContractHistory`]. #[derive(Clone, Eq, PartialEq, Debug)] @@ -372,106 +317,4 @@ impl ContractIface { ) -> impl Iterator + '_ { self.allocations(outpoint) } - - // TODO: Ignore blank state transition - fn operations<'c, C: StateChange>( - &'c self, - state: impl IntoIterator> + 'c, - allocations: impl Iterator> + 'c, - ) -> HashMap> - where - C::State: 'c, - { - fn f<'a, S, U>( - state: impl IntoIterator> + 'a, - ) -> impl Iterator> + 'a - where - S: Clone + KnownState + 'a, - U: From + KnownState + 'a, - { - state.into_iter().map(OutputAssignment::::transmute) - } - - let spent = f::<_, C::State>(state).map(OutputAssignment::from); - let mut ops = HashMap::>::new(); - for alloc in spent { - let Some(witness_id) = alloc.witness else { - continue; - }; - if let Some(op) = ops.get_mut(&witness_id) { - op.merge_spent(alloc); - } else { - ops.insert(witness_id, IfaceOp::from_spent(alloc)); - } - } - - for alloc in allocations { - let Some(witness_id) = alloc.witness else { - continue; - }; - if let Some(op) = ops.get_mut(&witness_id) { - op.merge_received(alloc); - } else { - ops.insert(witness_id, IfaceOp::from_received(alloc)); - } - } - - ops - } - - pub fn fungible_ops<'c, C: StateChange>( - &'c self, - name: impl Into, - outpoint_filter: impl OutpointFilter + Copy + 'c, - ) -> Result>, ContractError> { - Ok(self.operations( - self.state - .fungible_all() - .copied() - .map(OutputAssignment::transmute), - self.fungible(name, outpoint_filter)?, - )) - } - - pub fn data_ops<'c, C: StateChange>( - &'c self, - name: impl Into, - outpoint_filter: impl OutpointFilter + Copy + 'c, - ) -> Result>, ContractError> { - Ok(self.operations( - self.state - .data_all() - .cloned() - .map(OutputAssignment::transmute), - self.data(name, outpoint_filter)?, - )) - } - - pub fn rights_ops<'c, C: StateChange>( - &'c self, - name: impl Into, - outpoint_filter: impl OutpointFilter + Copy + 'c, - ) -> Result>, ContractError> { - Ok(self.operations( - self.state - .rights_all() - .copied() - .map(OutputAssignment::transmute), - self.rights(name, outpoint_filter)?, - )) - } - - pub fn attachment_ops<'c, C: StateChange>( - &'c self, - name: impl Into, - outpoint_filter: impl OutpointFilter + Copy + 'c, - ) -> Result>, ContractError> { - Ok(self.operations( - self.state - .attach_all() - .cloned() - .map(OutputAssignment::transmute), - self.attachments(name, outpoint_filter)?, - )) - } } diff --git a/src/interface/mod.rs b/src/interface/mod.rs index ea5ef2d9..240ab1cf 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -35,7 +35,7 @@ mod inheritance; pub use builder::{BuilderError, ContractBuilder, TransitionBuilder, TxOutpoint}; pub use contract::{ AllocatedState, AmountChange, AttachAllocation, ContractError, ContractIface, DataAllocation, - FungibleAllocation, IfaceOp, OwnedAllocation, RightsAllocation, StateChange, + FungibleAllocation, OwnedAllocation, RightsAllocation, StateChange, }; pub use contractum::IfaceDisplay; pub use filter::{FilterExclude, FilterIncludeAll, OutpointFilter}; From 806d73524efce8553f3653645a769fd030283794 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 20 Sep 2024 10:59:33 +0200 Subject: [PATCH 06/21] iface: remove unused methods from ContractIface --- src/interface/contract.rs | 40 --------------------------------------- 1 file changed, 40 deletions(-) diff --git a/src/interface/contract.rs b/src/interface/contract.rs index 720caacc..72c767c9 100644 --- a/src/interface/contract.rs +++ b/src/interface/contract.rs @@ -221,16 +221,6 @@ impl ContractIface { self.extract_state(self.state.rights_all(), name, filter) } - pub fn rights_all<'c>( - &'c self, - name: impl Into, - filter: impl OutpointFilter + 'c, - ) -> Result, ContractError> { - Ok(self - .extract_state(self.state.rights_all(), name, filter)? - .collect()) - } - pub fn fungible<'c>( &'c self, name: impl Into, @@ -239,16 +229,6 @@ impl ContractIface { self.extract_state(self.state.fungible_all(), name, filter) } - pub fn fungible_all<'c>( - &'c self, - name: impl Into, - filter: impl OutpointFilter + 'c, - ) -> Result, ContractError> { - Ok(self - .extract_state(self.state.fungible_all(), name, filter)? - .collect()) - } - pub fn data<'c>( &'c self, name: impl Into, @@ -257,16 +237,6 @@ impl ContractIface { self.extract_state(self.state.data_all(), name, filter) } - pub fn data_all<'c>( - &'c self, - name: impl Into, - filter: impl OutpointFilter + 'c, - ) -> Result, ContractError> { - Ok(self - .extract_state(self.state.data_all(), name, filter)? - .collect()) - } - pub fn attachments<'c>( &'c self, name: impl Into, @@ -275,16 +245,6 @@ impl ContractIface { self.extract_state(self.state.attach_all(), name, filter) } - pub fn attachments_all<'c>( - &'c self, - name: impl Into, - filter: impl OutpointFilter + 'c, - ) -> Result, ContractError> { - Ok(self - .extract_state(self.state.attach_all(), name, filter)? - .collect()) - } - pub fn allocations<'c>( &'c self, filter: impl OutpointFilter + Copy + 'c, From e75e07a118f06576fe7202f9a4a177e457b076c0 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 20 Sep 2024 11:00:09 +0200 Subject: [PATCH 07/21] contract: doc method OutputAssignment::transmute --- src/contract/assignments.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/contract/assignments.rs b/src/contract/assignments.rs index 8d66b25a..539046f8 100644 --- a/src/contract/assignments.rs +++ b/src/contract/assignments.rs @@ -142,6 +142,7 @@ impl OutputAssignment { } } + /// Transmutes output assignment from one form of state to another pub fn transmute>(self) -> OutputAssignment { OutputAssignment { opout: self.opout, From 8f056b8e5f8b1737dc3a94a42d56fcbf7e9dd87f Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 20 Sep 2024 11:00:40 +0200 Subject: [PATCH 08/21] iface: refactor OutpointFilter into AssignmentsFilter which accounts for witness id --- src/interface/contract.rs | 33 +++++++++----- src/interface/filter.rs | 90 ++++++++++++++++++++++++--------------- src/interface/mod.rs | 2 +- 3 files changed, 80 insertions(+), 45 deletions(-) diff --git a/src/interface/contract.rs b/src/interface/contract.rs index 72c767c9..78e85409 100644 --- a/src/interface/contract.rs +++ b/src/interface/contract.rs @@ -32,7 +32,7 @@ use strict_types::{StrictVal, TypeSystem}; use crate::contract::{KnownState, OutputAssignment}; use crate::info::ContractInfo; -use crate::interface::{IfaceImpl, OutpointFilter}; +use crate::interface::{AssignmentsFilter, IfaceImpl}; use crate::persistence::ContractStateRead; use crate::LIB_NAME_RGB_STD; @@ -194,7 +194,21 @@ impl ContractIface { &'c self, state: impl IntoIterator> + 'c, name: impl Into, - filter: impl OutpointFilter + 'c, + filter: impl AssignmentsFilter + 'c, + ) -> Result> + 'c, ContractError> + where + A: Clone + KnownState + 'c, + U: From + KnownState + 'c, + { + Ok(self + .extract_state_unfiltered(state, name)? + .filter(move |outp| filter.should_include(outp.seal, outp.witness))) + } + + fn extract_state_unfiltered<'c, A, U>( + &'c self, + state: impl IntoIterator> + 'c, + name: impl Into, ) -> Result> + 'c, ContractError> where A: Clone + KnownState + 'c, @@ -208,7 +222,6 @@ impl ContractIface { Ok(state .into_iter() .filter(move |outp| outp.opout.ty == type_id) - .filter(move |outp| filter.include_outpoint(outp.seal)) .cloned() .map(OutputAssignment::::transmute)) } @@ -216,7 +229,7 @@ impl ContractIface { pub fn rights<'c>( &'c self, name: impl Into, - filter: impl OutpointFilter + 'c, + filter: impl AssignmentsFilter + 'c, ) -> Result + 'c, ContractError> { self.extract_state(self.state.rights_all(), name, filter) } @@ -224,7 +237,7 @@ impl ContractIface { pub fn fungible<'c>( &'c self, name: impl Into, - filter: impl OutpointFilter + 'c, + filter: impl AssignmentsFilter + 'c, ) -> Result + 'c, ContractError> { self.extract_state(self.state.fungible_all(), name, filter) } @@ -232,7 +245,7 @@ impl ContractIface { pub fn data<'c>( &'c self, name: impl Into, - filter: impl OutpointFilter + 'c, + filter: impl AssignmentsFilter + 'c, ) -> Result + 'c, ContractError> { self.extract_state(self.state.data_all(), name, filter) } @@ -240,17 +253,17 @@ impl ContractIface { pub fn attachments<'c>( &'c self, name: impl Into, - filter: impl OutpointFilter + 'c, + filter: impl AssignmentsFilter + 'c, ) -> Result + 'c, ContractError> { self.extract_state(self.state.attach_all(), name, filter) } pub fn allocations<'c>( &'c self, - filter: impl OutpointFilter + Copy + 'c, + filter: impl AssignmentsFilter + Copy + 'c, ) -> impl Iterator + 'c { fn f<'a, S, U>( - filter: impl OutpointFilter + 'a, + filter: impl AssignmentsFilter + 'a, state: impl IntoIterator> + 'a, ) -> impl Iterator> + 'a where @@ -259,7 +272,7 @@ impl ContractIface { { state .into_iter() - .filter(move |outp| filter.include_outpoint(outp.seal)) + .filter(move |outp| filter.should_include(outp.seal, outp.witness)) .cloned() .map(OutputAssignment::::transmute) } diff --git a/src/interface/filter.rs b/src/interface/filter.rs index 5e802fff..b76ccd65 100644 --- a/src/interface/filter.rs +++ b/src/interface/filter.rs @@ -22,88 +22,110 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::ops::Deref; -use rgb::XOutpoint; - -pub trait OutpointFilter { - fn include_outpoint(&self, outpoint: impl Into) -> bool; +use rgb::{XOutpoint, XWitnessId}; + +pub trait AssignmentsFilter { + fn should_include( + &self, + outpoint: impl Into, + witness_id: Option, + ) -> bool; } pub struct FilterIncludeAll; pub struct FilterExclude(pub T); -impl OutpointFilter for FilterIncludeAll { - fn include_outpoint(&self, _: impl Into) -> bool { true } +impl AssignmentsFilter for FilterIncludeAll { + fn should_include(&self, _: impl Into, _: Option) -> bool { true } } -impl OutpointFilter for FilterExclude { - fn include_outpoint(&self, outpoint: impl Into) -> bool { - !self.0.include_outpoint(outpoint.into()) +impl AssignmentsFilter for FilterExclude { + fn should_include( + &self, + outpoint: impl Into, + witness_id: Option, + ) -> bool { + !self.0.should_include(outpoint.into(), witness_id) } } -impl OutpointFilter for &T { - fn include_outpoint(&self, outpoint: impl Into) -> bool { - (*self).include_outpoint(outpoint) +impl AssignmentsFilter for &T { + fn should_include( + &self, + outpoint: impl Into, + witness_id: Option, + ) -> bool { + (*self).should_include(outpoint, witness_id) } } -impl OutpointFilter for &mut T { - fn include_outpoint(&self, outpoint: impl Into) -> bool { - self.deref().include_outpoint(outpoint) +impl AssignmentsFilter for &mut T { + fn should_include( + &self, + outpoint: impl Into, + witness_id: Option, + ) -> bool { + self.deref().should_include(outpoint, witness_id) } } -impl OutpointFilter for Option { - fn include_outpoint(&self, outpoint: impl Into) -> bool { +impl AssignmentsFilter for Option { + fn should_include( + &self, + outpoint: impl Into, + witness_id: Option, + ) -> bool { self.as_ref() - .map(|filter| filter.include_outpoint(outpoint)) + .map(|filter| filter.should_include(outpoint, witness_id)) .unwrap_or(true) } } -impl OutpointFilter for XOutpoint { - fn include_outpoint(&self, outpoint: impl Into) -> bool { *self == outpoint.into() } +impl AssignmentsFilter for XOutpoint { + fn should_include(&self, outpoint: impl Into, _: Option) -> bool { + *self == outpoint.into() + } } -impl OutpointFilter for [XOutpoint; LEN] { - fn include_outpoint(&self, outpoint: impl Into) -> bool { +impl AssignmentsFilter for [XOutpoint; LEN] { + fn should_include(&self, outpoint: impl Into, _: Option) -> bool { self.contains(&outpoint.into()) } } -impl OutpointFilter for &[XOutpoint] { - fn include_outpoint(&self, outpoint: impl Into) -> bool { +impl AssignmentsFilter for &[XOutpoint] { + fn should_include(&self, outpoint: impl Into, _: Option) -> bool { self.contains(&outpoint.into()) } } -impl OutpointFilter for Vec { - fn include_outpoint(&self, outpoint: impl Into) -> bool { +impl AssignmentsFilter for Vec { + fn should_include(&self, outpoint: impl Into, _: Option) -> bool { self.contains(&outpoint.into()) } } -impl OutpointFilter for HashSet { - fn include_outpoint(&self, outpoint: impl Into) -> bool { +impl AssignmentsFilter for HashSet { + fn should_include(&self, outpoint: impl Into, _: Option) -> bool { self.contains(&outpoint.into()) } } -impl OutpointFilter for BTreeSet { - fn include_outpoint(&self, outpoint: impl Into) -> bool { +impl AssignmentsFilter for BTreeSet { + fn should_include(&self, outpoint: impl Into, _: Option) -> bool { self.contains(&outpoint.into()) } } -impl OutpointFilter for HashMap { - fn include_outpoint(&self, outpoint: impl Into) -> bool { +impl AssignmentsFilter for HashMap { + fn should_include(&self, outpoint: impl Into, _: Option) -> bool { let outpoint = outpoint.into(); self.keys().any(|o| *o == outpoint) } } -impl OutpointFilter for BTreeMap { - fn include_outpoint(&self, outpoint: impl Into) -> bool { +impl AssignmentsFilter for BTreeMap { + fn should_include(&self, outpoint: impl Into, _: Option) -> bool { let outpoint = outpoint.into(); self.keys().any(|o| *o == outpoint) } diff --git a/src/interface/mod.rs b/src/interface/mod.rs index 240ab1cf..2ffbac11 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -38,7 +38,7 @@ pub use contract::{ FungibleAllocation, OwnedAllocation, RightsAllocation, StateChange, }; pub use contractum::IfaceDisplay; -pub use filter::{FilterExclude, FilterIncludeAll, OutpointFilter}; +pub use filter::{AssignmentsFilter, FilterExclude, FilterIncludeAll}; pub use iface::{ ArgMap, AssignIface, ExtensionIface, GenesisIface, GlobalIface, Iface, IfaceClass, IfaceId, IfaceInconsistency, IfaceRef, IfaceWrapper, Modifier, OpName, OwnedIface, Req, TransitionIface, From 0ca8164a758e9ba421e45da9b50d0b47864e9815 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 20 Sep 2024 13:54:52 +0200 Subject: [PATCH 09/21] iface: refactor contract operation history deduction --- src/contract/assignments.rs | 13 +++ src/contract/mod.rs | 2 +- src/interface/contract.rs | 162 +++++++++++++++++++++++++++++++++++- src/interface/mod.rs | 4 +- src/persistence/memory.rs | 2 +- src/persistence/state.rs | 2 +- 6 files changed, 177 insertions(+), 8 deletions(-) diff --git a/src/contract/assignments.rs b/src/contract/assignments.rs index c64780cf..85dfec4a 100644 --- a/src/contract/assignments.rs +++ b/src/contract/assignments.rs @@ -50,6 +50,19 @@ impl KnownState for RevealedValue {} impl KnownState for RevealedData {} impl KnownState for RevealedAttach {} +#[derive(Copy, Clone, PartialEq, Eq, 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 WitnessInfo { + pub id: XWitnessId, + pub ord: WitnessOrd, +} + #[allow(clippy::derived_hash_with_manual_eq)] #[derive(Copy, Clone, Eq, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] diff --git a/src/contract/mod.rs b/src/contract/mod.rs index b7905e89..9322b78e 100644 --- a/src/contract/mod.rs +++ b/src/contract/mod.rs @@ -23,7 +23,7 @@ mod assignments; mod bundle; mod merge_reveal; -pub use assignments::{KnownState, OutputAssignment, TypedAssignsExt}; +pub use assignments::{KnownState, OutputAssignment, TypedAssignsExt, WitnessInfo}; pub use bundle::{BundleExt, RevealError}; pub use merge_reveal::{MergeReveal, MergeRevealError}; use rgb::vm::OrdOpRef; diff --git a/src/interface/contract.rs b/src/interface/contract.rs index 78e85409..1a75c339 100644 --- a/src/interface/contract.rs +++ b/src/interface/contract.rs @@ -21,16 +21,18 @@ use std::borrow::Borrow; use std::cmp::Ordering; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::iter::Sum; use invoice::{Allocation, Amount}; use rgb::{ - AttachState, ContractId, DataState, RevealedAttach, RevealedData, RevealedValue, Schema, - VoidState, XOutpoint, + AttachState, ContractId, DataState, OpId, RevealedAttach, RevealedData, RevealedValue, Schema, + VoidState, XOutpoint, XOutputSeal, XWitnessId, }; use strict_encoding::{FieldName, StrictDecode, StrictDumb, StrictEncode}; use strict_types::{StrictVal, TypeSystem}; -use crate::contract::{KnownState, OutputAssignment}; +use crate::contract::{KnownState, OutputAssignment, WitnessInfo}; use crate::info::ContractInfo; use crate::interface::{AssignmentsFilter, IfaceImpl}; use crate::persistence::ContractStateRead; @@ -146,6 +148,74 @@ impl StateChange for AmountChange { } } +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +pub struct ContractOp { + pub opids: BTreeSet, // may come from multiple bundles + pub state_change: S, + pub seals: BTreeSet, + pub witness: Option, +} + +impl ContractOp { + fn new_genesis(our_allocations: &HashSet>) -> Self + where S::State: Sum { + let opids = our_allocations.iter().map(|a| a.opout.op).collect(); + let seals = our_allocations.iter().map(|a| a.seal).collect(); + let state_change = + StateChange::from_received(our_allocations.iter().map(|a| a.state.clone()).sum()); + Self { + opids, + state_change, + seals, + witness: None, + } + } + + fn new_sent( + witness: WitnessInfo, + ext_allocations: &HashSet>, + our_allocations: &HashSet>, + ) -> Self + where + S::State: Sum, + { + let opids = our_allocations.iter().map(|a| a.opout.op).collect(); + let seals = ext_allocations.iter().map(|a| a.seal).collect(); + let mut state_change = S::from_spent(ext_allocations.iter().map(|a| a.state.clone()).sum()); + state_change.merge_received(our_allocations.iter().map(|a| a.state.clone()).sum()); + Self { + opids, + state_change, + seals, + witness: Some(witness), + } + } + + fn new_received( + witness: WitnessInfo, + our_allocations: &HashSet>, + ) -> Self + where + S::State: Sum, + { + let opids = our_allocations.iter().map(|a| a.opout.op).collect(); + let seals = our_allocations.iter().map(|a| a.seal).collect(); + let state_change = + StateChange::from_received(our_allocations.iter().map(|a| a.state.clone()).sum()); + Self { + opids, + state_change, + seals, + witness: Some(witness), + } + } +} + /// Contract state is an in-memory structure providing API to read structured /// data from the [`rgb::ContractHistory`]. #[derive(Clone, Eq, PartialEq, Debug)] @@ -290,4 +360,90 @@ impl ContractIface { ) -> impl Iterator + '_ { self.allocations(outpoint) } + + pub fn fungible_operations( + &self, + state_name: impl Into, + filter_outpoints: impl AssignmentsFilter, + filter_witnesses: impl AssignmentsFilter, + ) -> Result>, ContractError> { + let state_name = state_name.into(); + + // get all allocations which ever belonged to this wallet and store them by witness id + let allocations_our_outpoint = self.fungible(state_name.clone(), filter_outpoints)?.fold( + HashMap::<_, HashSet<_>>::new(), + |mut map, a| { + map.entry(a.witness).or_default().insert(a); + map + }, + ); + // get all allocations which has a witness transaction belonging to this wallet + let allocations_our_witness = self.fungible(state_name, filter_witnesses)?.fold( + HashMap::<_, HashSet<_>>::new(), + |mut map, a| { + let witness = a.witness.expect( + "all empty witnesses must be already filtered out by wallet.filter_witness()", + ); + map.entry(witness).or_default().insert(a); + map + }, + ); + + // gather all witnesses from both sets + let mut witness_ids = allocations_our_witness + .keys() + .cloned() + .collect::>(); + witness_ids.extend(allocations_our_outpoint.keys().filter_map(|x| *x)); + + // reconstruct contract history from the wallet perspective + let mut ops = Vec::with_capacity(witness_ids.len() + 1); + // add allocations with no witness to the beginning of the history + if let Some(genesis_allocations) = allocations_our_outpoint.get(&None) { + ops.push(ContractOp::new_genesis(genesis_allocations)); + } + for witness_id in witness_ids { + let our_outpoint = allocations_our_outpoint.get(&Some(witness_id)); + let our_witness = allocations_our_witness.get(&witness_id); + let witness_info = self.witness_info(witness_id).expect( + "witness id was returned from the contract state above, so it must be there", + ); + let op = match (our_outpoint, our_witness) { + // we own both allocation and witness transaction: these allocations are changes and + // outgoing payments. The difference between the change and the payments are whether + // a specific allocation is listed in the first tuple pattern field. + (Some(our_allocations), Some(all_allocations)) => { + // all_allocations - our_allocations = external payments + let ext_allocations = all_allocations.difference(our_allocations); + ContractOp::new_sent( + witness_info, + &ext_allocations.copied().collect(), + our_allocations, + ) + } + // the same as above, but the payment has no change + (None, Some(ext_allocations)) => { + ContractOp::new_sent(witness_info, ext_allocations, &set![]) + } + // we own allocation but the witness transaction was made by other wallet: + // this is an incoming payment to us. + (Some(our_allocations), None) => { + ContractOp::new_received(witness_info, our_allocations) + } + // these can't get into the `witness_ids` due to the used filters + (None, None) => unreachable!("broken allocation filters"), + }; + ops.push(op); + } + + Ok(ops) + } + + pub fn witness_info(&self, witness_id: XWitnessId) -> Option { + let ord = self.state.witness_ord(witness_id)?; + Some(WitnessInfo { + id: witness_id, + ord, + }) + } } diff --git a/src/interface/mod.rs b/src/interface/mod.rs index 2ffbac11..fc7c8d57 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -34,8 +34,8 @@ mod inheritance; pub use builder::{BuilderError, ContractBuilder, TransitionBuilder, TxOutpoint}; pub use contract::{ - AllocatedState, AmountChange, AttachAllocation, ContractError, ContractIface, DataAllocation, - FungibleAllocation, OwnedAllocation, RightsAllocation, StateChange, + AllocatedState, AmountChange, AttachAllocation, ContractError, ContractIface, ContractOp, + DataAllocation, FungibleAllocation, OwnedAllocation, RightsAllocation, StateChange, }; pub use contractum::IfaceDisplay; pub use filter::{AssignmentsFilter, FilterExclude, FilterIncludeAll}; diff --git a/src/persistence/memory.rs b/src/persistence/memory.rs index 7656b741..a37d1756 100644 --- a/src/persistence/memory.rs +++ b/src/persistence/memory.rs @@ -1083,7 +1083,7 @@ impl> ContractStateRead for MemContract { fn schema_id(&self) -> SchemaId { self.unfiltered.borrow().schema_id } #[inline] - fn witness_info(&self, witness_id: XWitnessId) -> Option { + fn witness_ord(&self, witness_id: XWitnessId) -> Option { self.filter.get(&witness_id).copied() } diff --git a/src/persistence/state.rs b/src/persistence/state.rs index a491b9a2..91e5356d 100644 --- a/src/persistence/state.rs +++ b/src/persistence/state.rs @@ -305,7 +305,7 @@ pub trait StateWriteProvider: StoreTransaction { pub trait ContractStateRead: ContractStateAccess { fn contract_id(&self) -> ContractId; fn schema_id(&self) -> SchemaId; - fn witness_info(&self, witness_id: XWitnessId) -> Option; + fn witness_ord(&self, witness_id: XWitnessId) -> Option; fn rights_all(&self) -> impl Iterator>; fn fungible_all(&self) -> impl Iterator>; fn data_all(&self) -> impl Iterator>; From b8748b7c62456f1f250345b12bcaf537dc48e1d5 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 20 Sep 2024 14:05:57 +0200 Subject: [PATCH 10/21] iface: remove StateChange to simplify APIs --- src/contract/assignments.rs | 6 ++ src/contract/mod.rs | 2 +- src/interface/contract.rs | 152 ++++++++++-------------------------- src/interface/mod.rs | 4 +- 4 files changed, 50 insertions(+), 114 deletions(-) diff --git a/src/contract/assignments.rs b/src/contract/assignments.rs index 85dfec4a..d0fd001d 100644 --- a/src/contract/assignments.rs +++ b/src/contract/assignments.rs @@ -22,6 +22,8 @@ use std::cmp::Ordering; use std::collections::HashMap; use std::fmt::Debug; +use std::iter::Sum; +use std::ops::{Add, AddAssign, Sub, SubAssign}; use amplify::confinement::SmallVec; use commit_verify::Conceal; @@ -50,6 +52,10 @@ impl KnownState for RevealedValue {} impl KnownState for RevealedData {} impl KnownState for RevealedAttach {} +pub trait FungibleState: KnownState + Sum + Add + AddAssign + Sub + SubAssign {} + +impl FungibleState for Amount {} + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB_STD)] diff --git a/src/contract/mod.rs b/src/contract/mod.rs index 9322b78e..4b2c4c45 100644 --- a/src/contract/mod.rs +++ b/src/contract/mod.rs @@ -23,7 +23,7 @@ mod assignments; mod bundle; mod merge_reveal; -pub use assignments::{KnownState, OutputAssignment, TypedAssignsExt, WitnessInfo}; +pub use assignments::{FungibleState, KnownState, OutputAssignment, TypedAssignsExt, WitnessInfo}; pub use bundle::{BundleExt, RevealError}; pub use merge_reveal::{MergeReveal, MergeRevealError}; use rgb::vm::OrdOpRef; diff --git a/src/interface/contract.rs b/src/interface/contract.rs index 1a75c339..d7abdc97 100644 --- a/src/interface/contract.rs +++ b/src/interface/contract.rs @@ -20,9 +20,7 @@ // limitations under the License. use std::borrow::Borrow; -use std::cmp::Ordering; use std::collections::{BTreeSet, HashMap, HashSet}; -use std::iter::Sum; use invoice::{Allocation, Amount}; use rgb::{ @@ -32,7 +30,7 @@ use rgb::{ use strict_encoding::{FieldName, StrictDecode, StrictDumb, StrictEncode}; use strict_types::{StrictVal, TypeSystem}; -use crate::contract::{KnownState, OutputAssignment, WitnessInfo}; +use crate::contract::{FungibleState, KnownState, OutputAssignment, WitnessInfo}; use crate::info::ContractInfo; use crate::interface::{AssignmentsFilter, IfaceImpl}; use crate::persistence::ContractStateRead; @@ -86,132 +84,64 @@ pub type FungibleAllocation = OutputAssignment; pub type DataAllocation = OutputAssignment; pub type AttachAllocation = OutputAssignment; -pub trait StateChange: Clone + Eq + StrictDumb + StrictEncode + StrictDecode { - type State: KnownState; - fn from_spent(state: Self::State) -> Self; - fn from_received(state: Self::State) -> Self; - fn merge_spent(&mut self, state: Self::State); - fn merge_received(&mut self, state: Self::State); -} - -#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)] -#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] -#[strict_type(lib = LIB_NAME_RGB_STD, tags = custom)] -#[cfg_attr( - feature = "serde", - derive(Serialize, Deserialize), - serde(crate = "serde_crate", rename_all = "camelCase") -)] -pub enum AmountChange { - #[display("-{0}")] - #[strict_type(tag = 0xFF)] - Dec(Amount), - - #[display("0")] - #[strict_type(tag = 0, dumb)] - Zero, - - #[display("+{0}")] - #[strict_type(tag = 0x01)] - Inc(Amount), -} - -impl StateChange for AmountChange { - type State = Amount; - - fn from_spent(state: Self::State) -> Self { AmountChange::Dec(state) } - - fn from_received(state: Self::State) -> Self { AmountChange::Inc(state) } - - fn merge_spent(&mut self, sub: Self::State) { - *self = match self { - AmountChange::Dec(neg) => AmountChange::Dec(*neg + sub), - AmountChange::Zero => AmountChange::Dec(sub), - AmountChange::Inc(pos) => match sub.cmp(pos) { - Ordering::Less => AmountChange::Inc(*pos - sub), - Ordering::Equal => AmountChange::Zero, - Ordering::Greater => AmountChange::Dec(sub - *pos), - }, - }; - } - - fn merge_received(&mut self, add: Self::State) { - *self = match self { - AmountChange::Inc(pos) => AmountChange::Inc(*pos + add), - AmountChange::Zero => AmountChange::Inc(add), - AmountChange::Dec(neg) => match add.cmp(neg) { - Ordering::Less => AmountChange::Dec(*neg - add), - Ordering::Equal => AmountChange::Zero, - Ordering::Greater => AmountChange::Inc(add - *neg), - }, - }; - } -} - #[derive(Clone, Eq, PartialEq, Hash, Debug)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate", rename_all = "camelCase") )] -pub struct ContractOp { - pub opids: BTreeSet, // may come from multiple bundles - pub state_change: S, - pub seals: BTreeSet, - pub witness: Option, +pub enum ContractOp { + Genesis { + issued: S, + to: BTreeSet, + }, + Received { + opids: BTreeSet, + amount: S, + to: BTreeSet, + witness: WitnessInfo, + }, + Sent { + opids: BTreeSet, + amount: S, + to: BTreeSet, + witness: WitnessInfo, + }, } -impl ContractOp { - fn new_genesis(our_allocations: &HashSet>) -> Self - where S::State: Sum { - let opids = our_allocations.iter().map(|a| a.opout.op).collect(); - let seals = our_allocations.iter().map(|a| a.seal).collect(); - let state_change = - StateChange::from_received(our_allocations.iter().map(|a| a.state.clone()).sum()); - Self { - opids, - state_change, - seals, - witness: None, - } +impl ContractOp { + fn new_genesis(our_allocations: &HashSet>) -> Self { + let to = our_allocations.iter().map(|a| a.seal).collect(); + let issued = our_allocations.iter().map(|a| a.state.clone()).sum(); + Self::Genesis { issued, to } } fn new_sent( witness: WitnessInfo, - ext_allocations: &HashSet>, - our_allocations: &HashSet>, - ) -> Self - where - S::State: Sum, - { + ext_allocations: &HashSet>, + our_allocations: &HashSet>, + ) -> Self { let opids = our_allocations.iter().map(|a| a.opout.op).collect(); - let seals = ext_allocations.iter().map(|a| a.seal).collect(); - let mut state_change = S::from_spent(ext_allocations.iter().map(|a| a.state.clone()).sum()); - state_change.merge_received(our_allocations.iter().map(|a| a.state.clone()).sum()); - Self { + let to = ext_allocations.iter().map(|a| a.seal).collect(); + let mut amount = ext_allocations.iter().map(|a| a.state.clone()).sum(); + amount -= our_allocations.iter().map(|a| a.state.clone()).sum(); + Self::Sent { opids, - state_change, - seals, - witness: Some(witness), + amount, + to, + witness, } } - fn new_received( - witness: WitnessInfo, - our_allocations: &HashSet>, - ) -> Self - where - S::State: Sum, - { + fn new_received(witness: WitnessInfo, our_allocations: &HashSet>) -> Self { let opids = our_allocations.iter().map(|a| a.opout.op).collect(); - let seals = our_allocations.iter().map(|a| a.seal).collect(); - let state_change = - StateChange::from_received(our_allocations.iter().map(|a| a.state.clone()).sum()); - Self { + let to = our_allocations.iter().map(|a| a.seal).collect(); + let amount = our_allocations.iter().map(|a| a.state.clone()).sum(); + Self::Received { opids, - state_change, - seals, - witness: Some(witness), + amount, + to, + witness, } } } @@ -366,7 +296,7 @@ impl ContractIface { state_name: impl Into, filter_outpoints: impl AssignmentsFilter, filter_witnesses: impl AssignmentsFilter, - ) -> Result>, ContractError> { + ) -> Result>, ContractError> { let state_name = state_name.into(); // get all allocations which ever belonged to this wallet and store them by witness id diff --git a/src/interface/mod.rs b/src/interface/mod.rs index fc7c8d57..6f9f7d01 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -34,8 +34,8 @@ mod inheritance; pub use builder::{BuilderError, ContractBuilder, TransitionBuilder, TxOutpoint}; pub use contract::{ - AllocatedState, AmountChange, AttachAllocation, ContractError, ContractIface, ContractOp, - DataAllocation, FungibleAllocation, OwnedAllocation, RightsAllocation, StateChange, + AllocatedState, AttachAllocation, ContractError, ContractIface, ContractOp, DataAllocation, + FungibleAllocation, OwnedAllocation, RightsAllocation, }; pub use contractum::IfaceDisplay; pub use filter::{AssignmentsFilter, FilterExclude, FilterIncludeAll}; From a3ba3400a3522f3c5e1938b55658d432be3ff131 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 20 Sep 2024 14:46:19 +0200 Subject: [PATCH 11/21] iface: differentiate fungible and non-fungible operations --- src/contract/assignments.rs | 6 --- src/contract/mod.rs | 2 +- src/interface/contract.rs | 87 ++++++++++++++++++++++++------------- src/interface/mod.rs | 4 +- 4 files changed, 60 insertions(+), 39 deletions(-) diff --git a/src/contract/assignments.rs b/src/contract/assignments.rs index d0fd001d..85dfec4a 100644 --- a/src/contract/assignments.rs +++ b/src/contract/assignments.rs @@ -22,8 +22,6 @@ use std::cmp::Ordering; use std::collections::HashMap; use std::fmt::Debug; -use std::iter::Sum; -use std::ops::{Add, AddAssign, Sub, SubAssign}; use amplify::confinement::SmallVec; use commit_verify::Conceal; @@ -52,10 +50,6 @@ impl KnownState for RevealedValue {} impl KnownState for RevealedData {} impl KnownState for RevealedAttach {} -pub trait FungibleState: KnownState + Sum + Add + AddAssign + Sub + SubAssign {} - -impl FungibleState for Amount {} - #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB_STD)] diff --git a/src/contract/mod.rs b/src/contract/mod.rs index 4b2c4c45..9322b78e 100644 --- a/src/contract/mod.rs +++ b/src/contract/mod.rs @@ -23,7 +23,7 @@ mod assignments; mod bundle; mod merge_reveal; -pub use assignments::{FungibleState, KnownState, OutputAssignment, TypedAssignsExt, WitnessInfo}; +pub use assignments::{KnownState, OutputAssignment, TypedAssignsExt, WitnessInfo}; pub use bundle::{BundleExt, RevealError}; pub use merge_reveal::{MergeReveal, MergeRevealError}; use rgb::vm::OrdOpRef; diff --git a/src/interface/contract.rs b/src/interface/contract.rs index d7abdc97..08a58759 100644 --- a/src/interface/contract.rs +++ b/src/interface/contract.rs @@ -30,7 +30,7 @@ use rgb::{ use strict_encoding::{FieldName, StrictDecode, StrictDumb, StrictEncode}; use strict_types::{StrictVal, TypeSystem}; -use crate::contract::{FungibleState, KnownState, OutputAssignment, WitnessInfo}; +use crate::contract::{KnownState, OutputAssignment, WitnessInfo}; use crate::info::ContractInfo; use crate::interface::{AssignmentsFilter, IfaceImpl}; use crate::persistence::ContractStateRead; @@ -90,27 +90,52 @@ pub type AttachAllocation = OutputAssignment; derive(Serialize, Deserialize), serde(crate = "serde_crate", rename_all = "camelCase") )] -pub enum ContractOp { +pub enum NonFungibleOp { Genesis { - issued: S, + state: S, + to: XOutputSeal, + }, + Received { + opid: OpId, + state: S, + to: XOutputSeal, + witness: WitnessInfo, + }, + Sent { + opid: OpId, + state: S, + to: XOutputSeal, + witness: WitnessInfo, + }, +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +pub enum FungibleOp { + Genesis { + issued: Amount, to: BTreeSet, }, Received { opids: BTreeSet, - amount: S, + amount: Amount, to: BTreeSet, witness: WitnessInfo, }, Sent { opids: BTreeSet, - amount: S, + amount: Amount, to: BTreeSet, witness: WitnessInfo, }, } -impl ContractOp { - fn new_genesis(our_allocations: &HashSet>) -> Self { +impl FungibleOp { + fn new_genesis(our_allocations: &HashSet>) -> Self { let to = our_allocations.iter().map(|a| a.seal).collect(); let issued = our_allocations.iter().map(|a| a.state.clone()).sum(); Self::Genesis { issued, to } @@ -118,8 +143,8 @@ impl ContractOp { fn new_sent( witness: WitnessInfo, - ext_allocations: &HashSet>, - our_allocations: &HashSet>, + ext_allocations: &HashSet>, + our_allocations: &HashSet>, ) -> Self { let opids = our_allocations.iter().map(|a| a.opout.op).collect(); let to = ext_allocations.iter().map(|a| a.seal).collect(); @@ -133,7 +158,10 @@ impl ContractOp { } } - fn new_received(witness: WitnessInfo, our_allocations: &HashSet>) -> Self { + fn new_received( + witness: WitnessInfo, + our_allocations: &HashSet>, + ) -> Self { let opids = our_allocations.iter().map(|a| a.opout.op).collect(); let to = our_allocations.iter().map(|a| a.seal).collect(); let amount = our_allocations.iter().map(|a| a.state.clone()).sum(); @@ -293,31 +321,30 @@ impl ContractIface { pub fn fungible_operations( &self, - state_name: impl Into, filter_outpoints: impl AssignmentsFilter, filter_witnesses: impl AssignmentsFilter, - ) -> Result>, ContractError> { - let state_name = state_name.into(); - + ) -> Result, ContractError> { // get all allocations which ever belonged to this wallet and store them by witness id - let allocations_our_outpoint = self.fungible(state_name.clone(), filter_outpoints)?.fold( - HashMap::<_, HashSet<_>>::new(), - |mut map, a| { - map.entry(a.witness).or_default().insert(a); + let allocations_our_outpoint = self + .state + .fungible_all() + .filter(move |outp| filter_outpoints.should_include(outp.seal, outp.witness)) + .fold(HashMap::<_, HashSet<_>>::new(), |mut map, a| { + map.entry(a.witness).or_default().insert(a.transmute()); map - }, - ); + }); // get all allocations which has a witness transaction belonging to this wallet - let allocations_our_witness = self.fungible(state_name, filter_witnesses)?.fold( - HashMap::<_, HashSet<_>>::new(), - |mut map, a| { + let allocations_our_witness = self + .state + .fungible_all() + .filter(move |outp| filter_witnesses.should_include(outp.seal, outp.witness)) + .fold(HashMap::<_, HashSet<_>>::new(), |mut map, a| { let witness = a.witness.expect( "all empty witnesses must be already filtered out by wallet.filter_witness()", ); - map.entry(witness).or_default().insert(a); + map.entry(witness).or_default().insert(a.transmute()); map - }, - ); + }); // gather all witnesses from both sets let mut witness_ids = allocations_our_witness @@ -330,7 +357,7 @@ impl ContractIface { let mut ops = Vec::with_capacity(witness_ids.len() + 1); // add allocations with no witness to the beginning of the history if let Some(genesis_allocations) = allocations_our_outpoint.get(&None) { - ops.push(ContractOp::new_genesis(genesis_allocations)); + ops.push(FungibleOp::new_genesis(genesis_allocations)); } for witness_id in witness_ids { let our_outpoint = allocations_our_outpoint.get(&Some(witness_id)); @@ -345,7 +372,7 @@ impl ContractIface { (Some(our_allocations), Some(all_allocations)) => { // all_allocations - our_allocations = external payments let ext_allocations = all_allocations.difference(our_allocations); - ContractOp::new_sent( + FungibleOp::new_sent( witness_info, &ext_allocations.copied().collect(), our_allocations, @@ -353,12 +380,12 @@ impl ContractIface { } // the same as above, but the payment has no change (None, Some(ext_allocations)) => { - ContractOp::new_sent(witness_info, ext_allocations, &set![]) + FungibleOp::new_sent(witness_info, ext_allocations, &set![]) } // we own allocation but the witness transaction was made by other wallet: // this is an incoming payment to us. (Some(our_allocations), None) => { - ContractOp::new_received(witness_info, our_allocations) + FungibleOp::new_received(witness_info, our_allocations) } // these can't get into the `witness_ids` due to the used filters (None, None) => unreachable!("broken allocation filters"), diff --git a/src/interface/mod.rs b/src/interface/mod.rs index 6f9f7d01..36770aaa 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -34,8 +34,8 @@ mod inheritance; pub use builder::{BuilderError, ContractBuilder, TransitionBuilder, TxOutpoint}; pub use contract::{ - AllocatedState, AttachAllocation, ContractError, ContractIface, ContractOp, DataAllocation, - FungibleAllocation, OwnedAllocation, RightsAllocation, + AllocatedState, AttachAllocation, ContractError, ContractIface, DataAllocation, + FungibleAllocation, FungibleOp, NonFungibleOp, OwnedAllocation, RightsAllocation, }; pub use contractum::IfaceDisplay; pub use filter::{AssignmentsFilter, FilterExclude, FilterIncludeAll}; From 53fa9a520b44973e34ec9b0e09d7ff645b24d652 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 20 Sep 2024 15:15:45 +0200 Subject: [PATCH 12/21] iface: support non-fungible history --- src/contract/assignments.rs | 3 +- src/interface/contract.rs | 188 +++++++++++++++++++++++++++++++++++- src/interface/mod.rs | 2 +- 3 files changed, 187 insertions(+), 6 deletions(-) diff --git a/src/contract/assignments.rs b/src/contract/assignments.rs index 85dfec4a..96ae5cf8 100644 --- a/src/contract/assignments.rs +++ b/src/contract/assignments.rs @@ -22,6 +22,7 @@ use std::cmp::Ordering; use std::collections::HashMap; use std::fmt::Debug; +use std::hash::Hash; use amplify::confinement::SmallVec; use commit_verify::Conceal; @@ -39,7 +40,7 @@ use crate::LIB_NAME_RGB_STD; /// Trait used by contract state. Unlike [`ExposedState`] it doesn't allow /// concealment of the state, i.e. may contain incomplete data without blinding /// factors, asset tags etc. -pub trait KnownState: Debug + StrictDumb + StrictEncode + StrictDecode + Eq + Clone {} +pub trait KnownState: Debug + StrictDumb + StrictEncode + StrictDecode + Eq + Clone + Hash {} impl KnownState for () {} impl KnownState for VoidState {} diff --git a/src/interface/contract.rs b/src/interface/contract.rs index 08a58759..53313e3f 100644 --- a/src/interface/contract.rs +++ b/src/interface/contract.rs @@ -88,11 +88,24 @@ pub type AttachAllocation = OutputAssignment; #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), - serde(crate = "serde_crate", rename_all = "camelCase") + serde(crate = "serde_crate", rename_all = "camelCase", tag = "form") +)] +pub enum ContractOp { + Rights(NonFungibleOp), + Fungible(FungibleOp), + Data(NonFungibleOp), + Attach(NonFungibleOp), +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase", tag = "type") )] pub enum NonFungibleOp { Genesis { - state: S, + issued: S, to: XOutputSeal, }, Received { @@ -109,11 +122,46 @@ pub enum NonFungibleOp { }, } +impl NonFungibleOp { + fn new_genesis( + our_allocations: HashSet>, + ) -> impl ExactSizeIterator { + our_allocations.into_iter().map(|a| Self::Genesis { + issued: a.state, + to: a.seal, + }) + } + + fn new_sent( + witness: WitnessInfo, + ext_allocations: HashSet>, + ) -> impl ExactSizeIterator { + ext_allocations.into_iter().map(move |a| Self::Sent { + opid: a.opout.op, + state: a.state, + to: a.seal, + witness, + }) + } + + fn new_received( + witness: WitnessInfo, + our_allocations: HashSet>, + ) -> impl ExactSizeIterator { + our_allocations.into_iter().map(move |a| Self::Received { + opid: a.opout.op, + state: a.state, + to: a.seal, + witness, + }) + } +} + #[derive(Clone, Eq, PartialEq, Hash, Debug)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), - serde(crate = "serde_crate", rename_all = "camelCase") + serde(crate = "serde_crate", rename_all = "camelCase", tag = "type") )] pub enum FungibleOp { Genesis { @@ -319,7 +367,34 @@ impl ContractIface { self.allocations(outpoint) } - pub fn fungible_operations( + pub fn history( + &self, + filter_outpoints: impl AssignmentsFilter + Clone, + filter_witnesses: impl AssignmentsFilter + Clone, + ) -> Result, ContractError> { + Ok(self + .history_fungible(filter_outpoints.clone(), filter_witnesses.clone())? + .into_iter() + .map(ContractOp::Fungible) + .chain( + self.history_rights(filter_outpoints.clone(), filter_witnesses.clone())? + .into_iter() + .map(ContractOp::Rights), + ) + .chain( + self.history_data(filter_outpoints.clone(), filter_witnesses.clone())? + .into_iter() + .map(ContractOp::Data), + ) + .chain( + self.history_attach(filter_outpoints, filter_witnesses)? + .into_iter() + .map(ContractOp::Attach), + ) + .collect()) + } + + pub fn history_fungible( &self, filter_outpoints: impl AssignmentsFilter, filter_witnesses: impl AssignmentsFilter, @@ -396,6 +471,111 @@ impl ContractIface { Ok(ops) } + pub fn history_rights( + &self, + filter_outpoints: impl AssignmentsFilter, + filter_witnesses: impl AssignmentsFilter, + ) -> Result>, ContractError> { + self.history_non_fungible(|state| state.rights_all(), filter_outpoints, filter_witnesses) + } + + pub fn history_data( + &self, + filter_outpoints: impl AssignmentsFilter, + filter_witnesses: impl AssignmentsFilter, + ) -> Result>, ContractError> { + self.history_non_fungible(|state| state.data_all(), filter_outpoints, filter_witnesses) + } + + pub fn history_attach( + &self, + filter_outpoints: impl AssignmentsFilter, + filter_witnesses: impl AssignmentsFilter, + ) -> Result>, ContractError> { + self.history_non_fungible(|state| state.attach_all(), filter_outpoints, filter_witnesses) + } + + fn history_non_fungible< + 'c, + State: KnownState + From, + T: KnownState + 'c, + I: Iterator>, + >( + &'c self, + state: impl Fn(&'c S) -> I, + filter_outpoints: impl AssignmentsFilter, + filter_witnesses: impl AssignmentsFilter, + ) -> Result>, ContractError> { + // get all allocations which ever belonged to this wallet and store them by witness id + let mut allocations_our_outpoint = state(&self.state) + .filter(move |outp| filter_outpoints.should_include(outp.seal, outp.witness)) + .fold(HashMap::<_, HashSet<_>>::new(), |mut map, a| { + map.entry(a.witness) + .or_default() + .insert(a.clone().transmute()); + map + }); + // get all allocations which has a witness transaction belonging to this wallet + let mut allocations_our_witness = state(&self.state) + .filter(move |outp| filter_witnesses.should_include(outp.seal, outp.witness)) + .fold(HashMap::<_, HashSet<_>>::new(), |mut map, a| { + let witness = a.witness.expect( + "all empty witnesses must be already filtered out by wallet.filter_witness()", + ); + map.entry(witness) + .or_default() + .insert(a.clone().transmute()); + map + }); + + // gather all witnesses from both sets + let mut witness_ids = allocations_our_witness + .keys() + .cloned() + .collect::>(); + witness_ids.extend(allocations_our_outpoint.keys().filter_map(|x| *x)); + + // reconstruct contract history from the wallet perspective + let mut ops = Vec::with_capacity(witness_ids.len() + 1); + // add allocations with no witness to the beginning of the history + if let Some(genesis_allocations) = allocations_our_outpoint.remove(&None) { + ops.extend(NonFungibleOp::new_genesis(genesis_allocations)); + } + for witness_id in witness_ids { + let our_outpoint = allocations_our_outpoint.remove(&Some(witness_id)); + let our_witness = allocations_our_witness.remove(&witness_id); + let witness_info = self.witness_info(witness_id).expect( + "witness id was returned from the contract state above, so it must be there", + ); + match (our_outpoint, our_witness) { + // we own both allocation and witness transaction: these allocations are changes and + // outgoing payments. The difference between the change and the payments are whether + // a specific allocation is listed in the first tuple pattern field. + (Some(our_allocations), Some(all_allocations)) => { + // all_allocations - our_allocations = external payments + let ext_allocations = all_allocations.difference(&our_allocations); + ops.extend(NonFungibleOp::new_sent( + witness_info, + ext_allocations.cloned().collect(), + )) + } + // the same as above, but the payment has no change + (None, Some(ext_allocations)) => { + ops.extend(NonFungibleOp::new_sent(witness_info, ext_allocations)) + } + // we own allocation but the witness transaction was made by other wallet: + // this is an incoming payment to us. + (Some(our_allocations), None) => { + ops.extend(NonFungibleOp::new_received(witness_info, our_allocations)) + } + // these can't get into the `witness_ids` due to the used filters + (None, None) => unreachable!("broken allocation filters"), + }; + } + + Ok(ops) + } + pub fn witness_info(&self, witness_id: XWitnessId) -> Option { let ord = self.state.witness_ord(witness_id)?; Some(WitnessInfo { diff --git a/src/interface/mod.rs b/src/interface/mod.rs index 36770aaa..ae2b0aae 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -34,7 +34,7 @@ mod inheritance; pub use builder::{BuilderError, ContractBuilder, TransitionBuilder, TxOutpoint}; pub use contract::{ - AllocatedState, AttachAllocation, ContractError, ContractIface, DataAllocation, + AllocatedState, AttachAllocation, ContractError, ContractIface, ContractOp, DataAllocation, FungibleAllocation, FungibleOp, NonFungibleOp, OwnedAllocation, RightsAllocation, }; pub use contractum::IfaceDisplay; From d5170f9d6892ba68a321ba26fa21b908909f1edf Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 20 Sep 2024 15:40:51 +0200 Subject: [PATCH 13/21] iface: remove code duplication for history methods --- src/interface/contract.rs | 210 ++++++++++++++++---------------------- 1 file changed, 87 insertions(+), 123 deletions(-) diff --git a/src/interface/contract.rs b/src/interface/contract.rs index 53313e3f..7c8e181a 100644 --- a/src/interface/contract.rs +++ b/src/interface/contract.rs @@ -21,6 +21,7 @@ use std::borrow::Borrow; use std::collections::{BTreeSet, HashMap, HashSet}; +use std::iter; use invoice::{Allocation, Amount}; use rgb::{ @@ -97,6 +98,27 @@ pub enum ContractOp { Attach(NonFungibleOp), } +// nobody knows what this strange warning by rustc compiler means +#[allow(opaque_hidden_inferred_bound)] +pub trait ContractOperation { + type State: KnownState; + + fn new_genesis( + our_allocations: HashSet>, + ) -> impl ExactSizeIterator; + + fn new_sent( + witness: WitnessInfo, + ext_allocations: HashSet>, + _: HashSet>, + ) -> impl ExactSizeIterator; + + fn new_received( + witness: WitnessInfo, + our_allocations: HashSet>, + ) -> impl ExactSizeIterator; +} + #[derive(Clone, Eq, PartialEq, Hash, Debug)] #[cfg_attr( feature = "serde", @@ -122,7 +144,9 @@ pub enum NonFungibleOp { }, } -impl NonFungibleOp { +impl ContractOperation for NonFungibleOp { + type State = S; + fn new_genesis( our_allocations: HashSet>, ) -> impl ExactSizeIterator { @@ -135,6 +159,7 @@ impl NonFungibleOp { fn new_sent( witness: WitnessInfo, ext_allocations: HashSet>, + _: HashSet>, ) -> impl ExactSizeIterator { ext_allocations.into_iter().map(move |a| Self::Sent { opid: a.opout.op, @@ -182,43 +207,47 @@ pub enum FungibleOp { }, } -impl FungibleOp { - fn new_genesis(our_allocations: &HashSet>) -> Self { +impl ContractOperation for FungibleOp { + type State = Amount; + + fn new_genesis( + our_allocations: HashSet>, + ) -> impl ExactSizeIterator { let to = our_allocations.iter().map(|a| a.seal).collect(); - let issued = our_allocations.iter().map(|a| a.state.clone()).sum(); - Self::Genesis { issued, to } + let issued = our_allocations.iter().map(|a| a.state).sum(); + iter::once(Self::Genesis { issued, to }) } fn new_sent( witness: WitnessInfo, - ext_allocations: &HashSet>, - our_allocations: &HashSet>, - ) -> Self { + ext_allocations: HashSet>, + our_allocations: HashSet>, + ) -> impl ExactSizeIterator { let opids = our_allocations.iter().map(|a| a.opout.op).collect(); let to = ext_allocations.iter().map(|a| a.seal).collect(); let mut amount = ext_allocations.iter().map(|a| a.state.clone()).sum(); - amount -= our_allocations.iter().map(|a| a.state.clone()).sum(); - Self::Sent { + amount -= our_allocations.iter().map(|a| a.state).sum(); + iter::once(Self::Sent { opids, amount, to, witness, - } + }) } fn new_received( witness: WitnessInfo, - our_allocations: &HashSet>, - ) -> Self { + our_allocations: HashSet>, + ) -> impl ExactSizeIterator { let opids = our_allocations.iter().map(|a| a.opout.op).collect(); let to = our_allocations.iter().map(|a| a.seal).collect(); - let amount = our_allocations.iter().map(|a| a.state.clone()).sum(); - Self::Received { + let amount = our_allocations.iter().map(|a| a.state).sum(); + iter::once(Self::Received { opids, amount, to, witness, - } + }) } } @@ -394,30 +423,39 @@ impl ContractIface { .collect()) } - pub fn history_fungible( - &self, + fn operations< + 'c, + Op: ContractOperation, + T: KnownState + 'c, + I: Iterator>, + >( + &'c self, + state: impl Fn(&'c S) -> I, filter_outpoints: impl AssignmentsFilter, filter_witnesses: impl AssignmentsFilter, - ) -> Result, ContractError> { + ) -> Result, ContractError> + where + Op::State: From, + { // get all allocations which ever belonged to this wallet and store them by witness id - let allocations_our_outpoint = self - .state - .fungible_all() + let mut allocations_our_outpoint = state(&self.state) .filter(move |outp| filter_outpoints.should_include(outp.seal, outp.witness)) .fold(HashMap::<_, HashSet<_>>::new(), |mut map, a| { - map.entry(a.witness).or_default().insert(a.transmute()); + map.entry(a.witness) + .or_default() + .insert(a.clone().transmute()); map }); // get all allocations which has a witness transaction belonging to this wallet - let allocations_our_witness = self - .state - .fungible_all() + let mut allocations_our_witness = state(&self.state) .filter(move |outp| filter_witnesses.should_include(outp.seal, outp.witness)) .fold(HashMap::<_, HashSet<_>>::new(), |mut map, a| { let witness = a.witness.expect( "all empty witnesses must be already filtered out by wallet.filter_witness()", ); - map.entry(witness).or_default().insert(a.transmute()); + map.entry(witness) + .or_default() + .insert(a.clone().transmute()); map }); @@ -431,52 +469,59 @@ impl ContractIface { // reconstruct contract history from the wallet perspective let mut ops = Vec::with_capacity(witness_ids.len() + 1); // add allocations with no witness to the beginning of the history - if let Some(genesis_allocations) = allocations_our_outpoint.get(&None) { - ops.push(FungibleOp::new_genesis(genesis_allocations)); + if let Some(genesis_allocations) = allocations_our_outpoint.remove(&None) { + ops.extend(Op::new_genesis(genesis_allocations)); } for witness_id in witness_ids { - let our_outpoint = allocations_our_outpoint.get(&Some(witness_id)); - let our_witness = allocations_our_witness.get(&witness_id); + let our_outpoint = allocations_our_outpoint.remove(&Some(witness_id)); + let our_witness = allocations_our_witness.remove(&witness_id); let witness_info = self.witness_info(witness_id).expect( "witness id was returned from the contract state above, so it must be there", ); - let op = match (our_outpoint, our_witness) { + match (our_outpoint, our_witness) { // we own both allocation and witness transaction: these allocations are changes and // outgoing payments. The difference between the change and the payments are whether // a specific allocation is listed in the first tuple pattern field. (Some(our_allocations), Some(all_allocations)) => { // all_allocations - our_allocations = external payments - let ext_allocations = all_allocations.difference(our_allocations); - FungibleOp::new_sent( + let ext_allocations = all_allocations.difference(&our_allocations); + ops.extend(Op::new_sent( witness_info, - &ext_allocations.copied().collect(), + ext_allocations.cloned().collect(), our_allocations, - ) + )) } // the same as above, but the payment has no change (None, Some(ext_allocations)) => { - FungibleOp::new_sent(witness_info, ext_allocations, &set![]) + ops.extend(Op::new_sent(witness_info, ext_allocations, set![])) } // we own allocation but the witness transaction was made by other wallet: // this is an incoming payment to us. (Some(our_allocations), None) => { - FungibleOp::new_received(witness_info, our_allocations) + ops.extend(Op::new_received(witness_info, our_allocations)) } // these can't get into the `witness_ids` due to the used filters (None, None) => unreachable!("broken allocation filters"), }; - ops.push(op); } Ok(ops) } + pub fn history_fungible( + &self, + filter_outpoints: impl AssignmentsFilter, + filter_witnesses: impl AssignmentsFilter, + ) -> Result, ContractError> { + self.operations(|state| state.fungible_all(), filter_outpoints, filter_witnesses) + } + pub fn history_rights( &self, filter_outpoints: impl AssignmentsFilter, filter_witnesses: impl AssignmentsFilter, ) -> Result>, ContractError> { - self.history_non_fungible(|state| state.rights_all(), filter_outpoints, filter_witnesses) + self.operations(|state| state.rights_all(), filter_outpoints, filter_witnesses) } pub fn history_data( @@ -484,7 +529,7 @@ impl ContractIface { filter_outpoints: impl AssignmentsFilter, filter_witnesses: impl AssignmentsFilter, ) -> Result>, ContractError> { - self.history_non_fungible(|state| state.data_all(), filter_outpoints, filter_witnesses) + self.operations(|state| state.data_all(), filter_outpoints, filter_witnesses) } pub fn history_attach( @@ -492,88 +537,7 @@ impl ContractIface { filter_outpoints: impl AssignmentsFilter, filter_witnesses: impl AssignmentsFilter, ) -> Result>, ContractError> { - self.history_non_fungible(|state| state.attach_all(), filter_outpoints, filter_witnesses) - } - - fn history_non_fungible< - 'c, - State: KnownState + From, - T: KnownState + 'c, - I: Iterator>, - >( - &'c self, - state: impl Fn(&'c S) -> I, - filter_outpoints: impl AssignmentsFilter, - filter_witnesses: impl AssignmentsFilter, - ) -> Result>, ContractError> { - // get all allocations which ever belonged to this wallet and store them by witness id - let mut allocations_our_outpoint = state(&self.state) - .filter(move |outp| filter_outpoints.should_include(outp.seal, outp.witness)) - .fold(HashMap::<_, HashSet<_>>::new(), |mut map, a| { - map.entry(a.witness) - .or_default() - .insert(a.clone().transmute()); - map - }); - // get all allocations which has a witness transaction belonging to this wallet - let mut allocations_our_witness = state(&self.state) - .filter(move |outp| filter_witnesses.should_include(outp.seal, outp.witness)) - .fold(HashMap::<_, HashSet<_>>::new(), |mut map, a| { - let witness = a.witness.expect( - "all empty witnesses must be already filtered out by wallet.filter_witness()", - ); - map.entry(witness) - .or_default() - .insert(a.clone().transmute()); - map - }); - - // gather all witnesses from both sets - let mut witness_ids = allocations_our_witness - .keys() - .cloned() - .collect::>(); - witness_ids.extend(allocations_our_outpoint.keys().filter_map(|x| *x)); - - // reconstruct contract history from the wallet perspective - let mut ops = Vec::with_capacity(witness_ids.len() + 1); - // add allocations with no witness to the beginning of the history - if let Some(genesis_allocations) = allocations_our_outpoint.remove(&None) { - ops.extend(NonFungibleOp::new_genesis(genesis_allocations)); - } - for witness_id in witness_ids { - let our_outpoint = allocations_our_outpoint.remove(&Some(witness_id)); - let our_witness = allocations_our_witness.remove(&witness_id); - let witness_info = self.witness_info(witness_id).expect( - "witness id was returned from the contract state above, so it must be there", - ); - match (our_outpoint, our_witness) { - // we own both allocation and witness transaction: these allocations are changes and - // outgoing payments. The difference between the change and the payments are whether - // a specific allocation is listed in the first tuple pattern field. - (Some(our_allocations), Some(all_allocations)) => { - // all_allocations - our_allocations = external payments - let ext_allocations = all_allocations.difference(&our_allocations); - ops.extend(NonFungibleOp::new_sent( - witness_info, - ext_allocations.cloned().collect(), - )) - } - // the same as above, but the payment has no change - (None, Some(ext_allocations)) => { - ops.extend(NonFungibleOp::new_sent(witness_info, ext_allocations)) - } - // we own allocation but the witness transaction was made by other wallet: - // this is an incoming payment to us. - (Some(our_allocations), None) => { - ops.extend(NonFungibleOp::new_received(witness_info, our_allocations)) - } - // these can't get into the `witness_ids` due to the used filters - (None, None) => unreachable!("broken allocation filters"), - }; - } - - Ok(ops) + self.operations(|state| state.attach_all(), filter_outpoints, filter_witnesses) } pub fn witness_info(&self, witness_id: XWitnessId) -> Option { From 4edbacf46f71fd2327f4c3f102a99408d01d3895 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 20 Sep 2024 16:16:46 +0200 Subject: [PATCH 14/21] iface: make history methods free of errors --- src/interface/contract.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/interface/contract.rs b/src/interface/contract.rs index 7c8e181a..f5e425fe 100644 --- a/src/interface/contract.rs +++ b/src/interface/contract.rs @@ -400,27 +400,26 @@ impl ContractIface { &self, filter_outpoints: impl AssignmentsFilter + Clone, filter_witnesses: impl AssignmentsFilter + Clone, - ) -> Result, ContractError> { - Ok(self - .history_fungible(filter_outpoints.clone(), filter_witnesses.clone())? + ) -> Vec { + self.history_fungible(filter_outpoints.clone(), filter_witnesses.clone()) .into_iter() .map(ContractOp::Fungible) .chain( - self.history_rights(filter_outpoints.clone(), filter_witnesses.clone())? + self.history_rights(filter_outpoints.clone(), filter_witnesses.clone()) .into_iter() .map(ContractOp::Rights), ) .chain( - self.history_data(filter_outpoints.clone(), filter_witnesses.clone())? + self.history_data(filter_outpoints.clone(), filter_witnesses.clone()) .into_iter() .map(ContractOp::Data), ) .chain( - self.history_attach(filter_outpoints, filter_witnesses)? + self.history_attach(filter_outpoints, filter_witnesses) .into_iter() .map(ContractOp::Attach), ) - .collect()) + .collect() } fn operations< @@ -433,7 +432,7 @@ impl ContractIface { state: impl Fn(&'c S) -> I, filter_outpoints: impl AssignmentsFilter, filter_witnesses: impl AssignmentsFilter, - ) -> Result, ContractError> + ) -> Vec where Op::State: From, { @@ -505,14 +504,14 @@ impl ContractIface { }; } - Ok(ops) + ops } pub fn history_fungible( &self, filter_outpoints: impl AssignmentsFilter, filter_witnesses: impl AssignmentsFilter, - ) -> Result, ContractError> { + ) -> Vec { self.operations(|state| state.fungible_all(), filter_outpoints, filter_witnesses) } @@ -520,7 +519,7 @@ impl ContractIface { &self, filter_outpoints: impl AssignmentsFilter, filter_witnesses: impl AssignmentsFilter, - ) -> Result>, ContractError> { + ) -> Vec> { self.operations(|state| state.rights_all(), filter_outpoints, filter_witnesses) } @@ -528,7 +527,7 @@ impl ContractIface { &self, filter_outpoints: impl AssignmentsFilter, filter_witnesses: impl AssignmentsFilter, - ) -> Result>, ContractError> { + ) -> Vec> { self.operations(|state| state.data_all(), filter_outpoints, filter_witnesses) } @@ -536,7 +535,7 @@ impl ContractIface { &self, filter_outpoints: impl AssignmentsFilter, filter_witnesses: impl AssignmentsFilter, - ) -> Result>, ContractError> { + ) -> Vec> { self.operations(|state| state.attach_all(), filter_outpoints, filter_witnesses) } From dbfa80b2600dac1f6e694b960e09a73be1dbc2b5 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 20 Sep 2024 17:27:31 +0200 Subject: [PATCH 15/21] iface: simplify contract operations type hierarchy --- src/contract/assignments.rs | 38 +++-- src/interface/contract.rs | 302 ++++++++++++++++++------------------ src/interface/mod.rs | 2 +- 3 files changed, 180 insertions(+), 162 deletions(-) diff --git a/src/contract/assignments.rs b/src/contract/assignments.rs index 96ae5cf8..d4967520 100644 --- a/src/contract/assignments.rs +++ b/src/contract/assignments.rs @@ -40,16 +40,34 @@ use crate::LIB_NAME_RGB_STD; /// Trait used by contract state. Unlike [`ExposedState`] it doesn't allow /// concealment of the state, i.e. may contain incomplete data without blinding /// factors, asset tags etc. -pub trait KnownState: Debug + StrictDumb + StrictEncode + StrictDecode + Eq + Clone + Hash {} - -impl KnownState for () {} -impl KnownState for VoidState {} -impl KnownState for DataState {} -impl KnownState for Amount {} -impl KnownState for AttachState {} -impl KnownState for RevealedValue {} -impl KnownState for RevealedData {} -impl KnownState for RevealedAttach {} +pub trait KnownState: Debug + StrictDumb + StrictEncode + StrictDecode + Eq + Clone + Hash { + const IS_FUNGIBLE: bool; +} + +impl KnownState for () { + const IS_FUNGIBLE: bool = false; +} +impl KnownState for VoidState { + const IS_FUNGIBLE: bool = false; +} +impl KnownState for DataState { + const IS_FUNGIBLE: bool = false; +} +impl KnownState for Amount { + const IS_FUNGIBLE: bool = true; +} +impl KnownState for AttachState { + const IS_FUNGIBLE: bool = false; +} +impl KnownState for RevealedValue { + const IS_FUNGIBLE: bool = true; +} +impl KnownState for RevealedData { + const IS_FUNGIBLE: bool = false; +} +impl KnownState for RevealedAttach { + const IS_FUNGIBLE: bool = false; +} #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] diff --git a/src/interface/contract.rs b/src/interface/contract.rs index f5e425fe..cd1180e6 100644 --- a/src/interface/contract.rs +++ b/src/interface/contract.rs @@ -21,12 +21,11 @@ use std::borrow::Borrow; use std::collections::{BTreeSet, HashMap, HashSet}; -use std::iter; use invoice::{Allocation, Amount}; use rgb::{ - AttachState, ContractId, DataState, OpId, RevealedAttach, RevealedData, RevealedValue, Schema, - VoidState, XOutpoint, XOutputSeal, XWitnessId, + AssignmentType, AttachState, ContractId, DataState, OpId, RevealedAttach, RevealedData, + RevealedValue, Schema, VoidState, XOutpoint, XOutputSeal, XWitnessId, }; use strict_encoding::{FieldName, StrictDecode, StrictDumb, StrictEncode}; use strict_types::{StrictVal, TypeSystem}; @@ -77,7 +76,18 @@ pub enum AllocatedState { Attachment(AttachState), } -impl KnownState for AllocatedState {} +impl KnownState for AllocatedState { + const IS_FUNGIBLE: bool = false; +} + +impl AllocatedState { + fn unwrap_fungible(&self) -> Amount { + match self { + AllocatedState::Amount(amount) => *amount, + _ => panic!("unwrapping non-fungible state"), + } + } +} pub type OwnedAllocation = OutputAssignment; pub type RightsAllocation = OutputAssignment; @@ -85,38 +95,16 @@ pub type FungibleAllocation = OutputAssignment; pub type DataAllocation = OutputAssignment; pub type AttachAllocation = OutputAssignment; -#[derive(Clone, Eq, PartialEq, Hash, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), - serde(crate = "serde_crate", rename_all = "camelCase", tag = "form") + serde(crate = "serde_crate", rename_all = "camelCase") )] -pub enum ContractOp { - Rights(NonFungibleOp), - Fungible(FungibleOp), - Data(NonFungibleOp), - Attach(NonFungibleOp), -} - -// nobody knows what this strange warning by rustc compiler means -#[allow(opaque_hidden_inferred_bound)] -pub trait ContractOperation { - type State: KnownState; - - fn new_genesis( - our_allocations: HashSet>, - ) -> impl ExactSizeIterator; - - fn new_sent( - witness: WitnessInfo, - ext_allocations: HashSet>, - _: HashSet>, - ) -> impl ExactSizeIterator; - - fn new_received( - witness: WitnessInfo, - our_allocations: HashSet>, - ) -> impl ExactSizeIterator; +pub enum OpDirection { + Issued, + Received, + Sent, } #[derive(Clone, Eq, PartialEq, Hash, Debug)] @@ -125,129 +113,126 @@ pub trait ContractOperation { derive(Serialize, Deserialize), serde(crate = "serde_crate", rename_all = "camelCase", tag = "type") )] -pub enum NonFungibleOp { - Genesis { - issued: S, - to: XOutputSeal, - }, - Received { - opid: OpId, - state: S, - to: XOutputSeal, - witness: WitnessInfo, - }, - Sent { - opid: OpId, - state: S, - to: XOutputSeal, - witness: WitnessInfo, - }, +pub struct ContractOp { + direction: OpDirection, + ty: AssignmentType, + opids: BTreeSet, + state: AllocatedState, + to: BTreeSet, + witness: Option, } -impl ContractOperation for NonFungibleOp { - type State = S; +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") +} - fn new_genesis( - our_allocations: HashSet>, +impl ContractOp { + fn non_fungible_genesis( + our_allocations: HashSet, ) -> impl ExactSizeIterator { - our_allocations.into_iter().map(|a| Self::Genesis { - issued: a.state, - to: a.seal, + our_allocations.into_iter().map(|a| Self { + direction: OpDirection::Issued, + ty: a.opout.ty, + opids: bset![a.opout.op], + state: a.state, + to: bset![a.seal], + witness: None, }) } - fn new_sent( + fn non_fungible_sent( witness: WitnessInfo, - ext_allocations: HashSet>, - _: HashSet>, + ext_allocations: HashSet, + _: HashSet, ) -> impl ExactSizeIterator { - ext_allocations.into_iter().map(move |a| Self::Sent { - opid: a.opout.op, + ext_allocations.into_iter().map(move |a| Self { + direction: OpDirection::Sent, + ty: a.opout.ty, + opids: bset![a.opout.op], state: a.state, - to: a.seal, - witness, + to: bset![a.seal], + witness: Some(witness), }) } - fn new_received( + fn non_fungible_received( witness: WitnessInfo, - our_allocations: HashSet>, + our_allocations: HashSet, ) -> impl ExactSizeIterator { - our_allocations.into_iter().map(move |a| Self::Received { - opid: a.opout.op, + our_allocations.into_iter().map(move |a| Self { + direction: OpDirection::Received, + ty: a.opout.ty, + opids: bset![a.opout.op], state: a.state, - to: a.seal, - witness, + to: bset![a.seal], + witness: Some(witness), }) } -} -#[derive(Clone, Eq, PartialEq, Hash, Debug)] -#[cfg_attr( - feature = "serde", - derive(Serialize, Deserialize), - serde(crate = "serde_crate", rename_all = "camelCase", tag = "type") -)] -pub enum FungibleOp { - Genesis { - issued: Amount, - to: BTreeSet, - }, - Received { - opids: BTreeSet, - amount: Amount, - to: BTreeSet, - witness: WitnessInfo, - }, - Sent { - opids: BTreeSet, - amount: Amount, - to: BTreeSet, - witness: WitnessInfo, - }, -} - -impl ContractOperation for FungibleOp { - type State = Amount; - - fn new_genesis( - our_allocations: HashSet>, - ) -> impl ExactSizeIterator { + fn fungible_genesis(our_allocations: HashSet) -> Self { let to = our_allocations.iter().map(|a| a.seal).collect(); - let issued = our_allocations.iter().map(|a| a.state).sum(); - iter::once(Self::Genesis { issued, to }) + let opids = our_allocations.iter().map(|a| a.opout.op).collect(); + let issued = our_allocations + .iter() + .map(|a| a.state.unwrap_fungible()) + .sum(); + Self { + direction: OpDirection::Issued, + ty: reduce_to_ty(our_allocations), + opids, + state: AllocatedState::Amount(issued), + to, + witness: None, + } } - fn new_sent( + fn fungible_sent( witness: WitnessInfo, - ext_allocations: HashSet>, - our_allocations: HashSet>, - ) -> impl ExactSizeIterator { + ext_allocations: HashSet, + our_allocations: HashSet, + ) -> Self { let opids = our_allocations.iter().map(|a| a.opout.op).collect(); let to = ext_allocations.iter().map(|a| a.seal).collect(); - let mut amount = ext_allocations.iter().map(|a| a.state.clone()).sum(); - amount -= our_allocations.iter().map(|a| a.state).sum(); - iter::once(Self::Sent { + let mut amount = ext_allocations + .iter() + .map(|a| a.state.unwrap_fungible()) + .sum(); + amount -= our_allocations + .iter() + .map(|a| a.state.unwrap_fungible()) + .sum(); + Self { + direction: OpDirection::Sent, + ty: reduce_to_ty(ext_allocations), opids, - amount, + state: AllocatedState::Amount(amount), to, - witness, - }) + witness: Some(witness), + } } - fn new_received( - witness: WitnessInfo, - our_allocations: HashSet>, - ) -> impl ExactSizeIterator { + fn fungible_received(witness: WitnessInfo, our_allocations: HashSet) -> Self { let opids = our_allocations.iter().map(|a| a.opout.op).collect(); let to = our_allocations.iter().map(|a| a.seal).collect(); - let amount = our_allocations.iter().map(|a| a.state).sum(); - iter::once(Self::Received { + let amount = our_allocations + .iter() + .map(|a| a.state.unwrap_fungible()) + .sum(); + Self { + direction: OpDirection::Received, + ty: reduce_to_ty(our_allocations), opids, - amount, + state: AllocatedState::Amount(amount), to, - witness, - }) + witness: Some(witness), + } } } @@ -403,38 +388,29 @@ impl ContractIface { ) -> Vec { self.history_fungible(filter_outpoints.clone(), filter_witnesses.clone()) .into_iter() - .map(ContractOp::Fungible) .chain( self.history_rights(filter_outpoints.clone(), filter_witnesses.clone()) - .into_iter() - .map(ContractOp::Rights), + .into_iter(), ) .chain( self.history_data(filter_outpoints.clone(), filter_witnesses.clone()) - .into_iter() - .map(ContractOp::Data), + .into_iter(), ) .chain( self.history_attach(filter_outpoints, filter_witnesses) - .into_iter() - .map(ContractOp::Attach), + .into_iter(), ) .collect() } - fn operations< - 'c, - Op: ContractOperation, - T: KnownState + 'c, - I: Iterator>, - >( + fn operations<'c, T: KnownState + 'c, I: Iterator>>( &'c self, state: impl Fn(&'c S) -> I, filter_outpoints: impl AssignmentsFilter, filter_witnesses: impl AssignmentsFilter, - ) -> Vec + ) -> Vec where - Op::State: From, + AllocatedState: From, { // get all allocations which ever belonged to this wallet and store them by witness id let mut allocations_our_outpoint = state(&self.state) @@ -442,7 +418,7 @@ impl ContractIface { .fold(HashMap::<_, HashSet<_>>::new(), |mut map, a| { map.entry(a.witness) .or_default() - .insert(a.clone().transmute()); + .insert(a.clone().transmute::()); map }); // get all allocations which has a witness transaction belonging to this wallet @@ -454,7 +430,7 @@ impl ContractIface { ); map.entry(witness) .or_default() - .insert(a.clone().transmute()); + .insert(a.clone().transmute::()); map }); @@ -469,7 +445,11 @@ impl ContractIface { let mut ops = Vec::with_capacity(witness_ids.len() + 1); // add allocations with no witness to the beginning of the history if let Some(genesis_allocations) = allocations_our_outpoint.remove(&None) { - ops.extend(Op::new_genesis(genesis_allocations)); + if T::IS_FUNGIBLE { + ops.push(ContractOp::fungible_genesis(genesis_allocations)); + } else { + ops.extend(ContractOp::non_fungible_genesis(genesis_allocations)); + } } for witness_id in witness_ids { let our_outpoint = allocations_our_outpoint.remove(&Some(witness_id)); @@ -484,20 +464,40 @@ impl ContractIface { (Some(our_allocations), Some(all_allocations)) => { // all_allocations - our_allocations = external payments let ext_allocations = all_allocations.difference(&our_allocations); - ops.extend(Op::new_sent( - witness_info, - ext_allocations.cloned().collect(), - our_allocations, - )) + if T::IS_FUNGIBLE { + ops.push(ContractOp::fungible_sent( + witness_info, + ext_allocations.cloned().collect(), + our_allocations, + )) + } else { + ops.extend(ContractOp::non_fungible_sent( + witness_info, + ext_allocations.cloned().collect(), + our_allocations, + )) + } } // the same as above, but the payment has no change (None, Some(ext_allocations)) => { - ops.extend(Op::new_sent(witness_info, ext_allocations, set![])) + if T::IS_FUNGIBLE { + ops.push(ContractOp::fungible_sent(witness_info, ext_allocations, set![])) + } else { + ops.extend(ContractOp::non_fungible_sent( + witness_info, + ext_allocations, + set![], + )) + } } // we own allocation but the witness transaction was made by other wallet: // this is an incoming payment to us. (Some(our_allocations), None) => { - ops.extend(Op::new_received(witness_info, our_allocations)) + if T::IS_FUNGIBLE { + ops.push(ContractOp::fungible_received(witness_info, our_allocations)) + } else { + ops.extend(ContractOp::non_fungible_received(witness_info, our_allocations)) + } } // these can't get into the `witness_ids` due to the used filters (None, None) => unreachable!("broken allocation filters"), @@ -511,7 +511,7 @@ impl ContractIface { &self, filter_outpoints: impl AssignmentsFilter, filter_witnesses: impl AssignmentsFilter, - ) -> Vec { + ) -> Vec { self.operations(|state| state.fungible_all(), filter_outpoints, filter_witnesses) } @@ -519,7 +519,7 @@ impl ContractIface { &self, filter_outpoints: impl AssignmentsFilter, filter_witnesses: impl AssignmentsFilter, - ) -> Vec> { + ) -> Vec { self.operations(|state| state.rights_all(), filter_outpoints, filter_witnesses) } @@ -527,7 +527,7 @@ impl ContractIface { &self, filter_outpoints: impl AssignmentsFilter, filter_witnesses: impl AssignmentsFilter, - ) -> Vec> { + ) -> Vec { self.operations(|state| state.data_all(), filter_outpoints, filter_witnesses) } @@ -535,7 +535,7 @@ impl ContractIface { &self, filter_outpoints: impl AssignmentsFilter, filter_witnesses: impl AssignmentsFilter, - ) -> Vec> { + ) -> Vec { self.operations(|state| state.attach_all(), filter_outpoints, filter_witnesses) } diff --git a/src/interface/mod.rs b/src/interface/mod.rs index ae2b0aae..6f9f7d01 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -35,7 +35,7 @@ mod inheritance; pub use builder::{BuilderError, ContractBuilder, TransitionBuilder, TxOutpoint}; pub use contract::{ AllocatedState, AttachAllocation, ContractError, ContractIface, ContractOp, DataAllocation, - FungibleAllocation, FungibleOp, NonFungibleOp, OwnedAllocation, RightsAllocation, + FungibleAllocation, OwnedAllocation, RightsAllocation, }; pub use contractum::IfaceDisplay; pub use filter::{AssignmentsFilter, FilterExclude, FilterIncludeAll}; From 268a3185ce544eda34e06d4d1c6422c609b4865a Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 20 Sep 2024 17:29:22 +0200 Subject: [PATCH 16/21] iface: impl Display for OpDirection --- src/interface/contract.rs | 3 ++- src/interface/mod.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/interface/contract.rs b/src/interface/contract.rs index cd1180e6..d97a3187 100644 --- a/src/interface/contract.rs +++ b/src/interface/contract.rs @@ -95,12 +95,13 @@ pub type FungibleAllocation = OutputAssignment; pub type DataAllocation = OutputAssignment; pub type AttachAllocation = OutputAssignment; -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate", rename_all = "camelCase") )] +#[display(lowercase)] pub enum OpDirection { Issued, Received, diff --git a/src/interface/mod.rs b/src/interface/mod.rs index 6f9f7d01..496ce87e 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -35,7 +35,7 @@ mod inheritance; pub use builder::{BuilderError, ContractBuilder, TransitionBuilder, TxOutpoint}; pub use contract::{ AllocatedState, AttachAllocation, ContractError, ContractIface, ContractOp, DataAllocation, - FungibleAllocation, OwnedAllocation, RightsAllocation, + FungibleAllocation, OpDirection, OwnedAllocation, RightsAllocation, }; pub use contractum::IfaceDisplay; pub use filter::{AssignmentsFilter, FilterExclude, FilterIncludeAll}; From 0a0ed6697f16c45a35b111f1bc88a429cb90ab94 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 20 Sep 2024 17:42:19 +0200 Subject: [PATCH 17/21] iface: make ContractOp fields public --- src/interface/contract.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/interface/contract.rs b/src/interface/contract.rs index d97a3187..b2105444 100644 --- a/src/interface/contract.rs +++ b/src/interface/contract.rs @@ -115,12 +115,12 @@ pub enum OpDirection { serde(crate = "serde_crate", rename_all = "camelCase", tag = "type") )] pub struct ContractOp { - direction: OpDirection, - ty: AssignmentType, - opids: BTreeSet, - state: AllocatedState, - to: BTreeSet, - witness: Option, + pub direction: OpDirection, + pub ty: AssignmentType, + pub opids: BTreeSet, + pub state: AllocatedState, + pub to: BTreeSet, + pub witness: Option, } fn reduce_to_ty(allocations: impl IntoIterator) -> AssignmentType { From 77f6ab81aadf09c04869fab1fa33eaa8b3b9366e Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 20 Sep 2024 18:04:03 +0200 Subject: [PATCH 18/21] persistence: add Stock::contract_info method --- src/persistence/stock.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/persistence/stock.rs b/src/persistence/stock.rs index 3f2e1849..ebc366a0 100644 --- a/src/persistence/stock.rs +++ b/src/persistence/stock.rs @@ -526,8 +526,14 @@ impl Stock { let state = self.state.contract_state(contract_id)?; let schema_id = state.schema_id(); let schema_ifaces = self.stash.schema(schema_id)?; - let info = ContractInfo::with(self.stash.genesis(contract_id)?); - Ok((schema_ifaces, state, info)) + Ok((schema_ifaces, state, self.contract_info(contract_id)?)) + } + + pub fn contract_info( + &self, + contract_id: ContractId, + ) -> Result> { + Ok(ContractInfo::with(self.stash.genesis(contract_id)?)) } pub fn contract_state( From 2c915f82b1cc3e3d7845696f085bc6dbe32b17bb Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Fri, 20 Sep 2024 20:33:24 +0200 Subject: [PATCH 19/21] iface: history - ignore blank transitions, fix spent calculation --- src/interface/contract.rs | 44 +++++++++++++-------------------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/src/interface/contract.rs b/src/interface/contract.rs index b2105444..1fa3a92d 100644 --- a/src/interface/contract.rs +++ b/src/interface/contract.rs @@ -151,7 +151,6 @@ impl ContractOp { fn non_fungible_sent( witness: WitnessInfo, ext_allocations: HashSet, - _: HashSet, ) -> impl ExactSizeIterator { ext_allocations.into_iter().map(move |a| Self { direction: OpDirection::Sent, @@ -194,18 +193,10 @@ impl ContractOp { } } - fn fungible_sent( - witness: WitnessInfo, - ext_allocations: HashSet, - our_allocations: HashSet, - ) -> Self { - let opids = our_allocations.iter().map(|a| a.opout.op).collect(); + fn fungible_sent(witness: WitnessInfo, ext_allocations: HashSet) -> Self { + let opids = ext_allocations.iter().map(|a| a.opout.op).collect(); let to = ext_allocations.iter().map(|a| a.seal).collect(); - let mut amount = ext_allocations - .iter() - .map(|a| a.state.unwrap_fungible()) - .sum(); - amount -= our_allocations + let amount = ext_allocations .iter() .map(|a| a.state.unwrap_fungible()) .sum(); @@ -464,31 +455,26 @@ impl ContractIface { // a specific allocation is listed in the first tuple pattern field. (Some(our_allocations), Some(all_allocations)) => { // all_allocations - our_allocations = external payments - let ext_allocations = all_allocations.difference(&our_allocations); + let ext_allocations = all_allocations + .difference(&our_allocations) + .cloned() + .collect::>(); + // This was a blank state transition with no external payment + if ext_allocations.is_empty() { + continue; + } if T::IS_FUNGIBLE { - ops.push(ContractOp::fungible_sent( - witness_info, - ext_allocations.cloned().collect(), - our_allocations, - )) + ops.push(ContractOp::fungible_sent(witness_info, ext_allocations)) } else { - ops.extend(ContractOp::non_fungible_sent( - witness_info, - ext_allocations.cloned().collect(), - our_allocations, - )) + ops.extend(ContractOp::non_fungible_sent(witness_info, ext_allocations)) } } // the same as above, but the payment has no change (None, Some(ext_allocations)) => { if T::IS_FUNGIBLE { - ops.push(ContractOp::fungible_sent(witness_info, ext_allocations, set![])) + ops.push(ContractOp::fungible_sent(witness_info, ext_allocations)) } else { - ops.extend(ContractOp::non_fungible_sent( - witness_info, - ext_allocations, - set![], - )) + ops.extend(ContractOp::non_fungible_sent(witness_info, ext_allocations)) } } // we own allocation but the witness transaction was made by other wallet: From 24af8a51867320ced4398ccc0543b2a99bf4bfe0 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sat, 21 Sep 2024 00:11:44 +0200 Subject: [PATCH 20/21] contract: export WitnessInfo --- src/interface/iface.rs | 5 ++--- src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/interface/iface.rs b/src/interface/iface.rs index 26fa99f9..6b2e7d1a 100644 --- a/src/interface/iface.rs +++ b/src/interface/iface.rs @@ -30,7 +30,6 @@ use amplify::{ByteArray, Bytes32}; use baid64::{Baid64ParseError, DisplayBaid64, FromBaid64Str}; use chrono::{DateTime, TimeZone, Utc}; use commit_verify::{CommitId, CommitmentId, DigestExt, Sha256}; -use rgb::vm::WitnessOrd; use rgb::{ContractId, Identity, Occurrences, SchemaId, XWitnessId}; use strict_encoding::{ FieldName, StrictDecode, StrictDeserialize, StrictDumb, StrictEncode, StrictSerialize, @@ -40,7 +39,7 @@ use strict_types::{SemId, SymbolicSys, TypeLib}; use crate::interface::{ContractIface, IfaceDisplay, IfaceImpl, VerNo}; use crate::persistence::{ContractStateRead, SchemaIfaces}; -use crate::LIB_NAME_RGB_STD; +use crate::{WitnessInfo, LIB_NAME_RGB_STD}; /// Interface identifier. /// @@ -364,7 +363,7 @@ pub trait IfaceWrapper { fn schema_id(&self) -> SchemaId; /// Returns information about a witness, if it is known to the contract state. - fn witness_info(&self, witness_id: XWitnessId) -> Option; + fn witness_info(&self, witness_id: XWitnessId) -> Option; } /// Interface definition. diff --git a/src/lib.rs b/src/lib.rs index 5adfce94..b2531ffd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,7 @@ pub mod info; pub use bp::{Outpoint, Txid}; pub use contract::{ BundleExt, KnownState, MergeReveal, MergeRevealError, OutputAssignment, RevealError, - TypedAssignsExt, + TypedAssignsExt, WitnessInfo, }; pub use invoice::{Allocation, Amount, CoinAmount, OwnedFraction, Precision, TokenIndex}; pub use rgb::prelude::*; From 94b5ebc6b998b4aa2321513c08b4ff2aebfd2a56 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sat, 21 Sep 2024 00:13:26 +0200 Subject: [PATCH 21/21] chore: fix clippy lints --- src/interface/contract.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/interface/contract.rs b/src/interface/contract.rs index 1fa3a92d..2709a054 100644 --- a/src/interface/contract.rs +++ b/src/interface/contract.rs @@ -380,18 +380,9 @@ impl ContractIface { ) -> Vec { self.history_fungible(filter_outpoints.clone(), filter_witnesses.clone()) .into_iter() - .chain( - self.history_rights(filter_outpoints.clone(), filter_witnesses.clone()) - .into_iter(), - ) - .chain( - self.history_data(filter_outpoints.clone(), filter_witnesses.clone()) - .into_iter(), - ) - .chain( - self.history_attach(filter_outpoints, filter_witnesses) - .into_iter(), - ) + .chain(self.history_rights(filter_outpoints.clone(), filter_witnesses.clone())) + .chain(self.history_data(filter_outpoints.clone(), filter_witnesses.clone())) + .chain(self.history_attach(filter_outpoints, filter_witnesses)) .collect() }