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)]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}