Skip to content

Commit

Permalink
iface: remove code duplication for history methods
Browse files Browse the repository at this point in the history
  • Loading branch information
dr-orlovsky committed Sep 20, 2024
1 parent 53fa9a5 commit d5170f9
Showing 1 changed file with 87 additions and 123 deletions.
210 changes: 87 additions & 123 deletions src/interface/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

use std::borrow::Borrow;
use std::collections::{BTreeSet, HashMap, HashSet};
use std::iter;

use invoice::{Allocation, Amount};
use rgb::{
Expand Down Expand Up @@ -97,6 +98,27 @@ pub enum ContractOp {
Attach(NonFungibleOp<AttachState>),
}

// 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<OutputAssignment<Self::State>>,
) -> impl ExactSizeIterator<Item = Self>;

fn new_sent(
witness: WitnessInfo,
ext_allocations: HashSet<OutputAssignment<Self::State>>,
_: HashSet<OutputAssignment<Self::State>>,
) -> impl ExactSizeIterator<Item = Self>;

fn new_received(
witness: WitnessInfo,
our_allocations: HashSet<OutputAssignment<Self::State>>,
) -> impl ExactSizeIterator<Item = Self>;
}

#[derive(Clone, Eq, PartialEq, Hash, Debug)]
#[cfg_attr(
feature = "serde",
Expand All @@ -122,7 +144,9 @@ pub enum NonFungibleOp<S: KnownState> {
},
}

impl<S: KnownState> NonFungibleOp<S> {
impl<S: KnownState> ContractOperation for NonFungibleOp<S> {
type State = S;

fn new_genesis(
our_allocations: HashSet<OutputAssignment<S>>,
) -> impl ExactSizeIterator<Item = Self> {
Expand All @@ -135,6 +159,7 @@ impl<S: KnownState> NonFungibleOp<S> {
fn new_sent(
witness: WitnessInfo,
ext_allocations: HashSet<OutputAssignment<S>>,
_: HashSet<OutputAssignment<S>>,
) -> impl ExactSizeIterator<Item = Self> {
ext_allocations.into_iter().map(move |a| Self::Sent {
opid: a.opout.op,
Expand Down Expand Up @@ -182,43 +207,47 @@ pub enum FungibleOp {
},
}

impl FungibleOp {
fn new_genesis(our_allocations: &HashSet<OutputAssignment<Amount>>) -> Self {
impl ContractOperation for FungibleOp {
type State = Amount;

fn new_genesis(
our_allocations: HashSet<OutputAssignment<Amount>>,
) -> impl ExactSizeIterator<Item = 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 }
let issued = our_allocations.iter().map(|a| a.state).sum();
iter::once(Self::Genesis { issued, to })

Check warning on line 218 in src/interface/contract.rs

View check run for this annotation

Codecov / codecov/patch

src/interface/contract.rs#L213-L218

Added lines #L213 - L218 were not covered by tests
}

fn new_sent(
witness: WitnessInfo,
ext_allocations: &HashSet<OutputAssignment<Amount>>,
our_allocations: &HashSet<OutputAssignment<Amount>>,
) -> Self {
ext_allocations: HashSet<OutputAssignment<Amount>>,
our_allocations: HashSet<OutputAssignment<Amount>>,
) -> impl ExactSizeIterator<Item = 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.clone()).sum();
Self::Sent {
amount -= our_allocations.iter().map(|a| a.state).sum();
iter::once(Self::Sent {
opids,
amount,
to,
witness,
}
})

Check warning on line 235 in src/interface/contract.rs

View check run for this annotation

Codecov / codecov/patch

src/interface/contract.rs#L221-L235

Added lines #L221 - L235 were not covered by tests
}

fn new_received(
witness: WitnessInfo,
our_allocations: &HashSet<OutputAssignment<Amount>>,
) -> Self {
our_allocations: HashSet<OutputAssignment<Amount>>,
) -> impl ExactSizeIterator<Item = 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();
Self::Received {
let amount = our_allocations.iter().map(|a| a.state).sum();
iter::once(Self::Received {
opids,
amount,
to,
witness,
}
})

Check warning on line 250 in src/interface/contract.rs

View check run for this annotation

Codecov / codecov/patch

src/interface/contract.rs#L238-L250

Added lines #L238 - L250 were not covered by tests
}
}

Expand Down Expand Up @@ -394,30 +423,39 @@ impl<S: ContractStateRead> ContractIface<S> {
.collect())
}

Check warning on line 424 in src/interface/contract.rs

View check run for this annotation

Codecov / codecov/patch

src/interface/contract.rs#L399-L424

Added lines #L399 - L424 were not covered by tests

pub fn history_fungible(
&self,
fn operations<
'c,
Op: ContractOperation,
T: KnownState + 'c,
I: Iterator<Item = &'c OutputAssignment<T>>,
>(

Check warning on line 431 in src/interface/contract.rs

View check run for this annotation

Codecov / codecov/patch

src/interface/contract.rs#L426-L431

Added lines #L426 - L431 were not covered by tests
&'c self,
state: impl Fn(&'c S) -> I,
filter_outpoints: impl AssignmentsFilter,
filter_witnesses: impl AssignmentsFilter,
) -> Result<Vec<FungibleOp>, ContractError> {
) -> Result<Vec<Op>, ContractError>

Check warning on line 436 in src/interface/contract.rs

View check run for this annotation

Codecov / codecov/patch

src/interface/contract.rs#L433-L436

Added lines #L433 - L436 were not covered by tests
where
Op::State: From<T>,

Check warning on line 438 in src/interface/contract.rs

View check run for this annotation

Codecov / codecov/patch

src/interface/contract.rs#L438

Added line #L438 was not covered by tests
{
// 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
});

Expand All @@ -431,149 +469,75 @@ impl<S: ContractStateRead> ContractIface<S> {
// reconstruct contract history from the wallet perspective
let mut ops = Vec::with_capacity(witness_ids.len() + 1);

Check warning on line 470 in src/interface/contract.rs

View check run for this annotation

Codecov / codecov/patch

src/interface/contract.rs#L440-L470

Added lines #L440 - L470 were not covered by tests
// 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));

Check warning on line 473 in src/interface/contract.rs

View check run for this annotation

Codecov / codecov/patch

src/interface/contract.rs#L472-L473

Added lines #L472 - L473 were not covered by tests
}
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) {

Check warning on line 481 in src/interface/contract.rs

View check run for this annotation

Codecov / codecov/patch

src/interface/contract.rs#L475-L481

Added lines #L475 - L481 were not covered by tests
// 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,
)
))

Check warning on line 492 in src/interface/contract.rs

View check run for this annotation

Codecov / codecov/patch

src/interface/contract.rs#L485-L492

Added lines #L485 - L492 were not covered by tests
}
// 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![]))

Check warning on line 496 in src/interface/contract.rs

View check run for this annotation

Codecov / codecov/patch

src/interface/contract.rs#L495-L496

Added lines #L495 - L496 were not covered by tests
}
// 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))

Check warning on line 501 in src/interface/contract.rs

View check run for this annotation

Codecov / codecov/patch

src/interface/contract.rs#L500-L501

Added lines #L500 - L501 were not covered by tests
}
// these can't get into the `witness_ids` due to the used filters
(None, None) => unreachable!("broken allocation filters"),

Check warning on line 504 in src/interface/contract.rs

View check run for this annotation

Codecov / codecov/patch

src/interface/contract.rs#L504

Added line #L504 was not covered by tests
};
ops.push(op);
}

Ok(ops)
}

Check warning on line 509 in src/interface/contract.rs

View check run for this annotation

Codecov / codecov/patch

src/interface/contract.rs#L508-L509

Added lines #L508 - L509 were not covered by tests

pub fn history_fungible(
&self,
filter_outpoints: impl AssignmentsFilter,
filter_witnesses: impl AssignmentsFilter,
) -> Result<Vec<FungibleOp>, ContractError> {
self.operations(|state| state.fungible_all(), filter_outpoints, filter_witnesses)

Check warning on line 516 in src/interface/contract.rs

View check run for this annotation

Codecov / codecov/patch

src/interface/contract.rs#L511-L516

Added lines #L511 - L516 were not covered by tests
}

pub fn history_rights(
&self,
filter_outpoints: impl AssignmentsFilter,
filter_witnesses: impl AssignmentsFilter,
) -> Result<Vec<NonFungibleOp<VoidState>>, ContractError> {
self.history_non_fungible(|state| state.rights_all(), filter_outpoints, filter_witnesses)
self.operations(|state| state.rights_all(), filter_outpoints, filter_witnesses)

Check warning on line 524 in src/interface/contract.rs

View check run for this annotation

Codecov / codecov/patch

src/interface/contract.rs#L519-L524

Added lines #L519 - L524 were not covered by tests
}

pub fn history_data(
&self,
filter_outpoints: impl AssignmentsFilter,
filter_witnesses: impl AssignmentsFilter,
) -> Result<Vec<NonFungibleOp<DataState>>, ContractError> {
self.history_non_fungible(|state| state.data_all(), filter_outpoints, filter_witnesses)
self.operations(|state| state.data_all(), filter_outpoints, filter_witnesses)

Check warning on line 532 in src/interface/contract.rs

View check run for this annotation

Codecov / codecov/patch

src/interface/contract.rs#L527-L532

Added lines #L527 - L532 were not covered by tests
}

pub fn history_attach(
&self,
filter_outpoints: impl AssignmentsFilter,
filter_witnesses: impl AssignmentsFilter,
) -> Result<Vec<NonFungibleOp<AttachState>>, ContractError> {
self.history_non_fungible(|state| state.attach_all(), filter_outpoints, filter_witnesses)
}

fn history_non_fungible<
'c,
State: KnownState + From<T>,
T: KnownState + 'c,
I: Iterator<Item = &'c OutputAssignment<T>>,
>(
&'c self,
state: impl Fn(&'c S) -> I,
filter_outpoints: impl AssignmentsFilter,
filter_witnesses: impl AssignmentsFilter,
) -> Result<Vec<NonFungibleOp<State>>, 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::<BTreeSet<_>>();
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)

Check warning on line 540 in src/interface/contract.rs

View check run for this annotation

Codecov / codecov/patch

src/interface/contract.rs#L535-L540

Added lines #L535 - L540 were not covered by tests
}

pub fn witness_info(&self, witness_id: XWitnessId) -> Option<WitnessInfo> {
Expand Down

0 comments on commit d5170f9

Please sign in to comment.