diff --git a/src/contract/assignments.rs b/src/contract/assignments.rs index 8d66b25a..d4967520 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,18 +40,50 @@ 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 {} - -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 {} - -#[derive(Copy, Clone, Eq, Debug)] +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)] +#[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)] #[strict_type(lib = LIB_NAME_RGB_STD)] #[cfg_attr( @@ -142,6 +175,7 @@ impl OutputAssignment { } } + /// Transmutes output assignment from one form of state to another pub fn transmute>(self) -> OutputAssignment { OutputAssignment { opout: self.opout, 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 b54e45d2..2709a054 100644 --- a/src/interface/contract.rs +++ b/src/interface/contract.rs @@ -20,21 +20,19 @@ // limitations under the License. use std::borrow::Borrow; -use std::cmp::Ordering; -use std::collections::HashMap; +use std::collections::{BTreeSet, HashMap, HashSet}; -use amplify::confinement::SmallOrdSet; 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}; -use crate::contract::{KnownState, OutputAssignment}; +use crate::contract::{KnownState, OutputAssignment, WitnessInfo}; use crate::info::ContractInfo; -use crate::interface::{IfaceImpl, OutpointFilter}; +use crate::interface::{AssignmentsFilter, IfaceImpl}; use crate::persistence::ContractStateRead; use crate::LIB_NAME_RGB_STD; @@ -78,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; @@ -86,118 +95,136 @@ 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)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display)] #[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), - }, - }; - } +#[display(lowercase)] +pub enum OpDirection { + Issued, + Received, + Sent, } #[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") + serde(crate = "serde_crate", rename_all = "camelCase", tag = "type") )] -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, +pub struct ContractOp { + pub direction: OpDirection, + pub ty: AssignmentType, + pub opids: BTreeSet, + pub state: AllocatedState, + pub to: BTreeSet, + pub witness: Option, } -impl IfaceOp { - fn from_spent(alloc: OutputAssignment) -> Self { +fn reduce_to_ty(allocations: impl IntoIterator) -> AssignmentType { + allocations + .into_iter() + .map(|a| a.opout.ty) + .reduce(|ty1, ty2| { + assert_eq!(ty1, ty2); + ty1 + }) + .expect("empty list of allocations") +} + +impl ContractOp { + fn non_fungible_genesis( + our_allocations: HashSet, + ) -> impl ExactSizeIterator { + 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 non_fungible_sent( + witness: WitnessInfo, + ext_allocations: HashSet, + ) -> impl ExactSizeIterator { + ext_allocations.into_iter().map(move |a| Self { + direction: OpDirection::Sent, + ty: a.opout.ty, + opids: bset![a.opout.op], + state: a.state, + to: bset![a.seal], + witness: Some(witness), + }) + } + + fn non_fungible_received( + witness: WitnessInfo, + our_allocations: HashSet, + ) -> impl ExactSizeIterator { + our_allocations.into_iter().map(move |a| Self { + direction: OpDirection::Received, + ty: a.opout.ty, + opids: bset![a.opout.op], + state: a.state, + to: bset![a.seal], + witness: Some(witness), + }) + } + + fn fungible_genesis(our_allocations: HashSet) -> Self { + let to = our_allocations.iter().map(|a| a.seal).collect(); + let opids = our_allocations.iter().map(|a| a.opout.op).collect(); + let issued = our_allocations + .iter() + .map(|a| a.state.unwrap_fungible()) + .sum(); 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!(), + direction: OpDirection::Issued, + ty: reduce_to_ty(our_allocations), + opids, + state: AllocatedState::Amount(issued), + to, + witness: None, } } - fn from_received(alloc: OutputAssignment) -> Self { + + 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 amount = ext_allocations + .iter() + .map(|a| a.state.unwrap_fungible()) + .sum(); 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!(), + direction: OpDirection::Sent, + ty: reduce_to_ty(ext_allocations), + opids, + state: AllocatedState::Amount(amount), + to, + witness: Some(witness), } } - 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 + + 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.unwrap_fungible()) + .sum(); + Self { + direction: OpDirection::Received, + ty: reduce_to_ty(our_allocations), + opids, + state: AllocatedState::Amount(amount), + to, + witness: Some(witness), + } } } @@ -249,7 +276,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, @@ -263,7 +304,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)) } @@ -271,81 +311,41 @@ 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) } - 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, - filter: impl OutpointFilter + 'c, + filter: impl AssignmentsFilter + 'c, ) -> Result + 'c, ContractError> { 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, - filter: impl OutpointFilter + 'c, + filter: impl AssignmentsFilter + 'c, ) -> Result + 'c, ContractError> { 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, - filter: impl OutpointFilter + 'c, + filter: impl AssignmentsFilter + 'c, ) -> Result + 'c, ContractError> { 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, + 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 @@ -354,7 +354,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) } @@ -373,105 +373,155 @@ impl ContractIface { self.allocations(outpoint) } - // TODO: Ignore blank state transition - fn operations<'c, C: StateChange>( + pub fn history( + &self, + filter_outpoints: impl AssignmentsFilter + Clone, + filter_witnesses: impl AssignmentsFilter + Clone, + ) -> Vec { + self.history_fungible(filter_outpoints.clone(), filter_witnesses.clone()) + .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() + } + + fn operations<'c, T: KnownState + 'c, I: Iterator>>( &'c self, - state: impl IntoIterator> + 'c, - allocations: impl Iterator> + 'c, - ) -> HashMap> + state: impl Fn(&'c S) -> I, + filter_outpoints: impl AssignmentsFilter, + filter_witnesses: impl AssignmentsFilter, + ) -> Vec where - C::State: 'c, + AllocatedState: From, { - 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); + // 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) { + if T::IS_FUNGIBLE { + ops.push(ContractOp::fungible_genesis(genesis_allocations)); } else { - ops.insert(witness_id, IfaceOp::from_spent(alloc)); + ops.extend(ContractOp::non_fungible_genesis(genesis_allocations)); } } - - for alloc in allocations { - let Some(witness_id) = alloc.witness else { - continue; + 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) + .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)) + } else { + 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)) + } else { + ops.extend(ContractOp::non_fungible_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) => { + 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"), }; - 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 history_fungible( + &self, + filter_outpoints: impl AssignmentsFilter, + filter_witnesses: impl AssignmentsFilter, + ) -> Vec { + self.operations(|state| state.fungible_all(), filter_outpoints, filter_witnesses) } - 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 history_rights( + &self, + filter_outpoints: impl AssignmentsFilter, + filter_witnesses: impl AssignmentsFilter, + ) -> Vec { + self.operations(|state| state.rights_all(), filter_outpoints, filter_witnesses) } - 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 history_data( + &self, + filter_outpoints: impl AssignmentsFilter, + filter_witnesses: impl AssignmentsFilter, + ) -> Vec { + self.operations(|state| state.data_all(), filter_outpoints, filter_witnesses) } - 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)?, - )) + pub fn history_attach( + &self, + filter_outpoints: impl AssignmentsFilter, + filter_witnesses: impl AssignmentsFilter, + ) -> Vec { + self.operations(|state| state.attach_all(), filter_outpoints, filter_witnesses) + } + + 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/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/iface.rs b/src/interface/iface.rs index c6cbfaaf..6b2e7d1a 100644 --- a/src/interface/iface.rs +++ b/src/interface/iface.rs @@ -30,7 +30,7 @@ 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::{ContractId, Identity, Occurrences, SchemaId, XWitnessId}; use strict_encoding::{ FieldName, StrictDecode, StrictDeserialize, StrictDumb, StrictEncode, StrictSerialize, StrictType, TypeName, VariantName, @@ -39,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. /// @@ -355,6 +355,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. diff --git a/src/interface/mod.rs b/src/interface/mod.rs index ea5ef2d9..496ce87e 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -34,11 +34,11 @@ mod inheritance; pub use builder::{BuilderError, ContractBuilder, TransitionBuilder, TxOutpoint}; pub use contract::{ - AllocatedState, AmountChange, AttachAllocation, ContractError, ContractIface, DataAllocation, - FungibleAllocation, IfaceOp, OwnedAllocation, RightsAllocation, StateChange, + AllocatedState, AttachAllocation, ContractError, ContractIface, ContractOp, DataAllocation, + FungibleAllocation, OpDirection, OwnedAllocation, RightsAllocation, }; 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, 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::*; diff --git a/src/persistence/memory.rs b/src/persistence/memory.rs index 7d4ce810..a37d1756 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_ord(&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 621ec2eb..91e5356d 100644 --- a/src/persistence/state.rs +++ b/src/persistence/state.rs @@ -305,6 +305,7 @@ pub trait StateWriteProvider: StoreTransaction { pub trait ContractStateRead: ContractStateAccess { fn contract_id(&self) -> ContractId; fn schema_id(&self) -> SchemaId; + 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>; 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(