Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fungible operation history #92

Merged
merged 2 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,8 @@ serde = ["serde_crate", "serde_with", "serde_yaml", "bp-std/serde", "bp-wallet/s

[package.metadata.docs.rs]
features = [ "all" ]

[patch.crates-io]
rgb-core = { git = "https://github.com/RGB-WG/rgb-core", branch = "v0.11" }
rgb-invoice = { git = "https://github.com/RGB-WG/rgb-std", branch = "v0.11" }
rgb-std = { git = "https://github.com/RGB-WG/rgb-std", branch = "v0.11" }
48 changes: 38 additions & 10 deletions cli/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ use psbt::{Psbt, PsbtVer};
use rgb_rt::{DescriptorRgb, RgbKeychain, RuntimeError, TransferParams};
use rgbstd::containers::{Bindle, BuilderSeal, Transfer, UniversalBindle};
use rgbstd::contract::{ContractId, GenesisSeal, GraphSeal, StateType};
use rgbstd::interface::{ContractBuilder, FilterExclude, IfaceId, SchemaIfaces};
use rgbstd::interface::{AmountChange, ContractBuilder, FilterExclude, IfaceId, SchemaIfaces};
use rgbstd::invoice::{Beneficiary, RgbInvoice, RgbInvoiceBuilder, XChainNet};
use rgbstd::persistence::{Inventory, Stash};
use rgbstd::schema::SchemaId;
use rgbstd::validation::Validity;
use rgbstd::{OutputSeal, XChain};
use rgbstd::{OutputSeal, XChain, XOutputSeal};
use seals::txout::CloseMethod;
use strict_types::encoding::{FieldName, TypeName};
use strict_types::StrictVal;
Expand Down Expand Up @@ -128,11 +128,15 @@ pub enum Command {
iface: String,
},

/// Print operation history
#[display("history")]
History {
/// Print operation history for a default fungible token under a given
/// interface
#[display("history-fungible")]
HistoryFungible {
/// Contract identifier
contract_id: Option<ContractId>,
contract_id: ContractId,

/// Interface to interpret the state data
iface: String,
},

/// Display all known UTXOs belonging to this wallet
Expand Down Expand Up @@ -335,8 +339,32 @@ impl Exec for RgbArgs {
None
}

Command::History { contract_id: _ } => {
todo!();
Command::HistoryFungible { contract_id, iface } => {
let runtime = self.rgb_runtime(&config)?;
let iface: TypeName = tn!(iface.clone());
let history = runtime.fungible_history(*contract_id, iface)?;
println!("Amount\tCounterparty\tWitness Id");
for (id, op) in history {
let (cparty, more) = match op.state_change {
AmountChange::Dec(_) => {
(op.beneficiaries.first(), op.beneficiaries.len().saturating_sub(1))
}
AmountChange::Zero => continue,
AmountChange::Inc(_) => {
(op.payers.first(), op.payers.len().saturating_sub(1))
}
};
let more = if more > 0 {
format!(" (+{more})")
} else {
s!("")
};
let cparty = cparty
.map(XOutputSeal::to_string)
.unwrap_or_else(|| s!("none"));
println!("{}\t{}{}\t{}", op.state_change, cparty, more, id);
}
None
}

Command::Import { armored, file } => {
Expand Down Expand Up @@ -439,7 +467,7 @@ impl Exec for RgbArgs {
for allocation in allocations {
println!(
" amount={}, utxo={}, witness={} # owned by the wallet",
allocation.value, allocation.owner, allocation.witness
allocation.state, allocation.seal, allocation.witness
);
}
}
Expand All @@ -450,7 +478,7 @@ impl Exec for RgbArgs {
for allocation in allocations {
println!(
" amount={}, utxo={}, witness={} # owner unknown",
allocation.value, allocation.owner, allocation.witness
allocation.state, allocation.seal, allocation.witness
);
}
}
Expand Down
14 changes: 8 additions & 6 deletions src/pay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use bpwallet::{Beneficiary as BpBeneficiary, ConstructionError, PsbtMeta, TxPara
use psbt::{CommitError, EmbedError, Psbt, RgbPsbt, TapretKeyError};
use rgbstd::containers::{Bindle, Transfer};
use rgbstd::interface::ContractError;
use rgbstd::invoice::{Beneficiary, InvoiceState, RgbInvoice};
use rgbstd::invoice::{Amount, Beneficiary, InvoiceState, RgbInvoice};
use rgbstd::persistence::{
ComposeError, ConsignerError, Inventory, InventoryError, Stash, StashError,
};
Expand Down Expand Up @@ -206,21 +206,23 @@ impl Runtime {
contract_id,
filter: self,
};
let mut state = contract.fungible(assignment_name, &filter)?.into_inner();
state.sort_by_key(|a| a.value);
let mut sum = 0u64;
let mut state = contract
.fungible(assignment_name, &filter)?
.collect::<Vec<_>>();
state.sort_by_key(|a| a.state);
let mut sum = Amount::ZERO;
state
.iter()
.rev()
.take_while(|a| {
if sum >= amount {
false
} else {
sum += a.value;
sum += a.state;
true
}
})
.map(|a| a.owner)
.map(|a| a.seal)
.collect::<Vec<_>>()
}
_ => return Err(CompositionError::Unsupported),
Expand Down
73 changes: 66 additions & 7 deletions src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#![allow(clippy::result_large_err)]

use std::collections::HashMap;
use std::convert::Infallible;
use std::io;
use std::io::ErrorKind;
Expand All @@ -32,12 +33,16 @@ use bpstd::{Network, XpubDerivable};
use bpwallet::Wallet;
use rgbfs::StockFs;
use rgbstd::containers::{Contract, LoadError, Transfer};
use rgbstd::interface::{BuilderError, OutpointFilter};
use rgbstd::persistence::{Inventory, InventoryDataError, InventoryError, StashError, Stock};
use rgbstd::interface::{
AmountChange, BuilderError, ContractError, IfaceOp, OutpointFilter, WitnessFilter,
};
use rgbstd::persistence::{
Inventory, InventoryDataError, InventoryError, Stash, StashError, Stock,
};
use rgbstd::resolvers::ResolveHeight;
use rgbstd::validation::{self, ResolveWitness};
use rgbstd::{ContractId, XChain, XOutpoint};
use strict_types::encoding::{DecodeError, DeserializeError, Ident, SerializeError};
use rgbstd::{AssignmentWitness, ContractId, WitnessId, XChain, XOutpoint};
use strict_types::encoding::{DecodeError, DeserializeError, Ident, SerializeError, TypeName};

use crate::{DescriptorRgb, RgbDescr};

Expand Down Expand Up @@ -67,6 +72,12 @@ pub enum RuntimeError {
#[from]
Builder(BuilderError),

#[from]
History(HistoryError),

#[from]
Contract(ContractError),

#[from]
PsbtDecode(psbt::DecodeError),

Expand Down Expand Up @@ -111,6 +122,7 @@ impl From<Infallible> for RuntimeError {
pub struct Runtime<D: DescriptorRgb<K> = RgbDescr, K = XpubDerivable> {
stock_path: PathBuf,
#[getter(as_mut)]
// TODO: Parametrize by the stock
stock: Stock,
bprt: bpwallet::Runtime<D, K /* TODO: Add layer 2 */>,
}
Expand All @@ -126,23 +138,33 @@ impl<D: DescriptorRgb<K>, K> DerefMut for Runtime<D, K> {
}

impl<D: DescriptorRgb<K>, K> OutpointFilter for Runtime<D, K> {
fn include_output(&self, output: impl Into<XOutpoint>) -> bool {
fn include_outpoint(&self, output: impl Into<XOutpoint>) -> bool {
let output = output.into();
self.wallet()
.coins()
.any(|utxo| XChain::Bitcoin(utxo.outpoint) == output)
}
}

impl<D: DescriptorRgb<K>, K> WitnessFilter for Runtime<D, K> {
fn include_witness(&self, witness: impl Into<AssignmentWitness>) -> bool {
let witness = witness.into();
self.wallet()
.transactions()
.keys()
.any(|txid| AssignmentWitness::Present(WitnessId::Bitcoin(*txid)) == witness)
}
}

pub struct ContractOutpointsFilter<'runtime, D: DescriptorRgb<K>, K> {
pub contract_id: ContractId,
pub filter: &'runtime Runtime<D, K>,
}

impl<'runtime, D: DescriptorRgb<K>, K> OutpointFilter for ContractOutpointsFilter<'runtime, D, K> {
fn include_output(&self, output: impl Into<XOutpoint>) -> bool {
fn include_outpoint(&self, output: impl Into<XOutpoint>) -> bool {
let output = output.into();
if !self.filter.include_output(output) {
if !self.filter.include_outpoint(output) {
return false;
}
matches!(self.filter.stock.state_for_outpoints(self.contract_id, [output]), Ok(list) if !list.is_empty())
Expand Down Expand Up @@ -234,4 +256,41 @@ impl<D: DescriptorRgb<K>, K> Runtime<D, K> {
.accept_transfer(transfer, resolver, force)
.map_err(RuntimeError::from)
}

// TODO: Integrate into BP Wallet `TxRow` as L2 and provide transactional info
pub fn fungible_history(
&self,
contract_id: ContractId,
iface_name: impl Into<TypeName>,
) -> Result<HashMap<WitnessId, IfaceOp<AmountChange>>, RuntimeError> {
let iface_name = iface_name.into();
let iface = self.stock.iface_by_name(&iface_name)?;
let default_op = iface
.default_operation
.as_ref()
.ok_or(HistoryError::NoDefaultOp)?;
let state_name = iface
.transitions
.get(default_op)
.ok_or(HistoryError::DefaultOpNotTransition)?
.default_assignment
.as_ref()
.ok_or(HistoryError::NoDefaultAssignment)?
.clone();
let contract = self.stock.contract_iface_named(contract_id, iface_name)?;
contract
.fungible_ops::<AmountChange>(state_name, self, self)
.map_err(RuntimeError::from)
}
}

#[derive(Debug, Display, Error, From)]
#[display(doc_comments)]
pub enum HistoryError {
/// interface doesn't define default operation
NoDefaultOp,
/// default operation defined by the interface is not a state transition
DefaultOpNotTransition,
/// interface doesn't define default fungible state
NoDefaultAssignment,
}
Loading