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 {