difftreelog
Merge pull request #944 from UniqueNetwork/fix/more-clippy-warnings
in: master
9 files changed
client/rpc/src/lib.rsdiffbeforeafterboth--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -328,7 +328,7 @@
macro_rules! pass_method {
(
$method_name:ident(
- $($(#[map(|$map_arg:ident| $map:expr)])? $name:ident: $ty:ty),* $(,)?
+ $($(#[map = $map:expr])? $name:ident: $ty:ty),* $(,)?
) -> $result:ty $(=> $mapper:expr)?,
//$runtime_name:ident $(<$($lt: tt),+>)*
$runtime_api_macro:ident
@@ -355,7 +355,7 @@
let result = $(if _api_version < $ver {
api.$changed_method_name(at, $($changed_name),*).map(|r| r.and_then($fixer))
} else)*
- { api.$method_name(at, $($((|$map_arg: $ty| $map))? ($name)),*) };
+ { api.$method_name(at, $($($map)? ($name)),*) };
Ok(result
.map_err(|e| anyhow!("unable to query: {e}"))?
@@ -413,7 +413,7 @@
pass_method!(collection_properties(
collection: CollectionId,
- #[map(|keys| string_keys_to_bytes_keys(keys))]
+ #[map = string_keys_to_bytes_keys]
keys: Option<Vec<String>>
) -> Vec<Property>, unique_api);
@@ -421,14 +421,14 @@
collection: CollectionId,
token_id: TokenId,
- #[map(|keys| string_keys_to_bytes_keys(keys))]
+ #[map = string_keys_to_bytes_keys]
keys: Option<Vec<String>>
) -> Vec<Property>, unique_api);
pass_method!(property_permissions(
collection: CollectionId,
- #[map(|keys| string_keys_to_bytes_keys(keys))]
+ #[map = string_keys_to_bytes_keys]
keys: Option<Vec<String>>
) -> Vec<PropertyKeyPermission>, unique_api);
@@ -437,7 +437,7 @@
collection: CollectionId,
token_id: TokenId,
- #[map(|keys| string_keys_to_bytes_keys(keys))]
+ #[map = string_keys_to_bytes_keys]
keys: Option<Vec<String>>,
) -> TokenData<CrossAccountId>, unique_api;
changed_in 3, token_data_before_version_3(collection, token_id, string_keys_to_bytes_keys(keys)) => |value| Ok(value.into())
node/cli/src/command.rsdiffbeforeafterboth--- a/node/cli/src/command.rs
+++ b/node/cli/src/command.rs
@@ -531,7 +531,11 @@
debug!("Parachain genesis block: {:?}", block);
info!(
"Is collating: {}",
- config.role.is_authority().then_some("yes").unwrap_or("no")
+ if config.role.is_authority() {
+ "yes"
+ } else {
+ "no"
+ }
);
start_node_using_chain_runtime! {
pallets/app-promotion/src/lib.rsdiffbeforeafterboth--- a/pallets/app-promotion/src/lib.rs
+++ b/pallets/app-promotion/src/lib.rs
@@ -30,11 +30,11 @@
//!
//!
//! ## Interface
-//! The pallet provides interfaces for funds, collection/contract operations (see [types] module).
+//! The pallet provides interfaces for funds, collection/contract operations (see [types] module).
//!
//! ### Dispatchable Functions
-//! - [`set_admin_address`][`Pallet::set_admin_address`] - sets an address as the the admin.
+//! - [`set_admin_address`][`Pallet::set_admin_address`] - sets an address as the the admin.
//! - [`stake`][`Pallet::stake`] - stakes the amount of native tokens.
//! - [`unstake`][`Pallet::unstake`] - unstakes all stakes.
//! - [`sponsor_collection`][`Pallet::sponsor_collection`] - sets the pallet to be the sponsor for the collection.
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -73,7 +73,7 @@
//!
//! - [`WithRecorder`](pallet_evm_coder_substrate::WithRecorder): Trait for EVM support
//! - [`CommonCollectionOperations`](pallet_common::CommonCollectionOperations): Functions for dealing with collections
-//! - [`CommonWeightInfo`](pallet_common::CommonWeightInfo): Functions for retrieval of transaction weight
+//! - [`CommonWeightInfo`](pallet_common::CommonWeightInfo): Functions for retrieval of transaction weight
//! - [`CommonEvmHandler`](pallet_common::erc::CommonEvmHandler): Function for handling EVM runtime calls
#![cfg_attr(not(feature = "std"), no_std)]
@@ -728,7 +728,7 @@
/// Transfer fungible tokens from one account to another.
/// Same as the [`transfer`][`Pallet::transfer`] but spender doesn't needs to be an owner of the token pieces.
/// The owner should set allowance for the spender to transfer pieces.
- /// See [`set_allowance`][`Pallet::set_allowance`] for more details.
+ /// See [`set_allowance`][`Pallet::set_allowance`] for more details.
pub fn transfer_from(
collection: &FungibleHandle<T>,
spender: &T::CrossAccountId,
@@ -778,7 +778,7 @@
Ok(())
}
- /// Creates fungible token.
+ /// Creates fungible token.
///
/// The sender should be the owner/admin of the collection or collection should be configured
/// to allow public minting.
@@ -799,7 +799,7 @@
)
}
- /// Creates fungible token.
+ /// Creates fungible token.
///
/// - `data`: Contains user who will become the owners of the tokens and amount
/// of tokens he will receive.
pallets/identity/src/lib.rsdiffbeforeafterboth--- a/pallets/identity/src/lib.rs
+++ b/pallets/identity/src/lib.rs
@@ -95,8 +95,13 @@
mod types;
pub mod weights;
-use frame_support::traits::{BalanceStatus, Currency, OnUnbalanced, ReservableCurrency};
-use sp_runtime::traits::{AppendZerosInput, Hash, Saturating, StaticLookup, Zero};
+use frame_support::{
+ traits::{BalanceStatus, Currency, OnUnbalanced, ReservableCurrency},
+};
+use sp_runtime::{
+ BoundedVec,
+ traits::{AppendZerosInput, Hash, Saturating, StaticLookup, Zero},
+};
use sp_std::prelude::*;
pub use weights::WeightInfo;
@@ -112,6 +117,18 @@
<T as frame_system::Config>::AccountId,
>>::NegativeImbalance;
type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
+type RegistrarInfoOf<T> = RegistrarInfo<BalanceOf<T>, <T as frame_system::Config>::AccountId>;
+type RegistrationOf<T> =
+ Registration<BalanceOf<T>, <T as Config>::MaxRegistrars, <T as Config>::MaxAdditionalFields>;
+type SubAccounts<T> =
+ sp_runtime::BoundedVec<<T as frame_system::Config>::AccountId, <T as Config>::MaxSubAccounts>;
+type SubAccountsByAccountId<T> = (
+ <T as frame_system::Config>::AccountId,
+ (
+ BalanceOf<T>,
+ BoundedVec<(<T as frame_system::Config>::AccountId, Data), <T as Config>::MaxSubAccounts>,
+ ),
+);
#[frame_support::pallet]
pub mod pallet {
@@ -198,13 +215,8 @@
/// TWOX-NOTE: OK ― `AccountId` is a secure hash.
#[pallet::storage]
#[pallet::getter(fn subs_of)]
- pub(super) type SubsOf<T: Config> = StorageMap<
- _,
- Twox64Concat,
- T::AccountId,
- (BalanceOf<T>, BoundedVec<T::AccountId, T::MaxSubAccounts>),
- ValueQuery,
- >;
+ pub(super) type SubsOf<T: Config> =
+ StorageMap<_, Twox64Concat, T::AccountId, (BalanceOf<T>, SubAccounts<T>), ValueQuery>;
/// The set of registrars. Not expected to get very big as can only be added through a
/// special origin (likely a council motion).
@@ -212,11 +224,8 @@
/// The index into this can be cast to `RegistrarIndex` to get a valid value.
#[pallet::storage]
#[pallet::getter(fn registrars)]
- pub(super) type Registrars<T: Config> = StorageValue<
- _,
- BoundedVec<Option<RegistrarInfo<BalanceOf<T>, T::AccountId>>, T::MaxRegistrars>,
- ValueQuery,
- >;
+ pub(super) type Registrars<T: Config> =
+ StorageValue<_, BoundedVec<Option<RegistrarInfoOf<T>>, T::MaxRegistrars>, ValueQuery>;
#[pallet::error]
pub enum Error<T> {
@@ -482,18 +491,21 @@
.all(|i| i.0 == sender);
ensure!(not_other_sub, Error::<T>::AlreadyClaimed);
- if old_deposit < new_deposit {
- T::Currency::reserve(&sender, new_deposit - old_deposit)?;
- } else if old_deposit > new_deposit {
- let err_amount = T::Currency::unreserve(&sender, old_deposit - new_deposit);
- debug_assert!(err_amount.is_zero());
+ match old_deposit.cmp(&new_deposit) {
+ core::cmp::Ordering::Less => {
+ T::Currency::reserve(&sender, new_deposit - old_deposit)?
+ }
+ core::cmp::Ordering::Equal => { /* do nothing if they're equal. */ }
+ core::cmp::Ordering::Greater => {
+ let err_amount = T::Currency::unreserve(&sender, old_deposit - new_deposit);
+ debug_assert!(err_amount.is_zero());
+ }
}
- // do nothing if they're equal.
for s in old_ids.iter() {
<SuperOf<T>>::remove(s);
}
- let mut ids = BoundedVec::<T::AccountId, T::MaxSubAccounts>::default();
+ let mut ids = <SubAccounts<T>>::default();
for (id, name) in subs {
<SuperOf<T>>::insert(&id, (sender.clone(), name));
ids.try_push(id)
@@ -1107,10 +1119,7 @@
))]
pub fn force_insert_identities(
origin: OriginFor<T>,
- identities: Vec<(
- T::AccountId,
- Registration<BalanceOf<T>, T::MaxRegistrars, T::MaxAdditionalFields>,
- )>,
+ identities: Vec<(T::AccountId, RegistrationOf<T>)>,
) -> DispatchResult {
T::ForceOrigin::ensure_origin(origin)?;
for identity in identities.clone() {
@@ -1162,13 +1171,7 @@
))]
pub fn force_set_subs(
origin: OriginFor<T>,
- subs: Vec<(
- T::AccountId,
- (
- BalanceOf<T>,
- BoundedVec<(T::AccountId, Data), T::MaxSubAccounts>,
- ),
- )>,
+ subs: Vec<SubAccountsByAccountId<T>>,
) -> DispatchResult {
T::ForceOrigin::ensure_origin(origin)?;
for identity in subs.clone() {
@@ -1178,7 +1181,7 @@
<SuperOf<T>>::remove(old_sub);
}
- let mut ids = BoundedVec::<T::AccountId, T::MaxSubAccounts>::default();
+ let mut ids = <SubAccounts<T>>::default();
for (id, name) in identity.1 .1 {
<SuperOf<T>>::insert(&id, (account.clone(), name));
ids.try_push(id)
pallets/inflation/src/lib.rsdiffbeforeafterboth--- a/pallets/inflation/src/lib.rs
+++ b/pallets/inflation/src/lib.rs
@@ -26,7 +26,7 @@
//!
//! * `start_inflation` - This method sets the inflation start date. Can be only called once.
//! Inflation start block can be backdated and will catch up. The method will create Treasury
-//! account if it does not exist and perform the first inflation deposit.
+//! account if it does not exist and perform the first inflation deposit.
// #![recursion_limit = "1024"]
#![cfg_attr(not(feature = "std"), no_std)]
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 core::ops::Deref;94use evm_coder::ToLog;95use frame_support::{ensure, storage::with_transaction, transactional};96use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};97use pallet_evm_coder_substrate::WithRecorder;98use pallet_common::{99 CommonCollectionOperations, Error as CommonError, eth::collection_id_to_address,100 Event as CommonEvent, Pallet as PalletCommon,101};102use pallet_structure::Pallet as PalletStructure;103use sp_core::{Get, H160};104use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};105use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};106use up_data_structs::{107 AccessMode, budget::Budget, CollectionId, CollectionFlags, CreateCollectionData,108 mapping::TokenAddressMapping, MAX_REFUNGIBLE_PIECES, Property, PropertyKey,109 PropertyKeyPermission, PropertyScope, PropertyValue, TokenId, TrySetProperty,110 PropertiesPermissionMap, CreateRefungibleExMultipleOwners, TokenOwnerError,111 TokenProperties as TokenPropertiesT,112};113114pub use pallet::*;115#[cfg(feature = "runtime-benchmarks")]116pub mod benchmarking;117pub mod common;118pub mod erc;119pub mod erc_token;120pub mod weights;121122pub type CreateItemData<T> =123 CreateRefungibleExMultipleOwners<<T as pallet_evm::Config>::CrossAccountId>;124pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;125126#[frame_support::pallet]127pub mod pallet {128 use super::*;129 use frame_support::{130 Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key,131 traits::StorageVersion,132 };133 use up_data_structs::{CollectionId, TokenId};134 use super::weights::WeightInfo;135136 #[pallet::error]137 pub enum Error<T> {138 /// Not Refungible item data used to mint in Refungible collection.139 NotRefungibleDataUsedToMintFungibleCollectionToken,140 /// Maximum refungibility exceeded.141 WrongRefungiblePieces,142 /// Refungible token can't be repartitioned by user who isn't owns all pieces.143 RepartitionWhileNotOwningAllPieces,144 /// Refungible token can't nest other tokens.145 RefungibleDisallowsNesting,146 /// Setting item properties is not allowed.147 SettingPropertiesNotAllowed,148 }149150 #[pallet::config]151 pub trait Config:152 frame_system::Config + pallet_common::Config + pallet_structure::Config153 {154 type WeightInfo: WeightInfo;155 }156157 const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);158159 #[pallet::pallet]160 #[pallet::storage_version(STORAGE_VERSION)]161 pub struct Pallet<T>(_);162163 /// Total amount of minted tokens in a collection.164 #[pallet::storage]165 pub type TokensMinted<T: Config> =166 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;167168 /// Amount of tokens burnt in a collection.169 #[pallet::storage]170 pub type TokensBurnt<T: Config> =171 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;172173 /// Amount of pieces a refungible token is split into.174 #[pallet::storage]175 #[pallet::getter(fn token_properties)]176 pub type TokenProperties<T: Config> = StorageNMap<177 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),178 Value = TokenPropertiesT,179 QueryKind = ValueQuery,180 >;181182 /// Total amount of pieces for token183 #[pallet::storage]184 pub type TotalSupply<T: Config> = StorageNMap<185 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),186 Value = u128,187 QueryKind = ValueQuery,188 >;189190 /// Used to enumerate tokens owned by account.191 #[pallet::storage]192 pub type Owned<T: Config> = StorageNMap<193 Key = (194 Key<Twox64Concat, CollectionId>,195 Key<Blake2_128Concat, T::CrossAccountId>,196 Key<Twox64Concat, TokenId>,197 ),198 Value = bool,199 QueryKind = ValueQuery,200 >;201202 /// Amount of tokens (not pieces) partially owned by an account within a collection.203 #[pallet::storage]204 pub type AccountBalance<T: Config> = StorageNMap<205 Key = (206 Key<Twox64Concat, CollectionId>,207 // Owner208 Key<Blake2_128Concat, T::CrossAccountId>,209 ),210 Value = u32,211 QueryKind = ValueQuery,212 >;213214 /// Amount of token pieces owned by account.215 #[pallet::storage]216 pub type Balance<T: Config> = StorageNMap<217 Key = (218 Key<Twox64Concat, CollectionId>,219 Key<Twox64Concat, TokenId>,220 // Owner221 Key<Blake2_128Concat, T::CrossAccountId>,222 ),223 Value = u128,224 QueryKind = ValueQuery,225 >;226227 /// Allowance set by a token owner for another user to perform one of certain transactions on a number of pieces of a token.228 #[pallet::storage]229 pub type Allowance<T: Config> = StorageNMap<230 Key = (231 Key<Twox64Concat, CollectionId>,232 Key<Twox64Concat, TokenId>,233 // Owner234 Key<Blake2_128, T::CrossAccountId>,235 // Spender236 Key<Blake2_128Concat, T::CrossAccountId>,237 ),238 Value = u128,239 QueryKind = ValueQuery,240 >;241242 /// Spender set by a wallet owner that could perform certain transactions on all tokens in the wallet.243 #[pallet::storage]244 pub type CollectionAllowance<T: Config> = StorageNMap<245 Key = (246 Key<Twox64Concat, CollectionId>,247 Key<Blake2_128Concat, T::CrossAccountId>, // Owner248 Key<Blake2_128Concat, T::CrossAccountId>, // Spender249 ),250 Value = bool,251 QueryKind = ValueQuery,252 >;253}254255pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);256impl<T: Config> RefungibleHandle<T> {257 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {258 Self(inner)259 }260 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {261 self.0262 }263 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {264 &mut self.0265 }266}267268impl<T: Config> Deref for RefungibleHandle<T> {269 type Target = pallet_common::CollectionHandle<T>;270271 fn deref(&self) -> &Self::Target {272 &self.0273 }274}275276impl<T: Config> WithRecorder<T> for RefungibleHandle<T> {277 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {278 self.0.recorder()279 }280 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {281 self.0.into_recorder()282 }283}284285impl<T: Config> Pallet<T> {286 /// Get number of RFT tokens in collection287 pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {288 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)289 }290291 /// Check that RFT token exists292 ///293 /// - `token`: Token ID.294 pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {295 <TotalSupply<T>>::contains_key((collection.id, token))296 }297298 pub fn set_scoped_token_property(299 collection_id: CollectionId,300 token_id: TokenId,301 scope: PropertyScope,302 property: Property,303 ) -> DispatchResult {304 TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {305 properties.try_scoped_set(scope, property.key, property.value)306 })307 .map_err(<CommonError<T>>::from)?;308309 Ok(())310 }311312 pub fn set_scoped_token_properties(313 collection_id: CollectionId,314 token_id: TokenId,315 scope: PropertyScope,316 properties: impl Iterator<Item = Property>,317 ) -> DispatchResult {318 TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {319 stored_properties.try_scoped_set_from_iter(scope, properties)320 })321 .map_err(<CommonError<T>>::from)?;322323 Ok(())324 }325}326327// unchecked calls skips any permission checks328impl<T: Config> Pallet<T> {329 /// Create RFT collection330 ///331 /// `init_collection` will take non-refundable deposit for collection creation.332 ///333 /// - `data`: Contains settings for collection limits and permissions.334 pub fn init_collection(335 owner: T::CrossAccountId,336 payer: T::CrossAccountId,337 data: CreateCollectionData<T::AccountId>,338 flags: CollectionFlags,339 ) -> Result<CollectionId, DispatchError> {340 <PalletCommon<T>>::init_collection(owner, payer, data, flags)341 }342343 /// Destroy RFT collection344 ///345 /// `destroy_collection` will throw error if collection contains any tokens.346 /// Only owner can destroy collection.347 pub fn destroy_collection(348 collection: RefungibleHandle<T>,349 sender: &T::CrossAccountId,350 ) -> DispatchResult {351 let id = collection.id;352353 if Self::collection_has_tokens(id) {354 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());355 }356357 // =========358359 PalletCommon::destroy_collection(collection.0, sender)?;360361 <TokensMinted<T>>::remove(id);362 <TokensBurnt<T>>::remove(id);363 let _ = <TotalSupply<T>>::clear_prefix((id,), u32::MAX, None);364 let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);365 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);366 let _ = <Owned<T>>::clear_prefix((id,), u32::MAX, None);367 let _ = <AccountBalance<T>>::clear_prefix((id,), u32::MAX, None);368 Ok(())369 }370371 fn collection_has_tokens(collection_id: CollectionId) -> bool {372 <TotalSupply<T>>::iter_prefix((collection_id,))373 .next()374 .is_some()375 }376377 pub fn burn_token_unchecked(378 collection: &RefungibleHandle<T>,379 owner: &T::CrossAccountId,380 token_id: TokenId,381 ) -> DispatchResult {382 let burnt = <TokensBurnt<T>>::get(collection.id)383 .checked_add(1)384 .ok_or(ArithmeticError::Overflow)?;385386 <TokensBurnt<T>>::insert(collection.id, burnt);387 <TokenProperties<T>>::remove((collection.id, token_id));388 <TotalSupply<T>>::remove((collection.id, token_id));389 let _ = <Balance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);390 let _ = <Allowance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);391 <PalletEvm<T>>::deposit_log(392 ERC721Events::Transfer {393 from: *owner.as_eth(),394 to: H160::default(),395 token_id: token_id.into(),396 }397 .to_log(collection_id_to_address(collection.id)),398 );399 Ok(())400 }401402 /// Burn RFT token pieces403 ///404 /// `burn` will decrease total amount of token pieces and amount owned by sender.405 /// `burn` can be called even if there are multiple owners of the RFT token.406 /// If sender wouldn't have any pieces left after `burn` than she will stop being407 /// one of the owners of the token. If there is no account that owns any pieces of408 /// the token than token will be burned too.409 ///410 /// - `amount`: Amount of token pieces to burn.411 /// - `token`: Token who's pieces should be burned412 /// - `collection`: Collection that contains the token413 pub fn burn(414 collection: &RefungibleHandle<T>,415 owner: &T::CrossAccountId,416 token: TokenId,417 amount: u128,418 ) -> DispatchResult {419 if <Balance<T>>::get((collection.id, token, owner)) == 0 {420 return Err(<CommonError<T>>::TokenValueTooLow.into());421 }422423 let total_supply = <TotalSupply<T>>::get((collection.id, token))424 .checked_sub(amount)425 .ok_or(<CommonError<T>>::TokenValueTooLow)?;426427 // This was probally last owner of this token?428 if total_supply == 0 {429 // Ensure user actually owns this amount430 ensure!(431 <Balance<T>>::get((collection.id, token, owner)) == amount,432 <CommonError<T>>::TokenValueTooLow433 );434 let account_balance = <AccountBalance<T>>::get((collection.id, owner))435 .checked_sub(1)436 // Should not occur437 .ok_or(ArithmeticError::Underflow)?;438439 // =========440441 <Owned<T>>::remove((collection.id, owner, token));442 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);443 <AccountBalance<T>>::insert((collection.id, owner), account_balance);444 Self::burn_token_unchecked(collection, owner, token)?;445 <PalletEvm<T>>::deposit_log(446 ERC20Events::Transfer {447 from: *owner.as_eth(),448 to: H160::default(),449 value: amount.into(),450 }451 .to_log(collection_id_to_address(collection.id)),452 );453 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(454 collection.id,455 token,456 owner.clone(),457 amount,458 ));459 return Ok(());460 }461462 let balance = <Balance<T>>::get((collection.id, token, owner))463 .checked_sub(amount)464 .ok_or(<CommonError<T>>::TokenValueTooLow)?;465 let account_balance = if balance == 0 {466 <AccountBalance<T>>::get((collection.id, owner))467 .checked_sub(1)468 // Should not occur469 .ok_or(ArithmeticError::Underflow)?470 } else {471 0472 };473474 // =========475476 if balance == 0 {477 <Owned<T>>::remove((collection.id, owner, token));478 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);479 <Balance<T>>::remove((collection.id, token, owner));480 <AccountBalance<T>>::insert((collection.id, owner), account_balance);481482 if let Ok(user) = Self::token_owner(collection.id, token) {483 <PalletEvm<T>>::deposit_log(484 ERC721Events::Transfer {485 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,486 to: *user.as_eth(),487 token_id: token.into(),488 }489 .to_log(collection_id_to_address(collection.id)),490 );491 }492 } else {493 <Balance<T>>::insert((collection.id, token, owner), balance);494 }495 <TotalSupply<T>>::insert((collection.id, token), total_supply);496497 <PalletEvm<T>>::deposit_log(498 ERC20Events::Transfer {499 from: *owner.as_eth(),500 to: H160::default(),501 value: amount.into(),502 }503 .to_log(T::EvmTokenAddressMapping::token_to_address(504 collection.id,505 token,506 )),507 );508 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(509 collection.id,510 token,511 owner.clone(),512 amount,513 ));514 Ok(())515 }516517 /// A batch operation to add, edit or remove properties for a token.518 /// It sets or removes a token's properties according to519 /// `properties_updates` contents:520 /// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`521 /// * removes a property under the <key> if the value is `None` `(<key>, None)`.522 ///523 /// - `nesting_budget`: Limit for searching parents in-depth to check ownership.524 /// - `is_token_create`: Indicates that method is called during token initialization.525 /// Allows to bypass ownership check.526 ///527 /// All affected properties should have `mutable` permission528 /// to be **deleted** or to be **set more than once**,529 /// and the sender should have permission to edit those properties.530 ///531 /// This function fires an event for each property change.532 /// In case of an error, all the changes (including the events) will be reverted533 /// since the function is transactional.534 #[transactional]535 fn modify_token_properties(536 collection: &RefungibleHandle<T>,537 sender: &T::CrossAccountId,538 token_id: TokenId,539 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,540 is_token_create: bool,541 nesting_budget: &dyn Budget,542 ) -> DispatchResult {543 let is_token_owner = || -> Result<bool, DispatchError> {544 let balance = collection.balance(sender.clone(), token_id);545 let total_pieces: u128 =546 Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);547 if balance != total_pieces {548 return Ok(false);549 }550551 let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(552 sender.clone(),553 collection.id,554 token_id,555 None,556 nesting_budget,557 )?;558559 Ok(is_bundle_owner)560 };561562 let stored_properties = <TokenProperties<T>>::get((collection.id, token_id));563564 <PalletCommon<T>>::modify_token_properties(565 collection,566 sender,567 token_id,568 properties_updates,569 is_token_create,570 stored_properties,571 is_token_owner,572 |properties| <TokenProperties<T>>::set((collection.id, token_id), properties),573 erc::ERC721TokenEvent::TokenChanged {574 token_id: token_id.into(),575 }576 .to_log(T::ContractAddress::get()),577 )578 }579580 pub fn set_token_properties(581 collection: &RefungibleHandle<T>,582 sender: &T::CrossAccountId,583 token_id: TokenId,584 properties: impl Iterator<Item = Property>,585 is_token_create: bool,586 nesting_budget: &dyn Budget,587 ) -> DispatchResult {588 Self::modify_token_properties(589 collection,590 sender,591 token_id,592 properties.map(|p| (p.key, Some(p.value))),593 is_token_create,594 nesting_budget,595 )596 }597598 pub fn set_token_property(599 collection: &RefungibleHandle<T>,600 sender: &T::CrossAccountId,601 token_id: TokenId,602 property: Property,603 nesting_budget: &dyn Budget,604 ) -> DispatchResult {605 let is_token_create = false;606607 Self::set_token_properties(608 collection,609 sender,610 token_id,611 [property].into_iter(),612 is_token_create,613 nesting_budget,614 )615 }616617 pub fn delete_token_properties(618 collection: &RefungibleHandle<T>,619 sender: &T::CrossAccountId,620 token_id: TokenId,621 property_keys: impl Iterator<Item = PropertyKey>,622 nesting_budget: &dyn Budget,623 ) -> DispatchResult {624 let is_token_create = false;625626 Self::modify_token_properties(627 collection,628 sender,629 token_id,630 property_keys.into_iter().map(|key| (key, None)),631 is_token_create,632 nesting_budget,633 )634 }635636 pub fn delete_token_property(637 collection: &RefungibleHandle<T>,638 sender: &T::CrossAccountId,639 token_id: TokenId,640 property_key: PropertyKey,641 nesting_budget: &dyn Budget,642 ) -> DispatchResult {643 Self::delete_token_properties(644 collection,645 sender,646 token_id,647 [property_key].into_iter(),648 nesting_budget,649 )650 }651652 /// Transfer RFT token pieces from one account to another.653 ///654 /// If the sender is no longer owns any pieces after the `transfer` than she stops being an owner of the token.655 ///656 /// - `from`: Owner of token pieces to transfer.657 /// - `to`: Recepient of transfered token pieces.658 /// - `amount`: Amount of token pieces to transfer.659 /// - `token`: Token whos pieces should be transfered660 /// - `collection`: Collection that contains the token661 pub fn transfer(662 collection: &RefungibleHandle<T>,663 from: &T::CrossAccountId,664 to: &T::CrossAccountId,665 token: TokenId,666 amount: u128,667 nesting_budget: &dyn Budget,668 ) -> DispatchResult {669 ensure!(670 collection.limits.transfers_enabled(),671 <CommonError<T>>::TransferNotAllowed672 );673674 if collection.permissions.access() == AccessMode::AllowList {675 collection.check_allowlist(from)?;676 collection.check_allowlist(to)?;677 }678 <PalletCommon<T>>::ensure_correct_receiver(to)?;679680 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));681682 if initial_balance_from == 0 {683 return Err(<CommonError<T>>::TokenValueTooLow.into());684 }685686 let updated_balance_from = initial_balance_from687 .checked_sub(amount)688 .ok_or(<CommonError<T>>::TokenValueTooLow)?;689 let mut create_target = false;690 let from_to_differ = from != to;691 let updated_balance_to = if from != to && amount != 0 {692 let old_balance = <Balance<T>>::get((collection.id, token, to));693 if old_balance == 0 {694 create_target = true;695 }696 Some(697 old_balance698 .checked_add(amount)699 .ok_or(ArithmeticError::Overflow)?,700 )701 } else {702 None703 };704705 let account_balance_from = if updated_balance_from == 0 {706 Some(707 <AccountBalance<T>>::get((collection.id, from))708 .checked_sub(1)709 // Should not occur710 .ok_or(ArithmeticError::Underflow)?,711 )712 } else {713 None714 };715 // Account data is created in token, AccountBalance should be increased716 // But only if from != to as we shouldn't check overflow in this case717 let account_balance_to = if create_target && from_to_differ {718 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))719 .checked_add(1)720 .ok_or(ArithmeticError::Overflow)?;721 ensure!(722 account_balance_to < collection.limits.account_token_ownership_limit(),723 <CommonError<T>>::AccountTokenLimitExceeded,724 );725726 Some(account_balance_to)727 } else {728 None729 };730731 // =========732733 if let Some(updated_balance_to) = updated_balance_to {734 // from != to && amount != 0735736 <PalletStructure<T>>::nest_if_sent_to_token(737 from.clone(),738 to,739 collection.id,740 token,741 nesting_budget,742 )?;743744 if updated_balance_from == 0 {745 <Balance<T>>::remove((collection.id, token, from));746 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);747 } else {748 <Balance<T>>::insert((collection.id, token, from), updated_balance_from);749 }750 <Balance<T>>::insert((collection.id, token, to), updated_balance_to);751 if let Some(account_balance_from) = account_balance_from {752 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);753 <Owned<T>>::remove((collection.id, from, token));754 }755 if let Some(account_balance_to) = account_balance_to {756 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);757 <Owned<T>>::insert((collection.id, to, token), true);758 }759 }760761 <PalletEvm<T>>::deposit_log(762 ERC20Events::Transfer {763 from: *from.as_eth(),764 to: *to.as_eth(),765 value: amount.into(),766 }767 .to_log(T::EvmTokenAddressMapping::token_to_address(768 collection.id,769 token,770 )),771 );772773 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(774 collection.id,775 token,776 from.clone(),777 to.clone(),778 amount,779 ));780781 let total_supply = <TotalSupply<T>>::get((collection.id, token));782783 if amount == total_supply {784 // if token was fully owned by `from` and will be fully owned by `to` after transfer785 <PalletEvm<T>>::deposit_log(786 ERC721Events::Transfer {787 from: *from.as_eth(),788 to: *to.as_eth(),789 token_id: token.into(),790 }791 .to_log(collection_id_to_address(collection.id)),792 );793 } else if let Some(updated_balance_to) = updated_balance_to {794 // if `from` not equals `to`. This condition is needed to avoid sending event795 // when `from` fully owns token and sends part of token pieces to itself.796 if initial_balance_from == total_supply {797 // if token was fully owned by `from` and will be only partially owned by `to`798 // and `from` after transfer799 <PalletEvm<T>>::deposit_log(800 ERC721Events::Transfer {801 from: *from.as_eth(),802 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,803 token_id: token.into(),804 }805 .to_log(collection_id_to_address(collection.id)),806 );807 } else if updated_balance_to == total_supply {808 // if token was partially owned by `from` and will be fully owned by `to` after transfer809 <PalletEvm<T>>::deposit_log(810 ERC721Events::Transfer {811 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,812 to: *to.as_eth(),813 token_id: token.into(),814 }815 .to_log(collection_id_to_address(collection.id)),816 );817 }818 }819820 Ok(())821 }822823 /// Batched operation to create multiple RFT tokens.824 ///825 /// Same as `create_item` but creates multiple tokens.826 ///827 /// - `data`: Same as 'data` in `create_item` but contains data for multiple tokens.828 pub fn create_multiple_items(829 collection: &RefungibleHandle<T>,830 sender: &T::CrossAccountId,831 data: Vec<CreateItemData<T>>,832 nesting_budget: &dyn Budget,833 ) -> DispatchResult {834 if !collection.is_owner_or_admin(sender) {835 ensure!(836 collection.permissions.mint_mode(),837 <CommonError<T>>::PublicMintingNotAllowed838 );839 collection.check_allowlist(sender)?;840841 for item in data.iter() {842 for user in item.users.keys() {843 collection.check_allowlist(user)?;844 }845 }846 }847848 for item in data.iter() {849 for (owner, _) in item.users.iter() {850 <PalletCommon<T>>::ensure_correct_receiver(owner)?;851 }852 }853854 // Total pieces per tokens855 let totals = data856 .iter()857 .map(|data| {858 Ok(data859 .users860 .iter()861 .map(|u| u.1)862 .try_fold(0u128, |acc, v| acc.checked_add(*v))863 .ok_or(ArithmeticError::Overflow)?)864 })865 .collect::<Result<Vec<_>, DispatchError>>()?;866 for total in &totals {867 ensure!(868 *total <= MAX_REFUNGIBLE_PIECES,869 <Error<T>>::WrongRefungiblePieces870 );871 }872873 let first_token_id = <TokensMinted<T>>::get(collection.id);874 let tokens_minted = first_token_id875 .checked_add(data.len() as u32)876 .ok_or(ArithmeticError::Overflow)?;877 ensure!(878 tokens_minted < collection.limits.token_limit(),879 <CommonError<T>>::CollectionTokenLimitExceeded880 );881882 let mut balances = BTreeMap::new();883 for data in &data {884 for owner in data.users.keys() {885 let balance = balances886 .entry(owner)887 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));888 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;889890 ensure!(891 *balance <= collection.limits.account_token_ownership_limit(),892 <CommonError<T>>::AccountTokenLimitExceeded,893 );894 }895 }896897 for (i, token) in data.iter().enumerate() {898 let token_id = TokenId(first_token_id + i as u32 + 1);899 for (to, _) in token.users.iter() {900 <PalletStructure<T>>::check_nesting(901 sender.clone(),902 to,903 collection.id,904 token_id,905 nesting_budget,906 )?;907 }908 }909910 // =========911912 with_transaction(|| {913 for (i, data) in data.iter().enumerate() {914 let token_id = first_token_id + i as u32 + 1;915 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);916917 for (user, amount) in data.users.iter() {918 if *amount == 0 {919 continue;920 }921 <Balance<T>>::insert((collection.id, token_id, &user), amount);922 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);923 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(924 user,925 collection.id,926 TokenId(token_id),927 );928 }929930 if let Err(e) = Self::set_token_properties(931 collection,932 sender,933 TokenId(token_id),934 data.properties.clone().into_iter(),935 true,936 nesting_budget,937 ) {938 return TransactionOutcome::Rollback(Err(e));939 }940 }941 TransactionOutcome::Commit(Ok(()))942 })?;943944 <TokensMinted<T>>::insert(collection.id, tokens_minted);945946 for (account, balance) in balances {947 <AccountBalance<T>>::insert((collection.id, account), balance);948 }949950 for (i, token) in data.into_iter().enumerate() {951 let token_id = first_token_id + i as u32 + 1;952953 let receivers = token954 .users955 .into_iter()956 .filter(|(_, amount)| *amount > 0)957 .collect::<Vec<_>>();958959 if let [(user, _)] = receivers.as_slice() {960 // if there is exactly one receiver961 <PalletEvm<T>>::deposit_log(962 ERC721Events::Transfer {963 from: H160::default(),964 to: *user.as_eth(),965 token_id: token_id.into(),966 }967 .to_log(collection_id_to_address(collection.id)),968 );969 } else if let [_, ..] = receivers.as_slice() {970 // if there is more than one receiver971 <PalletEvm<T>>::deposit_log(972 ERC721Events::Transfer {973 from: H160::default(),974 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,975 token_id: token_id.into(),976 }977 .to_log(collection_id_to_address(collection.id)),978 );979 }980981 for (user, amount) in receivers.into_iter() {982 <PalletEvm<T>>::deposit_log(983 ERC20Events::Transfer {984 from: H160::default(),985 to: *user.as_eth(),986 value: amount.into(),987 }988 .to_log(T::EvmTokenAddressMapping::token_to_address(989 collection.id,990 TokenId(token_id),991 )),992 );993 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(994 collection.id,995 TokenId(token_id),996 user,997 amount,998 ));999 }1000 }1001 Ok(())1002 }10031004 pub fn set_allowance_unchecked(1005 collection: &RefungibleHandle<T>,1006 sender: &T::CrossAccountId,1007 spender: &T::CrossAccountId,1008 token: TokenId,1009 amount: u128,1010 ) {1011 if amount == 0 {1012 <Allowance<T>>::remove((collection.id, token, sender, spender));1013 } else {1014 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);1015 }10161017 <PalletEvm<T>>::deposit_log(1018 ERC20Events::Approval {1019 owner: *sender.as_eth(),1020 spender: *spender.as_eth(),1021 value: amount.into(),1022 }1023 .to_log(T::EvmTokenAddressMapping::token_to_address(1024 collection.id,1025 token,1026 )),1027 );1028 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1029 collection.id,1030 token,1031 sender.clone(),1032 spender.clone(),1033 amount,1034 ))1035 }10361037 /// Set allowance for the spender to `transfer` or `burn` sender's token pieces.1038 ///1039 /// - `amount`: Amount of token pieces the spender is allowed to `transfer` or `burn.1040 pub fn set_allowance(1041 collection: &RefungibleHandle<T>,1042 sender: &T::CrossAccountId,1043 spender: &T::CrossAccountId,1044 token: TokenId,1045 amount: u128,1046 ) -> DispatchResult {1047 if collection.permissions.access() == AccessMode::AllowList {1048 collection.check_allowlist(sender)?;1049 collection.check_allowlist(spender)?;1050 }10511052 <PalletCommon<T>>::ensure_correct_receiver(spender)?;10531054 if <Balance<T>>::get((collection.id, token, sender)) < amount {1055 ensure!(1056 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),1057 <CommonError<T>>::CantApproveMoreThanOwned1058 );1059 }10601061 // =========10621063 Self::set_allowance_unchecked(collection, sender, spender, token, amount);1064 Ok(())1065 }10661067 /// Set allowance to spend from sender's eth mirror1068 ///1069 /// - `from`: Address of sender's eth mirror.1070 /// - `to`: Adress of spender.1071 /// - `amount`: Amount of token pieces the spender is allowed to `transfer` or `burn.1072 pub fn set_allowance_from(1073 collection: &RefungibleHandle<T>,1074 sender: &T::CrossAccountId,1075 from: &T::CrossAccountId,1076 to: &T::CrossAccountId,1077 token_id: TokenId,1078 amount: u128,1079 ) -> DispatchResult {1080 if collection.permissions.access() == AccessMode::AllowList {1081 collection.check_allowlist(sender)?;1082 collection.check_allowlist(from)?;1083 collection.check_allowlist(to)?;1084 }10851086 <PalletCommon<T>>::ensure_correct_receiver(to)?;10871088 ensure!(1089 sender.conv_eq(from),1090 <CommonError<T>>::AddressIsNotEthMirror1091 );10921093 if <Balance<T>>::get((collection.id, token_id, from)) < amount {1094 ensure!(1095 collection.limits.owner_can_transfer()1096 && (collection.is_owner_or_admin(sender) || collection.is_owner_or_admin(from))1097 && Self::token_exists(collection, token_id),1098 <CommonError<T>>::CantApproveMoreThanOwned1099 );1100 }11011102 // =========11031104 Self::set_allowance_unchecked(collection, from, to, token_id, amount);1105 Ok(())1106 }11071108 /// Returns allowance, which should be set after transaction1109 fn check_allowed(1110 collection: &RefungibleHandle<T>,1111 spender: &T::CrossAccountId,1112 from: &T::CrossAccountId,1113 token: TokenId,1114 amount: u128,1115 nesting_budget: &dyn Budget,1116 ) -> Result<Option<u128>, DispatchError> {1117 if spender.conv_eq(from) {1118 return Ok(None);1119 }1120 if collection.permissions.access() == AccessMode::AllowList {1121 // `from`, `to` checked in [`transfer`]1122 collection.check_allowlist(spender)?;1123 }11241125 if collection.ignores_token_restrictions(spender) {1126 return Ok(Self::compute_allowance_decrease(1127 collection, token, from, spender, amount,1128 ));1129 }11301131 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1132 // TODO: should collection owner be allowed to perform this transfer?1133 ensure!(1134 <PalletStructure<T>>::check_indirectly_owned(1135 spender.clone(),1136 source.0,1137 source.1,1138 None,1139 nesting_budget1140 )?,1141 <CommonError<T>>::ApprovedValueTooLow,1142 );1143 return Ok(None);1144 }11451146 let allowance = Self::compute_allowance_decrease(collection, token, from, spender, amount);1147 if allowance.is_some() {1148 return Ok(allowance);1149 }11501151 // Allowance (if any) would be reduced if spender is also wallet operator1152 if <CollectionAllowance<T>>::get((collection.id, from, spender)) {1153 return Ok(allowance);1154 }11551156 Err(<CommonError<T>>::ApprovedValueTooLow.into())1157 }11581159 /// Returns `Some(amount)` if the `spender` have allowance to spend this amount.1160 /// Otherwise, it returns `None`.1161 fn compute_allowance_decrease(1162 collection: &RefungibleHandle<T>,1163 token: TokenId,1164 from: &T::CrossAccountId,1165 spender: &T::CrossAccountId,1166 amount: u128,1167 ) -> Option<u128> {1168 <Allowance<T>>::get((collection.id, token, from, spender)).checked_sub(amount)1169 }11701171 /// Transfer RFT token pieces from one account to another.1172 ///1173 /// Same as the [`transfer`] but spender doesn't needs to be an owner of the token pieces.1174 /// The owner should set allowance for the spender to transfer pieces.1175 ///1176 /// [`transfer`]: struct.Pallet.html#method.transfer1177 pub fn transfer_from(1178 collection: &RefungibleHandle<T>,1179 spender: &T::CrossAccountId,1180 from: &T::CrossAccountId,1181 to: &T::CrossAccountId,1182 token: TokenId,1183 amount: u128,1184 nesting_budget: &dyn Budget,1185 ) -> DispatchResult {1186 let allowance =1187 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;11881189 // =========11901191 Self::transfer(collection, from, to, token, amount, nesting_budget)?;1192 if let Some(allowance) = allowance {1193 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1194 }1195 Ok(())1196 }11971198 /// Burn RFT token pieces from the account.1199 ///1200 /// Same as the [`burn`] but spender doesn't need to be an owner of the token pieces. The owner should1201 /// set allowance for the spender to burn pieces1202 ///1203 /// [`burn`]: struct.Pallet.html#method.burn1204 pub fn burn_from(1205 collection: &RefungibleHandle<T>,1206 spender: &T::CrossAccountId,1207 from: &T::CrossAccountId,1208 token: TokenId,1209 amount: u128,1210 nesting_budget: &dyn Budget,1211 ) -> DispatchResult {1212 let allowance =1213 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;12141215 // =========12161217 Self::burn(collection, from, token, amount)?;1218 if let Some(allowance) = allowance {1219 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1220 }1221 Ok(())1222 }12231224 /// Create RFT token.1225 ///1226 /// The sender should be the owner/admin of the collection or collection should be configured1227 /// to allow public minting.1228 ///1229 /// - `data`: Contains list of users who will become the owners of the token pieces and amount1230 /// of token pieces they will receive.1231 pub fn create_item(1232 collection: &RefungibleHandle<T>,1233 sender: &T::CrossAccountId,1234 data: CreateItemData<T>,1235 nesting_budget: &dyn Budget,1236 ) -> DispatchResult {1237 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1238 }12391240 /// Repartition RFT token.1241 ///1242 /// `repartition` will set token balance of the sender and total amount of token pieces.1243 /// Sender should own all of the token pieces. `repartition' could be done even if some1244 /// token pieces were burned before.1245 ///1246 /// - `amount`: Total amount of token pieces that the token will have after `repartition`.1247 pub fn repartition(1248 collection: &RefungibleHandle<T>,1249 owner: &T::CrossAccountId,1250 token: TokenId,1251 amount: u128,1252 ) -> DispatchResult {1253 ensure!(1254 amount <= MAX_REFUNGIBLE_PIECES,1255 <Error<T>>::WrongRefungiblePieces1256 );1257 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);1258 // Ensure user owns all pieces1259 let total_pieces = Self::total_pieces(collection.id, token).unwrap_or(u128::MAX);1260 let balance = <Balance<T>>::get((collection.id, token, owner));1261 ensure!(1262 total_pieces == balance,1263 <Error<T>>::RepartitionWhileNotOwningAllPieces1264 );12651266 <Balance<T>>::insert((collection.id, token, owner), amount);1267 <TotalSupply<T>>::insert((collection.id, token), amount);12681269 if amount > total_pieces {1270 let mint_amount = amount - total_pieces;1271 <PalletEvm<T>>::deposit_log(1272 ERC20Events::Transfer {1273 from: H160::default(),1274 to: *owner.as_eth(),1275 value: mint_amount.into(),1276 }1277 .to_log(T::EvmTokenAddressMapping::token_to_address(1278 collection.id,1279 token,1280 )),1281 );1282 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1283 collection.id,1284 token,1285 owner.clone(),1286 mint_amount,1287 ));1288 } else if total_pieces > amount {1289 let burn_amount = total_pieces - amount;1290 <PalletEvm<T>>::deposit_log(1291 ERC20Events::Transfer {1292 from: *owner.as_eth(),1293 to: H160::default(),1294 value: burn_amount.into(),1295 }1296 .to_log(T::EvmTokenAddressMapping::token_to_address(1297 collection.id,1298 token,1299 )),1300 );1301 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(1302 collection.id,1303 token,1304 owner.clone(),1305 burn_amount,1306 ));1307 }13081309 Ok(())1310 }13111312 fn token_owner(1313 collection_id: CollectionId,1314 token_id: TokenId,1315 ) -> Result<T::CrossAccountId, TokenOwnerError> {1316 let mut owner = None;1317 let mut count = 0;1318 for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {1319 count += 1;1320 if count > 1 {1321 return Err(TokenOwnerError::MultipleOwners);1322 }1323 owner = Some(key);1324 }1325 owner.ok_or(TokenOwnerError::NotFound)1326 }13271328 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {1329 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()1330 }13311332 pub fn set_collection_properties(1333 collection: &RefungibleHandle<T>,1334 sender: &T::CrossAccountId,1335 properties: Vec<Property>,1336 ) -> DispatchResult {1337 <PalletCommon<T>>::set_collection_properties(collection, sender, properties.into_iter())1338 }13391340 pub fn delete_collection_properties(1341 collection: &RefungibleHandle<T>,1342 sender: &T::CrossAccountId,1343 property_keys: Vec<PropertyKey>,1344 ) -> DispatchResult {1345 <PalletCommon<T>>::delete_collection_properties(1346 collection,1347 sender,1348 property_keys.into_iter(),1349 )1350 }13511352 pub fn set_token_property_permissions(1353 collection: &RefungibleHandle<T>,1354 sender: &T::CrossAccountId,1355 property_permissions: Vec<PropertyKeyPermission>,1356 ) -> DispatchResult {1357 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)1358 }13591360 pub fn token_property_permission(collection_id: CollectionId) -> PropertiesPermissionMap {1361 <PalletCommon<T>>::property_permissions(collection_id)1362 }13631364 pub fn set_scoped_token_property_permissions(1365 collection: &RefungibleHandle<T>,1366 sender: &T::CrossAccountId,1367 scope: PropertyScope,1368 property_permissions: Vec<PropertyKeyPermission>,1369 ) -> DispatchResult {1370 <PalletCommon<T>>::set_scoped_token_property_permissions(1371 collection,1372 sender,1373 scope,1374 property_permissions,1375 )1376 }13771378 /// Returns 10 token in no particular order.1379 ///1380 /// There is no direct way to get token holders in ascending order,1381 /// since `iter_prefix` returns values in no particular order.1382 /// Therefore, getting the 10 largest holders with a large value of holders1383 /// can lead to impact memory allocation + sorting with `n * log (n)`.1384 pub fn token_owners(1385 collection_id: CollectionId,1386 token: TokenId,1387 ) -> Option<Vec<T::CrossAccountId>> {1388 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection_id, token))1389 .map(|(owner, _amount)| owner)1390 .take(10)1391 .collect();13921393 if res.is_empty() {1394 None1395 } else {1396 Some(res)1397 }1398 }13991400 /// Sets or unsets the approval of a given operator.1401 ///1402 /// The `operator` is allowed to transfer all token pieces of the `owner` on their behalf.1403 /// - `owner`: Token owner1404 /// - `operator`: Operator1405 /// - `approve`: Should operator status be granted or revoked?1406 pub fn set_allowance_for_all(1407 collection: &RefungibleHandle<T>,1408 owner: &T::CrossAccountId,1409 spender: &T::CrossAccountId,1410 approve: bool,1411 ) -> DispatchResult {1412 <PalletCommon<T>>::set_allowance_for_all(1413 collection,1414 owner,1415 spender,1416 approve,1417 || <CollectionAllowance<T>>::insert((collection.id, owner, spender), approve),1418 ERC721Events::ApprovalForAll {1419 owner: *owner.as_eth(),1420 operator: *spender.as_eth(),1421 approved: approve,1422 }1423 .to_log(collection_id_to_address(collection.id)),1424 )1425 }14261427 /// Tells whether the given `owner` approves the `operator`.1428 pub fn allowance_for_all(1429 collection: &RefungibleHandle<T>,1430 owner: &T::CrossAccountId,1431 spender: &T::CrossAccountId,1432 ) -> bool {1433 <CollectionAllowance<T>>::get((collection.id, owner, spender))1434 }14351436 pub fn repair_item(collection: &RefungibleHandle<T>, token: TokenId) -> DispatchResult {1437 <TokenProperties<T>>::mutate((collection.id, token), |properties| {1438 properties.recompute_consumed_space();1439 });14401441 Ok(())1442 }1443}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 core::{ops::Deref, cmp::Ordering};94use evm_coder::ToLog;95use frame_support::{ensure, storage::with_transaction, transactional};96use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};97use pallet_evm_coder_substrate::WithRecorder;98use pallet_common::{99 CommonCollectionOperations, Error as CommonError, eth::collection_id_to_address,100 Event as CommonEvent, Pallet as PalletCommon,101};102use pallet_structure::Pallet as PalletStructure;103use sp_core::{Get, H160};104use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};105use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};106use up_data_structs::{107 AccessMode, budget::Budget, CollectionId, CollectionFlags, CreateCollectionData,108 mapping::TokenAddressMapping, MAX_REFUNGIBLE_PIECES, Property, PropertyKey,109 PropertyKeyPermission, PropertyScope, PropertyValue, TokenId, TrySetProperty,110 PropertiesPermissionMap, CreateRefungibleExMultipleOwners, TokenOwnerError,111 TokenProperties as TokenPropertiesT,112};113114pub use pallet::*;115#[cfg(feature = "runtime-benchmarks")]116pub mod benchmarking;117pub mod common;118pub mod erc;119pub mod erc_token;120pub mod weights;121122pub type CreateItemData<T> =123 CreateRefungibleExMultipleOwners<<T as pallet_evm::Config>::CrossAccountId>;124pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;125126#[frame_support::pallet]127pub mod pallet {128 use super::*;129 use frame_support::{130 Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key,131 traits::StorageVersion,132 };133 use up_data_structs::{CollectionId, TokenId};134 use super::weights::WeightInfo;135136 #[pallet::error]137 pub enum Error<T> {138 /// Not Refungible item data used to mint in Refungible collection.139 NotRefungibleDataUsedToMintFungibleCollectionToken,140 /// Maximum refungibility exceeded.141 WrongRefungiblePieces,142 /// Refungible token can't be repartitioned by user who isn't owns all pieces.143 RepartitionWhileNotOwningAllPieces,144 /// Refungible token can't nest other tokens.145 RefungibleDisallowsNesting,146 /// Setting item properties is not allowed.147 SettingPropertiesNotAllowed,148 }149150 #[pallet::config]151 pub trait Config:152 frame_system::Config + pallet_common::Config + pallet_structure::Config153 {154 type WeightInfo: WeightInfo;155 }156157 const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);158159 #[pallet::pallet]160 #[pallet::storage_version(STORAGE_VERSION)]161 pub struct Pallet<T>(_);162163 /// Total amount of minted tokens in a collection.164 #[pallet::storage]165 pub type TokensMinted<T: Config> =166 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;167168 /// Amount of tokens burnt in a collection.169 #[pallet::storage]170 pub type TokensBurnt<T: Config> =171 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;172173 /// Amount of pieces a refungible token is split into.174 #[pallet::storage]175 #[pallet::getter(fn token_properties)]176 pub type TokenProperties<T: Config> = StorageNMap<177 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),178 Value = TokenPropertiesT,179 QueryKind = ValueQuery,180 >;181182 /// Total amount of pieces for token183 #[pallet::storage]184 pub type TotalSupply<T: Config> = StorageNMap<185 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),186 Value = u128,187 QueryKind = ValueQuery,188 >;189190 /// Used to enumerate tokens owned by account.191 #[pallet::storage]192 pub type Owned<T: Config> = StorageNMap<193 Key = (194 Key<Twox64Concat, CollectionId>,195 Key<Blake2_128Concat, T::CrossAccountId>,196 Key<Twox64Concat, TokenId>,197 ),198 Value = bool,199 QueryKind = ValueQuery,200 >;201202 /// Amount of tokens (not pieces) partially owned by an account within a collection.203 #[pallet::storage]204 pub type AccountBalance<T: Config> = StorageNMap<205 Key = (206 Key<Twox64Concat, CollectionId>,207 // Owner208 Key<Blake2_128Concat, T::CrossAccountId>,209 ),210 Value = u32,211 QueryKind = ValueQuery,212 >;213214 /// Amount of token pieces owned by account.215 #[pallet::storage]216 pub type Balance<T: Config> = StorageNMap<217 Key = (218 Key<Twox64Concat, CollectionId>,219 Key<Twox64Concat, TokenId>,220 // Owner221 Key<Blake2_128Concat, T::CrossAccountId>,222 ),223 Value = u128,224 QueryKind = ValueQuery,225 >;226227 /// Allowance set by a token owner for another user to perform one of certain transactions on a number of pieces of a token.228 #[pallet::storage]229 pub type Allowance<T: Config> = StorageNMap<230 Key = (231 Key<Twox64Concat, CollectionId>,232 Key<Twox64Concat, TokenId>,233 // Owner234 Key<Blake2_128, T::CrossAccountId>,235 // Spender236 Key<Blake2_128Concat, T::CrossAccountId>,237 ),238 Value = u128,239 QueryKind = ValueQuery,240 >;241242 /// Spender set by a wallet owner that could perform certain transactions on all tokens in the wallet.243 #[pallet::storage]244 pub type CollectionAllowance<T: Config> = StorageNMap<245 Key = (246 Key<Twox64Concat, CollectionId>,247 Key<Blake2_128Concat, T::CrossAccountId>, // Owner248 Key<Blake2_128Concat, T::CrossAccountId>, // Spender249 ),250 Value = bool,251 QueryKind = ValueQuery,252 >;253}254255pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);256impl<T: Config> RefungibleHandle<T> {257 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {258 Self(inner)259 }260 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {261 self.0262 }263 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {264 &mut self.0265 }266}267268impl<T: Config> Deref for RefungibleHandle<T> {269 type Target = pallet_common::CollectionHandle<T>;270271 fn deref(&self) -> &Self::Target {272 &self.0273 }274}275276impl<T: Config> WithRecorder<T> for RefungibleHandle<T> {277 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {278 self.0.recorder()279 }280 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {281 self.0.into_recorder()282 }283}284285impl<T: Config> Pallet<T> {286 /// Get number of RFT tokens in collection287 pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {288 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)289 }290291 /// Check that RFT token exists292 ///293 /// - `token`: Token ID.294 pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {295 <TotalSupply<T>>::contains_key((collection.id, token))296 }297298 pub fn set_scoped_token_property(299 collection_id: CollectionId,300 token_id: TokenId,301 scope: PropertyScope,302 property: Property,303 ) -> DispatchResult {304 TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {305 properties.try_scoped_set(scope, property.key, property.value)306 })307 .map_err(<CommonError<T>>::from)?;308309 Ok(())310 }311312 pub fn set_scoped_token_properties(313 collection_id: CollectionId,314 token_id: TokenId,315 scope: PropertyScope,316 properties: impl Iterator<Item = Property>,317 ) -> DispatchResult {318 TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {319 stored_properties.try_scoped_set_from_iter(scope, properties)320 })321 .map_err(<CommonError<T>>::from)?;322323 Ok(())324 }325}326327// unchecked calls skips any permission checks328impl<T: Config> Pallet<T> {329 /// Create RFT collection330 ///331 /// `init_collection` will take non-refundable deposit for collection creation.332 ///333 /// - `data`: Contains settings for collection limits and permissions.334 pub fn init_collection(335 owner: T::CrossAccountId,336 payer: T::CrossAccountId,337 data: CreateCollectionData<T::AccountId>,338 flags: CollectionFlags,339 ) -> Result<CollectionId, DispatchError> {340 <PalletCommon<T>>::init_collection(owner, payer, data, flags)341 }342343 /// Destroy RFT collection344 ///345 /// `destroy_collection` will throw error if collection contains any tokens.346 /// Only owner can destroy collection.347 pub fn destroy_collection(348 collection: RefungibleHandle<T>,349 sender: &T::CrossAccountId,350 ) -> DispatchResult {351 let id = collection.id;352353 if Self::collection_has_tokens(id) {354 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());355 }356357 // =========358359 PalletCommon::destroy_collection(collection.0, sender)?;360361 <TokensMinted<T>>::remove(id);362 <TokensBurnt<T>>::remove(id);363 let _ = <TotalSupply<T>>::clear_prefix((id,), u32::MAX, None);364 let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);365 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);366 let _ = <Owned<T>>::clear_prefix((id,), u32::MAX, None);367 let _ = <AccountBalance<T>>::clear_prefix((id,), u32::MAX, None);368 Ok(())369 }370371 fn collection_has_tokens(collection_id: CollectionId) -> bool {372 <TotalSupply<T>>::iter_prefix((collection_id,))373 .next()374 .is_some()375 }376377 pub fn burn_token_unchecked(378 collection: &RefungibleHandle<T>,379 owner: &T::CrossAccountId,380 token_id: TokenId,381 ) -> DispatchResult {382 let burnt = <TokensBurnt<T>>::get(collection.id)383 .checked_add(1)384 .ok_or(ArithmeticError::Overflow)?;385386 <TokensBurnt<T>>::insert(collection.id, burnt);387 <TokenProperties<T>>::remove((collection.id, token_id));388 <TotalSupply<T>>::remove((collection.id, token_id));389 let _ = <Balance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);390 let _ = <Allowance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);391 <PalletEvm<T>>::deposit_log(392 ERC721Events::Transfer {393 from: *owner.as_eth(),394 to: H160::default(),395 token_id: token_id.into(),396 }397 .to_log(collection_id_to_address(collection.id)),398 );399 Ok(())400 }401402 /// Burn RFT token pieces403 ///404 /// `burn` will decrease total amount of token pieces and amount owned by sender.405 /// `burn` can be called even if there are multiple owners of the RFT token.406 /// If sender wouldn't have any pieces left after `burn` than she will stop being407 /// one of the owners of the token. If there is no account that owns any pieces of408 /// the token than token will be burned too.409 ///410 /// - `amount`: Amount of token pieces to burn.411 /// - `token`: Token who's pieces should be burned412 /// - `collection`: Collection that contains the token413 pub fn burn(414 collection: &RefungibleHandle<T>,415 owner: &T::CrossAccountId,416 token: TokenId,417 amount: u128,418 ) -> DispatchResult {419 if <Balance<T>>::get((collection.id, token, owner)) == 0 {420 return Err(<CommonError<T>>::TokenValueTooLow.into());421 }422423 let total_supply = <TotalSupply<T>>::get((collection.id, token))424 .checked_sub(amount)425 .ok_or(<CommonError<T>>::TokenValueTooLow)?;426427 // This was probally last owner of this token?428 if total_supply == 0 {429 // Ensure user actually owns this amount430 ensure!(431 <Balance<T>>::get((collection.id, token, owner)) == amount,432 <CommonError<T>>::TokenValueTooLow433 );434 let account_balance = <AccountBalance<T>>::get((collection.id, owner))435 .checked_sub(1)436 // Should not occur437 .ok_or(ArithmeticError::Underflow)?;438439 // =========440441 <Owned<T>>::remove((collection.id, owner, token));442 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);443 <AccountBalance<T>>::insert((collection.id, owner), account_balance);444 Self::burn_token_unchecked(collection, owner, token)?;445 <PalletEvm<T>>::deposit_log(446 ERC20Events::Transfer {447 from: *owner.as_eth(),448 to: H160::default(),449 value: amount.into(),450 }451 .to_log(collection_id_to_address(collection.id)),452 );453 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(454 collection.id,455 token,456 owner.clone(),457 amount,458 ));459 return Ok(());460 }461462 let balance = <Balance<T>>::get((collection.id, token, owner))463 .checked_sub(amount)464 .ok_or(<CommonError<T>>::TokenValueTooLow)?;465 let account_balance = if balance == 0 {466 <AccountBalance<T>>::get((collection.id, owner))467 .checked_sub(1)468 // Should not occur469 .ok_or(ArithmeticError::Underflow)?470 } else {471 0472 };473474 // =========475476 if balance == 0 {477 <Owned<T>>::remove((collection.id, owner, token));478 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);479 <Balance<T>>::remove((collection.id, token, owner));480 <AccountBalance<T>>::insert((collection.id, owner), account_balance);481482 if let Ok(user) = Self::token_owner(collection.id, token) {483 <PalletEvm<T>>::deposit_log(484 ERC721Events::Transfer {485 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,486 to: *user.as_eth(),487 token_id: token.into(),488 }489 .to_log(collection_id_to_address(collection.id)),490 );491 }492 } else {493 <Balance<T>>::insert((collection.id, token, owner), balance);494 }495 <TotalSupply<T>>::insert((collection.id, token), total_supply);496497 <PalletEvm<T>>::deposit_log(498 ERC20Events::Transfer {499 from: *owner.as_eth(),500 to: H160::default(),501 value: amount.into(),502 }503 .to_log(T::EvmTokenAddressMapping::token_to_address(504 collection.id,505 token,506 )),507 );508 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(509 collection.id,510 token,511 owner.clone(),512 amount,513 ));514 Ok(())515 }516517 /// A batch operation to add, edit or remove properties for a token.518 /// It sets or removes a token's properties according to519 /// `properties_updates` contents:520 /// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`521 /// * removes a property under the <key> if the value is `None` `(<key>, None)`.522 ///523 /// - `nesting_budget`: Limit for searching parents in-depth to check ownership.524 /// - `is_token_create`: Indicates that method is called during token initialization.525 /// Allows to bypass ownership check.526 ///527 /// All affected properties should have `mutable` permission528 /// to be **deleted** or to be **set more than once**,529 /// and the sender should have permission to edit those properties.530 ///531 /// This function fires an event for each property change.532 /// In case of an error, all the changes (including the events) will be reverted533 /// since the function is transactional.534 #[transactional]535 fn modify_token_properties(536 collection: &RefungibleHandle<T>,537 sender: &T::CrossAccountId,538 token_id: TokenId,539 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,540 is_token_create: bool,541 nesting_budget: &dyn Budget,542 ) -> DispatchResult {543 let is_token_owner = || -> Result<bool, DispatchError> {544 let balance = collection.balance(sender.clone(), token_id);545 let total_pieces: u128 =546 Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);547 if balance != total_pieces {548 return Ok(false);549 }550551 let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(552 sender.clone(),553 collection.id,554 token_id,555 None,556 nesting_budget,557 )?;558559 Ok(is_bundle_owner)560 };561562 let stored_properties = <TokenProperties<T>>::get((collection.id, token_id));563564 <PalletCommon<T>>::modify_token_properties(565 collection,566 sender,567 token_id,568 properties_updates,569 is_token_create,570 stored_properties,571 is_token_owner,572 |properties| <TokenProperties<T>>::set((collection.id, token_id), properties),573 erc::ERC721TokenEvent::TokenChanged {574 token_id: token_id.into(),575 }576 .to_log(T::ContractAddress::get()),577 )578 }579580 pub fn set_token_properties(581 collection: &RefungibleHandle<T>,582 sender: &T::CrossAccountId,583 token_id: TokenId,584 properties: impl Iterator<Item = Property>,585 is_token_create: bool,586 nesting_budget: &dyn Budget,587 ) -> DispatchResult {588 Self::modify_token_properties(589 collection,590 sender,591 token_id,592 properties.map(|p| (p.key, Some(p.value))),593 is_token_create,594 nesting_budget,595 )596 }597598 pub fn set_token_property(599 collection: &RefungibleHandle<T>,600 sender: &T::CrossAccountId,601 token_id: TokenId,602 property: Property,603 nesting_budget: &dyn Budget,604 ) -> DispatchResult {605 let is_token_create = false;606607 Self::set_token_properties(608 collection,609 sender,610 token_id,611 [property].into_iter(),612 is_token_create,613 nesting_budget,614 )615 }616617 pub fn delete_token_properties(618 collection: &RefungibleHandle<T>,619 sender: &T::CrossAccountId,620 token_id: TokenId,621 property_keys: impl Iterator<Item = PropertyKey>,622 nesting_budget: &dyn Budget,623 ) -> DispatchResult {624 let is_token_create = false;625626 Self::modify_token_properties(627 collection,628 sender,629 token_id,630 property_keys.into_iter().map(|key| (key, None)),631 is_token_create,632 nesting_budget,633 )634 }635636 pub fn delete_token_property(637 collection: &RefungibleHandle<T>,638 sender: &T::CrossAccountId,639 token_id: TokenId,640 property_key: PropertyKey,641 nesting_budget: &dyn Budget,642 ) -> DispatchResult {643 Self::delete_token_properties(644 collection,645 sender,646 token_id,647 [property_key].into_iter(),648 nesting_budget,649 )650 }651652 /// Transfer RFT token pieces from one account to another.653 ///654 /// If the sender is no longer owns any pieces after the `transfer` than she stops being an owner of the token.655 ///656 /// - `from`: Owner of token pieces to transfer.657 /// - `to`: Recepient of transfered token pieces.658 /// - `amount`: Amount of token pieces to transfer.659 /// - `token`: Token whos pieces should be transfered660 /// - `collection`: Collection that contains the token661 pub fn transfer(662 collection: &RefungibleHandle<T>,663 from: &T::CrossAccountId,664 to: &T::CrossAccountId,665 token: TokenId,666 amount: u128,667 nesting_budget: &dyn Budget,668 ) -> DispatchResult {669 ensure!(670 collection.limits.transfers_enabled(),671 <CommonError<T>>::TransferNotAllowed672 );673674 if collection.permissions.access() == AccessMode::AllowList {675 collection.check_allowlist(from)?;676 collection.check_allowlist(to)?;677 }678 <PalletCommon<T>>::ensure_correct_receiver(to)?;679680 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));681682 if initial_balance_from == 0 {683 return Err(<CommonError<T>>::TokenValueTooLow.into());684 }685686 let updated_balance_from = initial_balance_from687 .checked_sub(amount)688 .ok_or(<CommonError<T>>::TokenValueTooLow)?;689 let mut create_target = false;690 let from_to_differ = from != to;691 let updated_balance_to = if from != to && amount != 0 {692 let old_balance = <Balance<T>>::get((collection.id, token, to));693 if old_balance == 0 {694 create_target = true;695 }696 Some(697 old_balance698 .checked_add(amount)699 .ok_or(ArithmeticError::Overflow)?,700 )701 } else {702 None703 };704705 let account_balance_from = if updated_balance_from == 0 {706 Some(707 <AccountBalance<T>>::get((collection.id, from))708 .checked_sub(1)709 // Should not occur710 .ok_or(ArithmeticError::Underflow)?,711 )712 } else {713 None714 };715 // Account data is created in token, AccountBalance should be increased716 // But only if from != to as we shouldn't check overflow in this case717 let account_balance_to = if create_target && from_to_differ {718 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))719 .checked_add(1)720 .ok_or(ArithmeticError::Overflow)?;721 ensure!(722 account_balance_to < collection.limits.account_token_ownership_limit(),723 <CommonError<T>>::AccountTokenLimitExceeded,724 );725726 Some(account_balance_to)727 } else {728 None729 };730731 // =========732733 if let Some(updated_balance_to) = updated_balance_to {734 // from != to && amount != 0735736 <PalletStructure<T>>::nest_if_sent_to_token(737 from.clone(),738 to,739 collection.id,740 token,741 nesting_budget,742 )?;743744 if updated_balance_from == 0 {745 <Balance<T>>::remove((collection.id, token, from));746 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);747 } else {748 <Balance<T>>::insert((collection.id, token, from), updated_balance_from);749 }750 <Balance<T>>::insert((collection.id, token, to), updated_balance_to);751 if let Some(account_balance_from) = account_balance_from {752 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);753 <Owned<T>>::remove((collection.id, from, token));754 }755 if let Some(account_balance_to) = account_balance_to {756 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);757 <Owned<T>>::insert((collection.id, to, token), true);758 }759 }760761 <PalletEvm<T>>::deposit_log(762 ERC20Events::Transfer {763 from: *from.as_eth(),764 to: *to.as_eth(),765 value: amount.into(),766 }767 .to_log(T::EvmTokenAddressMapping::token_to_address(768 collection.id,769 token,770 )),771 );772773 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(774 collection.id,775 token,776 from.clone(),777 to.clone(),778 amount,779 ));780781 let total_supply = <TotalSupply<T>>::get((collection.id, token));782783 if amount == total_supply {784 // if token was fully owned by `from` and will be fully owned by `to` after transfer785 <PalletEvm<T>>::deposit_log(786 ERC721Events::Transfer {787 from: *from.as_eth(),788 to: *to.as_eth(),789 token_id: token.into(),790 }791 .to_log(collection_id_to_address(collection.id)),792 );793 } else if let Some(updated_balance_to) = updated_balance_to {794 // if `from` not equals `to`. This condition is needed to avoid sending event795 // when `from` fully owns token and sends part of token pieces to itself.796 if initial_balance_from == total_supply {797 // if token was fully owned by `from` and will be only partially owned by `to`798 // and `from` after transfer799 <PalletEvm<T>>::deposit_log(800 ERC721Events::Transfer {801 from: *from.as_eth(),802 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,803 token_id: token.into(),804 }805 .to_log(collection_id_to_address(collection.id)),806 );807 } else if updated_balance_to == total_supply {808 // if token was partially owned by `from` and will be fully owned by `to` after transfer809 <PalletEvm<T>>::deposit_log(810 ERC721Events::Transfer {811 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,812 to: *to.as_eth(),813 token_id: token.into(),814 }815 .to_log(collection_id_to_address(collection.id)),816 );817 }818 }819820 Ok(())821 }822823 /// Batched operation to create multiple RFT tokens.824 ///825 /// Same as `create_item` but creates multiple tokens.826 ///827 /// - `data`: Same as 'data` in `create_item` but contains data for multiple tokens.828 pub fn create_multiple_items(829 collection: &RefungibleHandle<T>,830 sender: &T::CrossAccountId,831 data: Vec<CreateItemData<T>>,832 nesting_budget: &dyn Budget,833 ) -> DispatchResult {834 if !collection.is_owner_or_admin(sender) {835 ensure!(836 collection.permissions.mint_mode(),837 <CommonError<T>>::PublicMintingNotAllowed838 );839 collection.check_allowlist(sender)?;840841 for item in data.iter() {842 for user in item.users.keys() {843 collection.check_allowlist(user)?;844 }845 }846 }847848 for item in data.iter() {849 for (owner, _) in item.users.iter() {850 <PalletCommon<T>>::ensure_correct_receiver(owner)?;851 }852 }853854 // Total pieces per tokens855 let totals = data856 .iter()857 .map(|data| {858 Ok(data859 .users860 .iter()861 .map(|u| u.1)862 .try_fold(0u128, |acc, v| acc.checked_add(*v))863 .ok_or(ArithmeticError::Overflow)?)864 })865 .collect::<Result<Vec<_>, DispatchError>>()?;866 for total in &totals {867 ensure!(868 *total <= MAX_REFUNGIBLE_PIECES,869 <Error<T>>::WrongRefungiblePieces870 );871 }872873 let first_token_id = <TokensMinted<T>>::get(collection.id);874 let tokens_minted = first_token_id875 .checked_add(data.len() as u32)876 .ok_or(ArithmeticError::Overflow)?;877 ensure!(878 tokens_minted < collection.limits.token_limit(),879 <CommonError<T>>::CollectionTokenLimitExceeded880 );881882 let mut balances = BTreeMap::new();883 for data in &data {884 for owner in data.users.keys() {885 let balance = balances886 .entry(owner)887 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));888 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;889890 ensure!(891 *balance <= collection.limits.account_token_ownership_limit(),892 <CommonError<T>>::AccountTokenLimitExceeded,893 );894 }895 }896897 for (i, token) in data.iter().enumerate() {898 let token_id = TokenId(first_token_id + i as u32 + 1);899 for (to, _) in token.users.iter() {900 <PalletStructure<T>>::check_nesting(901 sender.clone(),902 to,903 collection.id,904 token_id,905 nesting_budget,906 )?;907 }908 }909910 // =========911912 with_transaction(|| {913 for (i, data) in data.iter().enumerate() {914 let token_id = first_token_id + i as u32 + 1;915 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);916917 for (user, amount) in data.users.iter() {918 if *amount == 0 {919 continue;920 }921 <Balance<T>>::insert((collection.id, token_id, &user), amount);922 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);923 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(924 user,925 collection.id,926 TokenId(token_id),927 );928 }929930 if let Err(e) = Self::set_token_properties(931 collection,932 sender,933 TokenId(token_id),934 data.properties.clone().into_iter(),935 true,936 nesting_budget,937 ) {938 return TransactionOutcome::Rollback(Err(e));939 }940 }941 TransactionOutcome::Commit(Ok(()))942 })?;943944 <TokensMinted<T>>::insert(collection.id, tokens_minted);945946 for (account, balance) in balances {947 <AccountBalance<T>>::insert((collection.id, account), balance);948 }949950 for (i, token) in data.into_iter().enumerate() {951 let token_id = first_token_id + i as u32 + 1;952953 let receivers = token954 .users955 .into_iter()956 .filter(|(_, amount)| *amount > 0)957 .collect::<Vec<_>>();958959 if let [(user, _)] = receivers.as_slice() {960 // if there is exactly one receiver961 <PalletEvm<T>>::deposit_log(962 ERC721Events::Transfer {963 from: H160::default(),964 to: *user.as_eth(),965 token_id: token_id.into(),966 }967 .to_log(collection_id_to_address(collection.id)),968 );969 } else if let [_, ..] = receivers.as_slice() {970 // if there is more than one receiver971 <PalletEvm<T>>::deposit_log(972 ERC721Events::Transfer {973 from: H160::default(),974 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,975 token_id: token_id.into(),976 }977 .to_log(collection_id_to_address(collection.id)),978 );979 }980981 for (user, amount) in receivers.into_iter() {982 <PalletEvm<T>>::deposit_log(983 ERC20Events::Transfer {984 from: H160::default(),985 to: *user.as_eth(),986 value: amount.into(),987 }988 .to_log(T::EvmTokenAddressMapping::token_to_address(989 collection.id,990 TokenId(token_id),991 )),992 );993 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(994 collection.id,995 TokenId(token_id),996 user,997 amount,998 ));999 }1000 }1001 Ok(())1002 }10031004 pub fn set_allowance_unchecked(1005 collection: &RefungibleHandle<T>,1006 sender: &T::CrossAccountId,1007 spender: &T::CrossAccountId,1008 token: TokenId,1009 amount: u128,1010 ) {1011 if amount == 0 {1012 <Allowance<T>>::remove((collection.id, token, sender, spender));1013 } else {1014 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);1015 }10161017 <PalletEvm<T>>::deposit_log(1018 ERC20Events::Approval {1019 owner: *sender.as_eth(),1020 spender: *spender.as_eth(),1021 value: amount.into(),1022 }1023 .to_log(T::EvmTokenAddressMapping::token_to_address(1024 collection.id,1025 token,1026 )),1027 );1028 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1029 collection.id,1030 token,1031 sender.clone(),1032 spender.clone(),1033 amount,1034 ))1035 }10361037 /// Set allowance for the spender to `transfer` or `burn` sender's token pieces.1038 ///1039 /// - `amount`: Amount of token pieces the spender is allowed to `transfer` or `burn.1040 pub fn set_allowance(1041 collection: &RefungibleHandle<T>,1042 sender: &T::CrossAccountId,1043 spender: &T::CrossAccountId,1044 token: TokenId,1045 amount: u128,1046 ) -> DispatchResult {1047 if collection.permissions.access() == AccessMode::AllowList {1048 collection.check_allowlist(sender)?;1049 collection.check_allowlist(spender)?;1050 }10511052 <PalletCommon<T>>::ensure_correct_receiver(spender)?;10531054 if <Balance<T>>::get((collection.id, token, sender)) < amount {1055 ensure!(1056 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),1057 <CommonError<T>>::CantApproveMoreThanOwned1058 );1059 }10601061 // =========10621063 Self::set_allowance_unchecked(collection, sender, spender, token, amount);1064 Ok(())1065 }10661067 /// Set allowance to spend from sender's eth mirror1068 ///1069 /// - `from`: Address of sender's eth mirror.1070 /// - `to`: Adress of spender.1071 /// - `amount`: Amount of token pieces the spender is allowed to `transfer` or `burn.1072 pub fn set_allowance_from(1073 collection: &RefungibleHandle<T>,1074 sender: &T::CrossAccountId,1075 from: &T::CrossAccountId,1076 to: &T::CrossAccountId,1077 token_id: TokenId,1078 amount: u128,1079 ) -> DispatchResult {1080 if collection.permissions.access() == AccessMode::AllowList {1081 collection.check_allowlist(sender)?;1082 collection.check_allowlist(from)?;1083 collection.check_allowlist(to)?;1084 }10851086 <PalletCommon<T>>::ensure_correct_receiver(to)?;10871088 ensure!(1089 sender.conv_eq(from),1090 <CommonError<T>>::AddressIsNotEthMirror1091 );10921093 if <Balance<T>>::get((collection.id, token_id, from)) < amount {1094 ensure!(1095 collection.limits.owner_can_transfer()1096 && (collection.is_owner_or_admin(sender) || collection.is_owner_or_admin(from))1097 && Self::token_exists(collection, token_id),1098 <CommonError<T>>::CantApproveMoreThanOwned1099 );1100 }11011102 // =========11031104 Self::set_allowance_unchecked(collection, from, to, token_id, amount);1105 Ok(())1106 }11071108 /// Returns allowance, which should be set after transaction1109 fn check_allowed(1110 collection: &RefungibleHandle<T>,1111 spender: &T::CrossAccountId,1112 from: &T::CrossAccountId,1113 token: TokenId,1114 amount: u128,1115 nesting_budget: &dyn Budget,1116 ) -> Result<Option<u128>, DispatchError> {1117 if spender.conv_eq(from) {1118 return Ok(None);1119 }1120 if collection.permissions.access() == AccessMode::AllowList {1121 // `from`, `to` checked in [`transfer`]1122 collection.check_allowlist(spender)?;1123 }11241125 if collection.ignores_token_restrictions(spender) {1126 return Ok(Self::compute_allowance_decrease(1127 collection, token, from, spender, amount,1128 ));1129 }11301131 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1132 // TODO: should collection owner be allowed to perform this transfer?1133 ensure!(1134 <PalletStructure<T>>::check_indirectly_owned(1135 spender.clone(),1136 source.0,1137 source.1,1138 None,1139 nesting_budget1140 )?,1141 <CommonError<T>>::ApprovedValueTooLow,1142 );1143 return Ok(None);1144 }11451146 let allowance = Self::compute_allowance_decrease(collection, token, from, spender, amount);1147 if allowance.is_some() {1148 return Ok(allowance);1149 }11501151 // Allowance (if any) would be reduced if spender is also wallet operator1152 if <CollectionAllowance<T>>::get((collection.id, from, spender)) {1153 return Ok(allowance);1154 }11551156 Err(<CommonError<T>>::ApprovedValueTooLow.into())1157 }11581159 /// Returns `Some(amount)` if the `spender` have allowance to spend this amount.1160 /// Otherwise, it returns `None`.1161 fn compute_allowance_decrease(1162 collection: &RefungibleHandle<T>,1163 token: TokenId,1164 from: &T::CrossAccountId,1165 spender: &T::CrossAccountId,1166 amount: u128,1167 ) -> Option<u128> {1168 <Allowance<T>>::get((collection.id, token, from, spender)).checked_sub(amount)1169 }11701171 /// Transfer RFT token pieces from one account to another.1172 ///1173 /// Same as the [`transfer`] but spender doesn't needs to be an owner of the token pieces.1174 /// The owner should set allowance for the spender to transfer pieces.1175 ///1176 /// [`transfer`]: struct.Pallet.html#method.transfer1177 pub fn transfer_from(1178 collection: &RefungibleHandle<T>,1179 spender: &T::CrossAccountId,1180 from: &T::CrossAccountId,1181 to: &T::CrossAccountId,1182 token: TokenId,1183 amount: u128,1184 nesting_budget: &dyn Budget,1185 ) -> DispatchResult {1186 let allowance =1187 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;11881189 // =========11901191 Self::transfer(collection, from, to, token, amount, nesting_budget)?;1192 if let Some(allowance) = allowance {1193 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1194 }1195 Ok(())1196 }11971198 /// Burn RFT token pieces from the account.1199 ///1200 /// Same as the [`burn`] but spender doesn't need to be an owner of the token pieces. The owner should1201 /// set allowance for the spender to burn pieces1202 ///1203 /// [`burn`]: struct.Pallet.html#method.burn1204 pub fn burn_from(1205 collection: &RefungibleHandle<T>,1206 spender: &T::CrossAccountId,1207 from: &T::CrossAccountId,1208 token: TokenId,1209 amount: u128,1210 nesting_budget: &dyn Budget,1211 ) -> DispatchResult {1212 let allowance =1213 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;12141215 // =========12161217 Self::burn(collection, from, token, amount)?;1218 if let Some(allowance) = allowance {1219 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1220 }1221 Ok(())1222 }12231224 /// Create RFT token.1225 ///1226 /// The sender should be the owner/admin of the collection or collection should be configured1227 /// to allow public minting.1228 ///1229 /// - `data`: Contains list of users who will become the owners of the token pieces and amount1230 /// of token pieces they will receive.1231 pub fn create_item(1232 collection: &RefungibleHandle<T>,1233 sender: &T::CrossAccountId,1234 data: CreateItemData<T>,1235 nesting_budget: &dyn Budget,1236 ) -> DispatchResult {1237 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1238 }12391240 /// Repartition RFT token.1241 ///1242 /// `repartition` will set token balance of the sender and total amount of token pieces.1243 /// Sender should own all of the token pieces. `repartition' could be done even if some1244 /// token pieces were burned before.1245 ///1246 /// - `amount`: Total amount of token pieces that the token will have after `repartition`.1247 pub fn repartition(1248 collection: &RefungibleHandle<T>,1249 owner: &T::CrossAccountId,1250 token: TokenId,1251 amount: u128,1252 ) -> DispatchResult {1253 ensure!(1254 amount <= MAX_REFUNGIBLE_PIECES,1255 <Error<T>>::WrongRefungiblePieces1256 );1257 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);1258 // Ensure user owns all pieces1259 let total_pieces = Self::total_pieces(collection.id, token).unwrap_or(u128::MAX);1260 let balance = <Balance<T>>::get((collection.id, token, owner));1261 ensure!(1262 total_pieces == balance,1263 <Error<T>>::RepartitionWhileNotOwningAllPieces1264 );12651266 <Balance<T>>::insert((collection.id, token, owner), amount);1267 <TotalSupply<T>>::insert((collection.id, token), amount);12681269 match total_pieces.cmp(&amount) {1270 Ordering::Less => {1271 let mint_amount = amount - total_pieces;1272 <PalletEvm<T>>::deposit_log(1273 ERC20Events::Transfer {1274 from: H160::default(),1275 to: *owner.as_eth(),1276 value: mint_amount.into(),1277 }1278 .to_log(T::EvmTokenAddressMapping::token_to_address(1279 collection.id,1280 token,1281 )),1282 );1283 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1284 collection.id,1285 token,1286 owner.clone(),1287 mint_amount,1288 ));1289 }1290 Ordering::Greater => {1291 let burn_amount = total_pieces - amount;1292 <PalletEvm<T>>::deposit_log(1293 ERC20Events::Transfer {1294 from: *owner.as_eth(),1295 to: H160::default(),1296 value: burn_amount.into(),1297 }1298 .to_log(T::EvmTokenAddressMapping::token_to_address(1299 collection.id,1300 token,1301 )),1302 );1303 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(1304 collection.id,1305 token,1306 owner.clone(),1307 burn_amount,1308 ));1309 }1310 Ordering::Equal => {}1311 }13121313 Ok(())1314 }13151316 fn token_owner(1317 collection_id: CollectionId,1318 token_id: TokenId,1319 ) -> Result<T::CrossAccountId, TokenOwnerError> {1320 let mut owner = None;1321 let mut count = 0;1322 for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {1323 count += 1;1324 if count > 1 {1325 return Err(TokenOwnerError::MultipleOwners);1326 }1327 owner = Some(key);1328 }1329 owner.ok_or(TokenOwnerError::NotFound)1330 }13311332 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {1333 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()1334 }13351336 pub fn set_collection_properties(1337 collection: &RefungibleHandle<T>,1338 sender: &T::CrossAccountId,1339 properties: Vec<Property>,1340 ) -> DispatchResult {1341 <PalletCommon<T>>::set_collection_properties(collection, sender, properties.into_iter())1342 }13431344 pub fn delete_collection_properties(1345 collection: &RefungibleHandle<T>,1346 sender: &T::CrossAccountId,1347 property_keys: Vec<PropertyKey>,1348 ) -> DispatchResult {1349 <PalletCommon<T>>::delete_collection_properties(1350 collection,1351 sender,1352 property_keys.into_iter(),1353 )1354 }13551356 pub fn set_token_property_permissions(1357 collection: &RefungibleHandle<T>,1358 sender: &T::CrossAccountId,1359 property_permissions: Vec<PropertyKeyPermission>,1360 ) -> DispatchResult {1361 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)1362 }13631364 pub fn token_property_permission(collection_id: CollectionId) -> PropertiesPermissionMap {1365 <PalletCommon<T>>::property_permissions(collection_id)1366 }13671368 pub fn set_scoped_token_property_permissions(1369 collection: &RefungibleHandle<T>,1370 sender: &T::CrossAccountId,1371 scope: PropertyScope,1372 property_permissions: Vec<PropertyKeyPermission>,1373 ) -> DispatchResult {1374 <PalletCommon<T>>::set_scoped_token_property_permissions(1375 collection,1376 sender,1377 scope,1378 property_permissions,1379 )1380 }13811382 /// Returns 10 token in no particular order.1383 ///1384 /// There is no direct way to get token holders in ascending order,1385 /// since `iter_prefix` returns values in no particular order.1386 /// Therefore, getting the 10 largest holders with a large value of holders1387 /// can lead to impact memory allocation + sorting with `n * log (n)`.1388 pub fn token_owners(1389 collection_id: CollectionId,1390 token: TokenId,1391 ) -> Option<Vec<T::CrossAccountId>> {1392 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection_id, token))1393 .map(|(owner, _amount)| owner)1394 .take(10)1395 .collect();13961397 if res.is_empty() {1398 None1399 } else {1400 Some(res)1401 }1402 }14031404 /// Sets or unsets the approval of a given operator.1405 ///1406 /// The `operator` is allowed to transfer all token pieces of the `owner` on their behalf.1407 /// - `owner`: Token owner1408 /// - `operator`: Operator1409 /// - `approve`: Should operator status be granted or revoked?1410 pub fn set_allowance_for_all(1411 collection: &RefungibleHandle<T>,1412 owner: &T::CrossAccountId,1413 spender: &T::CrossAccountId,1414 approve: bool,1415 ) -> DispatchResult {1416 <PalletCommon<T>>::set_allowance_for_all(1417 collection,1418 owner,1419 spender,1420 approve,1421 || <CollectionAllowance<T>>::insert((collection.id, owner, spender), approve),1422 ERC721Events::ApprovalForAll {1423 owner: *owner.as_eth(),1424 operator: *spender.as_eth(),1425 approved: approve,1426 }1427 .to_log(collection_id_to_address(collection.id)),1428 )1429 }14301431 /// Tells whether the given `owner` approves the `operator`.1432 pub fn allowance_for_all(1433 collection: &RefungibleHandle<T>,1434 owner: &T::CrossAccountId,1435 spender: &T::CrossAccountId,1436 ) -> bool {1437 <CollectionAllowance<T>>::get((collection.id, owner, spender))1438 }14391440 pub fn repair_item(collection: &RefungibleHandle<T>, token: TokenId) -> DispatchResult {1441 <TokenProperties<T>>::mutate((collection.id, token), |properties| {1442 properties.recompute_consumed_space();1443 });14441445 Ok(())1446 }1447}runtime/common/ethereum/precompiles/utils/macro/src/lib.rsdiffbeforeafterboth--- a/runtime/common/ethereum/precompiles/utils/macro/src/lib.rs
+++ b/runtime/common/ethereum/precompiles/utils/macro/src/lib.rs
@@ -35,8 +35,8 @@
/// ```ignore
/// #[generate_function_selector]
/// enum Action {
-/// Toto = "toto()",
-/// Tata = "tata()",
+/// Toto = "toto()",
+/// Tata = "tata()",
/// }
/// ```
///
@@ -45,8 +45,8 @@
/// ```rust
/// #[repr(u32)]
/// enum Action {
-/// Toto = 119097542u32,
-/// Tata = 1414311903u32,
+/// Toto = 119097542u32,
+/// Tata = 1414311903u32,
/// }
/// ```
///
runtime/common/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/runtime_apis.rs
+++ b/runtime/common/runtime_apis.rs
@@ -687,7 +687,7 @@
fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) {
log::info!("try-runtime::on_runtime_upgrade unique-chain.");
let weight = Executive::try_runtime_upgrade(checks).unwrap();
- (weight, crate::config::substrate::RuntimeBlockWeights::get().max_block)
+ (weight, $crate::config::substrate::RuntimeBlockWeights::get().max_block)
}
fn execute_block(