Skip to content

Commit 986a5b6

Browse files
authored
refactor: account balance (#421)
1 parent e4020db commit 986a5b6

File tree

6 files changed

+234
-430
lines changed

6 files changed

+234
-430
lines changed

pallets/nfts/src/common_functions.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,87 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
151151
.expect("Failed to get next collection ID")
152152
}
153153
}
154+
155+
#[cfg(test)]
156+
mod tests {
157+
use crate::{mock::*, tests::*, Currency, Error, ReservableCurrency};
158+
159+
#[test]
160+
fn increment_account_balance_works() {
161+
new_test_ext().execute_with(|| {
162+
let collection_id = 0;
163+
let deposit_account = account(1);
164+
let deposit_amount = balance_deposit();
165+
let owner = account(2);
166+
assert_noop!(
167+
Nfts::increment_account_balance(
168+
collection_id,
169+
&owner,
170+
(&deposit_account, deposit_amount)
171+
),
172+
BalancesError::<Test>::InsufficientBalance
173+
);
174+
Balances::make_free_balance_be(&deposit_account, 100);
175+
// Initialize `AccountBalance` and increase the collection item count for the new
176+
// account.
177+
assert_ok!(Nfts::increment_account_balance(
178+
collection_id,
179+
&owner,
180+
(&deposit_account, deposit_amount)
181+
));
182+
assert_eq!(Balances::reserved_balance(&deposit_account), deposit_amount);
183+
assert_eq!(
184+
AccountBalance::get(collection_id, &owner),
185+
Some((1, (deposit_account.clone(), deposit_amount)))
186+
);
187+
// Increment the balance of a non-zero balance account. No additional reserves.
188+
assert_ok!(Nfts::increment_account_balance(
189+
collection_id,
190+
&owner,
191+
(&deposit_account, deposit_amount)
192+
));
193+
assert_eq!(Balances::reserved_balance(&deposit_account), deposit_amount);
194+
assert_eq!(
195+
AccountBalance::get(collection_id, &owner),
196+
Some((2, (deposit_account.clone(), deposit_amount)))
197+
);
198+
});
199+
}
200+
201+
#[test]
202+
fn decrement_account_balance_works() {
203+
new_test_ext().execute_with(|| {
204+
let collection_id = 0;
205+
let balance = 2u32;
206+
let deposit_account = account(1);
207+
let deposit_amount = balance_deposit();
208+
let owner = account(2);
209+
210+
Balances::make_free_balance_be(&deposit_account, 100);
211+
// Decrement non-existing `AccountBalance` record.
212+
assert_noop!(
213+
Nfts::decrement_account_balance(collection_id, &deposit_account),
214+
Error::<Test>::NoItemOwned
215+
);
216+
// Set account balance and reserve `DepositBalance`.
217+
AccountBalance::insert(
218+
collection_id,
219+
&owner,
220+
(&balance, (&deposit_account, deposit_amount)),
221+
);
222+
Balances::reserve(&deposit_account, deposit_amount).expect("should work");
223+
// Successfully decrement the value of the `AccountBalance` entry.
224+
assert_ok!(Nfts::decrement_account_balance(collection_id, &owner));
225+
assert_eq!(
226+
AccountBalance::get(collection_id, &owner),
227+
Some((balance - 1, (deposit_account.clone(), deposit_amount)))
228+
);
229+
assert_eq!(Balances::reserved_balance(&deposit_account), deposit_amount);
230+
// `AccountBalance` record is deleted, and the depositor's funds are unreserved if
231+
// the `AccountBalance` value reaches zero after decrementing.
232+
assert_ok!(Nfts::decrement_account_balance(collection_id, &owner));
233+
assert_eq!(Balances::reserved_balance(&deposit_account), 0);
234+
assert!(!AccountBalance::contains_key(collection_id, &owner));
235+
});
236+
}
237+
}

pallets/nfts/src/features/atomic_swap.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,14 +210,16 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
210210

211211
// This also removes the swap.
212212
Self::do_transfer(
213-
Some(&caller),
213+
Some(&receive_item.owner),
214214
send_collection_id,
215215
send_item_id,
216216
receive_item.owner.clone(),
217217
|_, _| Ok(()),
218218
)?;
219+
// Owner of `send_item` is responsible for the deposit if the collection balance
220+
// went to zero due to the previous transfer.
219221
Self::do_transfer(
220-
Some(&caller),
222+
Some(&send_item.owner),
221223
receive_collection_id,
222224
receive_item_id,
223225
send_item.owner.clone(),

pallets/nfts/src/features/transfer.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ use crate::*;
2525
impl<T: Config<I>, I: 'static> Pallet<T, I> {
2626
/// Transfer an NFT to the specified destination account.
2727
///
28-
/// - `caller`: The account transferring the collection item.
28+
/// - `depositor`: The account reserving the `CollectionBalanceDeposit` from if `dest` holds no
29+
/// items in the collection.
2930
/// - `collection`: The ID of the collection to which the NFT belongs.
3031
/// - `item`: The ID of the NFT to transfer.
3132
/// - `dest`: The destination account to which the NFT will be transferred.
@@ -46,7 +47,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
4647
/// - If the collection or item is non-transferable
4748
/// ([`ItemsNonTransferable`](crate::Error::ItemsNonTransferable)).
4849
pub fn do_transfer(
49-
caller: Option<&T::AccountId>,
50+
depositor: Option<&T::AccountId>,
5051
collection: T::CollectionId,
5152
item: T::ItemId,
5253
dest: T::AccountId,
@@ -91,16 +92,15 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
9192
// Update account balance of the owner.
9293
Self::decrement_account_balance(collection, &details.owner)?;
9394

94-
// Update account balance of the destination account.
95-
let deposit_amount =
96-
match collection_config.is_setting_enabled(CollectionSetting::DepositRequired) {
97-
true => T::CollectionBalanceDeposit::get(),
98-
false => Zero::zero(),
99-
};
100-
// Reserve `CollectionBalanceDeposit` from the caller if provided. Otherwise, reserve from
101-
// the collection item's owner.
102-
let deposit_account = caller.unwrap_or(&details.owner);
95+
let deposit_amount = collection_config
96+
.is_setting_enabled(CollectionSetting::DepositRequired)
97+
.then_some(T::CollectionBalanceDeposit::get())
98+
.unwrap_or_default();
99+
// Reserve `CollectionBalanceDeposit` from the depositor if provided. Otherwise, reserve
100+
// from the item's owner.
101+
let deposit_account = depositor.unwrap_or(&details.owner);
103102

103+
// Update account balance of the destination account.
104104
Self::increment_account_balance(collection, &dest, (deposit_account, deposit_amount))?;
105105

106106
// Update account ownership information.

pallets/nfts/src/impl_nonfungibles.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,10 @@ impl<T: Config<I>, I: 'static> Transfer<T::AccountId> for Pallet<T, I> {
411411
item: &Self::ItemId,
412412
destination: &T::AccountId,
413413
) -> DispatchResult {
414+
// The item's owner pays for the deposit of `AccountBalance` if the `dest` holds no items
415+
// in `collection`. A malicious actor could have a deposit reserved from `dest` without
416+
// them knowing about the transfer. The deposit amount can be accounted for in the off chain
417+
// price of the NFT.
414418
Self::do_transfer(None, *collection, *item, destination.clone(), |_, _| Ok(()))
415419
}
416420

pallets/nfts/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,6 +1092,10 @@ pub mod pallet {
10921092
let origin = ensure_signed(origin)?;
10931093
let dest = T::Lookup::lookup(dest)?;
10941094

1095+
// The item's owner pays for the deposit of `AccountBalance` if the `dest` holds no
1096+
// items in `collection`. A malicious actor could have a deposit reserved from `dest`
1097+
// without them knowing about the transfer. The deposit amount can be accounted for
1098+
// in the off chain price of the NFT.
10951099
Self::do_transfer(None, collection, item, dest, |_, details| {
10961100
if details.owner != origin {
10971101
Self::check_approval(&collection, &Some(item), &details.owner, &origin)?;

0 commit comments

Comments
 (0)