* 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
@@ -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(())
})
}
}