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

feat(nfts): AccountBalance deposit #413

Merged
merged 45 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
8a341d9
refactor: account balance deposit
evilrobot-01 Dec 18, 2024
e391888
chore: revert test changes
chungquantin Dec 18, 2024
31368ca
refactor: account balance deposit & tests
chungquantin Dec 19, 2024
3f9e07e
refactor: account balance deposit & tests
chungquantin Dec 19, 2024
99b31d8
Merge branch 'chungquantin/feat-nfts' into frank/nfts-balance-deposit
chungquantin Dec 19, 2024
5ecb661
chore: benchmarking
chungquantin Dec 19, 2024
ee364fb
fix: clippy warning
chungquantin Dec 19, 2024
5015e90
chore: comments
chungquantin Dec 19, 2024
94f0c74
chore: resolve comments (exclude weights)
chungquantin Dec 21, 2024
d3ae740
chore: resolve review comments
chungquantin Dec 22, 2024
ed6d6a2
fix: deposit bytes
chungquantin Dec 22, 2024
bda3e47
chore: update test & weights
chungquantin Dec 22, 2024
460859f
chore: benchmarking
chungquantin Dec 23, 2024
663066e
chore: update comments
chungquantin Dec 23, 2024
d975170
chore: reorder type
chungquantin Dec 23, 2024
f316e2f
chore: resolve comments
chungquantin Dec 26, 2024
28159d0
chore: comments
chungquantin Dec 26, 2024
911c5a9
chore: use balance deposit variable
chungquantin Dec 26, 2024
a16ecdc
chore: update comments
chungquantin Dec 26, 2024
02db7a5
chore: apply suggestion
chungquantin Dec 26, 2024
78a3815
chore: apply review suggestions
chungquantin Dec 30, 2024
26b43f3
chore: revert back test case order
chungquantin Dec 30, 2024
8210e9c
chore: format
chungquantin Dec 30, 2024
ff8d560
chore: remove redudant checks
chungquantin Dec 30, 2024
569aae7
chore: reorder
chungquantin Dec 30, 2024
439ad31
Merge branch 'chungquantin/feat-nfts' into frank/nfts-balance-deposit
chungquantin Dec 30, 2024
40a8bd8
chore: rebase
chungquantin Dec 30, 2024
3d1e367
chore: update tests
chungquantin Dec 30, 2024
b6c2529
chore: apply suggestion
chungquantin Dec 30, 2024
e1a8c4c
chore: test
chungquantin Dec 30, 2024
e2aedba
chore: update tests
chungquantin Dec 31, 2024
1a805f5
chore: update tests
chungquantin Dec 31, 2024
c76d469
Merge branch 'chungquantin/feat-nfts' into frank/nfts-balance-deposit
chungquantin Dec 31, 2024
d3b3141
chore: reformat
chungquantin Dec 31, 2024
7820d79
chore: update tests
chungquantin Dec 31, 2024
df965e1
chore: update tests
chungquantin Dec 31, 2024
6abe359
Merge branch 'chungquantin/feat-nfts' into frank/nfts-balance-deposit
chungquantin Dec 31, 2024
1159074
chore: format
chungquantin Dec 31, 2024
c5fd9e3
chore: resolve review comments
chungquantin Jan 6, 2025
af0b71e
fix: account balance logic (#418)
chungquantin Jan 7, 2025
270039e
chore: update benchmarking.rs
chungquantin Jan 7, 2025
b1719a6
chore: update tests
chungquantin Jan 7, 2025
e4020db
Merge branch 'chungquantin/feat-nfts' into frank/nfts-balance-deposit
chungquantin Jan 7, 2025
986a5b6
refactor: account balance (#421)
Daanvdplas Jan 8, 2025
1aa79f3
chore: resolve feedback comments
chungquantin Jan 9, 2025
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
8 changes: 5 additions & 3 deletions pallets/nfts/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ benchmarks_instance_pallet! {
mint {
let (collection, caller, caller_lookup) = create_collection::<T, I>();
let item = T::Helper::item(0);
T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance() + T::CollectionDeposit::get() + T::BalanceDeposit::get());
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This overrides the balance of caller in create_collection. The purpose is to make this more specific to what are reserved in the function.

}: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup, None)
verify {
assert_last_event::<T, I>(Event::Issued { collection, item, owner: caller }.into());
Expand All @@ -282,6 +283,7 @@ benchmarks_instance_pallet! {
force_mint {
let (collection, caller, caller_lookup) = create_collection::<T, I>();
let item = T::Helper::item(0);
T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance() + T::CollectionDeposit::get() + T::BalanceDeposit::get());
}: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup, default_item_config())
verify {
assert_last_event::<T, I>(Event::Issued { collection, item, owner: caller }.into());
Expand All @@ -301,7 +303,7 @@ benchmarks_instance_pallet! {

let target: T::AccountId = account("target", 0, SEED);
let target_lookup = T::Lookup::unlookup(target.clone());
T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance());
T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance() + T::BalanceDeposit::get());
}: _(SystemOrigin::Signed(caller.clone()), collection, item, target_lookup)
verify {
assert_last_event::<T, I>(Event::Transferred { collection, item, from: caller, to: target }.into());
Expand Down Expand Up @@ -667,7 +669,7 @@ benchmarks_instance_pallet! {
let price = ItemPrice::<T, I>::from(0u32);
let origin = SystemOrigin::Signed(seller.clone()).into();
Nfts::<T, I>::set_price(origin, collection, item, Some(price), Some(buyer_lookup))?;
T::Currency::make_free_balance_be(&buyer, DepositBalanceOf::<T, I>::max_value());
T::Currency::make_free_balance_be(&buyer, T::Currency::minimum_balance() + price + T::BalanceDeposit::get());
}: _(SystemOrigin::Signed(buyer.clone()), collection, item, price)
verify {
assert_last_event::<T, I>(Event::ItemBought {
Expand Down Expand Up @@ -757,7 +759,7 @@ benchmarks_instance_pallet! {
let duration = T::MaxDeadlineDuration::get();
let target: T::AccountId = account("target", 0, SEED);
let target_lookup = T::Lookup::unlookup(target.clone());
T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance());
T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance() + T::BalanceDeposit::get());
let origin = SystemOrigin::Signed(caller.clone());
frame_system::Pallet::<T>::set_block_number(One::one());
Nfts::<T, I>::transfer(origin.clone().into(), collection, item2, target_lookup)?;
Expand Down
48 changes: 47 additions & 1 deletion pallets/nfts/src/common_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

use alloc::vec::Vec;

use frame_support::pallet_prelude::*;
use frame_support::{pallet_prelude::*, sp_runtime::ArithmeticError};

use crate::*;

Expand Down Expand Up @@ -92,6 +92,52 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
Self::deposit_event(Event::NextCollectionIdIncremented { next_id });
}

/// Increment the number of items in the `collection` owned by the `owner`. If no entry exists
/// for the `owner` in `AccountBalance`, create a new record and reserve `deposit_amount` from
/// the `deposit_account`.
pub(crate) fn increment_account_balance(
collection: T::CollectionId,
owner: &T::AccountId,
(deposit_account, deposit_amount): (&T::AccountId, DepositBalanceOf<T, I>),
) -> DispatchResult {
AccountBalance::<T, I>::mutate(collection, owner, |maybe_balance| -> DispatchResult {
match maybe_balance {
None => {
T::Currency::reserve(deposit_account, deposit_amount)?;
*maybe_balance = Some((1, (deposit_account.clone(), deposit_amount)));
},
Some((balance, _deposit)) => {
balance.saturating_inc();
},
}
Ok(())
})
}

/// Decrement the number of `collection` items owned by the `owner`. If the `owner`'s item
/// count reaches zero after the reduction, remove the `AccountBalance` record and unreserve
/// the deposited funds.
pub(crate) fn decrement_account_balance(
collection: T::CollectionId,
owner: &T::AccountId,
) -> DispatchResult {
AccountBalance::<T, I>::try_mutate_exists(
collection,
owner,
|maybe_balance| -> DispatchResult {
let (balance, (deposit_account, deposit_amount)) =
maybe_balance.as_mut().ok_or(Error::<T, I>::NoItemOwned)?;

*balance = balance.checked_sub(1).ok_or(ArithmeticError::Underflow)?;
if *balance == 0 {
chungquantin marked this conversation as resolved.
Show resolved Hide resolved
T::Currency::unreserve(deposit_account, *deposit_amount);
*maybe_balance = None;
}
Ok(())
},
)
}

#[allow(missing_docs)]
#[cfg(any(test, feature = "runtime-benchmarks"))]
pub fn set_next_id(id: T::CollectionId) {
Expand Down
7 changes: 6 additions & 1 deletion pallets/nfts/src/features/approvals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,12 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
Self::is_pallet_feature_enabled(PalletFeature::Approvals),
Error::<T, I>::MethodDisabled
);
ensure!(AccountBalance::<T, I>::get(collection, &owner) > 0, Error::<T, I>::NoItemOwned);
ensure!(
chungquantin marked this conversation as resolved.
Show resolved Hide resolved
AccountBalance::<T, I>::get(collection, &owner)
.filter(|(balance, _)| *balance > 0)
chungquantin marked this conversation as resolved.
Show resolved Hide resolved
.is_some(),
Error::<T, I>::NoItemOwned
);

let collection_config = Self::get_collection_config(&collection)?;
ensure!(
Expand Down
11 changes: 8 additions & 3 deletions pallets/nfts/src/features/atomic_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,15 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
}

// This also removes the swap.
Self::do_transfer(send_collection_id, send_item_id, receive_item.owner.clone(), |_, _| {
Ok(())
})?;
Self::do_transfer(
&caller,
send_collection_id,
send_item_id,
receive_item.owner.clone(),
|_, _| Ok(()),
)?;
Self::do_transfer(
&caller,
receive_collection_id,
receive_item_id,
send_item.owner.clone(),
Expand Down
2 changes: 1 addition & 1 deletion pallets/nfts/src/features/buy_sell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {

let old_owner = details.owner.clone();

Self::do_transfer(collection, item, buyer.clone(), |_, _| Ok(()))?;
Self::do_transfer(&buyer, collection, item, buyer.clone(), |_, _| Ok(()))?;

Self::deposit_event(Event::ItemBought {
collection,
Expand Down
40 changes: 18 additions & 22 deletions pallets/nfts/src/features/create_delete_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
//! This module contains helper methods to perform functionality associated with minting and burning
//! items for the NFTs pallet.

use frame_support::{pallet_prelude::*, sp_runtime::ArithmeticError, traits::ExistenceRequirement};
use frame_support::{pallet_prelude::*, traits::ExistenceRequirement};

use crate::*;

Expand Down Expand Up @@ -68,20 +68,19 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {

collection_details.items.saturating_inc();

AccountBalance::<T, I>::mutate(collection, &mint_to, |balance| {
balance.saturating_inc();
});

let collection_config = Self::get_collection_config(&collection)?;
let deposit_amount =
match collection_config.is_setting_enabled(CollectionSetting::DepositRequired) {
true => T::ItemDeposit::get(),
false => Zero::zero(),
};
let deposit_account = match maybe_depositor {
None => collection_details.owner.clone(),
Some(depositor) => depositor,
};
let deposit_required =
collection_config.is_setting_enabled(CollectionSetting::DepositRequired);
let deposit_account =
maybe_depositor.unwrap_or_else(|| collection_details.owner.clone());

let balance_deposit =
deposit_required.then_some(T::BalanceDeposit::get()).unwrap_or_default();
Self::increment_account_balance(
chungquantin marked this conversation as resolved.
Show resolved Hide resolved
collection,
&mint_to,
(&deposit_account, balance_deposit),
)?;

let item_owner = mint_to.clone();
Account::<T, I>::insert((&item_owner, &collection, &item), ());
Expand All @@ -93,9 +92,11 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
collection_details.item_configs.saturating_inc();
}

T::Currency::reserve(&deposit_account, deposit_amount)?;
let item_deposit =
deposit_required.then_some(T::ItemDeposit::get()).unwrap_or_default();
T::Currency::reserve(&deposit_account, item_deposit)?;

let deposit = ItemDeposit { account: deposit_account, amount: deposit_amount };
let deposit = ItemDeposit { account: deposit_account, amount: item_deposit };
let details = ItemDetails {
owner: item_owner,
approvals: ApprovalsOf::<T, I>::default(),
Expand Down Expand Up @@ -264,12 +265,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
PendingSwapOf::<T, I>::remove(collection, item);
ItemAttributesApprovalsOf::<T, I>::remove(collection, item);

let balance = AccountBalance::<T, I>::take(collection, &owner)
.checked_sub(1)
.ok_or(ArithmeticError::Underflow)?;
if balance > 0 {
AccountBalance::<T, I>::insert(collection, &owner, balance);
}
Self::decrement_account_balance(collection, &owner)?;

if remove_config {
ItemConfigOf::<T, I>::remove(collection, item);
Expand Down
26 changes: 17 additions & 9 deletions pallets/nfts/src/features/transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use crate::*;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Transfer an NFT to the specified destination account.
///
/// - `caller`: The account transferring the collection item.
/// - `collection`: The ID of the collection to which the NFT belongs.
/// - `item`: The ID of the NFT to transfer.
/// - `dest`: The destination account to which the NFT will be transferred.
Expand All @@ -45,6 +46,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// - If the collection or item is non-transferable
/// ([`ItemsNonTransferable`](crate::Error::ItemsNonTransferable)).
pub fn do_transfer(
caller: &T::AccountId,
collection: T::CollectionId,
item: T::ItemId,
dest: T::AccountId,
Expand Down Expand Up @@ -87,16 +89,22 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
with_details(&collection_details, &mut details)?;

// Update account balance of the owner.
let balance = AccountBalance::<T, I>::take(collection, &details.owner)
.checked_sub(1)
.ok_or(Error::<T, I>::NoItemOwned)?;
if balance > 0 {
AccountBalance::<T, I>::insert(collection, &details.owner, balance);
}
Self::decrement_account_balance(collection, &details.owner)?;

// Update account balance of the destination account.
AccountBalance::<T, I>::mutate(collection, &dest, |balance| {
balance.saturating_inc();
});
let deposit_amount =
match collection_config.is_setting_enabled(CollectionSetting::DepositRequired) {
true => T::BalanceDeposit::get(),
false => Zero::zero(),
};
// The destination account covers the `BalanceDeposit` if it has sufficient balance.
// Otherwise, the caller is accountable for it.
chungquantin marked this conversation as resolved.
Show resolved Hide resolved
let deposit_account = match T::Currency::can_reserve(&dest, T::BalanceDeposit::get()) {
chungquantin marked this conversation as resolved.
Show resolved Hide resolved
true => &dest,
false => caller,
};

Self::increment_account_balance(collection, &dest, (deposit_account, deposit_amount))?;

// Update account ownership information.
Account::<T, I>::remove((&details.owner, &collection, &item));
Expand Down
2 changes: 1 addition & 1 deletion pallets/nfts/src/impl_nonfungibles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ impl<T: Config<I>, I: 'static> Transfer<T::AccountId> for Pallet<T, I> {
item: &Self::ItemId,
destination: &T::AccountId,
) -> DispatchResult {
Self::do_transfer(*collection, *item, destination.clone(), |_, _| Ok(()))
Self::do_transfer(destination, *collection, *item, destination.clone(), |_, _| Ok(()))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DQ: Is the above function (transfer of Transfer trait) something that other pallets can call (in other words public)?

Curious because it means that the destination account always has to pay for the deposit.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that function is used by other pallets. I put destination because we don't have any origin provided and we can't change the trait interface.

Copy link
Collaborator

@Daanvdplas Daanvdplas Dec 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a concern but like you say, we have no other origin to put in here and in the end the destination account should be the one paying for it. Perhaps in the future we could make the change to the trait function to allow for the caller parameter but I'd say this is something to reassess later.

}

fn disable_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> DispatchResult {
Expand Down
23 changes: 14 additions & 9 deletions pallets/nfts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,6 @@ pub mod pallet {
#[pallet::constant]
type CollectionDeposit: Get<DepositBalanceOf<Self, I>>;

/// The basic amount of funds that must be reserved for a collection approval.
// Key: `sizeof((CollectionId, AccountId, AccountId))` bytes.
// Value: `sizeof((Option<BlockNumber>, Balance))` bytes.
#[pallet::constant]
type CollectionApprovalDeposit: Get<DepositBalanceOf<Self, I>>;
chungquantin marked this conversation as resolved.
Show resolved Hide resolved

/// The basic amount of funds that must be reserved for an item.
#[pallet::constant]
type ItemDeposit: Get<DepositBalanceOf<Self, I>>;
Expand Down Expand Up @@ -259,6 +253,18 @@ pub mod pallet {

/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;

/// The basic amount of funds that must be reserved for a collection approval.
chungquantin marked this conversation as resolved.
Show resolved Hide resolved
// Key: `sizeof((CollectionId, AccountId, AccountId))` bytes.
// Value: `sizeof((Option<BlockNumber>, Balance))` bytes.
#[pallet::constant]
type CollectionApprovalDeposit: Get<DepositBalanceOf<Self, I>>;

/// The basic amount of funds that must be reversed for an account balance.
// Key: `sizeof((CollectionId, AccountId))` bytes.
// Value: `sizeof((u32, Some(AccountId, Balance)))` bytes.
#[pallet::constant]
type BalanceDeposit: Get<DepositBalanceOf<Self, I>>;
chungquantin marked this conversation as resolved.
Show resolved Hide resolved
}

/// Details of a collection.
Expand Down Expand Up @@ -435,8 +441,7 @@ pub mod pallet {
T::CollectionId,
Blake2_128Concat,
T::AccountId,
u32,
ValueQuery,
(u32, AccountDepositOf<T, I>),
chungquantin marked this conversation as resolved.
Show resolved Hide resolved
>;

/// Permission for a delegate to transfer all owner's collection items.
Expand Down Expand Up @@ -1086,7 +1091,7 @@ pub mod pallet {
let origin = ensure_signed(origin)?;
let dest = T::Lookup::lookup(dest)?;

Self::do_transfer(collection, item, dest, |_, details| {
Self::do_transfer(&origin, collection, item, dest, |_, details| {
if details.owner != origin {
Self::check_approval(&collection, &Some(item), &details.owner, &origin)?;
}
Expand Down
1 change: 1 addition & 0 deletions pallets/nfts/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ parameter_types! {
impl Config for Test {
type ApprovalsLimit = ConstU32<10>;
type AttributeDepositBase = ConstU64<1>;
type BalanceDeposit = ConstU64<1>;
type CollectionApprovalDeposit = ConstU64<1>;
type CollectionDeposit = ConstU64<2>;
type CollectionId = u32;
Expand Down
Loading
Loading