difftreelog
refactor switch to new prefix removal methods
in: master
New methods allows to call `remove_prefix` with limit multiple times in the same block However, we don't use prefix removal limits, so upgrade is straightforward
5 files changed
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -958,8 +958,8 @@
<DestroyedCollectionCount<T>>::put(destroyed_collections);
<CollectionById<T>>::remove(collection.id);
<AdminAmount<T>>::remove(collection.id);
- <IsAdmin<T>>::remove_prefix((collection.id,), None);
- <Allowlist<T>>::remove_prefix((collection.id,), None);
+ let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);
+ let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);
<CollectionProperties<T>>::remove(collection.id);
<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -231,8 +231,8 @@
PalletCommon::destroy_collection(collection.0, sender)?;
<TotalSupply<T>>::remove(id);
- <Balance<T>>::remove_prefix((id,), None);
- <Allowance<T>>::remove_prefix((id,), None);
+ let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);
+ let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);
Ok(())
}
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -429,13 +429,13 @@
PalletCommon::destroy_collection(collection.0, sender)?;
- <TokenData<T>>::remove_prefix((id,), None);
- <TokenChildren<T>>::remove_prefix((id,), None);
- <Owned<T>>::remove_prefix((id,), None);
+ let _ = <TokenData<T>>::clear_prefix((id,), u32::MAX, None);
+ let _ = <TokenChildren<T>>::clear_prefix((id,), u32::MAX, None);
+ let _ = <Owned<T>>::clear_prefix((id,), u32::MAX, None);
<TokensMinted<T>>::remove(id);
<TokensBurnt<T>>::remove(id);
- <Allowance<T>>::remove_prefix((id,), None);
- <AccountBalance<T>>::remove_prefix((id,), None);
+ let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);
+ let _ = <AccountBalance<T>>::clear_prefix((id,), u32::MAX, None);
Ok(())
}
@@ -487,7 +487,7 @@
<TokensBurnt<T>>::insert(collection.id, burnt);
<TokenData<T>>::remove((collection.id, token));
<TokenProperties<T>>::remove((collection.id, token));
- <TokenAuxProperties<T>>::remove_prefix((collection.id, token), None);
+ let _ = <TokenAuxProperties<T>>::clear_prefix((collection.id, token), u32::MAX, None);
let old_spender = <Allowance<T>>::take((collection.id, token));
if let Some(old_spender) = old_spender {
pallets/refungible/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Refungible Pallet18//!19//! The Refungible pallet provides functionality for handling refungible collections and tokens.20//!21//! - [`Config`]22//! - [`RefungibleHandle`]23//! - [`Pallet`]24//! - [`CommonWeights`](common::CommonWeights)25//!26//! ## Overview27//!28//! The Refungible pallet provides functions for:29//!30//! - RFT collection creation and removal31//! - Minting and burning of RFT tokens32//! - Partition and repartition of RFT tokens33//! - Retrieving number of pieces of RFT token34//! - Retrieving account balances35//! - Transfering RFT token pieces36//! - Burning RFT token pieces37//! - Setting and checking allowance for RFT tokens38//!39//! ### Terminology40//!41//! - **RFT token:** Non fungible token that was partitioned to pieces. If an account owns all42//! of the RFT token pieces than it owns the RFT token and can repartition it.43//!44//! - **RFT Collection:** A collection of RFT tokens. All RFT tokens are part of a collection.45//! Each collection has its own settings and set of permissions.46//!47//! - **RFT token piece:** A fungible part of an RFT token.48//!49//! - **Balance:** RFT token pieces owned by an account50//!51//! - **Allowance:** Maximum number of RFT token pieces that one account is allowed to52//! transfer from the balance of another account53//!54//! - **Burning:** The process of “deleting” a token from a collection or removing token pieces from55//! an account balance.56//!57//! ### Implementations58//!59//! The Refungible pallet provides implementations for the following traits. If these traits provide60//! the functionality that you need, then you can avoid coupling with the Refungible pallet.61//!62//! - [`CommonWeightInfo`](pallet_common::CommonWeightInfo): Functions for retrieval of transaction weight63//! - [`CommonCollectionOperations`](pallet_common::CommonCollectionOperations): Functions for dealing64//! with collections65//! - [`RefungibleExtensions`](pallet_common::RefungibleExtensions): Functions specific for refungible66//! collection67//!68//! ## Interface69//!70//! ### Dispatchable Functions71//!72//! - `init_collection` - Create RFT collection. RFT collection can be configured to allow or deny access for73//! some accounts.74//! - `destroy_collection` - Destroy exising RFT collection. There should be no tokens in the collection.75//! - `burn` - Burn some amount of RFT token pieces owned by account. Burns the RFT token if no pieces left.76//! - `transfer` - Transfer some amount of RFT token pieces. Transfers should be enabled for RFT collection.77//! Nests the RFT token if RFT token pieces are sent to another token.78//! - `create_item` - Mint RFT token in collection. Sender should have permission to mint tokens.79//! - `set_allowance` - Set allowance for another account to transfer balance from sender's account.80//! - `repartition` - Repartition token to selected number of pieces. Sender should own all existing pieces.81//!82//! ## Assumptions83//!84//! * Total number of pieces for one token shouldn't exceed `up_data_structs::MAX_REFUNGIBLE_PIECES`.85//! * Total number of tokens of all types shouldn't be greater than `up_data_structs::MAX_TOKEN_PREFIX_LENGTH`.86//! * Sender should be in collection's allow list to perform operations on tokens.8788#![cfg_attr(not(feature = "std"), no_std)]8990use crate::erc_token::ERC20Events;91use crate::erc::ERC721Events;9293use codec::{Encode, Decode, MaxEncodedLen};94use core::ops::Deref;95use evm_coder::ToLog;96use frame_support::{97 BoundedVec, ensure, fail, storage::with_transaction, transactional, pallet_prelude::ConstU32,98};99use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};100use pallet_evm_coder_substrate::WithRecorder;101use pallet_common::{102 CollectionHandle, CommonCollectionOperations,103 dispatch::CollectionDispatch,104 erc::static_property::{key, property_value_from_bytes},105 Error as CommonError,106 eth::collection_id_to_address,107 Event as CommonEvent, Pallet as PalletCommon,108};109use pallet_structure::Pallet as PalletStructure;110use scale_info::TypeInfo;111use sp_core::H160;112use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};113use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};114use up_data_structs::{115 AccessMode, budget::Budget, CollectionId, CollectionMode, CollectionPropertiesVec,116 CreateCollectionData, CustomDataLimit, mapping::TokenAddressMapping, MAX_ITEMS_PER_BATCH,117 MAX_REFUNGIBLE_PIECES, Property, PropertyKey, PropertyKeyPermission, PropertyPermission,118 PropertyScope, PropertyValue, TokenId, TrySetProperty,119};120use frame_support::BoundedBTreeMap;121use derivative::Derivative;122123pub use pallet::*;124#[cfg(feature = "runtime-benchmarks")]125pub mod benchmarking;126pub mod common;127pub mod erc;128pub mod erc_token;129pub mod weights;130131#[derive(Derivative, Clone)]132pub struct CreateItemData<CrossAccountId> {133 #[derivative(Debug(format_with = "bounded::map_debug"))]134 pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,135 #[derivative(Debug(format_with = "bounded::vec_debug"))]136 pub properties: CollectionPropertiesVec,137}138pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;139140/// Token data, stored independently from other data used to describe it141/// for the convenience of database access. Notably contains the token metadata.142#[struct_versioning::versioned(version = 2, upper)]143#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]144#[deprecated(since = "0.2.0", note = "ItemData is no more contains usefull data")]145pub struct ItemData {146 pub const_data: BoundedVec<u8, CustomDataLimit>,147148 #[version(..2)]149 pub variable_data: BoundedVec<u8, CustomDataLimit>,150}151152#[frame_support::pallet]153pub mod pallet {154 use super::*;155 use frame_support::{156 Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key,157 traits::StorageVersion,158 };159 use frame_system::pallet_prelude::*;160 use up_data_structs::{CollectionId, TokenId};161 use super::weights::WeightInfo;162163 #[pallet::error]164 pub enum Error<T> {165 /// Not Refungible item data used to mint in Refungible collection.166 NotRefungibleDataUsedToMintFungibleCollectionToken,167 /// Maximum refungibility exceeded.168 WrongRefungiblePieces,169 /// Refungible token can't be repartitioned by user who isn't owns all pieces.170 RepartitionWhileNotOwningAllPieces,171 /// Refungible token can't nest other tokens.172 RefungibleDisallowsNesting,173 /// Setting item properties is not allowed.174 SettingPropertiesNotAllowed,175 }176177 #[pallet::config]178 pub trait Config:179 frame_system::Config + pallet_common::Config + pallet_structure::Config180 {181 type WeightInfo: WeightInfo;182 }183184 const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);185186 #[pallet::pallet]187 #[pallet::storage_version(STORAGE_VERSION)]188 #[pallet::generate_store(pub(super) trait Store)]189 pub struct Pallet<T>(_);190191 /// Total amount of minted tokens in a collection.192 #[pallet::storage]193 pub type TokensMinted<T: Config> =194 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;195196 /// Amount of tokens burnt in a collection.197 #[pallet::storage]198 pub type TokensBurnt<T: Config> =199 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;200201 /// Token data, used to partially describe a token.202 // TODO: remove203 #[pallet::storage]204 #[deprecated(since = "0.2.0", note = "ItemData is no more contains usefull data")]205 pub type TokenData<T: Config> = StorageNMap<206 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),207 Value = ItemData,208 QueryKind = ValueQuery,209 >;210211 /// Amount of pieces a refungible token is split into.212 #[pallet::storage]213 #[pallet::getter(fn token_properties)]214 pub type TokenProperties<T: Config> = StorageNMap<215 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),216 Value = up_data_structs::Properties,217 QueryKind = ValueQuery,218 OnEmpty = up_data_structs::TokenProperties,219 >;220221 /// Total amount of pieces for token222 #[pallet::storage]223 pub type TotalSupply<T: Config> = StorageNMap<224 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),225 Value = u128,226 QueryKind = ValueQuery,227 >;228229 /// Used to enumerate tokens owned by account.230 #[pallet::storage]231 pub type Owned<T: Config> = StorageNMap<232 Key = (233 Key<Twox64Concat, CollectionId>,234 Key<Blake2_128Concat, T::CrossAccountId>,235 Key<Twox64Concat, TokenId>,236 ),237 Value = bool,238 QueryKind = ValueQuery,239 >;240241 /// Amount of tokens (not pieces) partially owned by an account within a collection.242 #[pallet::storage]243 pub type AccountBalance<T: Config> = StorageNMap<244 Key = (245 Key<Twox64Concat, CollectionId>,246 // Owner247 Key<Blake2_128Concat, T::CrossAccountId>,248 ),249 Value = u32,250 QueryKind = ValueQuery,251 >;252253 /// Amount of token pieces owned by account.254 #[pallet::storage]255 pub type Balance<T: Config> = StorageNMap<256 Key = (257 Key<Twox64Concat, CollectionId>,258 Key<Twox64Concat, TokenId>,259 // Owner260 Key<Blake2_128Concat, T::CrossAccountId>,261 ),262 Value = u128,263 QueryKind = ValueQuery,264 >;265266 /// Allowance set by a token owner for another user to perform one of certain transactions on a number of pieces of a token.267 #[pallet::storage]268 pub type Allowance<T: Config> = StorageNMap<269 Key = (270 Key<Twox64Concat, CollectionId>,271 Key<Twox64Concat, TokenId>,272 // Owner273 Key<Blake2_128, T::CrossAccountId>,274 // Spender275 Key<Blake2_128Concat, T::CrossAccountId>,276 ),277 Value = u128,278 QueryKind = ValueQuery,279 >;280281 #[pallet::hooks]282 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {283 fn on_runtime_upgrade() -> Weight {284 let storage_version = StorageVersion::get::<Pallet<T>>();285 if storage_version < StorageVersion::new(2) {286 <TokenData<T>>::remove_all(None);287 }288 StorageVersion::new(2).put::<Pallet<T>>();289290 0291 }292 }293}294295pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);296impl<T: Config> RefungibleHandle<T> {297 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {298 Self(inner)299 }300 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {301 self.0302 }303 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {304 &mut self.0305 }306}307308impl<T: Config> Deref for RefungibleHandle<T> {309 type Target = pallet_common::CollectionHandle<T>;310311 fn deref(&self) -> &Self::Target {312 &self.0313 }314}315316impl<T: Config> WithRecorder<T> for RefungibleHandle<T> {317 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {318 self.0.recorder()319 }320 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {321 self.0.into_recorder()322 }323}324325impl<T: Config> Pallet<T> {326 /// Get number of RFT tokens in collection327 pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {328 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)329 }330331 /// Check that RFT token exists332 ///333 /// - `token`: Token ID.334 pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {335 <TotalSupply<T>>::contains_key((collection.id, token))336 }337338 pub fn set_scoped_token_property(339 collection_id: CollectionId,340 token_id: TokenId,341 scope: PropertyScope,342 property: Property,343 ) -> DispatchResult {344 TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {345 properties.try_scoped_set(scope, property.key, property.value)346 })347 .map_err(<CommonError<T>>::from)?;348349 Ok(())350 }351352 pub fn set_scoped_token_properties(353 collection_id: CollectionId,354 token_id: TokenId,355 scope: PropertyScope,356 properties: impl Iterator<Item = Property>,357 ) -> DispatchResult {358 TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {359 stored_properties.try_scoped_set_from_iter(scope, properties)360 })361 .map_err(<CommonError<T>>::from)?;362363 Ok(())364 }365}366367// unchecked calls skips any permission checks368impl<T: Config> Pallet<T> {369 /// Create RFT collection370 ///371 /// `init_collection` will take non-refundable deposit for collection creation.372 ///373 /// - `data`: Contains settings for collection limits and permissions.374 pub fn init_collection(375 owner: T::CrossAccountId,376 data: CreateCollectionData<T::AccountId>,377 ) -> Result<CollectionId, DispatchError> {378 <PalletCommon<T>>::init_collection(owner, data, false)379 }380381 /// Destroy RFT collection382 ///383 /// `destroy_collection` will throw error if collection contains any tokens.384 /// Only owner can destroy collection.385 pub fn destroy_collection(386 collection: RefungibleHandle<T>,387 sender: &T::CrossAccountId,388 ) -> DispatchResult {389 let id = collection.id;390391 if Self::collection_has_tokens(id) {392 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());393 }394395 // =========396397 PalletCommon::destroy_collection(collection.0, sender)?;398399 <TokensMinted<T>>::remove(id);400 <TokensBurnt<T>>::remove(id);401 let _ = <TotalSupply<T>>::clear_prefix((id,), u32::MAX, None);402 let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);403 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);404 let _ = <Owned<T>>::clear_prefix((id,), u32::MAX, None);405 let _ = <AccountBalance<T>>::clear_prefix((id,), u32::MAX, None);406 Ok(())407 }408409 fn collection_has_tokens(collection_id: CollectionId) -> bool {410 <TotalSupply<T>>::iter_prefix((collection_id,))411 .next()412 .is_some()413 }414415 pub fn burn_token_unchecked(416 collection: &RefungibleHandle<T>,417 owner: &T::CrossAccountId,418 token_id: TokenId,419 ) -> DispatchResult {420 let burnt = <TokensBurnt<T>>::get(collection.id)421 .checked_add(1)422 .ok_or(ArithmeticError::Overflow)?;423424 <TokensBurnt<T>>::insert(collection.id, burnt);425 <TokenProperties<T>>::remove((collection.id, token_id));426 <TotalSupply<T>>::remove((collection.id, token_id));427<<<<<<< HEAD428 <Balance<T>>::remove_prefix((collection.id, token_id), None);429 <Allowance<T>>::remove_prefix((collection.id, token_id), None);430431 <PalletEvm<T>>::deposit_log(432 ERC721Events::Transfer {433 from: *owner.as_eth(),434 to: H160::default(),435 token_id: token_id.into(),436 }437 .to_log(collection_id_to_address(collection.id)),438 );439=======440 let _ = <Balance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);441 let _ = <Allowance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);442 // TODO: ERC721 transfer event443>>>>>>> 5d9665e0... refactor: switch to new prefix removal methods444 Ok(())445 }446447 /// Burn RFT token pieces448 ///449 /// `burn` will decrease total amount of token pieces and amount owned by sender.450 /// `burn` can be called even if there are multiple owners of the RFT token.451 /// If sender wouldn't have any pieces left after `burn` than she will stop being452 /// one of the owners of the token. If there is no account that owns any pieces of453 /// the token than token will be burned too.454 ///455 /// - `amount`: Amount of token pieces to burn.456 /// - `token`: Token who's pieces should be burned457 /// - `collection`: Collection that contains the token458 pub fn burn(459 collection: &RefungibleHandle<T>,460 owner: &T::CrossAccountId,461 token: TokenId,462 amount: u128,463 ) -> DispatchResult {464 let total_supply = <TotalSupply<T>>::get((collection.id, token))465 .checked_sub(amount)466 .ok_or(<CommonError<T>>::TokenValueTooLow)?;467468 // This was probally last owner of this token?469 if total_supply == 0 {470 // Ensure user actually owns this amount471 ensure!(472 <Balance<T>>::get((collection.id, token, owner)) == amount,473 <CommonError<T>>::TokenValueTooLow474 );475 let account_balance = <AccountBalance<T>>::get((collection.id, owner))476 .checked_sub(1)477 // Should not occur478 .ok_or(ArithmeticError::Underflow)?;479480 // =========481482 <Owned<T>>::remove((collection.id, owner, token));483 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);484 <AccountBalance<T>>::insert((collection.id, owner), account_balance);485 Self::burn_token_unchecked(collection, owner, token)?;486 <PalletEvm<T>>::deposit_log(487 ERC20Events::Transfer {488 from: *owner.as_eth(),489 to: H160::default(),490 value: amount.into(),491 }492 .to_log(collection_id_to_address(collection.id)),493 );494 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(495 collection.id,496 token,497 owner.clone(),498 amount,499 ));500 return Ok(());501 }502503 let balance = <Balance<T>>::get((collection.id, token, owner))504 .checked_sub(amount)505 .ok_or(<CommonError<T>>::TokenValueTooLow)?;506 let account_balance = if balance == 0 {507 <AccountBalance<T>>::get((collection.id, owner))508 .checked_sub(1)509 // Should not occur510 .ok_or(ArithmeticError::Underflow)?511 } else {512 0513 };514515 // =========516517 if balance == 0 {518 <Owned<T>>::remove((collection.id, owner, token));519 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);520 <Balance<T>>::remove((collection.id, token, owner));521 <AccountBalance<T>>::insert((collection.id, owner), account_balance);522523 if let Some(user) = Self::token_owner(collection.id, token) {524 <PalletEvm<T>>::deposit_log(525 ERC721Events::Transfer {526 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,527 to: *user.as_eth(),528 token_id: token.into(),529 }530 .to_log(collection_id_to_address(collection.id)),531 );532 }533 } else {534 <Balance<T>>::insert((collection.id, token, owner), balance);535 }536 <TotalSupply<T>>::insert((collection.id, token), total_supply);537538 <PalletEvm<T>>::deposit_log(539 ERC20Events::Transfer {540 from: *owner.as_eth(),541 to: H160::default(),542 value: amount.into(),543 }544 .to_log(T::EvmTokenAddressMapping::token_to_address(545 collection.id,546 token,547 )),548 );549 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(550 collection.id,551 token,552 owner.clone(),553 amount,554 ));555 Ok(())556 }557558 #[transactional]559 fn modify_token_properties(560 collection: &RefungibleHandle<T>,561 sender: &T::CrossAccountId,562 token_id: TokenId,563 properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,564 is_token_create: bool,565 nesting_budget: &dyn Budget,566 ) -> DispatchResult {567 let is_collection_admin = || collection.is_owner_or_admin(sender);568 let is_token_owner = || -> Result<bool, DispatchError> {569 let balance = collection.balance(sender.clone(), token_id);570 let total_pieces: u128 =571 Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);572 if balance != total_pieces {573 return Ok(false);574 }575576 let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(577 sender.clone(),578 collection.id,579 token_id,580 None,581 nesting_budget,582 )?;583584 Ok(is_bundle_owner)585 };586587 for (key, value) in properties {588 let permission = <PalletCommon<T>>::property_permissions(collection.id)589 .get(&key)590 .cloned()591 .unwrap_or_else(PropertyPermission::none);592593 let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))594 .get(&key)595 .is_some();596597 match permission {598 PropertyPermission { mutable: false, .. } if is_property_exists => {599 return Err(<CommonError<T>>::NoPermission.into());600 }601602 PropertyPermission {603 collection_admin,604 token_owner,605 ..606 } => {607 //TODO: investigate threats during public minting.608 let is_token_create =609 is_token_create && (collection_admin || token_owner) && value.is_some();610 if !(is_token_create611 || (collection_admin && is_collection_admin())612 || (token_owner && is_token_owner()?))613 {614 fail!(<CommonError<T>>::NoPermission);615 }616 }617 }618619 match value {620 Some(value) => {621 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {622 properties.try_set(key.clone(), value)623 })624 .map_err(<CommonError<T>>::from)?;625626 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(627 collection.id,628 token_id,629 key,630 ));631 }632 None => {633 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {634 properties.remove(&key)635 })636 .map_err(<CommonError<T>>::from)?;637638 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(639 collection.id,640 token_id,641 key,642 ));643 }644 }645 }646647 Ok(())648 }649650 pub fn set_token_properties(651 collection: &RefungibleHandle<T>,652 sender: &T::CrossAccountId,653 token_id: TokenId,654 properties: impl Iterator<Item = Property>,655 is_token_create: bool,656 nesting_budget: &dyn Budget,657 ) -> DispatchResult {658 Self::modify_token_properties(659 collection,660 sender,661 token_id,662 properties.map(|p| (p.key, Some(p.value))),663 is_token_create,664 nesting_budget,665 )666 }667668 pub fn set_token_property(669 collection: &RefungibleHandle<T>,670 sender: &T::CrossAccountId,671 token_id: TokenId,672 property: Property,673 nesting_budget: &dyn Budget,674 ) -> DispatchResult {675 let is_token_create = false;676677 Self::set_token_properties(678 collection,679 sender,680 token_id,681 [property].into_iter(),682 is_token_create,683 nesting_budget,684 )685 }686687 pub fn delete_token_properties(688 collection: &RefungibleHandle<T>,689 sender: &T::CrossAccountId,690 token_id: TokenId,691 property_keys: impl Iterator<Item = PropertyKey>,692 nesting_budget: &dyn Budget,693 ) -> DispatchResult {694 let is_token_create = false;695696 Self::modify_token_properties(697 collection,698 sender,699 token_id,700 property_keys.into_iter().map(|key| (key, None)),701 is_token_create,702 nesting_budget,703 )704 }705706 pub fn delete_token_property(707 collection: &RefungibleHandle<T>,708 sender: &T::CrossAccountId,709 token_id: TokenId,710 property_key: PropertyKey,711 nesting_budget: &dyn Budget,712 ) -> DispatchResult {713 Self::delete_token_properties(714 collection,715 sender,716 token_id,717 [property_key].into_iter(),718 nesting_budget,719 )720 }721722 /// Transfer RFT token pieces from one account to another.723 ///724 /// If the sender is no longer owns any pieces after the `transfer` than she stops being an owner of the token.725 ///726 /// - `from`: Owner of token pieces to transfer.727 /// - `to`: Recepient of transfered token pieces.728 /// - `amount`: Amount of token pieces to transfer.729 /// - `token`: Token whos pieces should be transfered730 /// - `collection`: Collection that contains the token731 pub fn transfer(732 collection: &RefungibleHandle<T>,733 from: &T::CrossAccountId,734 to: &T::CrossAccountId,735 token: TokenId,736 amount: u128,737 nesting_budget: &dyn Budget,738 ) -> DispatchResult {739 ensure!(740 collection.limits.transfers_enabled(),741 <CommonError<T>>::TransferNotAllowed742 );743744 if collection.permissions.access() == AccessMode::AllowList {745 collection.check_allowlist(from)?;746 collection.check_allowlist(to)?;747 }748 <PalletCommon<T>>::ensure_correct_receiver(to)?;749750 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));751 let updated_balance_from = initial_balance_from752 .checked_sub(amount)753 .ok_or(<CommonError<T>>::TokenValueTooLow)?;754 let mut create_target = false;755 let from_to_differ = from != to;756 let updated_balance_to = if from != to {757 let old_balance = <Balance<T>>::get((collection.id, token, to));758 if old_balance == 0 {759 create_target = true;760 }761 Some(762 old_balance763 .checked_add(amount)764 .ok_or(ArithmeticError::Overflow)?,765 )766 } else {767 None768 };769770 let account_balance_from = if updated_balance_from == 0 {771 Some(772 <AccountBalance<T>>::get((collection.id, from))773 .checked_sub(1)774 // Should not occur775 .ok_or(ArithmeticError::Underflow)?,776 )777 } else {778 None779 };780 // Account data is created in token, AccountBalance should be increased781 // But only if from != to as we shouldn't check overflow in this case782 let account_balance_to = if create_target && from_to_differ {783 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))784 .checked_add(1)785 .ok_or(ArithmeticError::Overflow)?;786 ensure!(787 account_balance_to < collection.limits.account_token_ownership_limit(),788 <CommonError<T>>::AccountTokenLimitExceeded,789 );790791 Some(account_balance_to)792 } else {793 None794 };795796 // =========797798 <PalletStructure<T>>::nest_if_sent_to_token(799 from.clone(),800 to,801 collection.id,802 token,803 nesting_budget,804 )?;805806 if let Some(updated_balance_to) = updated_balance_to {807 // from != to808 if updated_balance_from == 0 {809 <Balance<T>>::remove((collection.id, token, from));810 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);811 } else {812 <Balance<T>>::insert((collection.id, token, from), updated_balance_from);813 }814 <Balance<T>>::insert((collection.id, token, to), updated_balance_to);815 if let Some(account_balance_from) = account_balance_from {816 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);817 <Owned<T>>::remove((collection.id, from, token));818 }819 if let Some(account_balance_to) = account_balance_to {820 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);821 <Owned<T>>::insert((collection.id, to, token), true);822 }823 }824825 <PalletEvm<T>>::deposit_log(826 ERC20Events::Transfer {827 from: *from.as_eth(),828 to: *to.as_eth(),829 value: amount.into(),830 }831 .to_log(T::EvmTokenAddressMapping::token_to_address(832 collection.id,833 token,834 )),835 );836837 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(838 collection.id,839 token,840 from.clone(),841 to.clone(),842 amount,843 ));844845 let total_supply = <TotalSupply<T>>::get((collection.id, token));846847 if amount == total_supply {848 // if token was fully owned by `from` and will be fully owned by `to` after transfer849 <PalletEvm<T>>::deposit_log(850 ERC721Events::Transfer {851 from: *from.as_eth(),852 to: *to.as_eth(),853 token_id: token.into(),854 }855 .to_log(collection_id_to_address(collection.id)),856 );857 } else if let Some(updated_balance_to) = updated_balance_to {858 // if `from` not equals `to`. This condition is needed to avoid sending event859 // when `from` fully owns token and sends part of token pieces to itself.860 if initial_balance_from == total_supply {861 // if token was fully owned by `from` and will be only partially owned by `to`862 // and `from` after transfer863 <PalletEvm<T>>::deposit_log(864 ERC721Events::Transfer {865 from: *from.as_eth(),866 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,867 token_id: token.into(),868 }869 .to_log(collection_id_to_address(collection.id)),870 );871 } else if updated_balance_to == total_supply {872 // if token was partially owned by `from` and will be fully owned by `to` after transfer873 <PalletEvm<T>>::deposit_log(874 ERC721Events::Transfer {875 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,876 to: *to.as_eth(),877 token_id: token.into(),878 }879 .to_log(collection_id_to_address(collection.id)),880 );881 }882 }883884 Ok(())885 }886887 /// Batched operation to create multiple RFT tokens.888 ///889 /// Same as `create_item` but creates multiple tokens.890 ///891 /// - `data`: Same as 'data` in `create_item` but contains data for multiple tokens.892 pub fn create_multiple_items(893 collection: &RefungibleHandle<T>,894 sender: &T::CrossAccountId,895 data: Vec<CreateItemData<T::CrossAccountId>>,896 nesting_budget: &dyn Budget,897 ) -> DispatchResult {898 if !collection.is_owner_or_admin(sender) {899 ensure!(900 collection.permissions.mint_mode(),901 <CommonError<T>>::PublicMintingNotAllowed902 );903 collection.check_allowlist(sender)?;904905 for item in data.iter() {906 for user in item.users.keys() {907 collection.check_allowlist(user)?;908 }909 }910 }911912 for item in data.iter() {913 for (owner, _) in item.users.iter() {914 <PalletCommon<T>>::ensure_correct_receiver(owner)?;915 }916 }917918 // Total pieces per tokens919 let totals = data920 .iter()921 .map(|data| {922 Ok(data923 .users924 .iter()925 .map(|u| u.1)926 .try_fold(0u128, |acc, v| acc.checked_add(*v))927 .ok_or(ArithmeticError::Overflow)?)928 })929 .collect::<Result<Vec<_>, DispatchError>>()?;930 for total in &totals {931 ensure!(932 *total <= MAX_REFUNGIBLE_PIECES,933 <Error<T>>::WrongRefungiblePieces934 );935 }936937 let first_token_id = <TokensMinted<T>>::get(collection.id);938 let tokens_minted = first_token_id939 .checked_add(data.len() as u32)940 .ok_or(ArithmeticError::Overflow)?;941 ensure!(942 tokens_minted < collection.limits.token_limit(),943 <CommonError<T>>::CollectionTokenLimitExceeded944 );945946 let mut balances = BTreeMap::new();947 for data in &data {948 for owner in data.users.keys() {949 let balance = balances950 .entry(owner)951 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));952 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;953954 ensure!(955 *balance <= collection.limits.account_token_ownership_limit(),956 <CommonError<T>>::AccountTokenLimitExceeded,957 );958 }959 }960961 for (i, token) in data.iter().enumerate() {962 let token_id = TokenId(first_token_id + i as u32 + 1);963 for (to, _) in token.users.iter() {964 <PalletStructure<T>>::check_nesting(965 sender.clone(),966 to,967 collection.id,968 token_id,969 nesting_budget,970 )?;971 }972 }973974 // =========975976 with_transaction(|| {977 for (i, data) in data.iter().enumerate() {978 let token_id = first_token_id + i as u32 + 1;979 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);980981 for (user, amount) in data.users.iter() {982 if *amount == 0 {983 continue;984 }985 <Balance<T>>::insert((collection.id, token_id, &user), amount);986 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);987 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(988 user,989 collection.id,990 TokenId(token_id),991 );992 }993994 if let Err(e) = Self::set_token_properties(995 collection,996 sender,997 TokenId(token_id),998 data.properties.clone().into_iter(),999 true,1000 nesting_budget,1001 ) {1002 return TransactionOutcome::Rollback(Err(e));1003 }1004 }1005 TransactionOutcome::Commit(Ok(()))1006 })?;10071008 <TokensMinted<T>>::insert(collection.id, tokens_minted);10091010 for (account, balance) in balances {1011 <AccountBalance<T>>::insert((collection.id, account), balance);1012 }10131014 for (i, token) in data.into_iter().enumerate() {1015 let token_id = first_token_id + i as u32 + 1;10161017 let receivers = token1018 .users1019 .into_iter()1020 .filter(|(_, amount)| *amount > 0)1021 .collect::<Vec<_>>();10221023 if let [(user, _)] = receivers.as_slice() {1024 // if there is exactly one receiver1025 <PalletEvm<T>>::deposit_log(1026 ERC721Events::Transfer {1027 from: H160::default(),1028 to: *user.as_eth(),1029 token_id: token_id.into(),1030 }1031 .to_log(collection_id_to_address(collection.id)),1032 );1033 } else if let [_, ..] = receivers.as_slice() {1034 // if there is more than one receiver1035 <PalletEvm<T>>::deposit_log(1036 ERC721Events::Transfer {1037 from: H160::default(),1038 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,1039 token_id: token_id.into(),1040 }1041 .to_log(collection_id_to_address(collection.id)),1042 );1043 }10441045 for (user, amount) in receivers.into_iter() {1046 <PalletEvm<T>>::deposit_log(1047 ERC20Events::Transfer {1048 from: H160::default(),1049 to: *user.as_eth(),1050 value: amount.into(),1051 }1052 .to_log(T::EvmTokenAddressMapping::token_to_address(1053 collection.id,1054 TokenId(token_id),1055 )),1056 );1057 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1058 collection.id,1059 TokenId(token_id),1060 user,1061 amount,1062 ));1063 }1064 }1065 Ok(())1066 }10671068 pub fn set_allowance_unchecked(1069 collection: &RefungibleHandle<T>,1070 sender: &T::CrossAccountId,1071 spender: &T::CrossAccountId,1072 token: TokenId,1073 amount: u128,1074 ) {1075 if amount == 0 {1076 <Allowance<T>>::remove((collection.id, token, sender, spender));1077 } else {1078 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);1079 }10801081 <PalletEvm<T>>::deposit_log(1082 ERC20Events::Approval {1083 owner: *sender.as_eth(),1084 spender: *spender.as_eth(),1085 value: amount.into(),1086 }1087 .to_log(T::EvmTokenAddressMapping::token_to_address(1088 collection.id,1089 token,1090 )),1091 );1092 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1093 collection.id,1094 token,1095 sender.clone(),1096 spender.clone(),1097 amount,1098 ))1099 }11001101 /// Set allowance for the spender to `transfer` or `burn` sender's token pieces.1102 ///1103 /// - `amount`: Amount of token pieces the spender is allowed to `transfer` or `burn.1104 pub fn set_allowance(1105 collection: &RefungibleHandle<T>,1106 sender: &T::CrossAccountId,1107 spender: &T::CrossAccountId,1108 token: TokenId,1109 amount: u128,1110 ) -> DispatchResult {1111 if collection.permissions.access() == AccessMode::AllowList {1112 collection.check_allowlist(sender)?;1113 collection.check_allowlist(spender)?;1114 }11151116 <PalletCommon<T>>::ensure_correct_receiver(spender)?;11171118 if <Balance<T>>::get((collection.id, token, sender)) < amount {1119 ensure!(1120 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),1121 <CommonError<T>>::CantApproveMoreThanOwned1122 );1123 }11241125 // =========11261127 Self::set_allowance_unchecked(collection, sender, spender, token, amount);1128 Ok(())1129 }11301131 /// Returns allowance, which should be set after transaction1132 fn check_allowed(1133 collection: &RefungibleHandle<T>,1134 spender: &T::CrossAccountId,1135 from: &T::CrossAccountId,1136 token: TokenId,1137 amount: u128,1138 nesting_budget: &dyn Budget,1139 ) -> Result<Option<u128>, DispatchError> {1140 if spender.conv_eq(from) {1141 return Ok(None);1142 }1143 if collection.permissions.access() == AccessMode::AllowList {1144 // `from`, `to` checked in [`transfer`]1145 collection.check_allowlist(spender)?;1146 }1147 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1148 // TODO: should collection owner be allowed to perform this transfer?1149 ensure!(1150 <PalletStructure<T>>::check_indirectly_owned(1151 spender.clone(),1152 source.0,1153 source.1,1154 None,1155 nesting_budget1156 )?,1157 <CommonError<T>>::ApprovedValueTooLow,1158 );1159 return Ok(None);1160 }1161 let allowance =1162 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);1163 if allowance.is_none() {1164 ensure!(1165 collection.ignores_allowance(spender),1166 <CommonError<T>>::ApprovedValueTooLow1167 );1168 }1169 Ok(allowance)1170 }11711172 /// Transfer RFT token pieces from one account to another.1173 ///1174 /// Same as the [`transfer`] but spender doesn't needs to be an owner of the token pieces.1175 /// The owner should set allowance for the spender to transfer pieces.1176 ///1177 /// [`transfer`]: struct.Pallet.html#method.transfer1178 pub fn transfer_from(1179 collection: &RefungibleHandle<T>,1180 spender: &T::CrossAccountId,1181 from: &T::CrossAccountId,1182 to: &T::CrossAccountId,1183 token: TokenId,1184 amount: u128,1185 nesting_budget: &dyn Budget,1186 ) -> DispatchResult {1187 let allowance =1188 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;11891190 // =========11911192 Self::transfer(collection, from, to, token, amount, nesting_budget)?;1193 if let Some(allowance) = allowance {1194 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1195 }1196 Ok(())1197 }11981199 /// Burn RFT token pieces from the account.1200 ///1201 /// Same as the [`burn`] but spender doesn't need to be an owner of the token pieces. The owner should1202 /// set allowance for the spender to burn pieces1203 ///1204 /// [`burn`]: struct.Pallet.html#method.burn1205 pub fn burn_from(1206 collection: &RefungibleHandle<T>,1207 spender: &T::CrossAccountId,1208 from: &T::CrossAccountId,1209 token: TokenId,1210 amount: u128,1211 nesting_budget: &dyn Budget,1212 ) -> DispatchResult {1213 let allowance =1214 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;12151216 // =========12171218 Self::burn(collection, from, token, amount)?;1219 if let Some(allowance) = allowance {1220 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1221 }1222 Ok(())1223 }12241225 /// Create RFT token.1226 ///1227 /// The sender should be the owner/admin of the collection or collection should be configured1228 /// to allow public minting.1229 ///1230 /// - `data`: Contains list of users who will become the owners of the token pieces and amount1231 /// of token pieces they will receive.1232 pub fn create_item(1233 collection: &RefungibleHandle<T>,1234 sender: &T::CrossAccountId,1235 data: CreateItemData<T::CrossAccountId>,1236 nesting_budget: &dyn Budget,1237 ) -> DispatchResult {1238 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1239 }12401241 /// Repartition RFT token.1242 ///1243 /// `repartition` will set token balance of the sender and total amount of token pieces.1244 /// Sender should own all of the token pieces. `repartition' could be done even if some1245 /// token pieces were burned before.1246 ///1247 /// - `amount`: Total amount of token pieces that the token will have after `repartition`.1248 pub fn repartition(1249 collection: &RefungibleHandle<T>,1250 owner: &T::CrossAccountId,1251 token: TokenId,1252 amount: u128,1253 ) -> DispatchResult {1254 ensure!(1255 amount <= MAX_REFUNGIBLE_PIECES,1256 <Error<T>>::WrongRefungiblePieces1257 );1258 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);1259 // Ensure user owns all pieces1260 let total_pieces = Self::total_pieces(collection.id, token).unwrap_or(u128::MAX);1261 let balance = <Balance<T>>::get((collection.id, token, owner));1262 ensure!(1263 total_pieces == balance,1264 <Error<T>>::RepartitionWhileNotOwningAllPieces1265 );12661267 <Balance<T>>::insert((collection.id, token, owner), amount);1268 <TotalSupply<T>>::insert((collection.id, token), amount);12691270 if amount > total_pieces {1271 let mint_amount = amount - total_pieces;1272 <PalletEvm<T>>::deposit_log(1273 ERC20Events::Transfer {1274 from: H160::default(),1275 to: *owner.as_eth(),1276 value: mint_amount.into(),1277 }1278 .to_log(T::EvmTokenAddressMapping::token_to_address(1279 collection.id,1280 token,1281 )),1282 );1283 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1284 collection.id,1285 token,1286 owner.clone(),1287 mint_amount,1288 ));1289 } else if total_pieces > amount {1290 let burn_amount = total_pieces - amount;1291 <PalletEvm<T>>::deposit_log(1292 ERC20Events::Transfer {1293 from: *owner.as_eth(),1294 to: H160::default(),1295 value: burn_amount.into(),1296 }1297 .to_log(T::EvmTokenAddressMapping::token_to_address(1298 collection.id,1299 token,1300 )),1301 );1302 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(1303 collection.id,1304 token,1305 owner.clone(),1306 burn_amount,1307 ));1308 }13091310 Ok(())1311 }13121313 fn token_owner(collection_id: CollectionId, token_id: TokenId) -> Option<T::CrossAccountId> {1314 let mut owner = None;1315 let mut count = 0;1316 for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {1317 count += 1;1318 if count > 1 {1319 return None;1320 }1321 owner = Some(key);1322 }1323 owner1324 }13251326 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {1327 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()1328 }13291330 pub fn set_collection_properties(1331 collection: &RefungibleHandle<T>,1332 sender: &T::CrossAccountId,1333 properties: Vec<Property>,1334 ) -> DispatchResult {1335 <PalletCommon<T>>::set_collection_properties(collection, sender, properties)1336 }13371338 pub fn delete_collection_properties(1339 collection: &RefungibleHandle<T>,1340 sender: &T::CrossAccountId,1341 property_keys: Vec<PropertyKey>,1342 ) -> DispatchResult {1343 <PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)1344 }13451346 pub fn set_token_property_permissions(1347 collection: &RefungibleHandle<T>,1348 sender: &T::CrossAccountId,1349 property_permissions: Vec<PropertyKeyPermission>,1350 ) -> DispatchResult {1351 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)1352 }13531354 pub fn set_scoped_token_property_permissions(1355 collection: &RefungibleHandle<T>,1356 sender: &T::CrossAccountId,1357 scope: PropertyScope,1358 property_permissions: Vec<PropertyKeyPermission>,1359 ) -> DispatchResult {1360 <PalletCommon<T>>::set_scoped_token_property_permissions(1361 collection,1362 sender,1363 scope,1364 property_permissions,1365 )1366 }13671368 /// Returns 10 token in no particular order.1369 ///1370 /// There is no direct way to get token holders in ascending order,1371 /// since `iter_prefix` returns values in no particular order.1372 /// Therefore, getting the 10 largest holders with a large value of holders1373 /// can lead to impact memory allocation + sorting with `n * log (n)`.1374 pub fn token_owners(1375 collection_id: CollectionId,1376 token: TokenId,1377 ) -> Option<Vec<T::CrossAccountId>> {1378 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection_id, token))1379 .map(|(owner, _amount)| owner)1380 .take(10)1381 .collect();13821383 if res.is_empty() {1384 None1385 } else {1386 Some(res)1387 }1388 }13891390 /// Sets the NFT token as a parent for the RFT token1391 ///1392 /// Throws if `sender` is not the owner of the NFT token.1393 /// Throws if `sender` is not the owner of all of the RFT token pieces.1394 pub fn set_parent_nft(1395 collection: &RefungibleHandle<T>,1396 rft_token_id: TokenId,1397 sender: T::CrossAccountId,1398 nft_collection: CollectionId,1399 nft_token: TokenId,1400 ) -> DispatchResult {1401 let handle = <CollectionHandle<T>>::try_get(nft_collection)?;1402 if handle.mode != CollectionMode::NFT {1403 return Err("Only NFT token could be parent to RFT".into());1404 }1405 let dispatch = T::CollectionDispatch::dispatch(handle);1406 let dispatch = dispatch.as_dyn();14071408 let owner = dispatch.token_owner(nft_token).ok_or("owner not found")?;1409 if owner != sender {1410 return Err("Only owned token could be set as parent".into());1411 }14121413 let nft_token_address =1414 T::CrossTokenAddressMapping::token_to_address(nft_collection, nft_token);14151416 Self::set_parent_nft_unchecked(collection, rft_token_id, sender, nft_token_address)1417 }14181419 /// Sets the NFT token as a parent for the RFT token1420 ///1421 /// `sender` should be the owner of the NFT token.1422 /// Throws if `sender` is not the owner of all of the RFT token pieces.1423 pub fn set_parent_nft_unchecked(1424 collection: &RefungibleHandle<T>,1425 rft_token_id: TokenId,1426 sender: T::CrossAccountId,1427 nft_token_address: T::CrossAccountId,1428 ) -> DispatchResult {1429 let owner_balance = <Balance<T>>::get((collection.id, rft_token_id, &sender));1430 let total_supply = <TotalSupply<T>>::get((collection.id, rft_token_id));1431 if total_supply != owner_balance {1432 return Err("token has multiple owners".into());1433 }14341435 let parent_nft_property_key = key::parent_nft();14361437 let parent_nft_property_value =1438 property_value_from_bytes(&nft_token_address.as_eth().to_fixed_bytes())1439 .expect("address should fit in value length limit");14401441 <Pallet<T>>::set_scoped_token_property(1442 collection.id,1443 rft_token_id,1444 PropertyScope::Eth,1445 Property {1446 key: parent_nft_property_key,1447 value: parent_nft_property_value,1448 },1449 )?;14501451 Ok(())1452 }1453}pallets/unique/src/lib.rsdiffbeforeafterboth--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -373,13 +373,16 @@
T::CollectionDispatch::destroy(sender, collection)?;
- <NftTransferBasket<T>>::remove_prefix(collection_id, None);
- <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);
- <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);
+ // TODO: basket cleanup should be moved elsewhere
+ // Maybe runtime dispatch.rs should perform it?
- <NftApproveBasket<T>>::remove_prefix(collection_id, None);
- <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);
- <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);
+ let _ = <NftTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);
+ let _ = <FungibleTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);
+ let _ = <ReFungibleTransferBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);
+
+ let _ = <NftApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);
+ let _ = <FungibleApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);
+ let _ = <RefungibleApproveBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);
Ok(())
}