difftreelog
feat(pallet) evm gas metering
in: master
1 file changed
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"]78#![cfg_attr(not(feature = "std"), no_std)]91011pub use serde::{Serialize, Deserialize};1213use core::ops::{Deref, DerefMut};14use codec::{Decode, Encode};15pub use frame_support::{16 construct_runtime, decl_event, decl_module, decl_storage, decl_error,17 dispatch::DispatchResult,18 ensure, fail, parameter_types,19 traits::{20 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,21 Randomness, IsSubType, WithdrawReasons,22 },23 weights::{24 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},25 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,26 WeightToFeePolynomial, DispatchClass,27 },28 StorageValue,29 transactional,30};3132use frame_system::{self as system, ensure_signed, ensure_root};33use sp_core::{H160, H256};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36 traits::{37 Hash, DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38 },39 transaction_validity::{40 TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41 },42 FixedPointOperand, FixedU128,43};44use sp_runtime::traits::StaticLookup;45use pallet_contracts::chain_extension::UncheckedFrom;46use pallet_ethereum::EthereumTransactionSender;47use pallet_transaction_payment::OnChargeTransaction;4849#[cfg(test)]50mod mock;5152#[cfg(test)]53mod tests;5455mod default_weights;56mod eth;5758pub use eth::NftErcSupport;59pub use eth::account::*;60use eth::erc::{ERC20Events, ERC721Events};6162pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;63pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;64pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;65pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;6667// Structs68// #region6970pub type CollectionId = u32;71pub type TokenId = u32;72pub type DecimalPoints = u8;7374#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]75#[derive(Serialize, Deserialize)]76pub enum CollectionMode {77 Invalid,78 NFT,79 // decimal points80 Fungible(DecimalPoints),81 ReFungible,82}8384impl Default for CollectionMode {85 fn default() -> Self {86 Self::Invalid87 }88}8990impl Into<u8> for CollectionMode {91 fn into(self) -> u8 {92 match self {93 CollectionMode::Invalid => 0,94 CollectionMode::NFT => 1,95 CollectionMode::Fungible(_) => 2,96 CollectionMode::ReFungible => 3,97 }98 }99}100101#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]102#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]103pub enum AccessMode {104 Normal,105 WhiteList,106}107impl Default for AccessMode {108 fn default() -> Self {109 Self::Normal110 }111}112113#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]114#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]115pub enum SchemaVersion {116 ImageURL,117 Unique,118}119impl Default for SchemaVersion {120 fn default() -> Self {121 Self::ImageURL122 }123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct Ownership<AccountId> {128 pub owner: AccountId,129 pub fraction: u128,130}131132#[derive(Encode, Decode, Debug, Clone, PartialEq)]133#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]134pub enum SponsorshipState<AccountId> {135 /// The fees are applied to the transaction sender136 Disabled,137 Unconfirmed(AccountId),138 /// Transactions are sponsored by specified account139 Confirmed(AccountId),140}141142impl<AccountId> SponsorshipState<AccountId> {143 fn sponsor(&self) -> Option<&AccountId> {144 match self {145 Self::Confirmed(sponsor) => Some(sponsor),146 _ => None,147 }148 }149150 fn pending_sponsor(&self) -> Option<&AccountId> {151 match self {152 Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),153 _ => None,154 }155 }156157 fn confirmed(&self) -> bool {158 matches!(self, Self::Confirmed(_))159 }160}161162impl<T> Default for SponsorshipState<T> {163 fn default() -> Self {164 Self::Disabled165 }166}167168#[derive(Encode, Decode, Clone, PartialEq)]169#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]170pub struct Collection<T: Config> {171 pub owner: T::AccountId,172 pub mode: CollectionMode,173 pub access: AccessMode,174 pub decimal_points: DecimalPoints,175 pub name: Vec<u16>, // 64 include null escape char176 pub description: Vec<u16>, // 256 include null escape char177 pub token_prefix: Vec<u8>, // 16 include null escape char178 pub mint_mode: bool,179 pub offchain_schema: Vec<u8>,180 pub schema_version: SchemaVersion,181 pub sponsorship: SponsorshipState<T::AccountId>,182 pub limits: CollectionLimits<T::BlockNumber>, // Collection private restrictions 183 pub variable_on_chain_schema: Vec<u8>, //184 pub const_on_chain_schema: Vec<u8>, //185}186187pub struct CollectionHandle<T: Config> {188 pub id: CollectionId,189 collection: Collection<T>,190 logs: eth::log::LogRecorder,191}192impl<T: Config> CollectionHandle<T> {193 pub fn get(id: CollectionId) -> Option<Self> {194 <CollectionById<T>>::get(id)195 .map(|collection| Self {196 id,197 collection,198 logs: eth::log::LogRecorder::default(),199 })200 }201 pub fn log(&self, log: impl evm_coder::ToLog) {202 self.logs.log(log.to_log(self.evm_address))203 }204 pub fn into_inner(self) -> Collection<T> {205 self.collection.clone()206 }207}208209impl<T: Config> Deref for CollectionHandle<T> {210 type Target = Collection<T>;211212 fn deref(&self) -> &Self::Target {213 &self.collection214 }215}216217impl<T: Config> DerefMut for CollectionHandle<T> {218 fn deref_mut(&mut self) -> &mut Self::Target {219 &mut self.collection220 }221}222223#[derive(Encode, Decode, Debug, Clone, PartialEq)]224#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]225pub struct NftItemType<AccountId> {226 pub owner: AccountId,227 pub const_data: Vec<u8>,228 pub variable_data: Vec<u8>,229}230231#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]232#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]233pub struct FungibleItemType {234 pub value: u128,235}236237#[derive(Encode, Decode, Debug, Clone, PartialEq)]238#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]239pub struct ReFungibleItemType<AccountId> {240 pub owner: Vec<Ownership<AccountId>>,241 pub const_data: Vec<u8>,242 pub variable_data: Vec<u8>,243}244245// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]246// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]247// pub struct VestingItem<AccountId, Moment> {248// pub sender: AccountId,249// pub recipient: AccountId,250// pub collection_id: CollectionId,251// pub item_id: TokenId,252// pub amount: u64,253// pub vesting_date: Moment,254// }255256#[derive(Encode, Decode, Debug, Clone, PartialEq)]257#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]258pub struct CollectionLimits<BlockNumber: Encode + Decode> {259 pub account_token_ownership_limit: u32,260 pub sponsored_data_size: u32,261 /// None - setVariableMetadata is not sponsored262 /// Some(v) - setVariableMetadata is sponsored 263 /// if there is v block between txs264 pub sponsored_data_rate_limit: Option<BlockNumber>,265 pub token_limit: u32,266267 // Timeouts for item types in passed blocks268 pub sponsor_transfer_timeout: u32,269 pub owner_can_transfer: bool,270 pub owner_can_destroy: bool,271}272273impl<BlockNumber: Encode + Decode> Default for CollectionLimits<BlockNumber> {274 fn default() -> Self {275 Self { 276 account_token_ownership_limit: 10_000_000, 277 token_limit: u32::max_value(),278 sponsored_data_size: u32::MAX, 279 sponsored_data_rate_limit: None,280 sponsor_transfer_timeout: 14400,281 owner_can_transfer: true,282 owner_can_destroy: true283 }284 }285}286287#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]288#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]289pub struct ChainLimits {290 pub collection_numbers_limit: u32,291 pub account_token_ownership_limit: u32,292 pub collections_admins_limit: u64,293 pub custom_data_limit: u32,294295 // Timeouts for item types in passed blocks296 pub nft_sponsor_transfer_timeout: u32,297 pub fungible_sponsor_transfer_timeout: u32,298 pub refungible_sponsor_transfer_timeout: u32,299300 // Schema limits301 pub offchain_schema_limit: u32,302 pub variable_on_chain_schema_limit: u32,303 pub const_on_chain_schema_limit: u32,304}305306pub trait WeightInfo {307 fn create_collection() -> Weight;308 fn destroy_collection() -> Weight;309 fn add_to_white_list() -> Weight;310 fn remove_from_white_list() -> Weight;311 fn set_public_access_mode() -> Weight;312 fn set_mint_permission() -> Weight;313 fn change_collection_owner() -> Weight;314 fn add_collection_admin() -> Weight;315 fn remove_collection_admin() -> Weight;316 fn set_collection_sponsor() -> Weight;317 fn confirm_sponsorship() -> Weight;318 fn remove_collection_sponsor() -> Weight;319 fn create_item(s: usize) -> Weight;320 fn burn_item() -> Weight;321 fn transfer() -> Weight;322 fn approve() -> Weight;323 fn transfer_from() -> Weight;324 fn set_offchain_schema() -> Weight;325 fn set_const_on_chain_schema() -> Weight;326 fn set_variable_on_chain_schema() -> Weight;327 fn set_variable_meta_data() -> Weight;328 fn enable_contract_sponsoring() -> Weight;329 fn set_schema_version() -> Weight;330 fn set_chain_limits() -> Weight;331 fn set_contract_sponsoring_rate_limit() -> Weight;332 fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;333 fn toggle_contract_white_list() -> Weight;334 fn add_to_contract_white_list() -> Weight;335 fn remove_from_contract_white_list() -> Weight;336 fn set_collection_limits() -> Weight;337}338339#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]340#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]341pub struct CreateNftData {342 pub const_data: Vec<u8>,343 pub variable_data: Vec<u8>,344}345346#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]347#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]348pub struct CreateFungibleData {349 pub value: u128,350}351352#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]353#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]354pub struct CreateReFungibleData {355 pub const_data: Vec<u8>,356 pub variable_data: Vec<u8>,357 pub pieces: u128,358}359360#[derive(Encode, Decode, Debug, Clone, PartialEq)]361#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]362pub enum CreateItemData {363 NFT(CreateNftData),364 Fungible(CreateFungibleData),365 ReFungible(CreateReFungibleData),366}367368impl CreateItemData {369 pub fn len(&self) -> usize {370 let len = match self {371 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),372 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),373 _ => 0374 };375 376 return len;377 }378}379380impl From<CreateNftData> for CreateItemData {381 fn from(item: CreateNftData) -> Self {382 CreateItemData::NFT(item)383 }384}385386impl From<CreateReFungibleData> for CreateItemData {387 fn from(item: CreateReFungibleData) -> Self {388 CreateItemData::ReFungible(item)389 }390}391392impl From<CreateFungibleData> for CreateItemData {393 fn from(item: CreateFungibleData) -> Self {394 CreateItemData::Fungible(item)395 }396}397398399decl_error! {400 /// Error for non-fungible-token module.401 pub enum Error for Module<T: Config> {402 /// Total collections bound exceeded.403 TotalCollectionsLimitExceeded,404 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.405 CollectionDecimalPointLimitExceeded, 406 /// Collection name can not be longer than 63 char.407 CollectionNameLimitExceeded, 408 /// Collection description can not be longer than 255 char.409 CollectionDescriptionLimitExceeded, 410 /// Token prefix can not be longer than 15 char.411 CollectionTokenPrefixLimitExceeded,412 /// This collection does not exist.413 CollectionNotFound,414 /// Item not exists.415 TokenNotFound,416 /// Admin not found417 AdminNotFound,418 /// Arithmetic calculation overflow.419 NumOverflow, 420 /// Account already has admin role.421 AlreadyAdmin, 422 /// You do not own this collection.423 NoPermission,424 /// This address is not set as sponsor, use setCollectionSponsor first.425 ConfirmUnsetSponsorFail,426 /// Collection is not in mint mode.427 PublicMintingNotAllowed,428 /// Sender parameter and item owner must be equal.429 MustBeTokenOwner,430 /// Item balance not enough.431 TokenValueTooLow,432 /// Size of item is too large.433 NftSizeLimitExceeded,434 /// No approve found435 ApproveNotFound,436 /// Requested value more than approved.437 TokenValueNotEnough,438 /// Only approved addresses can call this method.439 ApproveRequired,440 /// Address is not in white list.441 AddresNotInWhiteList,442 /// Number of collection admins bound exceeded.443 CollectionAdminsLimitExceeded,444 /// Owned tokens by a single address bound exceeded.445 AddressOwnershipLimitExceeded,446 /// Length of items properties must be greater than 0.447 EmptyArgument,448 /// const_data exceeded data limit.449 TokenConstDataLimitExceeded,450 /// variable_data exceeded data limit.451 TokenVariableDataLimitExceeded,452 /// Not NFT item data used to mint in NFT collection.453 NotNftDataUsedToMintNftCollectionToken,454 /// Not Fungible item data used to mint in Fungible collection.455 NotFungibleDataUsedToMintFungibleCollectionToken,456 /// Not Re Fungible item data used to mint in Re Fungible collection.457 NotReFungibleDataUsedToMintReFungibleCollectionToken,458 /// Unexpected collection type.459 UnexpectedCollectionType,460 /// Can't store metadata in fungible tokens.461 CantStoreMetadataInFungibleTokens,462 /// Collection token limit exceeded463 CollectionTokenLimitExceeded,464 /// Account token limit exceeded per collection465 AccountTokenLimitExceeded,466 /// Collection limit bounds per collection exceeded467 CollectionLimitBoundsExceeded,468 /// Tried to enable permissions which are only permitted to be disabled469 OwnerPermissionsCantBeReverted,470 /// Schema data size limit bound exceeded471 SchemaDataLimitExceeded,472 /// Maximum refungibility exceeded473 WrongRefungiblePieces,474 /// createRefungible should be called with one owner475 BadCreateRefungibleCall,476 }477}478479pub trait Config: system::Config + Sized + pallet_transaction_payment::Config + pallet_contracts::Config {480 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;481482 /// Weight information for extrinsics in this pallet.483 type WeightInfo: WeightInfo;484485 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;486 type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;487 type EvmWithdrawOrigin: pallet_evm::EnsureAddressOrigin<Self::Origin, Success = Self::AccountId>;488489 type CrossAccountId: CrossAccountId<Self::AccountId>;490 type Currency: Currency<Self::AccountId>;491 type CollectionCreationPrice: Get<<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance>;492 type TreasuryAccountId: Get<Self::AccountId>;493494 type EthereumChainId: Get<u64>;495 type EthereumTransactionSender: pallet_ethereum::EthereumTransactionSender;496}497498#[cfg(feature = "runtime-benchmarks")]499mod benchmarking;500501// #endregion502503// # Used definitions504//505// ## User control levels506//507// chain-controlled - key is uncontrolled by user508// i.e autoincrementing index509// can use non-cryptographic hash510// real - key is controlled by user511// but it is hard to generate enough colliding values, i.e owner of signed txs512// can use non-cryptographic hash513// controlled - key is completly controlled by users514// i.e maps with mutable keys515// should use cryptographic hash516//517// ## User control level downgrade reasons518//519// ?1 - chain-controlled -> controlled520// collections/tokens can be destroyed, resulting in massive holes521// ?2 - chain-controlled -> controlled522// same as ?1, but can be only added, resulting in easier exploitation523// ?3 - real -> controlled524// no confirmation required, so addresses can be easily generated525decl_storage! {526 trait Store for Module<T: Config> as Nft {527528 //#region Private members529 /// Id of next collection530 CreatedCollectionCount: u32;531 /// Used for migrations532 ChainVersion: u64;533 /// Id of last collection token534 /// Collection id (controlled?1)535 ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;536 //#endregion537538 //#region Chain limits struct539 pub ChainLimit get(fn chain_limit) config(): ChainLimits;540 //#endregion541542 //#region Bound counters543 /// Amount of collections destroyed, used for total amount tracking with544 /// CreatedCollectionCount545 DestroyedCollectionCount: u32;546 /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)547 /// Account id (real)548 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;549 //#endregion550551 //#region Basic collections552 /// Collection info553 /// Collection id (controlled?1)554 pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;555 /// List of collection admins556 /// Collection id (controlled?2)557 pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;558 /// Whitelisted collection users559 /// Collection id (controlled?2), user id (controlled?3)560 pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;561 //#endregion562563 /// How many of collection items user have564 /// Collection id (controlled?2), account id (real)565 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;566567 /// Amount of items which spender can transfer out of owners account (via transferFrom)568 /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))569 /// TODO: Off chain worker should remove from this map when token gets removed570 pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;571572 //#region Item collections573 /// Collection id (controlled?2), token id (controlled?1)574 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;575 /// Collection id (controlled?2), owner (controlled?2)576 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;577 /// Collection id (controlled?2), token id (controlled?1)578 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;579 //#endregion580581 //#region Index list582 /// Collection id (controlled?2), tokens owner (controlled?2)583 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;584 //#endregion585586 //#region Tokens transfer rate limit baskets587 /// (Collection id (controlled?2), who created (real))588 /// TODO: Off chain worker should remove from this map when collection gets removed589 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;590 /// Collection id (controlled?2), token id (controlled?2)591 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;592 /// Collection id (controlled?2), owning user (real)593 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;594 /// Collection id (controlled?2), token id (controlled?2)595 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;596 //#endregion597598 /// Variable metadata sponsoring599 /// Collection id (controlled?2), token id (controlled?2)600 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;601 602 //#region Contract Sponsorship and Ownership603 /// Contract address (real)604 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => Option<T::AccountId>;605 /// Contract address (real)606 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;607 /// (Contract address(real), caller (real))608 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;609 /// Contract address (real)610 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;611 /// Contract address (real)612 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 613 /// Contract address (real) => Whitelisted user (controlled?3)614 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(blake2_128_concat) T::AccountId => bool; 615 //#endregion616 }617 add_extra_genesis {618 build(|config: &GenesisConfig<T>| {619 // Modification of storage620 for (_num, _c) in &config.collection_id {621 <Module<T>>::init_collection(_c);622 }623624 for (_num, _c, _i) in &config.nft_item_id {625 <Module<T>>::init_nft_token(*_c, _i);626 }627628 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {629 <Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);630 }631632 for (_num, _c, _i) in &config.refungible_item_id {633 <Module<T>>::init_refungible_token(*_c, _i);634 }635 })636 }637}638639decl_event!(640 pub enum Event<T>641 where642 AccountId = <T as frame_system::Config>::AccountId,643 CrossAccountId = <T as Config>::CrossAccountId,644 {645 /// New collection was created646 /// 647 /// # Arguments648 /// 649 /// * collection_id: Globally unique identifier of newly created collection.650 /// 651 /// * mode: [CollectionMode] converted into u8.652 /// 653 /// * account_id: Collection owner.654 CollectionCreated(CollectionId, u8, AccountId),655656 /// New item was created.657 /// 658 /// # Arguments659 /// 660 /// * collection_id: Id of the collection where item was created.661 /// 662 /// * item_id: Id of an item. Unique within the collection.663 ///664 /// * recipient: Owner of newly created item 665 ItemCreated(CollectionId, TokenId, CrossAccountId),666667 /// Collection item was burned.668 /// 669 /// # Arguments670 /// 671 /// collection_id.672 /// 673 /// item_id: Identifier of burned NFT.674 ItemDestroyed(CollectionId, TokenId),675676 /// Item was transferred677 ///678 /// * collection_id: Id of collection to which item is belong679 ///680 /// * item_id: Id of an item681 ///682 /// * sender: Original owner of item683 ///684 /// * recipient: New owner of item685 ///686 /// * amount: Always 1 for NFT687 Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),688689 /// * collection_id690 ///691 /// * item_id692 ///693 /// * sender694 ///695 /// * spender696 ///697 /// * amount698 Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),699 }700);701702decl_module! {703 pub struct Module<T: Config> for enum Call 704 where 705 origin: T::Origin706 {707 fn deposit_event() = default;708 type Error = Error<T>;709710 fn on_initialize(now: T::BlockNumber) -> Weight {711 0712 }713714 /// 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.715 /// 716 /// # Permissions717 /// 718 /// * Anyone.719 /// 720 /// # Arguments721 /// 722 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.723 /// 724 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.725 /// 726 /// * token_prefix: UTF-8 string with token prefix.727 /// 728 /// * mode: [CollectionMode] collection type and type dependent data.729 // returns collection ID730 #[weight = <T as Config>::WeightInfo::create_collection()]731 #[transactional]732 pub fn create_collection(origin,733 collection_name: Vec<u16>,734 collection_description: Vec<u16>,735 token_prefix: Vec<u8>,736 mode: CollectionMode) -> DispatchResult {737738 // Anyone can create a collection739 let who = ensure_signed(origin)?;740741 // Take a (non-refundable) deposit of collection creation742 let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();743 imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(744 &T::TreasuryAccountId::get(),745 T::CollectionCreationPrice::get(),746 ));747 <T as Config>::Currency::settle(748 &who,749 imbalance,750 WithdrawReasons::TRANSFER,751 ExistenceRequirement::KeepAlive,752 ).map_err(|_| Error::<T>::NoPermission)?;753754 let decimal_points = match mode {755 CollectionMode::Fungible(points) => points,756 _ => 0757 };758759 let chain_limit = ChainLimit::get();760761 let created_count = CreatedCollectionCount::get();762 let destroyed_count = DestroyedCollectionCount::get();763764 // bound Total number of collections765 ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);766767 // check params768 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);769 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);770 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);771 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);772773 // Generate next collection ID774 let next_id = created_count775 .checked_add(1)776 .ok_or(Error::<T>::NumOverflow)?;777778 CreatedCollectionCount::put(next_id);779780 let limits = CollectionLimits {781 sponsored_data_size: chain_limit.custom_data_limit,782 ..Default::default()783 };784785 // Create new collection786 let new_collection = Collection {787 owner: who.clone(),788 name: collection_name,789 mode: mode.clone(),790 mint_mode: false,791 access: AccessMode::Normal,792 description: collection_description,793 decimal_points: decimal_points,794 token_prefix: token_prefix,795 offchain_schema: Vec::new(),796 schema_version: SchemaVersion::ImageURL,797 sponsorship: SponsorshipState::Disabled,798 variable_on_chain_schema: Vec::new(),799 const_on_chain_schema: Vec::new(),800 limits,801 };802803 // Add new collection to map804 <CollectionById<T>>::insert(next_id, new_collection);805806 // call event807 Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.into(), who));808809 Ok(())810 }811812 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.813 /// 814 /// # Permissions815 /// 816 /// * Collection Owner.817 /// 818 /// # Arguments819 /// 820 /// * collection_id: collection to destroy.821 #[weight = <T as Config>::WeightInfo::destroy_collection()]822 #[transactional]823 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {824825 let sender = ensure_signed(origin)?;826 let collection = Self::get_collection(collection_id)?;827 Self::check_owner_permissions(&collection, &sender)?;828 if !collection.limits.owner_can_destroy {829 fail!(Error::<T>::NoPermission);830 }831832 <AddressTokens<T>>::remove_prefix(collection_id);833 <Allowances<T>>::remove_prefix(collection_id);834 <Balance<T>>::remove_prefix(collection_id);835 <ItemListIndex>::remove(collection_id);836 <AdminList<T>>::remove(collection_id);837 <CollectionById<T>>::remove(collection_id);838 <WhiteList<T>>::remove_prefix(collection_id);839840 <NftItemList<T>>::remove_prefix(collection_id);841 <FungibleItemList<T>>::remove_prefix(collection_id);842 <ReFungibleItemList<T>>::remove_prefix(collection_id);843844 <NftTransferBasket<T>>::remove_prefix(collection_id);845 <FungibleTransferBasket<T>>::remove_prefix(collection_id);846 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);847848 <VariableMetaDataBasket<T>>::remove_prefix(collection_id);849850 DestroyedCollectionCount::put(DestroyedCollectionCount::get()851 .checked_add(1)852 .ok_or(Error::<T>::NumOverflow)?);853854 Ok(())855 }856857 /// Add an address to white list.858 /// 859 /// # Permissions860 /// 861 /// * Collection Owner862 /// * Collection Admin863 /// 864 /// # Arguments865 /// 866 /// * collection_id.867 /// 868 /// * address.869 #[weight = <T as Config>::WeightInfo::add_to_white_list()]870 #[transactional]871 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{872873 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);874 let collection = Self::get_collection(collection_id)?;875876 Self::toggle_white_list_internal(877 &sender,878 &collection,879 &address,880 true,881 )?;882883 Ok(())884 }885886 /// Remove an address from white list.887 /// 888 /// # Permissions889 /// 890 /// * Collection Owner891 /// * Collection Admin892 /// 893 /// # Arguments894 /// 895 /// * collection_id.896 /// 897 /// * address.898 #[weight = <T as Config>::WeightInfo::remove_from_white_list()]899 #[transactional]900 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{901902 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);903 let collection = Self::get_collection(collection_id)?;904905 Self::toggle_white_list_internal(906 &sender,907 &collection,908 &address,909 false,910 )?;911912 Ok(())913 }914915 /// Toggle between normal and white list access for the methods with access for `Anyone`.916 /// 917 /// # Permissions918 /// 919 /// * Collection Owner.920 /// 921 /// # Arguments922 /// 923 /// * collection_id.924 /// 925 /// * mode: [AccessMode]926 #[weight = <T as Config>::WeightInfo::set_public_access_mode()]927 #[transactional]928 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult929 {930 let sender = ensure_signed(origin)?;931932 let mut target_collection = Self::get_collection(collection_id)?;933 Self::check_owner_permissions(&target_collection, &sender)?;934 target_collection.access = mode;935 Self::save_collection(target_collection);936937 Ok(())938 }939940 /// Allows Anyone to create tokens if:941 /// * White List is enabled, and942 /// * Address is added to white list, and943 /// * This method was called with True parameter944 /// 945 /// # Permissions946 /// * Collection Owner947 ///948 /// # Arguments949 /// 950 /// * collection_id.951 /// 952 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.953 #[weight = <T as Config>::WeightInfo::set_mint_permission()]954 #[transactional]955 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult956 {957 let sender = ensure_signed(origin)?;958959 let mut target_collection = Self::get_collection(collection_id)?;960 Self::check_owner_permissions(&target_collection, &sender)?;961 target_collection.mint_mode = mint_permission;962 Self::save_collection(target_collection);963964 Ok(())965 }966967 /// Change the owner of the collection.968 /// 969 /// # Permissions970 /// 971 /// * Collection Owner.972 /// 973 /// # Arguments974 /// 975 /// * collection_id.976 /// 977 /// * new_owner.978 #[weight = <T as Config>::WeightInfo::change_collection_owner()]979 #[transactional]980 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {981982 let sender = ensure_signed(origin)?;983 let mut target_collection = Self::get_collection(collection_id)?;984 Self::check_owner_permissions(&target_collection, &sender)?;985 target_collection.owner = new_owner;986 Self::save_collection(target_collection);987988 Ok(())989 }990991 /// Adds an admin of the Collection.992 /// 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. 993 /// 994 /// # Permissions995 /// 996 /// * Collection Owner.997 /// * Collection Admin.998 /// 999 /// # Arguments1000 /// 1001 /// * collection_id: ID of the Collection to add admin for.1002 /// 1003 /// * new_admin_id: Address of new admin to add.1004 #[weight = <T as Config>::WeightInfo::add_collection_admin()]1005 #[transactional]1006 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {1007 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1008 let collection = Self::get_collection(collection_id)?;1009 Self::check_owner_or_admin_permissions(&collection, &sender)?;1010 let mut admin_arr = <AdminList<T>>::get(collection_id);10111012 match admin_arr.binary_search(&new_admin_id) {1013 Ok(_) => {},1014 Err(idx) => {1015 let limits = ChainLimit::get();1016 ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);1017 admin_arr.insert(idx, new_admin_id);1018 <AdminList<T>>::insert(collection_id, admin_arr);1019 }1020 }1021 Ok(())1022 }10231024 /// 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.1025 ///1026 /// # Permissions1027 /// 1028 /// * Collection Owner.1029 /// * Collection Admin.1030 /// 1031 /// # Arguments1032 /// 1033 /// * collection_id: ID of the Collection to remove admin for.1034 /// 1035 /// * account_id: Address of admin to remove.1036 #[weight = <T as Config>::WeightInfo::remove_collection_admin()]1037 #[transactional]1038 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {1039 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1040 let collection = Self::get_collection(collection_id)?;1041 Self::check_owner_or_admin_permissions(&collection, &sender)?;1042 let mut admin_arr = <AdminList<T>>::get(collection_id);10431044 match admin_arr.binary_search(&account_id) {1045 Ok(idx) => {1046 admin_arr.remove(idx);1047 <AdminList<T>>::insert(collection_id, admin_arr);1048 },1049 Err(_) => {}1050 }1051 Ok(())1052 }10531054 /// # Permissions1055 /// 1056 /// * Collection Owner1057 /// 1058 /// # Arguments1059 /// 1060 /// * collection_id.1061 /// 1062 /// * new_sponsor.1063 #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]1064 #[transactional]1065 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {1066 let sender = ensure_signed(origin)?;1067 let mut target_collection = Self::get_collection(collection_id)?;1068 Self::check_owner_permissions(&target_collection, &sender)?;10691070 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);1071 Self::save_collection(target_collection);10721073 Ok(())1074 }10751076 /// # Permissions1077 /// 1078 /// * Sponsor.1079 /// 1080 /// # Arguments1081 /// 1082 /// * collection_id.1083 #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]1084 #[transactional]1085 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {1086 let sender = ensure_signed(origin)?;10871088 let mut target_collection = Self::get_collection(collection_id)?;1089 ensure!(1090 target_collection.sponsorship.pending_sponsor() == Some(&sender),1091 Error::<T>::ConfirmUnsetSponsorFail1092 );10931094 target_collection.sponsorship = SponsorshipState::Confirmed(sender);1095 Self::save_collection(target_collection);10961097 Ok(())1098 }10991100 /// Switch back to pay-per-own-transaction model.1101 ///1102 /// # Permissions1103 ///1104 /// * Collection owner.1105 /// 1106 /// # Arguments1107 /// 1108 /// * collection_id.1109 #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]1110 #[transactional]1111 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {1112 let sender = ensure_signed(origin)?;11131114 let mut target_collection = Self::get_collection(collection_id)?;1115 Self::check_owner_permissions(&target_collection, &sender)?;11161117 target_collection.sponsorship = SponsorshipState::Disabled;1118 Self::save_collection(target_collection);11191120 Ok(())1121 }11221123 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.1124 /// 1125 /// # Permissions1126 /// 1127 /// * Collection Owner.1128 /// * Collection Admin.1129 /// * Anyone if1130 /// * White List is enabled, and1131 /// * Address is added to white list, and1132 /// * MintPermission is enabled (see SetMintPermission method)1133 /// 1134 /// # Arguments1135 /// 1136 /// * collection_id: ID of the collection.1137 /// 1138 /// * owner: Address, initial owner of the NFT.1139 ///1140 /// * data: Token data to store on chain.1141 // #[weight =1142 // (130_000_000 as Weight)1143 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))1144 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))1145 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]11461147 #[weight = <T as Config>::WeightInfo::create_item(data.len())]1148 #[transactional]1149 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {1150 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1151 let collection = Self::get_collection(collection_id)?;11521153 Self::create_item_internal(&sender, &collection, &owner, data)?;11541155 Self::submit_logs(collection)?;1156 Ok(())1157 }11581159 /// This method creates multiple items in a collection created with CreateCollection method.1160 /// 1161 /// # Permissions1162 /// 1163 /// * Collection Owner.1164 /// * Collection Admin.1165 /// * Anyone if1166 /// * White List is enabled, and1167 /// * Address is added to white list, and1168 /// * MintPermission is enabled (see SetMintPermission method)1169 /// 1170 /// # Arguments1171 /// 1172 /// * collection_id: ID of the collection.1173 /// 1174 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].1175 /// 1176 /// * owner: Address, initial owner of the NFT.1177 #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()1178 .map(|data| { data.len() })1179 .sum())]1180 #[transactional]1181 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {11821183 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);1184 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1185 let collection = Self::get_collection(collection_id)?;11861187 Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;11881189 Self::submit_logs(collection)?;1190 Ok(())1191 }11921193 /// Destroys a concrete instance of NFT.1194 /// 1195 /// # Permissions1196 /// 1197 /// * Collection Owner.1198 /// * Collection Admin.1199 /// * Current NFT Owner.1200 /// 1201 /// # Arguments1202 /// 1203 /// * collection_id: ID of the collection.1204 /// 1205 /// * item_id: ID of NFT to burn.1206 #[weight = <T as Config>::WeightInfo::burn_item()]1207 #[transactional]1208 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {12091210 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1211 let target_collection = Self::get_collection(collection_id)?;12121213 Self::burn_item_internal(&sender, &target_collection, item_id, value)?;12141215 Self::submit_logs(target_collection)?;1216 Ok(())1217 }12181219 /// Change ownership of the token.1220 /// 1221 /// # Permissions1222 /// 1223 /// * Collection Owner1224 /// * Collection Admin1225 /// * Current NFT owner1226 ///1227 /// # Arguments1228 /// 1229 /// * recipient: Address of token recipient.1230 /// 1231 /// * collection_id.1232 /// 1233 /// * item_id: ID of the item1234 /// * Non-Fungible Mode: Required.1235 /// * Fungible Mode: Ignored.1236 /// * Re-Fungible Mode: Required.1237 /// 1238 /// * value: Amount to transfer.1239 /// * Non-Fungible Mode: Ignored1240 /// * Fungible Mode: Must specify transferred amount1241 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1242 #[weight = <T as Config>::WeightInfo::transfer()]1243 #[transactional]1244 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {1245 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1246 let collection = Self::get_collection(collection_id)?;12471248 Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;12491250 Self::submit_logs(collection)?;1251 Ok(())1252 }12531254 /// Set, change, or remove approved address to transfer the ownership of the NFT.1255 /// 1256 /// # Permissions1257 /// 1258 /// * Collection Owner1259 /// * Collection Admin1260 /// * Current NFT owner1261 /// 1262 /// # Arguments1263 /// 1264 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1265 /// 1266 /// * collection_id.1267 /// 1268 /// * item_id: ID of the item.1269 #[weight = <T as Config>::WeightInfo::approve()]1270 #[transactional]1271 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1272 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1273 let collection = Self::get_collection(collection_id)?;12741275 Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;12761277 Self::submit_logs(collection)?;1278 Ok(())1279 }1280 1281 /// 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.1282 /// 1283 /// # Permissions1284 /// * Collection Owner1285 /// * Collection Admin1286 /// * Current NFT owner1287 /// * Address approved by current NFT owner1288 /// 1289 /// # Arguments1290 /// 1291 /// * from: Address that owns token.1292 /// 1293 /// * recipient: Address of token recipient.1294 /// 1295 /// * collection_id.1296 /// 1297 /// * item_id: ID of the item.1298 /// 1299 /// * value: Amount to transfer.1300 #[weight = <T as Config>::WeightInfo::transfer_from()]1301 #[transactional]1302 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1303 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1304 let collection = Self::get_collection(collection_id)?;13051306 Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;13071308 Self::submit_logs(collection)?;1309 Ok(())1310 }1311 // #[weight = 0]1312 // // let no_perm_mes = "You do not have permissions to modify this collection";1313 // // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1314 // // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1315 // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);13161317 // // // on_nft_received call13181319 // // Self::transfer(origin, collection_id, item_id, new_owner)?;13201321 // Ok(())1322 // }13231324 /// Set off-chain data schema.1325 /// 1326 /// # Permissions1327 /// 1328 /// * Collection Owner1329 /// * Collection Admin1330 /// 1331 /// # Arguments1332 /// 1333 /// * collection_id.1334 /// 1335 /// * schema: String representing the offchain data schema.1336 #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1337 #[transactional]1338 pub fn set_variable_meta_data (1339 origin,1340 collection_id: CollectionId,1341 item_id: TokenId,1342 data: Vec<u8>1343 ) -> DispatchResult {1344 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1345 1346 let collection = Self::get_collection(collection_id)?;13471348 Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;13491350 Ok(())1351 }1352 1353 /// Set schema standard1354 /// ImageURL1355 /// Unique1356 /// 1357 /// # Permissions1358 /// 1359 /// * Collection Owner1360 /// * Collection Admin1361 /// 1362 /// # Arguments1363 /// 1364 /// * collection_id.1365 /// 1366 /// * schema: SchemaVersion: enum1367 #[weight = <T as Config>::WeightInfo::set_schema_version()]1368 #[transactional]1369 pub fn set_schema_version(1370 origin,1371 collection_id: CollectionId,1372 version: SchemaVersion1373 ) -> DispatchResult {1374 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1375 let mut target_collection = Self::get_collection(collection_id)?;1376 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1377 target_collection.schema_version = version;1378 Self::save_collection(target_collection);13791380 Ok(())1381 }13821383 /// Set off-chain data schema.1384 /// 1385 /// # Permissions1386 /// 1387 /// * Collection Owner1388 /// * Collection Admin1389 /// 1390 /// # Arguments1391 /// 1392 /// * collection_id.1393 /// 1394 /// * schema: String representing the offchain data schema.1395 #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1396 #[transactional]1397 pub fn set_offchain_schema(1398 origin,1399 collection_id: CollectionId,1400 schema: Vec<u8>1401 ) -> DispatchResult {1402 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1403 let mut target_collection = Self::get_collection(collection_id)?;1404 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;14051406 // check schema limit1407 ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");14081409 target_collection.offchain_schema = schema;1410 Self::save_collection(target_collection);14111412 Ok(())1413 }14141415 /// Set const on-chain data schema.1416 /// 1417 /// # Permissions1418 /// 1419 /// * Collection Owner1420 /// * Collection Admin1421 /// 1422 /// # Arguments1423 /// 1424 /// * collection_id.1425 /// 1426 /// * schema: String representing the const on-chain data schema.1427 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1428 #[transactional]1429 pub fn set_const_on_chain_schema (1430 origin,1431 collection_id: CollectionId,1432 schema: Vec<u8>1433 ) -> DispatchResult {1434 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1435 let mut target_collection = Self::get_collection(collection_id)?;1436 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;14371438 // check schema limit1439 ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");14401441 target_collection.const_on_chain_schema = schema;1442 Self::save_collection(target_collection);14431444 Ok(())1445 }14461447 /// Set variable on-chain data schema.1448 /// 1449 /// # Permissions1450 /// 1451 /// * Collection Owner1452 /// * Collection Admin1453 /// 1454 /// # Arguments1455 /// 1456 /// * collection_id.1457 /// 1458 /// * schema: String representing the variable on-chain data schema.1459 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1460 #[transactional]1461 pub fn set_variable_on_chain_schema (1462 origin,1463 collection_id: CollectionId,1464 schema: Vec<u8>1465 ) -> DispatchResult {1466 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1467 let mut target_collection = Self::get_collection(collection_id)?;1468 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;14691470 // check schema limit1471 ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");14721473 target_collection.variable_on_chain_schema = schema;1474 Self::save_collection(target_collection);14751476 Ok(())1477 }14781479 // Sudo permissions function1480 #[weight = <T as Config>::WeightInfo::set_chain_limits()]1481 #[transactional]1482 pub fn set_chain_limits(1483 origin,1484 limits: ChainLimits1485 ) -> DispatchResult {14861487 #[cfg(not(feature = "runtime-benchmarks"))]1488 ensure_root(origin)?;14891490 <ChainLimit>::put(limits);1491 Ok(())1492 }14931494 /// Enable smart contract self-sponsoring.1495 /// 1496 /// # Permissions1497 /// 1498 /// * Contract Owner1499 /// 1500 /// # Arguments1501 /// 1502 /// * contract address1503 /// * enable flag1504 /// 1505 #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1506 #[transactional]1507 pub fn enable_contract_sponsoring(1508 origin,1509 contract_address: T::AccountId,1510 enable: bool1511 ) -> DispatchResult {15121513 let sender = ensure_signed(origin)?;15141515 #[cfg(feature = "runtime-benchmarks")]1516 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15171518 Self::ensure_contract_owned(sender, &contract_address)?;15191520 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1521 Ok(())1522 }15231524 /// Set the rate limit for contract sponsoring to specified number of blocks.1525 /// 1526 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1527 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1528 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1529 /// from contract endowment if there are at least B blocks between such transactions. 1530 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1531 /// 1532 /// # Permissions1533 /// 1534 /// * Contract Owner1535 /// 1536 /// # Arguments1537 /// 1538 /// -`contract_address`: Address of the contract to sponsor1539 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1540 /// 1541 #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1542 #[transactional]1543 pub fn set_contract_sponsoring_rate_limit(1544 origin,1545 contract_address: T::AccountId,1546 rate_limit: T::BlockNumber1547 ) -> DispatchResult {1548 let sender = ensure_signed(origin)?;15491550 #[cfg(feature = "runtime-benchmarks")]1551 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15521553 Self::ensure_contract_owned(sender, &contract_address)?;1554 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1555 Ok(())1556 }15571558 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1559 /// 1560 /// # Permissions1561 /// 1562 /// * Address that deployed smart contract.1563 /// 1564 /// # Arguments1565 /// 1566 /// -`contract_address`: Address of the contract.1567 /// 1568 /// - `enable`: . 1569 #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1570 #[transactional]1571 pub fn toggle_contract_white_list(1572 origin,1573 contract_address: T::AccountId,1574 enable: bool1575 ) -> DispatchResult {1576 let sender = ensure_signed(origin)?;15771578 #[cfg(feature = "runtime-benchmarks")]1579 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15801581 Self::ensure_contract_owned(sender, &contract_address)?;1582 if enable {1583 <ContractWhiteListEnabled<T>>::insert(contract_address, true);1584 } else {1585 <ContractWhiteListEnabled<T>>::remove(contract_address);1586 }1587 Ok(())1588 }1589 1590 /// Add an address to smart contract white list.1591 /// 1592 /// # Permissions1593 /// 1594 /// * Address that deployed smart contract.1595 /// 1596 /// # Arguments1597 /// 1598 /// -`contract_address`: Address of the contract.1599 ///1600 /// -`account_address`: Address to add.1601 #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1602 #[transactional]1603 pub fn add_to_contract_white_list(1604 origin,1605 contract_address: T::AccountId,1606 account_address: T::AccountId1607 ) -> DispatchResult {1608 let sender = ensure_signed(origin)?;16091610 #[cfg(feature = "runtime-benchmarks")]1611 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1612 1613 Self::ensure_contract_owned(sender, &contract_address)?; 1614 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1615 Ok(())1616 }16171618 /// Remove an address from smart contract white list.1619 /// 1620 /// # Permissions1621 /// 1622 /// * Address that deployed smart contract.1623 /// 1624 /// # Arguments1625 /// 1626 /// -`contract_address`: Address of the contract.1627 ///1628 /// -`account_address`: Address to remove.1629 #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1630 #[transactional]1631 pub fn remove_from_contract_white_list(1632 origin,1633 contract_address: T::AccountId,1634 account_address: T::AccountId1635 ) -> DispatchResult {1636 let sender = ensure_signed(origin)?;16371638 #[cfg(feature = "runtime-benchmarks")]1639 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());16401641 Self::ensure_contract_owned(sender, &contract_address)?;1642 <ContractWhiteList<T>>::remove(contract_address, account_address);1643 Ok(())1644 }16451646 #[weight = <T as Config>::WeightInfo::set_collection_limits()]1647 #[transactional]1648 pub fn set_collection_limits(1649 origin,1650 collection_id: u32,1651 new_limits: CollectionLimits<T::BlockNumber>,1652 ) -> DispatchResult {1653 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1654 let mut target_collection = Self::get_collection(collection_id)?;1655 Self::check_owner_permissions(&target_collection, &sender.as_sub())?;1656 let old_limits = &target_collection.limits;1657 let chain_limits = ChainLimit::get();16581659 // collection bounds1660 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1661 new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && 1662 new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1663 Error::<T>::CollectionLimitBoundsExceeded);16641665 // token_limit check prev1666 ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1667 ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);16681669 ensure!(1670 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1671 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1672 Error::<T>::OwnerPermissionsCantBeReverted,1673 );16741675 target_collection.limits = new_limits;1676 Self::save_collection(target_collection);16771678 Ok(())1679 } 1680 }1681}16821683impl<T: Config> Module<T> {1684 pub fn create_item_internal(sender: &T::CrossAccountId, collection: &CollectionHandle<T>, owner: &T::CrossAccountId, data: CreateItemData) -> DispatchResult {1685 Self::can_create_items_in_collection(&collection, &sender, &owner, 1)?;1686 Self::validate_create_item_args(&collection, &data)?;1687 Self::create_item_no_validation(&collection, owner, data)?;16881689 Ok(())1690 }16911692 pub fn transfer_internal(sender: &T::CrossAccountId, recipient: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId, value: u128) -> DispatchResult {1693 // Limits check1694 Self::is_correct_transfer(target_collection, &recipient)?;16951696 // Transfer permissions check1697 ensure!(Self::is_item_owner(&sender, target_collection, item_id) ||1698 Self::is_owner_or_admin_permissions(target_collection, &sender),1699 Error::<T>::NoPermission);17001701 if target_collection.access == AccessMode::WhiteList {1702 Self::check_white_list(target_collection, &sender)?;1703 Self::check_white_list(target_collection, &recipient)?;1704 }17051706 match target_collection.mode1707 {1708 CollectionMode::NFT => Self::transfer_nft(target_collection, item_id, sender.clone(), recipient.clone())?,1709 CollectionMode::Fungible(_) => Self::transfer_fungible(target_collection, value, &sender, &recipient)?,1710 CollectionMode::ReFungible => Self::transfer_refungible(target_collection, item_id, value, sender.clone(), recipient.clone())?,1711 _ => ()1712 };17131714 Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, sender.clone(), recipient.clone(), value));17151716 Ok(())1717 }17181719 pub fn approve_internal(1720 sender: &T::CrossAccountId,1721 spender: &T::CrossAccountId,1722 collection: &CollectionHandle<T>,1723 item_id: TokenId,1724 amount: u1281725 ) -> DispatchResult {1726 Self::token_exists(&collection, item_id)?;17271728 // Transfer permissions check1729 let bypasses_limits = collection.limits.owner_can_transfer &&1730 Self::is_owner_or_admin_permissions(1731 &collection,1732 &sender,1733 );17341735 let allowance_limit = if bypasses_limits {1736 None1737 } else if let Some(amount) = Self::owned_amount(1738 &sender,1739 &collection,1740 item_id,1741 ) {1742 Some(amount)1743 } else {1744 fail!(Error::<T>::NoPermission);1745 };17461747 if collection.access == AccessMode::WhiteList {1748 Self::check_white_list(&collection, &sender)?;1749 Self::check_white_list(&collection, &spender)?;1750 }17511752 let allowance: u128 = amount1753 .checked_add(<Allowances<T>>::get(collection.id, (item_id, sender.as_sub(), spender.as_sub())))1754 .ok_or(Error::<T>::NumOverflow)?;1755 if let Some(limit) = allowance_limit {1756 ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1757 }1758 <Allowances<T>>::insert(collection.id, (item_id, sender.as_sub(), spender.as_sub()), allowance);17591760 if matches!(collection.mode, CollectionMode::NFT) {1761 // TODO: NFT: only one owner may exist for token in ERC7211762 collection.log(ERC721Events::Approval {1763 owner: *sender.as_eth(),1764 approved: *spender.as_eth(),1765 token_id: item_id.into(),1766 });1767 }17681769 if matches!(collection.mode, CollectionMode::Fungible(_)) {1770 // TODO: NFT: only one owner may exist for token in ERC201771 collection.log(ERC20Events::Approval {1772 owner: *sender.as_eth(),1773 spender: *spender.as_eth(),1774 value: allowance.into()1775 });1776 }17771778 Self::deposit_event(RawEvent::Approved(collection.id, item_id, sender.clone(), spender.clone(), allowance));1779 Ok(())1780 }17811782 pub fn transfer_from_internal(1783 sender: &T::CrossAccountId,1784 from: &T::CrossAccountId,1785 recipient: &T::CrossAccountId,1786 collection: &CollectionHandle<T>,1787 item_id: TokenId,1788 amount: u128,1789 ) -> DispatchResult {1790 // Check approval1791 let approval: u128 = <Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));17921793 // Limits check1794 Self::is_correct_transfer(&collection, &recipient)?;17951796 // Transfer permissions check1797 ensure!(1798 approval >= amount || 1799 (1800 collection.limits.owner_can_transfer &&1801 Self::is_owner_or_admin_permissions(&collection, &sender)1802 ),1803 Error::<T>::NoPermission1804 );18051806 if collection.access == AccessMode::WhiteList {1807 Self::check_white_list(&collection, &sender)?;1808 Self::check_white_list(&collection, &recipient)?;1809 }18101811 // Reduce approval by transferred amount or remove if remaining approval drops to 01812 let allowance = approval.saturating_sub(amount);1813 if allowance > 0 {1814 <Allowances<T>>::insert(collection.id, (item_id, from.as_sub(), sender.as_sub()), allowance);1815 } else {1816 <Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1817 }18181819 match collection.mode {1820 CollectionMode::NFT => {1821 Self::transfer_nft(&collection, item_id, from.clone(), recipient.clone())?1822 }1823 CollectionMode::Fungible(_) => {1824 Self::transfer_fungible(&collection, amount, &from, &recipient)?1825 }1826 CollectionMode::ReFungible => {1827 Self::transfer_refungible(&collection, item_id, amount, from.clone(), recipient.clone())?1828 }1829 _ => ()1830 };18311832 if matches!(collection.mode, CollectionMode::Fungible(_)) {1833 collection.log(ERC20Events::Approval {1834 owner: *from.as_eth(),1835 spender: *sender.as_eth(),1836 value: allowance.into()1837 });1838 }18391840 Ok(())1841 }18421843 pub fn set_variable_meta_data_internal(1844 sender: &T::CrossAccountId,1845 collection: &CollectionHandle<T>, 1846 item_id: TokenId,1847 data: Vec<u8>,1848 ) -> DispatchResult {1849 Self::token_exists(&collection, item_id)?;18501851 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);18521853 // Modify permissions check1854 ensure!(Self::is_item_owner(&sender, &collection, item_id) ||1855 Self::is_owner_or_admin_permissions(&collection, &sender),1856 Error::<T>::NoPermission);18571858 match collection.mode1859 {1860 CollectionMode::NFT => Self::set_nft_variable_data(&collection, item_id, data)?,1861 CollectionMode::ReFungible => Self::set_re_fungible_variable_data(&collection, item_id, data)?,1862 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1863 _ => fail!(Error::<T>::UnexpectedCollectionType)1864 };18651866 Ok(())1867 }18681869 pub fn create_multiple_items_internal(1870 sender: &T::CrossAccountId,1871 collection: &CollectionHandle<T>,1872 owner: &T::CrossAccountId,1873 items_data: Vec<CreateItemData>,1874 ) -> DispatchResult {1875 Self::can_create_items_in_collection(&collection, &sender, &owner, items_data.len() as u32)?;18761877 for data in &items_data {1878 Self::validate_create_item_args(&collection, data)?;1879 }1880 for data in &items_data {1881 Self::create_item_no_validation(&collection, owner, data.clone())?;1882 }18831884 Ok(())1885 }18861887 pub fn burn_item_internal(1888 sender: &T::CrossAccountId,1889 collection: &CollectionHandle<T>,1890 item_id: TokenId,1891 value: u128,1892 ) -> DispatchResult {1893 ensure!(1894 Self::is_item_owner(&sender, &collection, item_id) ||1895 (1896 collection.limits.owner_can_transfer &&1897 Self::is_owner_or_admin_permissions(&collection, &sender)1898 ),1899 Error::<T>::NoPermission1900 );19011902 if collection.access == AccessMode::WhiteList {1903 Self::check_white_list(&collection, &sender)?;1904 }19051906 match collection.mode1907 {1908 CollectionMode::NFT => Self::burn_nft_item(&collection, item_id)?,1909 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, &collection, value)?,1910 CollectionMode::ReFungible => Self::burn_refungible_item(&collection, item_id, &sender)?,1911 _ => ()1912 };19131914 Ok(())1915 }19161917 pub fn toggle_white_list_internal(1918 sender: &T::CrossAccountId,1919 collection: &CollectionHandle<T>,1920 address: &T::CrossAccountId,1921 whitelisted: bool,1922 ) -> DispatchResult {1923 Self::check_owner_or_admin_permissions(&collection, &sender)?;19241925 if whitelisted {1926 <WhiteList<T>>::insert(collection.id, address.as_sub(), true);1927 } else {1928 <WhiteList<T>>::remove(collection.id, address.as_sub());1929 }19301931 Ok(())1932 }19331934 fn is_correct_transfer(collection: &CollectionHandle<T>, recipient: &T::CrossAccountId) -> DispatchResult {1935 let collection_id = collection.id;19361937 // check token limit and account token limit1938 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1939 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1940 1941 Ok(())1942 }19431944 fn can_create_items_in_collection(collection: &CollectionHandle<T>, sender: &T::CrossAccountId, owner: &T::CrossAccountId, amount: u32) -> DispatchResult {1945 let collection_id = collection.id;19461947 // check token limit and account token limit1948 let total_items: u32 = ItemListIndex::get(collection_id)1949 .checked_add(amount)1950 .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1951 let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len() as u32)1952 .checked_add(amount)1953 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1954 ensure!(collection.limits.token_limit >= total_items, Error::<T>::CollectionTokenLimitExceeded);1955 ensure!(collection.limits.account_token_ownership_limit >= account_items, Error::<T>::AccountTokenLimitExceeded);19561957 if !Self::is_owner_or_admin_permissions(collection, &sender) {1958 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1959 Self::check_white_list(collection, owner)?;1960 Self::check_white_list(collection, sender)?;1961 }19621963 Ok(())1964 }19651966 fn validate_create_item_args(target_collection: &CollectionHandle<T>, data: &CreateItemData) -> DispatchResult {1967 match target_collection.mode1968 {1969 CollectionMode::NFT => {1970 if let CreateItemData::NFT(data) = data {1971 // check sizes1972 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1973 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1974 } else {1975 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1976 }1977 },1978 CollectionMode::Fungible(_) => {1979 if let CreateItemData::Fungible(_) = data {1980 } else {1981 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1982 }1983 },1984 CollectionMode::ReFungible => {1985 if let CreateItemData::ReFungible(data) = data {19861987 // check sizes1988 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1989 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);19901991 // Check refungibility limits1992 ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1993 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1994 } else {1995 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1996 }1997 },1998 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1999 };20002001 Ok(())2002 }20032004 fn create_item_no_validation(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, data: CreateItemData) -> DispatchResult {2005 match data2006 {2007 CreateItemData::NFT(data) => {2008 let item = NftItemType {2009 owner: owner.clone(),2010 const_data: data.const_data,2011 variable_data: data.variable_data2012 };20132014 Self::add_nft_item(collection, item)?;2015 },2016 CreateItemData::Fungible(data) => {2017 Self::add_fungible_item(collection, &owner, data.value)?;2018 },2019 CreateItemData::ReFungible(data) => {2020 let mut owner_list = Vec::new();2021 owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});20222023 let item = ReFungibleItemType {2024 owner: owner_list,2025 const_data: data.const_data,2026 variable_data: data.variable_data2027 };20282029 Self::add_refungible_item(collection, item)?;2030 }2031 };20322033 Ok(())2034 }20352036 fn add_fungible_item(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, value: u128) -> DispatchResult {2037 let collection_id = collection.id;20382039 // Does new owner already have an account?2040 let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;20412042 // Mint 2043 let item = FungibleItemType {2044 value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,2045 };2046 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);20472048 // Update balance2049 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2050 .checked_add(value)2051 .ok_or(Error::<T>::NumOverflow)?;2052 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);20532054 Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));2055 Ok(())2056 }20572058 fn add_refungible_item(collection: &CollectionHandle<T>, item: ReFungibleItemType<T::CrossAccountId>) -> DispatchResult {2059 let collection_id = collection.id;20602061 let current_index = <ItemListIndex>::get(collection_id)2062 .checked_add(1)2063 .ok_or(Error::<T>::NumOverflow)?;2064 let itemcopy = item.clone();20652066 ensure!(2067 item.owner.len() == 1,2068 Error::<T>::BadCreateRefungibleCall,2069 );2070 let item_owner = item.owner.first().expect("only one owner is defined");20712072 let value = item_owner.fraction;2073 let owner = item_owner.owner.clone();20742075 Self::add_token_index(collection_id, current_index, &owner)?;20762077 <ItemListIndex>::insert(collection_id, current_index);2078 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);20792080 // Update balance2081 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2082 .checked_add(value)2083 .ok_or(Error::<T>::NumOverflow)?;2084 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);20852086 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));2087 Ok(())2088 }20892090 fn add_nft_item(collection: &CollectionHandle<T>, item: NftItemType<T::CrossAccountId>) -> DispatchResult {2091 let collection_id = collection.id;20922093 let current_index = <ItemListIndex>::get(collection_id)2094 .checked_add(1)2095 .ok_or(Error::<T>::NumOverflow)?;20962097 let item_owner = item.owner.clone();2098 Self::add_token_index(collection_id, current_index, &item.owner)?;20992100 <ItemListIndex>::insert(collection_id, current_index);2101 <NftItemList<T>>::insert(collection_id, current_index, item);21022103 // Update balance2104 let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())2105 .checked_add(1)2106 .ok_or(Error::<T>::NumOverflow)?;2107 <Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);21082109 collection.log(ERC721Events::Transfer {2110 from: H160::default(),2111 to: *item_owner.as_eth(),2112 token_id: current_index.into(),2113 });2114 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, item_owner));2115 Ok(())2116 }21172118 fn burn_refungible_item(2119 collection: &CollectionHandle<T>,2120 item_id: TokenId,2121 owner: &T::CrossAccountId,2122 ) -> DispatchResult {2123 let collection_id = collection.id;21242125 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)2126 .ok_or(Error::<T>::TokenNotFound)?;2127 let rft_balance = token2128 .owner2129 .iter()2130 .find(|&i| i.owner == *owner)2131 .ok_or(Error::<T>::TokenNotFound)?;2132 Self::remove_token_index(collection_id, item_id, owner)?;21332134 // update balance2135 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())2136 .checked_sub(rft_balance.fraction)2137 .ok_or(Error::<T>::NumOverflow)?;2138 <Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);21392140 // Re-create owners list with sender removed2141 let index = token2142 .owner2143 .iter()2144 .position(|i| i.owner == *owner)2145 .expect("owned item is exists");2146 token.owner.remove(index);2147 let owner_count = token.owner.len();21482149 // Burn the token completely if this was the last (only) owner2150 if owner_count == 0 {2151 <ReFungibleItemList<T>>::remove(collection_id, item_id);2152 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);2153 }2154 else {2155 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);2156 }21572158 Ok(())2159 }21602161 fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {2162 let collection_id = collection.id;21632164 let item = <NftItemList<T>>::get(collection_id, item_id)2165 .ok_or(Error::<T>::TokenNotFound)?;2166 Self::remove_token_index(collection_id, item_id, &item.owner)?;21672168 // update balance2169 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2170 .checked_sub(1)2171 .ok_or(Error::<T>::NumOverflow)?;2172 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2173 <NftItemList<T>>::remove(collection_id, item_id);2174 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);21752176 Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));2177 Ok(())2178 }21792180 fn burn_fungible_item(owner: &T::CrossAccountId, collection: &CollectionHandle<T>, value: u128) -> DispatchResult {2181 let collection_id = collection.id;21822183 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());2184 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);21852186 // update balance2187 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2188 .checked_sub(value)2189 .ok_or(Error::<T>::NumOverflow)?;2190 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);21912192 if balance.value - value > 0 {2193 balance.value -= value;2194 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2195 }2196 else {2197 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());2198 }21992200 collection.log(ERC20Events::Transfer {2201 from: *owner.as_eth(),2202 to: H160::default(),2203 value: value.into(),2204 });2205 Ok(())2206 }22072208 pub fn get_collection(collection_id: CollectionId) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {2209 Ok(<CollectionHandle<T>>::get(collection_id)2210 .ok_or(Error::<T>::CollectionNotFound)?)2211 }22122213 fn save_collection(collection: CollectionHandle<T>) {2214 <CollectionById<T>>::insert(collection.id, collection.into_inner());2215 }22162217 pub fn submit_logs(collection: CollectionHandle<T>) -> DispatchResult {2218 if collection.logs.is_empty() {2219 return Ok(())2220 }2221 T::EthereumTransactionSender::submit_logs_transaction(2222 eth::generate_transaction(collection.id, T::EthereumChainId::get()),2223 collection.logs.retrieve_logs(),2224 )2225 }22262227 fn check_owner_permissions(target_collection: &CollectionHandle<T>, subject: &T::AccountId) -> DispatchResult {2228 ensure!(2229 *subject == target_collection.owner,2230 Error::<T>::NoPermission2231 );22322233 Ok(())2234 }22352236 fn is_owner_or_admin_permissions(collection: &CollectionHandle<T>, subject: &T::CrossAccountId) -> bool {2237 *subject.as_sub() == collection.owner || <AdminList<T>>::get(collection.id).contains(&subject)2238 }22392240 fn check_owner_or_admin_permissions(2241 collection: &CollectionHandle<T>,2242 subject: &T::CrossAccountId,2243 ) -> DispatchResult {2244 ensure!(Self::is_owner_or_admin_permissions(collection, subject), Error::<T>::NoPermission);22452246 Ok(())2247 }22482249 fn owned_amount(2250 subject: &T::CrossAccountId,2251 target_collection: &CollectionHandle<T>,2252 item_id: TokenId,2253 ) -> Option<u128> {2254 let collection_id = target_collection.id;22552256 match target_collection.mode {2257 CollectionMode::NFT => (<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject)2258 .then(|| 1),2259 CollectionMode::Fungible(_) => Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub())2260 .value),2261 CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?2262 .owner2263 .iter()2264 .find(|i| i.owner == *subject)2265 .map(|i| i.fraction),2266 CollectionMode::Invalid => None,2267 }2268 }22692270 fn is_item_owner(subject: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId) -> bool {2271 match target_collection.mode {2272 CollectionMode::Fungible(_) => true,2273 _ => Self::owned_amount(&subject, target_collection, item_id).is_some(),2274 }2275 }22762277 fn check_white_list(collection: &CollectionHandle<T>, address: &T::CrossAccountId) -> DispatchResult {2278 let collection_id = collection.id;22792280 let mes = Error::<T>::AddresNotInWhiteList;2281 ensure!(<WhiteList<T>>::contains_key(collection_id, address.as_sub()), mes);22822283 Ok(())2284 }22852286 /// Check if token exists. In case of Fungible, check if there is an entry for 2287 /// the owner in fungible balances double map2288 fn token_exists(2289 target_collection: &CollectionHandle<T>,2290 item_id: TokenId,2291 ) -> DispatchResult {2292 let collection_id = target_collection.id;2293 let exists = match target_collection.mode2294 {2295 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),2296 CollectionMode::Fungible(_) => true,2297 CollectionMode::ReFungible => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),2298 _ => false2299 };23002301 ensure!(exists == true, Error::<T>::TokenNotFound);2302 Ok(())2303 }23042305 fn transfer_fungible(2306 collection: &CollectionHandle<T>,2307 value: u128,2308 owner: &T::CrossAccountId,2309 recipient: &T::CrossAccountId,2310 ) -> DispatchResult {2311 let collection_id = collection.id;23122313 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());2314 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);23152316 // Send balance to recipient (updates balanceOf of recipient)2317 Self::add_fungible_item(collection, recipient, value)?;23182319 // update balanceOf of sender2320 <Balance<T>>::insert(collection_id, owner.as_sub(), balance.value - value);23212322 // Reduce or remove sender2323 if balance.value == value {2324 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());2325 }2326 else {2327 balance.value -= value;2328 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2329 }23302331 collection.log(ERC20Events::Transfer {2332 from: *owner.as_eth(),2333 to: *recipient.as_eth(),2334 value: value.into(),2335 });2336 Self::deposit_event(RawEvent::Transfer(collection.id, 1, owner.clone(), recipient.clone(), value));23372338 Ok(())2339 }23402341 fn transfer_refungible(2342 collection: &CollectionHandle<T>,2343 item_id: TokenId,2344 value: u128,2345 owner: T::CrossAccountId,2346 new_owner: T::CrossAccountId,2347 ) -> DispatchResult {2348 let collection_id = collection.id;2349 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2350 .ok_or(Error::<T>::TokenNotFound)?;23512352 let item = full_item2353 .owner2354 .iter()2355 .filter(|i| i.owner == owner)2356 .next()2357 .ok_or(Error::<T>::TokenNotFound)?;2358 let amount = item.fraction;23592360 ensure!(amount >= value, Error::<T>::TokenValueTooLow);23612362 // update balance2363 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2364 .checked_sub(value)2365 .ok_or(Error::<T>::NumOverflow)?;2366 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);23672368 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2369 .checked_add(value)2370 .ok_or(Error::<T>::NumOverflow)?;2371 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);23722373 let old_owner = item.owner.clone();2374 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);23752376 // transfer2377 if amount == value && !new_owner_has_account {2378 // change owner2379 // new owner do not have account2380 let mut new_full_item = full_item.clone();2381 new_full_item2382 .owner2383 .iter_mut()2384 .find(|i| i.owner == owner)2385 .expect("old owner does present in refungible")2386 .owner = new_owner.clone();2387 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);23882389 // update index collection2390 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2391 } else {2392 let mut new_full_item = full_item.clone();2393 new_full_item2394 .owner2395 .iter_mut()2396 .find(|i| i.owner == owner)2397 .expect("old owner does present in refungible")2398 .fraction -= value;23992400 // separate amount2401 if new_owner_has_account {2402 // new owner has account2403 new_full_item2404 .owner2405 .iter_mut()2406 .find(|i| i.owner == new_owner)2407 .expect("new owner has account")2408 .fraction += value;2409 } else {2410 // new owner do not have account2411 new_full_item.owner.push(Ownership {2412 owner: new_owner.clone(),2413 fraction: value,2414 });2415 Self::add_token_index(collection_id, item_id, &new_owner)?;2416 }24172418 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2419 }24202421 Self::deposit_event(RawEvent::Transfer(collection.id, item_id, owner, new_owner, amount));24222423 Ok(())2424 }24252426 fn transfer_nft(2427 collection: &CollectionHandle<T>,2428 item_id: TokenId,2429 sender: T::CrossAccountId,2430 new_owner: T::CrossAccountId,2431 ) -> DispatchResult {2432 let collection_id = collection.id;2433 let mut item = <NftItemList<T>>::get(collection_id, item_id)2434 .ok_or(Error::<T>::TokenNotFound)?;24352436 ensure!(2437 sender == item.owner,2438 Error::<T>::MustBeTokenOwner2439 );24402441 // update balance2442 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2443 .checked_sub(1)2444 .ok_or(Error::<T>::NumOverflow)?;2445 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);24462447 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2448 .checked_add(1)2449 .ok_or(Error::<T>::NumOverflow)?;2450 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);24512452 // change owner2453 let old_owner = item.owner.clone();2454 item.owner = new_owner.clone();2455 <NftItemList<T>>::insert(collection_id, item_id, item);24562457 // update index collection2458 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;24592460 collection.log(ERC721Events::Transfer {2461 from: *sender.as_eth(),2462 to: *new_owner.as_eth(),2463 token_id: item_id.into(),2464 });2465 Self::deposit_event(RawEvent::Transfer(collection.id, item_id, sender, new_owner, 1));24662467 Ok(())2468 }2469 2470 fn set_re_fungible_variable_data(2471 collection: &CollectionHandle<T>,2472 item_id: TokenId,2473 data: Vec<u8>2474 ) -> DispatchResult {2475 let collection_id = collection.id;2476 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2477 .ok_or(Error::<T>::TokenNotFound)?;24782479 item.variable_data = data;24802481 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);24822483 Ok(())2484 }24852486 fn set_nft_variable_data(2487 collection: &CollectionHandle<T>,2488 item_id: TokenId,2489 data: Vec<u8>2490 ) -> DispatchResult {2491 let collection_id = collection.id;2492 let mut item = <NftItemList<T>>::get(collection_id, item_id)2493 .ok_or(Error::<T>::TokenNotFound)?;2494 2495 item.variable_data = data;24962497 <NftItemList<T>>::insert(collection_id, item_id, item);2498 2499 Ok(())2500 }25012502 fn init_collection(item: &Collection<T>) {2503 // check params2504 assert!(2505 item.decimal_points <= MAX_DECIMAL_POINTS,2506 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2507 );2508 assert!(2509 item.name.len() <= 64,2510 "Collection name can not be longer than 63 char"2511 );2512 assert!(2513 item.name.len() <= 256,2514 "Collection description can not be longer than 255 char"2515 );2516 assert!(2517 item.token_prefix.len() <= 16,2518 "Token prefix can not be longer than 15 char"2519 );25202521 // Generate next collection ID2522 let next_id = CreatedCollectionCount::get()2523 .checked_add(1)2524 .unwrap();25252526 CreatedCollectionCount::put(next_id);2527 }25282529 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2530 let current_index = <ItemListIndex>::get(collection_id)2531 .checked_add(1)2532 .unwrap();25332534 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();25352536 <ItemListIndex>::insert(collection_id, current_index);25372538 // Update balance2539 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2540 .checked_add(1)2541 .unwrap();2542 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2543 }25442545 fn init_fungible_token(collection_id: CollectionId, owner: &T::CrossAccountId, item: &FungibleItemType) {2546 let current_index = <ItemListIndex>::get(collection_id)2547 .checked_add(1)2548 .unwrap();25492550 Self::add_token_index(collection_id, current_index, owner).unwrap();25512552 <ItemListIndex>::insert(collection_id, current_index);25532554 // Update balance2555 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2556 .checked_add(item.value)2557 .unwrap();2558 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2559 }25602561 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::CrossAccountId>) {2562 let current_index = <ItemListIndex>::get(collection_id)2563 .checked_add(1)2564 .unwrap();25652566 let value = item.owner.first().unwrap().fraction;2567 let owner = item.owner.first().unwrap().owner.clone();25682569 Self::add_token_index(collection_id, current_index, &owner).unwrap();25702571 <ItemListIndex>::insert(collection_id, current_index);25722573 // Update balance2574 let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2575 .checked_add(value)2576 .unwrap();2577 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2578 }25792580 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::CrossAccountId) -> DispatchResult {2581 // add to account limit2582 if <AccountItemCount<T>>::contains_key(owner.as_sub()) {25832584 // bound Owned tokens by a single address2585 let count = <AccountItemCount<T>>::get(owner.as_sub());2586 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);25872588 <AccountItemCount<T>>::insert(owner.as_sub(), count2589 .checked_add(1)2590 .ok_or(Error::<T>::NumOverflow)?);2591 }2592 else {2593 <AccountItemCount<T>>::insert(owner.as_sub(), 1);2594 }25952596 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2597 if list_exists {2598 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2599 let item_contains = list.contains(&item_index.clone());26002601 if !item_contains {2602 list.push(item_index.clone());2603 }26042605 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2606 } else {2607 let mut itm = Vec::new();2608 itm.push(item_index.clone());2609 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), itm);2610 }26112612 Ok(())2613 }26142615 fn remove_token_index(2616 collection_id: CollectionId,2617 item_index: TokenId,2618 owner: &T::CrossAccountId,2619 ) -> DispatchResult {26202621 // update counter2622 <AccountItemCount<T>>::insert(owner.as_sub(), 2623 <AccountItemCount<T>>::get(owner.as_sub())2624 .checked_sub(1)2625 .ok_or(Error::<T>::NumOverflow)?);262626272628 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2629 if list_exists {2630 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2631 let item_contains = list.contains(&item_index.clone());26322633 if item_contains {2634 list.retain(|&item| item != item_index);2635 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2636 }2637 }26382639 Ok(())2640 }26412642 fn move_token_index(2643 collection_id: CollectionId,2644 item_index: TokenId,2645 old_owner: &T::CrossAccountId,2646 new_owner: &T::CrossAccountId,2647 ) -> DispatchResult {2648 Self::remove_token_index(collection_id, item_index, old_owner)?;2649 Self::add_token_index(collection_id, item_index, new_owner)?;26502651 Ok(())2652 }2653 2654 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2655 ensure!(<ContractOwner<T>>::get(contract) == Some(account), Error::<T>::NoPermission);26562657 Ok(())2658 }2659}26602661////////////////////////////////////////////////////////////////////////////////////////////////////2662// Economic models2663// #region26642665/// Fee multiplier.2666pub type Multiplier = FixedU128;26672668type BalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::Balance;26692670/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2671/// in the queue.2672#[derive(Encode, Decode, Clone, Eq, PartialEq)]2673pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);26742675impl<T: Config + Send + Sync> sp_std::fmt::Debug 2676 for ChargeTransactionPayment<T>2677{2678 #[cfg(feature = "std")]2679 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2680 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2681 }2682 #[cfg(not(feature = "std"))]2683 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2684 Ok(())2685 }2686}26872688impl<T: Config> ChargeTransactionPayment<T>2689where2690 T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2691 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2692 T::AccountId: AsRef<[u8]>,2693 T::AccountId: UncheckedFrom<T::Hash>,2694{2695 fn traditional_fee(2696 len: usize,2697 info: &DispatchInfoOf<T::Call>,2698 tip: BalanceOf<T>,2699 ) -> BalanceOf<T>2700 where2701 T::Call: Dispatchable<Info = DispatchInfo>,2702 {2703 <pallet_transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2704 }27052706 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2707 let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);2708 let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);2709 let len_saturation = max_block_length as u64 / (len as u64).max(1);2710 let coefficient: BalanceOf<T> = weight_saturation2711 .min(len_saturation)2712 .saturated_into::<BalanceOf<T>>();2713 final_fee2714 .saturating_mul(coefficient)2715 .saturated_into::<TransactionPriority>()2716 }27172718 fn withdraw_fee(2719 &self,2720 who: &T::AccountId,2721 call: &T::Call,2722 info: &DispatchInfoOf<T::Call>,2723 len: usize,2724 ) -> Result<2725 (2726 BalanceOf<T>,2727 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2728 ),2729 TransactionValidityError,2730 > {2731 let tip = self.0;27322733 let fee = Self::traditional_fee(len, info, tip);27342735 // Only mess with balances if fee is not zero.2736 if fee.is_zero() {2737 return <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)2738 .map(|i| (fee, i));2739 }27402741 // Determine who is paying transaction fee based on ecnomic model2742 // Parse call to extract collection ID and access collection sponsor2743 let mut sponsor: Option<T::AccountId> = (|| match IsSubType::<Call<T>>::is_sub_type(call) {2744 Some(Call::create_item(collection_id, _owner, _properties)) => {2745 let collection = <CollectionById<T>>::get(collection_id)?;27462747 // sponsor timeout2748 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;27492750 let limit = collection.limits.sponsor_transfer_timeout;2751 if <CreateItemBasket<T>>::contains_key((collection_id, &who)) {2752 let last_tx_block = <CreateItemBasket<T>>::get((collection_id, &who));2753 let limit_time = last_tx_block + limit.into();2754 if block_number <= limit_time {2755 return None;2756 }2757 }2758 <CreateItemBasket<T>>::insert((collection_id, who.clone()), block_number);27592760 // check free create limit2761 if collection.limits.sponsored_data_size >= (_properties.len() as u32) {2762 collection.sponsorship.sponsor()2763 .cloned()2764 } else {2765 None2766 }2767 }2768 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2769 let collection = <CollectionById<T>>::get(collection_id)?;2770 2771 let mut sponsor_transfer = false;2772 if collection.sponsorship.confirmed() {27732774 let collection_limits = collection.limits;2775 let collection_mode = collection.mode;2776 2777 // sponsor timeout2778 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2779 sponsor_transfer = match collection_mode {2780 CollectionMode::NFT => {2781 2782 // get correct limit2783 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2784 collection_limits.sponsor_transfer_timeout2785 } else {2786 ChainLimit::get().nft_sponsor_transfer_timeout2787 };2788 2789 let mut sponsored = true;2790 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2791 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2792 let limit_time = last_tx_block + limit.into();2793 if block_number <= limit_time {2794 sponsored = false;2795 }2796 }2797 if sponsored {2798 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2799 }28002801 sponsored2802 }2803 CollectionMode::Fungible(_) => {2804 2805 // get correct limit2806 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2807 collection_limits.sponsor_transfer_timeout2808 } else {2809 ChainLimit::get().fungible_sponsor_transfer_timeout2810 };2811 2812 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2813 let mut sponsored = true;2814 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2815 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2816 let limit_time = last_tx_block + limit.into();2817 if block_number <= limit_time {2818 sponsored = false;2819 }2820 }2821 if sponsored {2822 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2823 }28242825 sponsored2826 }2827 CollectionMode::ReFungible => {2828 2829 // get correct limit2830 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2831 collection_limits.sponsor_transfer_timeout2832 } else {2833 ChainLimit::get().refungible_sponsor_transfer_timeout2834 };2835 2836 let mut sponsored = true;2837 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2838 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2839 let limit_time = last_tx_block + limit.into();2840 if block_number <= limit_time {2841 sponsored = false;2842 }2843 }2844 if sponsored {2845 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2846 }28472848 sponsored2849 }2850 _ => {2851 false2852 },2853 };2854 }28552856 if !sponsor_transfer {2857 None2858 } else {2859 collection.sponsorship.sponsor()2860 .cloned()2861 }2862 }28632864 Some(Call::set_variable_meta_data(collection_id, item_id, data)) => {2865 let mut sponsor_metadata_changes = false;28662867 let collection = <CollectionById<T>>::get(collection_id)?;28682869 if2870 collection.sponsorship.confirmed() &&2871 // Can't sponsor fungible collection, this tx will be rejected2872 // as invalid2873 !matches!(collection.mode, CollectionMode::Fungible(_)) &&2874 data.len() <= collection.limits.sponsored_data_size as usize2875 {2876 if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit {2877 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;28782879 if <VariableMetaDataBasket<T>>::get(collection_id, item_id)2880 .map(|last_block| block_number - last_block > rate_limit)2881 .unwrap_or(true) 2882 {2883 sponsor_metadata_changes = true;2884 <VariableMetaDataBasket<T>>::insert(collection_id, item_id, block_number);2885 }2886 }2887 }28882889 if !sponsor_metadata_changes {2890 None2891 } else {2892 collection.sponsorship.sponsor().cloned()2893 }2894 }28952896 _ => None,2897 })();28982899 match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {2900 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {29012902 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());29032904 let owned_contract = <ContractOwner<T>>::get(called_contract.clone()).as_ref() == Some(who);2905 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone());2906 2907 if !owned_contract && white_list_enabled {2908 if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2909 return Err(InvalidTransaction::Call.into());2910 }2911 }2912 },2913 _ => {},2914 }29152916 // Sponsor smart contracts2917 sponsor = sponsor.or_else(|| match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {29182919 // On instantiation: set the contract owner2920 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {29212922 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2923 &who,2924 code_hash,2925 salt,2926 );2927 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());29282929 None2930 },29312932 // On instantiation with code: set the contract owner2933 Some(pallet_contracts::Call::instantiate_with_code(_endowment, _gas_limit, _code, _data, _salt)) => {29342935 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2936 &who,2937 &T::Hashing::hash(&_code),2938 _salt,2939 );29402941 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());29422943 None2944 }29452946 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2947 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {29482949 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());29502951 let mut sponsor_transfer = false;2952 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2953 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2954 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2955 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2956 let limit_time = last_tx_block + rate_limit;29572958 if block_number >= limit_time {2959 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2960 sponsor_transfer = true;2961 }2962 } else {2963 sponsor_transfer = false;2964 }2965 2966 if sponsor_transfer {2967 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2968 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2969 return Some(called_contract);2970 }2971 }2972 }29732974 None2975 },29762977 _ => None,2978 });29792980 let who_pays_fee = sponsor.unwrap_or_else(|| who.clone());29812982 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(&who_pays_fee, call, info, fee, tip)2983 .map(|i| (fee, i))2984 }2985}298629872988impl<T: Config + Send + Sync> SignedExtension2989 for ChargeTransactionPayment<T>2990where2991 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2992 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2993 T::AccountId: AsRef<[u8]>,2994 T::AccountId: UncheckedFrom<T::Hash>,2995{2996 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2997 type AccountId = T::AccountId;2998 type Call = T::Call;2999 type AdditionalSigned = ();3000 type Pre = (3001 // tip3002 BalanceOf<T>,3003 // who pays fee3004 Self::AccountId,3005 // imbalance resulting from withdrawing the fee3006 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,3007 );3008 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {3009 Ok(())3010 }30113012 fn validate(3013 &self,3014 who: &Self::AccountId,3015 call: &Self::Call,3016 info: &DispatchInfoOf<Self::Call>,3017 len: usize,3018 ) -> TransactionValidity {3019 let (fee, _) = self.withdraw_fee(who, call, info, len)?;3020 Ok(ValidTransaction {3021 priority: Self::get_priority(len, info, fee),3022 ..Default::default()3023 })3024 }30253026 fn pre_dispatch(3027 self,3028 who: &Self::AccountId,3029 call: &Self::Call,3030 info: &DispatchInfoOf<Self::Call>,3031 len: usize,3032 ) -> Result<Self::Pre, TransactionValidityError> {3033 let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;3034 Ok((self.0, who.clone(), imbalance))3035 }30363037 fn post_dispatch(3038 pre: Self::Pre,3039 info: &DispatchInfoOf<Self::Call>,3040 post_info: &PostDispatchInfoOf<Self::Call>,3041 len: usize,3042 _result: &DispatchResult,3043 ) -> Result<(), TransactionValidityError> {3044 let (tip, who, imbalance) = pre;3045 let actual_fee = pallet_transaction_payment::Module::<T>::compute_actual_fee(3046 len as u32,3047 info,3048 post_info,3049 tip,3050 );3051 <T as pallet_transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;3052 Ok(())3053 }3054}30553056// #endregion30573058sp_api::decl_runtime_apis! {3059 pub trait NftApi {3060 /// Used for ethereum integration3061 fn eth_contract_code(account: H160) -> Option<Vec<u8>>;3062 }3063}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"]78#![cfg_attr(not(feature = "std"), no_std)]910extern crate alloc;1112pub use serde::{Serialize, Deserialize};1314use core::ops::{Deref, DerefMut};15use core::cell::RefCell;16use codec::{Decode, Encode};17pub use frame_support::{18 construct_runtime, decl_event, decl_module, decl_storage, decl_error,19 dispatch::DispatchResult,20 ensure, fail, parameter_types,21 traits::{22 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,23 Randomness, IsSubType, WithdrawReasons,24 },25 weights::{26 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28 WeightToFeePolynomial, DispatchClass,29 },30 StorageValue,31 transactional,32};3334use frame_system::{self as system, ensure_signed, ensure_root};35use sp_core::H160;36use sp_runtime::sp_std::prelude::Vec;37use sp_runtime::{38 traits::{39 Hash, DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,40 },41 transaction_validity::{42 TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,43 },44 FixedPointOperand, FixedU128,45};46use sp_runtime::traits::StaticLookup;47use pallet_contracts::chain_extension::UncheckedFrom;48use pallet_ethereum::EthereumTransactionSender;49use pallet_transaction_payment::OnChargeTransaction;5051#[cfg(test)]52mod mock;5354#[cfg(test)]55mod tests;5657mod default_weights;58mod eth;5960pub use eth::NftErcSupport;61pub use eth::account::*;62use eth::erc::{ERC20Events, ERC721Events};6364pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;65pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;66pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;67pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;6869// Structs70// #region7172pub type CollectionId = u32;73pub type TokenId = u32;74pub type DecimalPoints = u8;7576#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]77#[derive(Serialize, Deserialize)]78pub enum CollectionMode {79 Invalid,80 NFT,81 // decimal points82 Fungible(DecimalPoints),83 ReFungible,84}8586impl Default for CollectionMode {87 fn default() -> Self {88 Self::Invalid89 }90}9192impl Into<u8> for CollectionMode {93 fn into(self) -> u8 {94 match self {95 CollectionMode::Invalid => 0,96 CollectionMode::NFT => 1,97 CollectionMode::Fungible(_) => 2,98 CollectionMode::ReFungible => 3,99 }100 }101}102103#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]104#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]105pub enum AccessMode {106 Normal,107 WhiteList,108}109impl Default for AccessMode {110 fn default() -> Self {111 Self::Normal112 }113}114115#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]116#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]117pub enum SchemaVersion {118 ImageURL,119 Unique,120}121impl Default for SchemaVersion {122 fn default() -> Self {123 Self::ImageURL124 }125}126127#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]128#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]129pub struct Ownership<AccountId> {130 pub owner: AccountId,131 pub fraction: u128,132}133134#[derive(Encode, Decode, Debug, Clone, PartialEq)]135#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]136pub enum SponsorshipState<AccountId> {137 /// The fees are applied to the transaction sender138 Disabled,139 Unconfirmed(AccountId),140 /// Transactions are sponsored by specified account141 Confirmed(AccountId),142}143144impl<AccountId> SponsorshipState<AccountId> {145 fn sponsor(&self) -> Option<&AccountId> {146 match self {147 Self::Confirmed(sponsor) => Some(sponsor),148 _ => None,149 }150 }151152 fn pending_sponsor(&self) -> Option<&AccountId> {153 match self {154 Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),155 _ => None,156 }157 }158159 fn confirmed(&self) -> bool {160 matches!(self, Self::Confirmed(_))161 }162}163164impl<T> Default for SponsorshipState<T> {165 fn default() -> Self {166 Self::Disabled167 }168}169170#[derive(Encode, Decode, Clone, PartialEq)]171#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]172pub struct Collection<T: Config> {173 pub owner: T::AccountId,174 pub mode: CollectionMode,175 pub access: AccessMode,176 pub decimal_points: DecimalPoints,177 pub name: Vec<u16>, // 64 include null escape char178 pub description: Vec<u16>, // 256 include null escape char179 pub token_prefix: Vec<u8>, // 16 include null escape char180 pub mint_mode: bool,181 pub offchain_schema: Vec<u8>,182 pub schema_version: SchemaVersion,183 pub sponsorship: SponsorshipState<T::AccountId>,184 pub limits: CollectionLimits<T::BlockNumber>, // Collection private restrictions 185 pub variable_on_chain_schema: Vec<u8>, //186 pub const_on_chain_schema: Vec<u8>, //187}188189pub struct CollectionHandle<T: Config> {190 pub id: CollectionId,191 collection: Collection<T>,192 logs: eth::log::LogRecorder,193 evm_address: H160,194 gas_limit: RefCell<u64>,195}196impl<T: Config> CollectionHandle<T> {197 pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {198 <CollectionById<T>>::get(id)199 .map(|collection| Self {200 id,201 collection,202 logs: eth::log::LogRecorder::default(),203 evm_address: eth::collection_id_to_address(id),204 gas_limit: RefCell::new(gas_limit),205 })206 }207 pub fn get(id: CollectionId) -> Option<Self> {208 Self::get_with_gas_limit(id, u64::MAX)209 }210 pub fn gas_left(&self) -> u64 {211 *self.gas_limit.borrow()212 }213 pub fn consume_gas(&self, gas: u64) -> DispatchResult {214 let mut gas_limit = self.gas_limit.borrow_mut();215 if *gas_limit < gas {216 fail!(Error::<T>::OutOfGas);217 }218 *gas_limit -= gas;219 Ok(())220 }221 pub fn log(&self, log: impl evm_coder::ToLog) {222 self.logs.log(log.to_log(self.evm_address))223 }224 pub fn into_inner(self) -> Collection<T> {225 self.collection.clone()226 }227}228229impl<T: Config> Deref for CollectionHandle<T> {230 type Target = Collection<T>;231232 fn deref(&self) -> &Self::Target {233 &self.collection234 }235}236237impl<T: Config> DerefMut for CollectionHandle<T> {238 fn deref_mut(&mut self) -> &mut Self::Target {239 &mut self.collection240 }241}242243#[derive(Encode, Decode, Debug, Clone, PartialEq)]244#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]245pub struct NftItemType<AccountId> {246 pub owner: AccountId,247 pub const_data: Vec<u8>,248 pub variable_data: Vec<u8>,249}250251#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]252#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]253pub struct FungibleItemType {254 pub value: u128,255}256257#[derive(Encode, Decode, Debug, Clone, PartialEq)]258#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]259pub struct ReFungibleItemType<AccountId> {260 pub owner: Vec<Ownership<AccountId>>,261 pub const_data: Vec<u8>,262 pub variable_data: Vec<u8>,263}264265// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]266// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]267// pub struct VestingItem<AccountId, Moment> {268// pub sender: AccountId,269// pub recipient: AccountId,270// pub collection_id: CollectionId,271// pub item_id: TokenId,272// pub amount: u64,273// pub vesting_date: Moment,274// }275276#[derive(Encode, Decode, Debug, Clone, PartialEq)]277#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]278pub struct CollectionLimits<BlockNumber: Encode + Decode> {279 pub account_token_ownership_limit: u32,280 pub sponsored_data_size: u32,281 /// None - setVariableMetadata is not sponsored282 /// Some(v) - setVariableMetadata is sponsored 283 /// if there is v block between txs284 pub sponsored_data_rate_limit: Option<BlockNumber>,285 pub token_limit: u32,286287 // Timeouts for item types in passed blocks288 pub sponsor_transfer_timeout: u32,289 pub owner_can_transfer: bool,290 pub owner_can_destroy: bool,291}292293impl<BlockNumber: Encode + Decode> Default for CollectionLimits<BlockNumber> {294 fn default() -> Self {295 Self { 296 account_token_ownership_limit: 10_000_000, 297 token_limit: u32::max_value(),298 sponsored_data_size: u32::MAX, 299 sponsored_data_rate_limit: None,300 sponsor_transfer_timeout: 14400,301 owner_can_transfer: true,302 owner_can_destroy: true303 }304 }305}306307#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]308#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]309pub struct ChainLimits {310 pub collection_numbers_limit: u32,311 pub account_token_ownership_limit: u32,312 pub collections_admins_limit: u64,313 pub custom_data_limit: u32,314315 // Timeouts for item types in passed blocks316 pub nft_sponsor_transfer_timeout: u32,317 pub fungible_sponsor_transfer_timeout: u32,318 pub refungible_sponsor_transfer_timeout: u32,319320 // Schema limits321 pub offchain_schema_limit: u32,322 pub variable_on_chain_schema_limit: u32,323 pub const_on_chain_schema_limit: u32,324}325326pub trait WeightInfo {327 fn create_collection() -> Weight;328 fn destroy_collection() -> Weight;329 fn add_to_white_list() -> Weight;330 fn remove_from_white_list() -> Weight;331 fn set_public_access_mode() -> Weight;332 fn set_mint_permission() -> Weight;333 fn change_collection_owner() -> Weight;334 fn add_collection_admin() -> Weight;335 fn remove_collection_admin() -> Weight;336 fn set_collection_sponsor() -> Weight;337 fn confirm_sponsorship() -> Weight;338 fn remove_collection_sponsor() -> Weight;339 fn create_item(s: usize) -> Weight;340 fn burn_item() -> Weight;341 fn transfer() -> Weight;342 fn approve() -> Weight;343 fn transfer_from() -> Weight;344 fn set_offchain_schema() -> Weight;345 fn set_const_on_chain_schema() -> Weight;346 fn set_variable_on_chain_schema() -> Weight;347 fn set_variable_meta_data() -> Weight;348 fn enable_contract_sponsoring() -> Weight;349 fn set_schema_version() -> Weight;350 fn set_chain_limits() -> Weight;351 fn set_contract_sponsoring_rate_limit() -> Weight;352 fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;353 fn toggle_contract_white_list() -> Weight;354 fn add_to_contract_white_list() -> Weight;355 fn remove_from_contract_white_list() -> Weight;356 fn set_collection_limits() -> Weight;357}358359#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]360#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]361pub struct CreateNftData {362 pub const_data: Vec<u8>,363 pub variable_data: Vec<u8>,364}365366#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]367#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]368pub struct CreateFungibleData {369 pub value: u128,370}371372#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]373#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]374pub struct CreateReFungibleData {375 pub const_data: Vec<u8>,376 pub variable_data: Vec<u8>,377 pub pieces: u128,378}379380#[derive(Encode, Decode, Debug, Clone, PartialEq)]381#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]382pub enum CreateItemData {383 NFT(CreateNftData),384 Fungible(CreateFungibleData),385 ReFungible(CreateReFungibleData),386}387388impl CreateItemData {389 pub fn len(&self) -> usize {390 let len = match self {391 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),392 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),393 _ => 0394 };395 396 return len;397 }398}399400impl From<CreateNftData> for CreateItemData {401 fn from(item: CreateNftData) -> Self {402 CreateItemData::NFT(item)403 }404}405406impl From<CreateReFungibleData> for CreateItemData {407 fn from(item: CreateReFungibleData) -> Self {408 CreateItemData::ReFungible(item)409 }410}411412impl From<CreateFungibleData> for CreateItemData {413 fn from(item: CreateFungibleData) -> Self {414 CreateItemData::Fungible(item)415 }416}417418419decl_error! {420 /// Error for non-fungible-token module.421 pub enum Error for Module<T: Config> {422 /// Total collections bound exceeded.423 TotalCollectionsLimitExceeded,424 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.425 CollectionDecimalPointLimitExceeded, 426 /// Collection name can not be longer than 63 char.427 CollectionNameLimitExceeded, 428 /// Collection description can not be longer than 255 char.429 CollectionDescriptionLimitExceeded, 430 /// Token prefix can not be longer than 15 char.431 CollectionTokenPrefixLimitExceeded,432 /// This collection does not exist.433 CollectionNotFound,434 /// Item not exists.435 TokenNotFound,436 /// Admin not found437 AdminNotFound,438 /// Arithmetic calculation overflow.439 NumOverflow, 440 /// Account already has admin role.441 AlreadyAdmin, 442 /// You do not own this collection.443 NoPermission,444 /// This address is not set as sponsor, use setCollectionSponsor first.445 ConfirmUnsetSponsorFail,446 /// Collection is not in mint mode.447 PublicMintingNotAllowed,448 /// Sender parameter and item owner must be equal.449 MustBeTokenOwner,450 /// Item balance not enough.451 TokenValueTooLow,452 /// Size of item is too large.453 NftSizeLimitExceeded,454 /// No approve found455 ApproveNotFound,456 /// Requested value more than approved.457 TokenValueNotEnough,458 /// Only approved addresses can call this method.459 ApproveRequired,460 /// Address is not in white list.461 AddresNotInWhiteList,462 /// Number of collection admins bound exceeded.463 CollectionAdminsLimitExceeded,464 /// Owned tokens by a single address bound exceeded.465 AddressOwnershipLimitExceeded,466 /// Length of items properties must be greater than 0.467 EmptyArgument,468 /// const_data exceeded data limit.469 TokenConstDataLimitExceeded,470 /// variable_data exceeded data limit.471 TokenVariableDataLimitExceeded,472 /// Not NFT item data used to mint in NFT collection.473 NotNftDataUsedToMintNftCollectionToken,474 /// Not Fungible item data used to mint in Fungible collection.475 NotFungibleDataUsedToMintFungibleCollectionToken,476 /// Not Re Fungible item data used to mint in Re Fungible collection.477 NotReFungibleDataUsedToMintReFungibleCollectionToken,478 /// Unexpected collection type.479 UnexpectedCollectionType,480 /// Can't store metadata in fungible tokens.481 CantStoreMetadataInFungibleTokens,482 /// Collection token limit exceeded483 CollectionTokenLimitExceeded,484 /// Account token limit exceeded per collection485 AccountTokenLimitExceeded,486 /// Collection limit bounds per collection exceeded487 CollectionLimitBoundsExceeded,488 /// Tried to enable permissions which are only permitted to be disabled489 OwnerPermissionsCantBeReverted,490 /// Schema data size limit bound exceeded491 SchemaDataLimitExceeded,492 /// Maximum refungibility exceeded493 WrongRefungiblePieces,494 /// createRefungible should be called with one owner495 BadCreateRefungibleCall,496 /// Gas limit exceeded497 OutOfGas,498 }499}500501pub trait Config: system::Config + Sized + pallet_transaction_payment::Config + pallet_contracts::Config {502 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;503504 /// Weight information for extrinsics in this pallet.505 type WeightInfo: WeightInfo;506507 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;508 type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;509 type EvmWithdrawOrigin: pallet_evm::EnsureAddressOrigin<Self::Origin, Success = Self::AccountId>;510511 type CrossAccountId: CrossAccountId<Self::AccountId>;512 type Currency: Currency<Self::AccountId>;513 type CollectionCreationPrice: Get<<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance>;514 type TreasuryAccountId: Get<Self::AccountId>;515516 type EthereumChainId: Get<u64>;517 type EthereumTransactionSender: pallet_ethereum::EthereumTransactionSender;518}519520#[cfg(feature = "runtime-benchmarks")]521mod benchmarking;522523// #endregion524525// # Used definitions526//527// ## User control levels528//529// chain-controlled - key is uncontrolled by user530// i.e autoincrementing index531// can use non-cryptographic hash532// real - key is controlled by user533// but it is hard to generate enough colliding values, i.e owner of signed txs534// can use non-cryptographic hash535// controlled - key is completly controlled by users536// i.e maps with mutable keys537// should use cryptographic hash538//539// ## User control level downgrade reasons540//541// ?1 - chain-controlled -> controlled542// collections/tokens can be destroyed, resulting in massive holes543// ?2 - chain-controlled -> controlled544// same as ?1, but can be only added, resulting in easier exploitation545// ?3 - real -> controlled546// no confirmation required, so addresses can be easily generated547decl_storage! {548 trait Store for Module<T: Config> as Nft {549550 //#region Private members551 /// Id of next collection552 CreatedCollectionCount: u32;553 /// Used for migrations554 ChainVersion: u64;555 /// Id of last collection token556 /// Collection id (controlled?1)557 ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;558 //#endregion559560 //#region Chain limits struct561 pub ChainLimit get(fn chain_limit) config(): ChainLimits;562 //#endregion563564 //#region Bound counters565 /// Amount of collections destroyed, used for total amount tracking with566 /// CreatedCollectionCount567 DestroyedCollectionCount: u32;568 /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)569 /// Account id (real)570 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;571 //#endregion572573 //#region Basic collections574 /// Collection info575 /// Collection id (controlled?1)576 pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;577 /// List of collection admins578 /// Collection id (controlled?2)579 pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;580 /// Whitelisted collection users581 /// Collection id (controlled?2), user id (controlled?3)582 pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;583 //#endregion584585 /// How many of collection items user have586 /// Collection id (controlled?2), account id (real)587 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;588589 /// Amount of items which spender can transfer out of owners account (via transferFrom)590 /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))591 /// TODO: Off chain worker should remove from this map when token gets removed592 pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;593594 //#region Item collections595 /// Collection id (controlled?2), token id (controlled?1)596 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;597 /// Collection id (controlled?2), owner (controlled?2)598 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;599 /// Collection id (controlled?2), token id (controlled?1)600 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;601 //#endregion602603 //#region Index list604 /// Collection id (controlled?2), tokens owner (controlled?2)605 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;606 //#endregion607608 //#region Tokens transfer rate limit baskets609 /// (Collection id (controlled?2), who created (real))610 /// TODO: Off chain worker should remove from this map when collection gets removed611 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;612 /// Collection id (controlled?2), token id (controlled?2)613 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;614 /// Collection id (controlled?2), owning user (real)615 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;616 /// Collection id (controlled?2), token id (controlled?2)617 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;618 //#endregion619620 /// Variable metadata sponsoring621 /// Collection id (controlled?2), token id (controlled?2)622 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;623 624 //#region Contract Sponsorship and Ownership625 /// Contract address (real)626 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => Option<T::AccountId>;627 /// Contract address (real)628 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;629 /// (Contract address(real), caller (real))630 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;631 /// Contract address (real)632 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;633 /// Contract address (real)634 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 635 /// Contract address (real) => Whitelisted user (controlled?3)636 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(blake2_128_concat) T::AccountId => bool; 637 //#endregion638 }639 add_extra_genesis {640 build(|config: &GenesisConfig<T>| {641 // Modification of storage642 for (_num, _c) in &config.collection_id {643 <Module<T>>::init_collection(_c);644 }645646 for (_num, _c, _i) in &config.nft_item_id {647 <Module<T>>::init_nft_token(*_c, _i);648 }649650 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {651 <Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);652 }653654 for (_num, _c, _i) in &config.refungible_item_id {655 <Module<T>>::init_refungible_token(*_c, _i);656 }657 })658 }659}660661decl_event!(662 pub enum Event<T>663 where664 AccountId = <T as frame_system::Config>::AccountId,665 CrossAccountId = <T as Config>::CrossAccountId,666 {667 /// New collection was created668 /// 669 /// # Arguments670 /// 671 /// * collection_id: Globally unique identifier of newly created collection.672 /// 673 /// * mode: [CollectionMode] converted into u8.674 /// 675 /// * account_id: Collection owner.676 CollectionCreated(CollectionId, u8, AccountId),677678 /// New item was created.679 /// 680 /// # Arguments681 /// 682 /// * collection_id: Id of the collection where item was created.683 /// 684 /// * item_id: Id of an item. Unique within the collection.685 ///686 /// * recipient: Owner of newly created item 687 ItemCreated(CollectionId, TokenId, CrossAccountId),688689 /// Collection item was burned.690 /// 691 /// # Arguments692 /// 693 /// collection_id.694 /// 695 /// item_id: Identifier of burned NFT.696 ItemDestroyed(CollectionId, TokenId),697698 /// Item was transferred699 ///700 /// * collection_id: Id of collection to which item is belong701 ///702 /// * item_id: Id of an item703 ///704 /// * sender: Original owner of item705 ///706 /// * recipient: New owner of item707 ///708 /// * amount: Always 1 for NFT709 Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),710711 /// * collection_id712 ///713 /// * item_id714 ///715 /// * sender716 ///717 /// * spender718 ///719 /// * amount720 Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),721 }722);723724decl_module! {725 pub struct Module<T: Config> for enum Call 726 where 727 origin: T::Origin728 {729 fn deposit_event() = default;730 type Error = Error<T>;731732 fn on_initialize(now: T::BlockNumber) -> Weight {733 0734 }735736 /// 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.737 /// 738 /// # Permissions739 /// 740 /// * Anyone.741 /// 742 /// # Arguments743 /// 744 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.745 /// 746 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.747 /// 748 /// * token_prefix: UTF-8 string with token prefix.749 /// 750 /// * mode: [CollectionMode] collection type and type dependent data.751 // returns collection ID752 #[weight = <T as Config>::WeightInfo::create_collection()]753 #[transactional]754 pub fn create_collection(origin,755 collection_name: Vec<u16>,756 collection_description: Vec<u16>,757 token_prefix: Vec<u8>,758 mode: CollectionMode) -> DispatchResult {759760 // Anyone can create a collection761 let who = ensure_signed(origin)?;762763 // Take a (non-refundable) deposit of collection creation764 let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();765 imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(766 &T::TreasuryAccountId::get(),767 T::CollectionCreationPrice::get(),768 ));769 <T as Config>::Currency::settle(770 &who,771 imbalance,772 WithdrawReasons::TRANSFER,773 ExistenceRequirement::KeepAlive,774 ).map_err(|_| Error::<T>::NoPermission)?;775776 let decimal_points = match mode {777 CollectionMode::Fungible(points) => points,778 _ => 0779 };780781 let chain_limit = ChainLimit::get();782783 let created_count = CreatedCollectionCount::get();784 let destroyed_count = DestroyedCollectionCount::get();785786 // bound Total number of collections787 ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);788789 // check params790 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);791 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);792 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);793 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);794795 // Generate next collection ID796 let next_id = created_count797 .checked_add(1)798 .ok_or(Error::<T>::NumOverflow)?;799800 CreatedCollectionCount::put(next_id);801802 let limits = CollectionLimits {803 sponsored_data_size: chain_limit.custom_data_limit,804 ..Default::default()805 };806807 // Create new collection808 let new_collection = Collection {809 owner: who.clone(),810 name: collection_name,811 mode: mode.clone(),812 mint_mode: false,813 access: AccessMode::Normal,814 description: collection_description,815 decimal_points: decimal_points,816 token_prefix: token_prefix,817 offchain_schema: Vec::new(),818 schema_version: SchemaVersion::ImageURL,819 sponsorship: SponsorshipState::Disabled,820 variable_on_chain_schema: Vec::new(),821 const_on_chain_schema: Vec::new(),822 limits,823 };824825 // Add new collection to map826 <CollectionById<T>>::insert(next_id, new_collection);827828 // call event829 Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.into(), who));830831 Ok(())832 }833834 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.835 /// 836 /// # Permissions837 /// 838 /// * Collection Owner.839 /// 840 /// # Arguments841 /// 842 /// * collection_id: collection to destroy.843 #[weight = <T as Config>::WeightInfo::destroy_collection()]844 #[transactional]845 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {846847 let sender = ensure_signed(origin)?;848 let collection = Self::get_collection(collection_id)?;849 Self::check_owner_permissions(&collection, &sender)?;850 if !collection.limits.owner_can_destroy {851 fail!(Error::<T>::NoPermission);852 }853854 <AddressTokens<T>>::remove_prefix(collection_id);855 <Allowances<T>>::remove_prefix(collection_id);856 <Balance<T>>::remove_prefix(collection_id);857 <ItemListIndex>::remove(collection_id);858 <AdminList<T>>::remove(collection_id);859 <CollectionById<T>>::remove(collection_id);860 <WhiteList<T>>::remove_prefix(collection_id);861862 <NftItemList<T>>::remove_prefix(collection_id);863 <FungibleItemList<T>>::remove_prefix(collection_id);864 <ReFungibleItemList<T>>::remove_prefix(collection_id);865866 <NftTransferBasket<T>>::remove_prefix(collection_id);867 <FungibleTransferBasket<T>>::remove_prefix(collection_id);868 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);869870 <VariableMetaDataBasket<T>>::remove_prefix(collection_id);871872 DestroyedCollectionCount::put(DestroyedCollectionCount::get()873 .checked_add(1)874 .ok_or(Error::<T>::NumOverflow)?);875876 Ok(())877 }878879 /// Add an address to white list.880 /// 881 /// # Permissions882 /// 883 /// * Collection Owner884 /// * Collection Admin885 /// 886 /// # Arguments887 /// 888 /// * collection_id.889 /// 890 /// * address.891 #[weight = <T as Config>::WeightInfo::add_to_white_list()]892 #[transactional]893 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{894895 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);896 let collection = Self::get_collection(collection_id)?;897898 Self::toggle_white_list_internal(899 &sender,900 &collection,901 &address,902 true,903 )?;904905 Ok(())906 }907908 /// Remove an address from white list.909 /// 910 /// # Permissions911 /// 912 /// * Collection Owner913 /// * Collection Admin914 /// 915 /// # Arguments916 /// 917 /// * collection_id.918 /// 919 /// * address.920 #[weight = <T as Config>::WeightInfo::remove_from_white_list()]921 #[transactional]922 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{923924 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);925 let collection = Self::get_collection(collection_id)?;926927 Self::toggle_white_list_internal(928 &sender,929 &collection,930 &address,931 false,932 )?;933934 Ok(())935 }936937 /// Toggle between normal and white list access for the methods with access for `Anyone`.938 /// 939 /// # Permissions940 /// 941 /// * Collection Owner.942 /// 943 /// # Arguments944 /// 945 /// * collection_id.946 /// 947 /// * mode: [AccessMode]948 #[weight = <T as Config>::WeightInfo::set_public_access_mode()]949 #[transactional]950 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult951 {952 let sender = ensure_signed(origin)?;953954 let mut target_collection = Self::get_collection(collection_id)?;955 Self::check_owner_permissions(&target_collection, &sender)?;956 target_collection.access = mode;957 Self::save_collection(target_collection);958959 Ok(())960 }961962 /// Allows Anyone to create tokens if:963 /// * White List is enabled, and964 /// * Address is added to white list, and965 /// * This method was called with True parameter966 /// 967 /// # Permissions968 /// * Collection Owner969 ///970 /// # Arguments971 /// 972 /// * collection_id.973 /// 974 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.975 #[weight = <T as Config>::WeightInfo::set_mint_permission()]976 #[transactional]977 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult978 {979 let sender = ensure_signed(origin)?;980981 let mut target_collection = Self::get_collection(collection_id)?;982 Self::check_owner_permissions(&target_collection, &sender)?;983 target_collection.mint_mode = mint_permission;984 Self::save_collection(target_collection);985986 Ok(())987 }988989 /// Change the owner of the collection.990 /// 991 /// # Permissions992 /// 993 /// * Collection Owner.994 /// 995 /// # Arguments996 /// 997 /// * collection_id.998 /// 999 /// * new_owner.1000 #[weight = <T as Config>::WeightInfo::change_collection_owner()]1001 #[transactional]1002 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {10031004 let sender = ensure_signed(origin)?;1005 let mut target_collection = Self::get_collection(collection_id)?;1006 Self::check_owner_permissions(&target_collection, &sender)?;1007 target_collection.owner = new_owner;1008 Self::save_collection(target_collection);10091010 Ok(())1011 }10121013 /// Adds an admin of the Collection.1014 /// 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. 1015 /// 1016 /// # Permissions1017 /// 1018 /// * Collection Owner.1019 /// * Collection Admin.1020 /// 1021 /// # Arguments1022 /// 1023 /// * collection_id: ID of the Collection to add admin for.1024 /// 1025 /// * new_admin_id: Address of new admin to add.1026 #[weight = <T as Config>::WeightInfo::add_collection_admin()]1027 #[transactional]1028 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {1029 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1030 let collection = Self::get_collection(collection_id)?;1031 Self::check_owner_or_admin_permissions(&collection, &sender)?;1032 let mut admin_arr = <AdminList<T>>::get(collection_id);10331034 match admin_arr.binary_search(&new_admin_id) {1035 Ok(_) => {},1036 Err(idx) => {1037 let limits = ChainLimit::get();1038 ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);1039 admin_arr.insert(idx, new_admin_id);1040 <AdminList<T>>::insert(collection_id, admin_arr);1041 }1042 }1043 Ok(())1044 }10451046 /// 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.1047 ///1048 /// # Permissions1049 /// 1050 /// * Collection Owner.1051 /// * Collection Admin.1052 /// 1053 /// # Arguments1054 /// 1055 /// * collection_id: ID of the Collection to remove admin for.1056 /// 1057 /// * account_id: Address of admin to remove.1058 #[weight = <T as Config>::WeightInfo::remove_collection_admin()]1059 #[transactional]1060 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {1061 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1062 let collection = Self::get_collection(collection_id)?;1063 Self::check_owner_or_admin_permissions(&collection, &sender)?;1064 let mut admin_arr = <AdminList<T>>::get(collection_id);10651066 match admin_arr.binary_search(&account_id) {1067 Ok(idx) => {1068 admin_arr.remove(idx);1069 <AdminList<T>>::insert(collection_id, admin_arr);1070 },1071 Err(_) => {}1072 }1073 Ok(())1074 }10751076 /// # Permissions1077 /// 1078 /// * Collection Owner1079 /// 1080 /// # Arguments1081 /// 1082 /// * collection_id.1083 /// 1084 /// * new_sponsor.1085 #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]1086 #[transactional]1087 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {1088 let sender = ensure_signed(origin)?;1089 let mut target_collection = Self::get_collection(collection_id)?;1090 Self::check_owner_permissions(&target_collection, &sender)?;10911092 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);1093 Self::save_collection(target_collection);10941095 Ok(())1096 }10971098 /// # Permissions1099 /// 1100 /// * Sponsor.1101 /// 1102 /// # Arguments1103 /// 1104 /// * collection_id.1105 #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]1106 #[transactional]1107 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {1108 let sender = ensure_signed(origin)?;11091110 let mut target_collection = Self::get_collection(collection_id)?;1111 ensure!(1112 target_collection.sponsorship.pending_sponsor() == Some(&sender),1113 Error::<T>::ConfirmUnsetSponsorFail1114 );11151116 target_collection.sponsorship = SponsorshipState::Confirmed(sender);1117 Self::save_collection(target_collection);11181119 Ok(())1120 }11211122 /// Switch back to pay-per-own-transaction model.1123 ///1124 /// # Permissions1125 ///1126 /// * Collection owner.1127 /// 1128 /// # Arguments1129 /// 1130 /// * collection_id.1131 #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]1132 #[transactional]1133 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {1134 let sender = ensure_signed(origin)?;11351136 let mut target_collection = Self::get_collection(collection_id)?;1137 Self::check_owner_permissions(&target_collection, &sender)?;11381139 target_collection.sponsorship = SponsorshipState::Disabled;1140 Self::save_collection(target_collection);11411142 Ok(())1143 }11441145 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.1146 /// 1147 /// # Permissions1148 /// 1149 /// * Collection Owner.1150 /// * Collection Admin.1151 /// * Anyone if1152 /// * White List is enabled, and1153 /// * Address is added to white list, and1154 /// * MintPermission is enabled (see SetMintPermission method)1155 /// 1156 /// # Arguments1157 /// 1158 /// * collection_id: ID of the collection.1159 /// 1160 /// * owner: Address, initial owner of the NFT.1161 ///1162 /// * data: Token data to store on chain.1163 // #[weight =1164 // (130_000_000 as Weight)1165 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))1166 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))1167 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]11681169 #[weight = <T as Config>::WeightInfo::create_item(data.len())]1170 #[transactional]1171 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {1172 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1173 let collection = Self::get_collection(collection_id)?;11741175 Self::create_item_internal(&sender, &collection, &owner, data)?;11761177 Self::submit_logs(collection)?;1178 Ok(())1179 }11801181 /// This method creates multiple items in a collection created with CreateCollection method.1182 /// 1183 /// # Permissions1184 /// 1185 /// * Collection Owner.1186 /// * Collection Admin.1187 /// * Anyone if1188 /// * White List is enabled, and1189 /// * Address is added to white list, and1190 /// * MintPermission is enabled (see SetMintPermission method)1191 /// 1192 /// # Arguments1193 /// 1194 /// * collection_id: ID of the collection.1195 /// 1196 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].1197 /// 1198 /// * owner: Address, initial owner of the NFT.1199 #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()1200 .map(|data| { data.len() })1201 .sum())]1202 #[transactional]1203 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {12041205 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);1206 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1207 let collection = Self::get_collection(collection_id)?;12081209 Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;12101211 Self::submit_logs(collection)?;1212 Ok(())1213 }12141215 /// Destroys a concrete instance of NFT.1216 /// 1217 /// # Permissions1218 /// 1219 /// * Collection Owner.1220 /// * Collection Admin.1221 /// * Current NFT Owner.1222 /// 1223 /// # Arguments1224 /// 1225 /// * collection_id: ID of the collection.1226 /// 1227 /// * item_id: ID of NFT to burn.1228 #[weight = <T as Config>::WeightInfo::burn_item()]1229 #[transactional]1230 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {12311232 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1233 let target_collection = Self::get_collection(collection_id)?;12341235 Self::burn_item_internal(&sender, &target_collection, item_id, value)?;12361237 Self::submit_logs(target_collection)?;1238 Ok(())1239 }12401241 /// Change ownership of the token.1242 /// 1243 /// # Permissions1244 /// 1245 /// * Collection Owner1246 /// * Collection Admin1247 /// * Current NFT owner1248 ///1249 /// # Arguments1250 /// 1251 /// * recipient: Address of token recipient.1252 /// 1253 /// * collection_id.1254 /// 1255 /// * item_id: ID of the item1256 /// * Non-Fungible Mode: Required.1257 /// * Fungible Mode: Ignored.1258 /// * Re-Fungible Mode: Required.1259 /// 1260 /// * value: Amount to transfer.1261 /// * Non-Fungible Mode: Ignored1262 /// * Fungible Mode: Must specify transferred amount1263 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1264 #[weight = <T as Config>::WeightInfo::transfer()]1265 #[transactional]1266 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {1267 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1268 let collection = Self::get_collection(collection_id)?;12691270 Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;12711272 Self::submit_logs(collection)?;1273 Ok(())1274 }12751276 /// Set, change, or remove approved address to transfer the ownership of the NFT.1277 /// 1278 /// # Permissions1279 /// 1280 /// * Collection Owner1281 /// * Collection Admin1282 /// * Current NFT owner1283 /// 1284 /// # Arguments1285 /// 1286 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1287 /// 1288 /// * collection_id.1289 /// 1290 /// * item_id: ID of the item.1291 #[weight = <T as Config>::WeightInfo::approve()]1292 #[transactional]1293 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1294 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1295 let collection = Self::get_collection(collection_id)?;12961297 Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;12981299 Self::submit_logs(collection)?;1300 Ok(())1301 }1302 1303 /// 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.1304 /// 1305 /// # Permissions1306 /// * Collection Owner1307 /// * Collection Admin1308 /// * Current NFT owner1309 /// * Address approved by current NFT owner1310 /// 1311 /// # Arguments1312 /// 1313 /// * from: Address that owns token.1314 /// 1315 /// * recipient: Address of token recipient.1316 /// 1317 /// * collection_id.1318 /// 1319 /// * item_id: ID of the item.1320 /// 1321 /// * value: Amount to transfer.1322 #[weight = <T as Config>::WeightInfo::transfer_from()]1323 #[transactional]1324 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1325 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1326 let collection = Self::get_collection(collection_id)?;13271328 Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;13291330 Self::submit_logs(collection)?;1331 Ok(())1332 }1333 // #[weight = 0]1334 // // let no_perm_mes = "You do not have permissions to modify this collection";1335 // // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1336 // // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1337 // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);13381339 // // // on_nft_received call13401341 // // Self::transfer(origin, collection_id, item_id, new_owner)?;13421343 // Ok(())1344 // }13451346 /// Set off-chain data schema.1347 /// 1348 /// # Permissions1349 /// 1350 /// * Collection Owner1351 /// * Collection Admin1352 /// 1353 /// # Arguments1354 /// 1355 /// * collection_id.1356 /// 1357 /// * schema: String representing the offchain data schema.1358 #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1359 #[transactional]1360 pub fn set_variable_meta_data (1361 origin,1362 collection_id: CollectionId,1363 item_id: TokenId,1364 data: Vec<u8>1365 ) -> DispatchResult {1366 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1367 1368 let collection = Self::get_collection(collection_id)?;13691370 Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;13711372 Ok(())1373 }1374 1375 /// Set schema standard1376 /// ImageURL1377 /// Unique1378 /// 1379 /// # Permissions1380 /// 1381 /// * Collection Owner1382 /// * Collection Admin1383 /// 1384 /// # Arguments1385 /// 1386 /// * collection_id.1387 /// 1388 /// * schema: SchemaVersion: enum1389 #[weight = <T as Config>::WeightInfo::set_schema_version()]1390 #[transactional]1391 pub fn set_schema_version(1392 origin,1393 collection_id: CollectionId,1394 version: SchemaVersion1395 ) -> DispatchResult {1396 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1397 let mut target_collection = Self::get_collection(collection_id)?;1398 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1399 target_collection.schema_version = version;1400 Self::save_collection(target_collection);14011402 Ok(())1403 }14041405 /// Set off-chain data schema.1406 /// 1407 /// # Permissions1408 /// 1409 /// * Collection Owner1410 /// * Collection Admin1411 /// 1412 /// # Arguments1413 /// 1414 /// * collection_id.1415 /// 1416 /// * schema: String representing the offchain data schema.1417 #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1418 #[transactional]1419 pub fn set_offchain_schema(1420 origin,1421 collection_id: CollectionId,1422 schema: Vec<u8>1423 ) -> DispatchResult {1424 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1425 let mut target_collection = Self::get_collection(collection_id)?;1426 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;14271428 // check schema limit1429 ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");14301431 target_collection.offchain_schema = schema;1432 Self::save_collection(target_collection);14331434 Ok(())1435 }14361437 /// Set const on-chain data schema.1438 /// 1439 /// # Permissions1440 /// 1441 /// * Collection Owner1442 /// * Collection Admin1443 /// 1444 /// # Arguments1445 /// 1446 /// * collection_id.1447 /// 1448 /// * schema: String representing the const on-chain data schema.1449 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1450 #[transactional]1451 pub fn set_const_on_chain_schema (1452 origin,1453 collection_id: CollectionId,1454 schema: Vec<u8>1455 ) -> DispatchResult {1456 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1457 let mut target_collection = Self::get_collection(collection_id)?;1458 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;14591460 // check schema limit1461 ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");14621463 target_collection.const_on_chain_schema = schema;1464 Self::save_collection(target_collection);14651466 Ok(())1467 }14681469 /// Set variable on-chain data schema.1470 /// 1471 /// # Permissions1472 /// 1473 /// * Collection Owner1474 /// * Collection Admin1475 /// 1476 /// # Arguments1477 /// 1478 /// * collection_id.1479 /// 1480 /// * schema: String representing the variable on-chain data schema.1481 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1482 #[transactional]1483 pub fn set_variable_on_chain_schema (1484 origin,1485 collection_id: CollectionId,1486 schema: Vec<u8>1487 ) -> DispatchResult {1488 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1489 let mut target_collection = Self::get_collection(collection_id)?;1490 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;14911492 // check schema limit1493 ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");14941495 target_collection.variable_on_chain_schema = schema;1496 Self::save_collection(target_collection);14971498 Ok(())1499 }15001501 // Sudo permissions function1502 #[weight = <T as Config>::WeightInfo::set_chain_limits()]1503 #[transactional]1504 pub fn set_chain_limits(1505 origin,1506 limits: ChainLimits1507 ) -> DispatchResult {15081509 #[cfg(not(feature = "runtime-benchmarks"))]1510 ensure_root(origin)?;15111512 <ChainLimit>::put(limits);1513 Ok(())1514 }15151516 /// Enable smart contract self-sponsoring.1517 /// 1518 /// # Permissions1519 /// 1520 /// * Contract Owner1521 /// 1522 /// # Arguments1523 /// 1524 /// * contract address1525 /// * enable flag1526 /// 1527 #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1528 #[transactional]1529 pub fn enable_contract_sponsoring(1530 origin,1531 contract_address: T::AccountId,1532 enable: bool1533 ) -> DispatchResult {15341535 let sender = ensure_signed(origin)?;15361537 #[cfg(feature = "runtime-benchmarks")]1538 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15391540 Self::ensure_contract_owned(sender, &contract_address)?;15411542 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1543 Ok(())1544 }15451546 /// Set the rate limit for contract sponsoring to specified number of blocks.1547 /// 1548 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1549 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1550 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1551 /// from contract endowment if there are at least B blocks between such transactions. 1552 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1553 /// 1554 /// # Permissions1555 /// 1556 /// * Contract Owner1557 /// 1558 /// # Arguments1559 /// 1560 /// -`contract_address`: Address of the contract to sponsor1561 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1562 /// 1563 #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1564 #[transactional]1565 pub fn set_contract_sponsoring_rate_limit(1566 origin,1567 contract_address: T::AccountId,1568 rate_limit: T::BlockNumber1569 ) -> DispatchResult {1570 let sender = ensure_signed(origin)?;15711572 #[cfg(feature = "runtime-benchmarks")]1573 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15741575 Self::ensure_contract_owned(sender, &contract_address)?;1576 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1577 Ok(())1578 }15791580 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1581 /// 1582 /// # Permissions1583 /// 1584 /// * Address that deployed smart contract.1585 /// 1586 /// # Arguments1587 /// 1588 /// -`contract_address`: Address of the contract.1589 /// 1590 /// - `enable`: . 1591 #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1592 #[transactional]1593 pub fn toggle_contract_white_list(1594 origin,1595 contract_address: T::AccountId,1596 enable: bool1597 ) -> DispatchResult {1598 let sender = ensure_signed(origin)?;15991600 #[cfg(feature = "runtime-benchmarks")]1601 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());16021603 Self::ensure_contract_owned(sender, &contract_address)?;1604 if enable {1605 <ContractWhiteListEnabled<T>>::insert(contract_address, true);1606 } else {1607 <ContractWhiteListEnabled<T>>::remove(contract_address);1608 }1609 Ok(())1610 }1611 1612 /// Add an address to smart contract white list.1613 /// 1614 /// # Permissions1615 /// 1616 /// * Address that deployed smart contract.1617 /// 1618 /// # Arguments1619 /// 1620 /// -`contract_address`: Address of the contract.1621 ///1622 /// -`account_address`: Address to add.1623 #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1624 #[transactional]1625 pub fn add_to_contract_white_list(1626 origin,1627 contract_address: T::AccountId,1628 account_address: T::AccountId1629 ) -> DispatchResult {1630 let sender = ensure_signed(origin)?;16311632 #[cfg(feature = "runtime-benchmarks")]1633 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1634 1635 Self::ensure_contract_owned(sender, &contract_address)?; 1636 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1637 Ok(())1638 }16391640 /// Remove an address from smart contract white list.1641 /// 1642 /// # Permissions1643 /// 1644 /// * Address that deployed smart contract.1645 /// 1646 /// # Arguments1647 /// 1648 /// -`contract_address`: Address of the contract.1649 ///1650 /// -`account_address`: Address to remove.1651 #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1652 #[transactional]1653 pub fn remove_from_contract_white_list(1654 origin,1655 contract_address: T::AccountId,1656 account_address: T::AccountId1657 ) -> DispatchResult {1658 let sender = ensure_signed(origin)?;16591660 #[cfg(feature = "runtime-benchmarks")]1661 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());16621663 Self::ensure_contract_owned(sender, &contract_address)?;1664 <ContractWhiteList<T>>::remove(contract_address, account_address);1665 Ok(())1666 }16671668 #[weight = <T as Config>::WeightInfo::set_collection_limits()]1669 #[transactional]1670 pub fn set_collection_limits(1671 origin,1672 collection_id: u32,1673 new_limits: CollectionLimits<T::BlockNumber>,1674 ) -> DispatchResult {1675 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1676 let mut target_collection = Self::get_collection(collection_id)?;1677 Self::check_owner_permissions(&target_collection, &sender.as_sub())?;1678 let old_limits = &target_collection.limits;1679 let chain_limits = ChainLimit::get();16801681 // collection bounds1682 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1683 new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && 1684 new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1685 Error::<T>::CollectionLimitBoundsExceeded);16861687 // token_limit check prev1688 ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1689 ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);16901691 ensure!(1692 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1693 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1694 Error::<T>::OwnerPermissionsCantBeReverted,1695 );16961697 target_collection.limits = new_limits;1698 Self::save_collection(target_collection);16991700 Ok(())1701 } 1702 }1703}17041705impl<T: Config> Module<T> {1706 pub fn create_item_internal(sender: &T::CrossAccountId, collection: &CollectionHandle<T>, owner: &T::CrossAccountId, data: CreateItemData) -> DispatchResult {1707 Self::can_create_items_in_collection(&collection, &sender, &owner, 1)?;1708 Self::validate_create_item_args(&collection, &data)?;1709 Self::create_item_no_validation(&collection, owner, data)?;17101711 Ok(())1712 }17131714 pub fn transfer_internal(sender: &T::CrossAccountId, recipient: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId, value: u128) -> DispatchResult {1715 target_collection.consume_gas(2000000)?;1716 // Limits check1717 Self::is_correct_transfer(target_collection, &recipient)?;17181719 // Transfer permissions check1720 ensure!(Self::is_item_owner(&sender, target_collection, item_id) ||1721 Self::is_owner_or_admin_permissions(target_collection, &sender),1722 Error::<T>::NoPermission);17231724 if target_collection.access == AccessMode::WhiteList {1725 Self::check_white_list(target_collection, &sender)?;1726 Self::check_white_list(target_collection, &recipient)?;1727 }17281729 match target_collection.mode1730 {1731 CollectionMode::NFT => Self::transfer_nft(target_collection, item_id, sender.clone(), recipient.clone())?,1732 CollectionMode::Fungible(_) => Self::transfer_fungible(target_collection, value, &sender, &recipient)?,1733 CollectionMode::ReFungible => Self::transfer_refungible(target_collection, item_id, value, sender.clone(), recipient.clone())?,1734 _ => ()1735 };17361737 Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, sender.clone(), recipient.clone(), value));17381739 Ok(())1740 }17411742 pub fn approve_internal(1743 sender: &T::CrossAccountId,1744 spender: &T::CrossAccountId,1745 collection: &CollectionHandle<T>,1746 item_id: TokenId,1747 amount: u1281748 ) -> DispatchResult {1749 collection.consume_gas(2000000)?;1750 Self::token_exists(&collection, item_id)?;17511752 // Transfer permissions check1753 let bypasses_limits = collection.limits.owner_can_transfer &&1754 Self::is_owner_or_admin_permissions(1755 &collection,1756 &sender,1757 );17581759 let allowance_limit = if bypasses_limits {1760 None1761 } else if let Some(amount) = Self::owned_amount(1762 &sender,1763 &collection,1764 item_id,1765 ) {1766 Some(amount)1767 } else {1768 fail!(Error::<T>::NoPermission);1769 };17701771 if collection.access == AccessMode::WhiteList {1772 Self::check_white_list(&collection, &sender)?;1773 Self::check_white_list(&collection, &spender)?;1774 }17751776 let allowance: u128 = amount1777 .checked_add(<Allowances<T>>::get(collection.id, (item_id, sender.as_sub(), spender.as_sub())))1778 .ok_or(Error::<T>::NumOverflow)?;1779 if let Some(limit) = allowance_limit {1780 ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1781 }1782 <Allowances<T>>::insert(collection.id, (item_id, sender.as_sub(), spender.as_sub()), allowance);17831784 if matches!(collection.mode, CollectionMode::NFT) {1785 // TODO: NFT: only one owner may exist for token in ERC7211786 collection.log(ERC721Events::Approval {1787 owner: *sender.as_eth(),1788 approved: *spender.as_eth(),1789 token_id: item_id.into(),1790 });1791 }17921793 if matches!(collection.mode, CollectionMode::Fungible(_)) {1794 // TODO: NFT: only one owner may exist for token in ERC201795 collection.log(ERC20Events::Approval {1796 owner: *sender.as_eth(),1797 spender: *spender.as_eth(),1798 value: allowance.into()1799 });1800 }18011802 Self::deposit_event(RawEvent::Approved(collection.id, item_id, sender.clone(), spender.clone(), allowance));1803 Ok(())1804 }18051806 pub fn transfer_from_internal(1807 sender: &T::CrossAccountId,1808 from: &T::CrossAccountId,1809 recipient: &T::CrossAccountId,1810 collection: &CollectionHandle<T>,1811 item_id: TokenId,1812 amount: u128,1813 ) -> DispatchResult {1814 collection.consume_gas(2000000)?;1815 // Check approval1816 let approval: u128 = <Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));18171818 // Limits check1819 Self::is_correct_transfer(&collection, &recipient)?;18201821 // Transfer permissions check1822 ensure!(1823 approval >= amount || 1824 (1825 collection.limits.owner_can_transfer &&1826 Self::is_owner_or_admin_permissions(&collection, &sender)1827 ),1828 Error::<T>::NoPermission1829 );18301831 if collection.access == AccessMode::WhiteList {1832 Self::check_white_list(&collection, &sender)?;1833 Self::check_white_list(&collection, &recipient)?;1834 }18351836 // Reduce approval by transferred amount or remove if remaining approval drops to 01837 let allowance = approval.saturating_sub(amount);1838 if allowance > 0 {1839 <Allowances<T>>::insert(collection.id, (item_id, from.as_sub(), sender.as_sub()), allowance);1840 } else {1841 <Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1842 }18431844 match collection.mode {1845 CollectionMode::NFT => {1846 Self::transfer_nft(&collection, item_id, from.clone(), recipient.clone())?1847 }1848 CollectionMode::Fungible(_) => {1849 Self::transfer_fungible(&collection, amount, &from, &recipient)?1850 }1851 CollectionMode::ReFungible => {1852 Self::transfer_refungible(&collection, item_id, amount, from.clone(), recipient.clone())?1853 }1854 _ => ()1855 };18561857 if matches!(collection.mode, CollectionMode::Fungible(_)) {1858 collection.log(ERC20Events::Approval {1859 owner: *from.as_eth(),1860 spender: *sender.as_eth(),1861 value: allowance.into()1862 });1863 }18641865 Ok(())1866 }18671868 pub fn set_variable_meta_data_internal(1869 sender: &T::CrossAccountId,1870 collection: &CollectionHandle<T>, 1871 item_id: TokenId,1872 data: Vec<u8>,1873 ) -> DispatchResult {1874 Self::token_exists(&collection, item_id)?;18751876 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);18771878 // Modify permissions check1879 ensure!(Self::is_item_owner(&sender, &collection, item_id) ||1880 Self::is_owner_or_admin_permissions(&collection, &sender),1881 Error::<T>::NoPermission);18821883 match collection.mode1884 {1885 CollectionMode::NFT => Self::set_nft_variable_data(&collection, item_id, data)?,1886 CollectionMode::ReFungible => Self::set_re_fungible_variable_data(&collection, item_id, data)?,1887 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1888 _ => fail!(Error::<T>::UnexpectedCollectionType)1889 };18901891 Ok(())1892 }18931894 pub fn create_multiple_items_internal(1895 sender: &T::CrossAccountId,1896 collection: &CollectionHandle<T>,1897 owner: &T::CrossAccountId,1898 items_data: Vec<CreateItemData>,1899 ) -> DispatchResult {1900 Self::can_create_items_in_collection(&collection, &sender, &owner, items_data.len() as u32)?;19011902 for data in &items_data {1903 Self::validate_create_item_args(&collection, data)?;1904 }1905 for data in &items_data {1906 Self::create_item_no_validation(&collection, owner, data.clone())?;1907 }19081909 Ok(())1910 }19111912 pub fn burn_item_internal(1913 sender: &T::CrossAccountId,1914 collection: &CollectionHandle<T>,1915 item_id: TokenId,1916 value: u128,1917 ) -> DispatchResult {1918 ensure!(1919 Self::is_item_owner(&sender, &collection, item_id) ||1920 (1921 collection.limits.owner_can_transfer &&1922 Self::is_owner_or_admin_permissions(&collection, &sender)1923 ),1924 Error::<T>::NoPermission1925 );19261927 if collection.access == AccessMode::WhiteList {1928 Self::check_white_list(&collection, &sender)?;1929 }19301931 match collection.mode1932 {1933 CollectionMode::NFT => Self::burn_nft_item(&collection, item_id)?,1934 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, &collection, value)?,1935 CollectionMode::ReFungible => Self::burn_refungible_item(&collection, item_id, &sender)?,1936 _ => ()1937 };19381939 Ok(())1940 }19411942 pub fn toggle_white_list_internal(1943 sender: &T::CrossAccountId,1944 collection: &CollectionHandle<T>,1945 address: &T::CrossAccountId,1946 whitelisted: bool,1947 ) -> DispatchResult {1948 Self::check_owner_or_admin_permissions(&collection, &sender)?;19491950 if whitelisted {1951 <WhiteList<T>>::insert(collection.id, address.as_sub(), true);1952 } else {1953 <WhiteList<T>>::remove(collection.id, address.as_sub());1954 }19551956 Ok(())1957 }19581959 fn is_correct_transfer(collection: &CollectionHandle<T>, recipient: &T::CrossAccountId) -> DispatchResult {1960 let collection_id = collection.id;19611962 // check token limit and account token limit1963 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1964 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1965 1966 Ok(())1967 }19681969 fn can_create_items_in_collection(collection: &CollectionHandle<T>, sender: &T::CrossAccountId, owner: &T::CrossAccountId, amount: u32) -> DispatchResult {1970 let collection_id = collection.id;19711972 // check token limit and account token limit1973 let total_items: u32 = ItemListIndex::get(collection_id)1974 .checked_add(amount)1975 .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1976 let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len() as u32)1977 .checked_add(amount)1978 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1979 ensure!(collection.limits.token_limit >= total_items, Error::<T>::CollectionTokenLimitExceeded);1980 ensure!(collection.limits.account_token_ownership_limit >= account_items, Error::<T>::AccountTokenLimitExceeded);19811982 if !Self::is_owner_or_admin_permissions(collection, &sender) {1983 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1984 Self::check_white_list(collection, owner)?;1985 Self::check_white_list(collection, sender)?;1986 }19871988 Ok(())1989 }19901991 fn validate_create_item_args(target_collection: &CollectionHandle<T>, data: &CreateItemData) -> DispatchResult {1992 match target_collection.mode1993 {1994 CollectionMode::NFT => {1995 if let CreateItemData::NFT(data) = data {1996 // check sizes1997 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1998 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1999 } else {2000 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);2001 }2002 },2003 CollectionMode::Fungible(_) => {2004 if let CreateItemData::Fungible(_) = data {2005 } else {2006 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);2007 }2008 },2009 CollectionMode::ReFungible => {2010 if let CreateItemData::ReFungible(data) = data {20112012 // check sizes2013 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);2014 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);20152016 // Check refungibility limits2017 ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);2018 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);2019 } else {2020 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);2021 }2022 },2023 _ => { fail!(Error::<T>::UnexpectedCollectionType); }2024 };20252026 Ok(())2027 }20282029 fn create_item_no_validation(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, data: CreateItemData) -> DispatchResult {2030 match data2031 {2032 CreateItemData::NFT(data) => {2033 let item = NftItemType {2034 owner: owner.clone(),2035 const_data: data.const_data,2036 variable_data: data.variable_data2037 };20382039 Self::add_nft_item(collection, item)?;2040 },2041 CreateItemData::Fungible(data) => {2042 Self::add_fungible_item(collection, &owner, data.value)?;2043 },2044 CreateItemData::ReFungible(data) => {2045 let mut owner_list = Vec::new();2046 owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});20472048 let item = ReFungibleItemType {2049 owner: owner_list,2050 const_data: data.const_data,2051 variable_data: data.variable_data2052 };20532054 Self::add_refungible_item(collection, item)?;2055 }2056 };20572058 Ok(())2059 }20602061 fn add_fungible_item(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, value: u128) -> DispatchResult {2062 let collection_id = collection.id;20632064 // Does new owner already have an account?2065 let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;20662067 // Mint 2068 let item = FungibleItemType {2069 value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,2070 };2071 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);20722073 // Update balance2074 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2075 .checked_add(value)2076 .ok_or(Error::<T>::NumOverflow)?;2077 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);20782079 Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));2080 Ok(())2081 }20822083 fn add_refungible_item(collection: &CollectionHandle<T>, item: ReFungibleItemType<T::CrossAccountId>) -> DispatchResult {2084 let collection_id = collection.id;20852086 let current_index = <ItemListIndex>::get(collection_id)2087 .checked_add(1)2088 .ok_or(Error::<T>::NumOverflow)?;2089 let itemcopy = item.clone();20902091 ensure!(2092 item.owner.len() == 1,2093 Error::<T>::BadCreateRefungibleCall,2094 );2095 let item_owner = item.owner.first().expect("only one owner is defined");20962097 let value = item_owner.fraction;2098 let owner = item_owner.owner.clone();20992100 Self::add_token_index(collection_id, current_index, &owner)?;21012102 <ItemListIndex>::insert(collection_id, current_index);2103 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);21042105 // Update balance2106 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2107 .checked_add(value)2108 .ok_or(Error::<T>::NumOverflow)?;2109 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);21102111 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));2112 Ok(())2113 }21142115 fn add_nft_item(collection: &CollectionHandle<T>, item: NftItemType<T::CrossAccountId>) -> DispatchResult {2116 let collection_id = collection.id;21172118 let current_index = <ItemListIndex>::get(collection_id)2119 .checked_add(1)2120 .ok_or(Error::<T>::NumOverflow)?;21212122 let item_owner = item.owner.clone();2123 Self::add_token_index(collection_id, current_index, &item.owner)?;21242125 <ItemListIndex>::insert(collection_id, current_index);2126 <NftItemList<T>>::insert(collection_id, current_index, item);21272128 // Update balance2129 let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())2130 .checked_add(1)2131 .ok_or(Error::<T>::NumOverflow)?;2132 <Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);21332134 collection.log(ERC721Events::Transfer {2135 from: H160::default(),2136 to: *item_owner.as_eth(),2137 token_id: current_index.into(),2138 });2139 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, item_owner));2140 Ok(())2141 }21422143 fn burn_refungible_item(2144 collection: &CollectionHandle<T>,2145 item_id: TokenId,2146 owner: &T::CrossAccountId,2147 ) -> DispatchResult {2148 let collection_id = collection.id;21492150 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)2151 .ok_or(Error::<T>::TokenNotFound)?;2152 let rft_balance = token2153 .owner2154 .iter()2155 .find(|&i| i.owner == *owner)2156 .ok_or(Error::<T>::TokenNotFound)?;2157 Self::remove_token_index(collection_id, item_id, owner)?;21582159 // update balance2160 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())2161 .checked_sub(rft_balance.fraction)2162 .ok_or(Error::<T>::NumOverflow)?;2163 <Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);21642165 // Re-create owners list with sender removed2166 let index = token2167 .owner2168 .iter()2169 .position(|i| i.owner == *owner)2170 .expect("owned item is exists");2171 token.owner.remove(index);2172 let owner_count = token.owner.len();21732174 // Burn the token completely if this was the last (only) owner2175 if owner_count == 0 {2176 <ReFungibleItemList<T>>::remove(collection_id, item_id);2177 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);2178 }2179 else {2180 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);2181 }21822183 Ok(())2184 }21852186 fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {2187 let collection_id = collection.id;21882189 let item = <NftItemList<T>>::get(collection_id, item_id)2190 .ok_or(Error::<T>::TokenNotFound)?;2191 Self::remove_token_index(collection_id, item_id, &item.owner)?;21922193 // update balance2194 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2195 .checked_sub(1)2196 .ok_or(Error::<T>::NumOverflow)?;2197 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2198 <NftItemList<T>>::remove(collection_id, item_id);2199 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);22002201 Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));2202 Ok(())2203 }22042205 fn burn_fungible_item(owner: &T::CrossAccountId, collection: &CollectionHandle<T>, value: u128) -> DispatchResult {2206 let collection_id = collection.id;22072208 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());2209 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);22102211 // update balance2212 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2213 .checked_sub(value)2214 .ok_or(Error::<T>::NumOverflow)?;2215 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);22162217 if balance.value - value > 0 {2218 balance.value -= value;2219 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2220 }2221 else {2222 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());2223 }22242225 collection.log(ERC20Events::Transfer {2226 from: *owner.as_eth(),2227 to: H160::default(),2228 value: value.into(),2229 });2230 Ok(())2231 }22322233 pub fn get_collection(collection_id: CollectionId) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {2234 Ok(<CollectionHandle<T>>::get(collection_id)2235 .ok_or(Error::<T>::CollectionNotFound)?)2236 }22372238 fn save_collection(collection: CollectionHandle<T>) {2239 <CollectionById<T>>::insert(collection.id, collection.into_inner());2240 }22412242 pub fn submit_logs(collection: CollectionHandle<T>) -> DispatchResult {2243 if collection.logs.is_empty() {2244 return Ok(())2245 }2246 T::EthereumTransactionSender::submit_logs_transaction(2247 eth::generate_transaction(collection.id, T::EthereumChainId::get()),2248 collection.logs.retrieve_logs(),2249 )2250 }22512252 fn check_owner_permissions(target_collection: &CollectionHandle<T>, subject: &T::AccountId) -> DispatchResult {2253 ensure!(2254 *subject == target_collection.owner,2255 Error::<T>::NoPermission2256 );22572258 Ok(())2259 }22602261 fn is_owner_or_admin_permissions(collection: &CollectionHandle<T>, subject: &T::CrossAccountId) -> bool {2262 *subject.as_sub() == collection.owner || <AdminList<T>>::get(collection.id).contains(&subject)2263 }22642265 fn check_owner_or_admin_permissions(2266 collection: &CollectionHandle<T>,2267 subject: &T::CrossAccountId,2268 ) -> DispatchResult {2269 ensure!(Self::is_owner_or_admin_permissions(collection, subject), Error::<T>::NoPermission);22702271 Ok(())2272 }22732274 fn owned_amount(2275 subject: &T::CrossAccountId,2276 target_collection: &CollectionHandle<T>,2277 item_id: TokenId,2278 ) -> Option<u128> {2279 let collection_id = target_collection.id;22802281 match target_collection.mode {2282 CollectionMode::NFT => (<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject)2283 .then(|| 1),2284 CollectionMode::Fungible(_) => Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub())2285 .value),2286 CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?2287 .owner2288 .iter()2289 .find(|i| i.owner == *subject)2290 .map(|i| i.fraction),2291 CollectionMode::Invalid => None,2292 }2293 }22942295 fn is_item_owner(subject: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId) -> bool {2296 match target_collection.mode {2297 CollectionMode::Fungible(_) => true,2298 _ => Self::owned_amount(&subject, target_collection, item_id).is_some(),2299 }2300 }23012302 fn check_white_list(collection: &CollectionHandle<T>, address: &T::CrossAccountId) -> DispatchResult {2303 let collection_id = collection.id;23042305 let mes = Error::<T>::AddresNotInWhiteList;2306 ensure!(<WhiteList<T>>::contains_key(collection_id, address.as_sub()), mes);23072308 Ok(())2309 }23102311 /// Check if token exists. In case of Fungible, check if there is an entry for 2312 /// the owner in fungible balances double map2313 fn token_exists(2314 target_collection: &CollectionHandle<T>,2315 item_id: TokenId,2316 ) -> DispatchResult {2317 let collection_id = target_collection.id;2318 let exists = match target_collection.mode2319 {2320 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),2321 CollectionMode::Fungible(_) => true,2322 CollectionMode::ReFungible => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),2323 _ => false2324 };23252326 ensure!(exists == true, Error::<T>::TokenNotFound);2327 Ok(())2328 }23292330 fn transfer_fungible(2331 collection: &CollectionHandle<T>,2332 value: u128,2333 owner: &T::CrossAccountId,2334 recipient: &T::CrossAccountId,2335 ) -> DispatchResult {2336 let collection_id = collection.id;23372338 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());2339 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);23402341 // Send balance to recipient (updates balanceOf of recipient)2342 Self::add_fungible_item(collection, recipient, value)?;23432344 // update balanceOf of sender2345 <Balance<T>>::insert(collection_id, owner.as_sub(), balance.value - value);23462347 // Reduce or remove sender2348 if balance.value == value {2349 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());2350 }2351 else {2352 balance.value -= value;2353 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2354 }23552356 collection.log(ERC20Events::Transfer {2357 from: *owner.as_eth(),2358 to: *recipient.as_eth(),2359 value: value.into(),2360 });2361 Self::deposit_event(RawEvent::Transfer(collection.id, 1, owner.clone(), recipient.clone(), value));23622363 Ok(())2364 }23652366 fn transfer_refungible(2367 collection: &CollectionHandle<T>,2368 item_id: TokenId,2369 value: u128,2370 owner: T::CrossAccountId,2371 new_owner: T::CrossAccountId,2372 ) -> DispatchResult {2373 let collection_id = collection.id;2374 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2375 .ok_or(Error::<T>::TokenNotFound)?;23762377 let item = full_item2378 .owner2379 .iter()2380 .filter(|i| i.owner == owner)2381 .next()2382 .ok_or(Error::<T>::TokenNotFound)?;2383 let amount = item.fraction;23842385 ensure!(amount >= value, Error::<T>::TokenValueTooLow);23862387 // update balance2388 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2389 .checked_sub(value)2390 .ok_or(Error::<T>::NumOverflow)?;2391 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);23922393 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2394 .checked_add(value)2395 .ok_or(Error::<T>::NumOverflow)?;2396 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);23972398 let old_owner = item.owner.clone();2399 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);24002401 // transfer2402 if amount == value && !new_owner_has_account {2403 // change owner2404 // new owner do not have account2405 let mut new_full_item = full_item.clone();2406 new_full_item2407 .owner2408 .iter_mut()2409 .find(|i| i.owner == owner)2410 .expect("old owner does present in refungible")2411 .owner = new_owner.clone();2412 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);24132414 // update index collection2415 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2416 } else {2417 let mut new_full_item = full_item.clone();2418 new_full_item2419 .owner2420 .iter_mut()2421 .find(|i| i.owner == owner)2422 .expect("old owner does present in refungible")2423 .fraction -= value;24242425 // separate amount2426 if new_owner_has_account {2427 // new owner has account2428 new_full_item2429 .owner2430 .iter_mut()2431 .find(|i| i.owner == new_owner)2432 .expect("new owner has account")2433 .fraction += value;2434 } else {2435 // new owner do not have account2436 new_full_item.owner.push(Ownership {2437 owner: new_owner.clone(),2438 fraction: value,2439 });2440 Self::add_token_index(collection_id, item_id, &new_owner)?;2441 }24422443 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2444 }24452446 Self::deposit_event(RawEvent::Transfer(collection.id, item_id, owner, new_owner, amount));24472448 Ok(())2449 }24502451 fn transfer_nft(2452 collection: &CollectionHandle<T>,2453 item_id: TokenId,2454 sender: T::CrossAccountId,2455 new_owner: T::CrossAccountId,2456 ) -> DispatchResult {2457 let collection_id = collection.id;2458 let mut item = <NftItemList<T>>::get(collection_id, item_id)2459 .ok_or(Error::<T>::TokenNotFound)?;24602461 ensure!(2462 sender == item.owner,2463 Error::<T>::MustBeTokenOwner2464 );24652466 // update balance2467 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2468 .checked_sub(1)2469 .ok_or(Error::<T>::NumOverflow)?;2470 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);24712472 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2473 .checked_add(1)2474 .ok_or(Error::<T>::NumOverflow)?;2475 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);24762477 // change owner2478 let old_owner = item.owner.clone();2479 item.owner = new_owner.clone();2480 <NftItemList<T>>::insert(collection_id, item_id, item);24812482 // update index collection2483 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;24842485 collection.log(ERC721Events::Transfer {2486 from: *sender.as_eth(),2487 to: *new_owner.as_eth(),2488 token_id: item_id.into(),2489 });2490 Self::deposit_event(RawEvent::Transfer(collection.id, item_id, sender, new_owner, 1));24912492 Ok(())2493 }2494 2495 fn set_re_fungible_variable_data(2496 collection: &CollectionHandle<T>,2497 item_id: TokenId,2498 data: Vec<u8>2499 ) -> DispatchResult {2500 let collection_id = collection.id;2501 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2502 .ok_or(Error::<T>::TokenNotFound)?;25032504 item.variable_data = data;25052506 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);25072508 Ok(())2509 }25102511 fn set_nft_variable_data(2512 collection: &CollectionHandle<T>,2513 item_id: TokenId,2514 data: Vec<u8>2515 ) -> DispatchResult {2516 let collection_id = collection.id;2517 let mut item = <NftItemList<T>>::get(collection_id, item_id)2518 .ok_or(Error::<T>::TokenNotFound)?;2519 2520 item.variable_data = data;25212522 <NftItemList<T>>::insert(collection_id, item_id, item);2523 2524 Ok(())2525 }25262527 fn init_collection(item: &Collection<T>) {2528 // check params2529 assert!(2530 item.decimal_points <= MAX_DECIMAL_POINTS,2531 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2532 );2533 assert!(2534 item.name.len() <= 64,2535 "Collection name can not be longer than 63 char"2536 );2537 assert!(2538 item.name.len() <= 256,2539 "Collection description can not be longer than 255 char"2540 );2541 assert!(2542 item.token_prefix.len() <= 16,2543 "Token prefix can not be longer than 15 char"2544 );25452546 // Generate next collection ID2547 let next_id = CreatedCollectionCount::get()2548 .checked_add(1)2549 .unwrap();25502551 CreatedCollectionCount::put(next_id);2552 }25532554 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2555 let current_index = <ItemListIndex>::get(collection_id)2556 .checked_add(1)2557 .unwrap();25582559 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();25602561 <ItemListIndex>::insert(collection_id, current_index);25622563 // Update balance2564 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2565 .checked_add(1)2566 .unwrap();2567 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2568 }25692570 fn init_fungible_token(collection_id: CollectionId, owner: &T::CrossAccountId, item: &FungibleItemType) {2571 let current_index = <ItemListIndex>::get(collection_id)2572 .checked_add(1)2573 .unwrap();25742575 Self::add_token_index(collection_id, current_index, owner).unwrap();25762577 <ItemListIndex>::insert(collection_id, current_index);25782579 // Update balance2580 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2581 .checked_add(item.value)2582 .unwrap();2583 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2584 }25852586 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::CrossAccountId>) {2587 let current_index = <ItemListIndex>::get(collection_id)2588 .checked_add(1)2589 .unwrap();25902591 let value = item.owner.first().unwrap().fraction;2592 let owner = item.owner.first().unwrap().owner.clone();25932594 Self::add_token_index(collection_id, current_index, &owner).unwrap();25952596 <ItemListIndex>::insert(collection_id, current_index);25972598 // Update balance2599 let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2600 .checked_add(value)2601 .unwrap();2602 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2603 }26042605 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::CrossAccountId) -> DispatchResult {2606 // add to account limit2607 if <AccountItemCount<T>>::contains_key(owner.as_sub()) {26082609 // bound Owned tokens by a single address2610 let count = <AccountItemCount<T>>::get(owner.as_sub());2611 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);26122613 <AccountItemCount<T>>::insert(owner.as_sub(), count2614 .checked_add(1)2615 .ok_or(Error::<T>::NumOverflow)?);2616 }2617 else {2618 <AccountItemCount<T>>::insert(owner.as_sub(), 1);2619 }26202621 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2622 if list_exists {2623 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2624 let item_contains = list.contains(&item_index.clone());26252626 if !item_contains {2627 list.push(item_index.clone());2628 }26292630 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2631 } else {2632 let mut itm = Vec::new();2633 itm.push(item_index.clone());2634 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), itm);2635 }26362637 Ok(())2638 }26392640 fn remove_token_index(2641 collection_id: CollectionId,2642 item_index: TokenId,2643 owner: &T::CrossAccountId,2644 ) -> DispatchResult {26452646 // update counter2647 <AccountItemCount<T>>::insert(owner.as_sub(), 2648 <AccountItemCount<T>>::get(owner.as_sub())2649 .checked_sub(1)2650 .ok_or(Error::<T>::NumOverflow)?);265126522653 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2654 if list_exists {2655 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2656 let item_contains = list.contains(&item_index.clone());26572658 if item_contains {2659 list.retain(|&item| item != item_index);2660 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2661 }2662 }26632664 Ok(())2665 }26662667 fn move_token_index(2668 collection_id: CollectionId,2669 item_index: TokenId,2670 old_owner: &T::CrossAccountId,2671 new_owner: &T::CrossAccountId,2672 ) -> DispatchResult {2673 Self::remove_token_index(collection_id, item_index, old_owner)?;2674 Self::add_token_index(collection_id, item_index, new_owner)?;26752676 Ok(())2677 }2678 2679 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2680 ensure!(<ContractOwner<T>>::get(contract) == Some(account), Error::<T>::NoPermission);26812682 Ok(())2683 }2684}26852686////////////////////////////////////////////////////////////////////////////////////////////////////2687// Economic models2688// #region26892690/// Fee multiplier.2691pub type Multiplier = FixedU128;26922693type BalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::Balance;26942695/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2696/// in the queue.2697#[derive(Encode, Decode, Clone, Eq, PartialEq)]2698pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);26992700impl<T: Config + Send + Sync> sp_std::fmt::Debug 2701 for ChargeTransactionPayment<T>2702{2703 #[cfg(feature = "std")]2704 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2705 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2706 }2707 #[cfg(not(feature = "std"))]2708 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2709 Ok(())2710 }2711}27122713impl<T: Config> ChargeTransactionPayment<T>2714where2715 T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2716 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2717 T::AccountId: AsRef<[u8]>,2718 T::AccountId: UncheckedFrom<T::Hash>,2719{2720 fn traditional_fee(2721 len: usize,2722 info: &DispatchInfoOf<T::Call>,2723 tip: BalanceOf<T>,2724 ) -> BalanceOf<T>2725 where2726 T::Call: Dispatchable<Info = DispatchInfo>,2727 {2728 <pallet_transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2729 }27302731 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2732 let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);2733 let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);2734 let len_saturation = max_block_length as u64 / (len as u64).max(1);2735 let coefficient: BalanceOf<T> = weight_saturation2736 .min(len_saturation)2737 .saturated_into::<BalanceOf<T>>();2738 final_fee2739 .saturating_mul(coefficient)2740 .saturated_into::<TransactionPriority>()2741 }27422743 fn withdraw_fee(2744 &self,2745 who: &T::AccountId,2746 call: &T::Call,2747 info: &DispatchInfoOf<T::Call>,2748 len: usize,2749 ) -> Result<2750 (2751 BalanceOf<T>,2752 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2753 ),2754 TransactionValidityError,2755 > {2756 let tip = self.0;27572758 let fee = Self::traditional_fee(len, info, tip);27592760 // Only mess with balances if fee is not zero.2761 if fee.is_zero() {2762 return <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)2763 .map(|i| (fee, i));2764 }27652766 // Determine who is paying transaction fee based on ecnomic model2767 // Parse call to extract collection ID and access collection sponsor2768 let mut sponsor: Option<T::AccountId> = (|| match IsSubType::<Call<T>>::is_sub_type(call) {2769 Some(Call::create_item(collection_id, _owner, _properties)) => {2770 let collection = <CollectionById<T>>::get(collection_id)?;27712772 // sponsor timeout2773 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;27742775 let limit = collection.limits.sponsor_transfer_timeout;2776 if <CreateItemBasket<T>>::contains_key((collection_id, &who)) {2777 let last_tx_block = <CreateItemBasket<T>>::get((collection_id, &who));2778 let limit_time = last_tx_block + limit.into();2779 if block_number <= limit_time {2780 return None;2781 }2782 }2783 <CreateItemBasket<T>>::insert((collection_id, who.clone()), block_number);27842785 // check free create limit2786 if collection.limits.sponsored_data_size >= (_properties.len() as u32) {2787 collection.sponsorship.sponsor()2788 .cloned()2789 } else {2790 None2791 }2792 }2793 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2794 let collection = <CollectionById<T>>::get(collection_id)?;2795 2796 let mut sponsor_transfer = false;2797 if collection.sponsorship.confirmed() {27982799 let collection_limits = collection.limits;2800 let collection_mode = collection.mode;2801 2802 // sponsor timeout2803 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2804 sponsor_transfer = match collection_mode {2805 CollectionMode::NFT => {2806 2807 // get correct limit2808 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2809 collection_limits.sponsor_transfer_timeout2810 } else {2811 ChainLimit::get().nft_sponsor_transfer_timeout2812 };2813 2814 let mut sponsored = true;2815 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2816 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2817 let limit_time = last_tx_block + limit.into();2818 if block_number <= limit_time {2819 sponsored = false;2820 }2821 }2822 if sponsored {2823 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2824 }28252826 sponsored2827 }2828 CollectionMode::Fungible(_) => {2829 2830 // get correct limit2831 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2832 collection_limits.sponsor_transfer_timeout2833 } else {2834 ChainLimit::get().fungible_sponsor_transfer_timeout2835 };2836 2837 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2838 let mut sponsored = true;2839 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2840 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2841 let limit_time = last_tx_block + limit.into();2842 if block_number <= limit_time {2843 sponsored = false;2844 }2845 }2846 if sponsored {2847 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2848 }28492850 sponsored2851 }2852 CollectionMode::ReFungible => {2853 2854 // get correct limit2855 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2856 collection_limits.sponsor_transfer_timeout2857 } else {2858 ChainLimit::get().refungible_sponsor_transfer_timeout2859 };2860 2861 let mut sponsored = true;2862 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2863 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2864 let limit_time = last_tx_block + limit.into();2865 if block_number <= limit_time {2866 sponsored = false;2867 }2868 }2869 if sponsored {2870 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2871 }28722873 sponsored2874 }2875 _ => {2876 false2877 },2878 };2879 }28802881 if !sponsor_transfer {2882 None2883 } else {2884 collection.sponsorship.sponsor()2885 .cloned()2886 }2887 }28882889 Some(Call::set_variable_meta_data(collection_id, item_id, data)) => {2890 let mut sponsor_metadata_changes = false;28912892 let collection = <CollectionById<T>>::get(collection_id)?;28932894 if2895 collection.sponsorship.confirmed() &&2896 // Can't sponsor fungible collection, this tx will be rejected2897 // as invalid2898 !matches!(collection.mode, CollectionMode::Fungible(_)) &&2899 data.len() <= collection.limits.sponsored_data_size as usize2900 {2901 if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit {2902 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;29032904 if <VariableMetaDataBasket<T>>::get(collection_id, item_id)2905 .map(|last_block| block_number - last_block > rate_limit)2906 .unwrap_or(true) 2907 {2908 sponsor_metadata_changes = true;2909 <VariableMetaDataBasket<T>>::insert(collection_id, item_id, block_number);2910 }2911 }2912 }29132914 if !sponsor_metadata_changes {2915 None2916 } else {2917 collection.sponsorship.sponsor().cloned()2918 }2919 }29202921 _ => None,2922 })();29232924 match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {2925 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {29262927 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());29282929 let owned_contract = <ContractOwner<T>>::get(called_contract.clone()).as_ref() == Some(who);2930 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone());2931 2932 if !owned_contract && white_list_enabled {2933 if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2934 return Err(InvalidTransaction::Call.into());2935 }2936 }2937 },2938 _ => {},2939 }29402941 // Sponsor smart contracts2942 sponsor = sponsor.or_else(|| match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {29432944 // On instantiation: set the contract owner2945 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {29462947 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2948 &who,2949 code_hash,2950 salt,2951 );2952 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());29532954 None2955 },29562957 // On instantiation with code: set the contract owner2958 Some(pallet_contracts::Call::instantiate_with_code(_endowment, _gas_limit, _code, _data, _salt)) => {29592960 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2961 &who,2962 &T::Hashing::hash(&_code),2963 _salt,2964 );29652966 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());29672968 None2969 }29702971 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2972 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {29732974 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());29752976 let mut sponsor_transfer = false;2977 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2978 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2979 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2980 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2981 let limit_time = last_tx_block + rate_limit;29822983 if block_number >= limit_time {2984 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2985 sponsor_transfer = true;2986 }2987 } else {2988 sponsor_transfer = false;2989 }2990 2991 if sponsor_transfer {2992 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2993 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2994 return Some(called_contract);2995 }2996 }2997 }29982999 None3000 },30013002 _ => None,3003 });30043005 let who_pays_fee = sponsor.unwrap_or_else(|| who.clone());30063007 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(&who_pays_fee, call, info, fee, tip)3008 .map(|i| (fee, i))3009 }3010}301130123013impl<T: Config + Send + Sync> SignedExtension3014 for ChargeTransactionPayment<T>3015where3016 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,3017 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,3018 T::AccountId: AsRef<[u8]>,3019 T::AccountId: UncheckedFrom<T::Hash>,3020{3021 const IDENTIFIER: &'static str = "ChargeTransactionPayment";3022 type AccountId = T::AccountId;3023 type Call = T::Call;3024 type AdditionalSigned = ();3025 type Pre = (3026 // tip3027 BalanceOf<T>,3028 // who pays fee3029 Self::AccountId,3030 // imbalance resulting from withdrawing the fee3031 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,3032 );3033 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {3034 Ok(())3035 }30363037 fn validate(3038 &self,3039 who: &Self::AccountId,3040 call: &Self::Call,3041 info: &DispatchInfoOf<Self::Call>,3042 len: usize,3043 ) -> TransactionValidity {3044 let (fee, _) = self.withdraw_fee(who, call, info, len)?;3045 Ok(ValidTransaction {3046 priority: Self::get_priority(len, info, fee),3047 ..Default::default()3048 })3049 }30503051 fn pre_dispatch(3052 self,3053 who: &Self::AccountId,3054 call: &Self::Call,3055 info: &DispatchInfoOf<Self::Call>,3056 len: usize,3057 ) -> Result<Self::Pre, TransactionValidityError> {3058 let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;3059 Ok((self.0, who.clone(), imbalance))3060 }30613062 fn post_dispatch(3063 pre: Self::Pre,3064 info: &DispatchInfoOf<Self::Call>,3065 post_info: &PostDispatchInfoOf<Self::Call>,3066 len: usize,3067 _result: &DispatchResult,3068 ) -> Result<(), TransactionValidityError> {3069 let (tip, who, imbalance) = pre;3070 let actual_fee = pallet_transaction_payment::Module::<T>::compute_actual_fee(3071 len as u32,3072 info,3073 post_info,3074 tip,3075 );3076 <T as pallet_transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;3077 Ok(())3078 }3079}30803081// #endregion30823083sp_api::decl_runtime_apis! {3084 pub trait NftApi {3085 /// Used for ethereum integration3086 fn eth_contract_code(account: H160) -> Option<Vec<u8>>;3087 }3088}