use serde::{Deserialize, Serialize};
use tari_bor::BorTag;
use tari_template_abi::{
EngineOp,
call_engine,
rust::{fmt, prelude::*},
};
use tari_template_lib_types::{
BinaryTag,
NonFungibleId,
ResourceAddress,
confidential::ConfidentialWithdrawProof,
stealth::StealthTransferStatement,
};
use super::{NonFungible, Proof};
use crate::{
args::{BucketAction, BucketGetAmountArg, BucketInvokeArg, BucketRef, InvokeResult},
resource::ResourceManager,
types::{Amount, ResourceType},
};
const TAG: u64 = BinaryTag::BucketId.as_u64();
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "ts", derive(ts_rs::TS), ts(export))]
pub struct BucketId(BorTag<u32, TAG>);
impl From<u32> for BucketId {
fn from(value: u32) -> Self {
Self(BorTag::new(value))
}
}
impl fmt::Display for BucketId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "BucketId({})", self.0.inner())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Bucket {
id: BucketId,
}
impl Bucket {
pub(crate) fn id(&self) -> BucketId {
self.id
}
pub fn resource_address(&self) -> ResourceAddress {
let resp: InvokeResult = call_engine(EngineOp::BucketInvoke, &BucketInvokeArg {
bucket_ref: BucketRef::Ref(self.id),
action: BucketAction::GetResourceAddress,
args: invoke_args![],
});
resp.decode()
.expect("Bucket GetResourceAddress returned invalid resource address")
}
pub fn resource_type(&self) -> ResourceType {
let resp: InvokeResult = call_engine(EngineOp::BucketInvoke, &BucketInvokeArg {
bucket_ref: BucketRef::Ref(self.id),
action: BucketAction::GetResourceType,
args: invoke_args![],
});
resp.decode()
.expect("Bucket GetResourceType returned invalid resource type")
}
pub fn take(&mut self, amount: Amount) -> Self {
assert!(amount.is_positive());
let resp: InvokeResult = call_engine(EngineOp::BucketInvoke, &BucketInvokeArg {
bucket_ref: BucketRef::Ref(self.id),
action: BucketAction::Take,
args: invoke_args![amount],
});
resp.decode().expect("Bucket Take returned invalid bucket")
}
pub fn stealth_transfer(mut self, statement: StealthTransferStatement) -> Self {
let manager = ResourceManager::get(self.resource_address());
let output_bucket = if statement.inputs_statement.revealed_amount.is_positive() {
let revealed_input_funds = self.take(statement.inputs_statement.revealed_amount);
manager.stealth_transfer_with_opt_input_bucket(statement, Some(revealed_input_funds))
} else {
manager.stealth_transfer(statement)
};
if let Some(output_bucket) = output_bucket {
return self.join(output_bucket);
}
self
}
pub fn take_confidential(&mut self, proof: ConfidentialWithdrawProof) -> Self {
let resp: InvokeResult = call_engine(EngineOp::BucketInvoke, &BucketInvokeArg {
bucket_ref: BucketRef::Ref(self.id),
action: BucketAction::TakeConfidential,
args: invoke_args![proof],
});
resp.decode().expect("Bucket Take returned invalid bucket")
}
pub fn burn(&self) {
let resp: InvokeResult = call_engine(EngineOp::BucketInvoke, &BucketInvokeArg {
bucket_ref: BucketRef::Ref(self.id),
action: BucketAction::Burn,
args: invoke_args![],
});
resp.decode().expect("Bucket Burn returned invalid result")
}
pub fn join(self, other: Bucket) -> Self {
let resp: InvokeResult = call_engine(EngineOp::BucketInvoke, &BucketInvokeArg {
bucket_ref: BucketRef::Ref(self.id),
action: BucketAction::Join,
args: invoke_args![other.id],
});
resp.decode().expect("Bucket join returned invalid result")
}
pub fn drop_empty(self) {
let resp: InvokeResult = call_engine(EngineOp::BucketInvoke, &BucketInvokeArg {
bucket_ref: BucketRef::Ref(self.id),
action: BucketAction::DropEmpty,
args: invoke_args![],
});
resp.decode().expect("Bucket DropEmpty returned invalid result")
}
pub fn is_empty(&self) -> bool {
let resp: InvokeResult = call_engine(EngineOp::BucketInvoke, &BucketInvokeArg {
bucket_ref: BucketRef::Ref(self.id),
action: BucketAction::GetAmount,
args: invoke_args![BucketGetAmountArg::Everything],
});
let amount: Amount = resp.decode().expect("Bucket GetAmount returned invalid amount");
amount.is_zero()
}
pub fn amount(&self) -> Amount {
let resp: InvokeResult = call_engine(EngineOp::BucketInvoke, &BucketInvokeArg {
bucket_ref: BucketRef::Ref(self.id),
action: BucketAction::GetAmount,
args: invoke_args![BucketGetAmountArg::AmountOnly],
});
resp.decode().expect("Bucket GetAmount returned invalid amount")
}
pub fn locked_amount(&self) -> Amount {
let resp: InvokeResult = call_engine(EngineOp::BucketInvoke, &BucketInvokeArg {
bucket_ref: BucketRef::Ref(self.id),
action: BucketAction::GetAmount,
args: invoke_args![BucketGetAmountArg::LockedOnly],
});
resp.decode().expect("Bucket GetAmount returned invalid amount")
}
pub fn create_proof(&self) -> Proof {
let resp: InvokeResult = call_engine(EngineOp::BucketInvoke, &BucketInvokeArg {
bucket_ref: BucketRef::Ref(self.id),
action: BucketAction::CreateProof,
args: invoke_args![],
});
resp.decode().expect("Bucket CreateProof returned invalid proof")
}
pub fn get_non_fungible_ids(&self) -> Vec<NonFungibleId> {
let resp: InvokeResult = call_engine(EngineOp::BucketInvoke, &BucketInvokeArg {
bucket_ref: BucketRef::Ref(self.id),
action: BucketAction::GetNonFungibleIds,
args: invoke_args![],
});
resp.decode()
.expect("get_non_fungible_ids returned invalid non fungible ids")
}
pub fn get_non_fungibles(&self) -> Vec<NonFungible> {
let resp: InvokeResult = call_engine(EngineOp::BucketInvoke, &BucketInvokeArg {
bucket_ref: BucketRef::Ref(self.id),
action: BucketAction::GetNonFungibles,
args: invoke_args![],
});
resp.decode().expect("get_non_fungibles returned invalid non fungibles")
}
pub fn count_confidential_commitments(&self) -> u32 {
let resp: InvokeResult = call_engine(EngineOp::BucketInvoke, &BucketInvokeArg {
bucket_ref: BucketRef::Ref(self.id),
action: BucketAction::CountConfidentialCommitments,
args: invoke_args![],
});
resp.decode()
.expect("count_confidential_commitments returned invalid u32")
}
pub fn assert_contains_no_confidential_funds(&self) {
let count = self.count_confidential_commitments();
assert_eq!(
count, 0,
"Expected bucket to have no confidential commitments, but found {count}",
);
}
pub const fn from_id(id: BucketId) -> Self {
Self { id }
}
}
impl fmt::Display for Bucket {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Bucket({})", self.id.0.inner())
}
}