* Copy Uniques into Nfts

* Connect new pallet

* Update weights

* Nfts: Multiple approvals (#12178)

* multiple approvals

* clear

* tests & clean up

* fix in logic & fmt

* fix benchmarks

* deadline

* test deadline

* current_block + deadline

* update ApprovedTransfer event

* benchmark

* docs

* Update frame/nfts/src/lib.rs

Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com>

* fmt fix

* Update frame/nfts/src/lib.rs

Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com>

* update tests

* anyone can cancel

* Update frame/nfts/src/tests.rs

Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com>

* fmt

* fix logic

* unnecessary line

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

* Update frame/nfts/src/lib.rs

* Update lib.rs

* fmt

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* fmt

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* suggestion

* new line

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com>
Co-authored-by: command-bot <>
Co-authored-by: Squirrel <gilescope@gmail.com>

* Fixes

* cargo fmt

* Fixes

* Fixes

* Fix CI

* Nfts: Fix Auto-Increment (#12223)

* commit

* passing benchmarks

* clean up

* sync

* runtime implementation

* fix

* fmt

* fix benchmark

* cfg

* remove try-increment-id

* remove unused error

* impl Incrementable for unsigned types

* clean up

* fix in tests

* not needed anymore

* Use OptionQuery

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

* Rename Origin to RuntimeOrigin

* [Uniques V2] Tips (#12168)

* Allow to add tips when buying an NFT

* Chore

* Rework tips feature

* Add weights + benchmarks

* Convert tuple to struct

* Fix benchmark

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

* Update frame/nfts/src/benchmarking.rs

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix benchmarks

* Revert the bounded_vec![] approach

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

Co-authored-by: command-bot <>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* [Uniques V2] Atomic NFTs swap (#12285)

* Atomic NFTs swap

* Fmt

* Fix benchmark

* Rename swap -> atomic_swap

* Update target balance

* Rollback

* Fix

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

* Make desired item optional

* Apply suggestions

* Update frame/nfts/src/features/atomic_swap.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Rename fields

* Optimisation

* Add a comment

* deadline -> maybe_deadline

* Add docs

* Change comments

* Add price direction field

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

* Wrap price and direction

* Fix benchmarks

* Use ensure! instead of if {}

* Make duration param mandatory and limit it to MaxDeadlineDuration

* Make the code safer

* Fix clippy

* Chore

* Remove unused vars

* try

* try 2

* try 3

Co-authored-by: command-bot <>
Co-authored-by: Squirrel <gilescope@gmail.com>

* [Uniques V2] Feature flags (#12367)

* Basics

* WIP: change the data format

* Refactor

* Remove redundant new() method

* Rename settings

* Enable tests

* Chore

* Change params order

* Delete the config on collection removal

* Chore

* Remove redundant system features

* Rename force_item_status to force_collection_status

* Update node runtime

* Chore

* Remove thaw_collection

* Chore

* Connect collection.is_frozen to config

* Allow to lock the collection in a new way

* Move free_holding into settings

* Connect collection's metadata locker to feature flags

* DRY

* Chore

* Connect pallet level feature flags

* Prepare tests for the new changes

* Implement Item settings

* Allow to lock the metadata or attributes of an item

* Common -> Settings

* Extract settings related code to a separate file

* Move feature flag checks inside the do_* methods

* Split settings.rs into parts

* Extract repeated code into macro

* Extract macros into their own file

* Chore

* Fix traits

* Fix traits

* Test SystemFeatures

* Fix benchmarks

* Add missing benchmark

* Fix node/runtime/lib.rs

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

* Keep item's config on burn if it's not empty

* Fix the merge artifacts

* Fmt

* Add SystemFeature::NoSwaps check

* Rename SystemFeatures to PalletFeatures

* Rename errors

* Add docs

* Change error message

* Rework pallet features

* Move macros

* Change comments

* Fmt

* Refactor Incrementable

* Use pub(crate) for do_* functions

* Update comments

* Refactor freeze and lock functions

* Rework Collection config and Item confg api

* Chore

* Make clippy happy

* Chore

* Update comment

* RequiredDeposit => DepositRequired

* Address comments

Co-authored-by: command-bot <>

* [Uniques V2] Refactor roles (#12437)

* Basics

* WIP: change the data format

* Refactor

* Remove redundant new() method

* Rename settings

* Enable tests

* Chore

* Change params order

* Delete the config on collection removal

* Chore

* Remove redundant system features

* Rename force_item_status to force_collection_status

* Update node runtime

* Chore

* Remove thaw_collection

* Chore

* Connect collection.is_frozen to config

* Allow to lock the collection in a new way

* Move free_holding into settings

* Connect collection's metadata locker to feature flags

* DRY

* Chore

* Connect pallet level feature flags

* Prepare tests for the new changes

* Implement Item settings

* Allow to lock the metadata or attributes of an item

* Common -> Settings

* Extract settings related code to a separate file

* Move feature flag checks inside the do_* methods

* Split settings.rs into parts

* Extract repeated code into macro

* Extract macros into their own file

* Chore

* Fix traits

* Fix traits

* Test SystemFeatures

* Fix benchmarks

* Add missing benchmark

* Fix node/runtime/lib.rs

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

* Keep item's config on burn if it's not empty

* Fix the merge artifacts

* Fmt

* Add SystemFeature::NoSwaps check

* Refactor roles structure

* Rename SystemFeatures to PalletFeatures

* Rename errors

* Add docs

* Change error message

* Rework pallet features

* Move macros

* Change comments

* Fmt

* Refactor Incrementable

* Use pub(crate) for do_* functions

* Update comments

* Refactor freeze and lock functions

* Rework Collection config and Item confg api

* Chore

* Make clippy happy

* Chore

* Fix artifacts

* Address comments

* Further refactoring

* Add comments

* Add tests for group_roles_by_account()

* Update frame/nfts/src/impl_nonfungibles.rs

* Add test

* Replace Itertools group_by with a custom implementation

* ItemsNotTransferable => ItemsNonTransferable

* Update frame/nfts/src/features/roles.rs

Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>

* Address PR comments

* Add missed comment

Co-authored-by: command-bot <>
Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>

* Fix copy

* Remove storage_prefix

* Remove transactional

* Update comment

* [Uniques V2] Minting options (#12483)

* Basics

* WIP: change the data format

* Refactor

* Remove redundant new() method

* Rename settings

* Enable tests

* Chore

* Change params order

* Delete the config on collection removal

* Chore

* Remove redundant system features

* Rename force_item_status to force_collection_status

* Update node runtime

* Chore

* Remove thaw_collection

* Chore

* Connect collection.is_frozen to config

* Allow to lock the collection in a new way

* Move free_holding into settings

* Connect collection's metadata locker to feature flags

* DRY

* Chore

* Connect pallet level feature flags

* Prepare tests for the new changes

* Implement Item settings

* Allow to lock the metadata or attributes of an item

* Common -> Settings

* Extract settings related code to a separate file

* Move feature flag checks inside the do_* methods

* Split settings.rs into parts

* Extract repeated code into macro

* Extract macros into their own file

* Chore

* Fix traits

* Fix traits

* Test SystemFeatures

* Fix benchmarks

* Add missing benchmark

* Fix node/runtime/lib.rs

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

* Keep item's config on burn if it's not empty

* Fix the merge artifacts

* Fmt

* Add SystemFeature::NoSwaps check

* Rename SystemFeatures to PalletFeatures

* Rename errors

* Add docs

* Change error message

* Change the format of CollectionConfig to store more data

* Move max supply to the CollectionConfig and allow to change it

* Remove ItemConfig from the mint() function and use the one set in mint settings

* Add different mint options

* Allow to change the mint settings

* Add a force_mint() method

* Check mint params

* Some optimisations

* Cover with tests

* Remove merge artifacts

* Chore

* Use the new has_role() method

* Rework item deposits

* More tests

* Refactoring

* Address comments

* Refactor lock_collection()

* Update frame/nfts/src/types.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Update frame/nfts/src/types.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Private => Issuer

* Add more tests

* Fix benchmarks

* Add benchmarks for new methods

* [Uniques v2] Refactoring (#12570)

* Move do_set_price() and do_buy_item() to buy_sell.rs

* Move approvals to feature file

* Move metadata to feature files

* Move the rest of methods to feature files

* Remove artifacts

* Split force_collection_status into 2 methods

* Fix benchmarks

* Fix benchmarks

* Update deps

Co-authored-by: command-bot <>
Co-authored-by: Squirrel <gilescope@gmail.com>

* [Uniques V2] Smart attributes (#12702)

* Basics

* WIP: change the data format

* Refactor

* Remove redundant new() method

* Rename settings

* Enable tests

* Chore

* Change params order

* Delete the config on collection removal

* Chore

* Remove redundant system features

* Rename force_item_status to force_collection_status

* Update node runtime

* Chore

* Remove thaw_collection

* Chore

* Connect collection.is_frozen to config

* Allow to lock the collection in a new way

* Move free_holding into settings

* Connect collection's metadata locker to feature flags

* DRY

* Chore

* Connect pallet level feature flags

* Prepare tests for the new changes

* Implement Item settings

* Allow to lock the metadata or attributes of an item

* Common -> Settings

* Extract settings related code to a separate file

* Move feature flag checks inside the do_* methods

* Split settings.rs into parts

* Extract repeated code into macro

* Extract macros into their own file

* Chore

* Fix traits

* Fix traits

* Test SystemFeatures

* Fix benchmarks

* Add missing benchmark

* Fix node/runtime/lib.rs

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

* Keep item's config on burn if it's not empty

* Fix the merge artifacts

* Fmt

* Add SystemFeature::NoSwaps check

* Rename SystemFeatures to PalletFeatures

* Rename errors

* Add docs

* Change error message

* Change the format of CollectionConfig to store more data

* Move max supply to the CollectionConfig and allow to change it

* Remove ItemConfig from the mint() function and use the one set in mint settings

* Add different mint options

* Allow to change the mint settings

* Add a force_mint() method

* Check mint params

* Some optimisations

* Cover with tests

* Remove merge artifacts

* Chore

* Use the new has_role() method

* Rework item deposits

* More tests

* Refactoring

* Address comments

* Refactor lock_collection()

* Update frame/nfts/src/types.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Update frame/nfts/src/types.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Private => Issuer

* Add more tests

* Fix benchmarks

* Add benchmarks for new methods

* [Uniques v2] Refactoring (#12570)

* Move do_set_price() and do_buy_item() to buy_sell.rs

* Move approvals to feature file

* Move metadata to feature files

* Move the rest of methods to feature files

* Remove artifacts

* Smart attributes

* Split force_collection_status into 2 methods

* Fix benchmarks

* Fix benchmarks

* Update deps

* Fix merge artifact

* Weights + benchmarks + docs

* Change params order

* Chore

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Update docs

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* Add PalletId

* Chore

* Add tests

* More tests

* Add doc

* Update errors snapshots

* Ensure we track the owner_deposit field correctly

Co-authored-by: command-bot <>
Co-authored-by: Squirrel <gilescope@gmail.com>

* [Uniques V2] Final improvements (#12736)

* Use KeyPrefixIterator instead of Box

* Change create_collection()

* Restrict from claiming NFTs twice

* Update Readme

* Remove dead code

* Refactoring

* Update readme

* Fix clippy

* Update frame/nfts/src/lib.rs

Co-authored-by: Squirrel <gilescope@gmail.com>

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

* Update docs

* Typo

* Fix benchmarks

* Add more docs

* DepositRequired setting should affect only the attributes within the CollectionOwner namespace

* [NFTs] Implement missed methods to set the attributes from other pallets (#12919)

* Implement missed methods to set the attributes from other pallets

* Revert snapshots

* Update snapshot

* Update snapshot

* Revert snapshot changes

* Update snapshots

* Yet another snapshot update..

* Update frame/support/src/traits/tokens/nonfungible_v2.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/support/src/traits/tokens/nonfungible_v2.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/support/src/traits/tokens/nonfungible_v2.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/support/src/traits/tokens/nonfungibles_v2.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/support/src/traits/tokens/nonfungible_v2.rs

* Update frame/nfts/src/lib.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/support/src/traits/tokens/nonfungibles_v2.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/lib.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Address comments

* [NFTs] Add the new `owner` param to mint() method (#12997)

* Add the new `owner` param to mint() method

* Fmt

* Address comments

* ".git/.scripts/bench-bot.sh" pallet dev pallet_nfts

* Update frame/nfts/src/common_functions.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/types.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/types.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/types.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/types.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Update frame/nfts/src/types.rs

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>

* Add call indexes

* Update snapshots

Co-authored-by: Sergej Sakac <73715684+Szegoo@users.noreply.github.com>
Co-authored-by: Squirrel <gilescope@gmail.com>
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com>
Co-authored-by: command-bot <>
Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>
This commit is contained in:
Jegor Sidorenko
2022-12-23 18:07:27 +02:00
committed by GitHub
parent 34eb463d99
commit 0edab31776
36 changed files with 9269 additions and 13 deletions
+21 -2
View File
@@ -1640,9 +1640,9 @@ dependencies = [
[[package]]
name = "enumflags2"
version = "0.7.4"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b3ab37dc79652c9d85f1f7b6070d77d321d2467f5fe7b00d6b7a86c57b092ae"
checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb"
dependencies = [
"enumflags2_derive",
]
@@ -3093,6 +3093,7 @@ dependencies = [
"pallet-message-queue",
"pallet-mmr",
"pallet-multisig",
"pallet-nfts",
"pallet-nis",
"pallet-nomination-pools",
"pallet-nomination-pools-benchmarking",
@@ -5378,6 +5379,24 @@ dependencies = [
"sp-std",
]
[[package]]
name = "pallet-nfts"
version = "4.0.0-dev"
dependencies = [
"enumflags2",
"frame-benchmarking",
"frame-support",
"frame-system",
"log",
"pallet-balances",
"parity-scale-codec",
"scale-info",
"sp-core",
"sp-io",
"sp-runtime",
"sp-std",
]
[[package]]
name = "pallet-nicks"
version = "4.0.0-dev"
+1
View File
@@ -122,6 +122,7 @@ members = [
"frame/preimage",
"frame/proxy",
"frame/message-queue",
"frame/nfts",
"frame/nomination-pools",
"frame/nomination-pools/fuzzer",
"frame/nomination-pools/benchmarking",
+4
View File
@@ -78,6 +78,7 @@ pallet-membership = { version = "4.0.0-dev", default-features = false, path = ".
pallet-message-queue = { version = "7.0.0-dev", default-features = false, path = "../../../frame/message-queue" }
pallet-mmr = { version = "4.0.0-dev", default-features = false, path = "../../../frame/merkle-mountain-range" }
pallet-multisig = { version = "4.0.0-dev", default-features = false, path = "../../../frame/multisig" }
pallet-nfts = { version = "4.0.0-dev", default-features = false, path = "../../../frame/nfts" }
pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../../../frame/nomination-pools"}
pallet-nomination-pools-benchmarking = { version = "1.0.0", default-features = false, optional = true, path = "../../../frame/nomination-pools/benchmarking" }
pallet-nomination-pools-runtime-api = { version = "1.0.0-dev", default-features = false, path = "../../../frame/nomination-pools/runtime-api" }
@@ -197,6 +198,7 @@ std = [
"pallet-root-testing/std",
"pallet-recovery/std",
"pallet-uniques/std",
"pallet-nfts/std",
"pallet-vesting/std",
"log/std",
"frame-try-runtime?/std",
@@ -253,6 +255,7 @@ runtime-benchmarks = [
"pallet-treasury/runtime-benchmarks",
"pallet-utility/runtime-benchmarks",
"pallet-uniques/runtime-benchmarks",
"pallet-nfts/runtime-benchmarks",
"pallet-vesting/runtime-benchmarks",
"pallet-whitelist/runtime-benchmarks",
"frame-system-benchmarking/runtime-benchmarks",
@@ -312,6 +315,7 @@ try-runtime = [
"pallet-asset-tx-payment/try-runtime",
"pallet-transaction-storage/try-runtime",
"pallet-uniques/try-runtime",
"pallet-nfts/try-runtime",
"pallet-vesting/try-runtime",
"pallet-whitelist/try-runtime",
]
+38
View File
@@ -56,6 +56,7 @@ use pallet_grandpa::{
fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList,
};
use pallet_im_online::sr25519::AuthorityId as ImOnlineId;
use pallet_nfts::PalletFeatures;
use pallet_nis::WithMaximumOf;
use pallet_session::historical::{self as pallet_session_historical};
pub use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdjustment};
@@ -301,6 +302,7 @@ impl InstanceFilter<RuntimeCall> for ProxyType {
RuntimeCall::Balances(..) |
RuntimeCall::Assets(..) |
RuntimeCall::Uniques(..) |
RuntimeCall::Nfts(..) |
RuntimeCall::Vesting(pallet_vesting::Call::vested_transfer { .. }) |
RuntimeCall::Indices(pallet_indices::Call::transfer { .. })
),
@@ -1528,6 +1530,10 @@ parameter_types! {
pub const ItemDeposit: Balance = 1 * DOLLARS;
pub const KeyLimit: u32 = 32;
pub const ValueLimit: u32 = 256;
pub const ApprovalsLimit: u32 = 20;
pub const ItemAttributesApprovalsLimit: u32 = 20;
pub const MaxTips: u32 = 10;
pub const MaxDeadlineDuration: BlockNumber = 12 * 30 * DAYS;
}
impl pallet_uniques::Config for Runtime {
@@ -1551,6 +1557,36 @@ impl pallet_uniques::Config for Runtime {
type Locker = ();
}
parameter_types! {
pub Features: PalletFeatures = PalletFeatures::all_enabled();
}
impl pallet_nfts::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type CollectionId = u32;
type ItemId = u32;
type Currency = Balances;
type ForceOrigin = frame_system::EnsureRoot<AccountId>;
type CollectionDeposit = CollectionDeposit;
type ItemDeposit = ItemDeposit;
type MetadataDepositBase = MetadataDepositBase;
type AttributeDepositBase = MetadataDepositBase;
type DepositPerByte = MetadataDepositPerByte;
type StringLimit = StringLimit;
type KeyLimit = KeyLimit;
type ValueLimit = ValueLimit;
type ApprovalsLimit = ApprovalsLimit;
type ItemAttributesApprovalsLimit = ItemAttributesApprovalsLimit;
type MaxTips = MaxTips;
type MaxDeadlineDuration = MaxDeadlineDuration;
type Features = Features;
type WeightInfo = pallet_nfts::weights::SubstrateWeight<Runtime>;
#[cfg(feature = "runtime-benchmarks")]
type Helper = ();
type CreateOrigin = AsEnsureOriginWithArg<EnsureSigned<AccountId>>;
type Locker = ();
}
impl pallet_transaction_storage::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
@@ -1705,6 +1741,7 @@ construct_runtime!(
Lottery: pallet_lottery,
Nis: pallet_nis,
Uniques: pallet_uniques,
Nfts: pallet_nfts,
TransactionStorage: pallet_transaction_storage,
VoterList: pallet_bags_list::<Instance1>,
StateTrieMigration: pallet_state_trie_migration,
@@ -1836,6 +1873,7 @@ mod benches {
[pallet_transaction_storage, TransactionStorage]
[pallet_treasury, Treasury]
[pallet_uniques, Uniques]
[pallet_nfts, Nfts]
[pallet_utility, Utility]
[pallet_vesting, Vesting]
[pallet_whitelist, Whitelist]
+49
View File
@@ -0,0 +1,49 @@
[package]
name = "pallet-nfts"
version = "4.0.0-dev"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
license = "Apache-2.0"
homepage = "https://substrate.io"
repository = "https://github.com/paritytech/substrate/"
description = "FRAME NFTs pallet"
readme = "README.md"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false }
enumflags2 = { version = "0.7.5" }
log = { version = "0.4.17", default-features = false }
scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" }
frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" }
frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" }
sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" }
sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" }
[dev-dependencies]
pallet-balances = { version = "4.0.0-dev", path = "../balances" }
sp-core = { version = "7.0.0", path = "../../primitives/core" }
sp-io = { version = "7.0.0", path = "../../primitives/io" }
sp-std = { version = "5.0.0", path = "../../primitives/std" }
[features]
default = ["std"]
std = [
"codec/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"log/std",
"scale-info/std",
"sp-runtime/std",
"sp-std/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
try-runtime = ["frame-support/try-runtime"]
+106
View File
@@ -0,0 +1,106 @@
# NFTs pallet
A pallet for dealing with non-fungible assets.
## Overview
The NFTs pallet provides functionality for non-fungible tokens' management, including:
* Collection Creation
* NFT Minting
* NFT Transfers and Atomic Swaps
* NFT Trading methods
* Attributes Management
* NFT Burning
To use it in your runtime, you need to implement [`nfts::Config`](https://paritytech.github.io/substrate/master/pallet_nfts/pallet/trait.Config.html).
The supported dispatchable functions are documented in the [`nfts::Call`](https://paritytech.github.io/substrate/master/pallet_nfts/pallet/enum.Call.html) enum.
### Terminology
* **Collection creation:** The creation of a new collection.
* **NFT minting:** The action of creating a new item within a collection.
* **NFT transfer:** The action of sending an item from one account to another.
* **Atomic swap:** The action of exchanging items between accounts without needing a 3rd party service.
* **NFT burning:** The destruction of an item.
* **Non-fungible token (NFT):** An item for which each unit has unique characteristics. There is exactly
one instance of such an item in existence and there is exactly one owning account (though that owning account could be a proxy account or multi-sig account).
* **Soul Bound NFT:** An item that is non-transferable from the account which it is minted into.
### Goals
The NFTs pallet in Substrate is designed to make the following possible:
* Allow accounts to permissionlessly create nft collections.
* Allow a named (permissioned) account to mint and burn unique items within a collection.
* Move items between accounts permissionlessly.
* Allow a named (permissioned) account to freeze and unfreeze items within a
collection or the entire collection.
* Allow the owner of an item to delegate the ability to transfer the item to some
named third-party.
* Allow third-parties to store information in an NFT _without_ owning it (Eg. save game state).
## Interface
### Permissionless dispatchables
* `create`: Create a new collection by placing a deposit.
* `mint`: Mint a new item within a collection (when the minting is public).
* `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.
* `cancel_approval`: Revert the effects of a previous `approve_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.
* `buy_item`: Buy an item.
* `pay_tips`: Pay tips, could be used for paying the creator royalties.
* `create_swap`: Create an offer to swap an NFT for another NFT and optionally some fungibles.
* `cancel_swap`: Cancel previously created swap offer.
* `claim_swap`: Swap items in an atomic way.
### Permissioned dispatchables
* `destroy`: Destroy a collection. This destroys all the items inside the collection and refunds the deposit.
* `force_mint`: Mint a new item within a collection.
* `burn`: Destroy an item within a collection.
* `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`.
* `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 will not be affected.)
* `set_team`: Alter the permissioned accounts of a collection.
* `set_collection_max_supply`: Change the max supply of a collection.
* `update_mint_settings`: Update the minting settings for collection.
### Metadata (permissioned) dispatchables
* `set_attribute`: Set a metadata attribute of an item or collection.
* `clear_attribute`: Remove a metadata attribute of an item or collection.
* `set_metadata`: Set general metadata of an item (E.g. an IPFS address of an image url).
* `clear_metadata`: Remove general metadata of an item.
* `set_collection_metadata`: Set general metadata of a collection.
* `clear_collection_metadata`: Remove general metadata of a collection.
### Force (i.e. governance) dispatchables
* `force_create`: Create a new collection (the collection id can not be chosen).
* `force_collection_owner`: Change collection's owner.
* `force_collection_config`: Change collection's config.
* `force_set_attribute`: Set an attribute.
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.
## Related Modules
* [`System`](https://docs.rs/frame-system/latest/frame_system/)
* [`Support`](https://docs.rs/frame-support/latest/frame_support/)
* [`Assets`](https://docs.rs/pallet-assets/latest/pallet_assets/)
License: Apache-2.0
+718
View File
@@ -0,0 +1,718 @@
// This file is part of Substrate.
// Copyright (C) 2020-2022 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.
//! Nfts pallet benchmarking.
#![cfg(feature = "runtime-benchmarks")]
use super::*;
use enumflags2::{BitFlag, BitFlags};
use frame_benchmarking::{
account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller,
};
use frame_support::{
assert_ok,
dispatch::UnfilteredDispatchable,
traits::{EnsureOrigin, Get},
BoundedVec,
};
use frame_system::RawOrigin as SystemOrigin;
use sp_runtime::traits::{Bounded, One};
use sp_std::prelude::*;
use crate::Pallet as Nfts;
const SEED: u32 = 0;
fn create_collection<T: Config<I>, I: 'static>(
) -> (T::CollectionId, T::AccountId, AccountIdLookupOf<T>) {
let caller: T::AccountId = whitelisted_caller();
let caller_lookup = T::Lookup::unlookup(caller.clone());
let collection = T::Helper::collection(0);
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
assert_ok!(Nfts::<T, I>::force_create(
SystemOrigin::Root.into(),
caller_lookup.clone(),
default_collection_config::<T, I>()
));
(collection, caller, caller_lookup)
}
fn add_collection_metadata<T: Config<I>, I: 'static>() -> (T::AccountId, AccountIdLookupOf<T>) {
let caller = Collection::<T, I>::get(T::Helper::collection(0)).unwrap().owner;
if caller != whitelisted_caller() {
whitelist_account!(caller);
}
let caller_lookup = T::Lookup::unlookup(caller.clone());
assert_ok!(Nfts::<T, I>::set_collection_metadata(
SystemOrigin::Signed(caller.clone()).into(),
T::Helper::collection(0),
vec![0; T::StringLimit::get() as usize].try_into().unwrap(),
));
(caller, caller_lookup)
}
fn mint_item<T: Config<I>, I: 'static>(
index: u16,
) -> (T::ItemId, T::AccountId, AccountIdLookupOf<T>) {
let caller = Collection::<T, I>::get(T::Helper::collection(0)).unwrap().owner;
if caller != whitelisted_caller() {
whitelist_account!(caller);
}
let caller_lookup = T::Lookup::unlookup(caller.clone());
let item = T::Helper::item(index);
assert_ok!(Nfts::<T, I>::mint(
SystemOrigin::Signed(caller.clone()).into(),
T::Helper::collection(0),
item,
caller_lookup.clone(),
None,
));
(item, caller, caller_lookup)
}
fn add_item_metadata<T: Config<I>, I: 'static>(
item: T::ItemId,
) -> (T::AccountId, AccountIdLookupOf<T>) {
let caller = Collection::<T, I>::get(T::Helper::collection(0)).unwrap().owner;
if caller != whitelisted_caller() {
whitelist_account!(caller);
}
let caller_lookup = T::Lookup::unlookup(caller.clone());
assert_ok!(Nfts::<T, I>::set_metadata(
SystemOrigin::Signed(caller.clone()).into(),
T::Helper::collection(0),
item,
vec![0; T::StringLimit::get() as usize].try_into().unwrap(),
));
(caller, caller_lookup)
}
fn add_item_attribute<T: Config<I>, I: 'static>(
item: T::ItemId,
) -> (BoundedVec<u8, T::KeyLimit>, T::AccountId, AccountIdLookupOf<T>) {
let caller = Collection::<T, I>::get(T::Helper::collection(0)).unwrap().owner;
if caller != whitelisted_caller() {
whitelist_account!(caller);
}
let caller_lookup = T::Lookup::unlookup(caller.clone());
let key: BoundedVec<_, _> = vec![0; T::KeyLimit::get() as usize].try_into().unwrap();
assert_ok!(Nfts::<T, I>::set_attribute(
SystemOrigin::Signed(caller.clone()).into(),
T::Helper::collection(0),
Some(item),
AttributeNamespace::CollectionOwner,
key.clone(),
vec![0; T::ValueLimit::get() as usize].try_into().unwrap(),
));
(key, caller, caller_lookup)
}
fn assert_last_event<T: Config<I>, I: 'static>(generic_event: <T as Config<I>>::RuntimeEvent) {
let events = frame_system::Pallet::<T>::events();
let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into();
// compare to the last event record
let frame_system::EventRecord { event, .. } = &events[events.len() - 1];
assert_eq!(event, &system_event);
}
fn make_collection_config<T: Config<I>, I: 'static>(
disable_settings: BitFlags<CollectionSetting>,
) -> CollectionConfigFor<T, I> {
CollectionConfig {
settings: CollectionSettings::from_disabled(disable_settings),
max_supply: None,
mint_settings: MintSettings::default(),
}
}
fn default_collection_config<T: Config<I>, I: 'static>() -> CollectionConfigFor<T, I> {
make_collection_config::<T, I>(CollectionSetting::empty())
}
fn default_item_config() -> ItemConfig {
ItemConfig { settings: ItemSettings::all_enabled() }
}
benchmarks_instance_pallet! {
create {
let collection = T::Helper::collection(0);
let origin = T::CreateOrigin::successful_origin(&collection);
let caller = T::CreateOrigin::ensure_origin(origin.clone(), &collection).unwrap();
whitelist_account!(caller);
let admin = T::Lookup::unlookup(caller.clone());
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
let call = Call::<T, I>::create { admin, config: default_collection_config::<T, I>() };
}: { call.dispatch_bypass_filter(origin)? }
verify {
assert_last_event::<T, I>(Event::Created { collection: T::Helper::collection(0), creator: caller.clone(), owner: caller }.into());
}
force_create {
let caller: T::AccountId = whitelisted_caller();
let caller_lookup = T::Lookup::unlookup(caller.clone());
}: _(SystemOrigin::Root, caller_lookup, default_collection_config::<T, I>())
verify {
assert_last_event::<T, I>(Event::ForceCreated { collection: T::Helper::collection(0), owner: caller }.into());
}
destroy {
let n in 0 .. 1_000;
let m in 0 .. 1_000;
let a in 0 .. 1_000;
let (collection, caller, _) = create_collection::<T, I>();
add_collection_metadata::<T, I>();
for i in 0..n {
mint_item::<T, I>(i as u16);
}
for i in 0..m {
if !Item::<T, I>::contains_key(collection, T::Helper::item(i as u16)) {
mint_item::<T, I>(i as u16);
}
add_item_metadata::<T, I>(T::Helper::item(i as u16));
}
for i in 0..a {
if !Item::<T, I>::contains_key(collection, T::Helper::item(i as u16)) {
mint_item::<T, I>(i as u16);
}
add_item_attribute::<T, I>(T::Helper::item(i as u16));
}
let witness = Collection::<T, I>::get(collection).unwrap().destroy_witness();
}: _(SystemOrigin::Signed(caller), collection, witness)
verify {
assert_last_event::<T, I>(Event::Destroyed { collection }.into());
}
mint {
let (collection, caller, caller_lookup) = create_collection::<T, I>();
let item = T::Helper::item(0);
}: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup, None)
verify {
assert_last_event::<T, I>(Event::Issued { collection, item, owner: caller }.into());
}
force_mint {
let (collection, caller, caller_lookup) = create_collection::<T, I>();
let item = T::Helper::item(0);
}: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup, default_item_config())
verify {
assert_last_event::<T, I>(Event::Issued { collection, item, owner: caller }.into());
}
burn {
let (collection, caller, caller_lookup) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
}: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(caller_lookup))
verify {
assert_last_event::<T, I>(Event::Burned { collection, item, owner: caller }.into());
}
transfer {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
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)
verify {
assert_last_event::<T, I>(Event::Transferred { collection, item, from: caller, to: target }.into());
}
redeposit {
let i in 0 .. 5_000;
let (collection, caller, _) = create_collection::<T, I>();
let items = (0..i).map(|x| mint_item::<T, I>(x as u16).0).collect::<Vec<_>>();
Nfts::<T, I>::force_collection_config(
SystemOrigin::Root.into(),
collection,
make_collection_config::<T, I>(CollectionSetting::DepositRequired.into()),
)?;
}: _(SystemOrigin::Signed(caller.clone()), collection, items.clone())
verify {
assert_last_event::<T, I>(Event::Redeposited { collection, successful_items: items }.into());
}
lock_item_transfer {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
}: _(SystemOrigin::Signed(caller.clone()), T::Helper::collection(0), T::Helper::item(0))
verify {
assert_last_event::<T, I>(Event::ItemTransferLocked { collection: T::Helper::collection(0), item: T::Helper::item(0) }.into());
}
unlock_item_transfer {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
Nfts::<T, I>::lock_item_transfer(
SystemOrigin::Signed(caller.clone()).into(),
collection,
item,
)?;
}: _(SystemOrigin::Signed(caller.clone()), collection, item)
verify {
assert_last_event::<T, I>(Event::ItemTransferUnlocked { collection, item }.into());
}
lock_collection {
let (collection, caller, _) = create_collection::<T, I>();
let lock_settings = CollectionSettings::from_disabled(
CollectionSetting::TransferableItems |
CollectionSetting::UnlockedMetadata |
CollectionSetting::UnlockedAttributes |
CollectionSetting::UnlockedMaxSupply,
);
}: _(SystemOrigin::Signed(caller.clone()), collection, lock_settings)
verify {
assert_last_event::<T, I>(Event::CollectionLocked { collection }.into());
}
transfer_ownership {
let (collection, caller, _) = create_collection::<T, I>();
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());
let origin = SystemOrigin::Signed(target.clone()).into();
Nfts::<T, I>::set_accept_ownership(origin, Some(collection))?;
}: _(SystemOrigin::Signed(caller), collection, target_lookup)
verify {
assert_last_event::<T, I>(Event::OwnerChanged { collection, new_owner: target }.into());
}
set_team {
let (collection, caller, _) = create_collection::<T, I>();
let target0 = T::Lookup::unlookup(account("target", 0, SEED));
let target1 = T::Lookup::unlookup(account("target", 1, SEED));
let target2 = T::Lookup::unlookup(account("target", 2, SEED));
}: _(SystemOrigin::Signed(caller), collection, target0, target1, target2)
verify {
assert_last_event::<T, I>(Event::TeamChanged{
collection,
issuer: account("target", 0, SEED),
admin: account("target", 1, SEED),
freezer: account("target", 2, SEED),
}.into());
}
force_collection_owner {
let (collection, _, _) = create_collection::<T, I>();
let origin = T::ForceOrigin::successful_origin();
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());
let call = Call::<T, I>::force_collection_owner {
collection,
owner: target_lookup,
};
}: { call.dispatch_bypass_filter(origin)? }
verify {
assert_last_event::<T, I>(Event::OwnerChanged { collection, new_owner: target }.into());
}
force_collection_config {
let (collection, caller, _) = create_collection::<T, I>();
let origin = T::ForceOrigin::successful_origin();
let call = Call::<T, I>::force_collection_config {
collection,
config: make_collection_config::<T, I>(CollectionSetting::DepositRequired.into()),
};
}: { call.dispatch_bypass_filter(origin)? }
verify {
assert_last_event::<T, I>(Event::CollectionConfigChanged { collection }.into());
}
lock_item_properties {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
let lock_metadata = true;
let lock_attributes = true;
}: _(SystemOrigin::Signed(caller), collection, item, lock_metadata, lock_attributes)
verify {
assert_last_event::<T, I>(Event::ItemPropertiesLocked { collection, item, lock_metadata, lock_attributes }.into());
}
set_attribute {
let key: BoundedVec<_, _> = vec![0u8; T::KeyLimit::get() as usize].try_into().unwrap();
let value: BoundedVec<_, _> = vec![0u8; T::ValueLimit::get() as usize].try_into().unwrap();
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
}: _(SystemOrigin::Signed(caller), collection, Some(item), AttributeNamespace::CollectionOwner, key.clone(), value.clone())
verify {
assert_last_event::<T, I>(
Event::AttributeSet {
collection,
maybe_item: Some(item),
namespace: AttributeNamespace::CollectionOwner,
key,
value,
}
.into(),
);
}
force_set_attribute {
let key: BoundedVec<_, _> = vec![0u8; T::KeyLimit::get() as usize].try_into().unwrap();
let value: BoundedVec<_, _> = vec![0u8; T::ValueLimit::get() as usize].try_into().unwrap();
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
}: _(SystemOrigin::Root, Some(caller), collection, Some(item), AttributeNamespace::CollectionOwner, key.clone(), value.clone())
verify {
assert_last_event::<T, I>(
Event::AttributeSet {
collection,
maybe_item: Some(item),
namespace: AttributeNamespace::CollectionOwner,
key,
value,
}
.into(),
);
}
clear_attribute {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
add_item_metadata::<T, I>(item);
let (key, ..) = add_item_attribute::<T, I>(item);
}: _(SystemOrigin::Signed(caller), collection, Some(item), AttributeNamespace::CollectionOwner, key.clone())
verify {
assert_last_event::<T, I>(
Event::AttributeCleared {
collection,
maybe_item: Some(item),
namespace: AttributeNamespace::CollectionOwner,
key,
}.into(),
);
}
approve_item_attributes {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
let target: T::AccountId = account("target", 0, SEED);
let target_lookup = T::Lookup::unlookup(target.clone());
}: _(SystemOrigin::Signed(caller), collection, item, target_lookup)
verify {
assert_last_event::<T, I>(
Event::ItemAttributesApprovalAdded {
collection,
item,
delegate: target,
}
.into(),
);
}
cancel_item_attributes_approval {
let n in 0 .. 1_000;
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
let target: T::AccountId = account("target", 0, SEED);
let target_lookup = T::Lookup::unlookup(target.clone());
Nfts::<T, I>::approve_item_attributes(
SystemOrigin::Signed(caller.clone()).into(),
collection,
item,
target_lookup.clone(),
)?;
T::Currency::make_free_balance_be(&target, DepositBalanceOf::<T, I>::max_value());
let value: BoundedVec<_, _> = vec![0u8; T::ValueLimit::get() as usize].try_into().unwrap();
for i in 0..n {
let mut key = vec![0u8; T::KeyLimit::get() as usize];
let mut s = Vec::from((i as u16).to_be_bytes());
key.truncate(s.len());
key.append(&mut s);
Nfts::<T, I>::set_attribute(
SystemOrigin::Signed(target.clone()).into(),
T::Helper::collection(0),
Some(item),
AttributeNamespace::Account(target.clone()),
key.try_into().unwrap(),
value.clone(),
)?;
}
let witness = CancelAttributesApprovalWitness { account_attributes: n };
}: _(SystemOrigin::Signed(caller), collection, item, target_lookup, witness)
verify {
assert_last_event::<T, I>(
Event::ItemAttributesApprovalRemoved {
collection,
item,
delegate: target,
}
.into(),
);
}
set_metadata {
let data: BoundedVec<_, _> = vec![0u8; T::StringLimit::get() as usize].try_into().unwrap();
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
}: _(SystemOrigin::Signed(caller), collection, item, data.clone())
verify {
assert_last_event::<T, I>(Event::ItemMetadataSet { collection, item, data }.into());
}
clear_metadata {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
add_item_metadata::<T, I>(item);
}: _(SystemOrigin::Signed(caller), collection, item)
verify {
assert_last_event::<T, I>(Event::ItemMetadataCleared { collection, item }.into());
}
set_collection_metadata {
let data: BoundedVec<_, _> = vec![0u8; T::StringLimit::get() as usize].try_into().unwrap();
let (collection, caller, _) = create_collection::<T, I>();
}: _(SystemOrigin::Signed(caller), collection, data.clone())
verify {
assert_last_event::<T, I>(Event::CollectionMetadataSet { collection, data }.into());
}
clear_collection_metadata {
let (collection, caller, _) = create_collection::<T, I>();
add_collection_metadata::<T, I>();
}: _(SystemOrigin::Signed(caller), collection)
verify {
assert_last_event::<T, I>(Event::CollectionMetadataCleared { collection }.into());
}
approve_transfer {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
let delegate: T::AccountId = account("delegate", 0, SEED);
let delegate_lookup = T::Lookup::unlookup(delegate.clone());
let deadline = T::BlockNumber::max_value();
}: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup, Some(deadline))
verify {
assert_last_event::<T, I>(Event::TransferApproved { collection, item, owner: caller, delegate, deadline: Some(deadline) }.into());
}
cancel_approval {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(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 = T::BlockNumber::max_value();
Nfts::<T, I>::approve_transfer(origin, collection, item, delegate_lookup.clone(), Some(deadline))?;
}: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup)
verify {
assert_last_event::<T, I>(Event::ApprovalCancelled { collection, item, owner: caller, delegate }.into());
}
clear_all_transfer_approvals {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(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 = T::BlockNumber::max_value();
Nfts::<T, I>::approve_transfer(origin, collection, item, delegate_lookup.clone(), Some(deadline))?;
}: _(SystemOrigin::Signed(caller.clone()), collection, item)
verify {
assert_last_event::<T, I>(Event::AllApprovalsCancelled {collection, item, owner: caller}.into());
}
set_accept_ownership {
let caller: T::AccountId = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
let collection = T::Helper::collection(0);
}: _(SystemOrigin::Signed(caller.clone()), Some(collection))
verify {
assert_last_event::<T, I>(Event::OwnershipAcceptanceChanged {
who: caller,
maybe_collection: Some(collection),
}.into());
}
set_collection_max_supply {
let (collection, caller, _) = create_collection::<T, I>();
}: _(SystemOrigin::Signed(caller.clone()), collection, u32::MAX)
verify {
assert_last_event::<T, I>(Event::CollectionMaxSupplySet {
collection,
max_supply: u32::MAX,
}.into());
}
update_mint_settings {
let (collection, caller, _) = create_collection::<T, I>();
let mint_settings = MintSettings {
mint_type: MintType::HolderOf(T::Helper::collection(0)),
start_block: Some(One::one()),
end_block: Some(One::one()),
price: Some(ItemPrice::<T, I>::from(1u32)),
default_item_settings: ItemSettings::all_enabled(),
};
}: _(SystemOrigin::Signed(caller.clone()), collection, mint_settings)
verify {
assert_last_event::<T, I>(Event::CollectionMintSettingsUpdated { collection }.into());
}
set_price {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
let delegate: T::AccountId = account("delegate", 0, SEED);
let delegate_lookup = T::Lookup::unlookup(delegate.clone());
let price = ItemPrice::<T, I>::from(100u32);
}: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(price), Some(delegate_lookup))
verify {
assert_last_event::<T, I>(Event::ItemPriceSet {
collection,
item,
price,
whitelisted_buyer: Some(delegate),
}.into());
}
buy_item {
let (collection, seller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
let buyer: T::AccountId = account("buyer", 0, SEED);
let buyer_lookup = T::Lookup::unlookup(buyer.clone());
let price = ItemPrice::<T, I>::from(0u32);
let origin = SystemOrigin::Signed(seller.clone()).into();
Nfts::<T, I>::set_price(origin, collection, item, Some(price.clone()), Some(buyer_lookup))?;
T::Currency::make_free_balance_be(&buyer, DepositBalanceOf::<T, I>::max_value());
}: _(SystemOrigin::Signed(buyer.clone()), collection, item, price.clone())
verify {
assert_last_event::<T, I>(Event::ItemBought {
collection,
item,
price,
seller,
buyer,
}.into());
}
pay_tips {
let n in 0 .. T::MaxTips::get() as u32;
let amount = BalanceOf::<T, I>::from(100u32);
let caller: T::AccountId = whitelisted_caller();
let collection = T::Helper::collection(0);
let item = T::Helper::item(0);
let tips: BoundedVec<_, _> = vec![
ItemTip
{ collection, item, receiver: caller.clone(), amount }; n as usize
].try_into().unwrap();
}: _(SystemOrigin::Signed(caller.clone()), tips)
verify {
if !n.is_zero() {
assert_last_event::<T, I>(Event::TipSent {
collection,
item,
sender: caller.clone(),
receiver: caller.clone(),
amount,
}.into());
}
}
create_swap {
let (collection, caller, _) = create_collection::<T, I>();
let (item1, ..) = mint_item::<T, I>(0);
let (item2, ..) = mint_item::<T, I>(1);
let price = ItemPrice::<T, I>::from(100u32);
let price_direction = PriceDirection::Receive;
let price_with_direction = PriceWithDirection { amount: price, direction: price_direction };
let duration = T::MaxDeadlineDuration::get();
frame_system::Pallet::<T>::set_block_number(One::one());
}: _(SystemOrigin::Signed(caller.clone()), collection, item1, collection, Some(item2), Some(price_with_direction.clone()), duration)
verify {
let current_block = frame_system::Pallet::<T>::block_number();
assert_last_event::<T, I>(Event::SwapCreated {
offered_collection: collection,
offered_item: item1,
desired_collection: collection,
desired_item: Some(item2),
price: Some(price_with_direction),
deadline: current_block.saturating_add(duration),
}.into());
}
cancel_swap {
let (collection, caller, _) = create_collection::<T, I>();
let (item1, ..) = mint_item::<T, I>(0);
let (item2, ..) = mint_item::<T, I>(1);
let price = ItemPrice::<T, I>::from(100u32);
let origin = SystemOrigin::Signed(caller.clone()).into();
let duration = T::MaxDeadlineDuration::get();
let price_direction = PriceDirection::Receive;
let price_with_direction = PriceWithDirection { amount: price, direction: price_direction };
frame_system::Pallet::<T>::set_block_number(One::one());
Nfts::<T, I>::create_swap(origin, collection, item1, collection, Some(item2), Some(price_with_direction.clone()), duration)?;
}: _(SystemOrigin::Signed(caller.clone()), collection, item1)
verify {
assert_last_event::<T, I>(Event::SwapCancelled {
offered_collection: collection,
offered_item: item1,
desired_collection: collection,
desired_item: Some(item2),
price: Some(price_with_direction),
deadline: duration.saturating_add(One::one()),
}.into());
}
claim_swap {
let (collection, caller, _) = create_collection::<T, I>();
let (item1, ..) = mint_item::<T, I>(0);
let (item2, ..) = mint_item::<T, I>(1);
let price = ItemPrice::<T, I>::from(0u32);
let price_direction = PriceDirection::Receive;
let price_with_direction = PriceWithDirection { amount: price, direction: price_direction };
let duration = T::MaxDeadlineDuration::get();
let target: T::AccountId = account("target", 0, SEED);
let target_lookup = T::Lookup::unlookup(target.clone());
T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance());
let origin = SystemOrigin::Signed(caller.clone());
frame_system::Pallet::<T>::set_block_number(One::one());
Nfts::<T, I>::transfer(origin.clone().into(), collection, item2, target_lookup)?;
Nfts::<T, I>::create_swap(
origin.clone().into(),
collection,
item1,
collection,
Some(item2),
Some(price_with_direction.clone()),
duration,
)?;
}: _(SystemOrigin::Signed(target.clone()), collection, item2, collection, item1, Some(price_with_direction.clone()))
verify {
let current_block = frame_system::Pallet::<T>::block_number();
assert_last_event::<T, I>(Event::SwapClaimed {
sent_collection: collection,
sent_item: item2,
sent_item_owner: target,
received_collection: collection,
received_item: item1,
received_item_owner: caller,
price: Some(price_with_direction),
deadline: duration.saturating_add(One::one()),
}.into());
}
impl_benchmark_test_suite!(Nfts, crate::mock::new_test_ext(), crate::mock::Test);
}
@@ -0,0 +1,42 @@
// This file is part of Substrate.
// Copyright (C) 2017-2022 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.
//! Various pieces of common functionality.
use super::*;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Get the owner of the item, if the item exists.
pub fn owner(collection: T::CollectionId, item: T::ItemId) -> Option<T::AccountId> {
Item::<T, I>::get(collection, item).map(|i| i.owner)
}
/// Get the owner of the collection, if the collection exists.
pub fn collection_owner(collection: T::CollectionId) -> Option<T::AccountId> {
Collection::<T, I>::get(collection).map(|i| i.owner)
}
#[cfg(any(test, feature = "runtime-benchmarks"))]
pub fn set_next_id(id: T::CollectionId) {
NextCollectionId::<T, I>::set(Some(id));
}
#[cfg(test)]
pub fn get_next_id() -> T::CollectionId {
NextCollectionId::<T, I>::get().unwrap_or(T::CollectionId::initial_value())
}
}
@@ -0,0 +1,132 @@
// This file is part of Substrate.
// Copyright (C) 2022 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.
use crate::*;
use frame_support::pallet_prelude::*;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
pub(crate) fn do_approve_transfer(
maybe_check_origin: Option<T::AccountId>,
collection: T::CollectionId,
item: T::ItemId,
delegate: T::AccountId,
maybe_deadline: Option<<T as SystemConfig>::BlockNumber>,
) -> DispatchResult {
ensure!(
Self::is_pallet_feature_enabled(PalletFeature::Approvals),
Error::<T, I>::MethodDisabled
);
let mut details =
Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
let collection_config = Self::get_collection_config(&collection)?;
ensure!(
collection_config.is_setting_enabled(CollectionSetting::TransferableItems),
Error::<T, I>::ItemsNonTransferable
);
if let Some(check_origin) = maybe_check_origin {
let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin);
let permitted = is_admin || check_origin == details.owner;
ensure!(permitted, Error::<T, I>::NoPermission);
}
let now = frame_system::Pallet::<T>::block_number();
let deadline = maybe_deadline.map(|d| d.saturating_add(now));
details
.approvals
.try_insert(delegate.clone(), deadline)
.map_err(|_| Error::<T, I>::ReachedApprovalLimit)?;
Item::<T, I>::insert(&collection, &item, &details);
Self::deposit_event(Event::TransferApproved {
collection,
item,
owner: details.owner,
delegate,
deadline,
});
Ok(())
}
pub(crate) fn do_cancel_approval(
maybe_check_origin: Option<T::AccountId>,
collection: T::CollectionId,
item: T::ItemId,
delegate: T::AccountId,
) -> DispatchResult {
let mut details =
Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
let maybe_deadline = details.approvals.get(&delegate).ok_or(Error::<T, I>::NotDelegate)?;
let is_past_deadline = if let Some(deadline) = maybe_deadline {
let now = frame_system::Pallet::<T>::block_number();
now > *deadline
} else {
false
};
if !is_past_deadline {
if let Some(check_origin) = maybe_check_origin {
let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin);
let permitted = is_admin || check_origin == details.owner;
ensure!(permitted, Error::<T, I>::NoPermission);
}
}
details.approvals.remove(&delegate);
Item::<T, I>::insert(&collection, &item, &details);
Self::deposit_event(Event::ApprovalCancelled {
collection,
item,
owner: details.owner,
delegate,
});
Ok(())
}
pub(crate) fn do_clear_all_transfer_approvals(
maybe_check_origin: Option<T::AccountId>,
collection: T::CollectionId,
item: T::ItemId,
) -> DispatchResult {
let mut details =
Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownCollection)?;
if let Some(check_origin) = maybe_check_origin {
let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin);
let permitted = is_admin || check_origin == details.owner;
ensure!(permitted, Error::<T, I>::NoPermission);
}
details.approvals.clear();
Item::<T, I>::insert(&collection, &item, &details);
Self::deposit_event(Event::AllApprovalsCancelled {
collection,
item,
owner: details.owner,
});
Ok(())
}
}
@@ -0,0 +1,184 @@
// This file is part of Substrate.
// Copyright (C) 2022 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.
use crate::*;
use frame_support::{
pallet_prelude::*,
traits::{Currency, ExistenceRequirement::KeepAlive},
};
impl<T: Config<I>, I: 'static> Pallet<T, I> {
pub(crate) fn do_create_swap(
caller: T::AccountId,
offered_collection_id: T::CollectionId,
offered_item_id: T::ItemId,
desired_collection_id: T::CollectionId,
maybe_desired_item_id: Option<T::ItemId>,
maybe_price: Option<PriceWithDirection<ItemPrice<T, I>>>,
duration: <T as SystemConfig>::BlockNumber,
) -> DispatchResult {
ensure!(
Self::is_pallet_feature_enabled(PalletFeature::Swaps),
Error::<T, I>::MethodDisabled
);
ensure!(duration <= T::MaxDeadlineDuration::get(), Error::<T, I>::WrongDuration);
let item = Item::<T, I>::get(&offered_collection_id, &offered_item_id)
.ok_or(Error::<T, I>::UnknownItem)?;
ensure!(item.owner == caller, Error::<T, I>::NoPermission);
match maybe_desired_item_id {
Some(desired_item_id) => ensure!(
Item::<T, I>::contains_key(&desired_collection_id, &desired_item_id),
Error::<T, I>::UnknownItem
),
None => ensure!(
Collection::<T, I>::contains_key(&desired_collection_id),
Error::<T, I>::UnknownCollection
),
};
let now = frame_system::Pallet::<T>::block_number();
let deadline = duration.saturating_add(now);
PendingSwapOf::<T, I>::insert(
&offered_collection_id,
&offered_item_id,
PendingSwap {
desired_collection: desired_collection_id,
desired_item: maybe_desired_item_id,
price: maybe_price.clone(),
deadline,
},
);
Self::deposit_event(Event::SwapCreated {
offered_collection: offered_collection_id,
offered_item: offered_item_id,
desired_collection: desired_collection_id,
desired_item: maybe_desired_item_id,
price: maybe_price,
deadline,
});
Ok(())
}
pub(crate) fn do_cancel_swap(
caller: T::AccountId,
offered_collection_id: T::CollectionId,
offered_item_id: T::ItemId,
) -> DispatchResult {
let swap = PendingSwapOf::<T, I>::get(&offered_collection_id, &offered_item_id)
.ok_or(Error::<T, I>::UnknownSwap)?;
let now = frame_system::Pallet::<T>::block_number();
if swap.deadline > now {
let item = Item::<T, I>::get(&offered_collection_id, &offered_item_id)
.ok_or(Error::<T, I>::UnknownItem)?;
ensure!(item.owner == caller, Error::<T, I>::NoPermission);
}
PendingSwapOf::<T, I>::remove(&offered_collection_id, &offered_item_id);
Self::deposit_event(Event::SwapCancelled {
offered_collection: offered_collection_id,
offered_item: offered_item_id,
desired_collection: swap.desired_collection,
desired_item: swap.desired_item,
price: swap.price,
deadline: swap.deadline,
});
Ok(())
}
pub(crate) fn do_claim_swap(
caller: T::AccountId,
send_collection_id: T::CollectionId,
send_item_id: T::ItemId,
receive_collection_id: T::CollectionId,
receive_item_id: T::ItemId,
witness_price: Option<PriceWithDirection<ItemPrice<T, I>>>,
) -> DispatchResult {
ensure!(
Self::is_pallet_feature_enabled(PalletFeature::Swaps),
Error::<T, I>::MethodDisabled
);
let send_item = Item::<T, I>::get(&send_collection_id, &send_item_id)
.ok_or(Error::<T, I>::UnknownItem)?;
let receive_item = Item::<T, I>::get(&receive_collection_id, &receive_item_id)
.ok_or(Error::<T, I>::UnknownItem)?;
let swap = PendingSwapOf::<T, I>::get(&receive_collection_id, &receive_item_id)
.ok_or(Error::<T, I>::UnknownSwap)?;
ensure!(send_item.owner == caller, Error::<T, I>::NoPermission);
ensure!(
swap.desired_collection == send_collection_id && swap.price == witness_price,
Error::<T, I>::UnknownSwap
);
if let Some(desired_item) = swap.desired_item {
ensure!(desired_item == send_item_id, Error::<T, I>::UnknownSwap);
}
let now = frame_system::Pallet::<T>::block_number();
ensure!(now <= swap.deadline, Error::<T, I>::DeadlineExpired);
if let Some(ref price) = swap.price {
match price.direction {
PriceDirection::Send => T::Currency::transfer(
&receive_item.owner,
&send_item.owner,
price.amount,
KeepAlive,
)?,
PriceDirection::Receive => T::Currency::transfer(
&send_item.owner,
&receive_item.owner,
price.amount,
KeepAlive,
)?,
};
}
// 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,
receive_item_id,
send_item.owner.clone(),
|_, _| Ok(()),
)?;
Self::deposit_event(Event::SwapClaimed {
sent_collection: send_collection_id,
sent_item: send_item_id,
sent_item_owner: send_item.owner,
received_collection: receive_collection_id,
received_item: receive_item_id,
received_item_owner: receive_item.owner,
price: swap.price,
deadline: swap.deadline,
});
Ok(())
}
}
@@ -0,0 +1,323 @@
// This file is part of Substrate.
// Copyright (C) 2022 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.
use crate::*;
use frame_support::pallet_prelude::*;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
pub(crate) fn do_set_attribute(
origin: T::AccountId,
collection: T::CollectionId,
maybe_item: Option<T::ItemId>,
namespace: AttributeNamespace<T::AccountId>,
key: BoundedVec<u8, T::KeyLimit>,
value: BoundedVec<u8, T::ValueLimit>,
) -> DispatchResult {
ensure!(
Self::is_pallet_feature_enabled(PalletFeature::Attributes),
Error::<T, I>::MethodDisabled
);
let mut collection_details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
ensure!(
Self::is_valid_namespace(
&origin,
&namespace,
&collection,
&collection_details.owner,
&maybe_item,
)?,
Error::<T, I>::NoPermission
);
let collection_config = Self::get_collection_config(&collection)?;
// for the `CollectionOwner` namespace we need to check if the collection/item is not locked
match namespace {
AttributeNamespace::CollectionOwner => match maybe_item {
None => {
ensure!(
collection_config.is_setting_enabled(CollectionSetting::UnlockedAttributes),
Error::<T, I>::LockedCollectionAttributes
)
},
Some(item) => {
let maybe_is_locked = Self::get_item_config(&collection, &item)
.map(|c| c.has_disabled_setting(ItemSetting::UnlockedAttributes))?;
ensure!(!maybe_is_locked, Error::<T, I>::LockedItemAttributes);
},
},
_ => (),
}
let attribute = Attribute::<T, I>::get((collection, maybe_item, &namespace, &key));
if attribute.is_none() {
collection_details.attributes.saturating_inc();
}
let old_deposit =
attribute.map_or(AttributeDeposit { account: None, amount: Zero::zero() }, |m| m.1);
let mut deposit = Zero::zero();
if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) ||
namespace != AttributeNamespace::CollectionOwner
{
deposit = T::DepositPerByte::get()
.saturating_mul(((key.len() + value.len()) as u32).into())
.saturating_add(T::AttributeDepositBase::get());
}
// NOTE: when we transfer an item, we don't move attributes in the ItemOwner namespace.
// When the new owner updates the same attribute, we will update the depositor record
// and return the deposit to the previous owner.
if old_deposit.account.is_some() && old_deposit.account != Some(origin.clone()) {
T::Currency::unreserve(&old_deposit.account.unwrap(), old_deposit.amount);
T::Currency::reserve(&origin, deposit)?;
} else if deposit > old_deposit.amount {
T::Currency::reserve(&origin, deposit - old_deposit.amount)?;
} else if deposit < old_deposit.amount {
T::Currency::unreserve(&origin, old_deposit.amount - deposit);
}
// NOTE: we don't track the depositor in the CollectionOwner namespace as it's always a
// collection's owner. This simplifies the collection's transfer to another owner.
let deposit_owner = match namespace {
AttributeNamespace::CollectionOwner => {
collection_details.owner_deposit.saturating_accrue(deposit);
collection_details.owner_deposit.saturating_reduce(old_deposit.amount);
None
},
_ => Some(origin),
};
Attribute::<T, I>::insert(
(&collection, maybe_item, &namespace, &key),
(&value, AttributeDeposit { account: deposit_owner, amount: deposit }),
);
Collection::<T, I>::insert(collection, &collection_details);
Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value, namespace });
Ok(())
}
pub(crate) fn do_force_set_attribute(
set_as: Option<T::AccountId>,
collection: T::CollectionId,
maybe_item: Option<T::ItemId>,
namespace: AttributeNamespace<T::AccountId>,
key: BoundedVec<u8, T::KeyLimit>,
value: BoundedVec<u8, T::ValueLimit>,
) -> DispatchResult {
let mut collection_details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
let attribute = Attribute::<T, I>::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 {
T::Currency::unreserve(&deposit_account, deposit.amount);
}
}
} else {
collection_details.attributes.saturating_inc();
}
Attribute::<T, I>::insert(
(&collection, maybe_item, &namespace, &key),
(&value, AttributeDeposit { account: set_as, amount: Zero::zero() }),
);
Collection::<T, I>::insert(collection, &collection_details);
Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value, namespace });
Ok(())
}
pub(crate) fn do_clear_attribute(
maybe_check_owner: Option<T::AccountId>,
collection: T::CollectionId,
maybe_item: Option<T::ItemId>,
namespace: AttributeNamespace<T::AccountId>,
key: BoundedVec<u8, T::KeyLimit>,
) -> DispatchResult {
if let Some((_, deposit)) =
Attribute::<T, I>::take((collection, maybe_item, &namespace, &key))
{
let mut collection_details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
if let Some(check_owner) = &maybe_check_owner {
if deposit.account != maybe_check_owner {
ensure!(
Self::is_valid_namespace(
&check_owner,
&namespace,
&collection,
&collection_details.owner,
&maybe_item,
)?,
Error::<T, I>::NoPermission
);
}
// can't clear `CollectionOwner` type attributes if the collection/item is locked
match namespace {
AttributeNamespace::CollectionOwner => match maybe_item {
None => {
let collection_config = Self::get_collection_config(&collection)?;
ensure!(
collection_config
.is_setting_enabled(CollectionSetting::UnlockedAttributes),
Error::<T, I>::LockedCollectionAttributes
)
},
Some(item) => {
// NOTE: if the item was previously burned, the ItemConfigOf record
// might not exist. In that case, we allow to clear the attribute.
let maybe_is_locked = Self::get_item_config(&collection, &item)
.map_or(false, |c| {
c.has_disabled_setting(ItemSetting::UnlockedAttributes)
});
ensure!(!maybe_is_locked, Error::<T, I>::LockedItemAttributes);
},
},
_ => (),
};
}
collection_details.attributes.saturating_dec();
match namespace {
AttributeNamespace::CollectionOwner => {
collection_details.owner_deposit.saturating_reduce(deposit.amount);
T::Currency::unreserve(&collection_details.owner, deposit.amount);
},
_ => (),
};
if let Some(deposit_account) = deposit.account {
T::Currency::unreserve(&deposit_account, deposit.amount);
}
Collection::<T, I>::insert(collection, &collection_details);
Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key, namespace });
}
Ok(())
}
pub(crate) fn do_approve_item_attributes(
check_origin: T::AccountId,
collection: T::CollectionId,
item: T::ItemId,
delegate: T::AccountId,
) -> DispatchResult {
ensure!(
Self::is_pallet_feature_enabled(PalletFeature::Attributes),
Error::<T, I>::MethodDisabled
);
let details = Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
ensure!(check_origin == details.owner, Error::<T, I>::NoPermission);
ItemAttributesApprovalsOf::<T, I>::try_mutate(collection, item, |approvals| {
approvals
.try_insert(delegate.clone())
.map_err(|_| Error::<T, I>::ReachedApprovalLimit)?;
Self::deposit_event(Event::ItemAttributesApprovalAdded { collection, item, delegate });
Ok(())
})
}
pub(crate) fn do_cancel_item_attributes_approval(
check_origin: T::AccountId,
collection: T::CollectionId,
item: T::ItemId,
delegate: T::AccountId,
witness: CancelAttributesApprovalWitness,
) -> DispatchResult {
ensure!(
Self::is_pallet_feature_enabled(PalletFeature::Attributes),
Error::<T, I>::MethodDisabled
);
let details = Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
ensure!(check_origin == details.owner, Error::<T, I>::NoPermission);
ItemAttributesApprovalsOf::<T, I>::try_mutate(collection, item, |approvals| {
approvals.remove(&delegate);
let mut attributes: u32 = 0;
let mut deposited: DepositBalanceOf<T, I> = Zero::zero();
for (_, (_, deposit)) in Attribute::<T, I>::drain_prefix((
&collection,
Some(item),
AttributeNamespace::Account(delegate.clone()),
)) {
attributes.saturating_inc();
deposited = deposited.saturating_add(deposit.amount);
}
ensure!(attributes <= witness.account_attributes, Error::<T, I>::BadWitness);
if !deposited.is_zero() {
T::Currency::unreserve(&delegate, deposited);
}
Self::deposit_event(Event::ItemAttributesApprovalRemoved {
collection,
item,
delegate,
});
Ok(())
})
}
fn is_valid_namespace(
origin: &T::AccountId,
namespace: &AttributeNamespace<T::AccountId>,
collection: &T::CollectionId,
collection_owner: &T::AccountId,
maybe_item: &Option<T::ItemId>,
) -> Result<bool, DispatchError> {
let mut result = false;
match namespace {
AttributeNamespace::CollectionOwner => result = origin == collection_owner,
AttributeNamespace::ItemOwner =>
if let Some(item) = maybe_item {
let item_details =
Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
result = origin == &item_details.owner
},
AttributeNamespace::Account(account_id) =>
if let Some(item) = maybe_item {
let approvals = ItemAttributesApprovalsOf::<T, I>::get(&collection, &item);
result = account_id == origin && approvals.contains(&origin)
},
_ => (),
};
Ok(result)
}
/// A helper method to construct attribute's key.
pub fn construct_attribute_key(
key: Vec<u8>,
) -> Result<BoundedVec<u8, T::KeyLimit>, DispatchError> {
Ok(BoundedVec::try_from(key).map_err(|_| Error::<T, I>::IncorrectData)?)
}
/// A helper method to construct attribute's value.
pub fn construct_attribute_value(
value: Vec<u8>,
) -> Result<BoundedVec<u8, T::ValueLimit>, DispatchError> {
Ok(BoundedVec::try_from(value).map_err(|_| Error::<T, I>::IncorrectData)?)
}
}
@@ -0,0 +1,130 @@
// This file is part of Substrate.
// Copyright (C) 2022 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.
use crate::*;
use frame_support::{
pallet_prelude::*,
traits::{Currency, ExistenceRequirement, ExistenceRequirement::KeepAlive},
};
impl<T: Config<I>, I: 'static> Pallet<T, I> {
pub(crate) fn do_pay_tips(
sender: T::AccountId,
tips: BoundedVec<ItemTipOf<T, I>, T::MaxTips>,
) -> DispatchResult {
for tip in tips {
let ItemTip { collection, item, receiver, amount } = tip;
T::Currency::transfer(&sender, &receiver, amount, KeepAlive)?;
Self::deposit_event(Event::TipSent {
collection,
item,
sender: sender.clone(),
receiver,
amount,
});
}
Ok(())
}
pub(crate) fn do_set_price(
collection: T::CollectionId,
item: T::ItemId,
sender: T::AccountId,
price: Option<ItemPrice<T, I>>,
whitelisted_buyer: Option<T::AccountId>,
) -> DispatchResult {
ensure!(
Self::is_pallet_feature_enabled(PalletFeature::Trading),
Error::<T, I>::MethodDisabled
);
let details = Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
ensure!(details.owner == sender, Error::<T, I>::NoPermission);
let collection_config = Self::get_collection_config(&collection)?;
ensure!(
collection_config.is_setting_enabled(CollectionSetting::TransferableItems),
Error::<T, I>::ItemsNonTransferable
);
let item_config = Self::get_item_config(&collection, &item)?;
ensure!(
item_config.is_setting_enabled(ItemSetting::Transferable),
Error::<T, I>::ItemLocked
);
if let Some(ref price) = price {
ItemPriceOf::<T, I>::insert(&collection, &item, (price, whitelisted_buyer.clone()));
Self::deposit_event(Event::ItemPriceSet {
collection,
item,
price: *price,
whitelisted_buyer,
});
} else {
ItemPriceOf::<T, I>::remove(&collection, &item);
Self::deposit_event(Event::ItemPriceRemoved { collection, item });
}
Ok(())
}
pub(crate) fn do_buy_item(
collection: T::CollectionId,
item: T::ItemId,
buyer: T::AccountId,
bid_price: ItemPrice<T, I>,
) -> DispatchResult {
ensure!(
Self::is_pallet_feature_enabled(PalletFeature::Trading),
Error::<T, I>::MethodDisabled
);
let details = Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
ensure!(details.owner != buyer, Error::<T, I>::NoPermission);
let price_info =
ItemPriceOf::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::NotForSale)?;
ensure!(bid_price >= price_info.0, Error::<T, I>::BidTooLow);
if let Some(only_buyer) = price_info.1 {
ensure!(only_buyer == buyer, Error::<T, I>::NoPermission);
}
T::Currency::transfer(
&buyer,
&details.owner,
price_info.0,
ExistenceRequirement::KeepAlive,
)?;
let old_owner = details.owner.clone();
Self::do_transfer(collection, item, buyer.clone(), |_, _| Ok(()))?;
Self::deposit_event(Event::ItemBought {
collection,
item,
price: price_info.0,
seller: old_owner,
buyer,
});
Ok(())
}
}
@@ -0,0 +1,118 @@
// This file is part of Substrate.
// Copyright (C) 2022 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.
use crate::*;
use frame_support::pallet_prelude::*;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
pub fn do_create_collection(
collection: T::CollectionId,
owner: T::AccountId,
admin: T::AccountId,
config: CollectionConfigFor<T, I>,
deposit: DepositBalanceOf<T, I>,
event: Event<T, I>,
) -> DispatchResult {
ensure!(!Collection::<T, I>::contains_key(collection), Error::<T, I>::CollectionIdInUse);
T::Currency::reserve(&owner, deposit)?;
Collection::<T, I>::insert(
collection,
CollectionDetails {
owner: owner.clone(),
owner_deposit: deposit,
items: 0,
item_metadatas: 0,
attributes: 0,
},
);
CollectionRoleOf::<T, I>::insert(
collection,
admin,
CollectionRoles(
CollectionRole::Admin | CollectionRole::Freezer | CollectionRole::Issuer,
),
);
let next_id = collection.increment();
CollectionConfigOf::<T, I>::insert(&collection, config);
CollectionAccount::<T, I>::insert(&owner, &collection, ());
NextCollectionId::<T, I>::set(Some(next_id));
Self::deposit_event(Event::NextCollectionIdIncremented { next_id });
Self::deposit_event(event);
Ok(())
}
pub fn do_destroy_collection(
collection: T::CollectionId,
witness: DestroyWitness,
maybe_check_owner: Option<T::AccountId>,
) -> Result<DestroyWitness, DispatchError> {
Collection::<T, I>::try_mutate_exists(collection, |maybe_details| {
let collection_details =
maybe_details.take().ok_or(Error::<T, I>::UnknownCollection)?;
if let Some(check_owner) = maybe_check_owner {
ensure!(collection_details.owner == check_owner, Error::<T, I>::NoPermission);
}
ensure!(collection_details.items == witness.items, Error::<T, I>::BadWitness);
ensure!(
collection_details.item_metadatas == witness.item_metadatas,
Error::<T, I>::BadWitness
);
ensure!(collection_details.attributes == witness.attributes, Error::<T, I>::BadWitness);
for (item, details) in Item::<T, I>::drain_prefix(&collection) {
Account::<T, I>::remove((&details.owner, &collection, &item));
T::Currency::unreserve(&details.deposit.account, details.deposit.amount);
}
#[allow(deprecated)]
ItemMetadataOf::<T, I>::remove_prefix(&collection, None);
#[allow(deprecated)]
ItemPriceOf::<T, I>::remove_prefix(&collection, None);
#[allow(deprecated)]
PendingSwapOf::<T, I>::remove_prefix(&collection, None);
CollectionMetadataOf::<T, I>::remove(&collection);
Self::clear_roles(&collection)?;
for (_, (_, deposit)) in Attribute::<T, I>::drain_prefix((&collection,)) {
if !deposit.amount.is_zero() {
if let Some(account) = deposit.account {
T::Currency::unreserve(&account, deposit.amount);
}
}
}
CollectionAccount::<T, I>::remove(&collection_details.owner, &collection);
T::Currency::unreserve(&collection_details.owner, collection_details.owner_deposit);
CollectionConfigOf::<T, I>::remove(&collection);
let _ = ItemConfigOf::<T, I>::clear_prefix(&collection, witness.items, None);
let _ =
ItemAttributesApprovalsOf::<T, I>::clear_prefix(&collection, witness.items, None);
Self::deposit_event(Event::Destroyed { collection });
Ok(DestroyWitness {
items: collection_details.items,
item_metadatas: collection_details.item_metadatas,
attributes: collection_details.attributes,
})
})
}
}
@@ -0,0 +1,126 @@
// This file is part of Substrate.
// Copyright (C) 2022 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.
use crate::*;
use frame_support::pallet_prelude::*;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
pub fn do_mint(
collection: T::CollectionId,
item: T::ItemId,
depositor: T::AccountId,
mint_to: T::AccountId,
item_config: ItemConfig,
deposit_collection_owner: bool,
with_details_and_config: impl FnOnce(
&CollectionDetailsFor<T, I>,
&CollectionConfigFor<T, I>,
) -> DispatchResult,
) -> DispatchResult {
ensure!(!Item::<T, I>::contains_key(collection, item), Error::<T, I>::AlreadyExists);
Collection::<T, I>::try_mutate(
&collection,
|maybe_collection_details| -> DispatchResult {
let collection_details =
maybe_collection_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
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::<T, I>::MaxSupplyReached);
}
collection_details.items.saturating_inc();
let collection_config = Self::get_collection_config(&collection)?;
let deposit_amount = match collection_config
.is_setting_enabled(CollectionSetting::DepositRequired)
{
true => T::ItemDeposit::get(),
false => Zero::zero(),
};
let deposit_account = match deposit_collection_owner {
true => collection_details.owner.clone(),
false => depositor,
};
let item_owner = mint_to.clone();
Account::<T, I>::insert((&item_owner, &collection, &item), ());
if let Ok(existing_config) = ItemConfigOf::<T, I>::try_get(&collection, &item) {
ensure!(existing_config == item_config, Error::<T, I>::InconsistentItemConfig);
} else {
ItemConfigOf::<T, I>::insert(&collection, &item, item_config);
}
T::Currency::reserve(&deposit_account, deposit_amount)?;
let deposit = ItemDeposit { account: deposit_account, amount: deposit_amount };
let details = ItemDetails {
owner: item_owner,
approvals: ApprovalsOf::<T, I>::default(),
deposit,
};
Item::<T, I>::insert(&collection, &item, details);
Ok(())
},
)?;
Self::deposit_event(Event::Issued { collection, item, owner: mint_to });
Ok(())
}
pub fn do_burn(
collection: T::CollectionId,
item: T::ItemId,
with_details: impl FnOnce(&ItemDetailsFor<T, I>) -> DispatchResult,
) -> DispatchResult {
let owner = Collection::<T, I>::try_mutate(
&collection,
|maybe_collection_details| -> Result<T::AccountId, DispatchError> {
let collection_details =
maybe_collection_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
let details = Item::<T, I>::get(&collection, &item)
.ok_or(Error::<T, I>::UnknownCollection)?;
with_details(&details)?;
// Return the deposit.
T::Currency::unreserve(&details.deposit.account, details.deposit.amount);
collection_details.items.saturating_dec();
Ok(details.owner)
},
)?;
Item::<T, I>::remove(&collection, &item);
Account::<T, I>::remove((&owner, &collection, &item));
ItemPriceOf::<T, I>::remove(&collection, &item);
PendingSwapOf::<T, I>::remove(&collection, &item);
ItemAttributesApprovalsOf::<T, I>::remove(&collection, &item);
// NOTE: if item's settings are not empty (e.g. item's metadata is locked)
// then we keep the record and don't remove it
let config = Self::get_item_config(&collection, &item)?;
if !config.has_disabled_settings() {
ItemConfigOf::<T, I>::remove(&collection, &item);
}
Self::deposit_event(Event::Burned { collection, item, owner });
Ok(())
}
}
+120
View File
@@ -0,0 +1,120 @@
// This file is part of Substrate.
// Copyright (C) 2022 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.
use crate::*;
use frame_support::pallet_prelude::*;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
pub(crate) fn do_lock_collection(
origin: T::AccountId,
collection: T::CollectionId,
lock_settings: CollectionSettings,
) -> DispatchResult {
ensure!(
Self::has_role(&collection, &origin, CollectionRole::Freezer),
Error::<T, I>::NoPermission
);
ensure!(
!lock_settings.is_disabled(CollectionSetting::DepositRequired),
Error::<T, I>::WrongSetting
);
CollectionConfigOf::<T, I>::try_mutate(collection, |maybe_config| {
let config = maybe_config.as_mut().ok_or(Error::<T, I>::NoConfig)?;
for setting in lock_settings.get_disabled() {
config.disable_setting(setting);
}
Self::deposit_event(Event::<T, I>::CollectionLocked { collection });
Ok(())
})
}
pub(crate) fn do_lock_item_transfer(
origin: T::AccountId,
collection: T::CollectionId,
item: T::ItemId,
) -> DispatchResult {
ensure!(
Self::has_role(&collection, &origin, CollectionRole::Freezer),
Error::<T, I>::NoPermission
);
let mut config = Self::get_item_config(&collection, &item)?;
if !config.has_disabled_setting(ItemSetting::Transferable) {
config.disable_setting(ItemSetting::Transferable);
}
ItemConfigOf::<T, I>::insert(&collection, &item, config);
Self::deposit_event(Event::<T, I>::ItemTransferLocked { collection, item });
Ok(())
}
pub(crate) fn do_unlock_item_transfer(
origin: T::AccountId,
collection: T::CollectionId,
item: T::ItemId,
) -> DispatchResult {
ensure!(
Self::has_role(&collection, &origin, CollectionRole::Freezer),
Error::<T, I>::NoPermission
);
let mut config = Self::get_item_config(&collection, &item)?;
if config.has_disabled_setting(ItemSetting::Transferable) {
config.enable_setting(ItemSetting::Transferable);
}
ItemConfigOf::<T, I>::insert(&collection, &item, config);
Self::deposit_event(Event::<T, I>::ItemTransferUnlocked { collection, item });
Ok(())
}
pub(crate) fn do_lock_item_properties(
maybe_check_owner: Option<T::AccountId>,
collection: T::CollectionId,
item: T::ItemId,
lock_metadata: bool,
lock_attributes: bool,
) -> DispatchResult {
let collection_details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
if let Some(check_owner) = &maybe_check_owner {
ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
}
ItemConfigOf::<T, I>::try_mutate(collection, item, |maybe_config| {
let config = maybe_config.as_mut().ok_or(Error::<T, I>::UnknownItem)?;
if lock_metadata {
config.disable_setting(ItemSetting::UnlockedMetadata);
}
if lock_attributes {
config.disable_setting(ItemSetting::UnlockedAttributes);
}
Self::deposit_event(Event::<T, I>::ItemPropertiesLocked {
collection,
item,
lock_metadata,
lock_attributes,
});
Ok(())
})
}
}
@@ -0,0 +1,173 @@
// This file is part of Substrate.
// Copyright (C) 2022 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.
use crate::*;
use frame_support::pallet_prelude::*;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
pub(crate) fn do_set_item_metadata(
maybe_check_owner: Option<T::AccountId>,
collection: T::CollectionId,
item: T::ItemId,
data: BoundedVec<u8, T::StringLimit>,
) -> DispatchResult {
let mut collection_details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
let item_config = Self::get_item_config(&collection, &item)?;
ensure!(
maybe_check_owner.is_none() ||
item_config.is_setting_enabled(ItemSetting::UnlockedMetadata),
Error::<T, I>::LockedItemMetadata
);
if let Some(check_owner) = &maybe_check_owner {
ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
}
let collection_config = Self::get_collection_config(&collection)?;
ItemMetadataOf::<T, I>::try_mutate_exists(collection, item, |metadata| {
if metadata.is_none() {
collection_details.item_metadatas.saturating_inc();
}
let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
collection_details.owner_deposit.saturating_reduce(old_deposit);
let mut deposit = Zero::zero();
if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) &&
maybe_check_owner.is_some()
{
deposit = T::DepositPerByte::get()
.saturating_mul(((data.len()) as u32).into())
.saturating_add(T::MetadataDepositBase::get());
}
if deposit > old_deposit {
T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?;
} else if deposit < old_deposit {
T::Currency::unreserve(&collection_details.owner, old_deposit - deposit);
}
collection_details.owner_deposit.saturating_accrue(deposit);
*metadata = Some(ItemMetadata { deposit, data: data.clone() });
Collection::<T, I>::insert(&collection, &collection_details);
Self::deposit_event(Event::ItemMetadataSet { collection, item, data });
Ok(())
})
}
pub(crate) fn do_clear_item_metadata(
maybe_check_owner: Option<T::AccountId>,
collection: T::CollectionId,
item: T::ItemId,
) -> DispatchResult {
let mut collection_details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
if let Some(check_owner) = &maybe_check_owner {
ensure!(check_owner == &collection_details.owner, Error::<T, I>::NoPermission);
}
// NOTE: if the item was previously burned, the ItemConfigOf record might not exist
let is_locked = Self::get_item_config(&collection, &item)
.map_or(false, |c| c.has_disabled_setting(ItemSetting::UnlockedMetadata));
ensure!(maybe_check_owner.is_none() || !is_locked, Error::<T, I>::LockedItemMetadata);
ItemMetadataOf::<T, I>::try_mutate_exists(collection, item, |metadata| {
if metadata.is_some() {
collection_details.item_metadatas.saturating_dec();
}
let deposit = metadata.take().ok_or(Error::<T, I>::UnknownItem)?.deposit;
T::Currency::unreserve(&collection_details.owner, deposit);
collection_details.owner_deposit.saturating_reduce(deposit);
Collection::<T, I>::insert(&collection, &collection_details);
Self::deposit_event(Event::ItemMetadataCleared { collection, item });
Ok(())
})
}
pub(crate) fn do_set_collection_metadata(
maybe_check_owner: Option<T::AccountId>,
collection: T::CollectionId,
data: BoundedVec<u8, T::StringLimit>,
) -> DispatchResult {
let collection_config = Self::get_collection_config(&collection)?;
ensure!(
maybe_check_owner.is_none() ||
collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata),
Error::<T, I>::LockedCollectionMetadata
);
let mut details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
if let Some(check_owner) = &maybe_check_owner {
ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
}
CollectionMetadataOf::<T, I>::try_mutate_exists(collection, |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();
if maybe_check_owner.is_some() &&
collection_config.is_setting_enabled(CollectionSetting::DepositRequired)
{
deposit = T::DepositPerByte::get()
.saturating_mul(((data.len()) as u32).into())
.saturating_add(T::MetadataDepositBase::get());
}
if deposit > old_deposit {
T::Currency::reserve(&details.owner, deposit - old_deposit)?;
} else if deposit < old_deposit {
T::Currency::unreserve(&details.owner, old_deposit - deposit);
}
details.owner_deposit.saturating_accrue(deposit);
Collection::<T, I>::insert(&collection, details);
*metadata = Some(CollectionMetadata { deposit, data: data.clone() });
Self::deposit_event(Event::CollectionMetadataSet { collection, data });
Ok(())
})
}
pub(crate) fn do_clear_collection_metadata(
maybe_check_owner: Option<T::AccountId>,
collection: T::CollectionId,
) -> DispatchResult {
let details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
if let Some(check_owner) = &maybe_check_owner {
ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
}
let collection_config = Self::get_collection_config(&collection)?;
ensure!(
maybe_check_owner.is_none() ||
collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata),
Error::<T, I>::LockedCollectionMetadata
);
CollectionMetadataOf::<T, I>::try_mutate_exists(collection, |metadata| {
let deposit = metadata.take().ok_or(Error::<T, I>::UnknownCollection)?.deposit;
T::Currency::unreserve(&details.owner, deposit);
Self::deposit_event(Event::CollectionMetadataCleared { collection });
Ok(())
})
}
}
+28
View File
@@ -0,0 +1,28 @@
// This file is part of Substrate.
// Copyright (C) 2022 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.
pub mod approvals;
pub mod atomic_swap;
pub mod attributes;
pub mod buy_sell;
pub mod create_delete_collection;
pub mod create_delete_item;
pub mod lock;
pub mod metadata;
pub mod roles;
pub mod settings;
pub mod transfer;
@@ -0,0 +1,99 @@
// This file is part of Substrate.
// Copyright (C) 2022 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.
use crate::*;
use frame_support::pallet_prelude::*;
use sp_std::collections::btree_map::BTreeMap;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
pub(crate) fn do_set_team(
maybe_check_owner: Option<T::AccountId>,
collection: T::CollectionId,
issuer: T::AccountId,
admin: T::AccountId,
freezer: T::AccountId,
) -> DispatchResult {
Collection::<T, I>::try_mutate(collection, |maybe_details| {
let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
if let Some(check_origin) = maybe_check_owner {
ensure!(check_origin == details.owner, Error::<T, I>::NoPermission);
}
// delete previous values
Self::clear_roles(&collection)?;
let account_to_role = Self::group_roles_by_account(vec![
(issuer.clone(), CollectionRole::Issuer),
(admin.clone(), CollectionRole::Admin),
(freezer.clone(), CollectionRole::Freezer),
]);
for (account, roles) in account_to_role {
CollectionRoleOf::<T, I>::insert(&collection, &account, roles);
}
Self::deposit_event(Event::TeamChanged { collection, issuer, admin, freezer });
Ok(())
})
}
/// Clears all the roles in a specified collection.
///
/// - `collection_id`: A collection to clear the roles in.
///
/// Throws an error if some of the roles were left in storage.
/// This means the `CollectionRoles::max_roles()` needs to be adjusted.
pub(crate) fn clear_roles(collection_id: &T::CollectionId) -> Result<(), DispatchError> {
let res = CollectionRoleOf::<T, I>::clear_prefix(
&collection_id,
CollectionRoles::max_roles() as u32,
None,
);
ensure!(res.maybe_cursor.is_none(), Error::<T, I>::RolesNotCleared);
Ok(())
}
/// Returns true if a specified account has a provided role within that collection.
///
/// - `collection_id`: A collection to check the role in.
/// - `account_id`: An account to check the role for.
/// - `role`: A role to validate.
///
/// Returns boolean.
pub(crate) fn has_role(
collection_id: &T::CollectionId,
account_id: &T::AccountId,
role: CollectionRole,
) -> bool {
CollectionRoleOf::<T, I>::get(&collection_id, &account_id)
.map_or(false, |roles| roles.has_role(role))
}
/// Groups provided roles by account, given one account could have multiple roles.
///
/// - `input`: A vector of (Account, Role) tuples.
///
/// Returns a grouped vector.
pub fn group_roles_by_account(
input: Vec<(T::AccountId, CollectionRole)>,
) -> Vec<(T::AccountId, CollectionRoles)> {
let mut result = BTreeMap::new();
for (account, role) in input.into_iter() {
result.entry(account).or_insert(CollectionRoles::none()).add_role(role);
}
result.into_iter().collect()
}
}
@@ -0,0 +1,103 @@
// This file is part of Substrate.
// Copyright (C) 2022 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.
use crate::*;
use frame_support::pallet_prelude::*;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
pub(crate) fn do_force_collection_config(
collection: T::CollectionId,
config: CollectionConfigFor<T, I>,
) -> DispatchResult {
ensure!(Collection::<T, I>::contains_key(&collection), Error::<T, I>::UnknownCollection);
CollectionConfigOf::<T, I>::insert(&collection, config);
Self::deposit_event(Event::CollectionConfigChanged { collection });
Ok(())
}
pub(crate) fn do_set_collection_max_supply(
maybe_check_owner: Option<T::AccountId>,
collection: T::CollectionId,
max_supply: u32,
) -> DispatchResult {
let collection_config = Self::get_collection_config(&collection)?;
ensure!(
collection_config.is_setting_enabled(CollectionSetting::UnlockedMaxSupply),
Error::<T, I>::MaxSupplyLocked
);
let details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
if let Some(check_owner) = &maybe_check_owner {
ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
}
ensure!(details.items <= max_supply, Error::<T, I>::MaxSupplyTooSmall);
CollectionConfigOf::<T, I>::try_mutate(collection, |maybe_config| {
let config = maybe_config.as_mut().ok_or(Error::<T, I>::NoConfig)?;
config.max_supply = Some(max_supply);
Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply });
Ok(())
})
}
pub(crate) fn do_update_mint_settings(
maybe_check_owner: Option<T::AccountId>,
collection: T::CollectionId,
mint_settings: MintSettings<
BalanceOf<T, I>,
<T as SystemConfig>::BlockNumber,
T::CollectionId,
>,
) -> DispatchResult {
let details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
if let Some(check_owner) = &maybe_check_owner {
ensure!(check_owner == &details.owner, Error::<T, I>::NoPermission);
}
CollectionConfigOf::<T, I>::try_mutate(collection, |maybe_config| {
let config = maybe_config.as_mut().ok_or(Error::<T, I>::NoConfig)?;
config.mint_settings = mint_settings;
Self::deposit_event(Event::CollectionMintSettingsUpdated { collection });
Ok(())
})
}
pub(crate) fn get_collection_config(
collection_id: &T::CollectionId,
) -> Result<CollectionConfigFor<T, I>, DispatchError> {
let config =
CollectionConfigOf::<T, I>::get(&collection_id).ok_or(Error::<T, I>::NoConfig)?;
Ok(config)
}
pub(crate) fn get_item_config(
collection_id: &T::CollectionId,
item_id: &T::ItemId,
) -> Result<ItemConfig, DispatchError> {
let config = ItemConfigOf::<T, I>::get(&collection_id, &item_id)
.ok_or(Error::<T, I>::UnknownItem)?;
Ok(config)
}
pub(crate) fn is_pallet_feature_enabled(feature: PalletFeature) -> bool {
let features = T::Features::get();
return features.is_enabled(feature)
}
}
@@ -0,0 +1,166 @@
// This file is part of Substrate.
// Copyright (C) 2022 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.
use crate::*;
use frame_support::pallet_prelude::*;
impl<T: Config<I>, I: 'static> Pallet<T, I> {
pub fn do_transfer(
collection: T::CollectionId,
item: T::ItemId,
dest: T::AccountId,
with_details: impl FnOnce(
&CollectionDetailsFor<T, I>,
&mut ItemDetailsFor<T, I>,
) -> DispatchResult,
) -> DispatchResult {
let collection_details =
Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
ensure!(!T::Locker::is_locked(collection, item), Error::<T, I>::ItemLocked);
let collection_config = Self::get_collection_config(&collection)?;
ensure!(
collection_config.is_setting_enabled(CollectionSetting::TransferableItems),
Error::<T, I>::ItemsNonTransferable
);
let item_config = Self::get_item_config(&collection, &item)?;
ensure!(
item_config.is_setting_enabled(ItemSetting::Transferable),
Error::<T, I>::ItemLocked
);
let mut details =
Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
with_details(&collection_details, &mut details)?;
if details.deposit.account == details.owner {
// Move the deposit to the new owner.
T::Currency::repatriate_reserved(
&details.owner,
&dest,
details.deposit.amount,
Reserved,
)?;
}
Account::<T, I>::remove((&details.owner, &collection, &item));
Account::<T, I>::insert((&dest, &collection, &item), ());
let origin = details.owner;
details.owner = dest;
// The approved accounts have to be reset to None, because otherwise pre-approve attack
// would be possible, where the owner can approve his second account before making the
// transaction and then claiming the item back.
details.approvals.clear();
Item::<T, I>::insert(&collection, &item, &details);
ItemPriceOf::<T, I>::remove(&collection, &item);
PendingSwapOf::<T, I>::remove(&collection, &item);
Self::deposit_event(Event::Transferred {
collection,
item,
from: origin,
to: details.owner,
});
Ok(())
}
pub(crate) fn do_transfer_ownership(
origin: T::AccountId,
collection: T::CollectionId,
owner: T::AccountId,
) -> DispatchResult {
let acceptable_collection = OwnershipAcceptance::<T, I>::get(&owner);
ensure!(acceptable_collection.as_ref() == Some(&collection), Error::<T, I>::Unaccepted);
Collection::<T, I>::try_mutate(collection, |maybe_details| {
let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
ensure!(origin == details.owner, Error::<T, I>::NoPermission);
if details.owner == owner {
return Ok(())
}
// Move the deposit to the new owner.
T::Currency::repatriate_reserved(
&details.owner,
&owner,
details.owner_deposit,
Reserved,
)?;
CollectionAccount::<T, I>::remove(&details.owner, &collection);
CollectionAccount::<T, I>::insert(&owner, &collection, ());
details.owner = owner.clone();
OwnershipAcceptance::<T, I>::remove(&owner);
Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner });
Ok(())
})
}
pub(crate) fn do_set_accept_ownership(
who: T::AccountId,
maybe_collection: Option<T::CollectionId>,
) -> DispatchResult {
let old = OwnershipAcceptance::<T, I>::get(&who);
match (old.is_some(), maybe_collection.is_some()) {
(false, true) => {
frame_system::Pallet::<T>::inc_consumers(&who)?;
},
(true, false) => {
frame_system::Pallet::<T>::dec_consumers(&who);
},
_ => {},
}
if let Some(collection) = maybe_collection.as_ref() {
OwnershipAcceptance::<T, I>::insert(&who, collection);
} else {
OwnershipAcceptance::<T, I>::remove(&who);
}
Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection });
Ok(())
}
pub(crate) fn do_force_collection_owner(
collection: T::CollectionId,
owner: T::AccountId,
) -> DispatchResult {
Collection::<T, I>::try_mutate(collection, |maybe_details| {
let details = maybe_details.as_mut().ok_or(Error::<T, I>::UnknownCollection)?;
if details.owner == owner {
return Ok(())
}
// Move the deposit to the new owner.
T::Currency::repatriate_reserved(
&details.owner,
&owner,
details.owner_deposit,
Reserved,
)?;
CollectionAccount::<T, I>::remove(&details.owner, &collection);
CollectionAccount::<T, I>::insert(&owner, &collection, ());
details.owner = owner.clone();
Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner });
Ok(())
})
}
}
@@ -0,0 +1,289 @@
// This file is part of Substrate.
// Copyright (C) 2017-2022 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.
//! Implementations for `nonfungibles` traits.
use super::*;
use frame_support::{
ensure,
storage::KeyPrefixIterator,
traits::{tokens::nonfungibles_v2::*, Get},
BoundedSlice,
};
use sp_runtime::{DispatchError, DispatchResult};
use sp_std::prelude::*;
impl<T: Config<I>, I: 'static> Inspect<<T as SystemConfig>::AccountId> for Pallet<T, I> {
type ItemId = T::ItemId;
type CollectionId = T::CollectionId;
fn owner(
collection: &Self::CollectionId,
item: &Self::ItemId,
) -> Option<<T as SystemConfig>::AccountId> {
Item::<T, I>::get(collection, item).map(|a| a.owner)
}
fn collection_owner(collection: &Self::CollectionId) -> Option<<T as SystemConfig>::AccountId> {
Collection::<T, I>::get(collection).map(|a| a.owner)
}
/// Returns the attribute value of `item` of `collection` corresponding to `key`.
///
/// When `key` is empty, we return the item metadata value.
///
/// By default this is `None`; no attributes are defined.
fn attribute(
collection: &Self::CollectionId,
item: &Self::ItemId,
namespace: &AttributeNamespace<<T as SystemConfig>::AccountId>,
key: &[u8],
) -> Option<Vec<u8>> {
if key.is_empty() {
// We make the empty key map to the item metadata value.
ItemMetadataOf::<T, I>::get(collection, item).map(|m| m.data.into())
} else {
let key = BoundedSlice::<_, _>::try_from(key).ok()?;
Attribute::<T, I>::get((collection, Some(item), namespace, key)).map(|a| a.0.into())
}
}
/// Returns the attribute value of `item` of `collection` corresponding to `key`.
///
/// When `key` is empty, we return the item metadata value.
///
/// By default this is `None`; no attributes are defined.
fn collection_attribute(collection: &Self::CollectionId, key: &[u8]) -> Option<Vec<u8>> {
if key.is_empty() {
// We make the empty key map to the item metadata value.
CollectionMetadataOf::<T, I>::get(collection).map(|m| m.data.into())
} else {
let key = BoundedSlice::<_, _>::try_from(key).ok()?;
Attribute::<T, I>::get((
collection,
Option::<T::ItemId>::None,
AttributeNamespace::CollectionOwner,
key,
))
.map(|a| a.0.into())
}
}
/// Returns `true` if the `item` of `collection` may be transferred.
///
/// Default implementation is that all items are transferable.
fn can_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> bool {
match (
CollectionConfigOf::<T, I>::get(collection),
ItemConfigOf::<T, I>::get(collection, item),
) {
(Some(cc), Some(ic))
if cc.is_setting_enabled(CollectionSetting::TransferableItems) &&
ic.is_setting_enabled(ItemSetting::Transferable) =>
true,
_ => false,
}
}
}
impl<T: Config<I>, I: 'static> Create<<T as SystemConfig>::AccountId, CollectionConfigFor<T, I>>
for Pallet<T, I>
{
/// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`.
fn create_collection(
who: &T::AccountId,
admin: &T::AccountId,
config: &CollectionConfigFor<T, I>,
) -> Result<T::CollectionId, DispatchError> {
// DepositRequired can be disabled by calling the force_create() only
ensure!(
!config.has_disabled_setting(CollectionSetting::DepositRequired),
Error::<T, I>::WrongSetting
);
let collection =
NextCollectionId::<T, I>::get().unwrap_or(T::CollectionId::initial_value());
Self::do_create_collection(
collection,
who.clone(),
admin.clone(),
*config,
T::CollectionDeposit::get(),
Event::Created { collection, creator: who.clone(), owner: admin.clone() },
)?;
Ok(collection)
}
}
impl<T: Config<I>, I: 'static> Destroy<<T as SystemConfig>::AccountId> for Pallet<T, I> {
type DestroyWitness = DestroyWitness;
fn get_destroy_witness(collection: &Self::CollectionId) -> Option<DestroyWitness> {
Collection::<T, I>::get(collection).map(|a| a.destroy_witness())
}
fn destroy(
collection: Self::CollectionId,
witness: Self::DestroyWitness,
maybe_check_owner: Option<T::AccountId>,
) -> Result<Self::DestroyWitness, DispatchError> {
Self::do_destroy_collection(collection, witness, maybe_check_owner)
}
}
impl<T: Config<I>, I: 'static> Mutate<<T as SystemConfig>::AccountId, ItemConfig> for Pallet<T, I> {
fn mint_into(
collection: &Self::CollectionId,
item: &Self::ItemId,
who: &T::AccountId,
item_config: &ItemConfig,
deposit_collection_owner: bool,
) -> DispatchResult {
Self::do_mint(
*collection,
*item,
who.clone(),
who.clone(),
*item_config,
deposit_collection_owner,
|_, _| Ok(()),
)
}
fn burn(
collection: &Self::CollectionId,
item: &Self::ItemId,
maybe_check_owner: Option<&T::AccountId>,
) -> DispatchResult {
Self::do_burn(*collection, *item, |d| {
if let Some(check_owner) = maybe_check_owner {
if &d.owner != check_owner {
return Err(Error::<T, I>::NoPermission.into())
}
}
Ok(())
})
}
fn set_attribute(
collection: &Self::CollectionId,
item: &Self::ItemId,
key: &[u8],
value: &[u8],
) -> DispatchResult {
Self::do_force_set_attribute(
None,
*collection,
Some(*item),
AttributeNamespace::Pallet,
Self::construct_attribute_key(key.to_vec())?,
Self::construct_attribute_value(value.to_vec())?,
)
}
fn set_typed_attribute<K: Encode, V: Encode>(
collection: &Self::CollectionId,
item: &Self::ItemId,
key: &K,
value: &V,
) -> DispatchResult {
key.using_encoded(|k| {
value.using_encoded(|v| {
<Self as Mutate<T::AccountId, ItemConfig>>::set_attribute(collection, item, k, v)
})
})
}
fn set_collection_attribute(
collection: &Self::CollectionId,
key: &[u8],
value: &[u8],
) -> DispatchResult {
Self::do_force_set_attribute(
None,
*collection,
None,
AttributeNamespace::Pallet,
Self::construct_attribute_key(key.to_vec())?,
Self::construct_attribute_value(value.to_vec())?,
)
}
fn set_typed_collection_attribute<K: Encode, V: Encode>(
collection: &Self::CollectionId,
key: &K,
value: &V,
) -> DispatchResult {
key.using_encoded(|k| {
value.using_encoded(|v| {
<Self as Mutate<T::AccountId, ItemConfig>>::set_collection_attribute(
collection, k, v,
)
})
})
}
}
impl<T: Config<I>, I: 'static> Transfer<T::AccountId> for Pallet<T, I> {
fn transfer(
collection: &Self::CollectionId,
item: &Self::ItemId,
destination: &T::AccountId,
) -> DispatchResult {
Self::do_transfer(*collection, *item, destination.clone(), |_, _| Ok(()))
}
}
impl<T: Config<I>, I: 'static> InspectEnumerable<T::AccountId> for Pallet<T, I> {
type CollectionsIterator = KeyPrefixIterator<<T as Config<I>>::CollectionId>;
type ItemsIterator = KeyPrefixIterator<<T as Config<I>>::ItemId>;
type OwnedIterator =
KeyPrefixIterator<(<T as Config<I>>::CollectionId, <T as Config<I>>::ItemId)>;
type OwnedInCollectionIterator = KeyPrefixIterator<<T as Config<I>>::ItemId>;
/// Returns an iterator of the collections in existence.
///
/// NOTE: iterating this list invokes a storage read per item.
fn collections() -> Self::CollectionsIterator {
Collection::<T, I>::iter_keys()
}
/// Returns an iterator of the items of a `collection` in existence.
///
/// NOTE: iterating this list invokes a storage read per item.
fn items(collection: &Self::CollectionId) -> Self::ItemsIterator {
Item::<T, I>::iter_key_prefix(collection)
}
/// Returns an iterator of the items of all collections owned by `who`.
///
/// NOTE: iterating this list invokes a storage read per item.
fn owned(who: &T::AccountId) -> Self::OwnedIterator {
Account::<T, I>::iter_key_prefix((who,))
}
/// Returns an iterator of the items of `collection` owned by `who`.
///
/// NOTE: iterating this list invokes a storage read per item.
fn owned_in_collection(
collection: &Self::CollectionId,
who: &T::AccountId,
) -> Self::OwnedInCollectionIterator {
Account::<T, I>::iter_key_prefix((who, collection))
}
}
File diff suppressed because it is too large Load Diff
+74
View File
@@ -0,0 +1,74 @@
// This file is part of Substrate.
// Copyright (C) 2022 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.
macro_rules! impl_incrementable {
($($type:ty),+) => {
$(
impl Incrementable for $type {
fn increment(&self) -> Self {
let mut val = self.clone();
val.saturating_inc();
val
}
fn initial_value() -> Self {
0
}
}
)+
};
}
pub(crate) use impl_incrementable;
macro_rules! impl_codec_bitflags {
($wrapper:ty, $size:ty, $bitflag_enum:ty) => {
impl MaxEncodedLen for $wrapper {
fn max_encoded_len() -> usize {
<$size>::max_encoded_len()
}
}
impl Encode for $wrapper {
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
self.0.bits().using_encoded(f)
}
}
impl EncodeLike for $wrapper {}
impl Decode for $wrapper {
fn decode<I: codec::Input>(
input: &mut I,
) -> sp_std::result::Result<Self, codec::Error> {
let field = <$size>::decode(input)?;
Ok(Self(BitFlags::from_bits(field as $size).map_err(|_| "invalid value")?))
}
}
impl TypeInfo for $wrapper {
type Identity = Self;
fn type_info() -> Type {
Type::builder()
.path(Path::new("BitFlags", module_path!()))
.type_params(vec![TypeParameter::new("T", Some(meta_type::<$bitflag_enum>()))])
.composite(
Fields::unnamed()
.field(|f| f.ty::<$size>().type_name(stringify!($bitflag_enum))),
)
}
}
};
}
pub(crate) use impl_codec_bitflags;
+123
View File
@@ -0,0 +1,123 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 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.
//! Test environment for Nfts pallet.
use super::*;
use crate as pallet_nfts;
use frame_support::{
construct_runtime, parameter_types,
traits::{AsEnsureOriginWithArg, ConstU32, ConstU64},
};
use sp_core::H256;
use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentityLookup},
};
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
construct_runtime!(
pub enum Test where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
Nfts: pallet_nfts::{Pallet, Call, Storage, Event<T>},
}
);
impl frame_system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type BlockWeights = ();
type BlockLength = ();
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type Index = u64;
type BlockNumber = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = ConstU64<250>;
type DbWeight = ();
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = ConstU32<16>;
}
impl pallet_balances::Config for Test {
type Balance = u64;
type DustRemoval = ();
type RuntimeEvent = RuntimeEvent;
type ExistentialDeposit = ConstU64<1>;
type AccountStore = System;
type WeightInfo = ();
type MaxLocks = ();
type MaxReserves = ConstU32<50>;
type ReserveIdentifier = [u8; 8];
}
parameter_types! {
pub storage Features: PalletFeatures = PalletFeatures::all_enabled();
}
impl Config for Test {
type RuntimeEvent = RuntimeEvent;
type CollectionId = u32;
type ItemId = u32;
type Currency = Balances;
type CreateOrigin = AsEnsureOriginWithArg<frame_system::EnsureSigned<u64>>;
type ForceOrigin = frame_system::EnsureRoot<u64>;
type Locker = ();
type CollectionDeposit = ConstU64<2>;
type ItemDeposit = ConstU64<1>;
type MetadataDepositBase = ConstU64<1>;
type AttributeDepositBase = ConstU64<1>;
type DepositPerByte = ConstU64<1>;
type StringLimit = ConstU32<50>;
type KeyLimit = ConstU32<50>;
type ValueLimit = ConstU32<50>;
type ApprovalsLimit = ConstU32<10>;
type ItemAttributesApprovalsLimit = ConstU32<2>;
type MaxTips = ConstU32<10>;
type MaxDeadlineDuration = ConstU64<10000>;
type Features = Features;
type WeightInfo = ();
#[cfg(feature = "runtime-benchmarks")]
type Helper = ();
}
pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
let t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
}
File diff suppressed because it is too large Load Diff
+465
View File
@@ -0,0 +1,465 @@
// This file is part of Substrate.
// Copyright (C) 2017-2022 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.
//! Various basic types for use in the Nfts pallet.
use super::*;
use crate::macros::*;
use codec::EncodeLike;
use enumflags2::{bitflags, BitFlags};
use frame_support::{
pallet_prelude::{BoundedVec, MaxEncodedLen},
traits::Get,
BoundedBTreeMap, BoundedBTreeSet,
};
use scale_info::{build::Fields, meta_type, Path, Type, TypeInfo, TypeParameter};
pub(super) type DepositBalanceOf<T, I = ()> =
<<T as Config<I>>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
pub(super) type CollectionDetailsFor<T, I> =
CollectionDetails<<T as SystemConfig>::AccountId, DepositBalanceOf<T, I>>;
pub(super) type ApprovalsOf<T, I = ()> = BoundedBTreeMap<
<T as SystemConfig>::AccountId,
Option<<T as SystemConfig>::BlockNumber>,
<T as Config<I>>::ApprovalsLimit,
>;
pub(super) type ItemAttributesApprovals<T, I = ()> =
BoundedBTreeSet<<T as SystemConfig>::AccountId, <T as Config<I>>::ItemAttributesApprovalsLimit>;
pub(super) type ItemDepositOf<T, I> =
ItemDeposit<DepositBalanceOf<T, I>, <T as SystemConfig>::AccountId>;
pub(super) type AttributeDepositOf<T, I> =
AttributeDeposit<DepositBalanceOf<T, I>, <T as SystemConfig>::AccountId>;
pub(super) type ItemDetailsFor<T, I> =
ItemDetails<<T as SystemConfig>::AccountId, ItemDepositOf<T, I>, ApprovalsOf<T, I>>;
pub(super) type BalanceOf<T, I = ()> =
<<T as Config<I>>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
pub(super) type ItemPrice<T, I = ()> = BalanceOf<T, I>;
pub(super) type ItemTipOf<T, I = ()> = ItemTip<
<T as Config<I>>::CollectionId,
<T as Config<I>>::ItemId,
<T as SystemConfig>::AccountId,
BalanceOf<T, I>,
>;
pub(super) type CollectionConfigFor<T, I = ()> = CollectionConfig<
BalanceOf<T, I>,
<T as SystemConfig>::BlockNumber,
<T as Config<I>>::CollectionId,
>;
pub trait Incrementable {
fn increment(&self) -> Self;
fn initial_value() -> Self;
}
impl_incrementable!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128);
/// Information about a collection.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct CollectionDetails<AccountId, DepositBalance> {
/// Collection's owner.
pub(super) owner: AccountId,
/// The total balance deposited by the owner for all the storage data associated with this
/// collection. Used by `destroy`.
pub(super) owner_deposit: DepositBalance,
/// The total number of outstanding items of this collection.
pub(super) items: u32,
/// The total number of outstanding item metadata of this collection.
pub(super) item_metadatas: u32,
/// The total number of attributes for this collection.
pub(super) attributes: u32,
}
/// Witness data for the destroy transactions.
#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct DestroyWitness {
/// The total number of outstanding items of this collection.
#[codec(compact)]
pub items: u32,
/// The total number of items in this collection that have outstanding item metadata.
#[codec(compact)]
pub item_metadatas: u32,
/// The total number of attributes for this collection.
#[codec(compact)]
pub attributes: u32,
}
impl<AccountId, DepositBalance> CollectionDetails<AccountId, DepositBalance> {
pub fn destroy_witness(&self) -> DestroyWitness {
DestroyWitness {
items: self.items,
item_metadatas: self.item_metadatas,
attributes: self.attributes,
}
}
}
/// Witness data for items mint transactions.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub struct MintWitness<ItemId> {
/// Provide the id of the item in a required collection.
pub owner_of_item: ItemId,
}
/// Information concerning the ownership of a single unique item.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)]
pub struct ItemDetails<AccountId, Deposit, Approvals> {
/// The owner of this item.
pub(super) owner: AccountId,
/// The approved transferrer of this item, if one is set.
pub(super) approvals: Approvals,
/// The amount held in the pallet's default account for this item. Free-hold items will have
/// this as zero.
pub(super) deposit: Deposit,
}
/// Information about the reserved item deposit.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct ItemDeposit<DepositBalance, AccountId> {
/// A depositor account.
pub(super) account: AccountId,
/// An amount that gets reserved.
pub(super) amount: DepositBalance,
}
/// Information about the collection's metadata.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(StringLimit))]
#[codec(mel_bound(DepositBalance: MaxEncodedLen))]
pub struct CollectionMetadata<DepositBalance, StringLimit: Get<u32>> {
/// The balance deposited for this metadata.
///
/// This pays for the data stored in this struct.
pub(super) deposit: DepositBalance,
/// General information concerning this collection. Limited in length by `StringLimit`. This
/// will generally be either a JSON dump or the hash of some JSON which can be found on a
/// hash-addressable global publication system such as IPFS.
pub(super) data: BoundedVec<u8, StringLimit>,
}
/// Information about the item's metadata.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(StringLimit))]
#[codec(mel_bound(DepositBalance: MaxEncodedLen))]
pub struct ItemMetadata<DepositBalance, StringLimit: Get<u32>> {
/// The balance deposited for this metadata.
///
/// This pays for the data stored in this struct.
pub(super) deposit: DepositBalance,
/// General information concerning this item. Limited in length by `StringLimit`. This will
/// generally be either a JSON dump or the hash of some JSON which can be found on a
/// hash-addressable global publication system such as IPFS.
pub(super) data: BoundedVec<u8, StringLimit>,
}
/// Information about the tip.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct ItemTip<CollectionId, ItemId, AccountId, Amount> {
/// The collection of the item.
pub(super) collection: CollectionId,
/// An item of which the tip is sent for.
pub(super) item: ItemId,
/// A sender of the tip.
pub(super) receiver: AccountId,
/// An amount the sender is willing to tip.
pub(super) amount: Amount,
}
/// Information about the pending swap.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)]
pub struct PendingSwap<CollectionId, ItemId, ItemPriceWithDirection, Deadline> {
/// The collection that contains the item that the user wants to receive.
pub(super) desired_collection: CollectionId,
/// The item the user wants to receive.
pub(super) desired_item: Option<ItemId>,
/// A price for the desired `item` with the direction.
pub(super) price: Option<ItemPriceWithDirection>,
/// An optional deadline for the swap.
pub(super) deadline: Deadline,
}
/// Information about the reserved attribute deposit.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct AttributeDeposit<DepositBalance, AccountId> {
/// A depositor account.
pub(super) account: Option<AccountId>,
/// An amount that gets reserved.
pub(super) amount: DepositBalance,
}
/// Specifies whether the tokens will be sent or received.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub enum PriceDirection {
/// Tokens will be sent.
Send,
/// Tokens will be received.
Receive,
}
/// Holds the details about the price.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct PriceWithDirection<Amount> {
/// An amount.
pub(super) amount: Amount,
/// A direction (send or receive).
pub(super) direction: PriceDirection,
}
/// Support for up to 64 user-enabled features on a collection.
#[bitflags]
#[repr(u64)]
#[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub enum CollectionSetting {
/// Items in this collection are transferable.
TransferableItems,
/// The metadata of this collection can be modified.
UnlockedMetadata,
/// Attributes of this collection can be modified.
UnlockedAttributes,
/// The supply of this collection can be modified.
UnlockedMaxSupply,
/// When this isn't set then the deposit is required to hold the items of this collection.
DepositRequired,
}
/// Wrapper type for `BitFlags<CollectionSetting>` that implements `Codec`.
#[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)]
pub struct CollectionSettings(pub BitFlags<CollectionSetting>);
impl CollectionSettings {
pub fn all_enabled() -> Self {
Self(BitFlags::EMPTY)
}
pub fn get_disabled(&self) -> BitFlags<CollectionSetting> {
self.0
}
pub fn is_disabled(&self, setting: CollectionSetting) -> bool {
self.0.contains(setting)
}
pub fn from_disabled(settings: BitFlags<CollectionSetting>) -> Self {
Self(settings)
}
}
impl_codec_bitflags!(CollectionSettings, u64, CollectionSetting);
/// Mint type. Can the NFT be create by anyone, or only the creator of the collection,
/// or only by wallets that already hold an NFT from a certain collection?
/// The ownership of a privately minted NFT is still publicly visible.
#[derive(Clone, Copy, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub enum MintType<CollectionId> {
/// Only an `Issuer` could mint items.
Issuer,
/// Anyone could mint items.
Public,
/// Only holders of items in specified collection could mint new items.
HolderOf(CollectionId),
}
/// Holds the information about minting.
#[derive(Clone, Copy, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct MintSettings<Price, BlockNumber, CollectionId> {
/// Whether anyone can mint or if minters are restricted to some subset.
pub(super) mint_type: MintType<CollectionId>,
/// An optional price per mint.
pub(super) price: Option<Price>,
/// When the mint starts.
pub(super) start_block: Option<BlockNumber>,
/// When the mint ends.
pub(super) end_block: Option<BlockNumber>,
/// Default settings each item will get during the mint.
pub(super) default_item_settings: ItemSettings,
}
impl<Price, BlockNumber, CollectionId> Default for MintSettings<Price, BlockNumber, CollectionId> {
fn default() -> Self {
Self {
mint_type: MintType::Issuer,
price: None,
start_block: None,
end_block: None,
default_item_settings: ItemSettings::all_enabled(),
}
}
}
/// A witness data to cancel attributes approval operation.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub struct CancelAttributesApprovalWitness {
/// An amount of attributes previously created by account.
pub account_attributes: u32,
}
/// A list of possible pallet-level attributes.
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub enum PalletAttributes<CollectionId> {
/// Marks an item as being used in order to claim another item.
UsedToClaim(CollectionId),
}
/// Collection's configuration.
#[derive(
Clone, Copy, Decode, Default, Encode, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo,
)]
pub struct CollectionConfig<Price, BlockNumber, CollectionId> {
/// Collection's settings.
pub(super) settings: CollectionSettings,
/// Collection's max supply.
pub(super) max_supply: Option<u32>,
/// Default settings each item will get during the mint.
pub(super) mint_settings: MintSettings<Price, BlockNumber, CollectionId>,
}
impl<Price, BlockNumber, CollectionId> CollectionConfig<Price, BlockNumber, CollectionId> {
pub fn is_setting_enabled(&self, setting: CollectionSetting) -> bool {
!self.settings.is_disabled(setting)
}
pub fn has_disabled_setting(&self, setting: CollectionSetting) -> bool {
self.settings.is_disabled(setting)
}
pub fn enable_setting(&mut self, setting: CollectionSetting) {
self.settings.0.remove(setting);
}
pub fn disable_setting(&mut self, setting: CollectionSetting) {
self.settings.0.insert(setting);
}
}
/// Support for up to 64 user-enabled features on an item.
#[bitflags]
#[repr(u64)]
#[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub enum ItemSetting {
/// This item is transferable.
Transferable,
/// The metadata of this item can be modified.
UnlockedMetadata,
/// Attributes of this item can be modified.
UnlockedAttributes,
}
/// Wrapper type for `BitFlags<ItemSetting>` that implements `Codec`.
#[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)]
pub struct ItemSettings(pub BitFlags<ItemSetting>);
impl ItemSettings {
pub fn all_enabled() -> Self {
Self(BitFlags::EMPTY)
}
pub fn get_disabled(&self) -> BitFlags<ItemSetting> {
self.0
}
pub fn is_disabled(&self, setting: ItemSetting) -> bool {
self.0.contains(setting)
}
pub fn from_disabled(settings: BitFlags<ItemSetting>) -> Self {
Self(settings)
}
}
impl_codec_bitflags!(ItemSettings, u64, ItemSetting);
/// Item's configuration.
#[derive(
Encode, Decode, Default, PartialEq, RuntimeDebug, Clone, Copy, MaxEncodedLen, TypeInfo,
)]
pub struct ItemConfig {
/// Item's settings.
pub(super) settings: ItemSettings,
}
impl ItemConfig {
pub fn is_setting_enabled(&self, setting: ItemSetting) -> bool {
!self.settings.is_disabled(setting)
}
pub fn has_disabled_setting(&self, setting: ItemSetting) -> bool {
self.settings.is_disabled(setting)
}
pub fn has_disabled_settings(&self) -> bool {
!self.settings.get_disabled().is_empty()
}
pub fn enable_setting(&mut self, setting: ItemSetting) {
self.settings.0.remove(setting);
}
pub fn disable_setting(&mut self, setting: ItemSetting) {
self.settings.0.insert(setting);
}
}
/// Support for up to 64 system-enabled features on a collection.
#[bitflags]
#[repr(u64)]
#[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub enum PalletFeature {
/// Enable/disable trading operations.
Trading,
/// Allow/disallow setting attributes.
Attributes,
/// Allow/disallow transfer approvals.
Approvals,
/// Allow/disallow atomic items swap.
Swaps,
}
/// Wrapper type for `BitFlags<PalletFeature>` that implements `Codec`.
#[derive(Default, RuntimeDebug)]
pub struct PalletFeatures(pub BitFlags<PalletFeature>);
impl PalletFeatures {
pub fn all_enabled() -> Self {
Self(BitFlags::EMPTY)
}
pub fn from_disabled(features: BitFlags<PalletFeature>) -> Self {
Self(features)
}
pub fn is_enabled(&self, feature: PalletFeature) -> bool {
!self.0.contains(feature)
}
}
impl_codec_bitflags!(PalletFeatures, u64, PalletFeature);
/// Support for up to 8 different roles for collections.
#[bitflags]
#[repr(u8)]
#[derive(Copy, Clone, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub enum CollectionRole {
/// Can mint items.
Issuer,
/// Can freeze items.
Freezer,
/// Can thaw items, force transfers and burn items from any account.
Admin,
}
/// A wrapper type that implements `Codec`.
#[derive(Clone, Copy, PartialEq, Eq, Default, RuntimeDebug)]
pub struct CollectionRoles(pub BitFlags<CollectionRole>);
impl CollectionRoles {
pub fn none() -> Self {
Self(BitFlags::EMPTY)
}
pub fn has_role(&self, role: CollectionRole) -> bool {
self.0.contains(role)
}
pub fn add_role(&mut self, role: CollectionRole) {
self.0.insert(role);
}
pub fn max_roles() -> u8 {
let all: BitFlags<CollectionRole> = BitFlags::all();
all.len() as u8
}
}
impl_codec_bitflags!(CollectionRoles, u8, CollectionRole);
+851
View File
@@ -0,0 +1,851 @@
// This file is part of Substrate.
// Copyright (C) 2022 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 4.0.0-dev
//! DATE: 2022-12-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz`
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
// Executed Command:
// /home/benchbot/cargo_target_dir/production/substrate
// benchmark
// pallet
// --steps=50
// --repeat=20
// --extrinsic=*
// --execution=wasm
// --wasm-execution=compiled
// --heap-pages=4096
// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json
// --pallet=pallet_nfts
// --chain=dev
// --header=./HEADER-APACHE2
// --output=./frame/nfts/src/weights.rs
// --template=./.maintain/frame-weight-template.hbs
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use sp_std::marker::PhantomData;
/// Weight functions needed for pallet_nfts.
pub trait WeightInfo {
fn create() -> Weight;
fn force_create() -> Weight;
fn destroy(n: u32, m: u32, a: u32, ) -> Weight;
fn mint() -> Weight;
fn force_mint() -> Weight;
fn burn() -> Weight;
fn transfer() -> Weight;
fn redeposit(i: u32, ) -> Weight;
fn lock_item_transfer() -> Weight;
fn unlock_item_transfer() -> Weight;
fn lock_collection() -> Weight;
fn transfer_ownership() -> Weight;
fn set_team() -> Weight;
fn force_collection_owner() -> Weight;
fn force_collection_config() -> Weight;
fn lock_item_properties() -> Weight;
fn set_attribute() -> Weight;
fn force_set_attribute() -> Weight;
fn clear_attribute() -> Weight;
fn approve_item_attributes() -> Weight;
fn cancel_item_attributes_approval(n: u32, ) -> Weight;
fn set_metadata() -> Weight;
fn clear_metadata() -> Weight;
fn set_collection_metadata() -> Weight;
fn clear_collection_metadata() -> Weight;
fn approve_transfer() -> Weight;
fn cancel_approval() -> Weight;
fn clear_all_transfer_approvals() -> Weight;
fn set_accept_ownership() -> Weight;
fn set_collection_max_supply() -> Weight;
fn update_mint_settings() -> Weight;
fn set_price() -> Weight;
fn buy_item() -> Weight;
fn pay_tips(n: u32, ) -> Weight;
fn create_swap() -> Weight;
fn cancel_swap() -> Weight;
fn claim_swap() -> Weight;
}
/// Weights for pallet_nfts using the Substrate node and recommended hardware.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Storage: Nfts NextCollectionId (r:1 w:1)
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts CollectionRoleOf (r:0 w:1)
// Storage: Nfts CollectionConfigOf (r:0 w:1)
// Storage: Nfts CollectionAccount (r:0 w:1)
fn create() -> Weight {
// Minimum execution time: 44_312 nanoseconds.
Weight::from_ref_time(44_871_000)
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(5))
}
// Storage: Nfts NextCollectionId (r:1 w:1)
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts CollectionRoleOf (r:0 w:1)
// Storage: Nfts CollectionConfigOf (r:0 w:1)
// Storage: Nfts CollectionAccount (r:0 w:1)
fn force_create() -> Weight {
// Minimum execution time: 31_654 nanoseconds.
Weight::from_ref_time(32_078_000)
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(5))
}
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts Item (r:1001 w:1000)
// Storage: Nfts Attribute (r:1001 w:1000)
// Storage: Nfts ItemMetadataOf (r:0 w:1000)
// Storage: Nfts CollectionRoleOf (r:0 w:1)
// Storage: Nfts CollectionMetadataOf (r:0 w:1)
// Storage: Nfts CollectionConfigOf (r:0 w:1)
// Storage: Nfts ItemConfigOf (r:0 w:1000)
// Storage: Nfts Account (r:0 w:1000)
// Storage: Nfts CollectionAccount (r:0 w:1)
/// The range of component `n` is `[0, 1000]`.
/// The range of component `m` is `[0, 1000]`.
/// The range of component `a` is `[0, 1000]`.
fn destroy(n: u32, m: u32, a: u32, ) -> Weight {
// Minimum execution time: 19_183_393 nanoseconds.
Weight::from_ref_time(17_061_526_855)
// Standard Error: 16_689
.saturating_add(Weight::from_ref_time(353_523).saturating_mul(n.into()))
// Standard Error: 16_689
.saturating_add(Weight::from_ref_time(1_861_080).saturating_mul(m.into()))
// Standard Error: 16_689
.saturating_add(Weight::from_ref_time(8_858_987).saturating_mul(a.into()))
.saturating_add(T::DbWeight::get().reads(1003))
.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into())))
.saturating_add(T::DbWeight::get().writes(3005))
.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(m.into())))
.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into())))
}
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts Item (r:1 w:1)
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts CollectionRoleOf (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:1 w:1)
// Storage: Nfts Account (r:0 w:1)
fn mint() -> Weight {
// Minimum execution time: 57_753 nanoseconds.
Weight::from_ref_time(58_313_000)
.saturating_add(T::DbWeight::get().reads(5))
.saturating_add(T::DbWeight::get().writes(4))
}
// Storage: Nfts CollectionRoleOf (r:1 w:0)
// Storage: Nfts Item (r:1 w:1)
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:1 w:1)
// Storage: Nfts Account (r:0 w:1)
fn force_mint() -> Weight {
// Minimum execution time: 56_429 nanoseconds.
Weight::from_ref_time(57_202_000)
.saturating_add(T::DbWeight::get().reads(5))
.saturating_add(T::DbWeight::get().writes(4))
}
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts Item (r:1 w:1)
// Storage: Nfts CollectionRoleOf (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:1 w:1)
// Storage: Nfts Account (r:0 w:1)
// Storage: Nfts ItemPriceOf (r:0 w:1)
// Storage: Nfts ItemAttributesApprovalsOf (r:0 w:1)
// Storage: Nfts PendingSwapOf (r:0 w:1)
fn burn() -> Weight {
// Minimum execution time: 59_681 nanoseconds.
Weight::from_ref_time(60_058_000)
.saturating_add(T::DbWeight::get().reads(4))
.saturating_add(T::DbWeight::get().writes(7))
}
// Storage: Nfts Collection (r:1 w:0)
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:1 w:0)
// Storage: Nfts Item (r:1 w:1)
// Storage: Nfts CollectionRoleOf (r:1 w:0)
// Storage: System Account (r:1 w:1)
// Storage: Nfts Account (r:0 w:2)
// Storage: Nfts ItemPriceOf (r:0 w:1)
// Storage: Nfts PendingSwapOf (r:0 w:1)
fn transfer() -> Weight {
// Minimum execution time: 66_085 nanoseconds.
Weight::from_ref_time(67_065_000)
.saturating_add(T::DbWeight::get().reads(6))
.saturating_add(T::DbWeight::get().writes(6))
}
// Storage: Nfts Collection (r:1 w:0)
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts Item (r:102 w:102)
/// The range of component `i` is `[0, 5000]`.
fn redeposit(i: u32, ) -> Weight {
// Minimum execution time: 25_949 nanoseconds.
Weight::from_ref_time(26_106_000)
// Standard Error: 10_326
.saturating_add(Weight::from_ref_time(11_496_776).saturating_mul(i.into()))
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(i.into())))
.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into())))
}
// Storage: Nfts CollectionRoleOf (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:1 w:1)
fn lock_item_transfer() -> Weight {
// Minimum execution time: 30_080 nanoseconds.
Weight::from_ref_time(30_825_000)
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1))
}
// Storage: Nfts CollectionRoleOf (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:1 w:1)
fn unlock_item_transfer() -> Weight {
// Minimum execution time: 30_612 nanoseconds.
Weight::from_ref_time(31_422_000)
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1))
}
// Storage: Nfts CollectionRoleOf (r:1 w:0)
// Storage: Nfts CollectionConfigOf (r:1 w:1)
fn lock_collection() -> Weight {
// Minimum execution time: 27_470 nanoseconds.
Weight::from_ref_time(28_015_000)
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1))
}
// Storage: Nfts OwnershipAcceptance (r:1 w:1)
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts CollectionAccount (r:0 w:2)
fn transfer_ownership() -> Weight {
// Minimum execution time: 33_750 nanoseconds.
Weight::from_ref_time(34_139_000)
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(4))
}
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts CollectionRoleOf (r:0 w:4)
fn set_team() -> Weight {
// Minimum execution time: 36_565 nanoseconds.
Weight::from_ref_time(37_464_000)
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(5))
}
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts CollectionAccount (r:0 w:2)
fn force_collection_owner() -> Weight {
// Minimum execution time: 29_028 nanoseconds.
Weight::from_ref_time(29_479_000)
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(3))
}
// Storage: Nfts Collection (r:1 w:0)
// Storage: Nfts CollectionConfigOf (r:0 w:1)
fn force_collection_config() -> Weight {
// Minimum execution time: 24_695 nanoseconds.
Weight::from_ref_time(25_304_000)
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
// Storage: Nfts Collection (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:1 w:1)
fn lock_item_properties() -> Weight {
// Minimum execution time: 28_910 nanoseconds.
Weight::from_ref_time(29_186_000)
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1))
}
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:1 w:0)
// Storage: Nfts Attribute (r:1 w:1)
fn set_attribute() -> Weight {
// Minimum execution time: 56_407 nanoseconds.
Weight::from_ref_time(58_176_000)
.saturating_add(T::DbWeight::get().reads(4))
.saturating_add(T::DbWeight::get().writes(2))
}
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts Attribute (r:1 w:1)
fn force_set_attribute() -> Weight {
// Minimum execution time: 36_402 nanoseconds.
Weight::from_ref_time(37_034_000)
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(2))
}
// Storage: Nfts Attribute (r:1 w:1)
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts ItemConfigOf (r:1 w:0)
fn clear_attribute() -> Weight {
// Minimum execution time: 52_022 nanoseconds.
Weight::from_ref_time(54_059_000)
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(2))
}
// Storage: Nfts Item (r:1 w:0)
// Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1)
fn approve_item_attributes() -> Weight {
// Minimum execution time: 28_475 nanoseconds.
Weight::from_ref_time(29_162_000)
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1))
}
// Storage: Nfts Item (r:1 w:0)
// Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1)
// Storage: Nfts Attribute (r:1 w:0)
// Storage: System Account (r:1 w:1)
/// The range of component `n` is `[0, 1000]`.
fn cancel_item_attributes_approval(n: u32, ) -> Weight {
// Minimum execution time: 37_529 nanoseconds.
Weight::from_ref_time(38_023_000)
// Standard Error: 8_136
.saturating_add(Weight::from_ref_time(7_452_872).saturating_mul(n.into()))
.saturating_add(T::DbWeight::get().reads(4))
.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into())))
.saturating_add(T::DbWeight::get().writes(2))
.saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into())))
}
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts ItemConfigOf (r:1 w:0)
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts ItemMetadataOf (r:1 w:1)
fn set_metadata() -> Weight {
// Minimum execution time: 49_300 nanoseconds.
Weight::from_ref_time(49_790_000)
.saturating_add(T::DbWeight::get().reads(4))
.saturating_add(T::DbWeight::get().writes(2))
}
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts ItemConfigOf (r:1 w:0)
// Storage: Nfts ItemMetadataOf (r:1 w:1)
fn clear_metadata() -> Weight {
// Minimum execution time: 47_248 nanoseconds.
Weight::from_ref_time(48_094_000)
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(2))
}
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts CollectionMetadataOf (r:1 w:1)
fn set_collection_metadata() -> Weight {
// Minimum execution time: 44_137 nanoseconds.
Weight::from_ref_time(44_905_000)
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(2))
}
// Storage: Nfts Collection (r:1 w:0)
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts CollectionMetadataOf (r:1 w:1)
fn clear_collection_metadata() -> Weight {
// Minimum execution time: 43_005 nanoseconds.
Weight::from_ref_time(43_898_000)
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(1))
}
// Storage: Nfts Item (r:1 w:1)
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts CollectionRoleOf (r:1 w:0)
fn approve_transfer() -> Weight {
// Minimum execution time: 36_344 nanoseconds.
Weight::from_ref_time(36_954_000)
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(1))
}
// Storage: Nfts Item (r:1 w:1)
// Storage: Nfts CollectionRoleOf (r:1 w:0)
fn cancel_approval() -> Weight {
// Minimum execution time: 32_418 nanoseconds.
Weight::from_ref_time(33_029_000)
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1))
}
// Storage: Nfts Item (r:1 w:1)
// Storage: Nfts CollectionRoleOf (r:1 w:0)
fn clear_all_transfer_approvals() -> Weight {
// Minimum execution time: 31_448 nanoseconds.
Weight::from_ref_time(31_979_000)
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1))
}
// Storage: Nfts OwnershipAcceptance (r:1 w:1)
fn set_accept_ownership() -> Weight {
// Minimum execution time: 27_487 nanoseconds.
Weight::from_ref_time(28_080_000)
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
// Storage: Nfts CollectionConfigOf (r:1 w:1)
// Storage: Nfts Collection (r:1 w:0)
fn set_collection_max_supply() -> Weight {
// Minimum execution time: 28_235 nanoseconds.
Weight::from_ref_time(28_967_000)
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1))
}
// Storage: Nfts Collection (r:1 w:0)
// Storage: Nfts CollectionConfigOf (r:1 w:1)
fn update_mint_settings() -> Weight {
// Minimum execution time: 28_172 nanoseconds.
Weight::from_ref_time(28_636_000)
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1))
}
// Storage: Nfts Item (r:1 w:0)
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:1 w:0)
// Storage: Nfts ItemPriceOf (r:0 w:1)
fn set_price() -> Weight {
// Minimum execution time: 35_336 nanoseconds.
Weight::from_ref_time(36_026_000)
.saturating_add(T::DbWeight::get().reads(3))
.saturating_add(T::DbWeight::get().writes(1))
}
// Storage: Nfts Item (r:1 w:1)
// Storage: Nfts ItemPriceOf (r:1 w:1)
// Storage: Nfts Collection (r:1 w:0)
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:1 w:0)
// Storage: System Account (r:1 w:1)
// Storage: Nfts Account (r:0 w:2)
// Storage: Nfts PendingSwapOf (r:0 w:1)
fn buy_item() -> Weight {
// Minimum execution time: 70_971 nanoseconds.
Weight::from_ref_time(72_036_000)
.saturating_add(T::DbWeight::get().reads(6))
.saturating_add(T::DbWeight::get().writes(6))
}
/// The range of component `n` is `[0, 10]`.
fn pay_tips(n: u32, ) -> Weight {
// Minimum execution time: 5_151 nanoseconds.
Weight::from_ref_time(11_822_888)
// Standard Error: 38_439
.saturating_add(Weight::from_ref_time(3_511_844).saturating_mul(n.into()))
}
// Storage: Nfts Item (r:2 w:0)
// Storage: Nfts PendingSwapOf (r:0 w:1)
fn create_swap() -> Weight {
// Minimum execution time: 33_027 nanoseconds.
Weight::from_ref_time(33_628_000)
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1))
}
// Storage: Nfts PendingSwapOf (r:1 w:1)
// Storage: Nfts Item (r:1 w:0)
fn cancel_swap() -> Weight {
// Minimum execution time: 35_890 nanoseconds.
Weight::from_ref_time(36_508_000)
.saturating_add(T::DbWeight::get().reads(2))
.saturating_add(T::DbWeight::get().writes(1))
}
// Storage: Nfts Item (r:2 w:2)
// Storage: Nfts PendingSwapOf (r:1 w:2)
// Storage: Nfts Collection (r:1 w:0)
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:2 w:0)
// Storage: System Account (r:1 w:1)
// Storage: Nfts Account (r:0 w:4)
// Storage: Nfts ItemPriceOf (r:0 w:2)
fn claim_swap() -> Weight {
// Minimum execution time: 101_076 nanoseconds.
Weight::from_ref_time(101_863_000)
.saturating_add(T::DbWeight::get().reads(8))
.saturating_add(T::DbWeight::get().writes(11))
}
}
// For backwards compatibility and tests
impl WeightInfo for () {
// Storage: Nfts NextCollectionId (r:1 w:1)
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts CollectionRoleOf (r:0 w:1)
// Storage: Nfts CollectionConfigOf (r:0 w:1)
// Storage: Nfts CollectionAccount (r:0 w:1)
fn create() -> Weight {
// Minimum execution time: 44_312 nanoseconds.
Weight::from_ref_time(44_871_000)
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(5))
}
// Storage: Nfts NextCollectionId (r:1 w:1)
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts CollectionRoleOf (r:0 w:1)
// Storage: Nfts CollectionConfigOf (r:0 w:1)
// Storage: Nfts CollectionAccount (r:0 w:1)
fn force_create() -> Weight {
// Minimum execution time: 31_654 nanoseconds.
Weight::from_ref_time(32_078_000)
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(5))
}
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts Item (r:1001 w:1000)
// Storage: Nfts Attribute (r:1001 w:1000)
// Storage: Nfts ItemMetadataOf (r:0 w:1000)
// Storage: Nfts CollectionRoleOf (r:0 w:1)
// Storage: Nfts CollectionMetadataOf (r:0 w:1)
// Storage: Nfts CollectionConfigOf (r:0 w:1)
// Storage: Nfts ItemConfigOf (r:0 w:1000)
// Storage: Nfts Account (r:0 w:1000)
// Storage: Nfts CollectionAccount (r:0 w:1)
/// The range of component `n` is `[0, 1000]`.
/// The range of component `m` is `[0, 1000]`.
/// The range of component `a` is `[0, 1000]`.
fn destroy(n: u32, m: u32, a: u32, ) -> Weight {
// Minimum execution time: 19_183_393 nanoseconds.
Weight::from_ref_time(17_061_526_855)
// Standard Error: 16_689
.saturating_add(Weight::from_ref_time(353_523).saturating_mul(n.into()))
// Standard Error: 16_689
.saturating_add(Weight::from_ref_time(1_861_080).saturating_mul(m.into()))
// Standard Error: 16_689
.saturating_add(Weight::from_ref_time(8_858_987).saturating_mul(a.into()))
.saturating_add(RocksDbWeight::get().reads(1003))
.saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(a.into())))
.saturating_add(RocksDbWeight::get().writes(3005))
.saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(m.into())))
.saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(a.into())))
}
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts Item (r:1 w:1)
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts CollectionRoleOf (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:1 w:1)
// Storage: Nfts Account (r:0 w:1)
fn mint() -> Weight {
// Minimum execution time: 57_753 nanoseconds.
Weight::from_ref_time(58_313_000)
.saturating_add(RocksDbWeight::get().reads(5))
.saturating_add(RocksDbWeight::get().writes(4))
}
// Storage: Nfts CollectionRoleOf (r:1 w:0)
// Storage: Nfts Item (r:1 w:1)
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:1 w:1)
// Storage: Nfts Account (r:0 w:1)
fn force_mint() -> Weight {
// Minimum execution time: 56_429 nanoseconds.
Weight::from_ref_time(57_202_000)
.saturating_add(RocksDbWeight::get().reads(5))
.saturating_add(RocksDbWeight::get().writes(4))
}
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts Item (r:1 w:1)
// Storage: Nfts CollectionRoleOf (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:1 w:1)
// Storage: Nfts Account (r:0 w:1)
// Storage: Nfts ItemPriceOf (r:0 w:1)
// Storage: Nfts ItemAttributesApprovalsOf (r:0 w:1)
// Storage: Nfts PendingSwapOf (r:0 w:1)
fn burn() -> Weight {
// Minimum execution time: 59_681 nanoseconds.
Weight::from_ref_time(60_058_000)
.saturating_add(RocksDbWeight::get().reads(4))
.saturating_add(RocksDbWeight::get().writes(7))
}
// Storage: Nfts Collection (r:1 w:0)
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:1 w:0)
// Storage: Nfts Item (r:1 w:1)
// Storage: Nfts CollectionRoleOf (r:1 w:0)
// Storage: System Account (r:1 w:1)
// Storage: Nfts Account (r:0 w:2)
// Storage: Nfts ItemPriceOf (r:0 w:1)
// Storage: Nfts PendingSwapOf (r:0 w:1)
fn transfer() -> Weight {
// Minimum execution time: 66_085 nanoseconds.
Weight::from_ref_time(67_065_000)
.saturating_add(RocksDbWeight::get().reads(6))
.saturating_add(RocksDbWeight::get().writes(6))
}
// Storage: Nfts Collection (r:1 w:0)
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts Item (r:102 w:102)
/// The range of component `i` is `[0, 5000]`.
fn redeposit(i: u32, ) -> Weight {
// Minimum execution time: 25_949 nanoseconds.
Weight::from_ref_time(26_106_000)
// Standard Error: 10_326
.saturating_add(Weight::from_ref_time(11_496_776).saturating_mul(i.into()))
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(i.into())))
.saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(i.into())))
}
// Storage: Nfts CollectionRoleOf (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:1 w:1)
fn lock_item_transfer() -> Weight {
// Minimum execution time: 30_080 nanoseconds.
Weight::from_ref_time(30_825_000)
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
}
// Storage: Nfts CollectionRoleOf (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:1 w:1)
fn unlock_item_transfer() -> Weight {
// Minimum execution time: 30_612 nanoseconds.
Weight::from_ref_time(31_422_000)
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
}
// Storage: Nfts CollectionRoleOf (r:1 w:0)
// Storage: Nfts CollectionConfigOf (r:1 w:1)
fn lock_collection() -> Weight {
// Minimum execution time: 27_470 nanoseconds.
Weight::from_ref_time(28_015_000)
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
}
// Storage: Nfts OwnershipAcceptance (r:1 w:1)
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts CollectionAccount (r:0 w:2)
fn transfer_ownership() -> Weight {
// Minimum execution time: 33_750 nanoseconds.
Weight::from_ref_time(34_139_000)
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(4))
}
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts CollectionRoleOf (r:0 w:4)
fn set_team() -> Weight {
// Minimum execution time: 36_565 nanoseconds.
Weight::from_ref_time(37_464_000)
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(5))
}
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts CollectionAccount (r:0 w:2)
fn force_collection_owner() -> Weight {
// Minimum execution time: 29_028 nanoseconds.
Weight::from_ref_time(29_479_000)
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(3))
}
// Storage: Nfts Collection (r:1 w:0)
// Storage: Nfts CollectionConfigOf (r:0 w:1)
fn force_collection_config() -> Weight {
// Minimum execution time: 24_695 nanoseconds.
Weight::from_ref_time(25_304_000)
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
// Storage: Nfts Collection (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:1 w:1)
fn lock_item_properties() -> Weight {
// Minimum execution time: 28_910 nanoseconds.
Weight::from_ref_time(29_186_000)
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
}
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:1 w:0)
// Storage: Nfts Attribute (r:1 w:1)
fn set_attribute() -> Weight {
// Minimum execution time: 56_407 nanoseconds.
Weight::from_ref_time(58_176_000)
.saturating_add(RocksDbWeight::get().reads(4))
.saturating_add(RocksDbWeight::get().writes(2))
}
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts Attribute (r:1 w:1)
fn force_set_attribute() -> Weight {
// Minimum execution time: 36_402 nanoseconds.
Weight::from_ref_time(37_034_000)
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(2))
}
// Storage: Nfts Attribute (r:1 w:1)
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts ItemConfigOf (r:1 w:0)
fn clear_attribute() -> Weight {
// Minimum execution time: 52_022 nanoseconds.
Weight::from_ref_time(54_059_000)
.saturating_add(RocksDbWeight::get().reads(3))
.saturating_add(RocksDbWeight::get().writes(2))
}
// Storage: Nfts Item (r:1 w:0)
// Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1)
fn approve_item_attributes() -> Weight {
// Minimum execution time: 28_475 nanoseconds.
Weight::from_ref_time(29_162_000)
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
}
// Storage: Nfts Item (r:1 w:0)
// Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1)
// Storage: Nfts Attribute (r:1 w:0)
// Storage: System Account (r:1 w:1)
/// The range of component `n` is `[0, 1000]`.
fn cancel_item_attributes_approval(n: u32, ) -> Weight {
// Minimum execution time: 37_529 nanoseconds.
Weight::from_ref_time(38_023_000)
// Standard Error: 8_136
.saturating_add(Weight::from_ref_time(7_452_872).saturating_mul(n.into()))
.saturating_add(RocksDbWeight::get().reads(4))
.saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into())))
.saturating_add(RocksDbWeight::get().writes(2))
.saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into())))
}
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts ItemConfigOf (r:1 w:0)
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts ItemMetadataOf (r:1 w:1)
fn set_metadata() -> Weight {
// Minimum execution time: 49_300 nanoseconds.
Weight::from_ref_time(49_790_000)
.saturating_add(RocksDbWeight::get().reads(4))
.saturating_add(RocksDbWeight::get().writes(2))
}
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts ItemConfigOf (r:1 w:0)
// Storage: Nfts ItemMetadataOf (r:1 w:1)
fn clear_metadata() -> Weight {
// Minimum execution time: 47_248 nanoseconds.
Weight::from_ref_time(48_094_000)
.saturating_add(RocksDbWeight::get().reads(3))
.saturating_add(RocksDbWeight::get().writes(2))
}
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts Collection (r:1 w:1)
// Storage: Nfts CollectionMetadataOf (r:1 w:1)
fn set_collection_metadata() -> Weight {
// Minimum execution time: 44_137 nanoseconds.
Weight::from_ref_time(44_905_000)
.saturating_add(RocksDbWeight::get().reads(3))
.saturating_add(RocksDbWeight::get().writes(2))
}
// Storage: Nfts Collection (r:1 w:0)
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts CollectionMetadataOf (r:1 w:1)
fn clear_collection_metadata() -> Weight {
// Minimum execution time: 43_005 nanoseconds.
Weight::from_ref_time(43_898_000)
.saturating_add(RocksDbWeight::get().reads(3))
.saturating_add(RocksDbWeight::get().writes(1))
}
// Storage: Nfts Item (r:1 w:1)
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts CollectionRoleOf (r:1 w:0)
fn approve_transfer() -> Weight {
// Minimum execution time: 36_344 nanoseconds.
Weight::from_ref_time(36_954_000)
.saturating_add(RocksDbWeight::get().reads(3))
.saturating_add(RocksDbWeight::get().writes(1))
}
// Storage: Nfts Item (r:1 w:1)
// Storage: Nfts CollectionRoleOf (r:1 w:0)
fn cancel_approval() -> Weight {
// Minimum execution time: 32_418 nanoseconds.
Weight::from_ref_time(33_029_000)
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
}
// Storage: Nfts Item (r:1 w:1)
// Storage: Nfts CollectionRoleOf (r:1 w:0)
fn clear_all_transfer_approvals() -> Weight {
// Minimum execution time: 31_448 nanoseconds.
Weight::from_ref_time(31_979_000)
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
}
// Storage: Nfts OwnershipAcceptance (r:1 w:1)
fn set_accept_ownership() -> Weight {
// Minimum execution time: 27_487 nanoseconds.
Weight::from_ref_time(28_080_000)
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
// Storage: Nfts CollectionConfigOf (r:1 w:1)
// Storage: Nfts Collection (r:1 w:0)
fn set_collection_max_supply() -> Weight {
// Minimum execution time: 28_235 nanoseconds.
Weight::from_ref_time(28_967_000)
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
}
// Storage: Nfts Collection (r:1 w:0)
// Storage: Nfts CollectionConfigOf (r:1 w:1)
fn update_mint_settings() -> Weight {
// Minimum execution time: 28_172 nanoseconds.
Weight::from_ref_time(28_636_000)
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
}
// Storage: Nfts Item (r:1 w:0)
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:1 w:0)
// Storage: Nfts ItemPriceOf (r:0 w:1)
fn set_price() -> Weight {
// Minimum execution time: 35_336 nanoseconds.
Weight::from_ref_time(36_026_000)
.saturating_add(RocksDbWeight::get().reads(3))
.saturating_add(RocksDbWeight::get().writes(1))
}
// Storage: Nfts Item (r:1 w:1)
// Storage: Nfts ItemPriceOf (r:1 w:1)
// Storage: Nfts Collection (r:1 w:0)
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:1 w:0)
// Storage: System Account (r:1 w:1)
// Storage: Nfts Account (r:0 w:2)
// Storage: Nfts PendingSwapOf (r:0 w:1)
fn buy_item() -> Weight {
// Minimum execution time: 70_971 nanoseconds.
Weight::from_ref_time(72_036_000)
.saturating_add(RocksDbWeight::get().reads(6))
.saturating_add(RocksDbWeight::get().writes(6))
}
/// The range of component `n` is `[0, 10]`.
fn pay_tips(n: u32, ) -> Weight {
// Minimum execution time: 5_151 nanoseconds.
Weight::from_ref_time(11_822_888)
// Standard Error: 38_439
.saturating_add(Weight::from_ref_time(3_511_844).saturating_mul(n.into()))
}
// Storage: Nfts Item (r:2 w:0)
// Storage: Nfts PendingSwapOf (r:0 w:1)
fn create_swap() -> Weight {
// Minimum execution time: 33_027 nanoseconds.
Weight::from_ref_time(33_628_000)
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
}
// Storage: Nfts PendingSwapOf (r:1 w:1)
// Storage: Nfts Item (r:1 w:0)
fn cancel_swap() -> Weight {
// Minimum execution time: 35_890 nanoseconds.
Weight::from_ref_time(36_508_000)
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
}
// Storage: Nfts Item (r:2 w:2)
// Storage: Nfts PendingSwapOf (r:1 w:2)
// Storage: Nfts Collection (r:1 w:0)
// Storage: Nfts CollectionConfigOf (r:1 w:0)
// Storage: Nfts ItemConfigOf (r:2 w:0)
// Storage: System Account (r:1 w:1)
// Storage: Nfts Account (r:0 w:4)
// Storage: Nfts ItemPriceOf (r:0 w:2)
fn claim_swap() -> Weight {
// Minimum execution time: 101_076 nanoseconds.
Weight::from_ref_time(101_863_000)
.saturating_add(RocksDbWeight::get().reads(8))
.saturating_add(RocksDbWeight::get().writes(11))
}
}
+4 -2
View File
@@ -23,9 +23,11 @@ pub mod fungibles;
pub mod imbalance;
mod misc;
pub mod nonfungible;
pub mod nonfungible_v2;
pub mod nonfungibles;
pub mod nonfungibles_v2;
pub use imbalance::Imbalance;
pub use misc::{
AssetId, Balance, BalanceConversion, BalanceStatus, DepositConsequence, ExistenceRequirement,
Locker, WithdrawConsequence, WithdrawReasons,
AssetId, AttributeNamespace, Balance, BalanceConversion, BalanceStatus, DepositConsequence,
ExistenceRequirement, Locker, WithdrawConsequence, WithdrawReasons,
};
@@ -126,6 +126,21 @@ pub enum BalanceStatus {
Reserved,
}
/// Attribute namespaces for non-fungible tokens.
#[derive(
Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen,
)]
pub enum AttributeNamespace<AccountId> {
/// An attribute was set by the pallet.
Pallet,
/// An attribute was set by collection's owner.
CollectionOwner,
/// An attribute was set by item's owner.
ItemOwner,
/// An attribute was set by pre-approved account.
Account(AccountId),
}
bitflags::bitflags! {
/// Reasons for moving funds out of an account.
#[derive(Encode, Decode, MaxEncodedLen)]
@@ -0,0 +1,248 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 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.
//! Traits for dealing with a single non-fungible item.
//!
//! This assumes a single-level namespace identified by `Inspect::ItemId`, and could
//! reasonably be implemented by pallets that want to expose a single collection of NFT-like
//! objects.
//!
//! For an NFT API that has dual-level namespacing, the traits in `nonfungibles` are better to
//! use.
use super::nonfungibles_v2 as nonfungibles;
use crate::{
dispatch::DispatchResult,
traits::{tokens::misc::AttributeNamespace, Get},
};
use codec::{Decode, Encode};
use sp_runtime::TokenError;
use sp_std::prelude::*;
/// Trait for providing an interface to a read-only NFT-like item.
pub trait Inspect<AccountId> {
/// Type for identifying an item.
type ItemId;
/// Returns the owner of `item`, or `None` if the item doesn't exist or has no
/// owner.
fn owner(item: &Self::ItemId) -> Option<AccountId>;
/// Returns the attribute value of `item` corresponding to `key`.
///
/// By default this is `None`; no attributes are defined.
fn attribute(
_item: &Self::ItemId,
_namespace: &AttributeNamespace<AccountId>,
_key: &[u8],
) -> Option<Vec<u8>> {
None
}
/// Returns the strongly-typed attribute value of `item` corresponding to `key`.
///
/// By default this just attempts to use `attribute`.
fn typed_attribute<K: Encode, V: Decode>(
item: &Self::ItemId,
namespace: &AttributeNamespace<AccountId>,
key: &K,
) -> Option<V> {
key.using_encoded(|d| Self::attribute(item, namespace, d))
.and_then(|v| V::decode(&mut &v[..]).ok())
}
/// Returns `true` if the `item` may be transferred.
///
/// Default implementation is that all items are transferable.
fn can_transfer(_item: &Self::ItemId) -> bool {
true
}
}
/// Interface for enumerating items in existence or owned by a given account over a collection
/// of NFTs.
pub trait InspectEnumerable<AccountId>: Inspect<AccountId> {
/// The iterator type for [`Self::items`].
type ItemsIterator: Iterator<Item = Self::ItemId>;
/// The iterator type for [`Self::owned`].
type OwnedIterator: Iterator<Item = Self::ItemId>;
/// Returns an iterator of the items within a `collection` in existence.
fn items() -> Self::ItemsIterator;
/// Returns an iterator of the items of all collections owned by `who`.
fn owned(who: &AccountId) -> Self::OwnedIterator;
}
/// Trait for providing an interface for NFT-like items which may be minted, burned and/or have
/// attributes set on them.
pub trait Mutate<AccountId, ItemConfig>: Inspect<AccountId> {
/// Mint some `item` to be owned by `who`.
///
/// By default, this is not a supported operation.
fn mint_into(
_item: &Self::ItemId,
_who: &AccountId,
_config: &ItemConfig,
_deposit_collection_owner: bool,
) -> DispatchResult {
Err(TokenError::Unsupported.into())
}
/// Burn some `item`.
///
/// By default, this is not a supported operation.
fn burn(_item: &Self::ItemId, _maybe_check_owner: Option<&AccountId>) -> DispatchResult {
Err(TokenError::Unsupported.into())
}
/// Set attribute `value` of `item`'s `key`.
///
/// By default, this is not a supported operation.
fn set_attribute(_item: &Self::ItemId, _key: &[u8], _value: &[u8]) -> DispatchResult {
Err(TokenError::Unsupported.into())
}
/// Attempt to set the strongly-typed attribute `value` of `item`'s `key`.
///
/// By default this just attempts to use `set_attribute`.
fn set_typed_attribute<K: Encode, V: Encode>(
item: &Self::ItemId,
key: &K,
value: &V,
) -> DispatchResult {
key.using_encoded(|k| value.using_encoded(|v| Self::set_attribute(item, k, v)))
}
}
/// Trait for transferring a non-fungible item.
pub trait Transfer<AccountId>: Inspect<AccountId> {
/// Transfer `item` into `destination` account.
fn transfer(item: &Self::ItemId, destination: &AccountId) -> DispatchResult;
}
/// Convert a `nonfungibles` trait implementation into a `nonfungible` trait implementation by
/// identifying a single item.
pub struct ItemOf<
F: nonfungibles::Inspect<AccountId>,
A: Get<<F as nonfungibles::Inspect<AccountId>>::CollectionId>,
AccountId,
>(sp_std::marker::PhantomData<(F, A, AccountId)>);
impl<
F: nonfungibles::Inspect<AccountId>,
A: Get<<F as nonfungibles::Inspect<AccountId>>::CollectionId>,
AccountId,
> Inspect<AccountId> for ItemOf<F, A, AccountId>
{
type ItemId = <F as nonfungibles::Inspect<AccountId>>::ItemId;
fn owner(item: &Self::ItemId) -> Option<AccountId> {
<F as nonfungibles::Inspect<AccountId>>::owner(&A::get(), item)
}
fn attribute(
item: &Self::ItemId,
namespace: &AttributeNamespace<AccountId>,
key: &[u8],
) -> Option<Vec<u8>> {
<F as nonfungibles::Inspect<AccountId>>::attribute(&A::get(), item, namespace, key)
}
fn typed_attribute<K: Encode, V: Decode>(
item: &Self::ItemId,
namespace: &AttributeNamespace<AccountId>,
key: &K,
) -> Option<V> {
<F as nonfungibles::Inspect<AccountId>>::typed_attribute(&A::get(), item, namespace, key)
}
fn can_transfer(item: &Self::ItemId) -> bool {
<F as nonfungibles::Inspect<AccountId>>::can_transfer(&A::get(), item)
}
}
impl<
F: nonfungibles::InspectEnumerable<AccountId>,
A: Get<<F as nonfungibles::Inspect<AccountId>>::CollectionId>,
AccountId,
> InspectEnumerable<AccountId> for ItemOf<F, A, AccountId>
{
type ItemsIterator = <F as nonfungibles::InspectEnumerable<AccountId>>::ItemsIterator;
type OwnedIterator =
<F as nonfungibles::InspectEnumerable<AccountId>>::OwnedInCollectionIterator;
fn items() -> Self::ItemsIterator {
<F as nonfungibles::InspectEnumerable<AccountId>>::items(&A::get())
}
fn owned(who: &AccountId) -> Self::OwnedIterator {
<F as nonfungibles::InspectEnumerable<AccountId>>::owned_in_collection(&A::get(), who)
}
}
impl<
F: nonfungibles::Mutate<AccountId, ItemConfig>,
A: Get<<F as nonfungibles::Inspect<AccountId>>::CollectionId>,
AccountId,
ItemConfig,
> Mutate<AccountId, ItemConfig> for ItemOf<F, A, AccountId>
{
fn mint_into(
item: &Self::ItemId,
who: &AccountId,
config: &ItemConfig,
deposit_collection_owner: bool,
) -> DispatchResult {
<F as nonfungibles::Mutate<AccountId, ItemConfig>>::mint_into(
&A::get(),
item,
who,
config,
deposit_collection_owner,
)
}
fn burn(item: &Self::ItemId, maybe_check_owner: Option<&AccountId>) -> DispatchResult {
<F as nonfungibles::Mutate<AccountId, ItemConfig>>::burn(&A::get(), item, maybe_check_owner)
}
fn set_attribute(item: &Self::ItemId, key: &[u8], value: &[u8]) -> DispatchResult {
<F as nonfungibles::Mutate<AccountId, ItemConfig>>::set_attribute(
&A::get(),
item,
key,
value,
)
}
fn set_typed_attribute<K: Encode, V: Encode>(
item: &Self::ItemId,
key: &K,
value: &V,
) -> DispatchResult {
<F as nonfungibles::Mutate<AccountId, ItemConfig>>::set_typed_attribute(
&A::get(),
item,
key,
value,
)
}
}
impl<
F: nonfungibles::Transfer<AccountId>,
A: Get<<F as nonfungibles::Inspect<AccountId>>::CollectionId>,
AccountId,
> Transfer<AccountId> for ItemOf<F, A, AccountId>
{
fn transfer(item: &Self::ItemId, destination: &AccountId) -> DispatchResult {
<F as nonfungibles::Transfer<AccountId>>::transfer(&A::get(), item, destination)
}
}
@@ -0,0 +1,257 @@
// This file is part of Substrate.
// Copyright (C) 2019-2022 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.
//! Traits for dealing with multiple collections of non-fungible items.
//!
//! This assumes a dual-level namespace identified by `Inspect::ItemId`, and could
//! reasonably be implemented by pallets which want to expose multiple independent collections of
//! NFT-like objects.
//!
//! For an NFT API which has single-level namespacing, the traits in `nonfungible` are better to
//! use.
//!
//! Implementations of these traits may be converted to implementations of corresponding
//! `nonfungible` traits by using the `nonfungible::ItemOf` type adapter.
use crate::{
dispatch::{DispatchError, DispatchResult},
traits::tokens::misc::AttributeNamespace,
};
use codec::{Decode, Encode};
use sp_runtime::TokenError;
use sp_std::prelude::*;
/// Trait for providing an interface to many read-only NFT-like sets of items.
pub trait Inspect<AccountId> {
/// Type for identifying an item.
type ItemId;
/// Type for identifying a collection (an identifier for an independent collection of
/// items).
type CollectionId;
/// Returns the owner of `item` of `collection`, or `None` if the item doesn't exist
/// (or somehow has no owner).
fn owner(collection: &Self::CollectionId, item: &Self::ItemId) -> Option<AccountId>;
/// Returns the owner of the `collection`, if there is one. For many NFTs this may not
/// make any sense, so users of this API should not be surprised to find a collection
/// results in `None` here.
fn collection_owner(_collection: &Self::CollectionId) -> Option<AccountId> {
None
}
/// Returns the attribute value of `item` of `collection` corresponding to `key`.
///
/// By default this is `None`; no attributes are defined.
fn attribute(
_collection: &Self::CollectionId,
_item: &Self::ItemId,
_namespace: &AttributeNamespace<AccountId>,
_key: &[u8],
) -> Option<Vec<u8>> {
None
}
/// Returns the strongly-typed attribute value of `item` of `collection` corresponding to
/// `key`.
///
/// By default this just attempts to use `attribute`.
fn typed_attribute<K: Encode, V: Decode>(
collection: &Self::CollectionId,
item: &Self::ItemId,
namespace: &AttributeNamespace<AccountId>,
key: &K,
) -> Option<V> {
key.using_encoded(|d| Self::attribute(collection, item, namespace, d))
.and_then(|v| V::decode(&mut &v[..]).ok())
}
/// Returns the attribute value of `collection` corresponding to `key`.
///
/// By default this is `None`; no attributes are defined.
fn collection_attribute(_collection: &Self::CollectionId, _key: &[u8]) -> Option<Vec<u8>> {
None
}
/// Returns the strongly-typed attribute value of `collection` corresponding to `key`.
///
/// By default this just attempts to use `collection_attribute`.
fn typed_collection_attribute<K: Encode, V: Decode>(
collection: &Self::CollectionId,
key: &K,
) -> Option<V> {
key.using_encoded(|d| Self::collection_attribute(collection, d))
.and_then(|v| V::decode(&mut &v[..]).ok())
}
/// Returns `true` if the `item` of `collection` may be transferred.
///
/// Default implementation is that all items are transferable.
fn can_transfer(_collection: &Self::CollectionId, _item: &Self::ItemId) -> bool {
true
}
}
/// Interface for enumerating items in existence or owned by a given account over many collections
/// of NFTs.
pub trait InspectEnumerable<AccountId>: Inspect<AccountId> {
/// The iterator type for [`Self::collections`].
type CollectionsIterator: Iterator<Item = Self::CollectionId>;
/// The iterator type for [`Self::items`].
type ItemsIterator: Iterator<Item = Self::ItemId>;
/// The iterator type for [`Self::owned`].
type OwnedIterator: Iterator<Item = (Self::CollectionId, Self::ItemId)>;
/// The iterator type for [`Self::owned_in_collection`].
type OwnedInCollectionIterator: Iterator<Item = Self::ItemId>;
/// Returns an iterator of the collections in existence.
fn collections() -> Self::CollectionsIterator;
/// Returns an iterator of the items of a `collection` in existence.
fn items(collection: &Self::CollectionId) -> Self::ItemsIterator;
/// Returns an iterator of the items of all collections owned by `who`.
fn owned(who: &AccountId) -> Self::OwnedIterator;
/// Returns an iterator of the items of `collection` owned by `who`.
fn owned_in_collection(
collection: &Self::CollectionId,
who: &AccountId,
) -> Self::OwnedInCollectionIterator;
}
/// Trait for providing the ability to create collections of nonfungible items.
pub trait Create<AccountId, CollectionConfig>: Inspect<AccountId> {
/// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`.
fn create_collection(
who: &AccountId,
admin: &AccountId,
config: &CollectionConfig,
) -> Result<Self::CollectionId, DispatchError>;
}
/// Trait for providing the ability to destroy collections of nonfungible items.
pub trait Destroy<AccountId>: Inspect<AccountId> {
/// The witness data needed to destroy an item.
type DestroyWitness;
/// Provide the appropriate witness data needed to destroy an item.
fn get_destroy_witness(collection: &Self::CollectionId) -> Option<Self::DestroyWitness>;
/// Destroy an existing fungible item.
/// * `collection`: The `CollectionId` to be destroyed.
/// * `witness`: Any witness data that needs to be provided to complete the operation
/// successfully.
/// * `maybe_check_owner`: An optional `AccountId` that can be used to authorize the destroy
/// command. If not provided, we will not do any authorization checks before destroying the
/// item.
///
/// If successful, this function will return the actual witness data from the destroyed item.
/// This may be different than the witness data provided, and can be used to refund weight.
fn destroy(
collection: Self::CollectionId,
witness: Self::DestroyWitness,
maybe_check_owner: Option<AccountId>,
) -> Result<Self::DestroyWitness, DispatchError>;
}
/// Trait for providing an interface for multiple collections of NFT-like items which may be
/// minted, burned and/or have attributes set on them.
pub trait Mutate<AccountId, ItemConfig>: Inspect<AccountId> {
/// Mint some `item` of `collection` to be owned by `who`.
///
/// By default, this is not a supported operation.
fn mint_into(
_collection: &Self::CollectionId,
_item: &Self::ItemId,
_who: &AccountId,
_config: &ItemConfig,
_deposit_collection_owner: bool,
) -> DispatchResult {
Err(TokenError::Unsupported.into())
}
/// Burn some `item` of `collection`.
///
/// By default, this is not a supported operation.
fn burn(
_collection: &Self::CollectionId,
_item: &Self::ItemId,
_maybe_check_owner: Option<&AccountId>,
) -> DispatchResult {
Err(TokenError::Unsupported.into())
}
/// Set attribute `value` of `item` of `collection`'s `key`.
///
/// By default, this is not a supported operation.
fn set_attribute(
_collection: &Self::CollectionId,
_item: &Self::ItemId,
_key: &[u8],
_value: &[u8],
) -> DispatchResult {
Err(TokenError::Unsupported.into())
}
/// Attempt to set the strongly-typed attribute `value` of `item` of `collection`'s `key`.
///
/// By default this just attempts to use `set_attribute`.
fn set_typed_attribute<K: Encode, V: Encode>(
collection: &Self::CollectionId,
item: &Self::ItemId,
key: &K,
value: &V,
) -> DispatchResult {
key.using_encoded(|k| value.using_encoded(|v| Self::set_attribute(collection, item, k, v)))
}
/// Set attribute `value` of `collection`'s `key`.
///
/// By default, this is not a supported operation.
fn set_collection_attribute(
_collection: &Self::CollectionId,
_key: &[u8],
_value: &[u8],
) -> DispatchResult {
Err(TokenError::Unsupported.into())
}
/// Attempt to set the strongly-typed attribute `value` of `collection`'s `key`.
///
/// By default this just attempts to use `set_attribute`.
fn set_typed_collection_attribute<K: Encode, V: Encode>(
collection: &Self::CollectionId,
key: &K,
value: &V,
) -> DispatchResult {
key.using_encoded(|k| {
value.using_encoded(|v| Self::set_collection_attribute(collection, k, v))
})
}
}
/// Trait for transferring non-fungible sets of items.
pub trait Transfer<AccountId>: Inspect<AccountId> {
/// Transfer `item` of `collection` into `destination` account.
fn transfer(
collection: &Self::CollectionId,
item: &Self::ItemId,
destination: &AccountId,
) -> DispatchResult;
}
@@ -26,5 +26,5 @@ error[E0277]: the trait bound `Vec<u8>: MaxEncodedLen` is not satisfied
(TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5)
(TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6)
(TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7)
and 78 others
and 79 others
= note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageMyStorage<T>, Vec<u8>>` to implement `StorageInfoTrait`
@@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied
<&[(T,)] as EncodeLike<BinaryHeap<LikeT>>>
<&[(T,)] as EncodeLike<LinkedList<LikeT>>>
<&[T] as EncodeLike<Vec<U>>>
and 280 others
and 281 others
= note: required for `Bar` to implement `FullEncode`
= note: required for `Bar` to implement `FullCodec`
= note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo<T>, Bar>` to implement `PartialStorageInfoTrait`
@@ -69,7 +69,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied
(A, B, C, D)
(A, B, C, D, E)
(A, B, C, D, E, F)
and 162 others
and 163 others
= note: required for `Bar` to implement `StaticTypeInfo`
= note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo<T>, Bar>` to implement `StorageEntryMetadataBuilder`
@@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied
<&[(T,)] as EncodeLike<BinaryHeap<LikeT>>>
<&[(T,)] as EncodeLike<LinkedList<LikeT>>>
<&[T] as EncodeLike<Vec<U>>>
and 280 others
and 281 others
= note: required for `Bar` to implement `FullEncode`
= note: required for `Bar` to implement `FullCodec`
= note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo<T>, Bar>` to implement `StorageEntryMetadataBuilder`
@@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied
<&[(T,)] as EncodeLike<BinaryHeap<LikeT>>>
<&[(T,)] as EncodeLike<LinkedList<LikeT>>>
<&[T] as EncodeLike<Vec<U>>>
and 280 others
and 281 others
= note: required for `Bar` to implement `FullEncode`
= note: required for `Bar` to implement `FullCodec`
= note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo<T>, Bar>` to implement `PartialStorageInfoTrait`
@@ -69,7 +69,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied
(A, B, C, D)
(A, B, C, D, E)
(A, B, C, D, E, F)
and 162 others
and 163 others
= note: required for `Bar` to implement `StaticTypeInfo`
= note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo<T>, Bar>` to implement `StorageEntryMetadataBuilder`
@@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied
<&[(T,)] as EncodeLike<BinaryHeap<LikeT>>>
<&[(T,)] as EncodeLike<LinkedList<LikeT>>>
<&[T] as EncodeLike<Vec<U>>>
and 280 others
and 281 others
= note: required for `Bar` to implement `FullEncode`
= note: required for `Bar` to implement `FullCodec`
= note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo<T>, Bar>` to implement `StorageEntryMetadataBuilder`
@@ -13,5 +13,5 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied
(TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5)
(TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6)
(TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7)
and 78 others
and 79 others
= note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo<T>, Bar>` to implement `StorageInfoTrait`
@@ -13,6 +13,6 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied
(TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5)
(TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6)
(TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7)
and 78 others
and 79 others
= note: required for `Key<frame_support::Twox64Concat, Bar>` to implement `KeyGeneratorMaxEncodedLen`
= note: required for `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo<T>, Key<frame_support::Twox64Concat, Bar>, u32>` to implement `StorageInfoTrait`