diff --git a/Cargo.lock b/Cargo.lock index d2bf2acec..96d8c4e0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10807,12 +10807,16 @@ name = "pop-runtime-common" version = "0.0.0" dependencies = [ "frame-support", + "log", "parachains-common", "parity-scale-codec", "polkadot-primitives", "scale-info", "sp-runtime", "sp-std", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", ] [[package]] diff --git a/integration-tests/src/chains/pop_network/mod.rs b/integration-tests/src/chains/pop_network/mod.rs index 41daed278..f0a3496f1 100644 --- a/integration-tests/src/chains/pop_network/mod.rs +++ b/integration-tests/src/chains/pop_network/mod.rs @@ -27,6 +27,7 @@ decl_test_parachains! { pallets = { PolkadotXcm: runtime::PolkadotXcm, Balances: runtime::Balances, + ForeignNfts: runtime::ForeignNfts, } }, } diff --git a/pallets/nfts/README.md b/pallets/nfts/README.md index 93ccf2949..93ed18cf3 100644 --- a/pallets/nfts/README.md +++ b/pallets/nfts/README.md @@ -51,7 +51,9 @@ The NFTs pallet in Substrate is designed to make the following possible: * `transfer`: Send an item to a new owner. * `redeposit`: Update the deposit amount of an item, potentially freeing funds. * `approve_transfer`: Name a delegate who may authorize a transfer. +* `approve_collection_transfer`: Name a delegate who may authorize a transfer of all collection items owned by the account. * `cancel_approval`: Revert the effects of a previous `approve_transfer`. +* `cancel_collection_transfer`: Revert the effects of a previous `approve_collection_transfer`. * `approve_item_attributes`: Name a delegate who may change item's attributes within a namespace. * `cancel_item_attributes_approval`: Revert the effects of a previous `approve_item_attributes`. * `set_price`: Set the price for an item. @@ -70,6 +72,7 @@ The NFTs pallet in Substrate is designed to make the following possible: * `lock_item_transfer`: Prevent an individual item from being transferred. * `unlock_item_transfer`: Revert the effects of a previous `lock_item_transfer`. * `clear_all_transfer_approvals`: Clears all transfer approvals set by calling the `approve_transfer`. +* `clear_collection_approvals`: Clears collection approvals set by calling the `approve_collection_transfer`. * `lock_collection`: Prevent all items within a collection from being transferred (making them all `soul bound`). * `lock_item_properties`: Lock item's metadata or attributes. * `transfer_ownership`: Alter the owner of a collection, moving all associated deposits. (Ownership of individual items @@ -95,6 +98,9 @@ The NFTs pallet in Substrate is designed to make the following possible: * `force_collection_owner`: Change collection's owner. * `force_collection_config`: Change collection's config. * `force_set_attribute`: Set an attribute. +* `force_approve_collection_transfer`: Name a delegate who may authorize a transfer of all collection items owned by the specified account. +* `force_cancel_collection_transfer`: Revert the effects of a previous `approve_collection_transfer`. +* `force_clear_collection_approvals`: Reverts the effects of all previous `approve_collection_transfer` calls for the collection. Please refer to the [`Call`](https://paritytech.github.io/substrate/master/pallet_nfts/pallet/enum.Call.html) enum and its associated variants for documentation on each function. diff --git a/pallets/nfts/src/benchmarking.rs b/pallets/nfts/src/benchmarking.rs index bc25602c5..efbebae21 100644 --- a/pallets/nfts/src/benchmarking.rs +++ b/pallets/nfts/src/benchmarking.rs @@ -67,15 +67,15 @@ fn mint_item, I: 'static>( ) -> (T::ItemId, T::AccountId, AccountIdLookupOf) { let item = T::Helper::item(index); let collection = T::Helper::collection(0); - let caller = Collection::::get(collection).unwrap().owner; + let caller = Collection::::get(&collection).unwrap().owner; if caller != whitelisted_caller() { whitelist_account!(caller); } let caller_lookup = T::Lookup::unlookup(caller.clone()); - let item_exists = Item::::contains_key(collection, item); - let item_config = ItemConfigOf::::get(collection, item); + let item_exists = Item::::contains_key(&collection, item); + let item_config = ItemConfigOf::::get(&collection, item); if item_exists { - return (item, caller, caller_lookup) + return (item, caller, caller_lookup); } else if let Some(item_config) = item_config { assert_ok!(Nfts::::force_mint( SystemOrigin::Signed(caller.clone()).into(), @@ -265,8 +265,8 @@ benchmarks_instance_pallet! { for i in 0..a { add_collection_attribute::(i as u16); } - let witness = Collection::::get(collection).unwrap().destroy_witness(); - }: _(SystemOrigin::Signed(caller), collection, witness) + let witness = Collection::::get(&collection).unwrap().destroy_witness(); + }: _(SystemOrigin::Signed(caller), collection.clone(), witness) verify { assert_last_event::(Event::Destroyed { collection }.into()); } @@ -274,7 +274,7 @@ benchmarks_instance_pallet! { mint { let (collection, caller, caller_lookup) = create_collection::(); let item = T::Helper::item(0); - }: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup, None) + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), item, caller_lookup, None) verify { assert_last_event::(Event::Issued { collection, item, owner: caller }.into()); } @@ -282,7 +282,7 @@ benchmarks_instance_pallet! { force_mint { let (collection, caller, caller_lookup) = create_collection::(); let item = T::Helper::item(0); - }: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup, default_item_config()) + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), item, caller_lookup, default_item_config()) verify { assert_last_event::(Event::Issued { collection, item, owner: caller }.into()); } @@ -290,7 +290,7 @@ benchmarks_instance_pallet! { burn { let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); - }: _(SystemOrigin::Signed(caller.clone()), collection, item) + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), item) verify { assert_last_event::(Event::Burned { collection, item, owner: caller }.into()); } @@ -302,7 +302,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()); - }: _(SystemOrigin::Signed(caller.clone()), collection, item, target_lookup) + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), item, target_lookup) verify { assert_last_event::(Event::Transferred { collection, item, from: caller, to: target }.into()); } @@ -313,10 +313,10 @@ benchmarks_instance_pallet! { let items = (0..i).map(|x| mint_item::(x as u16).0).collect::>(); Nfts::::force_collection_config( SystemOrigin::Root.into(), - collection, + collection.clone(), make_collection_config::(CollectionSetting::DepositRequired.into()), )?; - }: _(SystemOrigin::Signed(caller.clone()), collection, items.clone()) + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), items.clone()) verify { assert_last_event::(Event::Redeposited { collection, successful_items: items }.into()); } @@ -334,10 +334,10 @@ benchmarks_instance_pallet! { let (item, ..) = mint_item::(0); Nfts::::lock_item_transfer( SystemOrigin::Signed(caller.clone()).into(), - collection, + collection.clone(), item, )?; - }: _(SystemOrigin::Signed(caller.clone()), collection, item) + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), item) verify { assert_last_event::(Event::ItemTransferUnlocked { collection, item }.into()); } @@ -350,7 +350,7 @@ benchmarks_instance_pallet! { CollectionSetting::UnlockedAttributes | CollectionSetting::UnlockedMaxSupply, ); - }: _(SystemOrigin::Signed(caller.clone()), collection, lock_settings) + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), lock_settings) verify { assert_last_event::(Event::CollectionLocked { collection }.into()); } @@ -361,8 +361,8 @@ benchmarks_instance_pallet! { let target_lookup = T::Lookup::unlookup(target.clone()); T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); let origin = SystemOrigin::Signed(target.clone()).into(); - Nfts::::set_accept_ownership(origin, Some(collection))?; - }: _(SystemOrigin::Signed(caller), collection, target_lookup) + Nfts::::set_accept_ownership(origin, Some(collection.clone()))?; + }: _(SystemOrigin::Signed(caller), collection.clone(), target_lookup) verify { assert_last_event::(Event::OwnerChanged { collection, new_owner: target }.into()); } @@ -372,7 +372,7 @@ benchmarks_instance_pallet! { let target0 = Some(T::Lookup::unlookup(account("target", 0, SEED))); let target1 = Some(T::Lookup::unlookup(account("target", 1, SEED))); let target2 = Some(T::Lookup::unlookup(account("target", 2, SEED))); - }: _(SystemOrigin::Signed(caller), collection, target0, target1, target2) + }: _(SystemOrigin::Signed(caller), collection.clone(), target0, target1, target2) verify { assert_last_event::(Event::TeamChanged{ collection, @@ -390,7 +390,7 @@ benchmarks_instance_pallet! { let target_lookup = T::Lookup::unlookup(target.clone()); T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); let call = Call::::force_collection_owner { - collection, + collection: collection.clone(), owner: target_lookup, }; }: { call.dispatch_bypass_filter(origin)? } @@ -403,7 +403,7 @@ benchmarks_instance_pallet! { let origin = T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let call = Call::::force_collection_config { - collection, + collection: collection.clone(), config: make_collection_config::(CollectionSetting::DepositRequired.into()), }; }: { call.dispatch_bypass_filter(origin)? } @@ -416,7 +416,7 @@ benchmarks_instance_pallet! { let (item, ..) = mint_item::(0); let lock_metadata = true; let lock_attributes = true; - }: _(SystemOrigin::Signed(caller), collection, item, lock_metadata, lock_attributes) + }: _(SystemOrigin::Signed(caller), collection.clone(), item, lock_metadata, lock_attributes) verify { assert_last_event::(Event::ItemPropertiesLocked { collection, item, lock_metadata, lock_attributes }.into()); } @@ -427,7 +427,7 @@ benchmarks_instance_pallet! { let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); - }: _(SystemOrigin::Signed(caller), collection, Some(item), AttributeNamespace::CollectionOwner, key.clone(), value.clone()) + }: _(SystemOrigin::Signed(caller), collection.clone(), Some(item), AttributeNamespace::CollectionOwner, key.clone(), value.clone()) verify { assert_last_event::( Event::AttributeSet { @@ -447,7 +447,7 @@ benchmarks_instance_pallet! { let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); - }: _(SystemOrigin::Root, Some(caller), collection, Some(item), AttributeNamespace::CollectionOwner, key.clone(), value.clone()) + }: _(SystemOrigin::Root, Some(caller), collection.clone(), Some(item), AttributeNamespace::CollectionOwner, key.clone(), value.clone()) verify { assert_last_event::( Event::AttributeSet { @@ -466,7 +466,7 @@ benchmarks_instance_pallet! { let (item, ..) = mint_item::(0); add_item_metadata::(item); let (key, ..) = add_item_attribute::(item); - }: _(SystemOrigin::Signed(caller), collection, Some(item), AttributeNamespace::CollectionOwner, key.clone()) + }: _(SystemOrigin::Signed(caller), collection.clone(), Some(item), AttributeNamespace::CollectionOwner, key.clone()) verify { assert_last_event::( Event::AttributeCleared { @@ -483,7 +483,7 @@ benchmarks_instance_pallet! { let (item, ..) = mint_item::(0); let target: T::AccountId = account("target", 0, SEED); let target_lookup = T::Lookup::unlookup(target.clone()); - }: _(SystemOrigin::Signed(caller), collection, item, target_lookup) + }: _(SystemOrigin::Signed(caller), collection.clone(), item, target_lookup) verify { assert_last_event::( Event::ItemAttributesApprovalAdded { @@ -504,7 +504,7 @@ benchmarks_instance_pallet! { let target_lookup = T::Lookup::unlookup(target.clone()); Nfts::::approve_item_attributes( SystemOrigin::Signed(caller.clone()).into(), - collection, + collection.clone(), item, target_lookup.clone(), )?; @@ -522,7 +522,7 @@ benchmarks_instance_pallet! { )?; } let witness = CancelAttributesApprovalWitness { account_attributes: n }; - }: _(SystemOrigin::Signed(caller), collection, item, target_lookup, witness) + }: _(SystemOrigin::Signed(caller), collection.clone(), item, target_lookup, witness) verify { assert_last_event::( Event::ItemAttributesApprovalRemoved { @@ -539,7 +539,7 @@ benchmarks_instance_pallet! { let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); - }: _(SystemOrigin::Signed(caller), collection, item, data.clone()) + }: _(SystemOrigin::Signed(caller), collection.clone(), item, data.clone()) verify { assert_last_event::(Event::ItemMetadataSet { collection, item, data }.into()); } @@ -548,7 +548,7 @@ benchmarks_instance_pallet! { let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); add_item_metadata::(item); - }: _(SystemOrigin::Signed(caller), collection, item) + }: _(SystemOrigin::Signed(caller), collection.clone(), item) verify { assert_last_event::(Event::ItemMetadataCleared { collection, item }.into()); } @@ -557,7 +557,7 @@ benchmarks_instance_pallet! { let data: BoundedVec<_, _> = vec![0u8; T::StringLimit::get() as usize].try_into().unwrap(); let (collection, caller, _) = create_collection::(); - }: _(SystemOrigin::Signed(caller), collection, data.clone()) + }: _(SystemOrigin::Signed(caller), collection.clone(), data.clone()) verify { assert_last_event::(Event::CollectionMetadataSet { collection, data }.into()); } @@ -565,7 +565,7 @@ benchmarks_instance_pallet! { clear_collection_metadata { let (collection, caller, _) = create_collection::(); add_collection_metadata::(); - }: _(SystemOrigin::Signed(caller), collection) + }: _(SystemOrigin::Signed(caller), collection.clone()) verify { assert_last_event::(Event::CollectionMetadataCleared { collection }.into()); } @@ -576,9 +576,32 @@ benchmarks_instance_pallet! { let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let deadline = BlockNumberFor::::max_value(); - }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup, Some(deadline)) + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), item, delegate_lookup, Some(deadline)) + verify { + assert_last_event::(Event::TransferApproved { collection, item: Some(item), owner: caller, delegate, deadline: Some(deadline) }.into()); + } + + approve_collection_transfer { + let (collection, caller, _) = create_collection::(); + mint_item::(0); + let delegate: T::AccountId = account("delegate", 0, SEED); + let delegate_lookup = T::Lookup::unlookup(delegate.clone()); + let deadline = BlockNumberFor::::max_value(); + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), delegate_lookup, Some(deadline)) verify { - assert_last_event::(Event::TransferApproved { collection, item, owner: caller, delegate, deadline: Some(deadline) }.into()); + assert_last_event::(Event::TransferApproved { collection, item: None, owner: caller, delegate, deadline: Some(deadline) }.into()); + } + + force_approve_collection_transfer { + let (collection, caller, _) = create_collection::(); + mint_item::(0); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + let delegate: T::AccountId = account("delegate", 0, SEED); + let delegate_lookup = T::Lookup::unlookup(delegate.clone()); + let deadline = BlockNumberFor::::max_value(); + }: _(SystemOrigin::Root, caller_lookup, collection.clone(), delegate_lookup, Some(deadline)) + verify { + assert_last_event::(Event::TransferApproved { collection, item: None, owner: caller, delegate, deadline: Some(deadline) }.into()); } cancel_approval { @@ -588,10 +611,36 @@ benchmarks_instance_pallet! { let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let origin = SystemOrigin::Signed(caller.clone()).into(); let deadline = BlockNumberFor::::max_value(); - Nfts::::approve_transfer(origin, collection, item, delegate_lookup.clone(), Some(deadline))?; - }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup) + Nfts::::approve_transfer(origin, collection.clone(), item, delegate_lookup.clone(), Some(deadline))?; + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), item, delegate_lookup) + verify { + assert_last_event::(Event::ApprovalCancelled { collection, item: Some(item), owner: caller, delegate }.into()); + } + + cancel_collection_approval { + let (collection, caller, _) = create_collection::(); + mint_item::(0); + let delegate: T::AccountId = account("delegate", 0, SEED); + let delegate_lookup = T::Lookup::unlookup(delegate.clone()); + let origin = SystemOrigin::Signed(caller.clone()).into(); + let deadline = BlockNumberFor::::max_value(); + Nfts::::approve_collection_transfer(origin, collection.clone(), delegate_lookup.clone(), Some(deadline))?; + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), delegate_lookup) + verify { + assert_last_event::(Event::ApprovalCancelled { collection, item: None, owner: caller, delegate }.into()); + } + + force_cancel_collection_approval { + let (collection, caller, _) = create_collection::(); + mint_item::(0); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + let delegate: T::AccountId = account("delegate", 0, SEED); + let delegate_lookup = T::Lookup::unlookup(delegate.clone()); + let deadline = BlockNumberFor::::max_value(); + Nfts::::approve_collection_transfer(SystemOrigin::Signed(caller.clone()).into(), collection.clone(), delegate_lookup.clone(), Some(deadline))?; + }: _(SystemOrigin::Root, caller_lookup, collection.clone(), delegate_lookup) verify { - assert_last_event::(Event::ApprovalCancelled { collection, item, owner: caller, delegate }.into()); + assert_last_event::(Event::ApprovalCancelled { collection, item: None, owner: caller, delegate }.into()); } clear_all_transfer_approvals { @@ -601,17 +650,54 @@ benchmarks_instance_pallet! { let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let origin = SystemOrigin::Signed(caller.clone()).into(); let deadline = BlockNumberFor::::max_value(); - Nfts::::approve_transfer(origin, collection, item, delegate_lookup.clone(), Some(deadline))?; - }: _(SystemOrigin::Signed(caller.clone()), collection, item) + Nfts::::approve_transfer(origin, collection.clone(), item, delegate_lookup.clone(), Some(deadline))?; + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), item) verify { - assert_last_event::(Event::AllApprovalsCancelled {collection, item, owner: caller}.into()); + assert_last_event::(Event::AllApprovalsCancelled {collection, item: Some(item), owner: caller}.into()); + } + + clear_collection_approvals { + let n in 1 .. T::ApprovalsLimit::get(); + let (collection, caller, _) = create_collection::(); + mint_item::(0); + for i in 0 .. n { + let delegate: T::AccountId = account("delegate", i, SEED); + Nfts::::approve_collection_transfer( + SystemOrigin::Signed(caller.clone()).into(), + collection.clone(), + T::Lookup::unlookup(delegate), + Some(BlockNumberFor::::max_value()), + )?; + } + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), n) + verify { + assert_last_event::(Event::ApprovalsCancelled {collection, item: None, owner: caller}.into()); + } + + force_clear_collection_approvals { + let n in 1 .. T::ApprovalsLimit::get(); + let (collection, caller, _) = create_collection::(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + mint_item::(0); + for i in 0 .. n { + let delegate: T::AccountId = account("delegate", i, SEED); + Nfts::::approve_collection_transfer( + SystemOrigin::Signed(caller.clone()).into(), + collection.clone(), + T::Lookup::unlookup(delegate), + Some(BlockNumberFor::::max_value()), + )?; + } + }: _(SystemOrigin::Root, caller_lookup, collection.clone(), n) + verify { + assert_last_event::(Event::ApprovalsCancelled {collection, item: None, owner: caller}.into()); } set_accept_ownership { let caller: T::AccountId = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); let collection = T::Helper::collection(0); - }: _(SystemOrigin::Signed(caller.clone()), Some(collection)) + }: _(SystemOrigin::Signed(caller.clone()), Some(collection.clone())) verify { assert_last_event::(Event::OwnershipAcceptanceChanged { who: caller, @@ -621,7 +707,7 @@ benchmarks_instance_pallet! { set_collection_max_supply { let (collection, caller, _) = create_collection::(); - }: _(SystemOrigin::Signed(caller.clone()), collection, u32::MAX) + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), u32::MAX) verify { assert_last_event::(Event::CollectionMaxSupplySet { collection, @@ -638,7 +724,7 @@ benchmarks_instance_pallet! { price: Some(ItemPrice::::from(1u32)), default_item_settings: ItemSettings::all_enabled(), }; - }: _(SystemOrigin::Signed(caller.clone()), collection, mint_settings) + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), mint_settings) verify { assert_last_event::(Event::CollectionMintSettingsUpdated { collection }.into()); } @@ -649,7 +735,7 @@ benchmarks_instance_pallet! { let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let price = ItemPrice::::from(100u32); - }: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(price), Some(delegate_lookup)) + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), item, Some(price), Some(delegate_lookup)) verify { assert_last_event::(Event::ItemPriceSet { collection, @@ -666,9 +752,9 @@ benchmarks_instance_pallet! { let buyer_lookup = T::Lookup::unlookup(buyer.clone()); let price = ItemPrice::::from(0u32); let origin = SystemOrigin::Signed(seller.clone()).into(); - Nfts::::set_price(origin, collection, item, Some(price), Some(buyer_lookup))?; + Nfts::::set_price(origin, collection.clone(), item, Some(price), Some(buyer_lookup))?; T::Currency::make_free_balance_be(&buyer, DepositBalanceOf::::max_value()); - }: _(SystemOrigin::Signed(buyer.clone()), collection, item, price) + }: _(SystemOrigin::Signed(buyer.clone()), collection.clone(), item, price) verify { assert_last_event::(Event::ItemBought { collection, @@ -687,7 +773,7 @@ benchmarks_instance_pallet! { let item = T::Helper::item(0); let tips: BoundedVec<_, _> = vec![ ItemTip - { collection, item, receiver: caller.clone(), amount }; n as usize + { collection: collection.clone(), item, receiver: caller.clone(), amount }; n as usize ].try_into().unwrap(); }: _(SystemOrigin::Signed(caller.clone()), tips) verify { @@ -711,11 +797,11 @@ benchmarks_instance_pallet! { let price_with_direction = PriceWithDirection { amount: price, direction: price_direction }; let duration = T::MaxDeadlineDuration::get(); frame_system::Pallet::::set_block_number(One::one()); - }: _(SystemOrigin::Signed(caller.clone()), collection, item1, collection, Some(item2), Some(price_with_direction.clone()), duration) + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), item1, collection.clone(), Some(item2), Some(price_with_direction.clone()), duration) verify { let current_block = frame_system::Pallet::::block_number(); assert_last_event::(Event::SwapCreated { - offered_collection: collection, + offered_collection: collection.clone(), offered_item: item1, desired_collection: collection, desired_item: Some(item2), @@ -734,11 +820,11 @@ benchmarks_instance_pallet! { let price_direction = PriceDirection::Receive; let price_with_direction = PriceWithDirection { amount: price, direction: price_direction }; frame_system::Pallet::::set_block_number(One::one()); - Nfts::::create_swap(origin, collection, item1, collection, Some(item2), Some(price_with_direction.clone()), duration)?; - }: _(SystemOrigin::Signed(caller.clone()), collection, item1) + Nfts::::create_swap(origin, collection.clone(), item1, collection.clone(), Some(item2), Some(price_with_direction.clone()), duration)?; + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), item1) verify { assert_last_event::(Event::SwapCancelled { - offered_collection: collection, + offered_collection: collection.clone(), offered_item: item1, desired_collection: collection, desired_item: Some(item2), @@ -760,21 +846,21 @@ benchmarks_instance_pallet! { T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); let origin = SystemOrigin::Signed(caller.clone()); frame_system::Pallet::::set_block_number(One::one()); - Nfts::::transfer(origin.clone().into(), collection, item2, target_lookup)?; + Nfts::::transfer(origin.clone().into(), collection.clone(), item2, target_lookup)?; Nfts::::create_swap( origin.clone().into(), - collection, + collection.clone(), item1, - collection, + collection.clone(), Some(item2), Some(price_with_direction.clone()), duration, )?; - }: _(SystemOrigin::Signed(target.clone()), collection, item2, collection, item1, Some(price_with_direction.clone())) + }: _(SystemOrigin::Signed(target.clone()), collection.clone(), item2, collection.clone(), item1, Some(price_with_direction.clone())) verify { let current_block = frame_system::Pallet::::block_number(); assert_last_event::(Event::SwapClaimed { - sent_collection: collection, + sent_collection: collection.clone(), sent_item: item2, sent_item_owner: target, received_collection: collection, @@ -807,7 +893,7 @@ benchmarks_instance_pallet! { attributes.push((attribute_key, attribute_value.clone())); } let mint_data = PreSignedMint { - collection, + collection: collection.clone(), item, attributes, metadata: metadata.clone(), @@ -841,7 +927,7 @@ benchmarks_instance_pallet! { let item = T::Helper::item(0); assert_ok!(Nfts::::force_mint( SystemOrigin::Root.into(), - collection, + collection.clone(), item, item_owner_lookup.clone(), default_item_config(), @@ -854,7 +940,7 @@ benchmarks_instance_pallet! { attributes.push((attribute_key, attribute_value.clone())); } let pre_signed_data = PreSignedAttributes { - collection, + collection: collection.clone(), item, attributes, namespace: AttributeNamespace::Account(signer.clone()), diff --git a/pallets/nfts/src/common_functions.rs b/pallets/nfts/src/common_functions.rs index f9224046f..273a810fb 100644 --- a/pallets/nfts/src/common_functions.rs +++ b/pallets/nfts/src/common_functions.rs @@ -25,15 +25,38 @@ use crate::*; impl, I: 'static> Pallet { /// Get the owner of the item, if the item exists. + /// + /// - `collection`: The identifier of the collection. + /// - `item`: The identifier of the collection item. pub fn owner(collection: T::CollectionId, item: T::ItemId) -> Option { Item::::get(collection, item).map(|i| i.owner) } /// Get the owner of the collection, if the collection exists. + /// + /// - `collection`: The identifier of the collection. pub fn collection_owner(collection: T::CollectionId) -> Option { Collection::::get(collection).map(|i| i.owner) } + /// Get the total number of items in the collection, if the collection exists. + /// + /// - `collection`: The identifier of the collection. + pub fn collection_items(collection: T::CollectionId) -> Option { + Collection::::get(collection).map(|i| i.items) + } + + /// Get the metadata of the collection item. + /// + /// - `collection`: The identifier of the collection. + /// - `item`: The identifier of the collection item. + pub fn item_metadata( + collection: T::CollectionId, + item: T::ItemId, + ) -> Option> { + ItemMetadataOf::::get(collection, item).map(|metadata| metadata.data) + } + /// Validates the signature of the given data with the provided signer's account ID. /// /// # Errors @@ -46,7 +69,7 @@ impl, I: 'static> Pallet { signer: &T::AccountId, ) -> DispatchResult { if signature.verify(&**data, signer) { - return Ok(()) + return Ok(()); } // NOTE: for security reasons modern UIs implicitly wrap the data requested to sign into @@ -65,7 +88,7 @@ impl, I: 'static> Pallet { pub(crate) fn set_next_collection_id(collection: T::CollectionId) { let next_id = collection.increment(); - NextCollectionId::::set(next_id); + NextCollectionId::::set(next_id.clone()); Self::deposit_event(Event::NextCollectionIdIncremented { next_id }); } diff --git a/pallets/nfts/src/features/approvals.rs b/pallets/nfts/src/features/approvals.rs index 5ffaeea72..77b46df1a 100644 --- a/pallets/nfts/src/features/approvals.rs +++ b/pallets/nfts/src/features/approvals.rs @@ -20,6 +20,7 @@ //! to have the functionality defined in this module. use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::BlockNumberFor; use crate::*; @@ -47,13 +48,13 @@ impl, I: 'static> Pallet { collection: T::CollectionId, item: T::ItemId, delegate: T::AccountId, - maybe_deadline: Option>, + maybe_deadline: Option>, ) -> DispatchResult { ensure!( Self::is_pallet_feature_enabled(PalletFeature::Approvals), Error::::MethodDisabled ); - let mut details = Item::::get(collection, item).ok_or(Error::::UnknownItem)?; + let mut details = Item::::get(&collection, item).ok_or(Error::::UnknownItem)?; let collection_config = Self::get_collection_config(&collection)?; ensure!( @@ -72,16 +73,15 @@ impl, I: 'static> Pallet { .approvals .try_insert(delegate.clone(), deadline) .map_err(|_| Error::::ReachedApprovalLimit)?; - Item::::insert(collection, item, &details); + Item::::insert(&collection, item, &details); Self::deposit_event(Event::TransferApproved { collection, - item, + item: Some(item), owner: details.owner, delegate, deadline, }); - Ok(()) } @@ -105,7 +105,7 @@ impl, I: 'static> Pallet { item: T::ItemId, delegate: T::AccountId, ) -> DispatchResult { - let mut details = Item::::get(collection, item).ok_or(Error::::UnknownItem)?; + let mut details = Item::::get(&collection, item).ok_or(Error::::UnknownItem)?; let maybe_deadline = details.approvals.get(&delegate).ok_or(Error::::NotDelegate)?; @@ -122,12 +122,19 @@ impl, I: 'static> Pallet { } } + // Cannot revoke approval for a specific collection item if the delegate has + // permission to transfer all collection items owned by the origin. + ensure!( + !CollectionApprovals::::contains_key((&collection, &details.owner, &delegate)), + Error::::DelegateApprovalConflict + ); + details.approvals.remove(&delegate); - Item::::insert(collection, item, &details); + Item::::insert(&collection, item, &details); Self::deposit_event(Event::ApprovalCancelled { collection, - item, + item: Some(item), owner: details.owner, delegate, }); @@ -146,7 +153,7 @@ impl, I: 'static> Pallet { /// - `maybe_check_origin`: The optional account that is required to be the owner of the item, /// granting permission to clear all transfer approvals. If `None`, no permission check is /// performed. - /// - `collection`: The collection ID containing the item. + /// - `collection`: The identifier of the collection. /// - `item`: The item ID for which transfer approvals will be cleared. pub(crate) fn do_clear_all_transfer_approvals( maybe_check_origin: Option, @@ -154,21 +161,208 @@ impl, I: 'static> Pallet { item: T::ItemId, ) -> DispatchResult { let mut details = - Item::::get(collection, item).ok_or(Error::::UnknownCollection)?; + Item::::get(&collection, item).ok_or(Error::::UnknownCollection)?; if let Some(check_origin) = maybe_check_origin { + // Cannot revoke approvals for individual items when there are existing approvals to + // transfer all items in the collection owned by the origin. + ensure!( + CollectionApprovals::::iter_prefix((&collection, &check_origin)) + .take(1) + .next() + .is_none(), + Error::::DelegateApprovalConflict + ); ensure!(check_origin == details.owner, Error::::NoPermission); } details.approvals.clear(); - Item::::insert(collection, item, &details); + Item::::insert(&collection, item, &details); Self::deposit_event(Event::AllApprovalsCancelled { collection, - item, + item: Some(item), owner: details.owner, }); Ok(()) } + + /// Approves the transfer of all collection items of `owner` to a delegate. + /// + /// This function is used to approve the transfer of all (current and future) collection items + /// of `owner` to a `delegate`. The `delegate` account will be allowed to take control of the + /// items. Optionally, a `deadline` can be specified to set a time limit for the approval. The + /// `deadline` is expressed in block numbers and is added to the current block number to + /// determine the absolute deadline for the approval. After approving the transfer, the + /// function emits the `TransferApproved` event. + /// + /// This function reserves the necessary deposit from the owner account. If an approval already + /// exists, additional funds are reserved only if the updated deposit exceeds the currently + /// reserved amount. The existing approval's deadline is also updated. + /// + /// - `owner`: The owner of the collection items. + /// - `collection`: The identifier of the collection. + /// - `delegate`: The account that will be allowed to take control of the collection items. + /// - `maybe_deadline`: The optional deadline (in block numbers) specifying the time limit for + /// the approval. + pub(crate) fn do_approve_collection_transfer( + owner: T::AccountId, + collection: T::CollectionId, + delegate: T::AccountId, + maybe_deadline: Option>, + ) -> DispatchResult { + ensure!( + Self::is_pallet_feature_enabled(PalletFeature::Approvals), + Error::::MethodDisabled + ); + ensure!(AccountBalance::::get(&collection, &owner) > 0, Error::::NoItemOwned); + + let collection_config = Self::get_collection_config(&collection)?; + ensure!( + collection_config.is_setting_enabled(CollectionSetting::TransferableItems), + Error::::ItemsNonTransferable + ); + let now = frame_system::Pallet::::block_number(); + let deadline = maybe_deadline.map(|d| d.saturating_add(now)); + + CollectionApprovals::::try_mutate_exists( + (&collection, &owner, &delegate), + |maybe_approval| -> DispatchResult { + let deposit_required = T::CollectionApprovalDeposit::get(); + let current_deposit = + maybe_approval.take().map(|(_, deposit)| deposit).unwrap_or_default(); + if current_deposit < deposit_required { + T::Currency::reserve(&owner, deposit_required - current_deposit)?; + } + *maybe_approval = Some((deadline, deposit_required)); + Ok(()) + }, + )?; + + Self::deposit_event(Event::TransferApproved { + collection, + item: None, + owner, + delegate, + deadline, + }); + + Ok(()) + } + + /// Cancels the transfer of all `collection` items of `owner` to a `delegate`. + /// + /// This function is used to cancel the approval for the transfer of the collection items of + /// `owner` to a `delegate`. After canceling the approval, the deposit is returned to the + /// `owner` account and the `ApprovalCancelled` event is emitted. + /// + /// - `owner`: The owner of the collection items. + /// - `collection`: The identifier of the collection. + /// - `delegate`: The account that had permission to transfer collection items. + pub(crate) fn do_cancel_collection_approval( + owner: T::AccountId, + collection: T::CollectionId, + delegate: T::AccountId, + ) -> DispatchResult { + let (_, deposit) = CollectionApprovals::::take((&collection, &owner, &delegate)) + .ok_or(Error::::Unapproved)?; + + T::Currency::unreserve(&owner, deposit); + + Self::deposit_event(Event::ApprovalCancelled { collection, owner, item: None, delegate }); + + Ok(()) + } + + /// Clears all collection approvals. + /// + /// This function is used to clear `limit` collection approvals for the + /// collection items of `owner`. After clearing all approvals, the deposit of each collection + /// approval is returned to the `owner` account and the `ApprovalsCancelled` event is + /// emitted. + /// + /// - `owner`: The owner of the collection items. + /// - `collection`: The identifier of the collection. + /// - `limit`: The amount of collection approvals that will be cleared. + pub(crate) fn do_clear_collection_approvals( + owner: T::AccountId, + collection: T::CollectionId, + limit: u32, + ) -> Result { + if limit == 0 { + return Ok(0); + } + let mut removed_approvals: u32 = 0; + let mut deposits: BalanceOf = Zero::zero(); + // Iterate and remove each collection approval, returning the deposit back to the `owner`. + for (_, (_, deposit)) in CollectionApprovals::::drain_prefix((&collection, &owner)) { + deposits = deposits.saturating_add(deposit); + removed_approvals.saturating_inc(); + if removed_approvals == limit { + break; + } + } + + if removed_approvals > 0 { + T::Currency::unreserve(&owner, deposits); + Self::deposit_event(Event::ApprovalsCancelled { collection, item: None, owner }); + } + Ok(removed_approvals) + } + + /// Checks whether the `delegate` is approved to transfer collection items of `owner`. + /// + /// - `collection`: The identifier of the collection. + /// - `owner`: The owner of the collection items. + /// - `delegate`: The account to check for approval to transfer collection items of `owner`. + fn check_collection_approval( + collection: &T::CollectionId, + owner: &T::AccountId, + delegate: &T::AccountId, + ) -> DispatchResult { + let (maybe_deadline, _) = + CollectionApprovals::::get((&collection, &owner, &delegate)) + .ok_or(Error::::NoPermission)?; + if let Some(deadline) = maybe_deadline { + let block_number = frame_system::Pallet::::block_number(); + ensure!(block_number <= deadline, Error::::ApprovalExpired); + } + Ok(()) + } + + /// Checks whether the `delegate` is approved by `owner` to transfer its collection item(s). If + /// the `delegate` is approved for all `owner`'s collection items it can transfer every item + /// without requiring explicit approval for an item. + /// + /// - `collection`: The identifier of the collection. + /// - `maybe_item`: The optional item of the collection that the delegated account has an + /// approval to transfer. If not provided, an approval to transfer all `owner`'s collection + /// items will be checked. + /// - `owner`: The owner of the specified collection item. + /// - `delegate`: The account to check for approval to transfer collection item(s) of owner. + pub fn check_approval( + collection: &T::CollectionId, + maybe_item: &Option, + owner: &T::AccountId, + delegate: &T::AccountId, + ) -> DispatchResult { + // Check if `delegate` has permission to transfer `owner`'s collection items. + let Err(error) = Self::check_collection_approval(collection, owner, delegate) else { + return Ok(()); + }; + + // Check if a `delegate` has permission to transfer the given collection item. + if let Some(item) = maybe_item { + let details = Item::::get(&collection, item).ok_or(Error::::UnknownItem)?; + let maybe_deadline = + details.approvals.get(delegate).ok_or(Error::::NoPermission)?; + if let Some(deadline) = maybe_deadline { + let block_number = frame_system::Pallet::::block_number(); + ensure!(block_number <= *deadline, Error::::ApprovalExpired); + } + return Ok(()); + }; + Err(error) + } } diff --git a/pallets/nfts/src/features/atomic_swap.rs b/pallets/nfts/src/features/atomic_swap.rs index 6c15f15ae..ac6a928e7 100644 --- a/pallets/nfts/src/features/atomic_swap.rs +++ b/pallets/nfts/src/features/atomic_swap.rs @@ -62,17 +62,17 @@ impl, I: 'static> Pallet { ); ensure!(duration <= T::MaxDeadlineDuration::get(), Error::::WrongDuration); - let item = Item::::get(offered_collection_id, offered_item_id) + let item = Item::::get(offered_collection_id.clone(), offered_item_id) .ok_or(Error::::UnknownItem)?; ensure!(item.owner == caller, Error::::NoPermission); match maybe_desired_item_id { Some(desired_item_id) => ensure!( - Item::::contains_key(desired_collection_id, desired_item_id), + Item::::contains_key(desired_collection_id.clone(), desired_item_id), Error::::UnknownItem ), None => ensure!( - Collection::::contains_key(desired_collection_id), + Collection::::contains_key(desired_collection_id.clone()), Error::::UnknownCollection ), }; @@ -81,10 +81,10 @@ impl, I: 'static> Pallet { let deadline = duration.saturating_add(now); PendingSwapOf::::insert( - offered_collection_id, + offered_collection_id.clone(), offered_item_id, PendingSwap { - desired_collection: desired_collection_id, + desired_collection: desired_collection_id.clone(), desired_item: maybe_desired_item_id, price: maybe_price.clone(), deadline, @@ -118,17 +118,17 @@ impl, I: 'static> Pallet { offered_collection_id: T::CollectionId, offered_item_id: T::ItemId, ) -> DispatchResult { - let swap = PendingSwapOf::::get(offered_collection_id, offered_item_id) + let swap = PendingSwapOf::::get(offered_collection_id.clone(), offered_item_id) .ok_or(Error::::UnknownSwap)?; let now = frame_system::Pallet::::block_number(); if swap.deadline > now { - let item = Item::::get(offered_collection_id, offered_item_id) + let item = Item::::get(offered_collection_id.clone(), offered_item_id) .ok_or(Error::::UnknownItem)?; ensure!(item.owner == caller, Error::::NoPermission); } - PendingSwapOf::::remove(offered_collection_id, offered_item_id); + PendingSwapOf::::remove(offered_collection_id.clone(), offered_item_id); Self::deposit_event(Event::SwapCancelled { offered_collection: offered_collection_id, @@ -172,11 +172,11 @@ impl, I: 'static> Pallet { Error::::MethodDisabled ); - let send_item = Item::::get(send_collection_id, send_item_id) + let send_item = Item::::get(send_collection_id.clone(), send_item_id) .ok_or(Error::::UnknownItem)?; - let receive_item = Item::::get(receive_collection_id, receive_item_id) + let receive_item = Item::::get(receive_collection_id.clone(), receive_item_id) .ok_or(Error::::UnknownItem)?; - let swap = PendingSwapOf::::get(receive_collection_id, receive_item_id) + let swap = PendingSwapOf::::get(receive_collection_id.clone(), receive_item_id) .ok_or(Error::::UnknownSwap)?; ensure!(send_item.owner == caller, Error::::NoPermission); @@ -210,11 +210,14 @@ impl, I: 'static> Pallet { } // This also removes the swap. - Self::do_transfer(send_collection_id, send_item_id, receive_item.owner.clone(), |_, _| { - Ok(()) - })?; Self::do_transfer( - receive_collection_id, + send_collection_id.clone(), + send_item_id, + receive_item.owner.clone(), + |_, _| Ok(()), + )?; + Self::do_transfer( + receive_collection_id.clone(), receive_item_id, send_item.owner.clone(), |_, _| Ok(()), diff --git a/pallets/nfts/src/features/attributes.rs b/pallets/nfts/src/features/attributes.rs index b7ba769ae..d87aaea73 100644 --- a/pallets/nfts/src/features/attributes.rs +++ b/pallets/nfts/src/features/attributes.rs @@ -86,9 +86,9 @@ impl, I: 'static> Pallet { } let mut collection_details = - Collection::::get(collection).ok_or(Error::::UnknownCollection)?; + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - let attribute = Attribute::::get((collection, maybe_item, &namespace, &key)); + let attribute = Attribute::::get((&collection, maybe_item, &namespace, &key)); let attribute_exists = attribute.is_some(); if !attribute_exists { collection_details.attributes.saturating_inc(); @@ -151,7 +151,7 @@ impl, I: 'static> Pallet { (&value, AttributeDeposit { account: new_deposit_owner, amount: deposit }), ); - Collection::::insert(collection, &collection_details); + Collection::::insert(&collection, &collection_details); Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value, namespace }); Ok(()) } @@ -182,9 +182,9 @@ impl, I: 'static> Pallet { value: BoundedVec, ) -> DispatchResult { let mut collection_details = - Collection::::get(collection).ok_or(Error::::UnknownCollection)?; + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - let attribute = Attribute::::get((collection, maybe_item, &namespace, &key)); + let attribute = Attribute::::get((&collection, maybe_item, &namespace, &key)); if let Some((_, deposit)) = attribute { if deposit.account != set_as && deposit.amount != Zero::zero() { if let Some(deposit_account) = deposit.account { @@ -199,7 +199,7 @@ impl, I: 'static> Pallet { (&collection, maybe_item, &namespace, &key), (&value, AttributeDeposit { account: set_as, amount: Zero::zero() }), ); - Collection::::insert(collection, &collection_details); + Collection::::insert(&collection, &collection_details); Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value, namespace }); Ok(()) } @@ -228,7 +228,8 @@ impl, I: 'static> Pallet { let now = frame_system::Pallet::::block_number(); ensure!(deadline >= now, Error::::DeadlineExpired); - let item_details = Item::::get(collection, item).ok_or(Error::::UnknownItem)?; + let item_details = + Item::::get(&collection, item).ok_or(Error::::UnknownItem)?; ensure!(item_details.owner == origin, Error::::NoPermission); // Only the CollectionOwner and Account() namespaces could be updated in this way. @@ -237,11 +238,11 @@ impl, I: 'static> Pallet { AttributeNamespace::CollectionOwner => {}, AttributeNamespace::Account(account) => { ensure!(account == &signer, Error::::NoPermission); - let approvals = ItemAttributesApprovalsOf::::get(collection, item); + let approvals = ItemAttributesApprovalsOf::::get(&collection, item); if !approvals.contains(account) { Self::do_approve_item_attributes( origin.clone(), - collection, + collection.clone(), item, account.clone(), )?; @@ -253,7 +254,7 @@ impl, I: 'static> Pallet { for (key, value) in attributes { Self::do_set_attribute( signer.clone(), - collection, + collection.clone(), Some(item), namespace.clone(), Self::construct_attribute_key(key)?, @@ -288,7 +289,7 @@ impl, I: 'static> Pallet { namespace: AttributeNamespace, key: BoundedVec, ) -> DispatchResult { - let (_, deposit) = Attribute::::take((collection, maybe_item, &namespace, &key)) + let (_, deposit) = Attribute::::take((&collection, maybe_item, &namespace, &key)) .ok_or(Error::::AttributeNotFound)?; if let Some(check_origin) = &maybe_check_origin { @@ -335,7 +336,7 @@ impl, I: 'static> Pallet { } let mut collection_details = - Collection::::get(collection).ok_or(Error::::UnknownCollection)?; + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; collection_details.attributes.saturating_dec(); @@ -350,7 +351,7 @@ impl, I: 'static> Pallet { _ => (), } - Collection::::insert(collection, &collection_details); + Collection::::insert(&collection, &collection_details); Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key, namespace }); Ok(()) @@ -378,10 +379,10 @@ impl, I: 'static> Pallet { Error::::MethodDisabled ); - let details = Item::::get(collection, item).ok_or(Error::::UnknownItem)?; + let details = Item::::get(&collection, item).ok_or(Error::::UnknownItem)?; ensure!(check_origin == details.owner, Error::::NoPermission); - ItemAttributesApprovalsOf::::try_mutate(collection, item, |approvals| { + ItemAttributesApprovalsOf::::try_mutate(collection.clone(), item, |approvals| { approvals .try_insert(delegate.clone()) .map_err(|_| Error::::ReachedApprovalLimit)?; @@ -419,10 +420,10 @@ impl, I: 'static> Pallet { Error::::MethodDisabled ); - let details = Item::::get(collection, item).ok_or(Error::::UnknownItem)?; + let details = Item::::get(&collection, item).ok_or(Error::::UnknownItem)?; ensure!(check_origin == details.owner, Error::::NoPermission); - ItemAttributesApprovalsOf::::try_mutate(collection, item, |approvals| { + ItemAttributesApprovalsOf::::try_mutate(collection.clone(), item, |approvals| { approvals.remove(&delegate); let mut attributes: u32 = 0; diff --git a/pallets/nfts/src/features/buy_sell.rs b/pallets/nfts/src/features/buy_sell.rs index 476053ee0..630d47a73 100644 --- a/pallets/nfts/src/features/buy_sell.rs +++ b/pallets/nfts/src/features/buy_sell.rs @@ -82,7 +82,7 @@ impl, I: 'static> Pallet { Error::::MethodDisabled ); - let details = Item::::get(collection, item).ok_or(Error::::UnknownItem)?; + let details = Item::::get(&collection, item).ok_or(Error::::UnknownItem)?; ensure!(details.owner == sender, Error::::NoPermission); let collection_config = Self::get_collection_config(&collection)?; @@ -98,7 +98,7 @@ impl, I: 'static> Pallet { ); if let Some(ref price) = price { - ItemPriceOf::::insert(collection, item, (price, whitelisted_buyer.clone())); + ItemPriceOf::::insert(&collection, item, (price, whitelisted_buyer.clone())); Self::deposit_event(Event::ItemPriceSet { collection, item, @@ -106,7 +106,7 @@ impl, I: 'static> Pallet { whitelisted_buyer, }); } else { - ItemPriceOf::::remove(collection, item); + ItemPriceOf::::remove(&collection, item); Self::deposit_event(Event::ItemPriceRemoved { collection, item }); } @@ -137,11 +137,11 @@ impl, I: 'static> Pallet { Error::::MethodDisabled ); - let details = Item::::get(collection, item).ok_or(Error::::UnknownItem)?; + let details = Item::::get(&collection, item).ok_or(Error::::UnknownItem)?; ensure!(details.owner != buyer, Error::::NoPermission); let price_info = - ItemPriceOf::::get(collection, item).ok_or(Error::::NotForSale)?; + ItemPriceOf::::get(&collection, item).ok_or(Error::::NotForSale)?; ensure!(bid_price >= price_info.0, Error::::BidTooLow); @@ -158,7 +158,7 @@ impl, I: 'static> Pallet { let old_owner = details.owner.clone(); - Self::do_transfer(collection, item, buyer.clone(), |_, _| Ok(()))?; + Self::do_transfer(collection.clone(), item, buyer.clone(), |_, _| Ok(()))?; Self::deposit_event(Event::ItemBought { collection, diff --git a/pallets/nfts/src/features/create_delete_collection.rs b/pallets/nfts/src/features/create_delete_collection.rs index 60286d701..e189a9659 100644 --- a/pallets/nfts/src/features/create_delete_collection.rs +++ b/pallets/nfts/src/features/create_delete_collection.rs @@ -42,12 +42,12 @@ impl, I: 'static> Pallet { deposit: DepositBalanceOf, event: Event, ) -> DispatchResult { - ensure!(!Collection::::contains_key(collection), Error::::CollectionIdInUse); + ensure!(!Collection::::contains_key(&collection), Error::::CollectionIdInUse); T::Currency::reserve(&owner, deposit)?; Collection::::insert( - collection, + &collection, CollectionDetails { owner: owner.clone(), owner_deposit: deposit, @@ -58,15 +58,15 @@ impl, I: 'static> Pallet { }, ); CollectionRoleOf::::insert( - collection, + &collection, admin, CollectionRoles( CollectionRole::Admin | CollectionRole::Freezer | CollectionRole::Issuer, ), ); - CollectionConfigOf::::insert(collection, config); - CollectionAccount::::insert(&owner, collection, ()); + CollectionConfigOf::::insert(&collection, config.clone()); + CollectionAccount::::insert(&owner, &collection, ()); Self::deposit_event(event); @@ -96,6 +96,8 @@ impl, I: 'static> Pallet { /// ([`NoPermission`](crate::Error::NoPermission)). /// - If the collection is not empty (contains items) /// ([`CollectionNotEmpty`](crate::Error::CollectionNotEmpty)). + /// - If there are collection approvals + /// ([`CollectionApprovalsExist`](crate::Error::CollectionApprovalsExist)). /// - If the `witness` does not match the actual collection details /// ([`BadWitness`](crate::Error::BadWitness)). pub fn do_destroy_collection( @@ -103,13 +105,20 @@ impl, I: 'static> Pallet { witness: DestroyWitness, maybe_check_owner: Option, ) -> Result { - Collection::::try_mutate_exists(collection, |maybe_details| { + Collection::::try_mutate_exists(collection.clone(), |maybe_details| { let collection_details = maybe_details.take().ok_or(Error::::UnknownCollection)?; if let Some(check_owner) = maybe_check_owner { ensure!(collection_details.owner == check_owner, Error::::NoPermission); } ensure!(collection_details.items == 0, Error::::CollectionNotEmpty); + ensure!( + CollectionApprovals::::iter_prefix((&collection,)) + .take(1) + .next() + .is_none(), + Error::::CollectionApprovalsExist + ); ensure!(collection_details.attributes == witness.attributes, Error::::BadWitness); ensure!( collection_details.item_metadatas == witness.item_metadatas, @@ -120,13 +129,13 @@ impl, I: 'static> Pallet { Error::::BadWitness ); - for (_, metadata) in ItemMetadataOf::::drain_prefix(collection) { + for (_, metadata) in ItemMetadataOf::::drain_prefix(&collection) { if let Some(depositor) = metadata.deposit.account { T::Currency::unreserve(&depositor, metadata.deposit.amount); } } - CollectionMetadataOf::::remove(collection); + CollectionMetadataOf::::remove(&collection); Self::clear_roles(&collection)?; for (_, (_, deposit)) in Attribute::::drain_prefix((&collection,)) { @@ -137,10 +146,10 @@ impl, I: 'static> Pallet { } } - CollectionAccount::::remove(&collection_details.owner, collection); + CollectionAccount::::remove(&collection_details.owner, &collection); T::Currency::unreserve(&collection_details.owner, collection_details.owner_deposit); - CollectionConfigOf::::remove(collection); - let _ = ItemConfigOf::::clear_prefix(collection, witness.item_configs, None); + CollectionConfigOf::::remove(&collection); + let _ = ItemConfigOf::::clear_prefix(&collection, witness.item_configs, None); Self::deposit_event(Event::Destroyed { collection }); diff --git a/pallets/nfts/src/features/create_delete_item.rs b/pallets/nfts/src/features/create_delete_item.rs index 7a69de9ea..e74ccfc91 100644 --- a/pallets/nfts/src/features/create_delete_item.rs +++ b/pallets/nfts/src/features/create_delete_item.rs @@ -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::*, traits::ExistenceRequirement}; +use frame_support::{pallet_prelude::*, sp_runtime::ArithmeticError, traits::ExistenceRequirement}; use crate::*; @@ -53,53 +53,61 @@ impl, I: 'static> Pallet { &CollectionConfigFor, ) -> DispatchResult, ) -> DispatchResult { - ensure!(!Item::::contains_key(collection, item), Error::::AlreadyExists); + ensure!(!Item::::contains_key(&collection, item), Error::::AlreadyExists); - Collection::::try_mutate(collection, |maybe_collection_details| -> DispatchResult { - let collection_details = - maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; + Collection::::try_mutate( + collection.clone(), + |maybe_collection_details| -> DispatchResult { + let collection_details = + maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; - let collection_config = Self::get_collection_config(&collection)?; - with_details_and_config(collection_details, &collection_config)?; + let collection_config = Self::get_collection_config(&collection)?; + with_details_and_config(collection_details, &collection_config)?; - if let Some(max_supply) = collection_config.max_supply { - ensure!(collection_details.items < max_supply, Error::::MaxSupplyReached); - } + if let Some(max_supply) = collection_config.max_supply { + ensure!(collection_details.items < max_supply, Error::::MaxSupplyReached); + } + + collection_details.items.saturating_inc(); - collection_details.items.saturating_inc(); + AccountBalance::::mutate(collection.clone(), &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) { + 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 item_owner = mint_to.clone(); - Account::::insert((&item_owner, &collection, &item), ()); - - if let Ok(existing_config) = ItemConfigOf::::try_get(collection, item) { - ensure!(existing_config == item_config, Error::::InconsistentItemConfig); - } else { - ItemConfigOf::::insert(collection, item, item_config); - collection_details.item_configs.saturating_inc(); - } + let deposit_account = match maybe_depositor { + None => collection_details.owner.clone(), + Some(depositor) => depositor, + }; - T::Currency::reserve(&deposit_account, deposit_amount)?; + let item_owner = mint_to.clone(); + Account::::insert((&item_owner, &collection, &item), ()); - let deposit = ItemDeposit { account: deposit_account, amount: deposit_amount }; - let details = ItemDetails { - owner: item_owner, - approvals: ApprovalsOf::::default(), - deposit, - }; - Item::::insert(collection, item, details); - Ok(()) - })?; + if let Ok(existing_config) = ItemConfigOf::::try_get(&collection, item) { + ensure!(existing_config == item_config, Error::::InconsistentItemConfig); + } else { + ItemConfigOf::::insert(&collection, item, item_config); + collection_details.item_configs.saturating_inc(); + } + + T::Currency::reserve(&deposit_account, deposit_amount)?; + + let deposit = ItemDeposit { account: deposit_account, amount: deposit_amount }; + let details = ItemDetails { + owner: item_owner, + approvals: ApprovalsOf::::default(), + deposit, + }; + Item::::insert(&collection, item, details); + Ok(()) + }, + )?; Self::deposit_event(Event::Issued { collection, item, owner: mint_to }); Ok(()) @@ -152,7 +160,7 @@ impl, I: 'static> Pallet { let item_config = ItemConfig { settings: Self::get_default_item_settings(&collection)? }; Self::do_mint( - collection, + collection.clone(), item, Some(mint_to.clone()), mint_to.clone(), @@ -174,7 +182,7 @@ impl, I: 'static> Pallet { for (key, value) in attributes { Self::do_set_attribute( admin_account.clone(), - collection, + collection.clone(), Some(item), AttributeNamespace::CollectionOwner, Self::construct_attribute_key(key)?, @@ -207,7 +215,7 @@ impl, I: 'static> Pallet { item: T::ItemId, with_details: impl FnOnce(&ItemDetailsFor) -> DispatchResult, ) -> DispatchResult { - ensure!(!T::Locker::is_locked(collection, item), Error::::ItemLocked); + ensure!(!T::Locker::is_locked(collection.clone(), item), Error::::ItemLocked); ensure!( !Self::has_system_attribute(&collection, &item, PalletAttributes::TransferDisabled)?, Error::::ItemLocked @@ -217,12 +225,12 @@ impl, I: 'static> Pallet { // then we keep the config record and don't remove it let remove_config = !item_config.has_disabled_settings(); let owner = Collection::::try_mutate( - collection, + collection.clone(), |maybe_collection_details| -> Result { let collection_details = maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; let details = - Item::::get(collection, item).ok_or(Error::::UnknownCollection)?; + Item::::get(&collection, item).ok_or(Error::::UnknownCollection)?; with_details(&details)?; // Return the deposit. @@ -235,7 +243,7 @@ impl, I: 'static> Pallet { // Clear the metadata if it's not locked. if item_config.is_setting_enabled(ItemSetting::UnlockedMetadata) { - if let Some(metadata) = ItemMetadataOf::::take(collection, item) { + if let Some(metadata) = ItemMetadataOf::::take(&collection, item) { let depositor_account = metadata.deposit.account.unwrap_or(collection_details.owner.clone()); @@ -254,14 +262,21 @@ impl, I: 'static> Pallet { }, )?; - Item::::remove(collection, item); + Item::::remove(&collection, item); Account::::remove((&owner, &collection, &item)); - ItemPriceOf::::remove(collection, item); - PendingSwapOf::::remove(collection, item); - ItemAttributesApprovalsOf::::remove(collection, item); + ItemPriceOf::::remove(&collection, item); + PendingSwapOf::::remove(&collection, item); + ItemAttributesApprovalsOf::::remove(&collection, item); + + let balance = AccountBalance::::take(&collection, &owner) + .checked_sub(1) + .ok_or(ArithmeticError::Underflow)?; + if balance > 0 { + AccountBalance::::insert(&collection, &owner, balance); + } if remove_config { - ItemConfigOf::::remove(collection, item); + ItemConfigOf::::remove(&collection, item); } Self::deposit_event(Event::Burned { collection, item, owner }); diff --git a/pallets/nfts/src/features/lock.rs b/pallets/nfts/src/features/lock.rs index a013d0149..cc744fc0d 100644 --- a/pallets/nfts/src/features/lock.rs +++ b/pallets/nfts/src/features/lock.rs @@ -39,12 +39,15 @@ impl, I: 'static> Pallet { collection: T::CollectionId, lock_settings: CollectionSettings, ) -> DispatchResult { - ensure!(Self::collection_owner(collection) == Some(origin), Error::::NoPermission); + ensure!( + Self::collection_owner(collection.clone()) == Some(origin), + Error::::NoPermission + ); ensure!( !lock_settings.is_disabled(CollectionSetting::DepositRequired), Error::::WrongSetting ); - CollectionConfigOf::::try_mutate(collection, |maybe_config| { + CollectionConfigOf::::try_mutate(collection.clone(), |maybe_config| { let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; for setting in lock_settings.get_disabled() { @@ -80,7 +83,7 @@ impl, I: 'static> Pallet { if !config.has_disabled_setting(ItemSetting::Transferable) { config.disable_setting(ItemSetting::Transferable); } - ItemConfigOf::::insert(collection, item, config); + ItemConfigOf::::insert(&collection, item, config); Self::deposit_event(Event::::ItemTransferLocked { collection, item }); Ok(()) @@ -110,7 +113,7 @@ impl, I: 'static> Pallet { if config.has_disabled_setting(ItemSetting::Transferable) { config.enable_setting(ItemSetting::Transferable); } - ItemConfigOf::::insert(collection, item, config); + ItemConfigOf::::insert(&collection, item, config); Self::deposit_event(Event::::ItemTransferUnlocked { collection, item }); Ok(()) @@ -145,7 +148,7 @@ impl, I: 'static> Pallet { ); } - ItemConfigOf::::try_mutate(collection, item, |maybe_config| { + ItemConfigOf::::try_mutate(collection.clone(), item, |maybe_config| { let config = maybe_config.as_mut().ok_or(Error::::UnknownItem)?; if lock_metadata { diff --git a/pallets/nfts/src/features/metadata.rs b/pallets/nfts/src/features/metadata.rs index 8c630832d..1c2796489 100644 --- a/pallets/nfts/src/features/metadata.rs +++ b/pallets/nfts/src/features/metadata.rs @@ -58,7 +58,7 @@ impl, I: 'static> Pallet { let is_root = maybe_check_origin.is_none(); let mut collection_details = - Collection::::get(collection).ok_or(Error::::UnknownCollection)?; + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; let item_config = Self::get_item_config(&collection, &item)?; ensure!( @@ -68,7 +68,7 @@ impl, I: 'static> Pallet { let collection_config = Self::get_collection_config(&collection)?; - ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { + ItemMetadataOf::::try_mutate_exists(collection.clone(), item, |metadata| { if metadata.is_none() { collection_details.item_metadatas.saturating_inc(); } @@ -107,7 +107,7 @@ impl, I: 'static> Pallet { data: data.clone(), }); - Collection::::insert(collection, &collection_details); + Collection::::insert(&collection, &collection_details); Self::deposit_event(Event::ItemMetadataSet { collection, item, data }); Ok(()) }) @@ -139,10 +139,10 @@ impl, I: 'static> Pallet { } let is_root = maybe_check_origin.is_none(); - let metadata = ItemMetadataOf::::take(collection, item) + let metadata = ItemMetadataOf::::take(&collection, item) .ok_or(Error::::MetadataNotFound)?; let mut collection_details = - Collection::::get(collection).ok_or(Error::::UnknownCollection)?; + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; let depositor_account = metadata.deposit.account.unwrap_or(collection_details.owner.clone()); @@ -160,7 +160,7 @@ impl, I: 'static> Pallet { collection_details.owner_deposit.saturating_reduce(metadata.deposit.amount); } - Collection::::insert(collection, &collection_details); + Collection::::insert(&collection, &collection_details); Self::deposit_event(Event::ItemMetadataCleared { collection, item }); Ok(()) @@ -199,9 +199,9 @@ impl, I: 'static> Pallet { ); let mut details = - Collection::::get(collection).ok_or(Error::::UnknownCollection)?; + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { + CollectionMetadataOf::::try_mutate_exists(collection.clone(), |metadata| { let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); details.owner_deposit.saturating_reduce(old_deposit); let mut deposit = Zero::zero(); @@ -222,7 +222,7 @@ impl, I: 'static> Pallet { } details.owner_deposit.saturating_accrue(deposit); - Collection::::insert(collection, details); + Collection::::insert(&collection, details); *metadata = Some(CollectionMetadata { deposit, data: data.clone() }); @@ -256,7 +256,7 @@ impl, I: 'static> Pallet { } let mut details = - Collection::::get(collection).ok_or(Error::::UnknownCollection)?; + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; let collection_config = Self::get_collection_config(&collection)?; ensure!( @@ -265,11 +265,11 @@ impl, I: 'static> Pallet { Error::::LockedCollectionMetadata ); - CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { + CollectionMetadataOf::::try_mutate_exists(collection.clone(), |metadata| { let deposit = metadata.take().ok_or(Error::::UnknownCollection)?.deposit; T::Currency::unreserve(&details.owner, deposit); details.owner_deposit.saturating_reduce(deposit); - Collection::::insert(collection, details); + Collection::::insert(&collection, details); Self::deposit_event(Event::CollectionMetadataCleared { collection }); Ok(()) }) diff --git a/pallets/nfts/src/features/roles.rs b/pallets/nfts/src/features/roles.rs index 8ddec3380..c7f3fa03b 100644 --- a/pallets/nfts/src/features/roles.rs +++ b/pallets/nfts/src/features/roles.rs @@ -44,7 +44,7 @@ impl, I: 'static> Pallet { admin: Option, freezer: Option, ) -> DispatchResult { - Collection::::try_mutate(collection, |maybe_details| { + Collection::::try_mutate(collection.clone(), |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; let is_root = maybe_check_owner.is_none(); if let Some(check_origin) = maybe_check_owner { @@ -81,7 +81,7 @@ impl, I: 'static> Pallet { // Insert new records. for (account, roles) in account_to_role { - CollectionRoleOf::::insert(collection, &account, roles); + CollectionRoleOf::::insert(&collection, &account, roles); } Self::deposit_event(Event::TeamChanged { collection, issuer, admin, freezer }); diff --git a/pallets/nfts/src/features/settings.rs b/pallets/nfts/src/features/settings.rs index 22495bc95..d653b4465 100644 --- a/pallets/nfts/src/features/settings.rs +++ b/pallets/nfts/src/features/settings.rs @@ -33,8 +33,8 @@ impl, I: 'static> Pallet { collection: T::CollectionId, config: CollectionConfigFor, ) -> DispatchResult { - ensure!(Collection::::contains_key(collection), Error::::UnknownCollection); - CollectionConfigOf::::insert(collection, config); + ensure!(Collection::::contains_key(&collection), Error::::UnknownCollection); + CollectionConfigOf::::insert(&collection, config); Self::deposit_event(Event::CollectionConfigChanged { collection }); Ok(()) } @@ -67,14 +67,14 @@ impl, I: 'static> Pallet { ); let details = - Collection::::get(collection).ok_or(Error::::UnknownCollection)?; + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; if let Some(check_owner) = &maybe_check_owner { ensure!(check_owner == &details.owner, Error::::NoPermission); } ensure!(details.items <= max_supply, Error::::MaxSupplyTooSmall); - CollectionConfigOf::::try_mutate(collection, |maybe_config| { + CollectionConfigOf::::try_mutate(collection.clone(), |maybe_config| { let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; config.max_supply = Some(max_supply); Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply }); @@ -110,7 +110,7 @@ impl, I: 'static> Pallet { ); } - CollectionConfigOf::::try_mutate(collection, |maybe_config| { + CollectionConfigOf::::try_mutate(collection.clone(), |maybe_config| { let config = maybe_config.as_mut().ok_or(Error::::NoConfig)?; config.mint_settings = mint_settings; Self::deposit_event(Event::CollectionMintSettingsUpdated { collection }); diff --git a/pallets/nfts/src/features/transfer.rs b/pallets/nfts/src/features/transfer.rs index e0ae770ab..0671e1f6c 100644 --- a/pallets/nfts/src/features/transfer.rs +++ b/pallets/nfts/src/features/transfer.rs @@ -18,7 +18,7 @@ //! This module contains helper methods to perform the transfer functionalities //! of the NFTs pallet. -use frame_support::pallet_prelude::*; +use frame_support::{pallet_prelude::*, sp_runtime::ArithmeticError}; use crate::*; @@ -55,10 +55,10 @@ impl, I: 'static> Pallet { ) -> DispatchResult { // Retrieve collection details. let collection_details = - Collection::::get(collection).ok_or(Error::::UnknownCollection)?; + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; // Ensure the item is not locked. - ensure!(!T::Locker::is_locked(collection, item), Error::::ItemLocked); + ensure!(!T::Locker::is_locked(collection.clone(), item), Error::::ItemLocked); // Ensure the item is not transfer disabled on the system level attribute. ensure!( @@ -81,11 +81,23 @@ impl, I: 'static> Pallet { ); // Retrieve the item details. - let mut details = Item::::get(collection, item).ok_or(Error::::UnknownItem)?; + let mut details = Item::::get(&collection, item).ok_or(Error::::UnknownItem)?; // Perform the transfer with custom details using the provided closure. with_details(&collection_details, &mut details)?; + // Update account balance of the destination account. + AccountBalance::::mutate(&collection, &dest, |balance| { + balance.saturating_inc(); + }); + // Update account balance of the owner. + let balance = AccountBalance::::take(&collection, &details.owner) + .checked_sub(1) + .ok_or(ArithmeticError::Underflow)?; + if balance > 0 { + AccountBalance::::insert(&collection, &details.owner, balance); + } + // Update account ownership information. Account::::remove((&details.owner, &collection, &item)); Account::::insert((&dest, &collection, &item), ()); @@ -98,9 +110,9 @@ impl, I: 'static> Pallet { details.approvals.clear(); // Update item details. - Item::::insert(collection, item, &details); - ItemPriceOf::::remove(collection, item); - PendingSwapOf::::remove(collection, item); + Item::::insert(&collection, item, &details); + ItemPriceOf::::remove(&collection, item); + PendingSwapOf::::remove(&collection, item); // Emit `Transferred` event. Self::deposit_event(Event::Transferred { @@ -131,12 +143,12 @@ impl, I: 'static> Pallet { ensure!(acceptable_collection.as_ref() == Some(&collection), Error::::Unaccepted); // Try to retrieve and mutate the collection details. - Collection::::try_mutate(collection, |maybe_details| { + Collection::::try_mutate(collection.clone(), |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; // Check if the `origin` is the current owner of the collection. ensure!(origin == details.owner, Error::::NoPermission); if details.owner == new_owner { - return Ok(()) + return Ok(()); } // Move the deposit to the new owner. @@ -148,8 +160,8 @@ impl, I: 'static> Pallet { )?; // Update account ownership information. - CollectionAccount::::remove(&details.owner, collection); - CollectionAccount::::insert(&new_owner, collection, ()); + CollectionAccount::::remove(&details.owner, &collection); + CollectionAccount::::insert(&new_owner, &collection, ()); details.owner = new_owner.clone(); OwnershipAcceptance::::remove(&new_owner); @@ -208,10 +220,10 @@ impl, I: 'static> Pallet { owner: T::AccountId, ) -> DispatchResult { // Try to retrieve and mutate the collection details. - Collection::::try_mutate(collection, |maybe_details| { + Collection::::try_mutate(collection.clone(), |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; if details.owner == owner { - return Ok(()) + return Ok(()); } // Move the deposit to the new owner. @@ -223,8 +235,8 @@ impl, I: 'static> Pallet { )?; // Update collection accounts and set the new owner. - CollectionAccount::::remove(&details.owner, collection); - CollectionAccount::::insert(&owner, collection, ()); + CollectionAccount::::remove(&details.owner, &collection); + CollectionAccount::::insert(&owner, &collection, ()); details.owner = owner.clone(); // Emit `OwnerChanged` event. diff --git a/pallets/nfts/src/impl_nonfungibles.rs b/pallets/nfts/src/impl_nonfungibles.rs index b014e3ed4..4c73d141b 100644 --- a/pallets/nfts/src/impl_nonfungibles.rs +++ b/pallets/nfts/src/impl_nonfungibles.rs @@ -166,15 +166,19 @@ impl, I: 'static> Create<::AccountId, Collection .ok_or(Error::::UnknownCollection)?; Self::do_create_collection( - collection, + collection.clone(), who.clone(), admin.clone(), - *config, + config.clone(), T::CollectionDeposit::get(), - Event::Created { collection, creator: who.clone(), owner: admin.clone() }, + Event::Created { + collection: collection.clone(), + creator: who.clone(), + owner: admin.clone(), + }, )?; - Self::set_next_collection_id(collection); + Self::set_next_collection_id(collection.clone()); Ok(collection) } @@ -199,12 +203,16 @@ impl, I: 'static> Create<::AccountId, Collection ); Self::do_create_collection( - collection, + collection.clone(), who.clone(), admin.clone(), - *config, + config.clone(), T::CollectionDeposit::get(), - Event::Created { collection, creator: who.clone(), owner: admin.clone() }, + Event::Created { + collection: collection.clone(), + creator: who.clone(), + owner: admin.clone(), + }, ) } } @@ -234,7 +242,7 @@ impl, I: 'static> Mutate<::AccountId, ItemConfig deposit_collection_owner: bool, ) -> DispatchResult { Self::do_mint( - *collection, + collection.clone(), *item, match deposit_collection_owner { true => None, @@ -251,10 +259,10 @@ impl, I: 'static> Mutate<::AccountId, ItemConfig item: &Self::ItemId, maybe_check_owner: Option<&T::AccountId>, ) -> DispatchResult { - Self::do_burn(*collection, *item, |d| { + Self::do_burn(collection.clone(), *item, |d| { if let Some(check_owner) = maybe_check_owner { if &d.owner != check_owner { - return Err(Error::::NoPermission.into()) + return Err(Error::::NoPermission.into()); } } Ok(()) @@ -269,7 +277,7 @@ impl, I: 'static> Mutate<::AccountId, ItemConfig ) -> DispatchResult { Self::do_force_set_attribute( None, - *collection, + collection.clone(), Some(*item), AttributeNamespace::Pallet, Self::construct_attribute_key(key.to_vec())?, @@ -297,7 +305,7 @@ impl, I: 'static> Mutate<::AccountId, ItemConfig ) -> DispatchResult { Self::do_force_set_attribute( None, - *collection, + collection.clone(), None, AttributeNamespace::Pallet, Self::construct_attribute_key(key.to_vec())?, @@ -327,7 +335,7 @@ impl, I: 'static> Mutate<::AccountId, ItemConfig ) -> DispatchResult { Self::do_set_item_metadata( who.cloned(), - *collection, + collection.clone(), *item, Self::construct_metadata(data.to_vec())?, None, @@ -341,7 +349,7 @@ impl, I: 'static> Mutate<::AccountId, ItemConfig ) -> DispatchResult { Self::do_set_collection_metadata( who.cloned(), - *collection, + collection.clone(), Self::construct_metadata(data.to_vec())?, ) } @@ -353,7 +361,7 @@ impl, I: 'static> Mutate<::AccountId, ItemConfig ) -> DispatchResult { Self::do_clear_attribute( None, - *collection, + collection.clone(), Some(*item), AttributeNamespace::Pallet, Self::construct_attribute_key(key.to_vec())?, @@ -373,7 +381,7 @@ impl, I: 'static> Mutate<::AccountId, ItemConfig fn clear_collection_attribute(collection: &Self::CollectionId, key: &[u8]) -> DispatchResult { Self::do_clear_attribute( None, - *collection, + collection.clone(), None, AttributeNamespace::Pallet, Self::construct_attribute_key(key.to_vec())?, @@ -394,14 +402,14 @@ impl, I: 'static> Mutate<::AccountId, ItemConfig collection: &Self::CollectionId, item: &Self::ItemId, ) -> DispatchResult { - Self::do_clear_item_metadata(who.cloned(), *collection, *item) + Self::do_clear_item_metadata(who.cloned(), collection.clone(), *item) } fn clear_collection_metadata( who: Option<&T::AccountId>, collection: &Self::CollectionId, ) -> DispatchResult { - Self::do_clear_collection_metadata(who.cloned(), *collection) + Self::do_clear_collection_metadata(who.cloned(), collection.clone()) } } @@ -411,7 +419,7 @@ impl, I: 'static> Transfer for Pallet { item: &Self::ItemId, destination: &T::AccountId, ) -> DispatchResult { - Self::do_transfer(*collection, *item, destination.clone(), |_, _| Ok(())) + Self::do_transfer(collection.clone(), *item, destination.clone(), |_, _| Ok(())) } fn disable_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> DispatchResult { @@ -419,7 +427,7 @@ impl, I: 'static> Transfer for Pallet { Self::has_system_attribute(collection, item, PalletAttributes::TransferDisabled)?; // Can't lock the item twice if transfer_disabled { - return Err(Error::::ItemLocked.into()) + return Err(Error::::ItemLocked.into()); } >::set_attribute( @@ -446,7 +454,7 @@ impl, I: 'static> Trading> for Pallet buyer: &T::AccountId, bid_price: &ItemPrice, ) -> DispatchResult { - Self::do_buy_item(*collection, *item, buyer.clone(), *bid_price) + Self::do_buy_item(collection.clone(), *item, buyer.clone(), *bid_price) } fn set_price( @@ -456,7 +464,7 @@ impl, I: 'static> Trading> for Pallet price: Option>, whitelisted_buyer: Option, ) -> DispatchResult { - Self::do_set_price(*collection, *item, sender.clone(), price, whitelisted_buyer) + Self::do_set_price(collection.clone(), *item, sender.clone(), price, whitelisted_buyer) } fn item_price(collection: &Self::CollectionId, item: &Self::ItemId) -> Option> { diff --git a/pallets/nfts/src/lib.rs b/pallets/nfts/src/lib.rs index 90d2a6224..04b73fa67 100644 --- a/pallets/nfts/src/lib.rs +++ b/pallets/nfts/src/lib.rs @@ -148,7 +148,7 @@ pub mod pallet { /// the `create_collection_with_id` function. However, if the `Incrementable` trait /// implementation has an incremental order, the `create_collection_with_id` function /// should not be used as it can claim a value in the ID sequence. - type CollectionId: Member + Parameter + MaxEncodedLen + Copy + Incrementable; + type CollectionId: Member + Parameter + MaxEncodedLen + Clone + Incrementable; /// The type used to identify a unique item within a collection. type ItemId: Member + Parameter + MaxEncodedLen + Copy; @@ -175,6 +175,12 @@ pub mod pallet { #[pallet::constant] type CollectionDeposit: Get>; + /// The basic amount of funds that must be reserved for a collection approval. + // Key: `sizeof((CollectionId, AccountId, AccountId))` bytes. + // Value: `sizeof((Option, Balance))` bytes. + #[pallet::constant] + type CollectionApprovalDeposit: Get>; + /// The basic amount of funds that must be reserved for an item. #[pallet::constant] type ItemDeposit: Get>; @@ -406,6 +412,30 @@ pub mod pallet { pub type CollectionConfigOf, I: 'static = ()> = StorageMap<_, Blake2_128Concat, T::CollectionId, CollectionConfigFor, OptionQuery>; + /// Number of collection items owned by an account. + #[pallet::storage] + pub type AccountBalance, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::AccountId, + u32, + ValueQuery, + >; + + /// Permission for a delegate to transfer all owner's collection items. + #[pallet::storage] + pub type CollectionApprovals, I: 'static = ()> = StorageNMap< + _, + ( + NMapKey, + NMapKey, // owner + NMapKey, // delegate + ), + (Option>, DepositBalanceOf), + >; + /// Config of an item. #[pallet::storage] pub type ItemConfigOf, I: 'static = ()> = StorageDoubleMap< @@ -465,21 +495,31 @@ pub mod pallet { /// a `delegate`. TransferApproved { collection: T::CollectionId, - item: T::ItemId, + item: Option, owner: T::AccountId, delegate: T::AccountId, deadline: Option>, }, - /// An approval for a `delegate` account to transfer the `item` of an item - /// `collection` was cancelled by its `owner`. + /// An approval for a `delegate` account to transfer a specific `item` in a `collection` or + /// all collection items owned by the `owner` has been cancelled by the owner. ApprovalCancelled { collection: T::CollectionId, - item: T::ItemId, + item: Option, owner: T::AccountId, delegate: T::AccountId, }, - /// All approvals of an item got cancelled. - AllApprovalsCancelled { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId }, + /// Multiple approvals of a collection or item were cancelled. + ApprovalsCancelled { + collection: T::CollectionId, + item: Option, + owner: T::AccountId, + }, + /// All approvals of a collection or item were cancelled. + AllApprovalsCancelled { + collection: T::CollectionId, + item: Option, + owner: T::AccountId, + }, /// A `collection` has had its config changed by the `Force` origin. CollectionConfigChanged { collection: T::CollectionId }, /// New metadata has been set for a `collection`. @@ -605,7 +645,7 @@ pub mod pallet { pub enum Error { /// The signing account has no permission to do the operation. NoPermission, - /// The given item ID is unknown. + /// The given collection ID is unknown. UnknownCollection, /// The item ID has already been used for an item. AlreadyExists, @@ -693,6 +733,24 @@ pub mod pallet { CollectionNotEmpty, /// The witness data should be provided. WitnessRequired, + /// The account owns zero items in the collection. + NoItemOwned, + /// The collection has existing approvals. + CollectionApprovalsExist, + /// Collection and item approval conflicts. + DelegateApprovalConflict, + } + + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet { + #[cfg(any(feature = "std", test))] + fn integrity_test() { + use core::any::TypeId; + assert!( + TypeId::of::<>::ItemId>() != TypeId::of::() && + TypeId::of::<>::ItemId>() != TypeId::of::() + ); + } } #[pallet::call] @@ -733,12 +791,12 @@ pub mod pallet { ); Self::do_create_collection( - collection, + collection.clone(), owner.clone(), admin.clone(), config, T::CollectionDeposit::get(), - Event::Created { collection, creator: owner, owner: admin }, + Event::Created { collection: collection.clone(), creator: owner, owner: admin }, )?; Self::set_next_collection_id(collection); @@ -775,12 +833,12 @@ pub mod pallet { .ok_or(Error::::UnknownCollection)?; Self::do_create_collection( - collection, + collection.clone(), owner.clone(), owner.clone(), config, Zero::zero(), - Event::ForceCreated { collection, owner }, + Event::ForceCreated { collection: collection.clone(), owner }, )?; Self::set_next_collection_id(collection); @@ -792,7 +850,7 @@ pub mod pallet { /// The origin must conform to `ForceOrigin` or must be `Signed` and the sender must be the /// owner of the `collection`. /// - /// NOTE: The collection must have 0 items to be destroyed. + /// NOTE: The collection must have 0 items and 0 approvals to be destroyed. /// /// - `collection`: The identifier of the collection to be destroyed. /// - `witness`: Information on the items minted in the collection. This must be @@ -859,13 +917,13 @@ pub mod pallet { ItemConfig { settings: Self::get_default_item_settings(&collection)? }; Self::do_mint( - collection, + collection.clone(), item, Some(caller.clone()), mint_to.clone(), item_config, |collection_details, collection_config| { - let mint_settings = collection_config.mint_settings; + let mint_settings = collection_config.clone().mint_settings; let now = frame_system::Pallet::::block_number(); if let Some(start_block) = mint_settings.start_block { @@ -1033,14 +1091,9 @@ pub mod pallet { let origin = ensure_signed(origin)?; let dest = T::Lookup::lookup(dest)?; - Self::do_transfer(collection, item, dest, |_, details| { + Self::do_transfer(collection.clone(), item, dest, |_, details| { if details.owner != origin { - let deadline = - details.approvals.get(&origin).ok_or(Error::::NoPermission)?; - if let Some(d) = deadline { - let block_number = frame_system::Pallet::::block_number(); - ensure!(block_number <= *d, Error::::ApprovalExpired); - } + Self::check_approval(&collection, &Some(item), &details.owner, &origin)?; } Ok(()) }) @@ -1073,7 +1126,7 @@ pub mod pallet { let origin = ensure_signed(origin)?; let collection_details = - Collection::::get(collection).ok_or(Error::::UnknownCollection)?; + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; ensure!(collection_details.owner == origin, Error::::NoPermission); let config = Self::get_collection_config(&collection)?; @@ -1084,7 +1137,7 @@ pub mod pallet { let mut successful = Vec::with_capacity(items.len()); for item in items.into_iter() { - let mut details = match Item::::get(collection, item) { + let mut details = match Item::::get(&collection, item) { Some(x) => x, None => continue, }; @@ -1097,13 +1150,13 @@ pub mod pallet { if T::Currency::reserve(&details.deposit.account, deposit - old).is_err() { // NOTE: No alterations made to collection_details in this iteration so // far, so this is OK to do. - continue + continue; } }, _ => continue, } details.deposit.amount = deposit; - Item::::insert(collection, item, &details); + Item::::insert(&collection, item, &details); successful.push(item); } @@ -1316,6 +1369,65 @@ pub mod pallet { ) } + /// Approve collection items owned by the origin to be transferred by a delegated + /// third-party account. This function reserves the required deposit + /// `CollectionApprovalDeposit` from the `origin` account. + /// + /// Origin must be Signed. + /// + /// - `collection`: The collection of the item to be approved for delegated transfer. + /// - `delegate`: The account to delegate permission to transfer collection items owned by + /// the origin. + /// - `maybe_deadline`: Optional deadline for the approval. Specified by providing the + /// number of blocks after which the approval will expire + /// + /// Emits `TransferApproved` on success. + /// + /// Weight: `O(1)` + #[pallet::call_index(39)] + #[pallet::weight(T::WeightInfo::approve_collection_transfer())] + pub fn approve_collection_transfer( + origin: OriginFor, + collection: T::CollectionId, + delegate: AccountIdLookupOf, + maybe_deadline: Option>, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let delegate = T::Lookup::lookup(delegate)?; + Self::do_approve_collection_transfer(origin, collection, delegate, maybe_deadline) + } + + /// Force-approve collection items owned by the `owner` to be transferred by a delegated + /// third-party account. This function reserves the required deposit + /// `CollectionApprovalDeposit` from the `owner` account. + /// + /// Origin must be the `ForceOrigin`. + /// + /// - `owner`: The account granting approval for delegated transfer. + /// - `collection`: The collection of the item to be approved for delegated transfer. + /// - `delegate`: The account to delegate permission to transfer collection items owned by + /// the `owner`. + /// - `maybe_deadline`: Optional deadline for the approval. Specified by providing the + /// number of blocks after which the approval will expire. + /// + /// Emits `TransferApproved` on success. + /// + /// Weight: `O(1)` + #[pallet::call_index(40)] + #[pallet::weight(T::WeightInfo::force_approve_collection_transfer())] + pub fn force_approve_collection_transfer( + origin: OriginFor, + owner: AccountIdLookupOf, + collection: T::CollectionId, + delegate: AccountIdLookupOf, + maybe_deadline: Option>, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + let delegate = T::Lookup::lookup(delegate)?; + let owner = T::Lookup::lookup(owner)?; + Self::do_approve_collection_transfer(owner, collection, delegate, maybe_deadline) + } + /// Cancel one of the transfer approvals for a specific item. /// /// Origin must be either: @@ -1325,7 +1437,7 @@ pub mod pallet { /// Arguments: /// - `collection`: The collection of the item of whose approval will be cancelled. /// - `item`: The item of the collection of whose approval will be cancelled. - /// - `delegate`: The account that is going to loose their approval. + /// - `delegate`: The account that is going to lose their approval. /// /// Emits `ApprovalCancelled` on success. /// @@ -1345,6 +1457,55 @@ pub mod pallet { Self::do_cancel_approval(maybe_check_origin, collection, item, delegate) } + /// Cancel a collection approval. + /// + /// Origin must be Signed. + /// + /// Arguments: + /// - `collection`: The collection whose approval will be cancelled. + /// - `delegate`: The account that is going to lose their approval. + /// + /// Emits `ApprovalCancelled` on success. + /// + /// Weight: `O(1)` + #[pallet::call_index(41)] + #[pallet::weight(T::WeightInfo::cancel_collection_approval())] + pub fn cancel_collection_approval( + origin: OriginFor, + collection: T::CollectionId, + delegate: AccountIdLookupOf, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let delegate = T::Lookup::lookup(delegate)?; + Self::do_cancel_collection_approval(origin, collection, delegate) + } + + /// Force-cancel a collection approval granted by `owner` account. + /// + /// Origin must be `ForceOrigin`. + /// + /// Arguments: + /// - `owner`: The account cancelling approval for delegated transfer. + /// - `collection`: The collection of whose approval will be cancelled. + /// - `delegate`: The account that is going to lose their approval. + /// + /// Emits `ApprovalCancelled` on success. + /// + /// Weight: `O(1)` + #[pallet::call_index(42)] + #[pallet::weight(T::WeightInfo::force_cancel_collection_approval())] + pub fn force_cancel_collection_approval( + origin: OriginFor, + owner: AccountIdLookupOf, + collection: T::CollectionId, + delegate: AccountIdLookupOf, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + let delegate = T::Lookup::lookup(delegate)?; + let owner = T::Lookup::lookup(owner)?; + Self::do_cancel_collection_approval(owner, collection, delegate) + } + /// Cancel all the approvals of a specific item. /// /// Origin must be either: @@ -1371,6 +1532,56 @@ pub mod pallet { Self::do_clear_all_transfer_approvals(maybe_check_origin, collection, item) } + /// Cancel all collection approvals, up to a specified limit. + /// + /// Origin must be Signed. + /// + /// Arguments: + /// - `collection`: The collection whose approvals will be cleared. + /// - `limit`: The amount of collection approvals that will be cleared. + /// + /// Emits `ApprovalsCancelled` on success. + /// + /// Weight: `O(1)` + #[pallet::call_index(43)] + #[pallet::weight(T::WeightInfo::clear_collection_approvals(*limit))] + pub fn clear_collection_approvals( + origin: OriginFor, + collection: T::CollectionId, + limit: u32, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::do_clear_collection_approvals(origin, collection, limit)?; + Ok(()) + } + + /// Force-cancel all collection approvals granted by `owner` account, up to a specified + /// limit. + /// + /// Origin must be `ForceOrigin`. + /// + /// Arguments: + /// - `owner`: The account clearing all collection approvals. + /// - `collection`: The collection whose approvals will be cleared. + /// - `limit`: The amount of collection approvals that will be cleared. + /// + /// Emits `ApprovalsCancelled` on success. + /// + /// Weight: `O(1)` + #[pallet::call_index(44)] + #[pallet::weight(T::WeightInfo::force_clear_collection_approvals(*limit))] + pub fn force_clear_collection_approvals( + origin: OriginFor, + owner: AccountIdLookupOf, + collection: T::CollectionId, + limit: u32, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + let owner = T::Lookup::lookup(owner)?; + Self::do_clear_collection_approvals(owner, collection, limit)?; + Ok(()) + } + /// Disallows changing the metadata or attributes of the item. /// /// Origin must be either `ForceOrigin` or Signed and the sender should be the Admin @@ -1443,8 +1654,8 @@ pub mod pallet { ) -> DispatchResult { let origin = ensure_signed(origin)?; let depositor = match namespace { - AttributeNamespace::CollectionOwner => - Self::collection_owner(collection).ok_or(Error::::UnknownCollection)?, + AttributeNamespace::CollectionOwner => Self::collection_owner(collection.clone()) + .ok_or(Error::::UnknownCollection)?, _ => origin.clone(), }; Self::do_set_attribute(origin, collection, maybe_item, namespace, key, value, depositor) diff --git a/pallets/nfts/src/mock.rs b/pallets/nfts/src/mock.rs index 5532be8f4..c6e4211e0 100644 --- a/pallets/nfts/src/mock.rs +++ b/pallets/nfts/src/mock.rs @@ -65,6 +65,7 @@ parameter_types! { impl Config for Test { type ApprovalsLimit = ConstU32<10>; type AttributeDepositBase = ConstU64<1>; + type CollectionApprovalDeposit = ConstU64<1>; type CollectionDeposit = ConstU64<2>; type CollectionId = u32; type CreateOrigin = AsEnsureOriginWithArg>; diff --git a/pallets/nfts/src/tests.rs b/pallets/nfts/src/tests.rs index 44f2f32ae..27e0aa905 100644 --- a/pallets/nfts/src/tests.rs +++ b/pallets/nfts/src/tests.rs @@ -20,11 +20,14 @@ use enumflags2::BitFlags; use frame_support::{ assert_noop, assert_ok, + pallet_prelude::MaxEncodedLen, traits::{ tokens::nonfungibles_v2::{Create, Destroy, Inspect, Mutate}, Currency, Get, }, + Blake2_128Concat, StorageHasher, }; +use frame_system::pallet_prelude::BlockNumberFor; use pallet_balances::Error as BalancesError; use sp_core::{bounded::BoundedVec, Pair}; use sp_runtime::{ @@ -157,116 +160,184 @@ fn basic_setup_works() { #[test] fn basic_minting_should_work() { new_test_ext().execute_with(|| { + let (collection_id_1, collection_id_2) = (0, 1); + let (item_id_1, item_id_2) = (42, 69); + let owner_1 = account(1); + let owner_2 = account(2); + assert_ok!(Nfts::force_create( RuntimeOrigin::root(), - account(1), + owner_1.clone(), default_collection_config() )); - assert_eq!(collections(), vec![(account(1), 0)]); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); - assert_eq!(items(), vec![(account(1), 0, 42)]); + assert_eq!(collections(), vec![(owner_1.clone(), collection_id_1)]); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(owner_1.clone()), + collection_id_1, + item_id_1, + owner_1.clone(), + None + )); + assert_eq!(AccountBalance::::get(collection_id_1, &owner_1), 1); + assert_eq!(items(), vec![(owner_1.clone(), collection_id_1, item_id_1)]); assert_ok!(Nfts::force_create( RuntimeOrigin::root(), - account(2), + owner_2.clone(), default_collection_config() )); - assert_eq!(collections(), vec![(account(1), 0), (account(2), 1)]); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(2)), 1, 69, account(1), None)); - assert_eq!(items(), vec![(account(1), 0, 42), (account(1), 1, 69)]); + assert_eq!( + collections(), + vec![(owner_1.clone(), collection_id_1), (owner_2.clone(), collection_id_2)] + ); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(owner_2), + collection_id_2, + item_id_2, + owner_1.clone(), + None + )); + assert_eq!(AccountBalance::::get(collection_id_2, &owner_1), 1); + assert_eq!( + items(), + vec![ + (owner_1.clone(), collection_id_1, item_id_1), + (owner_1, collection_id_2, item_id_2) + ] + ); }); } #[test] fn lifecycle_should_work() { new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&account(1), 100); + let collection_id = 0; + let owner = account(1); + + Balances::make_free_balance_be(&owner, 100); Balances::make_free_balance_be(&account(2), 100); assert_ok!(Nfts::create( - RuntimeOrigin::signed(account(1)), - account(1), + RuntimeOrigin::signed(owner.clone()), + owner.clone(), collection_config_with_all_settings_enabled() )); - assert_eq!(Balances::reserved_balance(&account(1)), 2); - assert_eq!(collections(), vec![(account(1), 0)]); + assert_eq!(Balances::reserved_balance(&owner), 2); + assert_eq!(collections(), vec![(owner.clone(), collection_id)]); assert_ok!(Nfts::set_collection_metadata( - RuntimeOrigin::signed(account(1)), - 0, + RuntimeOrigin::signed(owner.clone()), + collection_id, bvec![0, 0] )); - assert_eq!(Balances::reserved_balance(&account(1)), 5); + assert_eq!(Balances::reserved_balance(&owner), 5); assert!(CollectionMetadataOf::::contains_key(0)); assert_ok!(Nfts::force_mint( - RuntimeOrigin::signed(account(1)), - 0, + RuntimeOrigin::signed(owner.clone()), + collection_id, 42, account(10), default_item_config() )); - assert_eq!(Balances::reserved_balance(&account(1)), 6); + assert_eq!(AccountBalance::::get(collection_id, account(10)), 1); + assert_eq!(Balances::reserved_balance(&owner), 6); assert_ok!(Nfts::force_mint( - RuntimeOrigin::signed(account(1)), - 0, + RuntimeOrigin::signed(owner.clone()), + collection_id, 69, account(20), default_item_config() )); - assert_eq!(Balances::reserved_balance(&account(1)), 7); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 70, account(1), None)); - assert_eq!(items(), vec![(account(1), 0, 70), (account(10), 0, 42), (account(20), 0, 69)]); - assert_eq!(Collection::::get(0).unwrap().items, 3); - assert_eq!(Collection::::get(0).unwrap().item_metadatas, 0); - assert_eq!(Collection::::get(0).unwrap().item_configs, 3); + assert_eq!(AccountBalance::::get(collection_id, account(20)), 1); + assert_eq!(Balances::reserved_balance(&owner), 7); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(owner.clone()), + collection_id, + 70, + owner.clone(), + None + )); + assert_eq!(AccountBalance::::get(collection_id, &owner), 1); + assert_eq!( + items(), + vec![ + (owner.clone(), collection_id, 70), + (account(10), collection_id, 42), + (account(20), collection_id, 69) + ] + ); + assert_eq!(Collection::::get(collection_id).unwrap().items, 3); + assert_eq!(Collection::::get(collection_id).unwrap().item_metadatas, 0); + assert_eq!(Collection::::get(collection_id).unwrap().item_configs, 3); - assert_eq!(Balances::reserved_balance(&account(1)), 8); - assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 70, account(2))); - assert_eq!(Balances::reserved_balance(&account(1)), 8); + assert_eq!(Balances::reserved_balance(&owner), 8); + assert_ok!(Nfts::transfer( + RuntimeOrigin::signed(owner.clone()), + collection_id, + 70, + account(2) + )); + assert_eq!(AccountBalance::::get(collection_id, &owner), 0); + assert_eq!(AccountBalance::::get(collection_id, account(2)), 1); + assert_eq!(Balances::reserved_balance(&owner), 8); assert_eq!(Balances::reserved_balance(&account(2)), 0); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![42, 42])); - assert_eq!(Balances::reserved_balance(&account(1)), 11); - assert!(ItemMetadataOf::::contains_key(0, 42)); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 69, bvec![69, 69])); - assert_eq!(Balances::reserved_balance(&account(1)), 14); - assert!(ItemMetadataOf::::contains_key(0, 69)); - assert!(ItemConfigOf::::contains_key(0, 69)); + assert_ok!(Nfts::set_metadata( + RuntimeOrigin::signed(owner.clone()), + collection_id, + 42, + bvec![42, 42] + )); + assert_eq!(Balances::reserved_balance(&owner), 11); + assert!(ItemMetadataOf::::contains_key(collection_id, 42)); + assert_ok!(Nfts::set_metadata( + RuntimeOrigin::signed(owner.clone()), + collection_id, + 69, + bvec![69, 69] + )); + assert_eq!(Balances::reserved_balance(&owner), 14); + assert!(ItemMetadataOf::::contains_key(collection_id, 69)); + assert!(ItemConfigOf::::contains_key(collection_id, 69)); let w = Nfts::get_destroy_witness(&0).unwrap(); assert_eq!(w.item_metadatas, 2); assert_eq!(w.item_configs, 3); assert_noop!( - Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w), + Nfts::destroy(RuntimeOrigin::signed(owner.clone()), collection_id, w), Error::::CollectionNotEmpty ); + assert_eq!(AccountBalance::::get(collection_id, &owner), 0); assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(account(1)), - 0, + RuntimeOrigin::signed(owner.clone()), + collection_id, Some(69), AttributeNamespace::CollectionOwner, bvec![0], bvec![0], )); - assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(10)), 0, 42)); - assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(20)), 0, 69)); - assert_ok!(Nfts::burn(RuntimeOrigin::root(), 0, 70)); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(10)), collection_id, 42)); + assert_eq!(AccountBalance::::get(collection_id, account(10)), 0); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(20)), collection_id, 69)); + assert_eq!(AccountBalance::::get(collection_id, account(10)), 0); + assert_ok!(Nfts::burn(RuntimeOrigin::root(), collection_id, 70)); - let w = Nfts::get_destroy_witness(&0).unwrap(); + let w = Nfts::get_destroy_witness(&collection_id).unwrap(); assert_eq!(w.attributes, 1); assert_eq!(w.item_metadatas, 0); assert_eq!(w.item_configs, 0); - assert_ok!(Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w)); - assert_eq!(Balances::reserved_balance(&account(1)), 0); - - assert!(!Collection::::contains_key(0)); - assert!(!CollectionConfigOf::::contains_key(0)); - assert!(!Item::::contains_key(0, 42)); - assert!(!Item::::contains_key(0, 69)); - assert!(!CollectionMetadataOf::::contains_key(0)); - assert!(!ItemMetadataOf::::contains_key(0, 42)); - assert!(!ItemMetadataOf::::contains_key(0, 69)); - assert!(!ItemConfigOf::::contains_key(0, 69)); - assert_eq!(attributes(0), vec![]); + assert_ok!(Nfts::destroy(RuntimeOrigin::signed(owner.clone()), collection_id, w)); + assert_eq!(AccountBalance::::get(collection_id, &owner), 0); + assert_eq!(Balances::reserved_balance(&owner), 0); + + assert!(!Collection::::contains_key(collection_id)); + assert!(!CollectionConfigOf::::contains_key(collection_id)); + assert!(!Item::::contains_key(collection_id, 42)); + assert!(!Item::::contains_key(collection_id, 69)); + assert!(!CollectionMetadataOf::::contains_key(collection_id)); + assert!(!ItemMetadataOf::::contains_key(collection_id, 42)); + assert!(!ItemMetadataOf::::contains_key(collection_id, 69)); + assert!(!ItemConfigOf::::contains_key(collection_id, 69)); + assert_eq!(attributes(collection_id), vec![]); assert_eq!(collections(), vec![]); assert_eq!(items(), vec![]); }); @@ -297,34 +368,74 @@ fn destroy_with_bad_witness_should_not_work() { #[test] fn destroy_should_work() { new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&account(1), 100); + let collection_id = 0; + let item_id = 42; + let collection_owner = account(1); + let item_owner = account(2); + let delegate = account(3); + + Balances::make_free_balance_be(&collection_owner, 100); + Balances::make_free_balance_be(&item_owner, 100); assert_ok!(Nfts::create( - RuntimeOrigin::signed(account(1)), - account(1), + RuntimeOrigin::signed(collection_owner.clone()), + collection_owner.clone(), collection_config_with_all_settings_enabled() )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(2), None)); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(collection_owner.clone()), + collection_id, + item_id, + item_owner.clone(), + None + )); + assert_eq!(AccountBalance::::get(collection_id, &item_owner), 1); + assert_ok!(Nfts::approve_collection_transfer( + RuntimeOrigin::signed(item_owner.clone()), + collection_id, + delegate.clone(), + None + )); assert_noop!( Nfts::destroy( - RuntimeOrigin::signed(account(1)), - 0, - Nfts::get_destroy_witness(&0).unwrap() + RuntimeOrigin::signed(collection_owner.clone()), + collection_id, + Nfts::get_destroy_witness(&collection_id).unwrap() ), Error::::CollectionNotEmpty ); - assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(account(1)), 0, 42)); - assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(2)), 0, 42)); - assert_eq!(Collection::::get(0).unwrap().item_configs, 1); - assert_eq!(ItemConfigOf::::iter_prefix(0).count() as u32, 1); - assert!(ItemConfigOf::::contains_key(0, 42)); + assert_ok!(Nfts::lock_item_transfer( + RuntimeOrigin::signed(collection_owner.clone()), + collection_id, + item_id + )); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(item_owner.clone()), collection_id, item_id)); + assert_eq!(Collection::::get(collection_id).unwrap().item_configs, 1); + assert_eq!(ItemConfigOf::::iter_prefix(collection_id).count() as u32, 1); + assert!(ItemConfigOf::::contains_key(collection_id, item_id)); + assert_noop!( + Nfts::destroy( + RuntimeOrigin::signed(collection_owner.clone()), + collection_id, + Nfts::get_destroy_witness(&collection_id).unwrap() + ), + Error::::CollectionApprovalsExist + ); + assert_ok!(Nfts::cancel_collection_approval( + RuntimeOrigin::signed(item_owner), + collection_id, + delegate + )); assert_ok!(Nfts::destroy( - RuntimeOrigin::signed(account(1)), - 0, - Nfts::get_destroy_witness(&0).unwrap() + RuntimeOrigin::signed(collection_owner.clone()), + collection_id, + Nfts::get_destroy_witness(&collection_id).unwrap() )); - assert!(!ItemConfigOf::::contains_key(0, 42)); - assert_eq!(ItemConfigOf::::iter_prefix(0).count() as u32, 0); + assert_eq!(AccountBalance::::get(collection_id, &collection_owner), 0); + assert!(!AccountBalance::::contains_key(collection_id, &collection_owner)); + assert_eq!(CollectionApprovals::::iter_prefix((collection_id,)).count(), 0); + assert!(!ItemConfigOf::::contains_key(collection_id, item_id)); + assert_eq!(ItemConfigOf::::iter_prefix(collection_id).count() as u32, 0); }); } @@ -337,6 +448,7 @@ fn mint_should_work() { default_collection_config() )); assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); + assert_eq!(AccountBalance::::get(0, account(1)), 1); assert_eq!(Nfts::owner(0, 42).unwrap(), account(1)); assert_eq!(collections(), vec![(account(1), 0)]); assert_eq!(items(), vec![(account(1), 0, 42)]); @@ -402,6 +514,7 @@ fn mint_should_work() { account(2), Some(MintWitness { mint_price: Some(1), ..Default::default() }) )); + assert_eq!(AccountBalance::::get(0, account(2)), 1); assert_eq!(Balances::total_balance(&account(2)), 99); // validate types @@ -440,6 +553,7 @@ fn mint_should_work() { account(2), Some(MintWitness { owned_item: Some(43), ..Default::default() }) )); + assert_eq!(AccountBalance::::get(1, account(2)), 1); assert!(events().contains(&Event::::PalletAttributeSet { collection: 0, item: Some(43), @@ -476,8 +590,9 @@ fn transfer_should_work() { account(2), default_item_config() )); - assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(3))); + assert_eq!(AccountBalance::::get(0, account(2)), 0); + assert_eq!(AccountBalance::::get(0, account(3)), 1); assert_eq!(items(), vec![(account(3), 0, 42)]); assert_noop!( Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(4)), @@ -492,7 +607,8 @@ fn transfer_should_work() { None )); assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(4))); - + assert_eq!(AccountBalance::::get(0, account(3)), 0); + assert_eq!(AccountBalance::::get(0, account(4)), 1); // validate we can't transfer non-transferable items let collection_id = 1; assert_ok!(Nfts::force_create( @@ -1713,49 +1829,58 @@ fn force_update_collection_should_work() { #[test] fn burn_works() { new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&account(1), 100); + let collection_id = 0; + let item_id_1 = 42; + let item_id_2 = 69; + let collection_owner = account(1); + let admin = account(2); + let item_owner = account(5); + + Balances::make_free_balance_be(&collection_owner, 100); assert_ok!(Nfts::force_create( RuntimeOrigin::root(), - account(1), + collection_owner.clone(), collection_config_with_all_settings_enabled() )); assert_ok!(Nfts::set_team( - RuntimeOrigin::signed(account(1)), - 0, + RuntimeOrigin::signed(collection_owner.clone()), + collection_id, + Some(admin.clone()), Some(account(2)), Some(account(3)), - Some(account(4)), )); assert_noop!( - Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42), + Nfts::burn(RuntimeOrigin::signed(item_owner.clone()), collection_id, item_id_1), Error::::UnknownItem ); assert_ok!(Nfts::force_mint( - RuntimeOrigin::signed(account(2)), - 0, - 42, - account(5), + RuntimeOrigin::signed(admin.clone()), + collection_id, + item_id_1, + item_owner.clone(), default_item_config() )); assert_ok!(Nfts::force_mint( - RuntimeOrigin::signed(account(2)), - 0, - 69, - account(5), + RuntimeOrigin::signed(admin), + collection_id, + item_id_2, + item_owner.clone(), default_item_config() )); - assert_eq!(Balances::reserved_balance(account(1)), 2); + assert_eq!(AccountBalance::::get(collection_id, &item_owner), 2); + assert_eq!(Balances::reserved_balance(collection_owner.clone()), 2); assert_noop!( - Nfts::burn(RuntimeOrigin::signed(account(0)), 0, 42), + Nfts::burn(RuntimeOrigin::signed(account(0)), collection_id, item_id_1), Error::::NoPermission ); - - assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42)); - assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 69)); - assert_eq!(Balances::reserved_balance(account(1)), 0); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(item_owner.clone()), collection_id, item_id_1)); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(item_owner.clone()), collection_id, item_id_2)); + assert_eq!(AccountBalance::::get(collection_id, &item_owner), 0); + assert_eq!(AccountBalance::::contains_key(collection_id, &item_owner), false); + assert_eq!(Balances::reserved_balance(collection_owner), 0); }); } @@ -1828,6 +1953,135 @@ fn approval_lifecycle_works() { }); } +#[test] +fn check_approval_without_deadline_works() { + new_test_ext().execute_with(|| { + let collection_id = 0; + let item_id = 42; + let collection_owner = account(1); + let item_owner = account(2); + let delegate = account(3); + + Balances::make_free_balance_be(&item_owner, 100); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + collection_owner.clone(), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(collection_owner.clone()), + collection_id, + item_id, + item_owner.clone(), + default_item_config() + )); + + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(item_owner.clone()), + collection_id, + item_id, + delegate.clone(), + None + )); + // Has only item approval. + assert_ok!(Nfts::check_approval(&collection_id, &Some(item_id), &item_owner, &delegate)); + assert_noop!( + Nfts::check_approval(&collection_id, &None, &item_owner, &delegate), + Error::::NoPermission + ); + + assert_ok!(Nfts::approve_collection_transfer( + RuntimeOrigin::signed(item_owner.clone()), + collection_id, + delegate.clone(), + None + )); + // Has both collection and item approval. + assert_ok!(Nfts::check_approval(&collection_id, &None, &item_owner, &delegate)); + assert_ok!(Nfts::check_approval(&collection_id, &Some(item_id), &item_owner, &delegate)); + }); +} + +#[test] +fn check_approval_with_deadline_works() { + new_test_ext().execute_with(|| { + let collection_id = 0; + let item_id = 42; + let collection_owner = account(1); + let item_owner = account(2); + let delegate = account(3); + + Balances::make_free_balance_be(&item_owner, 100); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + collection_owner.clone(), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(collection_owner.clone()), + collection_id, + item_id, + item_owner.clone(), + default_item_config() + )); + + let deadline: BlockNumberFor = 10; + for case in [ + // Collection approval expires first. + (deadline, deadline + 1, Err(Error::::ApprovalExpired.into()), Ok(())), + // Item approval expires first. + (deadline + 1, deadline, Ok(()), Ok(())), + ] { + System::set_block_number(0); + assert_ok!(Nfts::approve_collection_transfer( + RuntimeOrigin::signed(item_owner.clone()), + collection_id, + delegate.clone(), + Some(case.0) + )); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(item_owner.clone()), + collection_id, + item_id, + delegate.clone(), + Some(case.1) + )); + + // Initially, all approvals should be valid. + assert_ok!(Nfts::check_approval(&collection_id, &None, &item_owner, &delegate)); + assert_ok!(Nfts::check_approval( + &collection_id, + &Some(item_id), + &item_owner, + &delegate + )); + + // Move past the deadline by 1 block. + System::set_block_number(deadline + 1); + + // (deadline + 1): Collection or item approval deadline has passed. + assert_eq!(Nfts::check_approval(&collection_id, &None, &item_owner, &delegate), case.2); + assert_eq!( + Nfts::check_approval(&collection_id, &Some(item_id), &item_owner, &delegate), + case.3 + ); + + // Move past the deadline by 2 blocks. + System::set_block_number(deadline + 2); + + // (deadline + 2): Both collection and item approval expires. + assert_eq!( + Nfts::check_approval(&collection_id, &None, &item_owner, &delegate), + Err(Error::::ApprovalExpired.into()) + ); + assert_eq!( + Nfts::check_approval(&collection_id, &Some(item_id), &item_owner, &delegate), + Err(Error::::ApprovalExpired.into()) + ); + } + }); +} + #[test] fn cancel_approval_works() { new_test_ext().execute_with(|| { @@ -1903,6 +2157,140 @@ fn cancel_approval_works() { }); } +#[test] +fn cancel_collection_approval_works() { + new_test_ext().execute_with(|| { + let collection_id = 0; + let item_id = 42; + let collection_owner = account(1); + let item_owner = account(2); + let delegate = account(3); + + Balances::make_free_balance_be(&item_owner, 100); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + collection_owner.clone(), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(collection_owner), + collection_id, + item_id, + item_owner.clone(), + default_item_config() + )); + + assert_ok!(Nfts::approve_collection_transfer( + RuntimeOrigin::signed(item_owner.clone()), + collection_id, + delegate.clone(), + None + )); + assert_eq!(Balances::reserved_balance(&item_owner), 1); + + // Cancel an unapproved delegate. + assert_noop!( + Nfts::cancel_collection_approval( + RuntimeOrigin::signed(item_owner.clone()), + 1, + delegate.clone() + ), + Error::::Unapproved + ); + + // Successfully cancel a collection approval. + assert_ok!(Nfts::cancel_collection_approval( + RuntimeOrigin::signed(item_owner.clone()), + collection_id, + delegate.clone() + )); + assert_eq!(Balances::reserved_balance(&item_owner), 0); + assert!(events().contains(&Event::::ApprovalCancelled { + collection: collection_id, + item: None, + owner: item_owner.clone(), + delegate: delegate.clone() + })); + assert_eq!( + CollectionApprovals::::get((collection_id, item_owner, delegate.clone())), + None + ); + + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(delegate), collection_id, item_id, account(4)), + Error::::NoPermission + ); + }); +} + +#[test] +fn force_cancel_collection_approvals_work() { + new_test_ext().execute_with(|| { + let collection_id = 0; + let item_id = 42; + let collection_owner = account(1); + let item_owner = account(2); + let delegate = account(3); + + Balances::make_free_balance_be(&item_owner, 100); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + collection_owner.clone(), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(collection_owner), + collection_id, + item_id, + item_owner.clone(), + default_item_config() + )); + + assert_ok!(Nfts::approve_collection_transfer( + RuntimeOrigin::signed(item_owner.clone()), + collection_id, + delegate.clone(), + None + )); + assert_eq!(Balances::reserved_balance(&item_owner), 1); + + // Cancel an unapproved delegate. + assert_noop!( + Nfts::force_cancel_collection_approval( + RuntimeOrigin::root(), + item_owner.clone(), + 1, + delegate.clone() + ), + Error::::Unapproved + ); + + // Successfully cancel a collection approval. + assert_ok!(Nfts::force_cancel_collection_approval( + RuntimeOrigin::root(), + item_owner.clone(), + collection_id, + delegate.clone() + )); + assert_eq!(Balances::reserved_balance(&item_owner), 0); + assert!(events().contains(&Event::::ApprovalCancelled { + collection: collection_id, + item: None, + owner: item_owner.clone(), + delegate: delegate.clone() + })); + assert_eq!( + CollectionApprovals::::get((collection_id, item_owner, delegate.clone())), + None + ); + + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(delegate), collection_id, item_id, account(4)), + Error::::NoPermission + ); + }); +} + #[test] fn approving_multiple_accounts_works() { new_test_ext().execute_with(|| { @@ -1942,53 +2330,307 @@ fn approving_multiple_accounts_works() { account(5), Some(2) )); - assert_eq!( - approvals(0, 42), - vec![(account(3), None), (account(4), None), (account(5), Some(current_block + 2))] + assert_eq!( + approvals(0, 42), + vec![(account(3), None), (account(4), None), (account(5), Some(current_block + 2))] + ); + + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(4)), 0, 42, account(6))); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(7)), + Error::::NoPermission + ); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(account(5)), 0, 42, account(8)), + Error::::NoPermission + ); + }); +} + +#[test] +fn approvals_limit_works() { + new_test_ext().execute_with(|| { + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); + + for i in 3..13 { + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(i), + None + )); + } + // the limit is 10 + assert_noop!( + Nfts::approve_transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(14), None), + Error::::ReachedApprovalLimit + ); + }); +} + +#[test] +fn approve_collection_transfer_works() { + new_test_ext().execute_with(|| { + let (collection, locked_collection) = (0, 1); + let item = 42; + let collection_owner = account(1); + let item_owner = account(2); + let delegate = account(3); + + Balances::make_free_balance_be(&item_owner, 100); + Balances::make_free_balance_be(&delegate, 100); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + collection_owner.clone(), + default_collection_config() + )); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + collection_owner.clone(), + default_collection_config() + )); + + // Approve collection without items, throws error `Error::NoItemOwned`. + assert_noop!( + Nfts::approve_collection_transfer( + RuntimeOrigin::signed(item_owner.clone()), + collection, + delegate.clone(), + None + ), + Error::::NoItemOwned + ); + + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(collection_owner.clone()), + collection, + item, + item_owner.clone(), + default_item_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(collection_owner.clone()), + locked_collection, + item, + item_owner.clone(), + default_item_config() + )); + + // Throws error `Error::ItemsNonTransferable`. + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(collection_owner), + locked_collection, + CollectionSettings::from_disabled(CollectionSetting::TransferableItems.into()) + )); + assert_noop!( + Nfts::approve_collection_transfer( + RuntimeOrigin::signed(item_owner.clone()), + locked_collection, + delegate.clone(), + None + ), + Error::::ItemsNonTransferable ); - assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(4)), 0, 42, account(6))); + // Approve unknown collection, throws error `Error::NoItemOwned`. assert_noop!( - Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(7)), - Error::::NoPermission + Nfts::approve_collection_transfer( + RuntimeOrigin::signed(item_owner.clone()), + 2, + delegate.clone(), + None + ), + Error::::NoItemOwned ); + + // Approval expires after `deadline`. + let deadline = 10; + assert_ok!(Nfts::approve_collection_transfer( + RuntimeOrigin::signed(item_owner.clone()), + collection, + delegate.clone(), + Some(deadline) + )); + assert_ok!(Nfts::check_approval(&collection, &None, &item_owner, &delegate)); + System::set_block_number(deadline + 2); assert_noop!( - Nfts::transfer(RuntimeOrigin::signed(account(5)), 0, 42, account(8)), - Error::::NoPermission + Nfts::check_approval(&collection, &None, &item_owner, &delegate), + Error::::ApprovalExpired ); + + // Approve delegate of an existing expired approval to transfer. + assert_ok!(Nfts::approve_collection_transfer( + RuntimeOrigin::signed(item_owner.clone()), + collection, + delegate.clone(), + Some(deadline) + )); + assert_eq!(Balances::reserved_balance(&item_owner), 1); + let now = System::block_number(); + assert!(events().contains(&Event::::TransferApproved { + collection: 0, + item: None, + owner: item_owner.clone(), + delegate: delegate.clone(), + deadline: Some(now + deadline) + })); + assert_eq!( + CollectionApprovals::::get((0, item_owner.clone(), delegate.clone())), + Some((Some(now + deadline), 1)) + ); + + // Approve same delegate again not updating the total reserved funds. + assert_ok!(Nfts::approve_collection_transfer( + RuntimeOrigin::signed(item_owner.clone()), + collection, + delegate.clone(), + None + )); + assert_eq!(Balances::reserved_balance(&item_owner), 1); + + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(delegate), collection, item, account(4))); }); } #[test] -fn approvals_limit_works() { +fn force_approve_collection_transfer_works() { new_test_ext().execute_with(|| { + let (collection, locked_collection) = (0, 1); + let item = 42; + let collection_owner = account(1); + let item_owner = account(2); + let delegate = account(3); + + Balances::make_free_balance_be(&item_owner, 100); + Balances::make_free_balance_be(&delegate, 100); assert_ok!(Nfts::force_create( RuntimeOrigin::root(), - account(1), + collection_owner.clone(), + default_collection_config() + )); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + collection_owner.clone(), default_collection_config() )); + + // Approve collection without items, throws error `Error::NoItemOwned`. + assert_noop!( + Nfts::force_approve_collection_transfer( + RuntimeOrigin::root(), + item_owner.clone(), + collection, + delegate.clone(), + None + ), + Error::::NoItemOwned + ); + assert_ok!(Nfts::force_mint( - RuntimeOrigin::signed(account(1)), - 0, - 42, - account(2), + RuntimeOrigin::signed(collection_owner.clone()), + collection, + item, + item_owner.clone(), + default_item_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(collection_owner.clone()), + locked_collection, + item, + item_owner.clone(), default_item_config() )); - for i in 3..13 { - assert_ok!(Nfts::approve_transfer( - RuntimeOrigin::signed(account(2)), - 0, - 42, - account(i), + // Throws error `Error::ItemsNonTransferable`. + assert_ok!(Nfts::lock_collection( + RuntimeOrigin::signed(collection_owner), + locked_collection, + CollectionSettings::from_disabled(CollectionSetting::TransferableItems.into()) + )); + assert_noop!( + Nfts::force_approve_collection_transfer( + RuntimeOrigin::root(), + item_owner.clone(), + locked_collection, + delegate.clone(), None - )); - } - // the limit is 10 + ), + Error::::ItemsNonTransferable + ); + + // Approve unknown collection, throws error `Error::NoItemOwned`. assert_noop!( - Nfts::approve_transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(14), None), - Error::::ReachedApprovalLimit + Nfts::force_approve_collection_transfer( + RuntimeOrigin::root(), + item_owner.clone(), + 2, + delegate.clone(), + None + ), + Error::::NoItemOwned + ); + + // Approval expires after `deadline`. + let deadline = 10; + assert_ok!(Nfts::force_approve_collection_transfer( + RuntimeOrigin::root(), + item_owner.clone(), + collection, + delegate.clone(), + Some(deadline) + )); + assert_ok!(Nfts::check_approval(&collection, &None, &item_owner, &delegate)); + System::set_block_number(deadline + 2); + assert_noop!( + Nfts::check_approval(&collection, &None, &item_owner, &delegate), + Error::::ApprovalExpired + ); + + // Approve delegate of an existing expired approval to transfer. + assert_ok!(Nfts::force_approve_collection_transfer( + RuntimeOrigin::root(), + item_owner.clone(), + collection, + delegate.clone(), + Some(deadline) + )); + assert_eq!(Balances::reserved_balance(&item_owner), 1); + let now = System::block_number(); + assert!(events().contains(&Event::::TransferApproved { + collection: 0, + item: None, + owner: item_owner.clone(), + delegate: delegate.clone(), + deadline: Some(now + deadline) + })); + assert_eq!( + CollectionApprovals::::get((0, item_owner.clone(), delegate.clone())), + Some((Some(now + deadline), 1)) ); + + // Approve same delegate again not updating the total reserved funds. + assert_ok!(Nfts::force_approve_collection_transfer( + RuntimeOrigin::root(), + item_owner.clone(), + collection, + delegate.clone(), + None + )); + assert_eq!(Balances::reserved_balance(&item_owner), 1); + + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(delegate), collection, item, account(4))); }); } @@ -2047,42 +2689,96 @@ fn approval_deadline_works() { #[test] fn cancel_approval_works_with_admin() { new_test_ext().execute_with(|| { + let collection_id = 0; + let item_id = 42; + let collection_owner = account(1); + let item_owner = account(2); + let delegate = account(3); + + Balances::make_free_balance_be(&item_owner, 100); assert_ok!(Nfts::force_create( RuntimeOrigin::root(), - account(1), + collection_owner.clone(), default_collection_config() )); assert_ok!(Nfts::force_mint( RuntimeOrigin::signed(account(1)), - 0, - 42, - account(2), + collection_id, + item_id, + item_owner.clone(), default_item_config() )); assert_ok!(Nfts::approve_transfer( - RuntimeOrigin::signed(account(2)), - 0, - 42, - account(3), + RuntimeOrigin::signed(item_owner.clone()), + collection_id, + item_id, + delegate.clone(), None )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, 42, account(1)), + Nfts::cancel_approval( + RuntimeOrigin::signed(item_owner.clone()), + 1, + item_id, + account(1) + ), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 43, account(1)), + Nfts::cancel_approval( + RuntimeOrigin::signed(item_owner.clone()), + collection_id, + 43, + account(1) + ), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(4)), + Nfts::cancel_approval( + RuntimeOrigin::signed(item_owner.clone()), + collection_id, + item_id, + account(4) + ), Error::::NotDelegate ); - assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(3))); + // delegate approval conflicts. + assert_ok!(Nfts::approve_collection_transfer( + RuntimeOrigin::signed(item_owner.clone()), + collection_id, + delegate.clone(), + None + )); + assert_noop!( + Nfts::cancel_approval( + RuntimeOrigin::signed(item_owner.clone()), + collection_id, + item_id, + delegate.clone() + ), + Error::::DelegateApprovalConflict + ); + assert_ok!(Nfts::cancel_collection_approval( + RuntimeOrigin::signed(item_owner.clone()), + collection_id, + delegate.clone() + )); + + assert_ok!(Nfts::cancel_approval( + RuntimeOrigin::signed(item_owner.clone()), + collection_id, + item_id, + delegate.clone() + )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(1)), + Nfts::cancel_approval( + RuntimeOrigin::signed(item_owner.clone()), + collection_id, + item_id, + account(5) + ), Error::::NotDelegate ); }); @@ -2172,10 +2868,11 @@ fn clear_all_transfer_approvals_works() { assert!(events().contains(&Event::::AllApprovalsCancelled { collection: 0, - item: 42, + item: Some(42), owner: account(2), })); assert_eq!(approvals(0, 42), vec![]); + assert_eq!(CollectionApprovals::::iter_prefix((0, account(2))).count(), 0); assert_noop!( Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(5)), @@ -2188,6 +2885,223 @@ fn clear_all_transfer_approvals_works() { }); } +#[test] +fn clear_collection_approvals_works() { + new_test_ext().execute_with(|| { + let collection_id = 0; + let item_id = 42; + let owner = account(1); + let delegate_1 = account(3); + let delegate_2 = account(4); + let balance = 100; + + Balances::make_free_balance_be(&owner, balance); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + owner.clone(), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + collection_id, + item_id, + owner.clone(), + default_item_config() + )); + + assert_ok!(Nfts::approve_collection_transfer( + RuntimeOrigin::signed(owner.clone()), + collection_id, + delegate_1.clone(), + None + )); + assert_ok!(Nfts::approve_collection_transfer( + RuntimeOrigin::signed(owner.clone()), + collection_id, + delegate_2.clone(), + None + )); + // Remove zero collection approvals, no event emitted. + assert_ok!(Nfts::clear_collection_approvals( + RuntimeOrigin::signed(owner.clone()), + collection_id, + 0 + )); + assert_eq!(Balances::free_balance(&owner), balance - 2); + assert_eq!( + CollectionApprovals::::iter_prefix((collection_id, owner.clone())).count(), + 2 + ); + assert!(!events().contains(&Event::::ApprovalsCancelled { + collection: collection_id, + item: None, + owner: owner.clone(), + })); + + // Partially removes collection approvals. + assert_ok!(Nfts::clear_collection_approvals( + RuntimeOrigin::signed(owner.clone()), + collection_id, + 1 + )); + assert_eq!(Balances::free_balance(&owner), balance - 1); + assert_eq!( + CollectionApprovals::::iter_prefix((collection_id, owner.clone())).count(), + 1 + ); + + // Successfully remove all collection approvals. + assert_ok!(Nfts::clear_collection_approvals( + RuntimeOrigin::signed(owner.clone()), + collection_id, + 2 + )); + assert!(events().contains(&Event::::ApprovalsCancelled { + collection: collection_id, + item: None, + owner: owner.clone(), + })); + assert_eq!(Balances::free_balance(&owner), balance); + assert!(CollectionApprovals::::iter_prefix((collection_id, owner)) + .count() + .is_zero()); + + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(delegate_1), collection_id, item_id, account(5)), + Error::::NoPermission + ); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(delegate_2), collection_id, item_id, account(5)), + Error::::NoPermission + ); + }); +} + +#[test] +fn force_clear_collection_approvals_work() { + new_test_ext().execute_with(|| { + let collection_id = 0; + let item_id = 42; + let owner = account(1); + let delegate_1 = account(3); + let delegate_2 = account(4); + let balance = 100; + + Balances::make_free_balance_be(&owner, balance); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + owner.clone(), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + collection_id, + item_id, + owner.clone(), + default_item_config() + )); + + assert_ok!(Nfts::approve_collection_transfer( + RuntimeOrigin::signed(owner.clone()), + collection_id, + delegate_1.clone(), + None + )); + assert_ok!(Nfts::approve_collection_transfer( + RuntimeOrigin::signed(owner.clone()), + collection_id, + delegate_2.clone(), + None + )); + // Remove zero collection approvals, no event emitted. + assert_ok!(Nfts::force_clear_collection_approvals( + RuntimeOrigin::root(), + owner.clone(), + collection_id, + 0 + )); + assert_eq!(Balances::free_balance(&owner), balance - 2); + assert_eq!( + CollectionApprovals::::iter_prefix((collection_id, owner.clone())).count(), + 2 + ); + assert!(!events().contains(&Event::::ApprovalsCancelled { + collection: collection_id, + item: None, + owner: owner.clone(), + })); + + // Partially removes collection approvals. + assert_ok!(Nfts::force_clear_collection_approvals( + RuntimeOrigin::root(), + owner.clone(), + collection_id, + 1 + )); + assert_eq!(Balances::free_balance(&owner), balance - 1); + assert_eq!( + CollectionApprovals::::iter_prefix((collection_id, owner.clone())).count(), + 1 + ); + + // Successfully remove all collection approvals. + assert_ok!(Nfts::force_clear_collection_approvals( + RuntimeOrigin::root(), + owner.clone(), + collection_id, + 2 + )); + assert!(events().contains(&Event::::ApprovalsCancelled { + collection: collection_id, + item: None, + owner: owner.clone(), + })); + assert_eq!(Balances::free_balance(&owner), balance); + assert!(CollectionApprovals::::iter_prefix((collection_id, owner)) + .count() + .is_zero()); + + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(delegate_1), collection_id, item_id, account(5)), + Error::::NoPermission + ); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(delegate_2), collection_id, item_id, account(5)), + Error::::NoPermission + ); + }); +} + +#[test] +fn collection_item_works() { + new_test_ext().execute_with(|| { + let collection_id = 0; + let user_id = account(1); + let total_items = 10; + + // No collection. + assert_eq!(Nfts::collection_items(collection_id), None); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id.clone(), + default_collection_config() + )); + + // Mint items and validate the total supply. + (0..total_items).into_iter().for_each(|i| { + assert_ok!(Nfts::force_mint( + RuntimeOrigin::root(), + collection_id, + i, + user_id.clone(), + ItemConfig::default() + )); + }); + assert_eq!(Nfts::collection_items(collection_id), Some(total_items)); + }); +} + #[test] fn max_supply_should_work() { new_test_ext().execute_with(|| { @@ -2524,6 +3438,7 @@ fn buy_item_should_work() { item_1, price_1 + 1, )); + assert_eq!(AccountBalance::::get(collection_id, &user_2), 1); // validate the new owner & balances let item = Item::::get(collection_id, item_1).unwrap(); @@ -2981,6 +3896,8 @@ fn claim_swap_should_work() { item_1, Some(price_with_direction.clone()), )); + assert_eq!(AccountBalance::::get(collection_id, &user_1), 2); + assert_eq!(AccountBalance::::get(collection_id, &user_2), 3); // validate the new owner let item = Item::::get(collection_id, item_1).unwrap(); @@ -3876,3 +4793,13 @@ fn clear_collection_metadata_works() { assert_eq!(Balances::reserved_balance(&account(1)), 10); }); } + +#[test] +fn ensure_collection_approval_bytes() { + let key = Blake2_128Concat::max_len::<::CollectionId>() + + Blake2_128Concat::max_len::() + + Blake2_128Concat::max_len::(); + let value = Option::>::max_encoded_len() + .saturating_add(BalanceOf::::max_encoded_len()); + assert_eq!(key + value, 133); +} diff --git a/pallets/nfts/src/weights.rs b/pallets/nfts/src/weights.rs index c5fb60a22..91faa0ae3 100644 --- a/pallets/nfts/src/weights.rs +++ b/pallets/nfts/src/weights.rs @@ -1,30 +1,14 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. //! Autogenerated weights for `pallet_nfts` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-04-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 40.0.0 +//! DATE: 2024-12-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `R0GUE`, CPU: `` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/substrate-node +// ./target/release/pop-node // benchmark // pallet // --chain=dev @@ -34,12 +18,11 @@ // --no-storage-info // --no-median-slopes // --no-min-squares -// --extrinsic=* // --wasm-execution=compiled // --heap-pages=4096 -// --output=./substrate/frame/nfts/src/weights.rs -// --header=./substrate/HEADER-APACHE2 -// --template=./substrate/.maintain/frame-weight-template.hbs +// --output=./pallets/nfts/src/weights.rs +// --template=./scripts/pallet-weights-template.hbs +// --extrinsic= #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -77,8 +60,14 @@ pub trait WeightInfo { fn set_collection_metadata() -> Weight; fn clear_collection_metadata() -> Weight; fn approve_transfer() -> Weight; + fn approve_collection_transfer() -> Weight; + fn force_approve_collection_transfer() -> Weight; fn cancel_approval() -> Weight; + fn cancel_collection_approval() -> Weight; + fn force_cancel_collection_approval() -> Weight; fn clear_all_transfer_approvals() -> Weight; + fn clear_collection_approvals(n: u32, ) -> Weight; + fn force_clear_collection_approvals(n: u32, ) -> Weight; fn set_accept_ownership() -> Weight; fn set_collection_max_supply() -> Weight; fn update_mint_settings() -> Weight; @@ -107,10 +96,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: - // Measured: `216` + // Measured: `105` // Estimated: `3549` - // Minimum execution time: 34_863_000 picoseconds. - Weight::from_parts(36_679_000, 3549) + // Minimum execution time: 29_000_000 picoseconds. + Weight::from_parts(37_000_000, 3549) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -126,15 +115,17 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn force_create() -> Weight { // Proof Size summary in bytes: - // Measured: `76` + // Measured: `3` // Estimated: `3549` - // Minimum execution time: 19_631_000 picoseconds. - Weight::from_parts(20_384_000, 3549) + // Minimum execution time: 15_000_000 picoseconds. + Weight::from_parts(15_000_000, 3549) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } /// Storage: `Nfts::Collection` (r:1 w:1) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionApprovals` (r:1 w:0) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemMetadataOf` (r:1 w:0) /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionRoleOf` (r:1 w:1) @@ -154,13 +145,13 @@ impl WeightInfo for SubstrateWeight { /// The range of component `a` is `[0, 1000]`. fn destroy(_m: u32, _c: u32, a: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `32204 + a * (366 ±0)` + // Measured: `32137 + a * (366 ±0)` // Estimated: `2523990 + a * (2954 ±0)` - // Minimum execution time: 1_282_083_000 picoseconds. - Weight::from_parts(1_249_191_963, 2523990) - // Standard Error: 4_719 - .saturating_add(Weight::from_parts(6_470_227, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(1004_u64)) + // Minimum execution time: 1_005_000_000 picoseconds. + Weight::from_parts(2_234_507_890, 2523990) + // Standard Error: 87_675 + .saturating_add(Weight::from_parts(5_794_302, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(1005_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) .saturating_add(T::DbWeight::get().writes(1005_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) @@ -174,18 +165,20 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) fn mint() -> Weight { // Proof Size summary in bytes: - // Measured: `455` + // Measured: `382` // Estimated: `4326` - // Minimum execution time: 49_055_000 picoseconds. - Weight::from_parts(50_592_000, 4326) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) + // Minimum execution time: 40_000_000 picoseconds. + Weight::from_parts(43_000_000, 4326) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) @@ -195,18 +188,20 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) fn force_mint() -> Weight { // Proof Size summary in bytes: - // Measured: `455` + // Measured: `382` // Estimated: `4326` - // Minimum execution time: 47_102_000 picoseconds. - Weight::from_parts(48_772_000, 4326) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) + // Minimum execution time: 38_000_000 picoseconds. + Weight::from_parts(40_000_000, 4326) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } /// Storage: `Nfts::Attribute` (r:1 w:0) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) @@ -218,22 +213,24 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemMetadataOf` (r:1 w:0) /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:0 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn burn() -> Weight { // Proof Size summary in bytes: - // Measured: `564` + // Measured: `584` // Estimated: `4326` - // Minimum execution time: 52_968_000 picoseconds. - Weight::from_parts(55_136_000, 4326) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(7_u64)) + // Minimum execution time: 46_000_000 picoseconds. + Weight::from_parts(47_000_000, 4326) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(8_u64)) } /// Storage: `Nfts::Collection` (r:1 w:0) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) @@ -245,6 +242,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Item` (r:1 w:1) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:2) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) @@ -253,12 +252,12 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `593` - // Estimated: `4326` - // Minimum execution time: 41_140_000 picoseconds. - Weight::from_parts(43_288_000, 4326) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(5_u64)) + // Measured: `613` + // Estimated: `6084` + // Minimum execution time: 39_000_000 picoseconds. + Weight::from_parts(45_000_000, 6084) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) } /// Storage: `Nfts::Collection` (r:1 w:0) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) @@ -269,12 +268,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `763 + i * (108 ±0)` + // Measured: `690 + i * (108 ±0)` // Estimated: `3549 + i * (3336 ±0)` - // Minimum execution time: 14_433_000 picoseconds. - Weight::from_parts(14_664_000, 3549) - // Standard Error: 23_078 - .saturating_add(Weight::from_parts(15_911_377, 0).saturating_mul(i.into())) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(11_000_000, 3549) + // Standard Error: 33_010 + .saturating_add(Weight::from_parts(17_115_197, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(i.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) @@ -286,10 +285,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn lock_item_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 18_307_000 picoseconds. - Weight::from_parts(18_966_000, 3534) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 3534) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -299,10 +298,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn unlock_item_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 18_078_000 picoseconds. - Weight::from_parts(18_593_000, 3534) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 3534) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -312,10 +311,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn lock_collection() -> Weight { // Proof Size summary in bytes: - // Measured: `340` + // Measured: `267` // Estimated: `3549` - // Minimum execution time: 15_175_000 picoseconds. - Weight::from_parts(15_762_000, 3549) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 3549) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -329,10 +328,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn transfer_ownership() -> Weight { // Proof Size summary in bytes: - // Measured: `562` + // Measured: `417` // Estimated: `3593` - // Minimum execution time: 26_164_000 picoseconds. - Weight::from_parts(27_117_000, 3593) + // Minimum execution time: 19_000_000 picoseconds. + Weight::from_parts(21_000_000, 3593) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -342,10 +341,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) fn set_team() -> Weight { // Proof Size summary in bytes: - // Measured: `369` + // Measured: `296` // Estimated: `6078` - // Minimum execution time: 38_523_000 picoseconds. - Weight::from_parts(39_486_000, 6078) + // Minimum execution time: 30_000_000 picoseconds. + Weight::from_parts(31_000_000, 6078) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -355,10 +354,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn force_collection_owner() -> Weight { // Proof Size summary in bytes: - // Measured: `311` + // Measured: `238` // Estimated: `3549` - // Minimum execution time: 15_733_000 picoseconds. - Weight::from_parts(16_227_000, 3549) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 3549) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -368,10 +367,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn force_collection_config() -> Weight { // Proof Size summary in bytes: - // Measured: `276` + // Measured: `203` // Estimated: `3549` - // Minimum execution time: 12_042_000 picoseconds. - Weight::from_parts(12_690_000, 3549) + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(10_000_000, 3549) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -381,10 +380,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn lock_item_properties() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 17_165_000 picoseconds. - Weight::from_parts(17_769_000, 3534) + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(14_000_000, 3534) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -400,10 +399,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) fn set_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `539` + // Measured: `499` // Estimated: `3944` - // Minimum execution time: 48_862_000 picoseconds. - Weight::from_parts(50_584_000, 3944) + // Minimum execution time: 38_000_000 picoseconds. + Weight::from_parts(39_000_000, 3944) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -413,10 +412,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) fn force_set_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `344` + // Measured: `271` // Estimated: `3944` - // Minimum execution time: 24_665_000 picoseconds. - Weight::from_parts(25_465_000, 3944) + // Minimum execution time: 18_000_000 picoseconds. + Weight::from_parts(20_000_000, 3944) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -430,30 +429,30 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) fn clear_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `983` + // Measured: `943` // Estimated: `3944` - // Minimum execution time: 44_617_000 picoseconds. - Weight::from_parts(46_458_000, 3944) + // Minimum execution time: 35_000_000 picoseconds. + Weight::from_parts(36_000_000, 3944) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) fn approve_item_attributes() -> Weight { // Proof Size summary in bytes: - // Measured: `381` - // Estimated: `4326` - // Minimum execution time: 15_710_000 picoseconds. - Weight::from_parts(16_191_000, 4326) + // Measured: `308` + // Estimated: `4466` + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 4466) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::Attribute` (r:1001 w:1000) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) @@ -461,12 +460,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[0, 1000]`. fn cancel_item_attributes_approval(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `831 + n * (398 ±0)` - // Estimated: `4326 + n * (2954 ±0)` - // Minimum execution time: 24_447_000 picoseconds. - Weight::from_parts(25_144_000, 4326) - // Standard Error: 4_872 - .saturating_add(Weight::from_parts(6_523_101, 0).saturating_mul(n.into())) + // Measured: `686 + n * (398 ±0)` + // Estimated: `4466 + n * (2954 ±0)` + // Minimum execution time: 19_000_000 picoseconds. + Weight::from_parts(20_000_000, 4466) + // Standard Error: 11_913 + .saturating_add(Weight::from_parts(5_265_987, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -485,10 +484,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) fn set_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `539` + // Measured: `499` // Estimated: `3812` - // Minimum execution time: 39_990_000 picoseconds. - Weight::from_parts(41_098_000, 3812) + // Minimum execution time: 32_000_000 picoseconds. + Weight::from_parts(38_000_000, 3812) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -502,10 +501,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn clear_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `849` + // Measured: `809` // Estimated: `3812` - // Minimum execution time: 38_030_000 picoseconds. - Weight::from_parts(39_842_000, 3812) + // Minimum execution time: 30_000_000 picoseconds. + Weight::from_parts(33_000_000, 3812) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -519,10 +518,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) fn set_collection_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `398` + // Measured: `325` // Estimated: `3759` - // Minimum execution time: 36_778_000 picoseconds. - Weight::from_parts(38_088_000, 3759) + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(30_000_000, 3759) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -536,10 +535,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) fn clear_collection_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `716` + // Measured: `643` // Estimated: `3759` - // Minimum execution time: 36_887_000 picoseconds. - Weight::from_parts(38_406_000, 3759) + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(30_000_000, 3759) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -549,43 +548,131 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn approve_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `410` + // Measured: `337` // Estimated: `4326` - // Minimum execution time: 18_734_000 picoseconds. - Weight::from_parts(19_267_000, 4326) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 4326) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } + /// Storage: `Nfts::AccountBalance` (r:1 w:0) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionApprovals` (r:1 w:1) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + fn approve_collection_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `450` + // Estimated: `3602` + // Minimum execution time: 20_000_000 picoseconds. + Weight::from_parts(21_000_000, 3602) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::AccountBalance` (r:1 w:0) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionApprovals` (r:1 w:1) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + fn force_approve_collection_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `450` + // Estimated: `3602` + // Minimum execution time: 20_000_000 picoseconds. + Weight::from_parts(21_000_000, 3602) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } /// Storage: `Nfts::Item` (r:1 w:1) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionApprovals` (r:1 w:0) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) fn cancel_approval() -> Weight { // Proof Size summary in bytes: - // Measured: `418` + // Measured: `345` // Estimated: `4326` - // Minimum execution time: 16_080_000 picoseconds. - Weight::from_parts(16_603_000, 4326) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 4326) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::CollectionApprovals` (r:1 w:1) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + fn cancel_collection_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `359` + // Estimated: `3602` + // Minimum execution time: 16_000_000 picoseconds. + Weight::from_parts(17_000_000, 3602) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::CollectionApprovals` (r:1 w:1) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + fn force_cancel_collection_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `359` + // Estimated: `3602` + // Minimum execution time: 17_000_000 picoseconds. + Weight::from_parts(17_000_000, 3602) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Nfts::Item` (r:1 w:1) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionApprovals` (r:1 w:0) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) fn clear_all_transfer_approvals() -> Weight { // Proof Size summary in bytes: - // Measured: `418` + // Measured: `345` // Estimated: `4326` - // Minimum execution time: 15_013_000 picoseconds. - Weight::from_parts(15_607_000, 4326) - .saturating_add(T::DbWeight::get().reads(1_u64)) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(12_000_000, 4326) + .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } + /// Storage: `Nfts::CollectionApprovals` (r:21 w:20) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// The range of component `n` is `[1, 20]`. + fn clear_collection_approvals(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `348 + n * (75 ±0)` + // Estimated: `3602 + n * (2612 ±0)` + // Minimum execution time: 18_000_000 picoseconds. + Weight::from_parts(16_365_393, 3602) + // Standard Error: 5_564 + .saturating_add(Weight::from_parts(3_008_044, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2612).saturating_mul(n.into())) + } + /// Storage: `Nfts::CollectionApprovals` (r:21 w:20) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// The range of component `n` is `[1, 20]`. + fn force_clear_collection_approvals(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `348 + n * (75 ±0)` + // Estimated: `3602 + n * (2612 ±0)` + // Minimum execution time: 18_000_000 picoseconds. + Weight::from_parts(16_193_192, 3602) + // Standard Error: 5_637 + .saturating_add(Weight::from_parts(3_029_821, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2612).saturating_mul(n.into())) + } /// Storage: `Nfts::OwnershipAcceptance` (r:1 w:1) /// Proof: `Nfts::OwnershipAcceptance` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) fn set_accept_ownership() -> Weight { // Proof Size summary in bytes: - // Measured: `76` + // Measured: `3` // Estimated: `3517` - // Minimum execution time: 13_077_000 picoseconds. - Weight::from_parts(13_635_000, 3517) + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(10_000_000, 3517) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -595,10 +682,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) fn set_collection_max_supply() -> Weight { // Proof Size summary in bytes: - // Measured: `340` + // Measured: `267` // Estimated: `3549` - // Minimum execution time: 17_146_000 picoseconds. - Weight::from_parts(17_453_000, 3549) + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(13_000_000, 3549) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -608,10 +695,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn update_mint_settings() -> Weight { // Proof Size summary in bytes: - // Measured: `323` + // Measured: `250` // Estimated: `3538` - // Minimum execution time: 16_102_000 picoseconds. - Weight::from_parts(16_629_000, 3538) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(13_000_000, 3538) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -625,10 +712,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) fn set_price() -> Weight { // Proof Size summary in bytes: - // Measured: `518` + // Measured: `478` // Estimated: `4326` - // Minimum execution time: 22_118_000 picoseconds. - Weight::from_parts(22_849_000, 4326) + // Minimum execution time: 17_000_000 picoseconds. + Weight::from_parts(18_000_000, 4326) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -644,28 +731,30 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:2) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn buy_item() -> Weight { // Proof Size summary in bytes: - // Measured: `705` - // Estimated: `4326` - // Minimum execution time: 50_369_000 picoseconds. - Weight::from_parts(51_816_000, 4326) - .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(5_u64)) + // Measured: `725` + // Estimated: `6084` + // Minimum execution time: 46_000_000 picoseconds. + Weight::from_parts(48_000_000, 6084) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) } /// The range of component `n` is `[0, 10]`. fn pay_tips(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_203_000 picoseconds. - Weight::from_parts(3_710_869, 0) - // Standard Error: 8_094 - .saturating_add(Weight::from_parts(2_201_869, 0).saturating_mul(n.into())) + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(1_971_321, 0) + // Standard Error: 11_267 + .saturating_add(Weight::from_parts(1_831_565, 0).saturating_mul(n.into())) } /// Storage: `Nfts::Item` (r:2 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) @@ -673,10 +762,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn create_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `494` + // Measured: `421` // Estimated: `7662` - // Minimum execution time: 18_893_000 picoseconds. - Weight::from_parts(19_506_000, 7662) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 7662) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -686,10 +775,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn cancel_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `513` + // Measured: `440` // Estimated: `4326` - // Minimum execution time: 19_086_000 picoseconds. - Weight::from_parts(19_609_000, 4326) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(14_000_000, 4326) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -705,18 +794,20 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:2 w:0) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:4) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:2) /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) fn claim_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `834` + // Measured: `916` // Estimated: `7662` - // Minimum execution time: 84_103_000 picoseconds. - Weight::from_parts(85_325_000, 7662) - .saturating_add(T::DbWeight::get().reads(9_u64)) - .saturating_add(T::DbWeight::get().writes(10_u64)) + // Minimum execution time: 81_000_000 picoseconds. + Weight::from_parts(87_000_000, 7662) + .saturating_add(T::DbWeight::get().reads(11_u64)) + .saturating_add(T::DbWeight::get().writes(12_u64)) } /// Storage: `Nfts::CollectionRoleOf` (r:2 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) @@ -726,6 +817,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::Collection` (r:1 w:1) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) @@ -739,22 +832,22 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[0, 10]`. fn mint_pre_signed(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `629` + // Measured: `485` // Estimated: `6078 + n * (2954 ±0)` - // Minimum execution time: 128_363_000 picoseconds. - Weight::from_parts(139_474_918, 6078) - // Standard Error: 79_252 - .saturating_add(Weight::from_parts(31_384_027, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(8_u64)) + // Minimum execution time: 102_000_000 picoseconds. + Weight::from_parts(115_875_572, 6078) + // Standard Error: 214_394 + .saturating_add(Weight::from_parts(29_706_731, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(9_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(6_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::Collection` (r:1 w:1) @@ -766,12 +859,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[0, 10]`. fn set_attributes_pre_signed(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `659` - // Estimated: `4326 + n * (2954 ±0)` - // Minimum execution time: 66_688_000 picoseconds. - Weight::from_parts(79_208_379, 4326) - // Standard Error: 74_020 - .saturating_add(Weight::from_parts(31_028_221, 0).saturating_mul(n.into())) + // Measured: `514` + // Estimated: `4466 + n * (2954 ±0)` + // Minimum execution time: 51_000_000 picoseconds. + Weight::from_parts(66_554_620, 4466) + // Standard Error: 115_497 + .saturating_add(Weight::from_parts(27_385_703, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -794,10 +887,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: - // Measured: `216` + // Measured: `105` // Estimated: `3549` - // Minimum execution time: 34_863_000 picoseconds. - Weight::from_parts(36_679_000, 3549) + // Minimum execution time: 29_000_000 picoseconds. + Weight::from_parts(37_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -813,15 +906,17 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn force_create() -> Weight { // Proof Size summary in bytes: - // Measured: `76` + // Measured: `3` // Estimated: `3549` - // Minimum execution time: 19_631_000 picoseconds. - Weight::from_parts(20_384_000, 3549) + // Minimum execution time: 15_000_000 picoseconds. + Weight::from_parts(15_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// Storage: `Nfts::Collection` (r:1 w:1) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionApprovals` (r:1 w:0) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemMetadataOf` (r:1 w:0) /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionRoleOf` (r:1 w:1) @@ -841,13 +936,13 @@ impl WeightInfo for () { /// The range of component `a` is `[0, 1000]`. fn destroy(_m: u32, _c: u32, a: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `32204 + a * (366 ±0)` + // Measured: `32137 + a * (366 ±0)` // Estimated: `2523990 + a * (2954 ±0)` - // Minimum execution time: 1_282_083_000 picoseconds. - Weight::from_parts(1_249_191_963, 2523990) - // Standard Error: 4_719 - .saturating_add(Weight::from_parts(6_470_227, 0).saturating_mul(a.into())) - .saturating_add(RocksDbWeight::get().reads(1004_u64)) + // Minimum execution time: 1_005_000_000 picoseconds. + Weight::from_parts(2_234_507_890, 2523990) + // Standard Error: 87_675 + .saturating_add(Weight::from_parts(5_794_302, 0).saturating_mul(a.into())) + .saturating_add(RocksDbWeight::get().reads(1005_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(a.into()))) .saturating_add(RocksDbWeight::get().writes(1005_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(a.into()))) @@ -861,18 +956,20 @@ impl WeightInfo for () { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) fn mint() -> Weight { // Proof Size summary in bytes: - // Measured: `455` + // Measured: `382` // Estimated: `4326` - // Minimum execution time: 49_055_000 picoseconds. - Weight::from_parts(50_592_000, 4326) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) + // Minimum execution time: 40_000_000 picoseconds. + Weight::from_parts(43_000_000, 4326) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// Storage: `Nfts::CollectionRoleOf` (r:1 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) @@ -882,18 +979,20 @@ impl WeightInfo for () { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) fn force_mint() -> Weight { // Proof Size summary in bytes: - // Measured: `455` + // Measured: `382` // Estimated: `4326` - // Minimum execution time: 47_102_000 picoseconds. - Weight::from_parts(48_772_000, 4326) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) + // Minimum execution time: 38_000_000 picoseconds. + Weight::from_parts(40_000_000, 4326) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// Storage: `Nfts::Attribute` (r:1 w:0) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) @@ -905,22 +1004,24 @@ impl WeightInfo for () { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemMetadataOf` (r:1 w:0) /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:1) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:0 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn burn() -> Weight { // Proof Size summary in bytes: - // Measured: `564` + // Measured: `584` // Estimated: `4326` - // Minimum execution time: 52_968_000 picoseconds. - Weight::from_parts(55_136_000, 4326) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(7_u64)) + // Minimum execution time: 46_000_000 picoseconds. + Weight::from_parts(47_000_000, 4326) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(8_u64)) } /// Storage: `Nfts::Collection` (r:1 w:0) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) @@ -932,6 +1033,8 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `Nfts::Item` (r:1 w:1) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:2) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:1) @@ -940,12 +1043,12 @@ impl WeightInfo for () { /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `593` - // Estimated: `4326` - // Minimum execution time: 41_140_000 picoseconds. - Weight::from_parts(43_288_000, 4326) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(5_u64)) + // Measured: `613` + // Estimated: `6084` + // Minimum execution time: 39_000_000 picoseconds. + Weight::from_parts(45_000_000, 6084) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) } /// Storage: `Nfts::Collection` (r:1 w:0) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) @@ -956,12 +1059,12 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `763 + i * (108 ±0)` + // Measured: `690 + i * (108 ±0)` // Estimated: `3549 + i * (3336 ±0)` - // Minimum execution time: 14_433_000 picoseconds. - Weight::from_parts(14_664_000, 3549) - // Standard Error: 23_078 - .saturating_add(Weight::from_parts(15_911_377, 0).saturating_mul(i.into())) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(11_000_000, 3549) + // Standard Error: 33_010 + .saturating_add(Weight::from_parts(17_115_197, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(i.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(i.into()))) @@ -973,10 +1076,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn lock_item_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 18_307_000 picoseconds. - Weight::from_parts(18_966_000, 3534) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 3534) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -986,10 +1089,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn unlock_item_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 18_078_000 picoseconds. - Weight::from_parts(18_593_000, 3534) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 3534) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -999,10 +1102,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn lock_collection() -> Weight { // Proof Size summary in bytes: - // Measured: `340` + // Measured: `267` // Estimated: `3549` - // Minimum execution time: 15_175_000 picoseconds. - Weight::from_parts(15_762_000, 3549) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1016,10 +1119,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn transfer_ownership() -> Weight { // Proof Size summary in bytes: - // Measured: `562` + // Measured: `417` // Estimated: `3593` - // Minimum execution time: 26_164_000 picoseconds. - Weight::from_parts(27_117_000, 3593) + // Minimum execution time: 19_000_000 picoseconds. + Weight::from_parts(21_000_000, 3593) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -1029,10 +1132,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) fn set_team() -> Weight { // Proof Size summary in bytes: - // Measured: `369` + // Measured: `296` // Estimated: `6078` - // Minimum execution time: 38_523_000 picoseconds. - Weight::from_parts(39_486_000, 6078) + // Minimum execution time: 30_000_000 picoseconds. + Weight::from_parts(31_000_000, 6078) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -1042,10 +1145,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionAccount` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) fn force_collection_owner() -> Weight { // Proof Size summary in bytes: - // Measured: `311` + // Measured: `238` // Estimated: `3549` - // Minimum execution time: 15_733_000 picoseconds. - Weight::from_parts(16_227_000, 3549) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1055,10 +1158,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn force_collection_config() -> Weight { // Proof Size summary in bytes: - // Measured: `276` + // Measured: `203` // Estimated: `3549` - // Minimum execution time: 12_042_000 picoseconds. - Weight::from_parts(12_690_000, 3549) + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(10_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1068,10 +1171,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn lock_item_properties() -> Weight { // Proof Size summary in bytes: - // Measured: `435` + // Measured: `395` // Estimated: `3534` - // Minimum execution time: 17_165_000 picoseconds. - Weight::from_parts(17_769_000, 3534) + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(14_000_000, 3534) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1087,10 +1190,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) fn set_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `539` + // Measured: `499` // Estimated: `3944` - // Minimum execution time: 48_862_000 picoseconds. - Weight::from_parts(50_584_000, 3944) + // Minimum execution time: 38_000_000 picoseconds. + Weight::from_parts(39_000_000, 3944) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1100,10 +1203,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) fn force_set_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `344` + // Measured: `271` // Estimated: `3944` - // Minimum execution time: 24_665_000 picoseconds. - Weight::from_parts(25_465_000, 3944) + // Minimum execution time: 18_000_000 picoseconds. + Weight::from_parts(20_000_000, 3944) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1117,30 +1220,30 @@ impl WeightInfo for () { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) fn clear_attribute() -> Weight { // Proof Size summary in bytes: - // Measured: `983` + // Measured: `943` // Estimated: `3944` - // Minimum execution time: 44_617_000 picoseconds. - Weight::from_parts(46_458_000, 3944) + // Minimum execution time: 35_000_000 picoseconds. + Weight::from_parts(36_000_000, 3944) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) fn approve_item_attributes() -> Weight { // Proof Size summary in bytes: - // Measured: `381` - // Estimated: `4326` - // Minimum execution time: 15_710_000 picoseconds. - Weight::from_parts(16_191_000, 4326) + // Measured: `308` + // Estimated: `4466` + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 4466) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::Attribute` (r:1001 w:1000) /// Proof: `Nfts::Attribute` (`max_values`: None, `max_size`: Some(479), added: 2954, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) @@ -1148,12 +1251,12 @@ impl WeightInfo for () { /// The range of component `n` is `[0, 1000]`. fn cancel_item_attributes_approval(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `831 + n * (398 ±0)` - // Estimated: `4326 + n * (2954 ±0)` - // Minimum execution time: 24_447_000 picoseconds. - Weight::from_parts(25_144_000, 4326) - // Standard Error: 4_872 - .saturating_add(Weight::from_parts(6_523_101, 0).saturating_mul(n.into())) + // Measured: `686 + n * (398 ±0)` + // Estimated: `4466 + n * (2954 ±0)` + // Minimum execution time: 19_000_000 picoseconds. + Weight::from_parts(20_000_000, 4466) + // Standard Error: 11_913 + .saturating_add(Weight::from_parts(5_265_987, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -1172,10 +1275,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemMetadataOf` (`max_values`: None, `max_size`: Some(347), added: 2822, mode: `MaxEncodedLen`) fn set_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `539` + // Measured: `499` // Estimated: `3812` - // Minimum execution time: 39_990_000 picoseconds. - Weight::from_parts(41_098_000, 3812) + // Minimum execution time: 32_000_000 picoseconds. + Weight::from_parts(38_000_000, 3812) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1189,10 +1292,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn clear_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `849` + // Measured: `809` // Estimated: `3812` - // Minimum execution time: 38_030_000 picoseconds. - Weight::from_parts(39_842_000, 3812) + // Minimum execution time: 30_000_000 picoseconds. + Weight::from_parts(33_000_000, 3812) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1206,10 +1309,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) fn set_collection_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `398` + // Measured: `325` // Estimated: `3759` - // Minimum execution time: 36_778_000 picoseconds. - Weight::from_parts(38_088_000, 3759) + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(30_000_000, 3759) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1223,10 +1326,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionMetadataOf` (`max_values`: None, `max_size`: Some(294), added: 2769, mode: `MaxEncodedLen`) fn clear_collection_metadata() -> Weight { // Proof Size summary in bytes: - // Measured: `716` + // Measured: `643` // Estimated: `3759` - // Minimum execution time: 36_887_000 picoseconds. - Weight::from_parts(38_406_000, 3759) + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(30_000_000, 3759) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1236,43 +1339,131 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn approve_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `410` + // Measured: `337` // Estimated: `4326` - // Minimum execution time: 18_734_000 picoseconds. - Weight::from_parts(19_267_000, 4326) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 4326) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// Storage: `Nfts::AccountBalance` (r:1 w:0) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionApprovals` (r:1 w:1) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + fn approve_collection_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `450` + // Estimated: `3602` + // Minimum execution time: 20_000_000 picoseconds. + Weight::from_parts(21_000_000, 3602) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::AccountBalance` (r:1 w:0) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) + /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionApprovals` (r:1 w:1) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + fn force_approve_collection_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `450` + // Estimated: `3602` + // Minimum execution time: 20_000_000 picoseconds. + Weight::from_parts(21_000_000, 3602) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } /// Storage: `Nfts::Item` (r:1 w:1) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionApprovals` (r:1 w:0) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) fn cancel_approval() -> Weight { // Proof Size summary in bytes: - // Measured: `418` + // Measured: `345` // Estimated: `4326` - // Minimum execution time: 16_080_000 picoseconds. - Weight::from_parts(16_603_000, 4326) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(12_000_000, 4326) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::CollectionApprovals` (r:1 w:1) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + fn cancel_collection_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `359` + // Estimated: `3602` + // Minimum execution time: 16_000_000 picoseconds. + Weight::from_parts(17_000_000, 3602) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Nfts::CollectionApprovals` (r:1 w:1) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + fn force_cancel_collection_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `359` + // Estimated: `3602` + // Minimum execution time: 17_000_000 picoseconds. + Weight::from_parts(17_000_000, 3602) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Nfts::Item` (r:1 w:1) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) + /// Storage: `Nfts::CollectionApprovals` (r:1 w:0) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) fn clear_all_transfer_approvals() -> Weight { // Proof Size summary in bytes: - // Measured: `418` + // Measured: `345` // Estimated: `4326` - // Minimum execution time: 15_013_000 picoseconds. - Weight::from_parts(15_607_000, 4326) - .saturating_add(RocksDbWeight::get().reads(1_u64)) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(12_000_000, 4326) + .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// Storage: `Nfts::CollectionApprovals` (r:21 w:20) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// The range of component `n` is `[1, 20]`. + fn clear_collection_approvals(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `348 + n * (75 ±0)` + // Estimated: `3602 + n * (2612 ±0)` + // Minimum execution time: 18_000_000 picoseconds. + Weight::from_parts(16_365_393, 3602) + // Standard Error: 5_564 + .saturating_add(Weight::from_parts(3_008_044, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2612).saturating_mul(n.into())) + } + /// Storage: `Nfts::CollectionApprovals` (r:21 w:20) + /// Proof: `Nfts::CollectionApprovals` (`max_values`: None, `max_size`: Some(137), added: 2612, mode: `MaxEncodedLen`) + /// The range of component `n` is `[1, 20]`. + fn force_clear_collection_approvals(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `348 + n * (75 ±0)` + // Estimated: `3602 + n * (2612 ±0)` + // Minimum execution time: 18_000_000 picoseconds. + Weight::from_parts(16_193_192, 3602) + // Standard Error: 5_637 + .saturating_add(Weight::from_parts(3_029_821, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2612).saturating_mul(n.into())) + } /// Storage: `Nfts::OwnershipAcceptance` (r:1 w:1) /// Proof: `Nfts::OwnershipAcceptance` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) fn set_accept_ownership() -> Weight { // Proof Size summary in bytes: - // Measured: `76` + // Measured: `3` // Estimated: `3517` - // Minimum execution time: 13_077_000 picoseconds. - Weight::from_parts(13_635_000, 3517) + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(10_000_000, 3517) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1282,10 +1473,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) fn set_collection_max_supply() -> Weight { // Proof Size summary in bytes: - // Measured: `340` + // Measured: `267` // Estimated: `3549` - // Minimum execution time: 17_146_000 picoseconds. - Weight::from_parts(17_453_000, 3549) + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(13_000_000, 3549) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1295,10 +1486,10 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn update_mint_settings() -> Weight { // Proof Size summary in bytes: - // Measured: `323` + // Measured: `250` // Estimated: `3538` - // Minimum execution time: 16_102_000 picoseconds. - Weight::from_parts(16_629_000, 3538) + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(13_000_000, 3538) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1312,10 +1503,10 @@ impl WeightInfo for () { /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) fn set_price() -> Weight { // Proof Size summary in bytes: - // Measured: `518` + // Measured: `478` // Estimated: `4326` - // Minimum execution time: 22_118_000 picoseconds. - Weight::from_parts(22_849_000, 4326) + // Minimum execution time: 17_000_000 picoseconds. + Weight::from_parts(18_000_000, 4326) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1331,28 +1522,30 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:0) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:2) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::PendingSwapOf` (r:0 w:1) /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn buy_item() -> Weight { // Proof Size summary in bytes: - // Measured: `705` - // Estimated: `4326` - // Minimum execution time: 50_369_000 picoseconds. - Weight::from_parts(51_816_000, 4326) - .saturating_add(RocksDbWeight::get().reads(6_u64)) - .saturating_add(RocksDbWeight::get().writes(5_u64)) + // Measured: `725` + // Estimated: `6084` + // Minimum execution time: 46_000_000 picoseconds. + Weight::from_parts(48_000_000, 6084) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) } /// The range of component `n` is `[0, 10]`. fn pay_tips(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_203_000 picoseconds. - Weight::from_parts(3_710_869, 0) - // Standard Error: 8_094 - .saturating_add(Weight::from_parts(2_201_869, 0).saturating_mul(n.into())) + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(1_971_321, 0) + // Standard Error: 11_267 + .saturating_add(Weight::from_parts(1_831_565, 0).saturating_mul(n.into())) } /// Storage: `Nfts::Item` (r:2 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) @@ -1360,10 +1553,10 @@ impl WeightInfo for () { /// Proof: `Nfts::PendingSwapOf` (`max_values`: None, `max_size`: Some(71), added: 2546, mode: `MaxEncodedLen`) fn create_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `494` + // Measured: `421` // Estimated: `7662` - // Minimum execution time: 18_893_000 picoseconds. - Weight::from_parts(19_506_000, 7662) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 7662) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1373,10 +1566,10 @@ impl WeightInfo for () { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) fn cancel_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `513` + // Measured: `440` // Estimated: `4326` - // Minimum execution time: 19_086_000 picoseconds. - Weight::from_parts(19_609_000, 4326) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(14_000_000, 4326) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1392,18 +1585,20 @@ impl WeightInfo for () { /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:2 w:0) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:2 w:2) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Nfts::Account` (r:0 w:4) /// Proof: `Nfts::Account` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemPriceOf` (r:0 w:2) /// Proof: `Nfts::ItemPriceOf` (`max_values`: None, `max_size`: Some(89), added: 2564, mode: `MaxEncodedLen`) fn claim_swap() -> Weight { // Proof Size summary in bytes: - // Measured: `834` + // Measured: `916` // Estimated: `7662` - // Minimum execution time: 84_103_000 picoseconds. - Weight::from_parts(85_325_000, 7662) - .saturating_add(RocksDbWeight::get().reads(9_u64)) - .saturating_add(RocksDbWeight::get().writes(10_u64)) + // Minimum execution time: 81_000_000 picoseconds. + Weight::from_parts(87_000_000, 7662) + .saturating_add(RocksDbWeight::get().reads(11_u64)) + .saturating_add(RocksDbWeight::get().writes(12_u64)) } /// Storage: `Nfts::CollectionRoleOf` (r:2 w:0) /// Proof: `Nfts::CollectionRoleOf` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) @@ -1413,6 +1608,8 @@ impl WeightInfo for () { /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::Collection` (r:1 w:1) /// Proof: `Nfts::Collection` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `Nfts::AccountBalance` (r:1 w:1) + /// Proof: `Nfts::AccountBalance` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemConfigOf` (r:1 w:1) /// Proof: `Nfts::ItemConfigOf` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) @@ -1426,22 +1623,22 @@ impl WeightInfo for () { /// The range of component `n` is `[0, 10]`. fn mint_pre_signed(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `629` + // Measured: `485` // Estimated: `6078 + n * (2954 ±0)` - // Minimum execution time: 128_363_000 picoseconds. - Weight::from_parts(139_474_918, 6078) - // Standard Error: 79_252 - .saturating_add(Weight::from_parts(31_384_027, 0).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(8_u64)) + // Minimum execution time: 102_000_000 picoseconds. + Weight::from_parts(115_875_572, 6078) + // Standard Error: 214_394 + .saturating_add(Weight::from_parts(29_706_731, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(9_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(6_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 2954).saturating_mul(n.into())) } /// Storage: `Nfts::Item` (r:1 w:0) /// Proof: `Nfts::Item` (`max_values`: None, `max_size`: Some(861), added: 3336, mode: `MaxEncodedLen`) /// Storage: `Nfts::ItemAttributesApprovalsOf` (r:1 w:1) - /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(681), added: 3156, mode: `MaxEncodedLen`) + /// Proof: `Nfts::ItemAttributesApprovalsOf` (`max_values`: None, `max_size`: Some(1001), added: 3476, mode: `MaxEncodedLen`) /// Storage: `Nfts::CollectionConfigOf` (r:1 w:0) /// Proof: `Nfts::CollectionConfigOf` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// Storage: `Nfts::Collection` (r:1 w:1) @@ -1453,12 +1650,12 @@ impl WeightInfo for () { /// The range of component `n` is `[0, 10]`. fn set_attributes_pre_signed(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `659` - // Estimated: `4326 + n * (2954 ±0)` - // Minimum execution time: 66_688_000 picoseconds. - Weight::from_parts(79_208_379, 4326) - // Standard Error: 74_020 - .saturating_add(Weight::from_parts(31_028_221, 0).saturating_mul(n.into())) + // Measured: `514` + // Estimated: `4466 + n * (2954 ±0)` + // Minimum execution time: 51_000_000 picoseconds. + Weight::from_parts(66_554_620, 4466) + // Standard Error: 115_497 + .saturating_add(Weight::from_parts(27_385_703, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml index 395abd246..428356bf8 100644 --- a/runtime/common/Cargo.toml +++ b/runtime/common/Cargo.toml @@ -13,6 +13,7 @@ targets = [ "x86_64-unknown-linux-gnu" ] [dependencies] codec = { workspace = true, default-features = false, features = [ "derive" ] } +log = { workspace = true, default-features = false } scale-info = { workspace = true, default-features = false, features = [ "derive" ] } # Substrate @@ -22,6 +23,9 @@ sp-std = { workspace = true, default-features = false } parachains-common = { workspace = true, default-features = false } polkadot-primitives = { workspace = true, default-features = false } +xcm = { workspace = true, default-features = false } +xcm-builder = { workspace = true, default-features = false } +xcm-executor = { workspace = true, default-features = false } [features] default = [ "std" ] @@ -29,4 +33,11 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] -std = [ "frame-support/std", "sp-runtime/std" ] +std = [ + "frame-support/std", + "log/std", + "sp-runtime/std", + "xcm-builder/std", + "xcm-executor/std", + "xcm/std", +] diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index f20ea377e..a26d13ae9 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -6,6 +6,8 @@ pub use parachains_common::{AccountId, AuraId, Balance, Block, BlockNumber, Hash pub use polkadot_primitives::MAX_POV_SIZE; use sp_runtime::Perbill; +pub mod xcm; + /// Nonce for an account pub type Nonce = u32; diff --git a/runtime/common/src/xcm/mod.rs b/runtime/common/src/xcm/mod.rs new file mode 100644 index 000000000..af9f66c42 --- /dev/null +++ b/runtime/common/src/xcm/mod.rs @@ -0,0 +1 @@ +pub mod nonfungibles_adapter; diff --git a/runtime/common/src/xcm/nonfungibles_adapter.rs b/runtime/common/src/xcm/nonfungibles_adapter.rs new file mode 100644 index 000000000..e7f5512aa --- /dev/null +++ b/runtime/common/src/xcm/nonfungibles_adapter.rs @@ -0,0 +1,445 @@ +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{ + ensure, + traits::{tokens::nonfungibles_v2, Get, Incrementable}, +}; +use scale_info::TypeInfo; +use sp_std::{marker::PhantomData, prelude::*}; +use xcm::latest::prelude::*; +use xcm_builder::{AssetChecking, MintLocation}; +use xcm_executor::{ + traits::{ConvertLocation, Error as MatchError, MatchesNonFungibles, TransactAsset}, + AssetsInHolding, +}; + +#[derive(Clone, Decode, Encode, Eq, PartialEq, Ord, PartialOrd, Debug, TypeInfo, MaxEncodedLen)] +/// Represents a collection ID based on a MultiLocation. +/// +/// This structure provides a way to map a MultiLocation to a collection ID, +/// which is useful for describing collections that do not follow an incremental pattern. +pub struct MultiLocationCollectionId(pub Location); + +impl MultiLocationCollectionId { + /// Consume `self` and return the inner MultiLocation. + pub fn into_inner(self) -> Location { + self.0 + } + + /// Return a reference to the inner MultiLocation. + pub fn inner(&self) -> &Location { + &self.0 + } +} + +impl From for MultiLocationCollectionId { + fn from(_: u16) -> Self { + unimplemented!("Not implemented. Requires for becnhmarking Helper config.") + } +} + +impl Incrementable for MultiLocationCollectionId { + fn increment(&self) -> Option { + None + } + + fn initial_value() -> Option { + None + } +} + +impl From for MultiLocationCollectionId { + fn from(value: Location) -> Self { + MultiLocationCollectionId(value) + } +} + +impl From for Location { + fn from(value: MultiLocationCollectionId) -> Location { + value.into_inner() + } +} + +const LOG_TARGET: &str = "xcm::nonfungibles_adapter_pop"; +/// Adapter for transferring non-fungible tokens (NFTs) using [`nonfungibles_v2`]. +/// +/// This adapter facilitates the transfer of NFTs between different locations. +pub struct NonFungiblesTransferAdapterPop( + PhantomData<(Assets, Matcher, AccountIdConverter, AccountId)>, +); +impl< + Assets: nonfungibles_v2::Transfer, + Matcher: MatchesNonFungibles, + AccountIdConverter: ConvertLocation, + AccountId: Clone, // can't get away without it since Currency is generic over it. + > TransactAsset + for NonFungiblesTransferAdapterPop +{ + fn transfer_asset( + what: &Asset, + from: &Location, + to: &Location, + context: &XcmContext, + ) -> Result { + log::trace!( + target: LOG_TARGET, + "transfer_asset what: {:?}, from: {:?}, to: {:?}, context: {:?}", + what, + from, + to, + context, + ); + // Check we handle this asset. + let (class, instance) = Matcher::matches_nonfungibles(what)?; + let destination = AccountIdConverter::convert_location(to) + .ok_or(MatchError::AccountIdConversionFailed)?; + Assets::transfer(&class, &instance, &destination) + .map_err(|e| XcmError::FailedToTransactAsset(e.into()))?; + Ok(what.clone().into()) + } +} + +/// Adapter for mutating non-fungible tokens (NFTs) using [`nonfungibles_v2`]. +/// +/// This adapter provides functions to withdraw, deposit, check in and check out non fungibles. +pub struct NonFungiblesMutateAdapterPop< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + ItemConfig, +>( + PhantomData<( + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + ItemConfig, + )>, +) +where + ItemConfig: Default; + +impl< + Assets: nonfungibles_v2::Mutate, + Matcher: MatchesNonFungibles, + AccountIdConverter: ConvertLocation, + AccountId: Clone + Eq, // can't get away without it since Currency is generic over it. + CheckAsset: AssetChecking, + CheckingAccount: Get>, + ItemConfig: Default, + > + NonFungiblesMutateAdapterPop< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + ItemConfig, + > +{ + fn can_accrue_checked(class: Assets::CollectionId, instance: Assets::ItemId) -> XcmResult { + ensure!(Assets::owner(&class, &instance).is_none(), XcmError::NotDepositable); + Ok(()) + } + + fn can_reduce_checked(class: Assets::CollectionId, instance: Assets::ItemId) -> XcmResult { + if let Some(checking_account) = CheckingAccount::get() { + // This is an asset whose teleports we track. + let owner = Assets::owner(&class, &instance); + ensure!(owner == Some(checking_account), XcmError::NotWithdrawable); + ensure!(Assets::can_transfer(&class, &instance), XcmError::NotWithdrawable); + } + Ok(()) + } + + fn accrue_checked(class: Assets::CollectionId, instance: Assets::ItemId) { + if let Some(checking_account) = CheckingAccount::get() { + let ok = Assets::mint_into( + &class, + &instance, + &checking_account, + &ItemConfig::default(), + true, + ) + .is_ok(); + debug_assert!(ok, "`mint_into` cannot generally fail; qed"); + } + } + + fn reduce_checked(class: Assets::CollectionId, instance: Assets::ItemId) { + let ok = Assets::burn(&class, &instance, None).is_ok(); + debug_assert!(ok, "`can_check_in` must have returned `true` immediately prior; qed"); + } +} + +impl< + Assets: nonfungibles_v2::Mutate, + Matcher: MatchesNonFungibles, + AccountIdConverter: ConvertLocation, + AccountId: Clone + Eq, // can't get away without it since Currency is generic over it. + CheckAsset: AssetChecking, + CheckingAccount: Get>, + ItemConfig: Default, + > TransactAsset + for NonFungiblesMutateAdapterPop< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + ItemConfig, + > +{ + fn can_check_in(_origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult { + log::trace!( + target: LOG_TARGET, + "can_check_in origin: {:?}, what: {:?}, context: {:?}", + _origin, + what, + context, + ); + // Check we handle this asset. + let (class, instance) = Matcher::matches_nonfungibles(what)?; + match CheckAsset::asset_checking(&class) { + // We track this asset's teleports to ensure no more come in than have gone out. + Some(MintLocation::Local) => Self::can_reduce_checked(class, instance), + // We track this asset's teleports to ensure no more go out than have come in. + Some(MintLocation::NonLocal) => Self::can_accrue_checked(class, instance), + _ => Ok(()), + } + } + + fn check_in(_origin: &Location, what: &Asset, context: &XcmContext) { + log::trace!( + target: LOG_TARGET, + "check_in origin: {:?}, what: {:?}, context: {:?}", + _origin, + what, + context, + ); + if let Ok((class, instance)) = Matcher::matches_nonfungibles(what) { + match CheckAsset::asset_checking(&class) { + // We track this asset's teleports to ensure no more come in than have gone out. + Some(MintLocation::Local) => Self::reduce_checked(class, instance), + // We track this asset's teleports to ensure no more go out than have come in. + Some(MintLocation::NonLocal) => Self::accrue_checked(class, instance), + _ => (), + } + } + } + + fn can_check_out(_dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult { + log::trace!( + target: LOG_TARGET, + "can_check_out dest: {:?}, what: {:?}, context: {:?}", + _dest, + what, + context, + ); + // Check we handle this asset. + let (class, instance) = Matcher::matches_nonfungibles(what)?; + match CheckAsset::asset_checking(&class) { + // We track this asset's teleports to ensure no more come in than have gone out. + Some(MintLocation::Local) => Self::can_accrue_checked(class, instance), + // We track this asset's teleports to ensure no more go out than have come in. + Some(MintLocation::NonLocal) => Self::can_reduce_checked(class, instance), + _ => Ok(()), + } + } + + fn check_out(_dest: &Location, what: &Asset, context: &XcmContext) { + log::trace!( + target: LOG_TARGET, + "check_out dest: {:?}, what: {:?}, context: {:?}", + _dest, + what, + context, + ); + if let Ok((class, instance)) = Matcher::matches_nonfungibles(what) { + match CheckAsset::asset_checking(&class) { + // We track this asset's teleports to ensure no more come in than have gone out. + Some(MintLocation::Local) => Self::accrue_checked(class, instance), + // We track this asset's teleports to ensure no more go out than have come in. + Some(MintLocation::NonLocal) => Self::reduce_checked(class, instance), + _ => (), + } + } + } + + fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult { + log::trace!( + target: LOG_TARGET, + "deposit_asset what: {:?}, who: {:?}, context: {:?}", + what, + who, + context, + ); + // Check we handle this asset. + let (class, instance) = Matcher::matches_nonfungibles(what)?; + let who = AccountIdConverter::convert_location(who) + .ok_or(MatchError::AccountIdConversionFailed)?; + + Assets::mint_into(&class, &instance, &who, &ItemConfig::default(), true) + .map_err(|e| XcmError::FailedToTransactAsset(e.into())) + } + + fn withdraw_asset( + what: &Asset, + who: &Location, + maybe_context: Option<&XcmContext>, + ) -> Result { + log::trace!( + target: LOG_TARGET, + "withdraw_asset what: {:?}, who: {:?}, maybe_context: {:?}", + what, + who, + maybe_context, + ); + // Check we handle this asset. + let who = AccountIdConverter::convert_location(who) + .ok_or(MatchError::AccountIdConversionFailed)?; + let (class, instance) = Matcher::matches_nonfungibles(what)?; + Assets::burn(&class, &instance, Some(&who)) + .map_err(|e| XcmError::FailedToTransactAsset(e.into()))?; + Ok(what.clone().into()) + } +} + +/// Adapter for handling non-fungible tokens (NFTs) using [`nonfungibles_v2`]. +/// +/// This adapter combines the functionalities of both the [`NonFungiblesTransferAdapterPop`] and +/// [`NonFungiblesMutateAdapterPop`] adapters, allowing handling NFTs in various scenarios. +/// For detailed information on the functions, refer to [`TransactAsset`]. +pub struct NonFungiblesAdapterPop< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + ItemConfig, +>( + PhantomData<( + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + ItemConfig, + )>, +) +where + ItemConfig: Default; +impl< + Assets: nonfungibles_v2::Mutate + nonfungibles_v2::Transfer, + Matcher: MatchesNonFungibles, + AccountIdConverter: ConvertLocation, + AccountId: Clone + Eq, // can't get away without it since Currency is generic over it. + CheckAsset: AssetChecking, + CheckingAccount: Get>, + ItemConfig: Default, + > TransactAsset + for NonFungiblesAdapterPop< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + ItemConfig, + > +{ + fn can_check_in(origin: &Location, what: &Asset, context: &XcmContext) -> XcmResult { + NonFungiblesMutateAdapterPop::< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + ItemConfig, + >::can_check_in(origin, what, context) + } + + fn check_in(origin: &Location, what: &Asset, context: &XcmContext) { + NonFungiblesMutateAdapterPop::< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + ItemConfig, + >::check_in(origin, what, context) + } + + fn can_check_out(dest: &Location, what: &Asset, context: &XcmContext) -> XcmResult { + NonFungiblesMutateAdapterPop::< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + ItemConfig, + >::can_check_out(dest, what, context) + } + + fn check_out(dest: &Location, what: &Asset, context: &XcmContext) { + NonFungiblesMutateAdapterPop::< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + ItemConfig, + >::check_out(dest, what, context) + } + + fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> XcmResult { + NonFungiblesMutateAdapterPop::< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + ItemConfig, + >::deposit_asset(what, who, context) + } + + fn withdraw_asset( + what: &Asset, + who: &Location, + maybe_context: Option<&XcmContext>, + ) -> Result { + NonFungiblesMutateAdapterPop::< + Assets, + Matcher, + AccountIdConverter, + AccountId, + CheckAsset, + CheckingAccount, + ItemConfig, + >::withdraw_asset(what, who, maybe_context) + } + + fn transfer_asset( + what: &Asset, + from: &Location, + to: &Location, + context: &XcmContext, + ) -> Result { + NonFungiblesTransferAdapterPop::::transfer_asset( + what, from, to, context, + ) + } +} diff --git a/runtime/devnet/src/config/assets.rs b/runtime/devnet/src/config/assets.rs index 326b7e594..8c06bd917 100644 --- a/runtime/devnet/src/config/assets.rs +++ b/runtime/devnet/src/config/assets.rs @@ -1,16 +1,19 @@ +use cumulus_primitives_core::AssetInstance; use frame_support::{ parameter_types, - traits::{AsEnsureOriginWithArg, ConstU32}, + traits::{AsEnsureOriginWithArg, ConstU32, EnsureOrigin, EnsureOriginWithArg, Everything}, BoundedVec, PalletId, }; use frame_system::{EnsureRoot, EnsureSigned}; use pallet_nfts::PalletFeatures; use parachains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId, Signature}; +use pop_runtime_common::xcm::nonfungibles_adapter::MultiLocationCollectionId; use sp_runtime::traits::Verify; +use xcm_executor::traits::ConvertLocation; use crate::{ - deposit, AccountId, Assets, Balance, Balances, BlockNumber, Nfts, Runtime, RuntimeEvent, - RuntimeHoldReason, DAYS, EXISTENTIAL_DEPOSIT, UNIT, + config::xcm::LocationToAccountId, deposit, AccountId, Assets, Balance, Balances, BlockNumber, + Nfts, Runtime, RuntimeEvent, RuntimeHoldReason, RuntimeOrigin, DAYS, EXISTENTIAL_DEPOSIT, UNIT, }; /// We allow root to execute privileged asset operations. @@ -30,6 +33,8 @@ parameter_types! { parameter_types! { pub NftsPalletFeatures: PalletFeatures = PalletFeatures::all_enabled(); pub const NftsCollectionDeposit: Balance = 10 * UNIT; + // Key = 116 bytes (4+16+32+16+32+16), Value = 17 bytes (1+8+8) + pub const NftsCollectionApprovalDeposit: Balance = deposit(1, 133); pub const NftsItemDeposit: Balance = UNIT / 100; pub const NftsMetadataDepositBase: Balance = deposit(1, 129); pub const NftsAttributeDepositBase: Balance = deposit(1, 0); @@ -37,10 +42,71 @@ parameter_types! { pub const NftsMaxDeadlineDuration: BlockNumber = 12 * 30 * DAYS; } -impl pallet_nfts::Config for Runtime { +pub struct ForeignCreatorsNfts; + +impl EnsureOriginWithArg for ForeignCreatorsNfts { + type Success = AccountId; + + fn try_origin( + o: RuntimeOrigin, + a: &MultiLocationCollectionId, + ) -> sp_std::result::Result { + let origin_location = pallet_xcm::EnsureXcm::::try_origin(o.clone())?; + if !a.inner().starts_with(&origin_location.clone().try_into().unwrap()) { + return Err(o); + } + LocationToAccountId::convert_location(&origin_location).ok_or(o) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(a: &MultiLocationCollectionId) -> Result { + Ok(pallet_xcm::Origin::Xcm(a.clone().into()).into()) + } +} + +pub(crate) type ForeignNftsInstance = pallet_nfts::Instance2; +pub type ForeignNftsCall = pallet_nfts::Call; +impl pallet_nfts::Config for Runtime { + // TODO: source from primitives + type ApprovalsLimit = ConstU32<20>; + type AttributeDepositBase = NftsAttributeDepositBase; + type CollectionApprovalDeposit = NftsCollectionApprovalDeposit; + type CollectionDeposit = NftsCollectionDeposit; + // TODO: source from primitives + type CollectionId = MultiLocationCollectionId; + type CreateOrigin = ForeignCreatorsNfts; + type Currency = Balances; + type DepositPerByte = NftsDepositPerByte; + type Features = NftsPalletFeatures; + type ForceOrigin = AssetsForceOrigin; + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); + type ItemAttributesApprovalsLimit = ConstU32<30>; + type ItemDeposit = NftsItemDeposit; + // TODO: source from primitives + type ItemId = AssetInstance; + // TODO: source from primitives + type KeyLimit = ConstU32<64>; + type Locker = (); + type MaxAttributesPerCall = ConstU32<10>; + type MaxDeadlineDuration = NftsMaxDeadlineDuration; + type MaxTips = ConstU32<10>; + type MetadataDepositBase = NftsMetadataDepositBase; + type OffchainPublic = ::Signer; + type OffchainSignature = Signature; + type RuntimeEvent = RuntimeEvent; + type StringLimit = ConstU32<256>; + type ValueLimit = ConstU32<256>; + type WeightInfo = pallet_nfts::weights::SubstrateWeight; +} + +pub(crate) type TrustBackedNftsInstance = pallet_nfts::Instance1; +pub type TrustBackedNftsCall = pallet_nfts::Call; +impl pallet_nfts::Config for Runtime { // TODO: source from primitives type ApprovalsLimit = ConstU32<20>; type AttributeDepositBase = NftsAttributeDepositBase; + type CollectionApprovalDeposit = NftsCollectionApprovalDeposit; type CollectionDeposit = NftsCollectionDeposit; // TODO: source from primitives type CollectionId = CollectionId; @@ -86,8 +152,8 @@ impl pallet_nft_fractionalization::Config for Runtime { type Deposit = AssetDeposit; type NewAssetName = NewAssetName; type NewAssetSymbol = NewAssetSymbol; - type NftCollectionId = ::CollectionId; - type NftId = ::ItemId; + type NftCollectionId = >::CollectionId; + type NftId = >::ItemId; type Nfts = Nfts; type PalletId = NftFractionalizationPalletId; type RuntimeEvent = RuntimeEvent; diff --git a/runtime/devnet/src/config/proxy.rs b/runtime/devnet/src/config/proxy.rs index ff70240e7..486530276 100644 --- a/runtime/devnet/src/config/proxy.rs +++ b/runtime/devnet/src/config/proxy.rs @@ -5,7 +5,7 @@ use pop_runtime_common::proxy::{ }; use sp_runtime::traits::BlakeTwo256; -use super::assets::TrustBackedAssetsCall; +use super::assets::{TrustBackedAssetsCall, TrustBackedNftsCall}; use crate::{Balances, Runtime, RuntimeCall, RuntimeEvent}; impl InstanceFilter for ProxyType { @@ -45,13 +45,13 @@ impl InstanceFilter for ProxyType { RuntimeCall::Assets(TrustBackedAssetsCall::set_metadata { .. }) | RuntimeCall::Assets(TrustBackedAssetsCall::clear_metadata { .. }) | RuntimeCall::Assets(TrustBackedAssetsCall::set_min_balance { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::create { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::destroy { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::redeposit { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::transfer_ownership { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::set_team { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::set_collection_max_supply { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::lock_collection { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::create { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::destroy { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::redeposit { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::transfer_ownership { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::set_team { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::set_collection_max_supply { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::lock_collection { .. }) | RuntimeCall::Utility { .. } | RuntimeCall::Multisig { .. } ), @@ -66,17 +66,17 @@ impl InstanceFilter for ProxyType { RuntimeCall::Assets(TrustBackedAssetsCall::thaw_asset { .. }) | RuntimeCall::Assets(TrustBackedAssetsCall::touch_other { .. }) | RuntimeCall::Assets(TrustBackedAssetsCall::refund_other { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::force_mint { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::update_mint_settings { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::mint_pre_signed { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::set_attributes_pre_signed { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::lock_item_transfer { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::unlock_item_transfer { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::lock_item_properties { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::set_metadata { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::clear_metadata { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::set_collection_metadata { .. }) | - RuntimeCall::Nfts(pallet_nfts::Call::clear_collection_metadata { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::force_mint { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::update_mint_settings { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::mint_pre_signed { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::set_attributes_pre_signed { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::lock_item_transfer { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::unlock_item_transfer { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::lock_item_properties { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::set_metadata { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::clear_metadata { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::set_collection_metadata { .. }) | + RuntimeCall::Nfts(TrustBackedNftsCall::clear_collection_metadata { .. }) | RuntimeCall::Utility { .. } | RuntimeCall::Multisig { .. } ), diff --git a/runtime/devnet/src/config/xcm.rs b/runtime/devnet/src/config/xcm.rs index 40f18c7c7..bd65cff93 100644 --- a/runtime/devnet/src/config/xcm.rs +++ b/runtime/devnet/src/config/xcm.rs @@ -6,23 +6,30 @@ use frame_support::{ weights::Weight, }; use frame_system::EnsureRoot; +use pallet_nfts::ItemConfig; use pallet_xcm::XcmPassthrough; use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::impls::ToAuthor; +use pop_runtime_common::xcm::nonfungibles_adapter::{ + MultiLocationCollectionId, NonFungiblesAdapterPop, +}; use xcm::latest::prelude::*; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom, EnsureXcmOrigin, FixedWeightBounds, - FrameTransactionalProcessor, FungibleAdapter, IsConcrete, NativeAsset, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + FrameTransactionalProcessor, FungibleAdapter, IsConcrete, NativeAsset, NoChecking, + ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, }; -use xcm_executor::XcmExecutor; +use xcm_executor::{ + traits::{Error as MatchError, MatchesNonFungibles}, + XcmExecutor, +}; use crate::{ - AccountId, AllPalletsWithSystem, Balances, ParachainInfo, ParachainSystem, PolkadotXcm, - Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, XcmpQueue, + AccountId, AllPalletsWithSystem, Balances, ForeignNfts, ParachainInfo, ParachainSystem, + PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, XcmpQueue, }; parameter_types! { @@ -84,6 +91,42 @@ pub type XcmOriginToTransactDispatchOrigin = ( XcmPassthrough, ); +pub struct MultiAssetToNftsConverter; +impl MatchesNonFungibles for MultiAssetToNftsConverter { + fn matches_nonfungibles( + a: &Asset, + ) -> Result<(MultiLocationCollectionId, AssetInstance), MatchError> { + let (location, instance) = match (&a.id, &a.fun) { + (AssetId(location), NonFungible(instance)) => (location, instance), + _ => return Err(MatchError::AssetNotHandled), + }; + + let collection_id = MultiLocationCollectionId(Location { + parents: location.parents, + interior: location.interior.clone().try_into().unwrap(), + }); + + let item_id = instance; + + Ok((collection_id, *item_id)) + } +} + +pub type NonFungiblesTransactor = NonFungiblesAdapterPop< + // Use the non-fungibles pallet: + ForeignNfts, + MultiAssetToNftsConverter, + // Convert an XCM Location into a local account id: + LocationToAccountId, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // We don't support teleport so no need to check any assets. + NoChecking, + // We don't support teleport so this is just a dummy account. + (), + ItemConfig, +>; + parameter_types! { // One XCM operation is 1_000_000_000 weight - almost certainly a conservative estimate. pub UnitWeightCost: Weight = Weight::from_parts(1_000_000_000, 64 * 1024); @@ -132,7 +175,7 @@ impl xcm_executor::Config for XcmConfig { type AssetExchanger = (); type AssetLocker = (); // How to withdraw and deposit an asset. - type AssetTransactor = LocalAssetTransactor; + type AssetTransactor = (LocalAssetTransactor, NonFungiblesTransactor); type AssetTrap = PolkadotXcm; type Barrier = Barrier; type CallDispatcher = RuntimeCall; diff --git a/runtime/devnet/src/lib.rs b/runtime/devnet/src/lib.rs index f539cbdee..88167e57f 100644 --- a/runtime/devnet/src/lib.rs +++ b/runtime/devnet/src/lib.rs @@ -628,12 +628,16 @@ mod runtime { // Assets #[runtime::pallet_index(50)] - pub type Nfts = pallet_nfts::Pallet; + pub type Nfts = pallet_nfts::Pallet; #[runtime::pallet_index(51)] pub type NftFractionalization = pallet_nft_fractionalization::Pallet; #[runtime::pallet_index(52)] pub type Assets = pallet_assets::Pallet; + // Foreign Assets + #[runtime::pallet_index(70)] + pub type ForeignNfts = pallet_nfts::Pallet; + // Pop API #[runtime::pallet_index(150)] pub type Fungibles = fungibles::Pallet; @@ -648,6 +652,7 @@ mod benches { [pallet_session, SessionBench::] [pallet_timestamp, Timestamp] [pallet_message_queue, MessageQueue] + [pallet_nfts, Nfts] [pallet_sudo, Sudo] [pallet_collator_selection, CollatorSelection] [cumulus_pallet_parachain_system, ParachainSystem] diff --git a/runtime/testnet/src/config/assets.rs b/runtime/testnet/src/config/assets.rs index 326b7e594..0ff2a0cf6 100644 --- a/runtime/testnet/src/config/assets.rs +++ b/runtime/testnet/src/config/assets.rs @@ -30,6 +30,8 @@ parameter_types! { parameter_types! { pub NftsPalletFeatures: PalletFeatures = PalletFeatures::all_enabled(); pub const NftsCollectionDeposit: Balance = 10 * UNIT; + // Key = 116 bytes (4+16+32+16+32+16), Value = 17 bytes (1+8+8) + pub const NftsCollectionApprovalDeposit: Balance = deposit(1, 133); pub const NftsItemDeposit: Balance = UNIT / 100; pub const NftsMetadataDepositBase: Balance = deposit(1, 129); pub const NftsAttributeDepositBase: Balance = deposit(1, 0); @@ -41,6 +43,7 @@ impl pallet_nfts::Config for Runtime { // TODO: source from primitives type ApprovalsLimit = ConstU32<20>; type AttributeDepositBase = NftsAttributeDepositBase; + type CollectionApprovalDeposit = NftsCollectionApprovalDeposit; type CollectionDeposit = NftsCollectionDeposit; // TODO: source from primitives type CollectionId = CollectionId;