difftreelog
fix nft indexed logs
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)]910#[cfg(feature = "std")]11pub use serde::*;1213use core::ops::{Deref, DerefMut};14use codec::{Decode, Encode};15pub use frame_support::{16 construct_runtime, decl_event, decl_module, decl_storage, decl_error,17 dispatch::DispatchResult,18 ensure, fail, parameter_types,19 traits::{20 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,21 Randomness, IsSubType, WithdrawReasons,22 },23 weights::{24 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},25 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,26 WeightToFeePolynomial, DispatchClass,27 },28 StorageValue,29 transactional,30};3132use frame_system::{self as system, ensure_signed, ensure_root};33use sp_core::{H160, H256};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36 traits::{37 Hash, DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38 },39 transaction_validity::{40 TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41 },42 FixedPointOperand, FixedU128,43};44use sp_runtime::traits::StaticLookup;45use pallet_contracts::chain_extension::UncheckedFrom;46use pallet_ethereum::EthereumTransactionSender;47use pallet_transaction_payment::OnChargeTransaction;4849#[cfg(test)]50mod mock;5152#[cfg(test)]53mod tests;5455mod default_weights;56mod eth;5758pub use eth::NftErcSupport;59pub use eth::account::*;6061pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;62pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;63pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;64pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;6566// Structs67// #region6869pub type CollectionId = u32;70pub type TokenId = u32;71pub type DecimalPoints = u8;7273#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]74#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]75pub enum CollectionMode {76 Invalid,77 NFT,78 // decimal points79 Fungible(DecimalPoints),80 ReFungible,81}8283impl Default for CollectionMode {84 fn default() -> Self {85 Self::Invalid86 }87}8889impl Into<u8> for CollectionMode {90 fn into(self) -> u8 {91 match self {92 CollectionMode::Invalid => 0,93 CollectionMode::NFT => 1,94 CollectionMode::Fungible(_) => 2,95 CollectionMode::ReFungible => 3,96 }97 }98}99100#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]101#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]102pub enum AccessMode {103 Normal,104 WhiteList,105}106impl Default for AccessMode {107 fn default() -> Self {108 Self::Normal109 }110}111112#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]113#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]114pub enum SchemaVersion {115 ImageURL,116 Unique,117}118impl Default for SchemaVersion {119 fn default() -> Self {120 Self::ImageURL121 }122}123124#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]125#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]126pub struct Ownership<AccountId> {127 pub owner: AccountId,128 pub fraction: u128,129}130131#[derive(Encode, Decode, Debug, Clone, PartialEq)]132#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]133pub enum SponsorshipState<AccountId> {134 /// The fees are applied to the transaction sender135 Disabled,136 Unconfirmed(AccountId),137 /// Transactions are sponsored by specified account138 Confirmed(AccountId),139}140141impl<AccountId> SponsorshipState<AccountId> {142 fn sponsor(&self) -> Option<&AccountId> {143 match self {144 Self::Confirmed(sponsor) => Some(sponsor),145 _ => None,146 }147 }148149 fn pending_sponsor(&self) -> Option<&AccountId> {150 match self {151 Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),152 _ => None,153 }154 }155156 fn confirmed(&self) -> bool {157 matches!(self, Self::Confirmed(_))158 }159}160161impl<T> Default for SponsorshipState<T> {162 fn default() -> Self {163 Self::Disabled164 }165}166167#[derive(Encode, Decode, Clone, PartialEq)]168#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]169pub struct Collection<T: Config> {170 pub owner: T::CrossAccountId,171 pub mode: CollectionMode,172 pub access: AccessMode,173 pub decimal_points: DecimalPoints,174 pub name: Vec<u16>, // 64 include null escape char175 pub description: Vec<u16>, // 256 include null escape char176 pub token_prefix: Vec<u8>, // 16 include null escape char177 pub mint_mode: bool,178 pub offchain_schema: Vec<u8>,179 pub schema_version: SchemaVersion,180 pub sponsorship: SponsorshipState<T::AccountId>,181 pub limits: CollectionLimits<T::BlockNumber>, // Collection private restrictions 182 pub variable_on_chain_schema: Vec<u8>, //183 pub const_on_chain_schema: Vec<u8>, //184}185186pub struct CollectionHandle<T: Config> {187 pub id: CollectionId,188 collection: Collection<T>,189 logs: eth::log::LogRecorder,190}191impl<T: Config> CollectionHandle<T> {192 pub fn get(id: CollectionId) -> Option<Self> {193 <CollectionById<T>>::get(id)194 .map(|collection| Self {195 id,196 collection,197 logs: eth::log::LogRecorder::default(),198 })199 }200 pub fn log(&self, topics: Vec<H256>, data: eth::abi::AbiWriter) {201 self.logs.log(topics, data)202 }203 pub fn into_inner(self) -> Collection<T> {204 self.collection.clone()205 }206}207208impl<T: Config> Deref for CollectionHandle<T> {209 type Target = Collection<T>;210211 fn deref(&self) -> &Self::Target {212 &self.collection213 }214}215216impl<T: Config> DerefMut for CollectionHandle<T> {217 fn deref_mut(&mut self) -> &mut Self::Target {218 &mut self.collection219 }220}221222#[derive(Encode, Decode, Debug, Clone, PartialEq)]223#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]224pub struct NftItemType<AccountId> {225 pub owner: AccountId,226 pub const_data: Vec<u8>,227 pub variable_data: Vec<u8>,228}229230#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]231#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]232pub struct FungibleItemType {233 pub value: u128,234}235236#[derive(Encode, Decode, Debug, Clone, PartialEq)]237#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]238pub struct ReFungibleItemType<AccountId> {239 pub owner: Vec<Ownership<AccountId>>,240 pub const_data: Vec<u8>,241 pub variable_data: Vec<u8>,242}243244// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]245// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]246// pub struct VestingItem<AccountId, Moment> {247// pub sender: AccountId,248// pub recipient: AccountId,249// pub collection_id: CollectionId,250// pub item_id: TokenId,251// pub amount: u64,252// pub vesting_date: Moment,253// }254255#[derive(Encode, Decode, Debug, Clone, PartialEq)]256#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]257pub struct CollectionLimits<BlockNumber: Encode + Decode> {258 pub account_token_ownership_limit: u32,259 pub sponsored_data_size: u32,260 /// None - setVariableMetadata is not sponsored261 /// Some(v) - setVariableMetadata is sponsored 262 /// if there is v block between txs263 pub sponsored_data_rate_limit: Option<BlockNumber>,264 pub token_limit: u32,265266 // Timeouts for item types in passed blocks267 pub sponsor_transfer_timeout: u32,268 pub owner_can_transfer: bool,269 pub owner_can_destroy: bool,270}271272impl<BlockNumber: Encode + Decode> Default for CollectionLimits<BlockNumber> {273 fn default() -> Self {274 Self { 275 account_token_ownership_limit: 10_000_000, 276 token_limit: u32::max_value(),277 sponsored_data_size: u32::MAX, 278 sponsored_data_rate_limit: None,279 sponsor_transfer_timeout: 14400,280 owner_can_transfer: true,281 owner_can_destroy: true282 }283 }284}285286#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]287#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]288pub struct ChainLimits {289 pub collection_numbers_limit: u32,290 pub account_token_ownership_limit: u32,291 pub collections_admins_limit: u64,292 pub custom_data_limit: u32,293294 // Timeouts for item types in passed blocks295 pub nft_sponsor_transfer_timeout: u32,296 pub fungible_sponsor_transfer_timeout: u32,297 pub refungible_sponsor_transfer_timeout: u32,298299 // Schema limits300 pub offchain_schema_limit: u32,301 pub variable_on_chain_schema_limit: u32,302 pub const_on_chain_schema_limit: u32,303}304305pub trait WeightInfo {306 fn create_collection() -> Weight;307 fn destroy_collection() -> Weight;308 fn add_to_white_list() -> Weight;309 fn remove_from_white_list() -> Weight;310 fn set_public_access_mode() -> Weight;311 fn set_mint_permission() -> Weight;312 fn change_collection_owner() -> Weight;313 fn add_collection_admin() -> Weight;314 fn remove_collection_admin() -> Weight;315 fn set_collection_sponsor() -> Weight;316 fn confirm_sponsorship() -> Weight;317 fn remove_collection_sponsor() -> Weight;318 fn create_item(s: usize) -> Weight;319 fn burn_item() -> Weight;320 fn transfer() -> Weight;321 fn approve() -> Weight;322 fn transfer_from() -> Weight;323 fn set_offchain_schema() -> Weight;324 fn set_const_on_chain_schema() -> Weight;325 fn set_variable_on_chain_schema() -> Weight;326 fn set_variable_meta_data() -> Weight;327 fn enable_contract_sponsoring() -> Weight;328 fn set_schema_version() -> Weight;329 fn set_chain_limits() -> Weight;330 fn set_contract_sponsoring_rate_limit() -> Weight;331 fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;332 fn toggle_contract_white_list() -> Weight;333 fn add_to_contract_white_list() -> Weight;334 fn remove_from_contract_white_list() -> Weight;335 fn set_collection_limits() -> Weight;336}337338#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]339#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]340pub struct CreateNftData {341 pub const_data: Vec<u8>,342 pub variable_data: Vec<u8>,343}344345#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]346#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]347pub struct CreateFungibleData {348 pub value: u128,349}350351#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]352#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]353pub struct CreateReFungibleData {354 pub const_data: Vec<u8>,355 pub variable_data: Vec<u8>,356 pub pieces: u128,357}358359#[derive(Encode, Decode, Debug, Clone, PartialEq)]360#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]361pub enum CreateItemData {362 NFT(CreateNftData),363 Fungible(CreateFungibleData),364 ReFungible(CreateReFungibleData),365}366367impl CreateItemData {368 pub fn len(&self) -> usize {369 let len = match self {370 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),371 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),372 _ => 0373 };374 375 return len;376 }377}378379impl From<CreateNftData> for CreateItemData {380 fn from(item: CreateNftData) -> Self {381 CreateItemData::NFT(item)382 }383}384385impl From<CreateReFungibleData> for CreateItemData {386 fn from(item: CreateReFungibleData) -> Self {387 CreateItemData::ReFungible(item)388 }389}390391impl From<CreateFungibleData> for CreateItemData {392 fn from(item: CreateFungibleData) -> Self {393 CreateItemData::Fungible(item)394 }395}396397398decl_error! {399 /// Error for non-fungible-token module.400 pub enum Error for Module<T: Config> {401 /// Total collections bound exceeded.402 TotalCollectionsLimitExceeded,403 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.404 CollectionDecimalPointLimitExceeded, 405 /// Collection name can not be longer than 63 char.406 CollectionNameLimitExceeded, 407 /// Collection description can not be longer than 255 char.408 CollectionDescriptionLimitExceeded, 409 /// Token prefix can not be longer than 15 char.410 CollectionTokenPrefixLimitExceeded,411 /// This collection does not exist.412 CollectionNotFound,413 /// Item not exists.414 TokenNotFound,415 /// Admin not found416 AdminNotFound,417 /// Arithmetic calculation overflow.418 NumOverflow, 419 /// Account already has admin role.420 AlreadyAdmin, 421 /// You do not own this collection.422 NoPermission,423 /// This address is not set as sponsor, use setCollectionSponsor first.424 ConfirmUnsetSponsorFail,425 /// Collection is not in mint mode.426 PublicMintingNotAllowed,427 /// Sender parameter and item owner must be equal.428 MustBeTokenOwner,429 /// Item balance not enough.430 TokenValueTooLow,431 /// Size of item is too large.432 NftSizeLimitExceeded,433 /// No approve found434 ApproveNotFound,435 /// Requested value more than approved.436 TokenValueNotEnough,437 /// Only approved addresses can call this method.438 ApproveRequired,439 /// Address is not in white list.440 AddresNotInWhiteList,441 /// Number of collection admins bound exceeded.442 CollectionAdminsLimitExceeded,443 /// Owned tokens by a single address bound exceeded.444 AddressOwnershipLimitExceeded,445 /// Length of items properties must be greater than 0.446 EmptyArgument,447 /// const_data exceeded data limit.448 TokenConstDataLimitExceeded,449 /// variable_data exceeded data limit.450 TokenVariableDataLimitExceeded,451 /// Not NFT item data used to mint in NFT collection.452 NotNftDataUsedToMintNftCollectionToken,453 /// Not Fungible item data used to mint in Fungible collection.454 NotFungibleDataUsedToMintFungibleCollectionToken,455 /// Not Re Fungible item data used to mint in Re Fungible collection.456 NotReFungibleDataUsedToMintReFungibleCollectionToken,457 /// Unexpected collection type.458 UnexpectedCollectionType,459 /// Can't store metadata in fungible tokens.460 CantStoreMetadataInFungibleTokens,461 /// Collection token limit exceeded462 CollectionTokenLimitExceeded,463 /// Account token limit exceeded per collection464 AccountTokenLimitExceeded,465 /// Collection limit bounds per collection exceeded466 CollectionLimitBoundsExceeded,467 /// Tried to enable permissions which are only permitted to be disabled468 OwnerPermissionsCantBeReverted,469 /// Schema data size limit bound exceeded470 SchemaDataLimitExceeded,471 /// Maximum refungibility exceeded472 WrongRefungiblePieces,473 /// createRefungible should be called with one owner474 BadCreateRefungibleCall,475 }476}477478pub trait Config: system::Config + Sized + pallet_transaction_payment::Config + pallet_contracts::Config {479 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;480481 /// Weight information for extrinsics in this pallet.482 type WeightInfo: WeightInfo;483484 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;485 type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;486 type EvmWithdrawOrigin: pallet_evm::EnsureAddressOrigin<Self::Origin, Success = Self::AccountId>;487488 type CrossAccountId: CrossAccountId<Self::AccountId>;489 type Currency: Currency<Self::AccountId>;490 type CollectionCreationPrice: Get<<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance>;491 type TreasuryAccountId: Get<Self::AccountId>;492493 type EthereumChainId: Get<u64>;494 type EthereumTransactionSender: pallet_ethereum::EthereumTransactionSender;495}496497#[cfg(feature = "runtime-benchmarks")]498mod benchmarking;499500// #endregion501502// # Used definitions503//504// ## User control levels505//506// chain-controlled - key is uncontrolled by user507// i.e autoincrementing index508// can use non-cryptographic hash509// real - key is controlled by user510// but it is hard to generate enough colliding values, i.e owner of signed txs511// can use non-cryptographic hash512// controlled - key is completly controlled by users513// i.e maps with mutable keys514// should use cryptographic hash515//516// ## User control level downgrade reasons517//518// ?1 - chain-controlled -> controlled519// collections/tokens can be destroyed, resulting in massive holes520// ?2 - chain-controlled -> controlled521// same as ?1, but can be only added, resulting in easier exploitation522// ?3 - real -> controlled523// no confirmation required, so addresses can be easily generated524decl_storage! {525 trait Store for Module<T: Config> as Nft {526527 //#region Private members528 /// Id of next collection529 CreatedCollectionCount: u32;530 /// Used for migrations531 ChainVersion: u64;532 /// Id of last collection token533 /// Collection id (controlled?1)534 ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;535 //#endregion536537 //#region Chain limits struct538 pub ChainLimit get(fn chain_limit) config(): ChainLimits;539 //#endregion540541 //#region Bound counters542 /// Amount of collections destroyed, used for total amount tracking with543 /// CreatedCollectionCount544 DestroyedCollectionCount: u32;545 /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)546 /// Account id (real)547 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;548 //#endregion549550 //#region Basic collections551 /// Collection info552 /// Collection id (controlled?1)553 pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;554 /// List of collection admins555 /// Collection id (controlled?2)556 pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;557 /// Whitelisted collection users558 /// Collection id (controlled?2), user id (controlled?3)559 pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;560 //#endregion561562 /// How many of collection items user have563 /// Collection id (controlled?2), account id (real)564 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;565566 /// Amount of items which spender can transfer out of owners account (via transferFrom)567 /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))568 /// TODO: Off chain worker should remove from this map when token gets removed569 pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;570571 //#region Item collections572 /// Collection id (controlled?2), token id (controlled?1)573 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;574 /// Collection id (controlled?2), owner (controlled?2)575 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;576 /// Collection id (controlled?2), token id (controlled?1)577 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;578 //#endregion579580 //#region Index list581 /// Collection id (controlled?2), tokens owner (controlled?2)582 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;583 //#endregion584585 //#region Tokens transfer rate limit baskets586 /// (Collection id (controlled?2), who created (real))587 /// TODO: Off chain worker should remove from this map when collection gets removed588 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;589 /// Collection id (controlled?2), token id (controlled?2)590 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;591 /// Collection id (controlled?2), owning user (real)592 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;593 /// Collection id (controlled?2), token id (controlled?2)594 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;595 //#endregion596597 /// Variable metadata sponsoring598 /// Collection id (controlled?2), token id (controlled?2)599 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;600 601 //#region Contract Sponsorship and Ownership602 /// Contract address (real)603 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => Option<T::AccountId>;604 /// Contract address (real)605 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;606 /// (Contract address(real), caller (real))607 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;608 /// Contract address (real)609 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;610 /// Contract address (real)611 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 612 /// Contract address (real) => Whitelisted user (controlled?3)613 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(blake2_128_concat) T::AccountId => bool; 614 //#endregion615 }616 add_extra_genesis {617 build(|config: &GenesisConfig<T>| {618 // Modification of storage619 for (_num, _c) in &config.collection_id {620 <Module<T>>::init_collection(_c);621 }622623 for (_num, _c, _i) in &config.nft_item_id {624 <Module<T>>::init_nft_token(*_c, _i);625 }626627 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {628 <Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);629 }630631 for (_num, _c, _i) in &config.refungible_item_id {632 <Module<T>>::init_refungible_token(*_c, _i);633 }634 })635 }636}637638decl_event!(639 pub enum Event<T>640 where641 CrossAccountId = <T as Config>::CrossAccountId,642 {643 /// New collection was created644 /// 645 /// # Arguments646 /// 647 /// * collection_id: Globally unique identifier of newly created collection.648 /// 649 /// * mode: [CollectionMode] converted into u8.650 /// 651 /// * account_id: Collection owner.652 CollectionCreated(CollectionId, u8, CrossAccountId),653654 /// New item was created.655 /// 656 /// # Arguments657 /// 658 /// * collection_id: Id of the collection where item was created.659 /// 660 /// * item_id: Id of an item. Unique within the collection.661 ///662 /// * recipient: Owner of newly created item 663 ItemCreated(CollectionId, TokenId, CrossAccountId),664665 /// Collection item was burned.666 /// 667 /// # Arguments668 /// 669 /// collection_id.670 /// 671 /// item_id: Identifier of burned NFT.672 ItemDestroyed(CollectionId, TokenId),673674 /// Item was transferred675 ///676 /// * collection_id: Id of collection to which item is belong677 ///678 /// * item_id: Id of an item679 ///680 /// * sender: Original owner of item681 ///682 /// * recipient: New owner of item683 ///684 /// * amount: Always 1 for NFT685 Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),686687 /// * collection_id688 ///689 /// * item_id690 ///691 /// * sender692 ///693 /// * spender694 ///695 /// * amount696 Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),697 }698);699700decl_module! {701 pub struct Module<T: Config> for enum Call 702 where 703 origin: T::Origin704 {705 fn deposit_event() = default;706 type Error = Error<T>;707708 fn on_initialize(now: T::BlockNumber) -> Weight {709 0710 }711712 /// 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.713 /// 714 /// # Permissions715 /// 716 /// * Anyone.717 /// 718 /// # Arguments719 /// 720 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.721 /// 722 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.723 /// 724 /// * token_prefix: UTF-8 string with token prefix.725 /// 726 /// * mode: [CollectionMode] collection type and type dependent data.727 // returns collection ID728 #[weight = <T as Config>::WeightInfo::create_collection()]729 #[transactional]730 pub fn create_collection(origin,731 collection_name: Vec<u16>,732 collection_description: Vec<u16>,733 token_prefix: Vec<u8>,734 mode: CollectionMode) -> DispatchResult {735736 // Anyone can create a collection737 let who = T::CrossAccountId::from_sub(ensure_signed(origin)?);738739 // Take a (non-refundable) deposit of collection creation740 let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();741 imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(742 &T::TreasuryAccountId::get(),743 T::CollectionCreationPrice::get(),744 ));745 <T as Config>::Currency::settle(746 who.as_sub(),747 imbalance,748 WithdrawReasons::TRANSFER,749 ExistenceRequirement::KeepAlive,750 ).map_err(|_| Error::<T>::NoPermission)?;751752 let decimal_points = match mode {753 CollectionMode::Fungible(points) => points,754 _ => 0755 };756757 let chain_limit = ChainLimit::get();758759 let created_count = CreatedCollectionCount::get();760 let destroyed_count = DestroyedCollectionCount::get();761762 // bound Total number of collections763 ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);764765 // check params766 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);767 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);768 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);769 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);770771 // Generate next collection ID772 let next_id = created_count773 .checked_add(1)774 .ok_or(Error::<T>::NumOverflow)?;775776 CreatedCollectionCount::put(next_id);777778 let limits = CollectionLimits {779 sponsored_data_size: chain_limit.custom_data_limit,780 ..Default::default()781 };782783 // Create new collection784 let new_collection = Collection {785 owner: who.clone(),786 name: collection_name,787 mode: mode.clone(),788 mint_mode: false,789 access: AccessMode::Normal,790 description: collection_description,791 decimal_points: decimal_points,792 token_prefix: token_prefix,793 offchain_schema: Vec::new(),794 schema_version: SchemaVersion::ImageURL,795 sponsorship: SponsorshipState::Disabled,796 variable_on_chain_schema: Vec::new(),797 const_on_chain_schema: Vec::new(),798 limits,799 };800801 // Add new collection to map802 <CollectionById<T>>::insert(next_id, new_collection);803804 // call event805 Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.into(), who));806807 Ok(())808 }809810 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.811 /// 812 /// # Permissions813 /// 814 /// * Collection Owner.815 /// 816 /// # Arguments817 /// 818 /// * collection_id: collection to destroy.819 #[weight = <T as Config>::WeightInfo::destroy_collection()]820 #[transactional]821 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {822823 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);824 let collection = Self::get_collection(collection_id)?;825 Self::check_owner_permissions(&collection, &sender)?;826 if !collection.limits.owner_can_destroy {827 fail!(Error::<T>::NoPermission);828 }829830 <AddressTokens<T>>::remove_prefix(collection_id);831 <Allowances<T>>::remove_prefix(collection_id);832 <Balance<T>>::remove_prefix(collection_id);833 <ItemListIndex>::remove(collection_id);834 <AdminList<T>>::remove(collection_id);835 <CollectionById<T>>::remove(collection_id);836 <WhiteList<T>>::remove_prefix(collection_id);837838 <NftItemList<T>>::remove_prefix(collection_id);839 <FungibleItemList<T>>::remove_prefix(collection_id);840 <ReFungibleItemList<T>>::remove_prefix(collection_id);841842 <NftTransferBasket<T>>::remove_prefix(collection_id);843 <FungibleTransferBasket<T>>::remove_prefix(collection_id);844 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);845846 <VariableMetaDataBasket<T>>::remove_prefix(collection_id);847848 DestroyedCollectionCount::put(DestroyedCollectionCount::get()849 .checked_add(1)850 .ok_or(Error::<T>::NumOverflow)?);851852 Ok(())853 }854855 /// Add an address to white list.856 /// 857 /// # Permissions858 /// 859 /// * Collection Owner860 /// * Collection Admin861 /// 862 /// # Arguments863 /// 864 /// * collection_id.865 /// 866 /// * address.867 #[weight = <T as Config>::WeightInfo::add_to_white_list()]868 #[transactional]869 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{870871 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);872 let collection = Self::get_collection(collection_id)?;873874 Self::toggle_white_list_internal(875 &sender,876 &collection,877 &address,878 true,879 )?;880881 Ok(())882 }883884 /// Remove an address from white list.885 /// 886 /// # Permissions887 /// 888 /// * Collection Owner889 /// * Collection Admin890 /// 891 /// # Arguments892 /// 893 /// * collection_id.894 /// 895 /// * address.896 #[weight = <T as Config>::WeightInfo::remove_from_white_list()]897 #[transactional]898 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{899900 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);901 let collection = Self::get_collection(collection_id)?;902903 Self::toggle_white_list_internal(904 &sender,905 &collection,906 &address,907 false,908 )?;909910 Ok(())911 }912913 /// Toggle between normal and white list access for the methods with access for `Anyone`.914 /// 915 /// # Permissions916 /// 917 /// * Collection Owner.918 /// 919 /// # Arguments920 /// 921 /// * collection_id.922 /// 923 /// * mode: [AccessMode]924 #[weight = <T as Config>::WeightInfo::set_public_access_mode()]925 #[transactional]926 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult927 {928 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);929930 let mut target_collection = Self::get_collection(collection_id)?;931 Self::check_owner_permissions(&target_collection, &sender)?;932 target_collection.access = mode;933 Self::save_collection(target_collection);934935 Ok(())936 }937938 /// Allows Anyone to create tokens if:939 /// * White List is enabled, and940 /// * Address is added to white list, and941 /// * This method was called with True parameter942 /// 943 /// # Permissions944 /// * Collection Owner945 ///946 /// # Arguments947 /// 948 /// * collection_id.949 /// 950 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.951 #[weight = <T as Config>::WeightInfo::set_mint_permission()]952 #[transactional]953 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult954 {955 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);956957 let mut target_collection = Self::get_collection(collection_id)?;958 Self::check_owner_permissions(&target_collection, &sender)?;959 target_collection.mint_mode = mint_permission;960 Self::save_collection(target_collection);961962 Ok(())963 }964965 /// Change the owner of the collection.966 /// 967 /// # Permissions968 /// 969 /// * Collection Owner.970 /// 971 /// # Arguments972 /// 973 /// * collection_id.974 /// 975 /// * new_owner.976 #[weight = <T as Config>::WeightInfo::change_collection_owner()]977 #[transactional]978 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::CrossAccountId) -> DispatchResult {979980 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);981 let mut target_collection = Self::get_collection(collection_id)?;982 Self::check_owner_permissions(&target_collection, &sender)?;983 target_collection.owner = new_owner;984 Self::save_collection(target_collection);985986 Ok(())987 }988989 /// Adds an admin of the Collection.990 /// 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. 991 /// 992 /// # Permissions993 /// 994 /// * Collection Owner.995 /// * Collection Admin.996 /// 997 /// # Arguments998 /// 999 /// * collection_id: ID of the Collection to add admin for.1000 /// 1001 /// * new_admin_id: Address of new admin to add.1002 #[weight = <T as Config>::WeightInfo::add_collection_admin()]1003 #[transactional]1004 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {1005 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1006 let collection = Self::get_collection(collection_id)?;1007 Self::check_owner_or_admin_permissions(&collection, &sender)?;1008 let mut admin_arr = <AdminList<T>>::get(collection_id);10091010 match admin_arr.binary_search(&new_admin_id) {1011 Ok(_) => {},1012 Err(idx) => {1013 let limits = ChainLimit::get();1014 ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);1015 admin_arr.insert(idx, new_admin_id);1016 <AdminList<T>>::insert(collection_id, admin_arr);1017 }1018 }1019 Ok(())1020 }10211022 /// 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.1023 ///1024 /// # Permissions1025 /// 1026 /// * Collection Owner.1027 /// * Collection Admin.1028 /// 1029 /// # Arguments1030 /// 1031 /// * collection_id: ID of the Collection to remove admin for.1032 /// 1033 /// * account_id: Address of admin to remove.1034 #[weight = <T as Config>::WeightInfo::remove_collection_admin()]1035 #[transactional]1036 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {1037 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1038 let collection = Self::get_collection(collection_id)?;1039 Self::check_owner_or_admin_permissions(&collection, &sender)?;1040 let mut admin_arr = <AdminList<T>>::get(collection_id);10411042 match admin_arr.binary_search(&account_id) {1043 Ok(idx) => {1044 admin_arr.remove(idx);1045 <AdminList<T>>::insert(collection_id, admin_arr);1046 },1047 Err(_) => {}1048 }1049 Ok(())1050 }10511052 /// # Permissions1053 /// 1054 /// * Collection Owner1055 /// 1056 /// # Arguments1057 /// 1058 /// * collection_id.1059 /// 1060 /// * new_sponsor.1061 #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]1062 #[transactional]1063 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {1064 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1065 let mut target_collection = Self::get_collection(collection_id)?;1066 Self::check_owner_permissions(&target_collection, &sender)?;10671068 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);1069 Self::save_collection(target_collection);10701071 Ok(())1072 }10731074 /// # Permissions1075 /// 1076 /// * Sponsor.1077 /// 1078 /// # Arguments1079 /// 1080 /// * collection_id.1081 #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]1082 #[transactional]1083 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {1084 let sender = ensure_signed(origin)?;10851086 let mut target_collection = Self::get_collection(collection_id)?;1087 ensure!(1088 target_collection.sponsorship.pending_sponsor() == Some(&sender),1089 Error::<T>::ConfirmUnsetSponsorFail1090 );10911092 target_collection.sponsorship = SponsorshipState::Confirmed(sender);1093 Self::save_collection(target_collection);10941095 Ok(())1096 }10971098 /// Switch back to pay-per-own-transaction model.1099 ///1100 /// # Permissions1101 ///1102 /// * Collection owner.1103 /// 1104 /// # Arguments1105 /// 1106 /// * collection_id.1107 #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]1108 #[transactional]1109 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {1110 let sender = ensure_signed(origin)?;11111112 let mut target_collection = Self::get_collection(collection_id)?;1113 Self::check_owner_permissions(&target_collection, &T::CrossAccountId::from_sub(sender))?;11141115 target_collection.sponsorship = SponsorshipState::Disabled;1116 Self::save_collection(target_collection);11171118 Ok(())1119 }11201121 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.1122 /// 1123 /// # Permissions1124 /// 1125 /// * Collection Owner.1126 /// * Collection Admin.1127 /// * Anyone if1128 /// * White List is enabled, and1129 /// * Address is added to white list, and1130 /// * MintPermission is enabled (see SetMintPermission method)1131 /// 1132 /// # Arguments1133 /// 1134 /// * collection_id: ID of the collection.1135 /// 1136 /// * owner: Address, initial owner of the NFT.1137 ///1138 /// * data: Token data to store on chain.1139 // #[weight =1140 // (130_000_000 as Weight)1141 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))1142 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))1143 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]11441145 #[weight = <T as Config>::WeightInfo::create_item(data.len())]1146 #[transactional]1147 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {1148 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1149 let collection = Self::get_collection(collection_id)?;11501151 Self::create_item_internal(&sender, &collection, &owner, data)?;11521153 Self::submit_logs(collection)?;1154 Ok(())1155 }11561157 /// This method creates multiple items in a collection created with CreateCollection method.1158 /// 1159 /// # Permissions1160 /// 1161 /// * Collection Owner.1162 /// * Collection Admin.1163 /// * Anyone if1164 /// * White List is enabled, and1165 /// * Address is added to white list, and1166 /// * MintPermission is enabled (see SetMintPermission method)1167 /// 1168 /// # Arguments1169 /// 1170 /// * collection_id: ID of the collection.1171 /// 1172 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].1173 /// 1174 /// * owner: Address, initial owner of the NFT.1175 #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()1176 .map(|data| { data.len() })1177 .sum())]1178 #[transactional]1179 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {11801181 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);1182 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1183 let collection = Self::get_collection(collection_id)?;11841185 Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;11861187 Self::submit_logs(collection)?;1188 Ok(())1189 }11901191 /// Destroys a concrete instance of NFT.1192 /// 1193 /// # Permissions1194 /// 1195 /// * Collection Owner.1196 /// * Collection Admin.1197 /// * Current NFT Owner.1198 /// 1199 /// # Arguments1200 /// 1201 /// * collection_id: ID of the collection.1202 /// 1203 /// * item_id: ID of NFT to burn.1204 #[weight = <T as Config>::WeightInfo::burn_item()]1205 #[transactional]1206 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {12071208 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1209 let target_collection = Self::get_collection(collection_id)?;12101211 Self::burn_item_internal(&sender, &target_collection, item_id, value)?;12121213 Self::submit_logs(target_collection)?;1214 Ok(())1215 }12161217 /// Change ownership of the token.1218 /// 1219 /// # Permissions1220 /// 1221 /// * Collection Owner1222 /// * Collection Admin1223 /// * Current NFT owner1224 ///1225 /// # Arguments1226 /// 1227 /// * recipient: Address of token recipient.1228 /// 1229 /// * collection_id.1230 /// 1231 /// * item_id: ID of the item1232 /// * Non-Fungible Mode: Required.1233 /// * Fungible Mode: Ignored.1234 /// * Re-Fungible Mode: Required.1235 /// 1236 /// * value: Amount to transfer.1237 /// * Non-Fungible Mode: Ignored1238 /// * Fungible Mode: Must specify transferred amount1239 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1240 #[weight = <T as Config>::WeightInfo::transfer()]1241 #[transactional]1242 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {1243 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1244 let collection = Self::get_collection(collection_id)?;12451246 Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;12471248 Self::submit_logs(collection)?;1249 Ok(())1250 }12511252 /// Set, change, or remove approved address to transfer the ownership of the NFT.1253 /// 1254 /// # Permissions1255 /// 1256 /// * Collection Owner1257 /// * Collection Admin1258 /// * Current NFT owner1259 /// 1260 /// # Arguments1261 /// 1262 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1263 /// 1264 /// * collection_id.1265 /// 1266 /// * item_id: ID of the item.1267 #[weight = <T as Config>::WeightInfo::approve()]1268 #[transactional]1269 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1270 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1271 let collection = Self::get_collection(collection_id)?;12721273 Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;12741275 Self::submit_logs(collection)?;1276 Ok(())1277 }1278 1279 /// 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.1280 /// 1281 /// # Permissions1282 /// * Collection Owner1283 /// * Collection Admin1284 /// * Current NFT owner1285 /// * Address approved by current NFT owner1286 /// 1287 /// # Arguments1288 /// 1289 /// * from: Address that owns token.1290 /// 1291 /// * recipient: Address of token recipient.1292 /// 1293 /// * collection_id.1294 /// 1295 /// * item_id: ID of the item.1296 /// 1297 /// * value: Amount to transfer.1298 #[weight = <T as Config>::WeightInfo::transfer_from()]1299 #[transactional]1300 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1301 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1302 let collection = Self::get_collection(collection_id)?;13031304 Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;13051306 Self::submit_logs(collection)?;1307 Ok(())1308 }1309 // #[weight = 0]1310 // // let no_perm_mes = "You do not have permissions to modify this collection";1311 // // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1312 // // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1313 // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);13141315 // // // on_nft_received call13161317 // // Self::transfer(origin, collection_id, item_id, new_owner)?;13181319 // Ok(())1320 // }13211322 /// Set off-chain data schema.1323 /// 1324 /// # Permissions1325 /// 1326 /// * Collection Owner1327 /// * Collection Admin1328 /// 1329 /// # Arguments1330 /// 1331 /// * collection_id.1332 /// 1333 /// * schema: String representing the offchain data schema.1334 #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1335 #[transactional]1336 pub fn set_variable_meta_data (1337 origin,1338 collection_id: CollectionId,1339 item_id: TokenId,1340 data: Vec<u8>1341 ) -> DispatchResult {1342 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1343 1344 let collection = Self::get_collection(collection_id)?;13451346 Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;13471348 Ok(())1349 }1350 1351 /// Set schema standard1352 /// ImageURL1353 /// Unique1354 /// 1355 /// # Permissions1356 /// 1357 /// * Collection Owner1358 /// * Collection Admin1359 /// 1360 /// # Arguments1361 /// 1362 /// * collection_id.1363 /// 1364 /// * schema: SchemaVersion: enum1365 #[weight = <T as Config>::WeightInfo::set_schema_version()]1366 #[transactional]1367 pub fn set_schema_version(1368 origin,1369 collection_id: CollectionId,1370 version: SchemaVersion1371 ) -> DispatchResult {1372 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1373 let mut target_collection = Self::get_collection(collection_id)?;1374 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1375 target_collection.schema_version = version;1376 Self::save_collection(target_collection);13771378 Ok(())1379 }13801381 /// Set off-chain data schema.1382 /// 1383 /// # Permissions1384 /// 1385 /// * Collection Owner1386 /// * Collection Admin1387 /// 1388 /// # Arguments1389 /// 1390 /// * collection_id.1391 /// 1392 /// * schema: String representing the offchain data schema.1393 #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1394 #[transactional]1395 pub fn set_offchain_schema(1396 origin,1397 collection_id: CollectionId,1398 schema: Vec<u8>1399 ) -> DispatchResult {1400 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1401 let mut target_collection = Self::get_collection(collection_id)?;1402 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;14031404 // check schema limit1405 ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");14061407 target_collection.offchain_schema = schema;1408 Self::save_collection(target_collection);14091410 Ok(())1411 }14121413 /// Set const on-chain data schema.1414 /// 1415 /// # Permissions1416 /// 1417 /// * Collection Owner1418 /// * Collection Admin1419 /// 1420 /// # Arguments1421 /// 1422 /// * collection_id.1423 /// 1424 /// * schema: String representing the const on-chain data schema.1425 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1426 #[transactional]1427 pub fn set_const_on_chain_schema (1428 origin,1429 collection_id: CollectionId,1430 schema: Vec<u8>1431 ) -> DispatchResult {1432 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1433 let mut target_collection = Self::get_collection(collection_id)?;1434 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;14351436 // check schema limit1437 ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");14381439 target_collection.const_on_chain_schema = schema;1440 Self::save_collection(target_collection);14411442 Ok(())1443 }14441445 /// Set variable on-chain data schema.1446 /// 1447 /// # Permissions1448 /// 1449 /// * Collection Owner1450 /// * Collection Admin1451 /// 1452 /// # Arguments1453 /// 1454 /// * collection_id.1455 /// 1456 /// * schema: String representing the variable on-chain data schema.1457 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1458 #[transactional]1459 pub fn set_variable_on_chain_schema (1460 origin,1461 collection_id: CollectionId,1462 schema: Vec<u8>1463 ) -> DispatchResult {1464 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1465 let mut target_collection = Self::get_collection(collection_id)?;1466 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;14671468 // check schema limit1469 ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");14701471 target_collection.variable_on_chain_schema = schema;1472 Self::save_collection(target_collection);14731474 Ok(())1475 }14761477 // Sudo permissions function1478 #[weight = <T as Config>::WeightInfo::set_chain_limits()]1479 #[transactional]1480 pub fn set_chain_limits(1481 origin,1482 limits: ChainLimits1483 ) -> DispatchResult {14841485 #[cfg(not(feature = "runtime-benchmarks"))]1486 ensure_root(origin)?;14871488 <ChainLimit>::put(limits);1489 Ok(())1490 }14911492 /// Enable smart contract self-sponsoring.1493 /// 1494 /// # Permissions1495 /// 1496 /// * Contract Owner1497 /// 1498 /// # Arguments1499 /// 1500 /// * contract address1501 /// * enable flag1502 /// 1503 #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1504 #[transactional]1505 pub fn enable_contract_sponsoring(1506 origin,1507 contract_address: T::AccountId,1508 enable: bool1509 ) -> DispatchResult {15101511 let sender = ensure_signed(origin)?;15121513 #[cfg(feature = "runtime-benchmarks")]1514 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15151516 Self::ensure_contract_owned(sender, &contract_address)?;15171518 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1519 Ok(())1520 }15211522 /// Set the rate limit for contract sponsoring to specified number of blocks.1523 /// 1524 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1525 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1526 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1527 /// from contract endowment if there are at least B blocks between such transactions. 1528 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1529 /// 1530 /// # Permissions1531 /// 1532 /// * Contract Owner1533 /// 1534 /// # Arguments1535 /// 1536 /// -`contract_address`: Address of the contract to sponsor1537 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1538 /// 1539 #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1540 #[transactional]1541 pub fn set_contract_sponsoring_rate_limit(1542 origin,1543 contract_address: T::AccountId,1544 rate_limit: T::BlockNumber1545 ) -> DispatchResult {1546 let sender = ensure_signed(origin)?;15471548 #[cfg(feature = "runtime-benchmarks")]1549 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15501551 Self::ensure_contract_owned(sender, &contract_address)?;1552 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1553 Ok(())1554 }15551556 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1557 /// 1558 /// # Permissions1559 /// 1560 /// * Address that deployed smart contract.1561 /// 1562 /// # Arguments1563 /// 1564 /// -`contract_address`: Address of the contract.1565 /// 1566 /// - `enable`: . 1567 #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1568 #[transactional]1569 pub fn toggle_contract_white_list(1570 origin,1571 contract_address: T::AccountId,1572 enable: bool1573 ) -> DispatchResult {1574 let sender = ensure_signed(origin)?;15751576 #[cfg(feature = "runtime-benchmarks")]1577 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15781579 Self::ensure_contract_owned(sender, &contract_address)?;1580 if enable {1581 <ContractWhiteListEnabled<T>>::insert(contract_address, true);1582 } else {1583 <ContractWhiteListEnabled<T>>::remove(contract_address);1584 }1585 Ok(())1586 }1587 1588 /// Add an address to smart contract white list.1589 /// 1590 /// # Permissions1591 /// 1592 /// * Address that deployed smart contract.1593 /// 1594 /// # Arguments1595 /// 1596 /// -`contract_address`: Address of the contract.1597 ///1598 /// -`account_address`: Address to add.1599 #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1600 #[transactional]1601 pub fn add_to_contract_white_list(1602 origin,1603 contract_address: T::AccountId,1604 account_address: T::AccountId1605 ) -> DispatchResult {1606 let sender = ensure_signed(origin)?;16071608 #[cfg(feature = "runtime-benchmarks")]1609 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1610 1611 Self::ensure_contract_owned(sender, &contract_address)?; 1612 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1613 Ok(())1614 }16151616 /// Remove an address from smart contract white list.1617 /// 1618 /// # Permissions1619 /// 1620 /// * Address that deployed smart contract.1621 /// 1622 /// # Arguments1623 /// 1624 /// -`contract_address`: Address of the contract.1625 ///1626 /// -`account_address`: Address to remove.1627 #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1628 #[transactional]1629 pub fn remove_from_contract_white_list(1630 origin,1631 contract_address: T::AccountId,1632 account_address: T::AccountId1633 ) -> DispatchResult {1634 let sender = ensure_signed(origin)?;16351636 #[cfg(feature = "runtime-benchmarks")]1637 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());16381639 Self::ensure_contract_owned(sender, &contract_address)?;1640 <ContractWhiteList<T>>::remove(contract_address, account_address);1641 Ok(())1642 }16431644 #[weight = <T as Config>::WeightInfo::set_collection_limits()]1645 #[transactional]1646 pub fn set_collection_limits(1647 origin,1648 collection_id: u32,1649 new_limits: CollectionLimits<T::BlockNumber>,1650 ) -> DispatchResult {1651 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1652 let mut target_collection = Self::get_collection(collection_id)?;1653 Self::check_owner_permissions(&target_collection, &sender)?;1654 let old_limits = &target_collection.limits;1655 let chain_limits = ChainLimit::get();16561657 // collection bounds1658 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1659 new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && 1660 new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1661 Error::<T>::CollectionLimitBoundsExceeded);16621663 // token_limit check prev1664 ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1665 ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);16661667 ensure!(1668 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1669 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1670 Error::<T>::OwnerPermissionsCantBeReverted,1671 );16721673 target_collection.limits = new_limits;1674 Self::save_collection(target_collection);16751676 Ok(())1677 } 1678 }1679}16801681impl<T: Config> Module<T> {1682 pub fn create_item_internal(sender: &T::CrossAccountId, collection: &CollectionHandle<T>, owner: &T::CrossAccountId, data: CreateItemData) -> DispatchResult {1683 Self::can_create_items_in_collection(&collection, &sender, &owner, 1)?;1684 Self::validate_create_item_args(&collection, &data)?;1685 Self::create_item_no_validation(&collection, owner, data)?;16861687 Ok(())1688 }16891690 pub fn transfer_internal(sender: &T::CrossAccountId, recipient: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId, value: u128) -> DispatchResult {1691 // Limits check1692 Self::is_correct_transfer(target_collection, &recipient)?;16931694 // Transfer permissions check1695 ensure!(Self::is_item_owner(&sender, target_collection, item_id) ||1696 Self::is_owner_or_admin_permissions(target_collection, &sender),1697 Error::<T>::NoPermission);16981699 if target_collection.access == AccessMode::WhiteList {1700 Self::check_white_list(target_collection, &sender)?;1701 Self::check_white_list(target_collection, &recipient)?;1702 }17031704 match target_collection.mode1705 {1706 CollectionMode::NFT => Self::transfer_nft(target_collection, item_id, sender.clone(), recipient.clone())?,1707 CollectionMode::Fungible(_) => Self::transfer_fungible(target_collection, value, &sender, &recipient)?,1708 CollectionMode::ReFungible => Self::transfer_refungible(target_collection, item_id, value, sender.clone(), recipient.clone())?,1709 _ => ()1710 };17111712 Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, sender.clone(), recipient.clone(), value));17131714 Ok(())1715 }17161717 pub fn approve_internal(1718 sender: &T::CrossAccountId,1719 spender: &T::CrossAccountId,1720 collection: &CollectionHandle<T>,1721 item_id: TokenId,1722 amount: u1281723 ) -> DispatchResult {1724 Self::token_exists(&collection, item_id)?;17251726 // Transfer permissions check1727 let bypasses_limits = collection.limits.owner_can_transfer &&1728 Self::is_owner_or_admin_permissions(1729 &collection,1730 &sender,1731 );17321733 let allowance_limit = if bypasses_limits {1734 None1735 } else if let Some(amount) = Self::owned_amount(1736 &sender,1737 &collection,1738 item_id,1739 ) {1740 Some(amount)1741 } else {1742 fail!(Error::<T>::NoPermission);1743 };17441745 if collection.access == AccessMode::WhiteList {1746 Self::check_white_list(&collection, &sender)?;1747 Self::check_white_list(&collection, &spender)?;1748 }17491750 let allowance: u128 = amount1751 .checked_add(<Allowances<T>>::get(collection.id, (item_id, sender.as_sub(), spender.as_sub())))1752 .ok_or(Error::<T>::NumOverflow)?;1753 if let Some(limit) = allowance_limit {1754 ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1755 }1756 <Allowances<T>>::insert(collection.id, (item_id, sender.as_sub(), spender.as_sub()), allowance);17571758 if matches!(collection.mode, CollectionMode::NFT) {1759 // TODO: NFT: only one owner may exist for token in ERC7211760 collection.log(1761 Vec::from([1762 eth::APPROVAL_NFT_TOPIC,1763 eth::address_to_topic(sender.as_eth()),1764 eth::address_to_topic(spender.as_eth()),1765 ]),1766 abi_encode!(uint256(item_id.into())),1767 );1768 }17691770 if matches!(collection.mode, CollectionMode::Fungible(_)) {1771 // TODO: NFT: only one owner may exist for token in ERC201772 collection.log(1773 Vec::from([1774 eth::APPROVAL_FUNGIBLE_TOPIC,1775 eth::address_to_topic(sender.as_eth()),1776 eth::address_to_topic(spender.as_eth()),1777 ]),1778 abi_encode!(uint256(allowance.into())),1779 );1780 }17811782 Self::deposit_event(RawEvent::Approved(collection.id, item_id, sender.clone(), spender.clone(), allowance));1783 Ok(())1784 }17851786 pub fn transfer_from_internal(1787 sender: &T::CrossAccountId,1788 from: &T::CrossAccountId,1789 recipient: &T::CrossAccountId,1790 collection: &CollectionHandle<T>,1791 item_id: TokenId,1792 amount: u128,1793 ) -> DispatchResult {1794 // Check approval1795 let approval: u128 = <Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));17961797 // Limits check1798 Self::is_correct_transfer(&collection, &recipient)?;17991800 // Transfer permissions check1801 ensure!(1802 approval >= amount || 1803 (1804 collection.limits.owner_can_transfer &&1805 Self::is_owner_or_admin_permissions(&collection, &sender)1806 ),1807 Error::<T>::NoPermission1808 );18091810 if collection.access == AccessMode::WhiteList {1811 Self::check_white_list(&collection, &sender)?;1812 Self::check_white_list(&collection, &recipient)?;1813 }18141815 // Reduce approval by transferred amount or remove if remaining approval drops to 01816 let allowance = approval.saturating_sub(amount);1817 if allowance > 0 {1818 <Allowances<T>>::insert(collection.id, (item_id, from.as_sub(), sender.as_sub()), allowance);1819 } else {1820 <Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1821 }18221823 match collection.mode {1824 CollectionMode::NFT => {1825 Self::transfer_nft(&collection, item_id, from.clone(), recipient.clone())?1826 }1827 CollectionMode::Fungible(_) => {1828 Self::transfer_fungible(&collection, amount, &from, &recipient)?1829 }1830 CollectionMode::ReFungible => {1831 Self::transfer_refungible(&collection, item_id, amount, from.clone(), recipient.clone())?1832 }1833 _ => ()1834 };18351836 if matches!(collection.mode, CollectionMode::Fungible(_)) {1837 collection.log(1838 Vec::from([1839 eth::APPROVAL_FUNGIBLE_TOPIC,1840 eth::address_to_topic(from.as_eth()),1841 eth::address_to_topic(sender.as_eth()),1842 ]),1843 abi_encode!(uint256(allowance.into())),1844 );1845 }18461847 Ok(())1848 }18491850 pub fn set_variable_meta_data_internal(1851 sender: &T::CrossAccountId,1852 collection: &CollectionHandle<T>, 1853 item_id: TokenId,1854 data: Vec<u8>,1855 ) -> DispatchResult {1856 Self::token_exists(&collection, item_id)?;18571858 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);18591860 // Modify permissions check1861 ensure!(Self::is_item_owner(&sender, &collection, item_id) ||1862 Self::is_owner_or_admin_permissions(&collection, &sender),1863 Error::<T>::NoPermission);18641865 match collection.mode1866 {1867 CollectionMode::NFT => Self::set_nft_variable_data(&collection, item_id, data)?,1868 CollectionMode::ReFungible => Self::set_re_fungible_variable_data(&collection, item_id, data)?,1869 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1870 _ => fail!(Error::<T>::UnexpectedCollectionType)1871 };18721873 Ok(())1874 }18751876 pub fn create_multiple_items_internal(1877 sender: &T::CrossAccountId,1878 collection: &CollectionHandle<T>,1879 owner: &T::CrossAccountId,1880 items_data: Vec<CreateItemData>,1881 ) -> DispatchResult {1882 Self::can_create_items_in_collection(&collection, &sender, &owner, items_data.len() as u32)?;18831884 for data in &items_data {1885 Self::validate_create_item_args(&collection, data)?;1886 }1887 for data in &items_data {1888 Self::create_item_no_validation(&collection, owner, data.clone())?;1889 }18901891 Ok(())1892 }18931894 pub fn burn_item_internal(1895 sender: &T::CrossAccountId,1896 collection: &CollectionHandle<T>,1897 item_id: TokenId,1898 value: u128,1899 ) -> DispatchResult {1900 ensure!(1901 Self::is_item_owner(&sender, &collection, item_id) ||1902 (1903 collection.limits.owner_can_transfer &&1904 Self::is_owner_or_admin_permissions(&collection, &sender)1905 ),1906 Error::<T>::NoPermission1907 );19081909 if collection.access == AccessMode::WhiteList {1910 Self::check_white_list(&collection, &sender)?;1911 }19121913 match collection.mode1914 {1915 CollectionMode::NFT => Self::burn_nft_item(&collection, item_id)?,1916 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, &collection, value)?,1917 CollectionMode::ReFungible => Self::burn_refungible_item(&collection, item_id, &sender)?,1918 _ => ()1919 };19201921 Ok(())1922 }19231924 pub fn toggle_white_list_internal(1925 sender: &T::CrossAccountId,1926 collection: &CollectionHandle<T>,1927 address: &T::CrossAccountId,1928 whitelisted: bool,1929 ) -> DispatchResult {1930 Self::check_owner_or_admin_permissions(&collection, &sender)?;19311932 if whitelisted {1933 <WhiteList<T>>::insert(collection.id, address.as_sub(), true);1934 } else {1935 <WhiteList<T>>::remove(collection.id, address.as_sub());1936 }19371938 Ok(())1939 }19401941 fn is_correct_transfer(collection: &CollectionHandle<T>, recipient: &T::CrossAccountId) -> DispatchResult {1942 let collection_id = collection.id;19431944 // check token limit and account token limit1945 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1946 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1947 1948 Ok(())1949 }19501951 fn can_create_items_in_collection(collection: &CollectionHandle<T>, sender: &T::CrossAccountId, owner: &T::CrossAccountId, amount: u32) -> DispatchResult {1952 let collection_id = collection.id;19531954 // check token limit and account token limit1955 let total_items: u32 = ItemListIndex::get(collection_id)1956 .checked_add(amount)1957 .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1958 let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len() as u32)1959 .checked_add(amount)1960 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1961 ensure!(collection.limits.token_limit >= total_items, Error::<T>::CollectionTokenLimitExceeded);1962 ensure!(collection.limits.account_token_ownership_limit >= account_items, Error::<T>::AccountTokenLimitExceeded);19631964 if !Self::is_owner_or_admin_permissions(collection, &sender) {1965 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1966 Self::check_white_list(collection, owner)?;1967 Self::check_white_list(collection, sender)?;1968 }19691970 Ok(())1971 }19721973 fn validate_create_item_args(target_collection: &CollectionHandle<T>, data: &CreateItemData) -> DispatchResult {1974 match target_collection.mode1975 {1976 CollectionMode::NFT => {1977 if let CreateItemData::NFT(data) = data {1978 // check sizes1979 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1980 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1981 } else {1982 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1983 }1984 },1985 CollectionMode::Fungible(_) => {1986 if let CreateItemData::Fungible(_) = data {1987 } else {1988 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1989 }1990 },1991 CollectionMode::ReFungible => {1992 if let CreateItemData::ReFungible(data) = data {19931994 // check sizes1995 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1996 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);19971998 // Check refungibility limits1999 ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);2000 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);2001 } else {2002 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);2003 }2004 },2005 _ => { fail!(Error::<T>::UnexpectedCollectionType); }2006 };20072008 Ok(())2009 }20102011 fn create_item_no_validation(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, data: CreateItemData) -> DispatchResult {2012 match data2013 {2014 CreateItemData::NFT(data) => {2015 let item = NftItemType {2016 owner: owner.clone(),2017 const_data: data.const_data,2018 variable_data: data.variable_data2019 };20202021 Self::add_nft_item(collection, item)?;2022 },2023 CreateItemData::Fungible(data) => {2024 Self::add_fungible_item(collection, &owner, data.value)?;2025 },2026 CreateItemData::ReFungible(data) => {2027 let mut owner_list = Vec::new();2028 owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});20292030 let item = ReFungibleItemType {2031 owner: owner_list,2032 const_data: data.const_data,2033 variable_data: data.variable_data2034 };20352036 Self::add_refungible_item(collection, item)?;2037 }2038 };20392040 Ok(())2041 }20422043 fn add_fungible_item(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, value: u128) -> DispatchResult {2044 let collection_id = collection.id;20452046 // Does new owner already have an account?2047 let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;20482049 // Mint 2050 let item = FungibleItemType {2051 value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,2052 };2053 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);20542055 // Update balance2056 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2057 .checked_add(value)2058 .ok_or(Error::<T>::NumOverflow)?;2059 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);20602061 Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));2062 Ok(())2063 }20642065 fn add_refungible_item(collection: &CollectionHandle<T>, item: ReFungibleItemType<T::CrossAccountId>) -> DispatchResult {2066 let collection_id = collection.id;20672068 let current_index = <ItemListIndex>::get(collection_id)2069 .checked_add(1)2070 .ok_or(Error::<T>::NumOverflow)?;2071 let itemcopy = item.clone();20722073 ensure!(2074 item.owner.len() == 1,2075 Error::<T>::BadCreateRefungibleCall,2076 );2077 let item_owner = item.owner.first().expect("only one owner is defined");20782079 let value = item_owner.fraction;2080 let owner = item_owner.owner.clone();20812082 Self::add_token_index(collection_id, current_index, &owner)?;20832084 <ItemListIndex>::insert(collection_id, current_index);2085 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);20862087 // Update balance2088 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2089 .checked_add(value)2090 .ok_or(Error::<T>::NumOverflow)?;2091 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);20922093 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));2094 Ok(())2095 }20962097 fn add_nft_item(collection: &CollectionHandle<T>, item: NftItemType<T::CrossAccountId>) -> DispatchResult {2098 let collection_id = collection.id;20992100 let current_index = <ItemListIndex>::get(collection_id)2101 .checked_add(1)2102 .ok_or(Error::<T>::NumOverflow)?;21032104 let item_owner = item.owner.clone();2105 Self::add_token_index(collection_id, current_index, &item.owner)?;21062107 <ItemListIndex>::insert(collection_id, current_index);2108 <NftItemList<T>>::insert(collection_id, current_index, item);21092110 // Update balance2111 let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())2112 .checked_add(1)2113 .ok_or(Error::<T>::NumOverflow)?;2114 <Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);21152116 collection.log(2117 Vec::from([2118 eth::TRANSFER_NFT_TOPIC,2119 eth::address_to_topic(&H160::default()),2120 eth::address_to_topic(item_owner.as_eth()),2121 ]),2122 abi_encode!(uint256(current_index.into())),2123 );2124 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, item_owner));2125 Ok(())2126 }21272128 fn burn_refungible_item(2129 collection: &CollectionHandle<T>,2130 item_id: TokenId,2131 owner: &T::CrossAccountId,2132 ) -> DispatchResult {2133 let collection_id = collection.id;21342135 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)2136 .ok_or(Error::<T>::TokenNotFound)?;2137 let rft_balance = token2138 .owner2139 .iter()2140 .find(|&i| i.owner == *owner)2141 .ok_or(Error::<T>::TokenNotFound)?;2142 Self::remove_token_index(collection_id, item_id, owner)?;21432144 // update balance2145 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())2146 .checked_sub(rft_balance.fraction)2147 .ok_or(Error::<T>::NumOverflow)?;2148 <Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);21492150 // Re-create owners list with sender removed2151 let index = token2152 .owner2153 .iter()2154 .position(|i| i.owner == *owner)2155 .expect("owned item is exists");2156 token.owner.remove(index);2157 let owner_count = token.owner.len();21582159 // Burn the token completely if this was the last (only) owner2160 if owner_count == 0 {2161 <ReFungibleItemList<T>>::remove(collection_id, item_id);2162 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);2163 }2164 else {2165 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);2166 }21672168 Ok(())2169 }21702171 fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {2172 let collection_id = collection.id;21732174 let item = <NftItemList<T>>::get(collection_id, item_id)2175 .ok_or(Error::<T>::TokenNotFound)?;2176 Self::remove_token_index(collection_id, item_id, &item.owner)?;21772178 // update balance2179 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2180 .checked_sub(1)2181 .ok_or(Error::<T>::NumOverflow)?;2182 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2183 <NftItemList<T>>::remove(collection_id, item_id);2184 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);21852186 Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));2187 Ok(())2188 }21892190 fn burn_fungible_item(owner: &T::CrossAccountId, collection: &CollectionHandle<T>, value: u128) -> DispatchResult {2191 let collection_id = collection.id;21922193 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());2194 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);21952196 // update balance2197 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2198 .checked_sub(value)2199 .ok_or(Error::<T>::NumOverflow)?;2200 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);22012202 if balance.value - value > 0 {2203 balance.value -= value;2204 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2205 }2206 else {2207 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());2208 }22092210 collection.log(2211 Vec::from([2212 eth::TRANSFER_FUNGIBLE_TOPIC,2213 eth::address_to_topic(owner.as_eth()),2214 eth::address_to_topic(&H160::default()),2215 ]),2216 abi_encode!(uint256(value.into())),2217 );2218 Ok(())2219 }22202221 pub fn get_collection(collection_id: CollectionId) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {2222 Ok(<CollectionHandle<T>>::get(collection_id)2223 .ok_or(Error::<T>::CollectionNotFound)?)2224 }22252226 fn save_collection(collection: CollectionHandle<T>) {2227 <CollectionById<T>>::insert(collection.id, collection.into_inner());2228 }22292230 pub fn submit_logs(collection: CollectionHandle<T>) -> DispatchResult {2231 if collection.logs.is_empty() {2232 return Ok(())2233 }2234 T::EthereumTransactionSender::submit_logs_transaction(2235 eth::generate_transaction(collection.id, T::EthereumChainId::get()),2236 collection.logs.retrieve_logs_for_contract(eth::collection_id_to_address(collection.id)),2237 )2238 }22392240 fn check_owner_permissions(target_collection: &CollectionHandle<T>, subject: &T::CrossAccountId) -> DispatchResult {2241 ensure!(2242 *subject == target_collection.owner,2243 Error::<T>::NoPermission2244 );22452246 Ok(())2247 }22482249 fn is_owner_or_admin_permissions(collection: &CollectionHandle<T>, subject: &T::CrossAccountId) -> bool {2250 *subject == collection.owner || <AdminList<T>>::get(collection.id).contains(&subject)2251 }22522253 fn check_owner_or_admin_permissions(2254 collection: &CollectionHandle<T>,2255 subject: &T::CrossAccountId,2256 ) -> DispatchResult {2257 ensure!(Self::is_owner_or_admin_permissions(collection, subject), Error::<T>::NoPermission);22582259 Ok(())2260 }22612262 fn owned_amount(2263 subject: &T::CrossAccountId,2264 target_collection: &CollectionHandle<T>,2265 item_id: TokenId,2266 ) -> Option<u128> {2267 let collection_id = target_collection.id;22682269 match target_collection.mode {2270 CollectionMode::NFT => (<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject)2271 .then(|| 1),2272 CollectionMode::Fungible(_) => Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub())2273 .value),2274 CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?2275 .owner2276 .iter()2277 .find(|i| i.owner == *subject)2278 .map(|i| i.fraction),2279 CollectionMode::Invalid => None,2280 }2281 }22822283 fn is_item_owner(subject: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId) -> bool {2284 match target_collection.mode {2285 CollectionMode::Fungible(_) => true,2286 _ => Self::owned_amount(&subject, target_collection, item_id).is_some(),2287 }2288 }22892290 fn check_white_list(collection: &CollectionHandle<T>, address: &T::CrossAccountId) -> DispatchResult {2291 let collection_id = collection.id;22922293 let mes = Error::<T>::AddresNotInWhiteList;2294 ensure!(<WhiteList<T>>::contains_key(collection_id, address.as_sub()), mes);22952296 Ok(())2297 }22982299 /// Check if token exists. In case of Fungible, check if there is an entry for 2300 /// the owner in fungible balances double map2301 fn token_exists(2302 target_collection: &CollectionHandle<T>,2303 item_id: TokenId,2304 ) -> DispatchResult {2305 let collection_id = target_collection.id;2306 let exists = match target_collection.mode2307 {2308 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),2309 CollectionMode::Fungible(_) => true,2310 CollectionMode::ReFungible => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),2311 _ => false2312 };23132314 ensure!(exists == true, Error::<T>::TokenNotFound);2315 Ok(())2316 }23172318 fn transfer_fungible(2319 collection: &CollectionHandle<T>,2320 value: u128,2321 owner: &T::CrossAccountId,2322 recipient: &T::CrossAccountId,2323 ) -> DispatchResult {2324 let collection_id = collection.id;23252326 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());2327 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);23282329 // Send balance to recipient (updates balanceOf of recipient)2330 Self::add_fungible_item(collection, recipient, value)?;23312332 // update balanceOf of sender2333 <Balance<T>>::insert(collection_id, owner.as_sub(), balance.value - value);23342335 // Reduce or remove sender2336 if balance.value == value {2337 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());2338 }2339 else {2340 balance.value -= value;2341 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2342 }23432344 collection.log(2345 Vec::from([2346 eth::TRANSFER_FUNGIBLE_TOPIC,2347 eth::address_to_topic(owner.as_eth()),2348 eth::address_to_topic(recipient.as_eth()),2349 ]),2350 abi_encode!(uint256(value.into())),2351 );2352 Self::deposit_event(RawEvent::Transfer(collection.id, 1, owner.clone(), recipient.clone(), value));23532354 Ok(())2355 }23562357 fn transfer_refungible(2358 collection: &CollectionHandle<T>,2359 item_id: TokenId,2360 value: u128,2361 owner: T::CrossAccountId,2362 new_owner: T::CrossAccountId,2363 ) -> DispatchResult {2364 let collection_id = collection.id;2365 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2366 .ok_or(Error::<T>::TokenNotFound)?;23672368 let item = full_item2369 .owner2370 .iter()2371 .filter(|i| i.owner == owner)2372 .next()2373 .ok_or(Error::<T>::TokenNotFound)?;2374 let amount = item.fraction;23752376 ensure!(amount >= value, Error::<T>::TokenValueTooLow);23772378 // update balance2379 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2380 .checked_sub(value)2381 .ok_or(Error::<T>::NumOverflow)?;2382 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);23832384 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2385 .checked_add(value)2386 .ok_or(Error::<T>::NumOverflow)?;2387 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);23882389 let old_owner = item.owner.clone();2390 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);23912392 // transfer2393 if amount == value && !new_owner_has_account {2394 // change owner2395 // new owner do not have account2396 let mut new_full_item = full_item.clone();2397 new_full_item2398 .owner2399 .iter_mut()2400 .find(|i| i.owner == owner)2401 .expect("old owner does present in refungible")2402 .owner = new_owner.clone();2403 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);24042405 // update index collection2406 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2407 } else {2408 let mut new_full_item = full_item.clone();2409 new_full_item2410 .owner2411 .iter_mut()2412 .find(|i| i.owner == owner)2413 .expect("old owner does present in refungible")2414 .fraction -= value;24152416 // separate amount2417 if new_owner_has_account {2418 // new owner has account2419 new_full_item2420 .owner2421 .iter_mut()2422 .find(|i| i.owner == new_owner)2423 .expect("new owner has account")2424 .fraction += value;2425 } else {2426 // new owner do not have account2427 new_full_item.owner.push(Ownership {2428 owner: new_owner.clone(),2429 fraction: value,2430 });2431 Self::add_token_index(collection_id, item_id, &new_owner)?;2432 }24332434 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2435 }24362437 Self::deposit_event(RawEvent::Transfer(collection.id, item_id, owner, new_owner, amount));24382439 Ok(())2440 }24412442 fn transfer_nft(2443 collection: &CollectionHandle<T>,2444 item_id: TokenId,2445 sender: T::CrossAccountId,2446 new_owner: T::CrossAccountId,2447 ) -> DispatchResult {2448 let collection_id = collection.id;2449 let mut item = <NftItemList<T>>::get(collection_id, item_id)2450 .ok_or(Error::<T>::TokenNotFound)?;24512452 ensure!(2453 sender == item.owner,2454 Error::<T>::MustBeTokenOwner2455 );24562457 // update balance2458 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2459 .checked_sub(1)2460 .ok_or(Error::<T>::NumOverflow)?;2461 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);24622463 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2464 .checked_add(1)2465 .ok_or(Error::<T>::NumOverflow)?;2466 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);24672468 // change owner2469 let old_owner = item.owner.clone();2470 item.owner = new_owner.clone();2471 <NftItemList<T>>::insert(collection_id, item_id, item);24722473 // update index collection2474 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;24752476 collection.log(2477 Vec::from([2478 eth::TRANSFER_NFT_TOPIC,2479 eth::address_to_topic(sender.as_eth()),2480 eth::address_to_topic(new_owner.as_eth()),2481 ]),2482 abi_encode!(uint256(item_id.into())),2483 );2484 Self::deposit_event(RawEvent::Transfer(collection.id, item_id, sender, new_owner, 1));24852486 Ok(())2487 }2488 2489 fn set_re_fungible_variable_data(2490 collection: &CollectionHandle<T>,2491 item_id: TokenId,2492 data: Vec<u8>2493 ) -> DispatchResult {2494 let collection_id = collection.id;2495 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2496 .ok_or(Error::<T>::TokenNotFound)?;24972498 item.variable_data = data;24992500 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);25012502 Ok(())2503 }25042505 fn set_nft_variable_data(2506 collection: &CollectionHandle<T>,2507 item_id: TokenId,2508 data: Vec<u8>2509 ) -> DispatchResult {2510 let collection_id = collection.id;2511 let mut item = <NftItemList<T>>::get(collection_id, item_id)2512 .ok_or(Error::<T>::TokenNotFound)?;2513 2514 item.variable_data = data;25152516 <NftItemList<T>>::insert(collection_id, item_id, item);2517 2518 Ok(())2519 }25202521 fn init_collection(item: &Collection<T>) {2522 // check params2523 assert!(2524 item.decimal_points <= MAX_DECIMAL_POINTS,2525 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2526 );2527 assert!(2528 item.name.len() <= 64,2529 "Collection name can not be longer than 63 char"2530 );2531 assert!(2532 item.name.len() <= 256,2533 "Collection description can not be longer than 255 char"2534 );2535 assert!(2536 item.token_prefix.len() <= 16,2537 "Token prefix can not be longer than 15 char"2538 );25392540 // Generate next collection ID2541 let next_id = CreatedCollectionCount::get()2542 .checked_add(1)2543 .unwrap();25442545 CreatedCollectionCount::put(next_id);2546 }25472548 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2549 let current_index = <ItemListIndex>::get(collection_id)2550 .checked_add(1)2551 .unwrap();25522553 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();25542555 <ItemListIndex>::insert(collection_id, current_index);25562557 // Update balance2558 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2559 .checked_add(1)2560 .unwrap();2561 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2562 }25632564 fn init_fungible_token(collection_id: CollectionId, owner: &T::CrossAccountId, item: &FungibleItemType) {2565 let current_index = <ItemListIndex>::get(collection_id)2566 .checked_add(1)2567 .unwrap();25682569 Self::add_token_index(collection_id, current_index, owner).unwrap();25702571 <ItemListIndex>::insert(collection_id, current_index);25722573 // Update balance2574 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2575 .checked_add(item.value)2576 .unwrap();2577 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2578 }25792580 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::CrossAccountId>) {2581 let current_index = <ItemListIndex>::get(collection_id)2582 .checked_add(1)2583 .unwrap();25842585 let value = item.owner.first().unwrap().fraction;2586 let owner = item.owner.first().unwrap().owner.clone();25872588 Self::add_token_index(collection_id, current_index, &owner).unwrap();25892590 <ItemListIndex>::insert(collection_id, current_index);25912592 // Update balance2593 let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2594 .checked_add(value)2595 .unwrap();2596 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2597 }25982599 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::CrossAccountId) -> DispatchResult {2600 // add to account limit2601 if <AccountItemCount<T>>::contains_key(owner.as_sub()) {26022603 // bound Owned tokens by a single address2604 let count = <AccountItemCount<T>>::get(owner.as_sub());2605 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);26062607 <AccountItemCount<T>>::insert(owner.as_sub(), count2608 .checked_add(1)2609 .ok_or(Error::<T>::NumOverflow)?);2610 }2611 else {2612 <AccountItemCount<T>>::insert(owner.as_sub(), 1);2613 }26142615 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2616 if list_exists {2617 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2618 let item_contains = list.contains(&item_index.clone());26192620 if !item_contains {2621 list.push(item_index.clone());2622 }26232624 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2625 } else {2626 let mut itm = Vec::new();2627 itm.push(item_index.clone());2628 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), itm);2629 }26302631 Ok(())2632 }26332634 fn remove_token_index(2635 collection_id: CollectionId,2636 item_index: TokenId,2637 owner: &T::CrossAccountId,2638 ) -> DispatchResult {26392640 // update counter2641 <AccountItemCount<T>>::insert(owner.as_sub(), 2642 <AccountItemCount<T>>::get(owner.as_sub())2643 .checked_sub(1)2644 .ok_or(Error::<T>::NumOverflow)?);264526462647 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2648 if list_exists {2649 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2650 let item_contains = list.contains(&item_index.clone());26512652 if item_contains {2653 list.retain(|&item| item != item_index);2654 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2655 }2656 }26572658 Ok(())2659 }26602661 fn move_token_index(2662 collection_id: CollectionId,2663 item_index: TokenId,2664 old_owner: &T::CrossAccountId,2665 new_owner: &T::CrossAccountId,2666 ) -> DispatchResult {2667 Self::remove_token_index(collection_id, item_index, old_owner)?;2668 Self::add_token_index(collection_id, item_index, new_owner)?;26692670 Ok(())2671 }2672 2673 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2674 ensure!(<ContractOwner<T>>::get(contract) == Some(account), Error::<T>::NoPermission);26752676 Ok(())2677 }2678}26792680////////////////////////////////////////////////////////////////////////////////////////////////////2681// Economic models2682// #region26832684/// Fee multiplier.2685pub type Multiplier = FixedU128;26862687type BalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::Balance;26882689/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2690/// in the queue.2691#[derive(Encode, Decode, Clone, Eq, PartialEq)]2692pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);26932694impl<T: Config + Send + Sync> sp_std::fmt::Debug 2695 for ChargeTransactionPayment<T>2696{2697 #[cfg(feature = "std")]2698 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2699 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2700 }2701 #[cfg(not(feature = "std"))]2702 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2703 Ok(())2704 }2705}27062707impl<T: Config> ChargeTransactionPayment<T>2708where2709 T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2710 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2711 T::AccountId: AsRef<[u8]>,2712 T::AccountId: UncheckedFrom<T::Hash>,2713{2714 fn traditional_fee(2715 len: usize,2716 info: &DispatchInfoOf<T::Call>,2717 tip: BalanceOf<T>,2718 ) -> BalanceOf<T>2719 where2720 T::Call: Dispatchable<Info = DispatchInfo>,2721 {2722 <pallet_transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2723 }27242725 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2726 let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);2727 let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);2728 let len_saturation = max_block_length as u64 / (len as u64).max(1);2729 let coefficient: BalanceOf<T> = weight_saturation2730 .min(len_saturation)2731 .saturated_into::<BalanceOf<T>>();2732 final_fee2733 .saturating_mul(coefficient)2734 .saturated_into::<TransactionPriority>()2735 }27362737 fn withdraw_fee(2738 &self,2739 who: &T::AccountId,2740 call: &T::Call,2741 info: &DispatchInfoOf<T::Call>,2742 len: usize,2743 ) -> Result<2744 (2745 BalanceOf<T>,2746 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2747 ),2748 TransactionValidityError,2749 > {2750 let tip = self.0;27512752 let fee = Self::traditional_fee(len, info, tip);27532754 // Only mess with balances if fee is not zero.2755 if fee.is_zero() {2756 return <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)2757 .map(|i| (fee, i));2758 }27592760 // Determine who is paying transaction fee based on ecnomic model2761 // Parse call to extract collection ID and access collection sponsor2762 let mut sponsor: Option<T::AccountId> = (|| match IsSubType::<Call<T>>::is_sub_type(call) {2763 Some(Call::create_item(collection_id, _owner, _properties)) => {2764 let collection = <CollectionById<T>>::get(collection_id)?;27652766 // sponsor timeout2767 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;27682769 let limit = collection.limits.sponsor_transfer_timeout;2770 if <CreateItemBasket<T>>::contains_key((collection_id, &who)) {2771 let last_tx_block = <CreateItemBasket<T>>::get((collection_id, &who));2772 let limit_time = last_tx_block + limit.into();2773 if block_number <= limit_time {2774 return None;2775 }2776 }2777 <CreateItemBasket<T>>::insert((collection_id, who.clone()), block_number);27782779 // check free create limit2780 if collection.limits.sponsored_data_size >= (_properties.len() as u32) {2781 collection.sponsorship.sponsor()2782 .cloned()2783 } else {2784 None2785 }2786 }2787 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2788 let collection = <CollectionById<T>>::get(collection_id)?;2789 2790 let mut sponsor_transfer = false;2791 if collection.sponsorship.confirmed() {27922793 let collection_limits = collection.limits;2794 let collection_mode = collection.mode;2795 2796 // sponsor timeout2797 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2798 sponsor_transfer = match collection_mode {2799 CollectionMode::NFT => {2800 2801 // get correct limit2802 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2803 collection_limits.sponsor_transfer_timeout2804 } else {2805 ChainLimit::get().nft_sponsor_transfer_timeout2806 };2807 2808 let mut sponsored = true;2809 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2810 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2811 let limit_time = last_tx_block + limit.into();2812 if block_number <= limit_time {2813 sponsored = false;2814 }2815 }2816 if sponsored {2817 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2818 }28192820 sponsored2821 }2822 CollectionMode::Fungible(_) => {2823 2824 // get correct limit2825 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2826 collection_limits.sponsor_transfer_timeout2827 } else {2828 ChainLimit::get().fungible_sponsor_transfer_timeout2829 };2830 2831 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2832 let mut sponsored = true;2833 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2834 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2835 let limit_time = last_tx_block + limit.into();2836 if block_number <= limit_time {2837 sponsored = false;2838 }2839 }2840 if sponsored {2841 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2842 }28432844 sponsored2845 }2846 CollectionMode::ReFungible => {2847 2848 // get correct limit2849 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2850 collection_limits.sponsor_transfer_timeout2851 } else {2852 ChainLimit::get().refungible_sponsor_transfer_timeout2853 };2854 2855 let mut sponsored = true;2856 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2857 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2858 let limit_time = last_tx_block + limit.into();2859 if block_number <= limit_time {2860 sponsored = false;2861 }2862 }2863 if sponsored {2864 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2865 }28662867 sponsored2868 }2869 _ => {2870 false2871 },2872 };2873 }28742875 if !sponsor_transfer {2876 None2877 } else {2878 collection.sponsorship.sponsor()2879 .cloned()2880 }2881 }28822883 Some(Call::set_variable_meta_data(collection_id, item_id, data)) => {2884 let mut sponsor_metadata_changes = false;28852886 let collection = <CollectionById<T>>::get(collection_id)?;28872888 if2889 collection.sponsorship.confirmed() &&2890 // Can't sponsor fungible collection, this tx will be rejected2891 // as invalid2892 !matches!(collection.mode, CollectionMode::Fungible(_)) &&2893 data.len() <= collection.limits.sponsored_data_size as usize2894 {2895 if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit {2896 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;28972898 if <VariableMetaDataBasket<T>>::get(collection_id, item_id)2899 .map(|last_block| block_number - last_block > rate_limit)2900 .unwrap_or(true) 2901 {2902 sponsor_metadata_changes = true;2903 <VariableMetaDataBasket<T>>::insert(collection_id, item_id, block_number);2904 }2905 }2906 }29072908 if !sponsor_metadata_changes {2909 None2910 } else {2911 collection.sponsorship.sponsor().cloned()2912 }2913 }29142915 _ => None,2916 })();29172918 match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {2919 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {29202921 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());29222923 let owned_contract = <ContractOwner<T>>::get(called_contract.clone()).as_ref() == Some(who);2924 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone());2925 2926 if !owned_contract && white_list_enabled {2927 if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2928 return Err(InvalidTransaction::Call.into());2929 }2930 }2931 },2932 _ => {},2933 }29342935 // Sponsor smart contracts2936 sponsor = sponsor.or_else(|| match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {29372938 // On instantiation: set the contract owner2939 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {29402941 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2942 &who,2943 code_hash,2944 salt,2945 );2946 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());29472948 None2949 },29502951 // On instantiation with code: set the contract owner2952 Some(pallet_contracts::Call::instantiate_with_code(_endowment, _gas_limit, _code, _data, _salt)) => {29532954 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2955 &who,2956 &T::Hashing::hash(&_code),2957 _salt,2958 );29592960 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());29612962 None2963 }29642965 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2966 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {29672968 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());29692970 let mut sponsor_transfer = false;2971 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2972 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2973 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2974 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2975 let limit_time = last_tx_block + rate_limit;29762977 if block_number >= limit_time {2978 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2979 sponsor_transfer = true;2980 }2981 } else {2982 sponsor_transfer = false;2983 }2984 2985 if sponsor_transfer {2986 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2987 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2988 return Some(called_contract);2989 }2990 }2991 }29922993 None2994 },29952996 _ => None,2997 });29982999 let who_pays_fee = sponsor.unwrap_or_else(|| who.clone());30003001 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(&who_pays_fee, call, info, fee, tip)3002 .map(|i| (fee, i))3003 }3004}300530063007impl<T: Config + Send + Sync> SignedExtension3008 for ChargeTransactionPayment<T>3009where3010 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,3011 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,3012 T::AccountId: AsRef<[u8]>,3013 T::AccountId: UncheckedFrom<T::Hash>,3014{3015 const IDENTIFIER: &'static str = "ChargeTransactionPayment";3016 type AccountId = T::AccountId;3017 type Call = T::Call;3018 type AdditionalSigned = ();3019 type Pre = (3020 // tip3021 BalanceOf<T>,3022 // who pays fee3023 Self::AccountId,3024 // imbalance resulting from withdrawing the fee3025 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,3026 );3027 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {3028 Ok(())3029 }30303031 fn validate(3032 &self,3033 who: &Self::AccountId,3034 call: &Self::Call,3035 info: &DispatchInfoOf<Self::Call>,3036 len: usize,3037 ) -> TransactionValidity {3038 let (fee, _) = self.withdraw_fee(who, call, info, len)?;3039 Ok(ValidTransaction {3040 priority: Self::get_priority(len, info, fee),3041 ..Default::default()3042 })3043 }30443045 fn pre_dispatch(3046 self,3047 who: &Self::AccountId,3048 call: &Self::Call,3049 info: &DispatchInfoOf<Self::Call>,3050 len: usize,3051 ) -> Result<Self::Pre, TransactionValidityError> {3052 let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;3053 Ok((self.0, who.clone(), imbalance))3054 }30553056 fn post_dispatch(3057 pre: Self::Pre,3058 info: &DispatchInfoOf<Self::Call>,3059 post_info: &PostDispatchInfoOf<Self::Call>,3060 len: usize,3061 _result: &DispatchResult,3062 ) -> Result<(), TransactionValidityError> {3063 let (tip, who, imbalance) = pre;3064 let actual_fee = pallet_transaction_payment::Module::<T>::compute_actual_fee(3065 len as u32,3066 info,3067 post_info,3068 tip,3069 );3070 <T as pallet_transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;3071 Ok(())3072 }3073}30743075// #endregion30763077sp_api::decl_runtime_apis! {3078 pub trait NftApi {3079 /// Used for ethereum integration3080 fn eth_contract_code(account: H160) -> Option<Vec<u8>>;3081 }3082}