Skip to content

Commit

Permalink
Accout locker claim non fungibles by amount (#208)
Browse files Browse the repository at this point in the history
* Accout locker claim non fungibles by amount

* Address PR comments

* Added doc

* Address PR comments

* Version bump

* Add a comment
  • Loading branch information
sergiupuhalschi-rdx authored Sep 5, 2024
1 parent 2d0cdb3 commit e5da6f7
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 76 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion crates/sargon/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sargon"
version = "1.1.10"
version = "1.1.11"
edition = "2021"
build = "build.rs"

Expand Down
24 changes: 8 additions & 16 deletions crates/sargon/fixtures/transaction/account_locker_claim.rtm
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,14 @@ CALL_METHOD
;
CALL_METHOD
Address("locker_rdx1drn4q2zk6dvljehytnhfah330xk7emfznv59rqlps5ayy52d7xkzzz")
"claim_non_fungibles"
"claim"
Address("account_rdx128y6j78mt0aqv6372evz28hrxp8mn06ccddkr7xppc88hyvynvjdwr")
Address("resource_rdx1nfyg2f68jw7hfdlg5hzvd8ylsa7e0kjl68t5t62v3ttamtejc9wlxa")
Array<NonFungibleLocalId>(
NonFungibleLocalId("#1#")
)
Decimal("10")
;
TAKE_NON_FUNGIBLES_FROM_WORKTOP
TAKE_FROM_WORKTOP
Address("resource_rdx1nfyg2f68jw7hfdlg5hzvd8ylsa7e0kjl68t5t62v3ttamtejc9wlxa")
Array<NonFungibleLocalId>(
NonFungibleLocalId("#1#")
)
Decimal("10")
Bucket("bucket2")
;
CALL_METHOD
Expand All @@ -55,18 +51,14 @@ CALL_METHOD
;
CALL_METHOD
Address("locker_rdx1drn4q2zk6dvljehytnhfah330xk7emfznv59rqlps5ayy52d7xkzzz")
"claim_non_fungibles"
"claim"
Address("account_rdx128y6j78mt0aqv6372evz28hrxp8mn06ccddkr7xppc88hyvynvjdwr")
Address("resource_rdx1n2ekdd2m0jsxjt9wasmu3p49twy2yfalpaa6wf08md46sk8dfmldnd")
Array<NonFungibleLocalId>(
NonFungibleLocalId("<foobar>")
)
Decimal("1")
;
TAKE_NON_FUNGIBLES_FROM_WORKTOP
TAKE_FROM_WORKTOP
Address("resource_rdx1n2ekdd2m0jsxjt9wasmu3p49twy2yfalpaa6wf08md46sk8dfmldnd")
Array<NonFungibleLocalId>(
NonFungibleLocalId("<foobar>")
)
Decimal("1")
Bucket("bucket4")
;
CALL_METHOD
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
CALL_METHOD
Address("locker_rdx1drn4q2zk6dvljehytnhfah330xk7emfznv59rqlps5ayy52d7xkzzz")
"claim"
Address("account_rdx128y6j78mt0aqv6372evz28hrxp8mn06ccddkr7xppc88hyvynvjdwr")
Address("resource_rdx1n2ekdd2m0jsxjt9wasmu3p49twy2yfalpaa6wf08md46sk8dfmldnd")
Decimal("50")
;
TAKE_FROM_WORKTOP
Address("resource_rdx1n2ekdd2m0jsxjt9wasmu3p49twy2yfalpaa6wf08md46sk8dfmldnd")
Decimal("50")
Bucket("bucket1")
;
CALL_METHOD
Address("account_rdx128y6j78mt0aqv6372evz28hrxp8mn06ccddkr7xppc88hyvynvjdwr")
"deposit"
Bucket("bucket1")
;
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use radix_common::prelude::ManifestExpression;
use radix_engine_interface::blueprints::locker::{
ACCOUNT_LOCKER_CLAIM_IDENT, ACCOUNT_LOCKER_CLAIM_NON_FUNGIBLES_IDENT,
};
use std::cmp::min;

impl From<AccountAddress> for ScryptoComponentAddress {
fn from(value: AccountAddress) -> ScryptoComponentAddress {
Expand All @@ -23,73 +24,63 @@ impl TransactionManifest {
Self::build_claimable_batch(claimable_resources, 50);

for claimable in claimable_resources.iter() {
match claimable.clone() {
let (resource_arg, amount_arg) = match claimable.clone() {
AccountLockerClaimableResource::Fungible {
resource_address,
amount,
} => {
let resource_arg: ScryptoResourceAddress =
resource_address.into();
let amount_arg: ScryptoDecimal192 = amount.into();

builder = builder.call_method(
locker_address,
ACCOUNT_LOCKER_CLAIM_IDENT,
(claimant_arg, resource_arg, amount_arg),
);

let bucket = &bucket_factory.next();

builder = builder.take_from_worktop(
resource_arg,
amount_arg,
bucket,
);
builder = builder.deposit(claimant_arg, bucket);
(resource_arg, amount_arg)
}
AccountLockerClaimableResource::NonFungible {
resource_address,
ids,
number_of_items: count,
} => {
let resource_arg: ScryptoResourceAddress =
resource_address.into();
let ids_arg: Vec<ScryptoNonFungibleLocalId> =
ids.iter().map(|id| id.clone().into()).collect();

builder = builder.call_method(
locker_address,
ACCOUNT_LOCKER_CLAIM_NON_FUNGIBLES_IDENT,
(claimant_arg, resource_arg, ids_arg.clone()),
);
let amount_arg: ScryptoDecimal192 = count.into();
(resource_arg, amount_arg)
}
};

let bucket = &bucket_factory.next();
builder = builder.call_method(
locker_address,
ACCOUNT_LOCKER_CLAIM_IDENT,
(claimant_arg, resource_arg, amount_arg),
);

builder = builder.take_non_fungibles_from_worktop(
resource_arg,
ids_arg,
bucket,
);
let bucket = &bucket_factory.next();

builder = builder.deposit(claimant_arg, bucket);
}
}
builder =
builder.take_from_worktop(resource_arg, amount_arg, bucket);
builder = builder.deposit(claimant_arg, bucket);
}

TransactionManifest::sargon_built(builder, claimant.network_id())
}

fn build_claimable_batch(
claimable_resources: Vec<AccountLockerClaimableResource>,
size: usize,
max_size: u64,
) -> IndexSet<AccountLockerClaimableResource> {
let mut current_batch_size = 0;
claimable_resources
.into_iter()
.take_while(|claimable| {
current_batch_size += claimable.resource_count();
current_batch_size <= size
})
.collect()
let mut number_of_items_to_add = max_size;
let mut result = IndexSet::<AccountLockerClaimableResource>::new();

for claimable_resource in claimable_resources {
let updated_resource = claimable_resource
.coerce_number_of_items_at_most(number_of_items_to_add);
result.insert(updated_resource.clone());
number_of_items_to_add -= updated_resource.number_of_items();

// can never be negative thanks to clamping performed in `coerce_number_of_items_at_most`
if number_of_items_to_add == 0 {
break;
}
}

result
}
}

Expand Down Expand Up @@ -128,15 +119,15 @@ mod tests {
},
AccountLockerClaimableResource::NonFungible {
resource_address: "resource_rdx1nfyg2f68jw7hfdlg5hzvd8ylsa7e0kjl68t5t62v3ttamtejc9wlxa".into(),
ids: vec![NonFungibleLocalId::integer(1)],
number_of_items: 10,
},
AccountLockerClaimableResource::Fungible {
resource_address: "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd".into(),
amount: 1500.into(),
},
AccountLockerClaimableResource::NonFungible {
resource_address: "resource_rdx1n2ekdd2m0jsxjt9wasmu3p49twy2yfalpaa6wf08md46sk8dfmldnd".into(),
ids: vec![NonFungibleLocalId::string("foobar").unwrap()],
number_of_items: 1,
},
],
);
Expand All @@ -146,15 +137,25 @@ mod tests {

#[test]
fn claim_limited_to_required_batch_size() {
let expected_manifest = include_str!(concat!(
env!("FIXTURES_TX"),
"account_locker_claim_max_nft_items.rtm"
));
let manifest = SUT::account_locker_claim(
&"locker_rdx1drn4q2zk6dvljehytnhfah330xk7emfznv59rqlps5ayy52d7xkzzz".into(),
&"account_rdx128y6j78mt0aqv6372evz28hrxp8mn06ccddkr7xppc88hyvynvjdwr".into(),
(0..100).map(|i| AccountLockerClaimableResource::NonFungible {
resource_address: "resource_rdx1n2ekdd2m0jsxjt9wasmu3p49twy2yfalpaa6wf08md46sk8dfmldnd".into(),
ids: vec![NonFungibleLocalId::integer(i)]
}).collect(),
vec![
AccountLockerClaimableResource::NonFungible {
resource_address: "resource_rdx1n2ekdd2m0jsxjt9wasmu3p49twy2yfalpaa6wf08md46sk8dfmldnd".into(),
number_of_items: 100,
},
AccountLockerClaimableResource::Fungible {
resource_address: "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd".into(),
amount: Decimal192::one(),
}
],
);

assert_eq!(manifest.instructions().len(), 150)
manifest_eq(manifest, expected_manifest)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::prelude::*;
use std::cmp::min;

/// A claimable resource in an account locker.
#[derive(Debug, Clone, PartialEq, Eq, Hash, uniffi::Enum)]
Expand All @@ -8,18 +9,40 @@ pub enum AccountLockerClaimableResource {
resource_address: ResourceAddress,
amount: Decimal192,
},
/// A non-fungible resource with specific claimable IDs
/// A non-fungible resource with the total number of items that can be claimed
NonFungible {
resource_address: ResourceAddress,
ids: Vec<NonFungibleLocalId>,
number_of_items: u64,
},
}

impl AccountLockerClaimableResource {
pub fn resource_count(&self) -> usize {
pub fn number_of_items(&self) -> u64 {
match self {
Self::Fungible { .. } => 1,
Self::NonFungible { ids, .. } => ids.len(),
Self::NonFungible {
number_of_items, ..
} => *number_of_items,
}
}

/// Coerces the number of items to be at most the given maximum.
///
/// If the resource is fungible, it will be returned as is,
/// because it's always considered to be a single item regardless of the amount.
///
/// If the resource is non-fungible, the number of items will be clamped to the given maximum.
pub fn coerce_number_of_items_at_most(&self, maximum: u64) -> Self {
assert!(maximum > 0, "Invalid input, maximum must be greater than 0");
match self {
Self::Fungible { .. } => self.clone(),
Self::NonFungible {
resource_address,
number_of_items: count,
} => Self::NonFungible {
resource_address: *resource_address,
number_of_items: min(*count, maximum),
},
}
}
}
Expand All @@ -35,10 +58,7 @@ impl HasSampleValues for AccountLockerClaimableResource {
fn sample_other() -> Self {
Self::NonFungible {
resource_address: ResourceAddress::sample_other(),
ids: vec![
NonFungibleLocalId::sample(),
NonFungibleLocalId::sample_other(),
],
number_of_items: 2,
}
}
}
Expand All @@ -60,4 +80,29 @@ mod tests {
fn inequality() {
assert_ne!(SUT::sample(), SUT::sample_other());
}

#[test]
fn number_of_items() {
assert_eq!(SUT::sample().number_of_items(), 1);
assert_eq!(SUT::sample_other().number_of_items(), 2);
}

#[test]
fn coerce_number_of_items_at_most() {
let mut sut = SUT::sample();

let actual = sut.coerce_number_of_items_at_most(10);
assert_eq!(actual, sut);

sut = SUT::NonFungible {
resource_address: ResourceAddress::sample_other(),
number_of_items: 5,
};
let actual = sut.coerce_number_of_items_at_most(1);
let expected = SUT::NonFungible {
resource_address: ResourceAddress::sample_other(),
number_of_items: 1,
};
assert_eq!(actual, expected);
}
}

0 comments on commit e5da6f7

Please sign in to comment.