difftreelog
refactor move foreign flag to collection struct
in: master
9 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -697,6 +697,26 @@
]
[[package]]
+name = "bondrewd"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d1660fac8d3acced44dac64453fafedf5aab2de196b932c727e63e4ae42d1cc"
+dependencies = [
+ "bondrewd-derive",
+]
+
+[[package]]
+name = "bondrewd-derive"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "723da0dee1eef38edc021b0793f892bdc024500c6a5b0727a2efe16f0e0a6977"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "bounded-vec"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5892,6 +5912,7 @@
name = "pallet-foreign-assets"
version = "0.1.0"
dependencies = [
+ "frame-benchmarking",
"frame-support",
"frame-system",
"hex",
@@ -6600,7 +6621,7 @@
[[package]]
name = "pallet-unique"
-version = "0.1.4"
+version = "0.2.0"
dependencies = [
"ethereum",
"evm-coder",
@@ -12634,6 +12655,7 @@
name = "up-data-structs"
version = "0.2.2"
dependencies = [
+ "bondrewd",
"derivative",
"frame-support",
"frame-system",
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -70,6 +70,7 @@
COLLECTION_NUMBER_LIMIT,
Collection,
RpcCollection,
+ CollectionFlags,
CollectionId,
CreateItemData,
MAX_TOKEN_PREFIX_LENGTH,
@@ -252,9 +253,9 @@
}
/// Checks that the collection was created with, and must be operated upon through **Unique API**.
- /// Now check only the `external_collection` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.
+ /// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.
pub fn check_is_internal(&self) -> DispatchResult {
- if self.external_collection {
+ if self.flags.external {
return Err(<Error<T>>::CollectionIsExternal)?;
}
@@ -262,9 +263,9 @@
}
/// Checks that the collection was created with, and must be operated upon through an **assimilated API**.
- /// Now check only the `external_collection` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.
+ /// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.
pub fn check_is_external(&self) -> DispatchResult {
- if !self.external_collection {
+ if !self.flags.external {
return Err(<Error<T>>::CollectionIsInternal)?;
}
@@ -345,7 +346,7 @@
use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};
use frame_system::pallet_prelude::*;
use frame_support::traits::Currency;
- use up_data_structs::{TokenId, mapping::TokenAddressMapping};
+ use up_data_structs::{TokenId, mapping::TokenAddressMapping, CollectionFlags};
use scale_info::TypeInfo;
use weights::WeightInfo;
@@ -792,7 +793,7 @@
sponsorship,
limits,
permissions,
- external_collection,
+ flags,
} = <CollectionById<T>>::get(collection)?;
let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)
@@ -822,7 +823,8 @@
permissions,
token_property_permissions,
properties,
- read_only: external_collection,
+ read_only: flags.external,
+ foreign: flags.foreign,
})
}
}
@@ -861,11 +863,11 @@
///
/// * `owner` - The owner of the collection.
/// * `data` - Description of the created collection.
- /// * `is_external` - Marks that collection managet by not "Unique network".
+ /// * `flags` - Extra flags to store.
pub fn init_collection(
owner: T::CrossAccountId,
data: CreateCollectionData<T::AccountId>,
- is_external: bool,
+ flags: CollectionFlags,
) -> Result<CollectionId, DispatchError> {
{
ensure!(
@@ -909,7 +911,7 @@
Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)
})
.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,
- external_collection: is_external,
+ flags,
};
let mut collection_properties = up_data_structs::CollectionProperties::get();
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -83,8 +83,8 @@
use frame_support::{ensure};
use pallet_evm::account::CrossAccountId;
use up_data_structs::{
- AccessMode, CollectionId, TokenId, CreateCollectionData, mapping::TokenAddressMapping,
- budget::Budget,
+ AccessMode, CollectionId, CollectionFlags, TokenId, CreateCollectionData,
+ mapping::TokenAddressMapping, budget::Budget,
};
use pallet_common::{
Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,
@@ -167,11 +167,6 @@
Value = u128,
QueryKind = ValueQuery,
>;
-
- /// Foreign collection flag
- #[pallet::storage]
- pub type ForeignCollection<T: Config> =
- StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = bool, QueryKind = ValueQuery>;
}
/// Wrapper around untyped collection handle, asserting inner collection is of fungible type.
@@ -217,7 +212,7 @@
owner: T::CrossAccountId,
data: CreateCollectionData<T::AccountId>,
) -> Result<CollectionId, DispatchError> {
- <PalletCommon<T>>::init_collection(owner, data, false)
+ <PalletCommon<T>>::init_collection(owner, data, CollectionFlags::default())
}
/// Initializes the collection with ForeignCollection flag. Returns [CollectionId] on success, [DispatchError] otherwise.
@@ -225,8 +220,14 @@
owner: T::CrossAccountId,
data: CreateCollectionData<T::AccountId>,
) -> Result<CollectionId, DispatchError> {
- let id = <PalletCommon<T>>::init_collection(owner, data, false)?;
- <ForeignCollection<T>>::insert(id, true);
+ let id = <PalletCommon<T>>::init_collection(
+ owner,
+ data,
+ CollectionFlags {
+ foreign: true,
+ ..Default::default()
+ },
+ )?;
Ok(id)
}
@@ -245,7 +246,6 @@
PalletCommon::destroy_collection(collection.0, sender)?;
- <ForeignCollection<T>>::remove(id);
<TotalSupply<T>>::remove(id);
let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);
let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);
@@ -274,10 +274,7 @@
.ok_or(<CommonError<T>>::TokenValueTooLow)?;
// Foreign collection check
- ensure!(
- !<ForeignCollection<T>>::get(collection.id),
- <CommonError<T>>::NoPermission
- );
+ ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);
if collection.permissions.access() == AccessMode::AllowList {
collection.check_allowlist(owner)?;
@@ -502,10 +499,7 @@
nesting_budget: &dyn Budget,
) -> DispatchResult {
// Foreign collection check
- ensure!(
- !<ForeignCollection<T>>::get(collection.id),
- <CommonError<T>>::NoPermission
- );
+ ensure!(!collection.flags.foreign, <CommonError<T>>::NoPermission);
if !collection.is_owner_or_admin(sender) {
ensure!(
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -100,10 +100,10 @@
weights::{PostDispatchInfo, Pays},
};
use up_data_structs::{
- AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,
- mapping::TokenAddressMapping, budget::Budget, Property, PropertyPermission, PropertyKey,
- PropertyValue, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty, TokenChild,
- AuxPropertyValue,
+ AccessMode, CollectionId, CollectionFlags, CustomDataLimit, TokenId, CreateCollectionData,
+ CreateNftExData, mapping::TokenAddressMapping, budget::Budget, Property, PropertyPermission,
+ PropertyKey, PropertyValue, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty,
+ TokenChild, AuxPropertyValue,
};
use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};
use pallet_common::{
@@ -408,7 +408,14 @@
data: CreateCollectionData<T::AccountId>,
is_external: bool,
) -> Result<CollectionId, DispatchError> {
- <PalletCommon<T>>::init_collection(owner, data, is_external)
+ <PalletCommon<T>>::init_collection(
+ owner,
+ data,
+ CollectionFlags {
+ external: is_external,
+ ..Default::default()
+ },
+ )
}
/// Destroy NFT collection
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 let _ = <Balance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);428 let _ = <Allowance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);429 <PalletEvm<T>>::deposit_log(430 ERC721Events::Transfer {431 from: *owner.as_eth(),432 to: H160::default(),433 token_id: token_id.into(),434 }435 .to_log(collection_id_to_address(collection.id)),436 );437 Ok(())438 }439440 /// Burn RFT token pieces441 ///442 /// `burn` will decrease total amount of token pieces and amount owned by sender.443 /// `burn` can be called even if there are multiple owners of the RFT token.444 /// If sender wouldn't have any pieces left after `burn` than she will stop being445 /// one of the owners of the token. If there is no account that owns any pieces of446 /// the token than token will be burned too.447 ///448 /// - `amount`: Amount of token pieces to burn.449 /// - `token`: Token who's pieces should be burned450 /// - `collection`: Collection that contains the token451 pub fn burn(452 collection: &RefungibleHandle<T>,453 owner: &T::CrossAccountId,454 token: TokenId,455 amount: u128,456 ) -> DispatchResult {457 let total_supply = <TotalSupply<T>>::get((collection.id, token))458 .checked_sub(amount)459 .ok_or(<CommonError<T>>::TokenValueTooLow)?;460461 // This was probally last owner of this token?462 if total_supply == 0 {463 // Ensure user actually owns this amount464 ensure!(465 <Balance<T>>::get((collection.id, token, owner)) == amount,466 <CommonError<T>>::TokenValueTooLow467 );468 let account_balance = <AccountBalance<T>>::get((collection.id, owner))469 .checked_sub(1)470 // Should not occur471 .ok_or(ArithmeticError::Underflow)?;472473 // =========474475 <Owned<T>>::remove((collection.id, owner, token));476 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);477 <AccountBalance<T>>::insert((collection.id, owner), account_balance);478 Self::burn_token_unchecked(collection, owner, token)?;479 <PalletEvm<T>>::deposit_log(480 ERC20Events::Transfer {481 from: *owner.as_eth(),482 to: H160::default(),483 value: amount.into(),484 }485 .to_log(collection_id_to_address(collection.id)),486 );487 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(488 collection.id,489 token,490 owner.clone(),491 amount,492 ));493 return Ok(());494 }495496 let balance = <Balance<T>>::get((collection.id, token, owner))497 .checked_sub(amount)498 .ok_or(<CommonError<T>>::TokenValueTooLow)?;499 let account_balance = if balance == 0 {500 <AccountBalance<T>>::get((collection.id, owner))501 .checked_sub(1)502 // Should not occur503 .ok_or(ArithmeticError::Underflow)?504 } else {505 0506 };507508 // =========509510 if balance == 0 {511 <Owned<T>>::remove((collection.id, owner, token));512 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);513 <Balance<T>>::remove((collection.id, token, owner));514 <AccountBalance<T>>::insert((collection.id, owner), account_balance);515516 if let Some(user) = Self::token_owner(collection.id, token) {517 <PalletEvm<T>>::deposit_log(518 ERC721Events::Transfer {519 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,520 to: *user.as_eth(),521 token_id: token.into(),522 }523 .to_log(collection_id_to_address(collection.id)),524 );525 }526 } else {527 <Balance<T>>::insert((collection.id, token, owner), balance);528 }529 <TotalSupply<T>>::insert((collection.id, token), total_supply);530531 <PalletEvm<T>>::deposit_log(532 ERC20Events::Transfer {533 from: *owner.as_eth(),534 to: H160::default(),535 value: amount.into(),536 }537 .to_log(T::EvmTokenAddressMapping::token_to_address(538 collection.id,539 token,540 )),541 );542 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(543 collection.id,544 token,545 owner.clone(),546 amount,547 ));548 Ok(())549 }550551 #[transactional]552 fn modify_token_properties(553 collection: &RefungibleHandle<T>,554 sender: &T::CrossAccountId,555 token_id: TokenId,556 properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,557 is_token_create: bool,558 nesting_budget: &dyn Budget,559 ) -> DispatchResult {560 let is_collection_admin = || collection.is_owner_or_admin(sender);561 let is_token_owner = || -> Result<bool, DispatchError> {562 let balance = collection.balance(sender.clone(), token_id);563 let total_pieces: u128 =564 Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);565 if balance != total_pieces {566 return Ok(false);567 }568569 let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(570 sender.clone(),571 collection.id,572 token_id,573 None,574 nesting_budget,575 )?;576577 Ok(is_bundle_owner)578 };579580 for (key, value) in properties {581 let permission = <PalletCommon<T>>::property_permissions(collection.id)582 .get(&key)583 .cloned()584 .unwrap_or_else(PropertyPermission::none);585586 let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))587 .get(&key)588 .is_some();589590 match permission {591 PropertyPermission { mutable: false, .. } if is_property_exists => {592 return Err(<CommonError<T>>::NoPermission.into());593 }594595 PropertyPermission {596 collection_admin,597 token_owner,598 ..599 } => {600 //TODO: investigate threats during public minting.601 let is_token_create =602 is_token_create && (collection_admin || token_owner) && value.is_some();603 if !(is_token_create604 || (collection_admin && is_collection_admin())605 || (token_owner && is_token_owner()?))606 {607 fail!(<CommonError<T>>::NoPermission);608 }609 }610 }611612 match value {613 Some(value) => {614 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {615 properties.try_set(key.clone(), value)616 })617 .map_err(<CommonError<T>>::from)?;618619 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(620 collection.id,621 token_id,622 key,623 ));624 }625 None => {626 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {627 properties.remove(&key)628 })629 .map_err(<CommonError<T>>::from)?;630631 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(632 collection.id,633 token_id,634 key,635 ));636 }637 }638 }639640 Ok(())641 }642643 pub fn set_token_properties(644 collection: &RefungibleHandle<T>,645 sender: &T::CrossAccountId,646 token_id: TokenId,647 properties: impl Iterator<Item = Property>,648 is_token_create: bool,649 nesting_budget: &dyn Budget,650 ) -> DispatchResult {651 Self::modify_token_properties(652 collection,653 sender,654 token_id,655 properties.map(|p| (p.key, Some(p.value))),656 is_token_create,657 nesting_budget,658 )659 }660661 pub fn set_token_property(662 collection: &RefungibleHandle<T>,663 sender: &T::CrossAccountId,664 token_id: TokenId,665 property: Property,666 nesting_budget: &dyn Budget,667 ) -> DispatchResult {668 let is_token_create = false;669670 Self::set_token_properties(671 collection,672 sender,673 token_id,674 [property].into_iter(),675 is_token_create,676 nesting_budget,677 )678 }679680 pub fn delete_token_properties(681 collection: &RefungibleHandle<T>,682 sender: &T::CrossAccountId,683 token_id: TokenId,684 property_keys: impl Iterator<Item = PropertyKey>,685 nesting_budget: &dyn Budget,686 ) -> DispatchResult {687 let is_token_create = false;688689 Self::modify_token_properties(690 collection,691 sender,692 token_id,693 property_keys.into_iter().map(|key| (key, None)),694 is_token_create,695 nesting_budget,696 )697 }698699 pub fn delete_token_property(700 collection: &RefungibleHandle<T>,701 sender: &T::CrossAccountId,702 token_id: TokenId,703 property_key: PropertyKey,704 nesting_budget: &dyn Budget,705 ) -> DispatchResult {706 Self::delete_token_properties(707 collection,708 sender,709 token_id,710 [property_key].into_iter(),711 nesting_budget,712 )713 }714715 /// Transfer RFT token pieces from one account to another.716 ///717 /// If the sender is no longer owns any pieces after the `transfer` than she stops being an owner of the token.718 ///719 /// - `from`: Owner of token pieces to transfer.720 /// - `to`: Recepient of transfered token pieces.721 /// - `amount`: Amount of token pieces to transfer.722 /// - `token`: Token whos pieces should be transfered723 /// - `collection`: Collection that contains the token724 pub fn transfer(725 collection: &RefungibleHandle<T>,726 from: &T::CrossAccountId,727 to: &T::CrossAccountId,728 token: TokenId,729 amount: u128,730 nesting_budget: &dyn Budget,731 ) -> DispatchResult {732 ensure!(733 collection.limits.transfers_enabled(),734 <CommonError<T>>::TransferNotAllowed735 );736737 if collection.permissions.access() == AccessMode::AllowList {738 collection.check_allowlist(from)?;739 collection.check_allowlist(to)?;740 }741 <PalletCommon<T>>::ensure_correct_receiver(to)?;742743 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));744 let updated_balance_from = initial_balance_from745 .checked_sub(amount)746 .ok_or(<CommonError<T>>::TokenValueTooLow)?;747 let mut create_target = false;748 let from_to_differ = from != to;749 let updated_balance_to = if from != to {750 let old_balance = <Balance<T>>::get((collection.id, token, to));751 if old_balance == 0 {752 create_target = true;753 }754 Some(755 old_balance756 .checked_add(amount)757 .ok_or(ArithmeticError::Overflow)?,758 )759 } else {760 None761 };762763 let account_balance_from = if updated_balance_from == 0 {764 Some(765 <AccountBalance<T>>::get((collection.id, from))766 .checked_sub(1)767 // Should not occur768 .ok_or(ArithmeticError::Underflow)?,769 )770 } else {771 None772 };773 // Account data is created in token, AccountBalance should be increased774 // But only if from != to as we shouldn't check overflow in this case775 let account_balance_to = if create_target && from_to_differ {776 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))777 .checked_add(1)778 .ok_or(ArithmeticError::Overflow)?;779 ensure!(780 account_balance_to < collection.limits.account_token_ownership_limit(),781 <CommonError<T>>::AccountTokenLimitExceeded,782 );783784 Some(account_balance_to)785 } else {786 None787 };788789 // =========790791 <PalletStructure<T>>::nest_if_sent_to_token(792 from.clone(),793 to,794 collection.id,795 token,796 nesting_budget,797 )?;798799 if let Some(updated_balance_to) = updated_balance_to {800 // from != to801 if updated_balance_from == 0 {802 <Balance<T>>::remove((collection.id, token, from));803 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);804 } else {805 <Balance<T>>::insert((collection.id, token, from), updated_balance_from);806 }807 <Balance<T>>::insert((collection.id, token, to), updated_balance_to);808 if let Some(account_balance_from) = account_balance_from {809 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);810 <Owned<T>>::remove((collection.id, from, token));811 }812 if let Some(account_balance_to) = account_balance_to {813 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);814 <Owned<T>>::insert((collection.id, to, token), true);815 }816 }817818 <PalletEvm<T>>::deposit_log(819 ERC20Events::Transfer {820 from: *from.as_eth(),821 to: *to.as_eth(),822 value: amount.into(),823 }824 .to_log(T::EvmTokenAddressMapping::token_to_address(825 collection.id,826 token,827 )),828 );829830 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(831 collection.id,832 token,833 from.clone(),834 to.clone(),835 amount,836 ));837838 let total_supply = <TotalSupply<T>>::get((collection.id, token));839840 if amount == total_supply {841 // if token was fully owned by `from` and will be fully owned by `to` after transfer842 <PalletEvm<T>>::deposit_log(843 ERC721Events::Transfer {844 from: *from.as_eth(),845 to: *to.as_eth(),846 token_id: token.into(),847 }848 .to_log(collection_id_to_address(collection.id)),849 );850 } else if let Some(updated_balance_to) = updated_balance_to {851 // if `from` not equals `to`. This condition is needed to avoid sending event852 // when `from` fully owns token and sends part of token pieces to itself.853 if initial_balance_from == total_supply {854 // if token was fully owned by `from` and will be only partially owned by `to`855 // and `from` after transfer856 <PalletEvm<T>>::deposit_log(857 ERC721Events::Transfer {858 from: *from.as_eth(),859 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,860 token_id: token.into(),861 }862 .to_log(collection_id_to_address(collection.id)),863 );864 } else if updated_balance_to == total_supply {865 // if token was partially owned by `from` and will be fully owned by `to` after transfer866 <PalletEvm<T>>::deposit_log(867 ERC721Events::Transfer {868 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,869 to: *to.as_eth(),870 token_id: token.into(),871 }872 .to_log(collection_id_to_address(collection.id)),873 );874 }875 }876877 Ok(())878 }879880 /// Batched operation to create multiple RFT tokens.881 ///882 /// Same as `create_item` but creates multiple tokens.883 ///884 /// - `data`: Same as 'data` in `create_item` but contains data for multiple tokens.885 pub fn create_multiple_items(886 collection: &RefungibleHandle<T>,887 sender: &T::CrossAccountId,888 data: Vec<CreateItemData<T::CrossAccountId>>,889 nesting_budget: &dyn Budget,890 ) -> DispatchResult {891 if !collection.is_owner_or_admin(sender) {892 ensure!(893 collection.permissions.mint_mode(),894 <CommonError<T>>::PublicMintingNotAllowed895 );896 collection.check_allowlist(sender)?;897898 for item in data.iter() {899 for user in item.users.keys() {900 collection.check_allowlist(user)?;901 }902 }903 }904905 for item in data.iter() {906 for (owner, _) in item.users.iter() {907 <PalletCommon<T>>::ensure_correct_receiver(owner)?;908 }909 }910911 // Total pieces per tokens912 let totals = data913 .iter()914 .map(|data| {915 Ok(data916 .users917 .iter()918 .map(|u| u.1)919 .try_fold(0u128, |acc, v| acc.checked_add(*v))920 .ok_or(ArithmeticError::Overflow)?)921 })922 .collect::<Result<Vec<_>, DispatchError>>()?;923 for total in &totals {924 ensure!(925 *total <= MAX_REFUNGIBLE_PIECES,926 <Error<T>>::WrongRefungiblePieces927 );928 }929930 let first_token_id = <TokensMinted<T>>::get(collection.id);931 let tokens_minted = first_token_id932 .checked_add(data.len() as u32)933 .ok_or(ArithmeticError::Overflow)?;934 ensure!(935 tokens_minted < collection.limits.token_limit(),936 <CommonError<T>>::CollectionTokenLimitExceeded937 );938939 let mut balances = BTreeMap::new();940 for data in &data {941 for owner in data.users.keys() {942 let balance = balances943 .entry(owner)944 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));945 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;946947 ensure!(948 *balance <= collection.limits.account_token_ownership_limit(),949 <CommonError<T>>::AccountTokenLimitExceeded,950 );951 }952 }953954 for (i, token) in data.iter().enumerate() {955 let token_id = TokenId(first_token_id + i as u32 + 1);956 for (to, _) in token.users.iter() {957 <PalletStructure<T>>::check_nesting(958 sender.clone(),959 to,960 collection.id,961 token_id,962 nesting_budget,963 )?;964 }965 }966967 // =========968969 with_transaction(|| {970 for (i, data) in data.iter().enumerate() {971 let token_id = first_token_id + i as u32 + 1;972 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);973974 for (user, amount) in data.users.iter() {975 if *amount == 0 {976 continue;977 }978 <Balance<T>>::insert((collection.id, token_id, &user), amount);979 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);980 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(981 user,982 collection.id,983 TokenId(token_id),984 );985 }986987 if let Err(e) = Self::set_token_properties(988 collection,989 sender,990 TokenId(token_id),991 data.properties.clone().into_iter(),992 true,993 nesting_budget,994 ) {995 return TransactionOutcome::Rollback(Err(e));996 }997 }998 TransactionOutcome::Commit(Ok(()))999 })?;10001001 <TokensMinted<T>>::insert(collection.id, tokens_minted);10021003 for (account, balance) in balances {1004 <AccountBalance<T>>::insert((collection.id, account), balance);1005 }10061007 for (i, token) in data.into_iter().enumerate() {1008 let token_id = first_token_id + i as u32 + 1;10091010 let receivers = token1011 .users1012 .into_iter()1013 .filter(|(_, amount)| *amount > 0)1014 .collect::<Vec<_>>();10151016 if let [(user, _)] = receivers.as_slice() {1017 // if there is exactly one receiver1018 <PalletEvm<T>>::deposit_log(1019 ERC721Events::Transfer {1020 from: H160::default(),1021 to: *user.as_eth(),1022 token_id: token_id.into(),1023 }1024 .to_log(collection_id_to_address(collection.id)),1025 );1026 } else if let [_, ..] = receivers.as_slice() {1027 // if there is more than one receiver1028 <PalletEvm<T>>::deposit_log(1029 ERC721Events::Transfer {1030 from: H160::default(),1031 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,1032 token_id: token_id.into(),1033 }1034 .to_log(collection_id_to_address(collection.id)),1035 );1036 }10371038 for (user, amount) in receivers.into_iter() {1039 <PalletEvm<T>>::deposit_log(1040 ERC20Events::Transfer {1041 from: H160::default(),1042 to: *user.as_eth(),1043 value: amount.into(),1044 }1045 .to_log(T::EvmTokenAddressMapping::token_to_address(1046 collection.id,1047 TokenId(token_id),1048 )),1049 );1050 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1051 collection.id,1052 TokenId(token_id),1053 user,1054 amount,1055 ));1056 }1057 }1058 Ok(())1059 }10601061 pub fn set_allowance_unchecked(1062 collection: &RefungibleHandle<T>,1063 sender: &T::CrossAccountId,1064 spender: &T::CrossAccountId,1065 token: TokenId,1066 amount: u128,1067 ) {1068 if amount == 0 {1069 <Allowance<T>>::remove((collection.id, token, sender, spender));1070 } else {1071 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);1072 }10731074 <PalletEvm<T>>::deposit_log(1075 ERC20Events::Approval {1076 owner: *sender.as_eth(),1077 spender: *spender.as_eth(),1078 value: amount.into(),1079 }1080 .to_log(T::EvmTokenAddressMapping::token_to_address(1081 collection.id,1082 token,1083 )),1084 );1085 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1086 collection.id,1087 token,1088 sender.clone(),1089 spender.clone(),1090 amount,1091 ))1092 }10931094 /// Set allowance for the spender to `transfer` or `burn` sender's token pieces.1095 ///1096 /// - `amount`: Amount of token pieces the spender is allowed to `transfer` or `burn.1097 pub fn set_allowance(1098 collection: &RefungibleHandle<T>,1099 sender: &T::CrossAccountId,1100 spender: &T::CrossAccountId,1101 token: TokenId,1102 amount: u128,1103 ) -> DispatchResult {1104 if collection.permissions.access() == AccessMode::AllowList {1105 collection.check_allowlist(sender)?;1106 collection.check_allowlist(spender)?;1107 }11081109 <PalletCommon<T>>::ensure_correct_receiver(spender)?;11101111 if <Balance<T>>::get((collection.id, token, sender)) < amount {1112 ensure!(1113 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),1114 <CommonError<T>>::CantApproveMoreThanOwned1115 );1116 }11171118 // =========11191120 Self::set_allowance_unchecked(collection, sender, spender, token, amount);1121 Ok(())1122 }11231124 /// Returns allowance, which should be set after transaction1125 fn check_allowed(1126 collection: &RefungibleHandle<T>,1127 spender: &T::CrossAccountId,1128 from: &T::CrossAccountId,1129 token: TokenId,1130 amount: u128,1131 nesting_budget: &dyn Budget,1132 ) -> Result<Option<u128>, DispatchError> {1133 if spender.conv_eq(from) {1134 return Ok(None);1135 }1136 if collection.permissions.access() == AccessMode::AllowList {1137 // `from`, `to` checked in [`transfer`]1138 collection.check_allowlist(spender)?;1139 }1140 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1141 // TODO: should collection owner be allowed to perform this transfer?1142 ensure!(1143 <PalletStructure<T>>::check_indirectly_owned(1144 spender.clone(),1145 source.0,1146 source.1,1147 None,1148 nesting_budget1149 )?,1150 <CommonError<T>>::ApprovedValueTooLow,1151 );1152 return Ok(None);1153 }1154 let allowance =1155 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);1156 if allowance.is_none() {1157 ensure!(1158 collection.ignores_allowance(spender),1159 <CommonError<T>>::ApprovedValueTooLow1160 );1161 }1162 Ok(allowance)1163 }11641165 /// Transfer RFT token pieces from one account to another.1166 ///1167 /// Same as the [`transfer`] but spender doesn't needs to be an owner of the token pieces.1168 /// The owner should set allowance for the spender to transfer pieces.1169 ///1170 /// [`transfer`]: struct.Pallet.html#method.transfer1171 pub fn transfer_from(1172 collection: &RefungibleHandle<T>,1173 spender: &T::CrossAccountId,1174 from: &T::CrossAccountId,1175 to: &T::CrossAccountId,1176 token: TokenId,1177 amount: u128,1178 nesting_budget: &dyn Budget,1179 ) -> DispatchResult {1180 let allowance =1181 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;11821183 // =========11841185 Self::transfer(collection, from, to, token, amount, nesting_budget)?;1186 if let Some(allowance) = allowance {1187 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1188 }1189 Ok(())1190 }11911192 /// Burn RFT token pieces from the account.1193 ///1194 /// Same as the [`burn`] but spender doesn't need to be an owner of the token pieces. The owner should1195 /// set allowance for the spender to burn pieces1196 ///1197 /// [`burn`]: struct.Pallet.html#method.burn1198 pub fn burn_from(1199 collection: &RefungibleHandle<T>,1200 spender: &T::CrossAccountId,1201 from: &T::CrossAccountId,1202 token: TokenId,1203 amount: u128,1204 nesting_budget: &dyn Budget,1205 ) -> DispatchResult {1206 let allowance =1207 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;12081209 // =========12101211 Self::burn(collection, from, token, amount)?;1212 if let Some(allowance) = allowance {1213 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1214 }1215 Ok(())1216 }12171218 /// Create RFT token.1219 ///1220 /// The sender should be the owner/admin of the collection or collection should be configured1221 /// to allow public minting.1222 ///1223 /// - `data`: Contains list of users who will become the owners of the token pieces and amount1224 /// of token pieces they will receive.1225 pub fn create_item(1226 collection: &RefungibleHandle<T>,1227 sender: &T::CrossAccountId,1228 data: CreateItemData<T::CrossAccountId>,1229 nesting_budget: &dyn Budget,1230 ) -> DispatchResult {1231 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1232 }12331234 /// Repartition RFT token.1235 ///1236 /// `repartition` will set token balance of the sender and total amount of token pieces.1237 /// Sender should own all of the token pieces. `repartition' could be done even if some1238 /// token pieces were burned before.1239 ///1240 /// - `amount`: Total amount of token pieces that the token will have after `repartition`.1241 pub fn repartition(1242 collection: &RefungibleHandle<T>,1243 owner: &T::CrossAccountId,1244 token: TokenId,1245 amount: u128,1246 ) -> DispatchResult {1247 ensure!(1248 amount <= MAX_REFUNGIBLE_PIECES,1249 <Error<T>>::WrongRefungiblePieces1250 );1251 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);1252 // Ensure user owns all pieces1253 let total_pieces = Self::total_pieces(collection.id, token).unwrap_or(u128::MAX);1254 let balance = <Balance<T>>::get((collection.id, token, owner));1255 ensure!(1256 total_pieces == balance,1257 <Error<T>>::RepartitionWhileNotOwningAllPieces1258 );12591260 <Balance<T>>::insert((collection.id, token, owner), amount);1261 <TotalSupply<T>>::insert((collection.id, token), amount);12621263 if amount > total_pieces {1264 let mint_amount = amount - total_pieces;1265 <PalletEvm<T>>::deposit_log(1266 ERC20Events::Transfer {1267 from: H160::default(),1268 to: *owner.as_eth(),1269 value: mint_amount.into(),1270 }1271 .to_log(T::EvmTokenAddressMapping::token_to_address(1272 collection.id,1273 token,1274 )),1275 );1276 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1277 collection.id,1278 token,1279 owner.clone(),1280 mint_amount,1281 ));1282 } else if total_pieces > amount {1283 let burn_amount = total_pieces - amount;1284 <PalletEvm<T>>::deposit_log(1285 ERC20Events::Transfer {1286 from: *owner.as_eth(),1287 to: H160::default(),1288 value: burn_amount.into(),1289 }1290 .to_log(T::EvmTokenAddressMapping::token_to_address(1291 collection.id,1292 token,1293 )),1294 );1295 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(1296 collection.id,1297 token,1298 owner.clone(),1299 burn_amount,1300 ));1301 }13021303 Ok(())1304 }13051306 fn token_owner(collection_id: CollectionId, token_id: TokenId) -> Option<T::CrossAccountId> {1307 let mut owner = None;1308 let mut count = 0;1309 for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {1310 count += 1;1311 if count > 1 {1312 return None;1313 }1314 owner = Some(key);1315 }1316 owner1317 }13181319 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {1320 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()1321 }13221323 pub fn set_collection_properties(1324 collection: &RefungibleHandle<T>,1325 sender: &T::CrossAccountId,1326 properties: Vec<Property>,1327 ) -> DispatchResult {1328 <PalletCommon<T>>::set_collection_properties(collection, sender, properties)1329 }13301331 pub fn delete_collection_properties(1332 collection: &RefungibleHandle<T>,1333 sender: &T::CrossAccountId,1334 property_keys: Vec<PropertyKey>,1335 ) -> DispatchResult {1336 <PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)1337 }13381339 pub fn set_token_property_permissions(1340 collection: &RefungibleHandle<T>,1341 sender: &T::CrossAccountId,1342 property_permissions: Vec<PropertyKeyPermission>,1343 ) -> DispatchResult {1344 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)1345 }13461347 pub fn set_scoped_token_property_permissions(1348 collection: &RefungibleHandle<T>,1349 sender: &T::CrossAccountId,1350 scope: PropertyScope,1351 property_permissions: Vec<PropertyKeyPermission>,1352 ) -> DispatchResult {1353 <PalletCommon<T>>::set_scoped_token_property_permissions(1354 collection,1355 sender,1356 scope,1357 property_permissions,1358 )1359 }13601361 /// Returns 10 token in no particular order.1362 ///1363 /// There is no direct way to get token holders in ascending order,1364 /// since `iter_prefix` returns values in no particular order.1365 /// Therefore, getting the 10 largest holders with a large value of holders1366 /// can lead to impact memory allocation + sorting with `n * log (n)`.1367 pub fn token_owners(1368 collection_id: CollectionId,1369 token: TokenId,1370 ) -> Option<Vec<T::CrossAccountId>> {1371 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection_id, token))1372 .map(|(owner, _amount)| owner)1373 .take(10)1374 .collect();13751376 if res.is_empty() {1377 None1378 } else {1379 Some(res)1380 }1381 }1382}1// 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, CollectionFlags,116 CollectionPropertiesVec, CreateCollectionData, CustomDataLimit, mapping::TokenAddressMapping,117 MAX_ITEMS_PER_BATCH, MAX_REFUNGIBLE_PIECES, Property, PropertyKey, PropertyKeyPermission,118 PropertyPermission, 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, CollectionFlags::default())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 let _ = <Balance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);428 let _ = <Allowance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);429 <PalletEvm<T>>::deposit_log(430 ERC721Events::Transfer {431 from: *owner.as_eth(),432 to: H160::default(),433 token_id: token_id.into(),434 }435 .to_log(collection_id_to_address(collection.id)),436 );437 Ok(())438 }439440 /// Burn RFT token pieces441 ///442 /// `burn` will decrease total amount of token pieces and amount owned by sender.443 /// `burn` can be called even if there are multiple owners of the RFT token.444 /// If sender wouldn't have any pieces left after `burn` than she will stop being445 /// one of the owners of the token. If there is no account that owns any pieces of446 /// the token than token will be burned too.447 ///448 /// - `amount`: Amount of token pieces to burn.449 /// - `token`: Token who's pieces should be burned450 /// - `collection`: Collection that contains the token451 pub fn burn(452 collection: &RefungibleHandle<T>,453 owner: &T::CrossAccountId,454 token: TokenId,455 amount: u128,456 ) -> DispatchResult {457 let total_supply = <TotalSupply<T>>::get((collection.id, token))458 .checked_sub(amount)459 .ok_or(<CommonError<T>>::TokenValueTooLow)?;460461 // This was probally last owner of this token?462 if total_supply == 0 {463 // Ensure user actually owns this amount464 ensure!(465 <Balance<T>>::get((collection.id, token, owner)) == amount,466 <CommonError<T>>::TokenValueTooLow467 );468 let account_balance = <AccountBalance<T>>::get((collection.id, owner))469 .checked_sub(1)470 // Should not occur471 .ok_or(ArithmeticError::Underflow)?;472473 // =========474475 <Owned<T>>::remove((collection.id, owner, token));476 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);477 <AccountBalance<T>>::insert((collection.id, owner), account_balance);478 Self::burn_token_unchecked(collection, owner, token)?;479 <PalletEvm<T>>::deposit_log(480 ERC20Events::Transfer {481 from: *owner.as_eth(),482 to: H160::default(),483 value: amount.into(),484 }485 .to_log(collection_id_to_address(collection.id)),486 );487 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(488 collection.id,489 token,490 owner.clone(),491 amount,492 ));493 return Ok(());494 }495496 let balance = <Balance<T>>::get((collection.id, token, owner))497 .checked_sub(amount)498 .ok_or(<CommonError<T>>::TokenValueTooLow)?;499 let account_balance = if balance == 0 {500 <AccountBalance<T>>::get((collection.id, owner))501 .checked_sub(1)502 // Should not occur503 .ok_or(ArithmeticError::Underflow)?504 } else {505 0506 };507508 // =========509510 if balance == 0 {511 <Owned<T>>::remove((collection.id, owner, token));512 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);513 <Balance<T>>::remove((collection.id, token, owner));514 <AccountBalance<T>>::insert((collection.id, owner), account_balance);515516 if let Some(user) = Self::token_owner(collection.id, token) {517 <PalletEvm<T>>::deposit_log(518 ERC721Events::Transfer {519 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,520 to: *user.as_eth(),521 token_id: token.into(),522 }523 .to_log(collection_id_to_address(collection.id)),524 );525 }526 } else {527 <Balance<T>>::insert((collection.id, token, owner), balance);528 }529 <TotalSupply<T>>::insert((collection.id, token), total_supply);530531 <PalletEvm<T>>::deposit_log(532 ERC20Events::Transfer {533 from: *owner.as_eth(),534 to: H160::default(),535 value: amount.into(),536 }537 .to_log(T::EvmTokenAddressMapping::token_to_address(538 collection.id,539 token,540 )),541 );542 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(543 collection.id,544 token,545 owner.clone(),546 amount,547 ));548 Ok(())549 }550551 #[transactional]552 fn modify_token_properties(553 collection: &RefungibleHandle<T>,554 sender: &T::CrossAccountId,555 token_id: TokenId,556 properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,557 is_token_create: bool,558 nesting_budget: &dyn Budget,559 ) -> DispatchResult {560 let is_collection_admin = || collection.is_owner_or_admin(sender);561 let is_token_owner = || -> Result<bool, DispatchError> {562 let balance = collection.balance(sender.clone(), token_id);563 let total_pieces: u128 =564 Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);565 if balance != total_pieces {566 return Ok(false);567 }568569 let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(570 sender.clone(),571 collection.id,572 token_id,573 None,574 nesting_budget,575 )?;576577 Ok(is_bundle_owner)578 };579580 for (key, value) in properties {581 let permission = <PalletCommon<T>>::property_permissions(collection.id)582 .get(&key)583 .cloned()584 .unwrap_or_else(PropertyPermission::none);585586 let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))587 .get(&key)588 .is_some();589590 match permission {591 PropertyPermission { mutable: false, .. } if is_property_exists => {592 return Err(<CommonError<T>>::NoPermission.into());593 }594595 PropertyPermission {596 collection_admin,597 token_owner,598 ..599 } => {600 //TODO: investigate threats during public minting.601 let is_token_create =602 is_token_create && (collection_admin || token_owner) && value.is_some();603 if !(is_token_create604 || (collection_admin && is_collection_admin())605 || (token_owner && is_token_owner()?))606 {607 fail!(<CommonError<T>>::NoPermission);608 }609 }610 }611612 match value {613 Some(value) => {614 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {615 properties.try_set(key.clone(), value)616 })617 .map_err(<CommonError<T>>::from)?;618619 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(620 collection.id,621 token_id,622 key,623 ));624 }625 None => {626 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {627 properties.remove(&key)628 })629 .map_err(<CommonError<T>>::from)?;630631 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(632 collection.id,633 token_id,634 key,635 ));636 }637 }638 }639640 Ok(())641 }642643 pub fn set_token_properties(644 collection: &RefungibleHandle<T>,645 sender: &T::CrossAccountId,646 token_id: TokenId,647 properties: impl Iterator<Item = Property>,648 is_token_create: bool,649 nesting_budget: &dyn Budget,650 ) -> DispatchResult {651 Self::modify_token_properties(652 collection,653 sender,654 token_id,655 properties.map(|p| (p.key, Some(p.value))),656 is_token_create,657 nesting_budget,658 )659 }660661 pub fn set_token_property(662 collection: &RefungibleHandle<T>,663 sender: &T::CrossAccountId,664 token_id: TokenId,665 property: Property,666 nesting_budget: &dyn Budget,667 ) -> DispatchResult {668 let is_token_create = false;669670 Self::set_token_properties(671 collection,672 sender,673 token_id,674 [property].into_iter(),675 is_token_create,676 nesting_budget,677 )678 }679680 pub fn delete_token_properties(681 collection: &RefungibleHandle<T>,682 sender: &T::CrossAccountId,683 token_id: TokenId,684 property_keys: impl Iterator<Item = PropertyKey>,685 nesting_budget: &dyn Budget,686 ) -> DispatchResult {687 let is_token_create = false;688689 Self::modify_token_properties(690 collection,691 sender,692 token_id,693 property_keys.into_iter().map(|key| (key, None)),694 is_token_create,695 nesting_budget,696 )697 }698699 pub fn delete_token_property(700 collection: &RefungibleHandle<T>,701 sender: &T::CrossAccountId,702 token_id: TokenId,703 property_key: PropertyKey,704 nesting_budget: &dyn Budget,705 ) -> DispatchResult {706 Self::delete_token_properties(707 collection,708 sender,709 token_id,710 [property_key].into_iter(),711 nesting_budget,712 )713 }714715 /// Transfer RFT token pieces from one account to another.716 ///717 /// If the sender is no longer owns any pieces after the `transfer` than she stops being an owner of the token.718 ///719 /// - `from`: Owner of token pieces to transfer.720 /// - `to`: Recepient of transfered token pieces.721 /// - `amount`: Amount of token pieces to transfer.722 /// - `token`: Token whos pieces should be transfered723 /// - `collection`: Collection that contains the token724 pub fn transfer(725 collection: &RefungibleHandle<T>,726 from: &T::CrossAccountId,727 to: &T::CrossAccountId,728 token: TokenId,729 amount: u128,730 nesting_budget: &dyn Budget,731 ) -> DispatchResult {732 ensure!(733 collection.limits.transfers_enabled(),734 <CommonError<T>>::TransferNotAllowed735 );736737 if collection.permissions.access() == AccessMode::AllowList {738 collection.check_allowlist(from)?;739 collection.check_allowlist(to)?;740 }741 <PalletCommon<T>>::ensure_correct_receiver(to)?;742743 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));744 let updated_balance_from = initial_balance_from745 .checked_sub(amount)746 .ok_or(<CommonError<T>>::TokenValueTooLow)?;747 let mut create_target = false;748 let from_to_differ = from != to;749 let updated_balance_to = if from != to {750 let old_balance = <Balance<T>>::get((collection.id, token, to));751 if old_balance == 0 {752 create_target = true;753 }754 Some(755 old_balance756 .checked_add(amount)757 .ok_or(ArithmeticError::Overflow)?,758 )759 } else {760 None761 };762763 let account_balance_from = if updated_balance_from == 0 {764 Some(765 <AccountBalance<T>>::get((collection.id, from))766 .checked_sub(1)767 // Should not occur768 .ok_or(ArithmeticError::Underflow)?,769 )770 } else {771 None772 };773 // Account data is created in token, AccountBalance should be increased774 // But only if from != to as we shouldn't check overflow in this case775 let account_balance_to = if create_target && from_to_differ {776 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))777 .checked_add(1)778 .ok_or(ArithmeticError::Overflow)?;779 ensure!(780 account_balance_to < collection.limits.account_token_ownership_limit(),781 <CommonError<T>>::AccountTokenLimitExceeded,782 );783784 Some(account_balance_to)785 } else {786 None787 };788789 // =========790791 <PalletStructure<T>>::nest_if_sent_to_token(792 from.clone(),793 to,794 collection.id,795 token,796 nesting_budget,797 )?;798799 if let Some(updated_balance_to) = updated_balance_to {800 // from != to801 if updated_balance_from == 0 {802 <Balance<T>>::remove((collection.id, token, from));803 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);804 } else {805 <Balance<T>>::insert((collection.id, token, from), updated_balance_from);806 }807 <Balance<T>>::insert((collection.id, token, to), updated_balance_to);808 if let Some(account_balance_from) = account_balance_from {809 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);810 <Owned<T>>::remove((collection.id, from, token));811 }812 if let Some(account_balance_to) = account_balance_to {813 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);814 <Owned<T>>::insert((collection.id, to, token), true);815 }816 }817818 <PalletEvm<T>>::deposit_log(819 ERC20Events::Transfer {820 from: *from.as_eth(),821 to: *to.as_eth(),822 value: amount.into(),823 }824 .to_log(T::EvmTokenAddressMapping::token_to_address(825 collection.id,826 token,827 )),828 );829830 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(831 collection.id,832 token,833 from.clone(),834 to.clone(),835 amount,836 ));837838 let total_supply = <TotalSupply<T>>::get((collection.id, token));839840 if amount == total_supply {841 // if token was fully owned by `from` and will be fully owned by `to` after transfer842 <PalletEvm<T>>::deposit_log(843 ERC721Events::Transfer {844 from: *from.as_eth(),845 to: *to.as_eth(),846 token_id: token.into(),847 }848 .to_log(collection_id_to_address(collection.id)),849 );850 } else if let Some(updated_balance_to) = updated_balance_to {851 // if `from` not equals `to`. This condition is needed to avoid sending event852 // when `from` fully owns token and sends part of token pieces to itself.853 if initial_balance_from == total_supply {854 // if token was fully owned by `from` and will be only partially owned by `to`855 // and `from` after transfer856 <PalletEvm<T>>::deposit_log(857 ERC721Events::Transfer {858 from: *from.as_eth(),859 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,860 token_id: token.into(),861 }862 .to_log(collection_id_to_address(collection.id)),863 );864 } else if updated_balance_to == total_supply {865 // if token was partially owned by `from` and will be fully owned by `to` after transfer866 <PalletEvm<T>>::deposit_log(867 ERC721Events::Transfer {868 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,869 to: *to.as_eth(),870 token_id: token.into(),871 }872 .to_log(collection_id_to_address(collection.id)),873 );874 }875 }876877 Ok(())878 }879880 /// Batched operation to create multiple RFT tokens.881 ///882 /// Same as `create_item` but creates multiple tokens.883 ///884 /// - `data`: Same as 'data` in `create_item` but contains data for multiple tokens.885 pub fn create_multiple_items(886 collection: &RefungibleHandle<T>,887 sender: &T::CrossAccountId,888 data: Vec<CreateItemData<T::CrossAccountId>>,889 nesting_budget: &dyn Budget,890 ) -> DispatchResult {891 if !collection.is_owner_or_admin(sender) {892 ensure!(893 collection.permissions.mint_mode(),894 <CommonError<T>>::PublicMintingNotAllowed895 );896 collection.check_allowlist(sender)?;897898 for item in data.iter() {899 for user in item.users.keys() {900 collection.check_allowlist(user)?;901 }902 }903 }904905 for item in data.iter() {906 for (owner, _) in item.users.iter() {907 <PalletCommon<T>>::ensure_correct_receiver(owner)?;908 }909 }910911 // Total pieces per tokens912 let totals = data913 .iter()914 .map(|data| {915 Ok(data916 .users917 .iter()918 .map(|u| u.1)919 .try_fold(0u128, |acc, v| acc.checked_add(*v))920 .ok_or(ArithmeticError::Overflow)?)921 })922 .collect::<Result<Vec<_>, DispatchError>>()?;923 for total in &totals {924 ensure!(925 *total <= MAX_REFUNGIBLE_PIECES,926 <Error<T>>::WrongRefungiblePieces927 );928 }929930 let first_token_id = <TokensMinted<T>>::get(collection.id);931 let tokens_minted = first_token_id932 .checked_add(data.len() as u32)933 .ok_or(ArithmeticError::Overflow)?;934 ensure!(935 tokens_minted < collection.limits.token_limit(),936 <CommonError<T>>::CollectionTokenLimitExceeded937 );938939 let mut balances = BTreeMap::new();940 for data in &data {941 for owner in data.users.keys() {942 let balance = balances943 .entry(owner)944 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));945 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;946947 ensure!(948 *balance <= collection.limits.account_token_ownership_limit(),949 <CommonError<T>>::AccountTokenLimitExceeded,950 );951 }952 }953954 for (i, token) in data.iter().enumerate() {955 let token_id = TokenId(first_token_id + i as u32 + 1);956 for (to, _) in token.users.iter() {957 <PalletStructure<T>>::check_nesting(958 sender.clone(),959 to,960 collection.id,961 token_id,962 nesting_budget,963 )?;964 }965 }966967 // =========968969 with_transaction(|| {970 for (i, data) in data.iter().enumerate() {971 let token_id = first_token_id + i as u32 + 1;972 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);973974 for (user, amount) in data.users.iter() {975 if *amount == 0 {976 continue;977 }978 <Balance<T>>::insert((collection.id, token_id, &user), amount);979 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);980 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(981 user,982 collection.id,983 TokenId(token_id),984 );985 }986987 if let Err(e) = Self::set_token_properties(988 collection,989 sender,990 TokenId(token_id),991 data.properties.clone().into_iter(),992 true,993 nesting_budget,994 ) {995 return TransactionOutcome::Rollback(Err(e));996 }997 }998 TransactionOutcome::Commit(Ok(()))999 })?;10001001 <TokensMinted<T>>::insert(collection.id, tokens_minted);10021003 for (account, balance) in balances {1004 <AccountBalance<T>>::insert((collection.id, account), balance);1005 }10061007 for (i, token) in data.into_iter().enumerate() {1008 let token_id = first_token_id + i as u32 + 1;10091010 let receivers = token1011 .users1012 .into_iter()1013 .filter(|(_, amount)| *amount > 0)1014 .collect::<Vec<_>>();10151016 if let [(user, _)] = receivers.as_slice() {1017 // if there is exactly one receiver1018 <PalletEvm<T>>::deposit_log(1019 ERC721Events::Transfer {1020 from: H160::default(),1021 to: *user.as_eth(),1022 token_id: token_id.into(),1023 }1024 .to_log(collection_id_to_address(collection.id)),1025 );1026 } else if let [_, ..] = receivers.as_slice() {1027 // if there is more than one receiver1028 <PalletEvm<T>>::deposit_log(1029 ERC721Events::Transfer {1030 from: H160::default(),1031 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,1032 token_id: token_id.into(),1033 }1034 .to_log(collection_id_to_address(collection.id)),1035 );1036 }10371038 for (user, amount) in receivers.into_iter() {1039 <PalletEvm<T>>::deposit_log(1040 ERC20Events::Transfer {1041 from: H160::default(),1042 to: *user.as_eth(),1043 value: amount.into(),1044 }1045 .to_log(T::EvmTokenAddressMapping::token_to_address(1046 collection.id,1047 TokenId(token_id),1048 )),1049 );1050 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1051 collection.id,1052 TokenId(token_id),1053 user,1054 amount,1055 ));1056 }1057 }1058 Ok(())1059 }10601061 pub fn set_allowance_unchecked(1062 collection: &RefungibleHandle<T>,1063 sender: &T::CrossAccountId,1064 spender: &T::CrossAccountId,1065 token: TokenId,1066 amount: u128,1067 ) {1068 if amount == 0 {1069 <Allowance<T>>::remove((collection.id, token, sender, spender));1070 } else {1071 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);1072 }10731074 <PalletEvm<T>>::deposit_log(1075 ERC20Events::Approval {1076 owner: *sender.as_eth(),1077 spender: *spender.as_eth(),1078 value: amount.into(),1079 }1080 .to_log(T::EvmTokenAddressMapping::token_to_address(1081 collection.id,1082 token,1083 )),1084 );1085 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1086 collection.id,1087 token,1088 sender.clone(),1089 spender.clone(),1090 amount,1091 ))1092 }10931094 /// Set allowance for the spender to `transfer` or `burn` sender's token pieces.1095 ///1096 /// - `amount`: Amount of token pieces the spender is allowed to `transfer` or `burn.1097 pub fn set_allowance(1098 collection: &RefungibleHandle<T>,1099 sender: &T::CrossAccountId,1100 spender: &T::CrossAccountId,1101 token: TokenId,1102 amount: u128,1103 ) -> DispatchResult {1104 if collection.permissions.access() == AccessMode::AllowList {1105 collection.check_allowlist(sender)?;1106 collection.check_allowlist(spender)?;1107 }11081109 <PalletCommon<T>>::ensure_correct_receiver(spender)?;11101111 if <Balance<T>>::get((collection.id, token, sender)) < amount {1112 ensure!(1113 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),1114 <CommonError<T>>::CantApproveMoreThanOwned1115 );1116 }11171118 // =========11191120 Self::set_allowance_unchecked(collection, sender, spender, token, amount);1121 Ok(())1122 }11231124 /// Returns allowance, which should be set after transaction1125 fn check_allowed(1126 collection: &RefungibleHandle<T>,1127 spender: &T::CrossAccountId,1128 from: &T::CrossAccountId,1129 token: TokenId,1130 amount: u128,1131 nesting_budget: &dyn Budget,1132 ) -> Result<Option<u128>, DispatchError> {1133 if spender.conv_eq(from) {1134 return Ok(None);1135 }1136 if collection.permissions.access() == AccessMode::AllowList {1137 // `from`, `to` checked in [`transfer`]1138 collection.check_allowlist(spender)?;1139 }1140 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1141 // TODO: should collection owner be allowed to perform this transfer?1142 ensure!(1143 <PalletStructure<T>>::check_indirectly_owned(1144 spender.clone(),1145 source.0,1146 source.1,1147 None,1148 nesting_budget1149 )?,1150 <CommonError<T>>::ApprovedValueTooLow,1151 );1152 return Ok(None);1153 }1154 let allowance =1155 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);1156 if allowance.is_none() {1157 ensure!(1158 collection.ignores_allowance(spender),1159 <CommonError<T>>::ApprovedValueTooLow1160 );1161 }1162 Ok(allowance)1163 }11641165 /// Transfer RFT token pieces from one account to another.1166 ///1167 /// Same as the [`transfer`] but spender doesn't needs to be an owner of the token pieces.1168 /// The owner should set allowance for the spender to transfer pieces.1169 ///1170 /// [`transfer`]: struct.Pallet.html#method.transfer1171 pub fn transfer_from(1172 collection: &RefungibleHandle<T>,1173 spender: &T::CrossAccountId,1174 from: &T::CrossAccountId,1175 to: &T::CrossAccountId,1176 token: TokenId,1177 amount: u128,1178 nesting_budget: &dyn Budget,1179 ) -> DispatchResult {1180 let allowance =1181 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;11821183 // =========11841185 Self::transfer(collection, from, to, token, amount, nesting_budget)?;1186 if let Some(allowance) = allowance {1187 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1188 }1189 Ok(())1190 }11911192 /// Burn RFT token pieces from the account.1193 ///1194 /// Same as the [`burn`] but spender doesn't need to be an owner of the token pieces. The owner should1195 /// set allowance for the spender to burn pieces1196 ///1197 /// [`burn`]: struct.Pallet.html#method.burn1198 pub fn burn_from(1199 collection: &RefungibleHandle<T>,1200 spender: &T::CrossAccountId,1201 from: &T::CrossAccountId,1202 token: TokenId,1203 amount: u128,1204 nesting_budget: &dyn Budget,1205 ) -> DispatchResult {1206 let allowance =1207 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;12081209 // =========12101211 Self::burn(collection, from, token, amount)?;1212 if let Some(allowance) = allowance {1213 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1214 }1215 Ok(())1216 }12171218 /// Create RFT token.1219 ///1220 /// The sender should be the owner/admin of the collection or collection should be configured1221 /// to allow public minting.1222 ///1223 /// - `data`: Contains list of users who will become the owners of the token pieces and amount1224 /// of token pieces they will receive.1225 pub fn create_item(1226 collection: &RefungibleHandle<T>,1227 sender: &T::CrossAccountId,1228 data: CreateItemData<T::CrossAccountId>,1229 nesting_budget: &dyn Budget,1230 ) -> DispatchResult {1231 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1232 }12331234 /// Repartition RFT token.1235 ///1236 /// `repartition` will set token balance of the sender and total amount of token pieces.1237 /// Sender should own all of the token pieces. `repartition' could be done even if some1238 /// token pieces were burned before.1239 ///1240 /// - `amount`: Total amount of token pieces that the token will have after `repartition`.1241 pub fn repartition(1242 collection: &RefungibleHandle<T>,1243 owner: &T::CrossAccountId,1244 token: TokenId,1245 amount: u128,1246 ) -> DispatchResult {1247 ensure!(1248 amount <= MAX_REFUNGIBLE_PIECES,1249 <Error<T>>::WrongRefungiblePieces1250 );1251 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);1252 // Ensure user owns all pieces1253 let total_pieces = Self::total_pieces(collection.id, token).unwrap_or(u128::MAX);1254 let balance = <Balance<T>>::get((collection.id, token, owner));1255 ensure!(1256 total_pieces == balance,1257 <Error<T>>::RepartitionWhileNotOwningAllPieces1258 );12591260 <Balance<T>>::insert((collection.id, token, owner), amount);1261 <TotalSupply<T>>::insert((collection.id, token), amount);12621263 if amount > total_pieces {1264 let mint_amount = amount - total_pieces;1265 <PalletEvm<T>>::deposit_log(1266 ERC20Events::Transfer {1267 from: H160::default(),1268 to: *owner.as_eth(),1269 value: mint_amount.into(),1270 }1271 .to_log(T::EvmTokenAddressMapping::token_to_address(1272 collection.id,1273 token,1274 )),1275 );1276 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1277 collection.id,1278 token,1279 owner.clone(),1280 mint_amount,1281 ));1282 } else if total_pieces > amount {1283 let burn_amount = total_pieces - amount;1284 <PalletEvm<T>>::deposit_log(1285 ERC20Events::Transfer {1286 from: *owner.as_eth(),1287 to: H160::default(),1288 value: burn_amount.into(),1289 }1290 .to_log(T::EvmTokenAddressMapping::token_to_address(1291 collection.id,1292 token,1293 )),1294 );1295 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(1296 collection.id,1297 token,1298 owner.clone(),1299 burn_amount,1300 ));1301 }13021303 Ok(())1304 }13051306 fn token_owner(collection_id: CollectionId, token_id: TokenId) -> Option<T::CrossAccountId> {1307 let mut owner = None;1308 let mut count = 0;1309 for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {1310 count += 1;1311 if count > 1 {1312 return None;1313 }1314 owner = Some(key);1315 }1316 owner1317 }13181319 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {1320 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()1321 }13221323 pub fn set_collection_properties(1324 collection: &RefungibleHandle<T>,1325 sender: &T::CrossAccountId,1326 properties: Vec<Property>,1327 ) -> DispatchResult {1328 <PalletCommon<T>>::set_collection_properties(collection, sender, properties)1329 }13301331 pub fn delete_collection_properties(1332 collection: &RefungibleHandle<T>,1333 sender: &T::CrossAccountId,1334 property_keys: Vec<PropertyKey>,1335 ) -> DispatchResult {1336 <PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)1337 }13381339 pub fn set_token_property_permissions(1340 collection: &RefungibleHandle<T>,1341 sender: &T::CrossAccountId,1342 property_permissions: Vec<PropertyKeyPermission>,1343 ) -> DispatchResult {1344 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)1345 }13461347 pub fn set_scoped_token_property_permissions(1348 collection: &RefungibleHandle<T>,1349 sender: &T::CrossAccountId,1350 scope: PropertyScope,1351 property_permissions: Vec<PropertyKeyPermission>,1352 ) -> DispatchResult {1353 <PalletCommon<T>>::set_scoped_token_property_permissions(1354 collection,1355 sender,1356 scope,1357 property_permissions,1358 )1359 }13601361 /// Returns 10 token in no particular order.1362 ///1363 /// There is no direct way to get token holders in ascending order,1364 /// since `iter_prefix` returns values in no particular order.1365 /// Therefore, getting the 10 largest holders with a large value of holders1366 /// can lead to impact memory allocation + sorting with `n * log (n)`.1367 pub fn token_owners(1368 collection_id: CollectionId,1369 token: TokenId,1370 ) -> Option<Vec<T::CrossAccountId>> {1371 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection_id, token))1372 .map(|(owner, _amount)| owner)1373 .take(10)1374 .collect();13751376 if res.is_empty() {1377 None1378 } else {1379 Some(res)1380 }1381 }1382}primitives/data-structs/Cargo.tomldiffbeforeafterboth--- a/primitives/data-structs/Cargo.toml
+++ b/primitives/data-structs/Cargo.toml
@@ -27,6 +27,7 @@
struct-versioning = { path = "../../crates/struct-versioning" }
pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.27-fee-limit" }
rmrk-traits = { default-features = false, path = "../rmrk-traits" }
+bondrewd = { version = "0.1.14", features = ["derive"], default-features = false }
[features]
default = ["std"]
primitives/data-structs/src/bondrewd_codec.rsdiffbeforeafterboth--- /dev/null
+++ b/primitives/data-structs/src/bondrewd_codec.rs
@@ -0,0 +1,31 @@
+//! Integration between bondrewd and parity scale codec
+//! Maybe we can move it to scale-codec itself in future?
+
+#[macro_export]
+macro_rules! bondrewd_codec {
+ ($T:ty) => {
+ impl Encode for $T {
+ fn encode_to<O: codec::Output + ?Sized>(&self, dest: &mut O) {
+ dest.write(&self.into_bytes())
+ }
+ }
+ impl codec::Decode for $T {
+ fn decode<I: codec::Input + ?Sized>(from: &mut I) -> Result<Self, codec::Error> {
+ let mut bytes = [0; Self::BYTE_SIZE];
+ from.read(&mut bytes)?;
+ Ok(Self::from_bytes(bytes))
+ }
+ }
+ impl MaxEncodedLen for $T {
+ fn max_encoded_len() -> usize {
+ Self::BYTE_SIZE
+ }
+ }
+ impl TypeInfo for $T {
+ type Identity = [u8; Self::BYTE_SIZE];
+ fn type_info() -> scale_info::Type {
+ <[u8; Self::BYTE_SIZE] as TypeInfo>::type_info()
+ }
+ }
+ };
+}
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -36,6 +36,7 @@
use sp_core::U256;
use sp_runtime::{ArithmeticError, sp_std::prelude::Vec, Permill};
use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
+use bondrewd::Bitfields;
use frame_support::{BoundedVec, traits::ConstU32};
use derivative::Derivative;
use scale_info::TypeInfo;
@@ -54,6 +55,7 @@
FixedPart as RmrkFixedPart, SlotPart as RmrkSlotPart,
};
+mod bondrewd_codec;
mod bounded;
pub mod budget;
pub mod mapping;
@@ -357,6 +359,21 @@
pub type CollectionDescription = BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>;
pub type CollectionTokenPrefix = BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>;
+#[derive(Bitfields, Clone, Copy, PartialEq, Eq, Debug, Default)]
+#[bondrewd(enforce_bytes = 1)]
+pub struct CollectionFlags {
+ /// Tokens in foreign collections can be transferred, but not burnt
+ #[bondrewd(bits = "0..1")]
+ pub foreign: bool,
+ /// External collections can't be managed using `unique` api
+ #[bondrewd(bits = "7..8")]
+ pub external: bool,
+
+ #[bondrewd(reserve, bits = "1..7")]
+ pub reserved: u8,
+}
+bondrewd_codec!(CollectionFlags);
+
/// Base structure for represent collection.
///
/// Used to provide basic functionality for all types of collections.
@@ -404,9 +421,8 @@
#[version(2.., upper(Default::default()))]
pub permissions: CollectionPermissions,
- /// Marks that this collection is not "unique", and managed from external.
- #[version(2.., upper(false))]
- pub external_collection: bool,
+ #[version(2.., upper(Default::default()))]
+ pub flags: CollectionFlags,
#[version(..2)]
pub variable_on_chain_schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>,
@@ -454,6 +470,9 @@
/// Is collection read only.
pub read_only: bool,
+
+ /// Is collection is foreign.
+ pub foreign: bool,
}
/// Data used for create collection.
primitives/data-structs/src/migration.rsdiffbeforeafterboth--- a/primitives/data-structs/src/migration.rs
+++ b/primitives/data-structs/src/migration.rs
@@ -38,3 +38,26 @@
test_to_option(SponsoringRateLimit::SponsoringDisabled);
test_to_option(SponsoringRateLimit::Blocks(10));
}
+
+#[test]
+fn collection_flags_have_same_encoding_as_bool() {
+ use crate::CollectionFlags;
+ use codec::Encode;
+
+ assert_eq!(
+ true.encode(),
+ CollectionFlags {
+ external: true,
+ ..Default::default()
+ }
+ .encode()
+ );
+ assert_eq!(
+ false.encode(),
+ CollectionFlags {
+ external: false,
+ ..Default::default()
+ }
+ .encode()
+ );
+}