difftreelog
refactor share unq sponsoring code with evm
in: master
4 files changed
pallets/nft/src/eth/sponsoring.rsdiffbeforeafterboth--- a/pallets/nft/src/eth/sponsoring.rs
+++ b/pallets/nft/src/eth/sponsoring.rs
@@ -1,123 +1,64 @@
//! Implements EVM sponsoring logic via OnChargeEVMTransaction
-use crate::{Collection, Config, FungibleTransferBasket, NftTransferBasket};
+use crate::{Config, sponsorship::*};
use evm_coder::{Call, abi::AbiReader};
-use frame_support::{
- storage::{StorageDoubleMap},
-};
-use pallet_common::eth::map_eth_to_id;
+use pallet_common::{CollectionHandle, eth::map_eth_to_id};
use sp_core::H160;
use sp_std::prelude::*;
use up_sponsorship::SponsorshipHandler;
use core::marker::PhantomData;
use core::convert::TryInto;
-use nft_data_structs::{CollectionId, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT};
-use pallet_common::{
- CollectionById,
- account::{CrossAccountId, EvmBackwardsAddressMapping},
-};
+use nft_data_structs::TokenId;
+use up_evm_mapping::EvmBackwardsAddressMapping;
+use pallet_evm::AddressMapping;
use pallet_nonfungible::erc::{UniqueNFTCall, ERC721UniqueExtensionsCall, ERC721Call};
use pallet_fungible::erc::{UniqueFungibleCall, ERC20Call};
-struct AnyError;
-
-fn try_sponsor<T: Config>(
- caller: &H160,
- collection_id: CollectionId,
- collection: &Collection<T>,
- call: &[u8],
-) -> Result<(), AnyError> {
- let (method_id, mut reader) = AbiReader::new_call(call).map_err(|_| AnyError)?;
- match &collection.mode {
- crate::CollectionMode::NFT => {
- let call: UniqueNFTCall = UniqueNFTCall::parse(method_id, &mut reader)
- .map_err(|_| AnyError)?
- .ok_or(AnyError)?;
- match call {
- UniqueNFTCall::ERC721UniqueExtensions(ERC721UniqueExtensionsCall::Transfer {
- token_id,
- ..
- })
- | UniqueNFTCall::ERC721(ERC721Call::TransferFrom { token_id, .. }) => {
- let token_id: u32 = token_id.try_into().map_err(|_| AnyError)?;
- let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
- let collection_limits = &collection.limits;
- let limit =
- collection_limits.sponsor_transfer_timeout(NFT_SPONSOR_TRANSFER_TIMEOUT);
-
- let mut sponsor = true;
- if <NftTransferBasket<T>>::contains_key(collection_id, token_id) {
- let last_tx_block = <NftTransferBasket<T>>::get(collection_id, token_id);
- let limit_time = last_tx_block + limit.into();
- if block_number <= limit_time {
- sponsor = false;
- }
+pub struct NftEthSponsorshipHandler<T: Config>(PhantomData<*const T>);
+impl<T: Config> SponsorshipHandler<H160, (H160, Vec<u8>)> for NftEthSponsorshipHandler<T> {
+ fn get_sponsor(who: &H160, call: &(H160, Vec<u8>)) -> Option<H160> {
+ let collection_id = map_eth_to_id(&call.0)?;
+ let collection = <CollectionHandle<T>>::new(collection_id)?;
+ let sponsor = collection.sponsorship.sponsor()?.clone();
+ let sponsor =
+ <T as pallet_common::Config>::EvmBackwardsAddressMapping::from_account_id(sponsor);
+ let who = <T as pallet_common::Config>::EvmAddressMapping::into_account_id(*who);
+ let (method_id, mut reader) = AbiReader::new_call(&call.1).ok()?;
+ match &collection.mode {
+ crate::CollectionMode::NFT => {
+ let call = UniqueNFTCall::parse(method_id, &mut reader).ok()??;
+ match call {
+ UniqueNFTCall::ERC721UniqueExtensions(
+ ERC721UniqueExtensionsCall::Transfer { token_id, .. },
+ )
+ | UniqueNFTCall::ERC721(ERC721Call::TransferFrom { token_id, .. }) => {
+ let token_id: TokenId = token_id.try_into().ok()?;
+ withdraw_transfer::<T>(&collection, &who, &token_id).map(|()| sponsor)
}
- if sponsor {
- <NftTransferBasket<T>>::insert(collection_id, token_id, block_number);
- return Ok(());
+ UniqueNFTCall::ERC721(ERC721Call::Approve { token_id, .. }) => {
+ let token_id: TokenId = token_id.try_into().ok()?;
+ withdraw_approve::<T>(&collection, &who, &token_id).map(|()| sponsor)
}
+ _ => None,
}
- _ => {}
}
- }
- crate::CollectionMode::Fungible(_) => {
- let call: UniqueFungibleCall = UniqueFungibleCall::parse(method_id, &mut reader)
- .map_err(|_| AnyError)?
- .ok_or(AnyError)?;
- #[allow(clippy::single_match)]
- match call {
- UniqueFungibleCall::ERC20(ERC20Call::Transfer { .. }) => {
- let who = T::CrossAccountId::from_eth(*caller);
- let collection_limits = &collection.limits;
- let limit = collection_limits
- .sponsor_transfer_timeout(FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT);
-
- let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
- let mut sponsored = true;
- if <FungibleTransferBasket<T>>::contains_key(collection_id, who.as_sub()) {
- let last_tx_block =
- <FungibleTransferBasket<T>>::get(collection_id, who.as_sub());
- let limit_time = last_tx_block + limit.into();
- if block_number <= limit_time {
- sponsored = false;
- }
- }
- if sponsored {
- <FungibleTransferBasket<T>>::insert(
- collection_id,
- who.as_sub(),
- block_number,
- );
- return Ok(());
+ crate::CollectionMode::Fungible(_) => {
+ let call = UniqueFungibleCall::parse(method_id, &mut reader).ok()??;
+ #[allow(clippy::single_match)]
+ match call {
+ UniqueFungibleCall::ERC20(
+ ERC20Call::Transfer { .. } | ERC20Call::TransferFrom { .. },
+ ) => withdraw_transfer::<T>(&collection, &who, &TokenId::default())
+ .map(|()| sponsor),
+ UniqueFungibleCall::ERC20(ERC20Call::Approve { .. }) => {
+ withdraw_approve::<T>(&collection, &who, &TokenId::default())
+ .map(|()| sponsor)
}
- }
- _ => {}
- }
- }
- _ => {}
- }
- Err(AnyError)
-}
-
-pub struct NftEthSponsorshipHandler<T: Config>(PhantomData<*const T>);
-impl<T: Config> SponsorshipHandler<H160, (H160, Vec<u8>)> for NftEthSponsorshipHandler<T> {
- fn get_sponsor(who: &H160, call: &(H160, Vec<u8>)) -> Option<H160> {
- if let Some(collection_id) = map_eth_to_id(&call.0) {
- if let Some(collection) = <CollectionById<T>>::get(collection_id) {
- if !collection.sponsorship.confirmed() {
- return None;
- }
- if try_sponsor(who, collection_id, &collection, &call.1).is_ok() {
- return collection
- .sponsorship
- .sponsor()
- .cloned()
- .map(T::EvmBackwardsAddressMapping::from_account_id);
+ _ => None,
}
}
+ _ => None,
}
- None
}
}
pallets/nft/src/lib.rsdiffbeforeafterboth1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9 clippy::too_many_arguments,10 clippy::unnecessary_mut_passed,11 clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19 construct_runtime, decl_module, decl_storage, decl_error,20 dispatch::DispatchResult,21 ensure, fail, parameter_types,22 traits::{23 ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness,24 IsSubType, WithdrawReasons,25 },26 weights::{27 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29 WeightToFeePolynomial, DispatchClass,30 },31 StorageValue, transactional,32 pallet_prelude::DispatchResultWithPostInfo,33};34use scale_info::TypeInfo;35use frame_system::{self as system, ensure_signed};36use sp_runtime::{sp_std::prelude::Vec};37use nft_data_structs::{38 MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,39 VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,40 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,41 NFT_SPONSOR_TRANSFER_TIMEOUT, AccessMode, Collection, CreateItemData, CollectionLimits,42 CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,43};44use pallet_common::{45 account::CrossAccountId, CollectionHandle, IsAdmin, Pallet as PalletCommon,46 Error as CommonError, CommonWeightInfo, Allowlist,47};48use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};49use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};50use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};5152#[cfg(test)]53mod mock;5455#[cfg(test)]56mod tests;5758mod eth;59mod sponsorship;60pub use sponsorship::NftSponsorshipHandler;61pub use eth::sponsoring::NftEthSponsorshipHandler;6263pub use eth::NftErcSupport;6465pub mod common;66use common::CommonWeights;67pub mod dispatch;68use dispatch::dispatch_call;6970#[cfg(feature = "runtime-benchmarks")]71mod benchmarking;72pub mod weights;73use weights::WeightInfo;7475decl_error! {76 /// Error for non-fungible-token module.77 pub enum Error for Module<T: Config> {78 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.79 CollectionDecimalPointLimitExceeded,80 /// This address is not set as sponsor, use setCollectionSponsor first.81 ConfirmUnsetSponsorFail,82 /// Length of items properties must be greater than 0.83 EmptyArgument,84 /// Collection limit bounds per collection exceeded85 CollectionLimitBoundsExceeded,86 /// Tried to enable permissions which are only permitted to be disabled87 OwnerPermissionsCantBeReverted,88 }89}90pub trait Config:91 system::Config92 + pallet_evm_coder_substrate::Config93 + pallet_common::Config94 + pallet_nonfungible::Config95 + pallet_refungible::Config96 + pallet_fungible::Config97 + Sized98 + TypeInfo99{100 /// Weight information for extrinsics in this pallet.101 type WeightInfo: WeightInfo;102}103104type SelfWeightOf<T> = <T as Config>::WeightInfo;105106// # Used definitions107//108// ## User control levels109//110// chain-controlled - key is uncontrolled by user111// i.e autoincrementing index112// can use non-cryptographic hash113// real - key is controlled by user114// but it is hard to generate enough colliding values, i.e owner of signed txs115// can use non-cryptographic hash116// controlled - key is completly controlled by users117// i.e maps with mutable keys118// should use cryptographic hash119//120// ## User control level downgrade reasons121//122// ?1 - chain-controlled -> controlled123// collections/tokens can be destroyed, resulting in massive holes124// ?2 - chain-controlled -> controlled125// same as ?1, but can be only added, resulting in easier exploitation126// ?3 - real -> controlled127// no confirmation required, so addresses can be easily generated128decl_storage! {129 trait Store for Module<T: Config> as Nft {130131 //#region Private members132 /// Used for migrations133 ChainVersion: u64;134 //#endregion135136 //#region Tokens transfer rate limit baskets137 /// (Collection id (controlled?2), who created (real))138 /// TODO: Off chain worker should remove from this map when collection gets removed139 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;140 /// Collection id (controlled?2), token id (controlled?2)141 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;142 /// Collection id (controlled?2), owning user (real)143 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;144 /// Collection id (controlled?2), token id (controlled?2)145 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;146 //#endregion147148 /// Variable metadata sponsoring149 /// Collection id (controlled?2), token id (controlled?2)150 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;151 /// Approval sponsoring152 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;153 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;154 pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;155 }156}157158decl_module! {159 pub struct Module<T: Config> for enum Call160 where161 origin: T::Origin162 {163 type Error = Error<T>;164165 fn on_initialize(_now: T::BlockNumber) -> Weight {166 0167 }168169 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.170 ///171 /// # Permissions172 ///173 /// * Anyone.174 ///175 /// # Arguments176 ///177 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.178 ///179 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.180 ///181 /// * token_prefix: UTF-8 string with token prefix.182 ///183 /// * mode: [CollectionMode] collection type and type dependent data.184 // returns collection ID185 #[weight = <SelfWeightOf<T>>::create_collection()]186 #[transactional]187 pub fn create_collection(origin,188 collection_name: Vec<u16>,189 collection_description: Vec<u16>,190 token_prefix: Vec<u8>,191 mode: CollectionMode) -> DispatchResult {192193 // Anyone can create a collection194 let who = ensure_signed(origin)?;195196 // Create new collection197 let new_collection = Collection::<T> {198 owner: who.clone(),199 name: collection_name,200 mode: mode.clone(),201 mint_mode: false,202 access: AccessMode::Normal,203 description: collection_description,204 token_prefix,205 offchain_schema: Vec::new(),206 schema_version: SchemaVersion::ImageURL,207 sponsorship: SponsorshipState::Disabled,208 variable_on_chain_schema: Vec::new(),209 const_on_chain_schema: Vec::new(),210 limits: Default::default(),211 meta_update_permission: Default::default(),212 };213214 let _id = match mode {215 CollectionMode::NFT => {PalletNonfungible::init_collection(new_collection)?},216 CollectionMode::Fungible(decimal_points) => {217 // check params218 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);219 PalletFungible::init_collection(new_collection)?220 }221 CollectionMode::ReFungible => {222 PalletRefungible::init_collection(new_collection)?223 }224 };225226 Ok(())227 }228229 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.230 ///231 /// # Permissions232 ///233 /// * Collection Owner.234 ///235 /// # Arguments236 ///237 /// * collection_id: collection to destroy.238 #[weight = <SelfWeightOf<T>>::destroy_collection()]239 #[transactional]240 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {241 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);242243 let collection = <CollectionHandle<T>>::try_get(collection_id)?;244 collection.check_is_owner(&sender)?;245246 // =========247248 match collection.mode {249 CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,250 CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,251 CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,252 }253254 <NftTransferBasket<T>>::remove_prefix(collection_id, None);255 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);256 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);257258 <VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);259 <NftApproveBasket<T>>::remove_prefix(collection_id, None);260 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);261 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);262263 Ok(())264 }265266 /// Add an address to allow list.267 ///268 /// # Permissions269 ///270 /// * Collection Owner271 /// * Collection Admin272 ///273 /// # Arguments274 ///275 /// * collection_id.276 ///277 /// * address.278 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]279 #[transactional]280 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{281282 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);283 let collection = <CollectionHandle<T>>::try_get(collection_id)?;284285 <PalletCommon<T>>::toggle_allowlist(286 &collection,287 &sender,288 &address,289 true,290 )?;291292 Ok(())293 }294295 /// Remove an address from allow list.296 ///297 /// # Permissions298 ///299 /// * Collection Owner300 /// * Collection Admin301 ///302 /// # Arguments303 ///304 /// * collection_id.305 ///306 /// * address.307 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]308 #[transactional]309 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{310311 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);312 let collection = <CollectionHandle<T>>::try_get(collection_id)?;313314 <PalletCommon<T>>::toggle_allowlist(315 &collection,316 &sender,317 &address,318 false,319 )?;320321 Ok(())322 }323324 /// Toggle between normal and allow list access for the methods with access for `Anyone`.325 ///326 /// # Permissions327 ///328 /// * Collection Owner.329 ///330 /// # Arguments331 ///332 /// * collection_id.333 ///334 /// * mode: [AccessMode]335 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]336 #[transactional]337 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult338 {339 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);340341 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;342 target_collection.check_is_owner(&sender)?;343344 target_collection.access = mode;345 target_collection.save()346 }347348 /// Allows Anyone to create tokens if:349 /// * Allow List is enabled, and350 /// * Address is added to allow list, and351 /// * This method was called with True parameter352 ///353 /// # Permissions354 /// * Collection Owner355 ///356 /// # Arguments357 ///358 /// * collection_id.359 ///360 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.361 #[weight = <SelfWeightOf<T>>::set_mint_permission()]362 #[transactional]363 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult364 {365 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);366367 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;368 target_collection.check_is_owner(&sender)?;369370 target_collection.mint_mode = mint_permission;371 target_collection.save()372 }373374 /// Change the owner of the collection.375 ///376 /// # Permissions377 ///378 /// * Collection Owner.379 ///380 /// # Arguments381 ///382 /// * collection_id.383 ///384 /// * new_owner.385 #[weight = <SelfWeightOf<T>>::change_collection_owner()]386 #[transactional]387 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {388389 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);390391 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;392 target_collection.check_is_owner(&sender)?;393394 target_collection.owner = new_owner;395 target_collection.save()396 }397398 /// Adds an admin of the Collection.399 /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.400 ///401 /// # Permissions402 ///403 /// * Collection Owner.404 /// * Collection Admin.405 ///406 /// # Arguments407 ///408 /// * collection_id: ID of the Collection to add admin for.409 ///410 /// * new_admin_id: Address of new admin to add.411 #[weight = <SelfWeightOf<T>>::add_collection_admin()]412 #[transactional]413 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {414 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);415 let collection = <CollectionHandle<T>>::try_get(collection_id)?;416417 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)418 }419420 /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.421 ///422 /// # Permissions423 ///424 /// * Collection Owner.425 /// * Collection Admin.426 ///427 /// # Arguments428 ///429 /// * collection_id: ID of the Collection to remove admin for.430 ///431 /// * account_id: Address of admin to remove.432 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]433 #[transactional]434 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {435 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);436 let collection = <CollectionHandle<T>>::try_get(collection_id)?;437438 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)439 }440441 /// # Permissions442 ///443 /// * Collection Owner444 ///445 /// # Arguments446 ///447 /// * collection_id.448 ///449 /// * new_sponsor.450 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]451 #[transactional]452 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {453 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);454455 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;456 target_collection.check_is_owner(&sender)?;457458 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);459 target_collection.save()460 }461462 /// # Permissions463 ///464 /// * Sponsor.465 ///466 /// # Arguments467 ///468 /// * collection_id.469 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]470 #[transactional]471 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {472 let sender = ensure_signed(origin)?;473474 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;475 ensure!(476 target_collection.sponsorship.pending_sponsor() == Some(&sender),477 Error::<T>::ConfirmUnsetSponsorFail478 );479480 target_collection.sponsorship = SponsorshipState::Confirmed(sender);481 target_collection.save()482 }483484 /// Switch back to pay-per-own-transaction model.485 ///486 /// # Permissions487 ///488 /// * Collection owner.489 ///490 /// # Arguments491 ///492 /// * collection_id.493 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]494 #[transactional]495 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {496 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);497498 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;499 target_collection.check_is_owner(&sender)?;500501 target_collection.sponsorship = SponsorshipState::Disabled;502 target_collection.save()503 }504505 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.506 ///507 /// # Permissions508 ///509 /// * Collection Owner.510 /// * Collection Admin.511 /// * Anyone if512 /// * Allow List is enabled, and513 /// * Address is added to allow list, and514 /// * MintPermission is enabled (see SetMintPermission method)515 ///516 /// # Arguments517 ///518 /// * collection_id: ID of the collection.519 ///520 /// * owner: Address, initial owner of the NFT.521 ///522 /// * data: Token data to store on chain.523 #[weight = <CommonWeights<T>>::create_item()]524 #[transactional]525 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {526 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);527528 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))529 }530531 /// This method creates multiple items in a collection created with CreateCollection method.532 ///533 /// # Permissions534 ///535 /// * Collection Owner.536 /// * Collection Admin.537 /// * Anyone if538 /// * Allow List is enabled, and539 /// * Address is added to allow list, and540 /// * MintPermission is enabled (see SetMintPermission method)541 ///542 /// # Arguments543 ///544 /// * collection_id: ID of the collection.545 ///546 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].547 ///548 /// * owner: Address, initial owner of the NFT.549 #[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]550 #[transactional]551 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {552 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);553 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);554555 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))556 }557558 // TODO! transaction weight559560 /// Set transfers_enabled value for particular collection561 ///562 /// # Permissions563 ///564 /// * Collection Owner.565 ///566 /// # Arguments567 ///568 /// * collection_id: ID of the collection.569 ///570 /// * value: New flag value.571 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]572 #[transactional]573 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {574 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);575 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;576 target_collection.check_is_owner(&sender)?;577578 // =========579580 target_collection.limits.transfers_enabled = Some(value);581 target_collection.save()582 }583584 /// Destroys a concrete instance of NFT.585 ///586 /// # Permissions587 ///588 /// * Collection Owner.589 /// * Collection Admin.590 /// * Current NFT Owner.591 ///592 /// # Arguments593 ///594 /// * collection_id: ID of the collection.595 ///596 /// * item_id: ID of NFT to burn.597 #[weight = <CommonWeights<T>>::burn_item()]598 #[transactional]599 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {600 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);601602 let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;603 if value == 1 {604 <NftTransferBasket<T>>::remove(collection_id, item_id);605 <NftApproveBasket<T>>::remove(collection_id, item_id);606 }607 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?608 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());609 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));610 Ok(post_info)611 }612613 /// Destroys a concrete instance of NFT on behalf of the owner614 /// See also: [`approve`]615 ///616 /// # Permissions617 ///618 /// * Collection Owner.619 /// * Collection Admin.620 /// * Current NFT Owner.621 ///622 /// # Arguments623 ///624 /// * collection_id: ID of the collection.625 ///626 /// * item_id: ID of NFT to burn.627 ///628 /// * from: owner of item629 #[weight = <CommonWeights<T>>::burn_from()]630 #[transactional]631 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {632 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);633634 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))635 }636637 /// Change ownership of the token.638 ///639 /// # Permissions640 ///641 /// * Collection Owner642 /// * Collection Admin643 /// * Current NFT owner644 ///645 /// # Arguments646 ///647 /// * recipient: Address of token recipient.648 ///649 /// * collection_id.650 ///651 /// * item_id: ID of the item652 /// * Non-Fungible Mode: Required.653 /// * Fungible Mode: Ignored.654 /// * Re-Fungible Mode: Required.655 ///656 /// * value: Amount to transfer.657 /// * Non-Fungible Mode: Ignored658 /// * Fungible Mode: Must specify transferred amount659 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)660 #[weight = <CommonWeights<T>>::transfer()]661 #[transactional]662 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {663 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);664665 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))666 }667668 /// Set, change, or remove approved address to transfer the ownership of the NFT.669 ///670 /// # Permissions671 ///672 /// * Collection Owner673 /// * Collection Admin674 /// * Current NFT owner675 ///676 /// # Arguments677 ///678 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).679 ///680 /// * collection_id.681 ///682 /// * item_id: ID of the item.683 #[weight = <CommonWeights<T>>::approve()]684 #[transactional]685 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {686 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);687688 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))689 }690691 /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.692 ///693 /// # Permissions694 /// * Collection Owner695 /// * Collection Admin696 /// * Current NFT owner697 /// * Address approved by current NFT owner698 ///699 /// # Arguments700 ///701 /// * from: Address that owns token.702 ///703 /// * recipient: Address of token recipient.704 ///705 /// * collection_id.706 ///707 /// * item_id: ID of the item.708 ///709 /// * value: Amount to transfer.710 #[weight = <CommonWeights<T>>::transfer_from()]711 #[transactional]712 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {713 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);714715 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))716 }717718 /// Set off-chain data schema.719 ///720 /// # Permissions721 ///722 /// * Collection Owner723 /// * Collection Admin724 ///725 /// # Arguments726 ///727 /// * collection_id.728 ///729 /// * schema: String representing the offchain data schema.730 #[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]731 #[transactional]732 pub fn set_variable_meta_data (733 origin,734 collection_id: CollectionId,735 item_id: TokenId,736 data: Vec<u8>737 ) -> DispatchResultWithPostInfo {738 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);739740 dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))741 }742743 /// Set meta_update_permission value for particular collection744 ///745 /// # Permissions746 ///747 /// * Collection Owner.748 ///749 /// # Arguments750 ///751 /// * collection_id: ID of the collection.752 ///753 /// * value: New flag value.754 #[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]755 #[transactional]756 pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {757 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);758 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;759760 ensure!(761 target_collection.meta_update_permission != MetaUpdatePermission::None,762 <CommonError<T>>::MetadataFlagFrozen,763 );764 target_collection.check_is_owner(&sender)?;765766 target_collection.meta_update_permission = value;767768 target_collection.save()769 }770771 /// Set schema standard772 /// ImageURL773 /// Unique774 ///775 /// # Permissions776 ///777 /// * Collection Owner778 /// * Collection Admin779 ///780 /// # Arguments781 ///782 /// * collection_id.783 ///784 /// * schema: SchemaVersion: enum785 #[weight = <SelfWeightOf<T>>::set_schema_version()]786 #[transactional]787 pub fn set_schema_version(788 origin,789 collection_id: CollectionId,790 version: SchemaVersion791 ) -> DispatchResult {792 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);793 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;794 target_collection.check_is_owner_or_admin(&sender)?;795 target_collection.schema_version = version;796 target_collection.save()797 }798799 /// Set off-chain data schema.800 ///801 /// # Permissions802 ///803 /// * Collection Owner804 /// * Collection Admin805 ///806 /// # Arguments807 ///808 /// * collection_id.809 ///810 /// * schema: String representing the offchain data schema.811 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]812 #[transactional]813 pub fn set_offchain_schema(814 origin,815 collection_id: CollectionId,816 schema: Vec<u8>817 ) -> DispatchResult {818 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);819 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;820 target_collection.check_is_owner_or_admin(&sender)?;821822 // check schema limit823 ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");824825 target_collection.offchain_schema = schema;826 target_collection.save()827 }828829 /// Set const on-chain data schema.830 ///831 /// # Permissions832 ///833 /// * Collection Owner834 /// * Collection Admin835 ///836 /// # Arguments837 ///838 /// * collection_id.839 ///840 /// * schema: String representing the const on-chain data schema.841 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]842 #[transactional]843 pub fn set_const_on_chain_schema (844 origin,845 collection_id: CollectionId,846 schema: Vec<u8>847 ) -> DispatchResult {848 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);849 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;850 target_collection.check_is_owner_or_admin(&sender)?;851852 // check schema limit853 ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");854855 target_collection.const_on_chain_schema = schema;856 target_collection.save()857 }858859 /// Set variable on-chain data schema.860 ///861 /// # Permissions862 ///863 /// * Collection Owner864 /// * Collection Admin865 ///866 /// # Arguments867 ///868 /// * collection_id.869 ///870 /// * schema: String representing the variable on-chain data schema.871 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]872 #[transactional]873 pub fn set_variable_on_chain_schema (874 origin,875 collection_id: CollectionId,876 schema: Vec<u8>877 ) -> DispatchResult {878 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);879 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;880 target_collection.check_is_owner_or_admin(&sender)?;881882 // check schema limit883 ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");884885 target_collection.variable_on_chain_schema = schema;886 target_collection.save()887 }888889 #[weight = <SelfWeightOf<T>>::set_collection_limits()]890 #[transactional]891 pub fn set_collection_limits(892 origin,893 collection_id: CollectionId,894 new_limit: CollectionLimits,895 ) -> DispatchResult {896 let mut new_limit = new_limit;897 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);898 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;899 target_collection.check_is_owner(&sender)?;900 let old_limit = &target_collection.limits;901902 macro_rules! limit_default {903 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{904 $(905 if let Some($new) = $new.$field {906 let $old = $old.$field($($arg)?);907 let _ = $new;908 let _ = $old;909 $check910 } else {911 $new.$field = $old.$field912 }913 )*914 }};915 }916917 limit_default!(old_limit, new_limit,918 account_token_ownership_limit => ensure!(919 new_limit <= MAX_TOKEN_OWNERSHIP,920 <Error<T>>::CollectionLimitBoundsExceeded,921 ),922 sponsor_transfer_timeout(match target_collection.mode {923 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,924 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,925 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,926 }) => ensure!(927 new_limit <= MAX_SPONSOR_TIMEOUT,928 <Error<T>>::CollectionLimitBoundsExceeded,929 ),930 sponsored_data_size => ensure!(931 new_limit <= CUSTOM_DATA_LIMIT,932 <Error<T>>::CollectionLimitBoundsExceeded,933 ),934 token_limit => ensure!(935 old_limit >= new_limit && new_limit > 0,936 <CommonError<T>>::CollectionTokenLimitExceeded937 ),938 owner_can_transfer => ensure!(939 old_limit || !new_limit,940 <Error<T>>::OwnerPermissionsCantBeReverted,941 ),942 owner_can_destroy => ensure!(943 old_limit || !new_limit,944 <Error<T>>::OwnerPermissionsCantBeReverted,945 ),946 sponsored_data_rate_limit => {},947 transfers_enabled => {},948 );949950 target_collection.limits = new_limit;951952 target_collection.save()953 }954 }955}956957// TODO: limit returned entries?958impl<T: Config> Pallet<T> {959 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {960 <IsAdmin<T>>::iter_prefix((collection,))961 .map(|(a, _)| a)962 .collect()963 }964 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {965 <Allowlist<T>>::iter_prefix((collection,))966 .map(|(a, _)| a)967 .collect()968 }969}1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9 clippy::too_many_arguments,10 clippy::unnecessary_mut_passed,11 clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19 construct_runtime, decl_module, decl_storage, decl_error,20 dispatch::DispatchResult,21 ensure, fail, parameter_types,22 traits::{23 ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness,24 IsSubType, WithdrawReasons,25 },26 weights::{27 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29 WeightToFeePolynomial, DispatchClass,30 },31 StorageValue, transactional,32 pallet_prelude::DispatchResultWithPostInfo,33};34use scale_info::TypeInfo;35use frame_system::{self as system, ensure_signed};36use sp_runtime::{sp_std::prelude::Vec};37use nft_data_structs::{38 MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,39 VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,40 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,41 NFT_SPONSOR_TRANSFER_TIMEOUT, AccessMode, Collection, CreateItemData, CollectionLimits,42 CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,43};44use pallet_common::{45 account::CrossAccountId, CollectionHandle, IsAdmin, Pallet as PalletCommon,46 Error as CommonError, CommonWeightInfo, Allowlist,47};48use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};49use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};50use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};5152#[cfg(test)]53mod mock;5455#[cfg(test)]56mod tests;5758mod eth;59mod sponsorship;60pub use sponsorship::NftSponsorshipHandler;61pub use eth::sponsoring::NftEthSponsorshipHandler;6263pub use eth::NftErcSupport;6465pub mod common;66use common::CommonWeights;67pub mod dispatch;68use dispatch::dispatch_call;6970#[cfg(feature = "runtime-benchmarks")]71mod benchmarking;72pub mod weights;73use weights::WeightInfo;7475decl_error! {76 /// Error for non-fungible-token module.77 pub enum Error for Module<T: Config> {78 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.79 CollectionDecimalPointLimitExceeded,80 /// This address is not set as sponsor, use setCollectionSponsor first.81 ConfirmUnsetSponsorFail,82 /// Length of items properties must be greater than 0.83 EmptyArgument,84 /// Collection limit bounds per collection exceeded85 CollectionLimitBoundsExceeded,86 /// Tried to enable permissions which are only permitted to be disabled87 OwnerPermissionsCantBeReverted,88 }89}90pub trait Config:91 system::Config92 + pallet_evm_coder_substrate::Config93 + pallet_common::Config94 + pallet_nonfungible::Config95 + pallet_refungible::Config96 + pallet_fungible::Config97 + Sized98 + TypeInfo99{100 /// Weight information for extrinsics in this pallet.101 type WeightInfo: WeightInfo;102}103104type SelfWeightOf<T> = <T as Config>::WeightInfo;105106// # Used definitions107//108// ## User control levels109//110// chain-controlled - key is uncontrolled by user111// i.e autoincrementing index112// can use non-cryptographic hash113// real - key is controlled by user114// but it is hard to generate enough colliding values, i.e owner of signed txs115// can use non-cryptographic hash116// controlled - key is completly controlled by users117// i.e maps with mutable keys118// should use cryptographic hash119//120// ## User control level downgrade reasons121//122// ?1 - chain-controlled -> controlled123// collections/tokens can be destroyed, resulting in massive holes124// ?2 - chain-controlled -> controlled125// same as ?1, but can be only added, resulting in easier exploitation126// ?3 - real -> controlled127// no confirmation required, so addresses can be easily generated128decl_storage! {129 trait Store for Module<T: Config> as Nft {130131 //#region Private members132 /// Used for migrations133 ChainVersion: u64;134 //#endregion135136 //#region Tokens transfer rate limit baskets137 /// (Collection id (controlled?2), who created (real))138 /// TODO: Off chain worker should remove from this map when collection gets removed139 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;140 /// Collection id (controlled?2), token id (controlled?2)141 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;142 /// Collection id (controlled?2), owning user (real)143 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;144 /// Collection id (controlled?2), token id (controlled?2)145 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;146 //#endregion147148 /// Variable metadata sponsoring149 /// Collection id (controlled?2), token id (controlled?2)150 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;151 /// Approval sponsoring152 pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;153 pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;154 pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;155 }156}157158decl_module! {159 pub struct Module<T: Config> for enum Call160 where161 origin: T::Origin162 {163 type Error = Error<T>;164165 fn on_initialize(_now: T::BlockNumber) -> Weight {166 0167 }168169 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.170 ///171 /// # Permissions172 ///173 /// * Anyone.174 ///175 /// # Arguments176 ///177 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.178 ///179 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.180 ///181 /// * token_prefix: UTF-8 string with token prefix.182 ///183 /// * mode: [CollectionMode] collection type and type dependent data.184 // returns collection ID185 #[weight = <SelfWeightOf<T>>::create_collection()]186 #[transactional]187 pub fn create_collection(origin,188 collection_name: Vec<u16>,189 collection_description: Vec<u16>,190 token_prefix: Vec<u8>,191 mode: CollectionMode) -> DispatchResult {192193 // Anyone can create a collection194 let who = ensure_signed(origin)?;195196 // Create new collection197 let new_collection = Collection::<T> {198 owner: who.clone(),199 name: collection_name,200 mode: mode.clone(),201 mint_mode: false,202 access: AccessMode::Normal,203 description: collection_description,204 token_prefix,205 offchain_schema: Vec::new(),206 schema_version: SchemaVersion::ImageURL,207 sponsorship: SponsorshipState::Disabled,208 variable_on_chain_schema: Vec::new(),209 const_on_chain_schema: Vec::new(),210 limits: Default::default(),211 meta_update_permission: Default::default(),212 };213214 let _id = match mode {215 CollectionMode::NFT => {PalletNonfungible::init_collection(new_collection)?},216 CollectionMode::Fungible(decimal_points) => {217 // check params218 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);219 PalletFungible::init_collection(new_collection)?220 }221 CollectionMode::ReFungible => {222 PalletRefungible::init_collection(new_collection)?223 }224 };225226 Ok(())227 }228229 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.230 ///231 /// # Permissions232 ///233 /// * Collection Owner.234 ///235 /// # Arguments236 ///237 /// * collection_id: collection to destroy.238 #[weight = <SelfWeightOf<T>>::destroy_collection()]239 #[transactional]240 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {241 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);242243 let collection = <CollectionHandle<T>>::try_get(collection_id)?;244 collection.check_is_owner(&sender)?;245246 // =========247248 match collection.mode {249 CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,250 CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,251 CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,252 }253254 <NftTransferBasket<T>>::remove_prefix(collection_id, None);255 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);256 <ReFungibleTransferBasket<T>>::remove_prefix((collection_id,), None);257258 <VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);259 <NftApproveBasket<T>>::remove_prefix(collection_id, None);260 <FungibleApproveBasket<T>>::remove_prefix(collection_id, None);261 <RefungibleApproveBasket<T>>::remove_prefix((collection_id,), None);262263 Ok(())264 }265266 /// Add an address to allow list.267 ///268 /// # Permissions269 ///270 /// * Collection Owner271 /// * Collection Admin272 ///273 /// # Arguments274 ///275 /// * collection_id.276 ///277 /// * address.278 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]279 #[transactional]280 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{281282 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);283 let collection = <CollectionHandle<T>>::try_get(collection_id)?;284285 <PalletCommon<T>>::toggle_allowlist(286 &collection,287 &sender,288 &address,289 true,290 )?;291292 Ok(())293 }294295 /// Remove an address from allow list.296 ///297 /// # Permissions298 ///299 /// * Collection Owner300 /// * Collection Admin301 ///302 /// # Arguments303 ///304 /// * collection_id.305 ///306 /// * address.307 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]308 #[transactional]309 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{310311 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);312 let collection = <CollectionHandle<T>>::try_get(collection_id)?;313314 <PalletCommon<T>>::toggle_allowlist(315 &collection,316 &sender,317 &address,318 false,319 )?;320321 Ok(())322 }323324 /// Toggle between normal and allow list access for the methods with access for `Anyone`.325 ///326 /// # Permissions327 ///328 /// * Collection Owner.329 ///330 /// # Arguments331 ///332 /// * collection_id.333 ///334 /// * mode: [AccessMode]335 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]336 #[transactional]337 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult338 {339 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);340341 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;342 target_collection.check_is_owner(&sender)?;343344 target_collection.access = mode;345 target_collection.save()346 }347348 /// Allows Anyone to create tokens if:349 /// * Allow List is enabled, and350 /// * Address is added to allow list, and351 /// * This method was called with True parameter352 ///353 /// # Permissions354 /// * Collection Owner355 ///356 /// # Arguments357 ///358 /// * collection_id.359 ///360 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.361 #[weight = <SelfWeightOf<T>>::set_mint_permission()]362 #[transactional]363 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult364 {365 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);366367 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;368 target_collection.check_is_owner(&sender)?;369370 target_collection.mint_mode = mint_permission;371 target_collection.save()372 }373374 /// Change the owner of the collection.375 ///376 /// # Permissions377 ///378 /// * Collection Owner.379 ///380 /// # Arguments381 ///382 /// * collection_id.383 ///384 /// * new_owner.385 #[weight = <SelfWeightOf<T>>::change_collection_owner()]386 #[transactional]387 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {388389 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);390391 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;392 target_collection.check_is_owner(&sender)?;393394 target_collection.owner = new_owner;395 target_collection.save()396 }397398 /// Adds an admin of the Collection.399 /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.400 ///401 /// # Permissions402 ///403 /// * Collection Owner.404 /// * Collection Admin.405 ///406 /// # Arguments407 ///408 /// * collection_id: ID of the Collection to add admin for.409 ///410 /// * new_admin_id: Address of new admin to add.411 #[weight = <SelfWeightOf<T>>::add_collection_admin()]412 #[transactional]413 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {414 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);415 let collection = <CollectionHandle<T>>::try_get(collection_id)?;416417 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)418 }419420 /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.421 ///422 /// # Permissions423 ///424 /// * Collection Owner.425 /// * Collection Admin.426 ///427 /// # Arguments428 ///429 /// * collection_id: ID of the Collection to remove admin for.430 ///431 /// * account_id: Address of admin to remove.432 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]433 #[transactional]434 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {435 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);436 let collection = <CollectionHandle<T>>::try_get(collection_id)?;437438 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)439 }440441 /// # Permissions442 ///443 /// * Collection Owner444 ///445 /// # Arguments446 ///447 /// * collection_id.448 ///449 /// * new_sponsor.450 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]451 #[transactional]452 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {453 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);454455 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;456 target_collection.check_is_owner(&sender)?;457458 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);459 target_collection.save()460 }461462 /// # Permissions463 ///464 /// * Sponsor.465 ///466 /// # Arguments467 ///468 /// * collection_id.469 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]470 #[transactional]471 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {472 let sender = ensure_signed(origin)?;473474 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;475 ensure!(476 target_collection.sponsorship.pending_sponsor() == Some(&sender),477 Error::<T>::ConfirmUnsetSponsorFail478 );479480 target_collection.sponsorship = SponsorshipState::Confirmed(sender);481 target_collection.save()482 }483484 /// Switch back to pay-per-own-transaction model.485 ///486 /// # Permissions487 ///488 /// * Collection owner.489 ///490 /// # Arguments491 ///492 /// * collection_id.493 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]494 #[transactional]495 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {496 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);497498 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;499 target_collection.check_is_owner(&sender)?;500501 target_collection.sponsorship = SponsorshipState::Disabled;502 target_collection.save()503 }504505 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.506 ///507 /// # Permissions508 ///509 /// * Collection Owner.510 /// * Collection Admin.511 /// * Anyone if512 /// * Allow List is enabled, and513 /// * Address is added to allow list, and514 /// * MintPermission is enabled (see SetMintPermission method)515 ///516 /// # Arguments517 ///518 /// * collection_id: ID of the collection.519 ///520 /// * owner: Address, initial owner of the NFT.521 ///522 /// * data: Token data to store on chain.523 #[weight = <CommonWeights<T>>::create_item()]524 #[transactional]525 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {526 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);527528 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))529 }530531 /// This method creates multiple items in a collection created with CreateCollection method.532 ///533 /// # Permissions534 ///535 /// * Collection Owner.536 /// * Collection Admin.537 /// * Anyone if538 /// * Allow List is enabled, and539 /// * Address is added to allow list, and540 /// * MintPermission is enabled (see SetMintPermission method)541 ///542 /// # Arguments543 ///544 /// * collection_id: ID of the collection.545 ///546 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].547 ///548 /// * owner: Address, initial owner of the NFT.549 #[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]550 #[transactional]551 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {552 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);553 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);554555 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))556 }557558 // TODO! transaction weight559560 /// Set transfers_enabled value for particular collection561 ///562 /// # Permissions563 ///564 /// * Collection Owner.565 ///566 /// # Arguments567 ///568 /// * collection_id: ID of the collection.569 ///570 /// * value: New flag value.571 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]572 #[transactional]573 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {574 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);575 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;576 target_collection.check_is_owner(&sender)?;577578 // =========579580 target_collection.limits.transfers_enabled = Some(value);581 target_collection.save()582 }583584 /// Destroys a concrete instance of NFT.585 ///586 /// # Permissions587 ///588 /// * Collection Owner.589 /// * Collection Admin.590 /// * Current NFT Owner.591 ///592 /// # Arguments593 ///594 /// * collection_id: ID of the collection.595 ///596 /// * item_id: ID of NFT to burn.597 #[weight = <CommonWeights<T>>::burn_item()]598 #[transactional]599 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {600 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);601602 let post_info = dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;603 if value == 1 {604 <NftTransferBasket<T>>::remove(collection_id, item_id);605 <NftApproveBasket<T>>::remove(collection_id, item_id);606 }607 // Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?608 // <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());609 // <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));610 Ok(post_info)611 }612613 /// Destroys a concrete instance of NFT on behalf of the owner614 /// See also: [`approve`]615 ///616 /// # Permissions617 ///618 /// * Collection Owner.619 /// * Collection Admin.620 /// * Current NFT Owner.621 ///622 /// # Arguments623 ///624 /// * collection_id: ID of the collection.625 ///626 /// * item_id: ID of NFT to burn.627 ///628 /// * from: owner of item629 #[weight = <CommonWeights<T>>::burn_from()]630 #[transactional]631 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {632 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);633634 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))635 }636637 /// Change ownership of the token.638 ///639 /// # Permissions640 ///641 /// * Collection Owner642 /// * Collection Admin643 /// * Current NFT owner644 ///645 /// # Arguments646 ///647 /// * recipient: Address of token recipient.648 ///649 /// * collection_id.650 ///651 /// * item_id: ID of the item652 /// * Non-Fungible Mode: Required.653 /// * Fungible Mode: Ignored.654 /// * Re-Fungible Mode: Required.655 ///656 /// * value: Amount to transfer.657 /// * Non-Fungible Mode: Ignored658 /// * Fungible Mode: Must specify transferred amount659 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)660 #[weight = <CommonWeights<T>>::transfer()]661 #[transactional]662 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {663 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);664665 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))666 }667668 /// Set, change, or remove approved address to transfer the ownership of the NFT.669 ///670 /// # Permissions671 ///672 /// * Collection Owner673 /// * Collection Admin674 /// * Current NFT owner675 ///676 /// # Arguments677 ///678 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).679 ///680 /// * collection_id.681 ///682 /// * item_id: ID of the item.683 #[weight = <CommonWeights<T>>::approve()]684 #[transactional]685 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {686 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);687688 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))689 }690691 /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.692 ///693 /// # Permissions694 /// * Collection Owner695 /// * Collection Admin696 /// * Current NFT owner697 /// * Address approved by current NFT owner698 ///699 /// # Arguments700 ///701 /// * from: Address that owns token.702 ///703 /// * recipient: Address of token recipient.704 ///705 /// * collection_id.706 ///707 /// * item_id: ID of the item.708 ///709 /// * value: Amount to transfer.710 #[weight = <CommonWeights<T>>::transfer_from()]711 #[transactional]712 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {713 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);714715 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))716 }717718 /// Set off-chain data schema.719 ///720 /// # Permissions721 ///722 /// * Collection Owner723 /// * Collection Admin724 ///725 /// # Arguments726 ///727 /// * collection_id.728 ///729 /// * schema: String representing the offchain data schema.730 #[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]731 #[transactional]732 pub fn set_variable_meta_data (733 origin,734 collection_id: CollectionId,735 item_id: TokenId,736 data: Vec<u8>737 ) -> DispatchResultWithPostInfo {738 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);739740 dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))741 }742743 /// Set meta_update_permission value for particular collection744 ///745 /// # Permissions746 ///747 /// * Collection Owner.748 ///749 /// # Arguments750 ///751 /// * collection_id: ID of the collection.752 ///753 /// * value: New flag value.754 #[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]755 #[transactional]756 pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {757 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);758 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;759760 ensure!(761 target_collection.meta_update_permission != MetaUpdatePermission::None,762 <CommonError<T>>::MetadataFlagFrozen,763 );764 target_collection.check_is_owner(&sender)?;765766 target_collection.meta_update_permission = value;767768 target_collection.save()769 }770771 /// Set schema standard772 /// ImageURL773 /// Unique774 ///775 /// # Permissions776 ///777 /// * Collection Owner778 /// * Collection Admin779 ///780 /// # Arguments781 ///782 /// * collection_id.783 ///784 /// * schema: SchemaVersion: enum785 #[weight = <SelfWeightOf<T>>::set_schema_version()]786 #[transactional]787 pub fn set_schema_version(788 origin,789 collection_id: CollectionId,790 version: SchemaVersion791 ) -> DispatchResult {792 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);793 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;794 target_collection.check_is_owner_or_admin(&sender)?;795 target_collection.schema_version = version;796 target_collection.save()797 }798799 /// Set off-chain data schema.800 ///801 /// # Permissions802 ///803 /// * Collection Owner804 /// * Collection Admin805 ///806 /// # Arguments807 ///808 /// * collection_id.809 ///810 /// * schema: String representing the offchain data schema.811 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]812 #[transactional]813 pub fn set_offchain_schema(814 origin,815 collection_id: CollectionId,816 schema: Vec<u8>817 ) -> DispatchResult {818 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);819 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;820 target_collection.check_is_owner_or_admin(&sender)?;821822 // check schema limit823 ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");824825 target_collection.offchain_schema = schema;826 target_collection.save()827 }828829 /// Set const on-chain data schema.830 ///831 /// # Permissions832 ///833 /// * Collection Owner834 /// * Collection Admin835 ///836 /// # Arguments837 ///838 /// * collection_id.839 ///840 /// * schema: String representing the const on-chain data schema.841 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]842 #[transactional]843 pub fn set_const_on_chain_schema (844 origin,845 collection_id: CollectionId,846 schema: Vec<u8>847 ) -> DispatchResult {848 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);849 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;850 target_collection.check_is_owner_or_admin(&sender)?;851852 // check schema limit853 ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");854855 target_collection.const_on_chain_schema = schema;856 target_collection.save()857 }858859 /// Set variable on-chain data schema.860 ///861 /// # Permissions862 ///863 /// * Collection Owner864 /// * Collection Admin865 ///866 /// # Arguments867 ///868 /// * collection_id.869 ///870 /// * schema: String representing the variable on-chain data schema.871 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]872 #[transactional]873 pub fn set_variable_on_chain_schema (874 origin,875 collection_id: CollectionId,876 schema: Vec<u8>877 ) -> DispatchResult {878 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);879 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;880 target_collection.check_is_owner_or_admin(&sender)?;881882 // check schema limit883 ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");884885 target_collection.variable_on_chain_schema = schema;886 target_collection.save()887 }888889 #[weight = <SelfWeightOf<T>>::set_collection_limits()]890 #[transactional]891 pub fn set_collection_limits(892 origin,893 collection_id: CollectionId,894 new_limit: CollectionLimits,895 ) -> DispatchResult {896 let mut new_limit = new_limit;897 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);898 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;899 target_collection.check_is_owner(&sender)?;900 let old_limit = &target_collection.limits;901902 macro_rules! limit_default {903 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{904 $(905 if let Some($new) = $new.$field {906 let $old = $old.$field($($arg)?);907 let _ = $new;908 let _ = $old;909 $check910 } else {911 $new.$field = $old.$field912 }913 )*914 }};915 }916917 limit_default!(old_limit, new_limit,918 account_token_ownership_limit => ensure!(919 new_limit <= MAX_TOKEN_OWNERSHIP,920 <Error<T>>::CollectionLimitBoundsExceeded,921 ),922 sponsor_transfer_timeout(match target_collection.mode {923 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,924 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,925 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,926 }) => ensure!(927 new_limit <= MAX_SPONSOR_TIMEOUT,928 <Error<T>>::CollectionLimitBoundsExceeded,929 ),930 sponsored_data_size => ensure!(931 new_limit <= CUSTOM_DATA_LIMIT,932 <Error<T>>::CollectionLimitBoundsExceeded,933 ),934 token_limit => ensure!(935 old_limit >= new_limit && new_limit > 0,936 <CommonError<T>>::CollectionTokenLimitExceeded937 ),938 owner_can_transfer => ensure!(939 old_limit || !new_limit,940 <Error<T>>::OwnerPermissionsCantBeReverted,941 ),942 owner_can_destroy => ensure!(943 old_limit || !new_limit,944 <Error<T>>::OwnerPermissionsCantBeReverted,945 ),946 sponsored_data_rate_limit => {},947 transfers_enabled => {},948 );949950 target_collection.limits = new_limit;951952 target_collection.save()953 }954 }955}956957// TODO: limit returned entries?958impl<T: Config> Pallet<T> {959 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {960 <IsAdmin<T>>::iter_prefix((collection,))961 .map(|(a, _)| a)962 .collect()963 }964 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {965 <Allowlist<T>>::iter_prefix((collection,))966 .map(|(a, _)| a)967 .collect()968 }969}pallets/nft/src/sponsorship.rsdiffbeforeafterboth--- a/pallets/nft/src/sponsorship.rs
+++ b/pallets/nft/src/sponsorship.rs
@@ -1,175 +1,167 @@
use crate::{
Config, Call, CreateItemBasket, VariableMetaDataBasket, ReFungibleTransferBasket,
- FungibleTransferBasket, NftTransferBasket, CreateItemData, CollectionMode,
+ FungibleTransferBasket, NftTransferBasket, CreateItemData, CollectionMode, NftApproveBasket,
+ FungibleApproveBasket, RefungibleApproveBasket,
};
use core::marker::PhantomData;
use up_sponsorship::SponsorshipHandler;
use frame_support::{
traits::{IsSubType},
- storage::{StorageMap, StorageDoubleMap},
+ storage::{StorageMap, StorageDoubleMap, StorageNMap},
};
use nft_data_structs::{
- TokenId, CollectionId, NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
- FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+ CollectionId, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, NFT_SPONSOR_TRANSFER_TIMEOUT,
+ REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, TokenId,
};
-use pallet_common::{CollectionById};
+use pallet_common::{CollectionHandle};
-pub struct NftSponsorshipHandler<T>(PhantomData<T>);
-impl<T: Config> NftSponsorshipHandler<T> {
- pub fn withdraw_create_item(
- who: &T::AccountId,
- collection_id: &CollectionId,
- _properties: &CreateItemData,
- ) -> Option<T::AccountId> {
- let collection = CollectionById::<T>::get(collection_id)?;
+pub fn withdraw_transfer<T: Config>(
+ collection: &CollectionHandle<T>,
+ who: &T::AccountId,
+ item_id: &TokenId,
+) -> Option<()> {
+ // sponsor timeout
+ let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+ let limit = collection
+ .limits
+ .sponsor_transfer_timeout(match collection.mode {
+ CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,
+ CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+ CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+ });
- // sponsor timeout
- let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
-
- let limit = collection
- .limits
- .sponsor_transfer_timeout(match _properties {
- CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT,
- CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
- CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
- });
- if CreateItemBasket::<T>::contains_key((collection_id, &who)) {
- let last_tx_block = CreateItemBasket::<T>::get((collection_id, &who));
- let limit_time = last_tx_block + limit.into();
- if block_number <= limit_time {
- return None;
- }
+ let last_tx_block = match collection.mode {
+ CollectionMode::NFT => <NftTransferBasket<T>>::get(collection.id, item_id),
+ CollectionMode::Fungible(_) => <FungibleTransferBasket<T>>::get(collection.id, who),
+ CollectionMode::ReFungible => {
+ <ReFungibleTransferBasket<T>>::get((collection.id, item_id, who))
}
- CreateItemBasket::<T>::insert((collection_id, who.clone()), block_number);
+ };
- // check free create limit
- if collection.limits.sponsored_data_size() >= (_properties.data_size() as u32) {
- collection.sponsorship.sponsor().cloned()
- } else {
- None
+ if let Some(last_tx_block) = last_tx_block {
+ let timeout = last_tx_block + limit.into();
+ if block_number < timeout {
+ return None;
}
}
- pub fn withdraw_transfer(
- who: &T::AccountId,
- collection_id: &CollectionId,
- item_id: &TokenId,
- ) -> Option<T::AccountId> {
- let collection = CollectionById::<T>::get(collection_id)?;
+ match collection.mode {
+ CollectionMode::NFT => <NftTransferBasket<T>>::insert(collection.id, item_id, block_number),
+ CollectionMode::Fungible(_) => {
+ <FungibleTransferBasket<T>>::insert(collection.id, who, block_number)
+ }
+ CollectionMode::ReFungible => {
+ <ReFungibleTransferBasket<T>>::insert((collection.id, item_id, who), block_number)
+ }
+ };
- let mut sponsor_transfer = false;
- if collection.sponsorship.confirmed() {
- let collection_limits = collection.limits.clone();
- let collection_mode = collection.mode.clone();
+ Some(())
+}
- // sponsor timeout
- let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
- sponsor_transfer = match collection_mode {
- CollectionMode::NFT => {
- // get correct limit
- let limit =
- collection_limits.sponsor_transfer_timeout(NFT_SPONSOR_TRANSFER_TIMEOUT);
+pub fn withdraw_create_item<T: Config>(
+ collection: &CollectionHandle<T>,
+ who: &T::AccountId,
+ _properties: &CreateItemData,
+) -> Option<()> {
+ if _properties.data_size() as u32 > collection.limits.sponsored_data_size() {
+ return None;
+ }
- let mut sponsored = true;
- if NftTransferBasket::<T>::contains_key(collection_id, item_id) {
- let last_tx_block = NftTransferBasket::<T>::get(collection_id, item_id);
- let limit_time = last_tx_block + limit.into();
- if block_number <= limit_time {
- sponsored = false;
- }
- }
- if sponsored {
- NftTransferBasket::<T>::insert(collection_id, item_id, block_number);
- }
+ // sponsor timeout
+ let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+ let limit = collection
+ .limits
+ .sponsor_transfer_timeout(match _properties {
+ CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT,
+ CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+ CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+ });
- sponsored
- }
- CollectionMode::Fungible(_) => {
- // get correct limit
- let limit = collection_limits
- .sponsor_transfer_timeout(FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT);
+ if let Some(last_tx_block) = <CreateItemBasket<T>>::get((collection.id, &who)) {
+ let timeout = last_tx_block + limit.into();
+ if block_number < timeout {
+ return None;
+ }
+ }
- let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
- let mut sponsored = true;
- if FungibleTransferBasket::<T>::contains_key(collection_id, who) {
- let last_tx_block = FungibleTransferBasket::<T>::get(collection_id, who);
- let limit_time = last_tx_block + limit.into();
- if block_number <= limit_time {
- sponsored = false;
- }
- }
- if sponsored {
- FungibleTransferBasket::<T>::insert(collection_id, who, block_number);
- }
+ CreateItemBasket::<T>::insert((collection.id, who.clone()), block_number);
- sponsored
- }
- CollectionMode::ReFungible => {
- // get correct limit
- let limit = collection_limits
- .sponsor_transfer_timeout(REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT);
+ Some(())
+}
- let mut sponsored = true;
- if ReFungibleTransferBasket::<T>::contains_key(collection_id, item_id) {
- let last_tx_block =
- ReFungibleTransferBasket::<T>::get(collection_id, item_id);
- let limit_time = last_tx_block + limit.into();
- if block_number <= limit_time {
- sponsored = false;
- }
- }
- if sponsored {
- ReFungibleTransferBasket::<T>::insert(collection_id, item_id, block_number);
- }
+pub fn withdraw_set_variable_meta_data<T: Config>(
+ collection: &CollectionHandle<T>,
+ item_id: &TokenId,
+ data: &[u8],
+) -> Option<()> {
+ // Can't sponsor fungible collection, this tx will be rejected
+ // as invalid
+ if matches!(collection.mode, CollectionMode::Fungible(_)) {
+ return None;
+ }
+ if data.len() > collection.limits.sponsored_data_size() as usize {
+ return None;
+ }
- sponsored
- }
- };
- }
+ let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+ let limit = collection.limits.sponsored_data_rate_limit()?;
- if !sponsor_transfer {
- None
- } else {
- collection.sponsorship.sponsor().cloned()
+ if let Some(last_tx_block) = VariableMetaDataBasket::<T>::get(collection.id, item_id) {
+ let timeout = last_tx_block + limit.into();
+ if block_number < timeout {
+ return None;
}
}
- pub fn withdraw_set_variable_meta_data(
- collection_id: &CollectionId,
- item_id: &TokenId,
- data: &[u8],
- ) -> Option<T::AccountId> {
- let mut sponsor_metadata_changes = false;
+ <VariableMetaDataBasket<T>>::insert(collection.id, item_id, block_number);
- let collection = CollectionById::<T>::get(collection_id)?;
+ Some(())
+}
- if collection.sponsorship.confirmed() &&
- // Can't sponsor fungible collection, this tx will be rejected
- // as invalid
- !matches!(collection.mode, CollectionMode::Fungible(_)) &&
- data.len() <= collection.limits.sponsored_data_size() as usize
- {
- if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit() {
- let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+pub fn withdraw_approve<T: Config>(
+ collection: &CollectionHandle<T>,
+ who: &T::AccountId,
+ item_id: &TokenId,
+) -> Option<()> {
+ // sponsor timeout
+ let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
+ let limit = collection.limits.sponsor_approve_timeout();
- if VariableMetaDataBasket::<T>::get(collection_id, item_id)
- .map(|last_block| block_number - last_block > rate_limit.into())
- .unwrap_or(true)
- {
- sponsor_metadata_changes = true;
- VariableMetaDataBasket::<T>::insert(collection_id, item_id, block_number);
- }
- }
+ let last_tx_block = match collection.mode {
+ CollectionMode::NFT => <NftApproveBasket<T>>::get(collection.id, item_id),
+ CollectionMode::Fungible(_) => <FungibleApproveBasket<T>>::get(collection.id, who),
+ CollectionMode::ReFungible => {
+ <RefungibleApproveBasket<T>>::get((collection.id, item_id, who))
}
+ };
- if !sponsor_metadata_changes {
- None
- } else {
- collection.sponsorship.sponsor().cloned()
+ if let Some(last_tx_block) = last_tx_block {
+ let timeout = last_tx_block + limit.into();
+ if block_number < timeout {
+ return None;
}
}
+
+ match collection.mode {
+ CollectionMode::NFT => <NftApproveBasket<T>>::insert(collection.id, item_id, block_number),
+ CollectionMode::Fungible(_) => {
+ <FungibleApproveBasket<T>>::insert(collection.id, who, block_number)
+ }
+ CollectionMode::ReFungible => {
+ <RefungibleApproveBasket<T>>::insert((collection.id, item_id, who), block_number)
+ }
+ };
+
+ Some(())
}
+fn load<T: Config>(id: CollectionId) -> Option<(T::AccountId, CollectionHandle<T>)> {
+ let collection = CollectionHandle::new(id)?;
+ let sponsor = collection.sponsorship.sponsor().cloned()?;
+ Some((sponsor, collection))
+}
+
+pub struct NftSponsorshipHandler<T>(PhantomData<T>);
impl<T, C> SponsorshipHandler<T::AccountId, C> for NftSponsorshipHandler<T>
where
T: Config,
@@ -181,17 +173,39 @@
collection_id,
data,
..
- } => Self::withdraw_create_item(who, collection_id, data),
+ } => {
+ let (sponsor, collection) = load(*collection_id)?;
+ withdraw_create_item::<T>(&collection, who, data).map(|()| sponsor)
+ }
Call::transfer {
collection_id,
item_id,
..
- } => Self::withdraw_transfer(who, collection_id, item_id),
+ }
+ | Call::transfer_from {
+ collection_id,
+ item_id,
+ ..
+ } => {
+ let (sponsor, collection) = load(*collection_id)?;
+ withdraw_transfer::<T>(&collection, who, item_id).map(|()| sponsor)
+ }
+ Call::approve {
+ collection_id,
+ item_id,
+ ..
+ } => {
+ let (sponsor, collection) = load(*collection_id)?;
+ withdraw_approve::<T>(&collection, who, item_id).map(|()| sponsor)
+ }
Call::set_variable_meta_data {
collection_id,
item_id,
data,
- } => Self::withdraw_set_variable_meta_data(collection_id, item_id, data),
+ } => {
+ let (sponsor, collection) = load(*collection_id)?;
+ withdraw_set_variable_meta_data::<T>(&collection, item_id, data).map(|()| sponsor)
+ }
_ => None,
}
}
primitives/evm-mapping/src/lib.rsdiffbeforeafterboth--- a/primitives/evm-mapping/src/lib.rs
+++ b/primitives/evm-mapping/src/lib.rs
@@ -4,9 +4,9 @@
use sp_core::H160;
/// Transforms substrate addresses to ethereum (Reverse of `EvmAddressMapping`)
-/// pallet_evm doesn't have this, as it only checks if eth address
+/// pallet_evm doesn't have this, as it only checks if eth address
/// is owned by substrate via `EnsureAddressOrigin` trait
-///
+///
/// This trait implementations shouldn't conflict with used `EnsureAddressOrigin`
pub trait EvmBackwardsAddressMapping<AccountId> {
fn from_account_id(account_id: AccountId) -> H160;