difftreelog
refactor revert changing collection owner type
in: master
Not necessary yet, we don't have to support evm accounts create collections
3 files 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 eth::u32_to_topic(item_id),1766 ]),1767 abi_encode!(),1768 );1769 }17701771 if matches!(collection.mode, CollectionMode::Fungible(_)) {1772 // TODO: NFT: only one owner may exist for token in ERC201773 collection.log(1774 Vec::from([1775 eth::APPROVAL_FUNGIBLE_TOPIC,1776 eth::address_to_topic(sender.as_eth()),1777 eth::address_to_topic(spender.as_eth()),1778 ]),1779 abi_encode!(uint256(allowance.into())),1780 );1781 }17821783 Self::deposit_event(RawEvent::Approved(collection.id, item_id, sender.clone(), spender.clone(), allowance));1784 Ok(())1785 }17861787 pub fn transfer_from_internal(1788 sender: &T::CrossAccountId,1789 from: &T::CrossAccountId,1790 recipient: &T::CrossAccountId,1791 collection: &CollectionHandle<T>,1792 item_id: TokenId,1793 amount: u128,1794 ) -> DispatchResult {1795 // Check approval1796 let approval: u128 = <Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));17971798 // Limits check1799 Self::is_correct_transfer(&collection, &recipient)?;18001801 // Transfer permissions check1802 ensure!(1803 approval >= amount || 1804 (1805 collection.limits.owner_can_transfer &&1806 Self::is_owner_or_admin_permissions(&collection, &sender)1807 ),1808 Error::<T>::NoPermission1809 );18101811 if collection.access == AccessMode::WhiteList {1812 Self::check_white_list(&collection, &sender)?;1813 Self::check_white_list(&collection, &recipient)?;1814 }18151816 // Reduce approval by transferred amount or remove if remaining approval drops to 01817 let allowance = approval.saturating_sub(amount);1818 if allowance > 0 {1819 <Allowances<T>>::insert(collection.id, (item_id, from.as_sub(), sender.as_sub()), allowance);1820 } else {1821 <Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1822 }18231824 match collection.mode {1825 CollectionMode::NFT => {1826 Self::transfer_nft(&collection, item_id, from.clone(), recipient.clone())?1827 }1828 CollectionMode::Fungible(_) => {1829 Self::transfer_fungible(&collection, amount, &from, &recipient)?1830 }1831 CollectionMode::ReFungible => {1832 Self::transfer_refungible(&collection, item_id, amount, from.clone(), recipient.clone())?1833 }1834 _ => ()1835 };18361837 if matches!(collection.mode, CollectionMode::Fungible(_)) {1838 collection.log(1839 Vec::from([1840 eth::APPROVAL_FUNGIBLE_TOPIC,1841 eth::address_to_topic(from.as_eth()),1842 eth::address_to_topic(sender.as_eth()),1843 ]),1844 abi_encode!(uint256(allowance.into())),1845 );1846 }18471848 Ok(())1849 }18501851 pub fn set_variable_meta_data_internal(1852 sender: &T::CrossAccountId,1853 collection: &CollectionHandle<T>, 1854 item_id: TokenId,1855 data: Vec<u8>,1856 ) -> DispatchResult {1857 Self::token_exists(&collection, item_id)?;18581859 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);18601861 // Modify permissions check1862 ensure!(Self::is_item_owner(&sender, &collection, item_id) ||1863 Self::is_owner_or_admin_permissions(&collection, &sender),1864 Error::<T>::NoPermission);18651866 match collection.mode1867 {1868 CollectionMode::NFT => Self::set_nft_variable_data(&collection, item_id, data)?,1869 CollectionMode::ReFungible => Self::set_re_fungible_variable_data(&collection, item_id, data)?,1870 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1871 _ => fail!(Error::<T>::UnexpectedCollectionType)1872 };18731874 Ok(())1875 }18761877 pub fn create_multiple_items_internal(1878 sender: &T::CrossAccountId,1879 collection: &CollectionHandle<T>,1880 owner: &T::CrossAccountId,1881 items_data: Vec<CreateItemData>,1882 ) -> DispatchResult {1883 Self::can_create_items_in_collection(&collection, &sender, &owner, items_data.len() as u32)?;18841885 for data in &items_data {1886 Self::validate_create_item_args(&collection, data)?;1887 }1888 for data in &items_data {1889 Self::create_item_no_validation(&collection, owner, data.clone())?;1890 }18911892 Ok(())1893 }18941895 pub fn burn_item_internal(1896 sender: &T::CrossAccountId,1897 collection: &CollectionHandle<T>,1898 item_id: TokenId,1899 value: u128,1900 ) -> DispatchResult {1901 ensure!(1902 Self::is_item_owner(&sender, &collection, item_id) ||1903 (1904 collection.limits.owner_can_transfer &&1905 Self::is_owner_or_admin_permissions(&collection, &sender)1906 ),1907 Error::<T>::NoPermission1908 );19091910 if collection.access == AccessMode::WhiteList {1911 Self::check_white_list(&collection, &sender)?;1912 }19131914 match collection.mode1915 {1916 CollectionMode::NFT => Self::burn_nft_item(&collection, item_id)?,1917 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, &collection, value)?,1918 CollectionMode::ReFungible => Self::burn_refungible_item(&collection, item_id, &sender)?,1919 _ => ()1920 };19211922 Ok(())1923 }19241925 pub fn toggle_white_list_internal(1926 sender: &T::CrossAccountId,1927 collection: &CollectionHandle<T>,1928 address: &T::CrossAccountId,1929 whitelisted: bool,1930 ) -> DispatchResult {1931 Self::check_owner_or_admin_permissions(&collection, &sender)?;19321933 if whitelisted {1934 <WhiteList<T>>::insert(collection.id, address.as_sub(), true);1935 } else {1936 <WhiteList<T>>::remove(collection.id, address.as_sub());1937 }19381939 Ok(())1940 }19411942 fn is_correct_transfer(collection: &CollectionHandle<T>, recipient: &T::CrossAccountId) -> DispatchResult {1943 let collection_id = collection.id;19441945 // check token limit and account token limit1946 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1947 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1948 1949 Ok(())1950 }19511952 fn can_create_items_in_collection(collection: &CollectionHandle<T>, sender: &T::CrossAccountId, owner: &T::CrossAccountId, amount: u32) -> DispatchResult {1953 let collection_id = collection.id;19541955 // check token limit and account token limit1956 let total_items: u32 = ItemListIndex::get(collection_id)1957 .checked_add(amount)1958 .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1959 let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len() as u32)1960 .checked_add(amount)1961 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1962 ensure!(collection.limits.token_limit >= total_items, Error::<T>::CollectionTokenLimitExceeded);1963 ensure!(collection.limits.account_token_ownership_limit >= account_items, Error::<T>::AccountTokenLimitExceeded);19641965 if !Self::is_owner_or_admin_permissions(collection, &sender) {1966 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1967 Self::check_white_list(collection, owner)?;1968 Self::check_white_list(collection, sender)?;1969 }19701971 Ok(())1972 }19731974 fn validate_create_item_args(target_collection: &CollectionHandle<T>, data: &CreateItemData) -> DispatchResult {1975 match target_collection.mode1976 {1977 CollectionMode::NFT => {1978 if let CreateItemData::NFT(data) = data {1979 // check sizes1980 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1981 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1982 } else {1983 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1984 }1985 },1986 CollectionMode::Fungible(_) => {1987 if let CreateItemData::Fungible(_) = data {1988 } else {1989 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1990 }1991 },1992 CollectionMode::ReFungible => {1993 if let CreateItemData::ReFungible(data) = data {19941995 // check sizes1996 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1997 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);19981999 // Check refungibility limits2000 ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);2001 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);2002 } else {2003 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);2004 }2005 },2006 _ => { fail!(Error::<T>::UnexpectedCollectionType); }2007 };20082009 Ok(())2010 }20112012 fn create_item_no_validation(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, data: CreateItemData) -> DispatchResult {2013 match data2014 {2015 CreateItemData::NFT(data) => {2016 let item = NftItemType {2017 owner: owner.clone(),2018 const_data: data.const_data,2019 variable_data: data.variable_data2020 };20212022 Self::add_nft_item(collection, item)?;2023 },2024 CreateItemData::Fungible(data) => {2025 Self::add_fungible_item(collection, &owner, data.value)?;2026 },2027 CreateItemData::ReFungible(data) => {2028 let mut owner_list = Vec::new();2029 owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});20302031 let item = ReFungibleItemType {2032 owner: owner_list,2033 const_data: data.const_data,2034 variable_data: data.variable_data2035 };20362037 Self::add_refungible_item(collection, item)?;2038 }2039 };20402041 Ok(())2042 }20432044 fn add_fungible_item(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, value: u128) -> DispatchResult {2045 let collection_id = collection.id;20462047 // Does new owner already have an account?2048 let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;20492050 // Mint 2051 let item = FungibleItemType {2052 value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,2053 };2054 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);20552056 // Update balance2057 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2058 .checked_add(value)2059 .ok_or(Error::<T>::NumOverflow)?;2060 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);20612062 Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));2063 Ok(())2064 }20652066 fn add_refungible_item(collection: &CollectionHandle<T>, item: ReFungibleItemType<T::CrossAccountId>) -> DispatchResult {2067 let collection_id = collection.id;20682069 let current_index = <ItemListIndex>::get(collection_id)2070 .checked_add(1)2071 .ok_or(Error::<T>::NumOverflow)?;2072 let itemcopy = item.clone();20732074 ensure!(2075 item.owner.len() == 1,2076 Error::<T>::BadCreateRefungibleCall,2077 );2078 let item_owner = item.owner.first().expect("only one owner is defined");20792080 let value = item_owner.fraction;2081 let owner = item_owner.owner.clone();20822083 Self::add_token_index(collection_id, current_index, &owner)?;20842085 <ItemListIndex>::insert(collection_id, current_index);2086 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);20872088 // Update balance2089 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2090 .checked_add(value)2091 .ok_or(Error::<T>::NumOverflow)?;2092 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);20932094 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));2095 Ok(())2096 }20972098 fn add_nft_item(collection: &CollectionHandle<T>, item: NftItemType<T::CrossAccountId>) -> DispatchResult {2099 let collection_id = collection.id;21002101 let current_index = <ItemListIndex>::get(collection_id)2102 .checked_add(1)2103 .ok_or(Error::<T>::NumOverflow)?;21042105 let item_owner = item.owner.clone();2106 Self::add_token_index(collection_id, current_index, &item.owner)?;21072108 <ItemListIndex>::insert(collection_id, current_index);2109 <NftItemList<T>>::insert(collection_id, current_index, item);21102111 // Update balance2112 let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())2113 .checked_add(1)2114 .ok_or(Error::<T>::NumOverflow)?;2115 <Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);21162117 collection.log(2118 Vec::from([2119 eth::TRANSFER_NFT_TOPIC,2120 eth::address_to_topic(&H160::default()),2121 eth::address_to_topic(item_owner.as_eth()),2122 eth::u32_to_topic(current_index),2123 ]),2124 abi_encode!(),2125 );2126 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, item_owner));2127 Ok(())2128 }21292130 fn burn_refungible_item(2131 collection: &CollectionHandle<T>,2132 item_id: TokenId,2133 owner: &T::CrossAccountId,2134 ) -> DispatchResult {2135 let collection_id = collection.id;21362137 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)2138 .ok_or(Error::<T>::TokenNotFound)?;2139 let rft_balance = token2140 .owner2141 .iter()2142 .find(|&i| i.owner == *owner)2143 .ok_or(Error::<T>::TokenNotFound)?;2144 Self::remove_token_index(collection_id, item_id, owner)?;21452146 // update balance2147 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())2148 .checked_sub(rft_balance.fraction)2149 .ok_or(Error::<T>::NumOverflow)?;2150 <Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);21512152 // Re-create owners list with sender removed2153 let index = token2154 .owner2155 .iter()2156 .position(|i| i.owner == *owner)2157 .expect("owned item is exists");2158 token.owner.remove(index);2159 let owner_count = token.owner.len();21602161 // Burn the token completely if this was the last (only) owner2162 if owner_count == 0 {2163 <ReFungibleItemList<T>>::remove(collection_id, item_id);2164 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);2165 }2166 else {2167 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);2168 }21692170 Ok(())2171 }21722173 fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {2174 let collection_id = collection.id;21752176 let item = <NftItemList<T>>::get(collection_id, item_id)2177 .ok_or(Error::<T>::TokenNotFound)?;2178 Self::remove_token_index(collection_id, item_id, &item.owner)?;21792180 // update balance2181 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2182 .checked_sub(1)2183 .ok_or(Error::<T>::NumOverflow)?;2184 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2185 <NftItemList<T>>::remove(collection_id, item_id);2186 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);21872188 Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));2189 Ok(())2190 }21912192 fn burn_fungible_item(owner: &T::CrossAccountId, collection: &CollectionHandle<T>, value: u128) -> DispatchResult {2193 let collection_id = collection.id;21942195 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());2196 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);21972198 // update balance2199 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2200 .checked_sub(value)2201 .ok_or(Error::<T>::NumOverflow)?;2202 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);22032204 if balance.value - value > 0 {2205 balance.value -= value;2206 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2207 }2208 else {2209 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());2210 }22112212 collection.log(2213 Vec::from([2214 eth::TRANSFER_FUNGIBLE_TOPIC,2215 eth::address_to_topic(owner.as_eth()),2216 eth::address_to_topic(&H160::default()),2217 ]),2218 abi_encode!(uint256(value.into())),2219 );2220 Ok(())2221 }22222223 pub fn get_collection(collection_id: CollectionId) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {2224 Ok(<CollectionHandle<T>>::get(collection_id)2225 .ok_or(Error::<T>::CollectionNotFound)?)2226 }22272228 fn save_collection(collection: CollectionHandle<T>) {2229 <CollectionById<T>>::insert(collection.id, collection.into_inner());2230 }22312232 pub fn submit_logs(collection: CollectionHandle<T>) -> DispatchResult {2233 if collection.logs.is_empty() {2234 return Ok(())2235 }2236 T::EthereumTransactionSender::submit_logs_transaction(2237 eth::generate_transaction(collection.id, T::EthereumChainId::get()),2238 collection.logs.retrieve_logs_for_contract(eth::collection_id_to_address(collection.id)),2239 )2240 }22412242 fn check_owner_permissions(target_collection: &CollectionHandle<T>, subject: &T::CrossAccountId) -> DispatchResult {2243 ensure!(2244 *subject == target_collection.owner,2245 Error::<T>::NoPermission2246 );22472248 Ok(())2249 }22502251 fn is_owner_or_admin_permissions(collection: &CollectionHandle<T>, subject: &T::CrossAccountId) -> bool {2252 *subject == collection.owner || <AdminList<T>>::get(collection.id).contains(&subject)2253 }22542255 fn check_owner_or_admin_permissions(2256 collection: &CollectionHandle<T>,2257 subject: &T::CrossAccountId,2258 ) -> DispatchResult {2259 ensure!(Self::is_owner_or_admin_permissions(collection, subject), Error::<T>::NoPermission);22602261 Ok(())2262 }22632264 fn owned_amount(2265 subject: &T::CrossAccountId,2266 target_collection: &CollectionHandle<T>,2267 item_id: TokenId,2268 ) -> Option<u128> {2269 let collection_id = target_collection.id;22702271 match target_collection.mode {2272 CollectionMode::NFT => (<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject)2273 .then(|| 1),2274 CollectionMode::Fungible(_) => Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub())2275 .value),2276 CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?2277 .owner2278 .iter()2279 .find(|i| i.owner == *subject)2280 .map(|i| i.fraction),2281 CollectionMode::Invalid => None,2282 }2283 }22842285 fn is_item_owner(subject: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId) -> bool {2286 match target_collection.mode {2287 CollectionMode::Fungible(_) => true,2288 _ => Self::owned_amount(&subject, target_collection, item_id).is_some(),2289 }2290 }22912292 fn check_white_list(collection: &CollectionHandle<T>, address: &T::CrossAccountId) -> DispatchResult {2293 let collection_id = collection.id;22942295 let mes = Error::<T>::AddresNotInWhiteList;2296 ensure!(<WhiteList<T>>::contains_key(collection_id, address.as_sub()), mes);22972298 Ok(())2299 }23002301 /// Check if token exists. In case of Fungible, check if there is an entry for 2302 /// the owner in fungible balances double map2303 fn token_exists(2304 target_collection: &CollectionHandle<T>,2305 item_id: TokenId,2306 ) -> DispatchResult {2307 let collection_id = target_collection.id;2308 let exists = match target_collection.mode2309 {2310 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),2311 CollectionMode::Fungible(_) => true,2312 CollectionMode::ReFungible => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),2313 _ => false2314 };23152316 ensure!(exists == true, Error::<T>::TokenNotFound);2317 Ok(())2318 }23192320 fn transfer_fungible(2321 collection: &CollectionHandle<T>,2322 value: u128,2323 owner: &T::CrossAccountId,2324 recipient: &T::CrossAccountId,2325 ) -> DispatchResult {2326 let collection_id = collection.id;23272328 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());2329 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);23302331 // Send balance to recipient (updates balanceOf of recipient)2332 Self::add_fungible_item(collection, recipient, value)?;23332334 // update balanceOf of sender2335 <Balance<T>>::insert(collection_id, owner.as_sub(), balance.value - value);23362337 // Reduce or remove sender2338 if balance.value == value {2339 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());2340 }2341 else {2342 balance.value -= value;2343 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2344 }23452346 collection.log(2347 Vec::from([2348 eth::TRANSFER_FUNGIBLE_TOPIC,2349 eth::address_to_topic(owner.as_eth()),2350 eth::address_to_topic(recipient.as_eth()),2351 ]),2352 abi_encode!(uint256(value.into())),2353 );2354 Self::deposit_event(RawEvent::Transfer(collection.id, 1, owner.clone(), recipient.clone(), value));23552356 Ok(())2357 }23582359 fn transfer_refungible(2360 collection: &CollectionHandle<T>,2361 item_id: TokenId,2362 value: u128,2363 owner: T::CrossAccountId,2364 new_owner: T::CrossAccountId,2365 ) -> DispatchResult {2366 let collection_id = collection.id;2367 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2368 .ok_or(Error::<T>::TokenNotFound)?;23692370 let item = full_item2371 .owner2372 .iter()2373 .filter(|i| i.owner == owner)2374 .next()2375 .ok_or(Error::<T>::TokenNotFound)?;2376 let amount = item.fraction;23772378 ensure!(amount >= value, Error::<T>::TokenValueTooLow);23792380 // update balance2381 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2382 .checked_sub(value)2383 .ok_or(Error::<T>::NumOverflow)?;2384 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);23852386 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2387 .checked_add(value)2388 .ok_or(Error::<T>::NumOverflow)?;2389 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);23902391 let old_owner = item.owner.clone();2392 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);23932394 // transfer2395 if amount == value && !new_owner_has_account {2396 // change owner2397 // new owner do not have account2398 let mut new_full_item = full_item.clone();2399 new_full_item2400 .owner2401 .iter_mut()2402 .find(|i| i.owner == owner)2403 .expect("old owner does present in refungible")2404 .owner = new_owner.clone();2405 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);24062407 // update index collection2408 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2409 } else {2410 let mut new_full_item = full_item.clone();2411 new_full_item2412 .owner2413 .iter_mut()2414 .find(|i| i.owner == owner)2415 .expect("old owner does present in refungible")2416 .fraction -= value;24172418 // separate amount2419 if new_owner_has_account {2420 // new owner has account2421 new_full_item2422 .owner2423 .iter_mut()2424 .find(|i| i.owner == new_owner)2425 .expect("new owner has account")2426 .fraction += value;2427 } else {2428 // new owner do not have account2429 new_full_item.owner.push(Ownership {2430 owner: new_owner.clone(),2431 fraction: value,2432 });2433 Self::add_token_index(collection_id, item_id, &new_owner)?;2434 }24352436 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2437 }24382439 Self::deposit_event(RawEvent::Transfer(collection.id, item_id, owner, new_owner, amount));24402441 Ok(())2442 }24432444 fn transfer_nft(2445 collection: &CollectionHandle<T>,2446 item_id: TokenId,2447 sender: T::CrossAccountId,2448 new_owner: T::CrossAccountId,2449 ) -> DispatchResult {2450 let collection_id = collection.id;2451 let mut item = <NftItemList<T>>::get(collection_id, item_id)2452 .ok_or(Error::<T>::TokenNotFound)?;24532454 ensure!(2455 sender == item.owner,2456 Error::<T>::MustBeTokenOwner2457 );24582459 // update balance2460 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2461 .checked_sub(1)2462 .ok_or(Error::<T>::NumOverflow)?;2463 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);24642465 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2466 .checked_add(1)2467 .ok_or(Error::<T>::NumOverflow)?;2468 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);24692470 // change owner2471 let old_owner = item.owner.clone();2472 item.owner = new_owner.clone();2473 <NftItemList<T>>::insert(collection_id, item_id, item);24742475 // update index collection2476 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;24772478 collection.log(2479 Vec::from([2480 eth::TRANSFER_NFT_TOPIC,2481 eth::address_to_topic(sender.as_eth()),2482 eth::address_to_topic(new_owner.as_eth()),2483 eth::u32_to_topic(item_id),2484 ]),2485 abi_encode!(),2486 );2487 Self::deposit_event(RawEvent::Transfer(collection.id, item_id, sender, new_owner, 1));24882489 Ok(())2490 }2491 2492 fn set_re_fungible_variable_data(2493 collection: &CollectionHandle<T>,2494 item_id: TokenId,2495 data: Vec<u8>2496 ) -> DispatchResult {2497 let collection_id = collection.id;2498 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2499 .ok_or(Error::<T>::TokenNotFound)?;25002501 item.variable_data = data;25022503 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);25042505 Ok(())2506 }25072508 fn set_nft_variable_data(2509 collection: &CollectionHandle<T>,2510 item_id: TokenId,2511 data: Vec<u8>2512 ) -> DispatchResult {2513 let collection_id = collection.id;2514 let mut item = <NftItemList<T>>::get(collection_id, item_id)2515 .ok_or(Error::<T>::TokenNotFound)?;2516 2517 item.variable_data = data;25182519 <NftItemList<T>>::insert(collection_id, item_id, item);2520 2521 Ok(())2522 }25232524 fn init_collection(item: &Collection<T>) {2525 // check params2526 assert!(2527 item.decimal_points <= MAX_DECIMAL_POINTS,2528 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2529 );2530 assert!(2531 item.name.len() <= 64,2532 "Collection name can not be longer than 63 char"2533 );2534 assert!(2535 item.name.len() <= 256,2536 "Collection description can not be longer than 255 char"2537 );2538 assert!(2539 item.token_prefix.len() <= 16,2540 "Token prefix can not be longer than 15 char"2541 );25422543 // Generate next collection ID2544 let next_id = CreatedCollectionCount::get()2545 .checked_add(1)2546 .unwrap();25472548 CreatedCollectionCount::put(next_id);2549 }25502551 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2552 let current_index = <ItemListIndex>::get(collection_id)2553 .checked_add(1)2554 .unwrap();25552556 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();25572558 <ItemListIndex>::insert(collection_id, current_index);25592560 // Update balance2561 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2562 .checked_add(1)2563 .unwrap();2564 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2565 }25662567 fn init_fungible_token(collection_id: CollectionId, owner: &T::CrossAccountId, item: &FungibleItemType) {2568 let current_index = <ItemListIndex>::get(collection_id)2569 .checked_add(1)2570 .unwrap();25712572 Self::add_token_index(collection_id, current_index, owner).unwrap();25732574 <ItemListIndex>::insert(collection_id, current_index);25752576 // Update balance2577 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2578 .checked_add(item.value)2579 .unwrap();2580 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2581 }25822583 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::CrossAccountId>) {2584 let current_index = <ItemListIndex>::get(collection_id)2585 .checked_add(1)2586 .unwrap();25872588 let value = item.owner.first().unwrap().fraction;2589 let owner = item.owner.first().unwrap().owner.clone();25902591 Self::add_token_index(collection_id, current_index, &owner).unwrap();25922593 <ItemListIndex>::insert(collection_id, current_index);25942595 // Update balance2596 let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2597 .checked_add(value)2598 .unwrap();2599 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2600 }26012602 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::CrossAccountId) -> DispatchResult {2603 // add to account limit2604 if <AccountItemCount<T>>::contains_key(owner.as_sub()) {26052606 // bound Owned tokens by a single address2607 let count = <AccountItemCount<T>>::get(owner.as_sub());2608 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);26092610 <AccountItemCount<T>>::insert(owner.as_sub(), count2611 .checked_add(1)2612 .ok_or(Error::<T>::NumOverflow)?);2613 }2614 else {2615 <AccountItemCount<T>>::insert(owner.as_sub(), 1);2616 }26172618 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2619 if list_exists {2620 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2621 let item_contains = list.contains(&item_index.clone());26222623 if !item_contains {2624 list.push(item_index.clone());2625 }26262627 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2628 } else {2629 let mut itm = Vec::new();2630 itm.push(item_index.clone());2631 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), itm);2632 }26332634 Ok(())2635 }26362637 fn remove_token_index(2638 collection_id: CollectionId,2639 item_index: TokenId,2640 owner: &T::CrossAccountId,2641 ) -> DispatchResult {26422643 // update counter2644 <AccountItemCount<T>>::insert(owner.as_sub(), 2645 <AccountItemCount<T>>::get(owner.as_sub())2646 .checked_sub(1)2647 .ok_or(Error::<T>::NumOverflow)?);264826492650 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2651 if list_exists {2652 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2653 let item_contains = list.contains(&item_index.clone());26542655 if item_contains {2656 list.retain(|&item| item != item_index);2657 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2658 }2659 }26602661 Ok(())2662 }26632664 fn move_token_index(2665 collection_id: CollectionId,2666 item_index: TokenId,2667 old_owner: &T::CrossAccountId,2668 new_owner: &T::CrossAccountId,2669 ) -> DispatchResult {2670 Self::remove_token_index(collection_id, item_index, old_owner)?;2671 Self::add_token_index(collection_id, item_index, new_owner)?;26722673 Ok(())2674 }2675 2676 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2677 ensure!(<ContractOwner<T>>::get(contract) == Some(account), Error::<T>::NoPermission);26782679 Ok(())2680 }2681}26822683////////////////////////////////////////////////////////////////////////////////////////////////////2684// Economic models2685// #region26862687/// Fee multiplier.2688pub type Multiplier = FixedU128;26892690type BalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::Balance;26912692/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2693/// in the queue.2694#[derive(Encode, Decode, Clone, Eq, PartialEq)]2695pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);26962697impl<T: Config + Send + Sync> sp_std::fmt::Debug 2698 for ChargeTransactionPayment<T>2699{2700 #[cfg(feature = "std")]2701 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2702 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2703 }2704 #[cfg(not(feature = "std"))]2705 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2706 Ok(())2707 }2708}27092710impl<T: Config> ChargeTransactionPayment<T>2711where2712 T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2713 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2714 T::AccountId: AsRef<[u8]>,2715 T::AccountId: UncheckedFrom<T::Hash>,2716{2717 fn traditional_fee(2718 len: usize,2719 info: &DispatchInfoOf<T::Call>,2720 tip: BalanceOf<T>,2721 ) -> BalanceOf<T>2722 where2723 T::Call: Dispatchable<Info = DispatchInfo>,2724 {2725 <pallet_transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2726 }27272728 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2729 let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);2730 let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);2731 let len_saturation = max_block_length as u64 / (len as u64).max(1);2732 let coefficient: BalanceOf<T> = weight_saturation2733 .min(len_saturation)2734 .saturated_into::<BalanceOf<T>>();2735 final_fee2736 .saturating_mul(coefficient)2737 .saturated_into::<TransactionPriority>()2738 }27392740 fn withdraw_fee(2741 &self,2742 who: &T::AccountId,2743 call: &T::Call,2744 info: &DispatchInfoOf<T::Call>,2745 len: usize,2746 ) -> Result<2747 (2748 BalanceOf<T>,2749 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2750 ),2751 TransactionValidityError,2752 > {2753 let tip = self.0;27542755 let fee = Self::traditional_fee(len, info, tip);27562757 // Only mess with balances if fee is not zero.2758 if fee.is_zero() {2759 return <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)2760 .map(|i| (fee, i));2761 }27622763 // Determine who is paying transaction fee based on ecnomic model2764 // Parse call to extract collection ID and access collection sponsor2765 let mut sponsor: Option<T::AccountId> = (|| match IsSubType::<Call<T>>::is_sub_type(call) {2766 Some(Call::create_item(collection_id, _owner, _properties)) => {2767 let collection = <CollectionById<T>>::get(collection_id)?;27682769 // sponsor timeout2770 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;27712772 let limit = collection.limits.sponsor_transfer_timeout;2773 if <CreateItemBasket<T>>::contains_key((collection_id, &who)) {2774 let last_tx_block = <CreateItemBasket<T>>::get((collection_id, &who));2775 let limit_time = last_tx_block + limit.into();2776 if block_number <= limit_time {2777 return None;2778 }2779 }2780 <CreateItemBasket<T>>::insert((collection_id, who.clone()), block_number);27812782 // check free create limit2783 if collection.limits.sponsored_data_size >= (_properties.len() as u32) {2784 collection.sponsorship.sponsor()2785 .cloned()2786 } else {2787 None2788 }2789 }2790 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2791 let collection = <CollectionById<T>>::get(collection_id)?;2792 2793 let mut sponsor_transfer = false;2794 if collection.sponsorship.confirmed() {27952796 let collection_limits = collection.limits;2797 let collection_mode = collection.mode;2798 2799 // sponsor timeout2800 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2801 sponsor_transfer = match collection_mode {2802 CollectionMode::NFT => {2803 2804 // get correct limit2805 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2806 collection_limits.sponsor_transfer_timeout2807 } else {2808 ChainLimit::get().nft_sponsor_transfer_timeout2809 };2810 2811 let mut sponsored = true;2812 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2813 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2814 let limit_time = last_tx_block + limit.into();2815 if block_number <= limit_time {2816 sponsored = false;2817 }2818 }2819 if sponsored {2820 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2821 }28222823 sponsored2824 }2825 CollectionMode::Fungible(_) => {2826 2827 // get correct limit2828 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2829 collection_limits.sponsor_transfer_timeout2830 } else {2831 ChainLimit::get().fungible_sponsor_transfer_timeout2832 };2833 2834 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2835 let mut sponsored = true;2836 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2837 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2838 let limit_time = last_tx_block + limit.into();2839 if block_number <= limit_time {2840 sponsored = false;2841 }2842 }2843 if sponsored {2844 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2845 }28462847 sponsored2848 }2849 CollectionMode::ReFungible => {2850 2851 // get correct limit2852 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2853 collection_limits.sponsor_transfer_timeout2854 } else {2855 ChainLimit::get().refungible_sponsor_transfer_timeout2856 };2857 2858 let mut sponsored = true;2859 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2860 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2861 let limit_time = last_tx_block + limit.into();2862 if block_number <= limit_time {2863 sponsored = false;2864 }2865 }2866 if sponsored {2867 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2868 }28692870 sponsored2871 }2872 _ => {2873 false2874 },2875 };2876 }28772878 if !sponsor_transfer {2879 None2880 } else {2881 collection.sponsorship.sponsor()2882 .cloned()2883 }2884 }28852886 Some(Call::set_variable_meta_data(collection_id, item_id, data)) => {2887 let mut sponsor_metadata_changes = false;28882889 let collection = <CollectionById<T>>::get(collection_id)?;28902891 if2892 collection.sponsorship.confirmed() &&2893 // Can't sponsor fungible collection, this tx will be rejected2894 // as invalid2895 !matches!(collection.mode, CollectionMode::Fungible(_)) &&2896 data.len() <= collection.limits.sponsored_data_size as usize2897 {2898 if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit {2899 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;29002901 if <VariableMetaDataBasket<T>>::get(collection_id, item_id)2902 .map(|last_block| block_number - last_block > rate_limit)2903 .unwrap_or(true) 2904 {2905 sponsor_metadata_changes = true;2906 <VariableMetaDataBasket<T>>::insert(collection_id, item_id, block_number);2907 }2908 }2909 }29102911 if !sponsor_metadata_changes {2912 None2913 } else {2914 collection.sponsorship.sponsor().cloned()2915 }2916 }29172918 _ => None,2919 })();29202921 match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {2922 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {29232924 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());29252926 let owned_contract = <ContractOwner<T>>::get(called_contract.clone()).as_ref() == Some(who);2927 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone());2928 2929 if !owned_contract && white_list_enabled {2930 if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2931 return Err(InvalidTransaction::Call.into());2932 }2933 }2934 },2935 _ => {},2936 }29372938 // Sponsor smart contracts2939 sponsor = sponsor.or_else(|| match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {29402941 // On instantiation: set the contract owner2942 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {29432944 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2945 &who,2946 code_hash,2947 salt,2948 );2949 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());29502951 None2952 },29532954 // On instantiation with code: set the contract owner2955 Some(pallet_contracts::Call::instantiate_with_code(_endowment, _gas_limit, _code, _data, _salt)) => {29562957 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2958 &who,2959 &T::Hashing::hash(&_code),2960 _salt,2961 );29622963 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());29642965 None2966 }29672968 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2969 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {29702971 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());29722973 let mut sponsor_transfer = false;2974 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2975 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2976 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2977 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2978 let limit_time = last_tx_block + rate_limit;29792980 if block_number >= limit_time {2981 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2982 sponsor_transfer = true;2983 }2984 } else {2985 sponsor_transfer = false;2986 }2987 2988 if sponsor_transfer {2989 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2990 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2991 return Some(called_contract);2992 }2993 }2994 }29952996 None2997 },29982999 _ => None,3000 });30013002 let who_pays_fee = sponsor.unwrap_or_else(|| who.clone());30033004 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(&who_pays_fee, call, info, fee, tip)3005 .map(|i| (fee, i))3006 }3007}300830093010impl<T: Config + Send + Sync> SignedExtension3011 for ChargeTransactionPayment<T>3012where3013 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,3014 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,3015 T::AccountId: AsRef<[u8]>,3016 T::AccountId: UncheckedFrom<T::Hash>,3017{3018 const IDENTIFIER: &'static str = "ChargeTransactionPayment";3019 type AccountId = T::AccountId;3020 type Call = T::Call;3021 type AdditionalSigned = ();3022 type Pre = (3023 // tip3024 BalanceOf<T>,3025 // who pays fee3026 Self::AccountId,3027 // imbalance resulting from withdrawing the fee3028 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,3029 );3030 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {3031 Ok(())3032 }30333034 fn validate(3035 &self,3036 who: &Self::AccountId,3037 call: &Self::Call,3038 info: &DispatchInfoOf<Self::Call>,3039 len: usize,3040 ) -> TransactionValidity {3041 let (fee, _) = self.withdraw_fee(who, call, info, len)?;3042 Ok(ValidTransaction {3043 priority: Self::get_priority(len, info, fee),3044 ..Default::default()3045 })3046 }30473048 fn pre_dispatch(3049 self,3050 who: &Self::AccountId,3051 call: &Self::Call,3052 info: &DispatchInfoOf<Self::Call>,3053 len: usize,3054 ) -> Result<Self::Pre, TransactionValidityError> {3055 let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;3056 Ok((self.0, who.clone(), imbalance))3057 }30583059 fn post_dispatch(3060 pre: Self::Pre,3061 info: &DispatchInfoOf<Self::Call>,3062 post_info: &PostDispatchInfoOf<Self::Call>,3063 len: usize,3064 _result: &DispatchResult,3065 ) -> Result<(), TransactionValidityError> {3066 let (tip, who, imbalance) = pre;3067 let actual_fee = pallet_transaction_payment::Module::<T>::compute_actual_fee(3068 len as u32,3069 info,3070 post_info,3071 tip,3072 );3073 <T as pallet_transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;3074 Ok(())3075 }3076}30773078// #endregion30793080sp_api::decl_runtime_apis! {3081 pub trait NftApi {3082 /// Used for ethereum integration3083 fn eth_contract_code(account: H160) -> Option<Vec<u8>>;3084 }3085}1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]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::AccountId,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 AccountId = <T as frame_system::Config>::AccountId,642 CrossAccountId = <T as Config>::CrossAccountId,643 {644 /// New collection was created645 /// 646 /// # Arguments647 /// 648 /// * collection_id: Globally unique identifier of newly created collection.649 /// 650 /// * mode: [CollectionMode] converted into u8.651 /// 652 /// * account_id: Collection owner.653 CollectionCreated(CollectionId, u8, AccountId),654655 /// New item was created.656 /// 657 /// # Arguments658 /// 659 /// * collection_id: Id of the collection where item was created.660 /// 661 /// * item_id: Id of an item. Unique within the collection.662 ///663 /// * recipient: Owner of newly created item 664 ItemCreated(CollectionId, TokenId, CrossAccountId),665666 /// Collection item was burned.667 /// 668 /// # Arguments669 /// 670 /// collection_id.671 /// 672 /// item_id: Identifier of burned NFT.673 ItemDestroyed(CollectionId, TokenId),674675 /// Item was transferred676 ///677 /// * collection_id: Id of collection to which item is belong678 ///679 /// * item_id: Id of an item680 ///681 /// * sender: Original owner of item682 ///683 /// * recipient: New owner of item684 ///685 /// * amount: Always 1 for NFT686 Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),687688 /// * collection_id689 ///690 /// * item_id691 ///692 /// * sender693 ///694 /// * spender695 ///696 /// * amount697 Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),698 }699);700701decl_module! {702 pub struct Module<T: Config> for enum Call 703 where 704 origin: T::Origin705 {706 fn deposit_event() = default;707 type Error = Error<T>;708709 fn on_initialize(now: T::BlockNumber) -> Weight {710 0711 }712713 /// 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.714 /// 715 /// # Permissions716 /// 717 /// * Anyone.718 /// 719 /// # Arguments720 /// 721 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.722 /// 723 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.724 /// 725 /// * token_prefix: UTF-8 string with token prefix.726 /// 727 /// * mode: [CollectionMode] collection type and type dependent data.728 // returns collection ID729 #[weight = <T as Config>::WeightInfo::create_collection()]730 #[transactional]731 pub fn create_collection(origin,732 collection_name: Vec<u16>,733 collection_description: Vec<u16>,734 token_prefix: Vec<u8>,735 mode: CollectionMode) -> DispatchResult {736737 // Anyone can create a collection738 let who = ensure_signed(origin)?;739740 // Take a (non-refundable) deposit of collection creation741 let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();742 imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(743 &T::TreasuryAccountId::get(),744 T::CollectionCreationPrice::get(),745 ));746 <T as Config>::Currency::settle(747 &who,748 imbalance,749 WithdrawReasons::TRANSFER,750 ExistenceRequirement::KeepAlive,751 ).map_err(|_| Error::<T>::NoPermission)?;752753 let decimal_points = match mode {754 CollectionMode::Fungible(points) => points,755 _ => 0756 };757758 let chain_limit = ChainLimit::get();759760 let created_count = CreatedCollectionCount::get();761 let destroyed_count = DestroyedCollectionCount::get();762763 // bound Total number of collections764 ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);765766 // check params767 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);768 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);769 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);770 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);771772 // Generate next collection ID773 let next_id = created_count774 .checked_add(1)775 .ok_or(Error::<T>::NumOverflow)?;776777 CreatedCollectionCount::put(next_id);778779 let limits = CollectionLimits {780 sponsored_data_size: chain_limit.custom_data_limit,781 ..Default::default()782 };783784 // Create new collection785 let new_collection = Collection {786 owner: who.clone(),787 name: collection_name,788 mode: mode.clone(),789 mint_mode: false,790 access: AccessMode::Normal,791 description: collection_description,792 decimal_points: decimal_points,793 token_prefix: token_prefix,794 offchain_schema: Vec::new(),795 schema_version: SchemaVersion::ImageURL,796 sponsorship: SponsorshipState::Disabled,797 variable_on_chain_schema: Vec::new(),798 const_on_chain_schema: Vec::new(),799 limits,800 };801802 // Add new collection to map803 <CollectionById<T>>::insert(next_id, new_collection);804805 // call event806 Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.into(), who));807808 Ok(())809 }810811 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.812 /// 813 /// # Permissions814 /// 815 /// * Collection Owner.816 /// 817 /// # Arguments818 /// 819 /// * collection_id: collection to destroy.820 #[weight = <T as Config>::WeightInfo::destroy_collection()]821 #[transactional]822 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {823824 let sender = ensure_signed(origin)?;825 let collection = Self::get_collection(collection_id)?;826 Self::check_owner_permissions(&collection, &sender)?;827 if !collection.limits.owner_can_destroy {828 fail!(Error::<T>::NoPermission);829 }830831 <AddressTokens<T>>::remove_prefix(collection_id);832 <Allowances<T>>::remove_prefix(collection_id);833 <Balance<T>>::remove_prefix(collection_id);834 <ItemListIndex>::remove(collection_id);835 <AdminList<T>>::remove(collection_id);836 <CollectionById<T>>::remove(collection_id);837 <WhiteList<T>>::remove_prefix(collection_id);838839 <NftItemList<T>>::remove_prefix(collection_id);840 <FungibleItemList<T>>::remove_prefix(collection_id);841 <ReFungibleItemList<T>>::remove_prefix(collection_id);842843 <NftTransferBasket<T>>::remove_prefix(collection_id);844 <FungibleTransferBasket<T>>::remove_prefix(collection_id);845 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);846847 <VariableMetaDataBasket<T>>::remove_prefix(collection_id);848849 DestroyedCollectionCount::put(DestroyedCollectionCount::get()850 .checked_add(1)851 .ok_or(Error::<T>::NumOverflow)?);852853 Ok(())854 }855856 /// Add an address to white list.857 /// 858 /// # Permissions859 /// 860 /// * Collection Owner861 /// * Collection Admin862 /// 863 /// # Arguments864 /// 865 /// * collection_id.866 /// 867 /// * address.868 #[weight = <T as Config>::WeightInfo::add_to_white_list()]869 #[transactional]870 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{871872 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);873 let collection = Self::get_collection(collection_id)?;874875 Self::toggle_white_list_internal(876 &sender,877 &collection,878 &address,879 true,880 )?;881882 Ok(())883 }884885 /// Remove an address from white list.886 /// 887 /// # Permissions888 /// 889 /// * Collection Owner890 /// * Collection Admin891 /// 892 /// # Arguments893 /// 894 /// * collection_id.895 /// 896 /// * address.897 #[weight = <T as Config>::WeightInfo::remove_from_white_list()]898 #[transactional]899 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{900901 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);902 let collection = Self::get_collection(collection_id)?;903904 Self::toggle_white_list_internal(905 &sender,906 &collection,907 &address,908 false,909 )?;910911 Ok(())912 }913914 /// Toggle between normal and white list access for the methods with access for `Anyone`.915 /// 916 /// # Permissions917 /// 918 /// * Collection Owner.919 /// 920 /// # Arguments921 /// 922 /// * collection_id.923 /// 924 /// * mode: [AccessMode]925 #[weight = <T as Config>::WeightInfo::set_public_access_mode()]926 #[transactional]927 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult928 {929 let sender = ensure_signed(origin)?;930931 let mut target_collection = Self::get_collection(collection_id)?;932 Self::check_owner_permissions(&target_collection, &sender)?;933 target_collection.access = mode;934 Self::save_collection(target_collection);935936 Ok(())937 }938939 /// Allows Anyone to create tokens if:940 /// * White List is enabled, and941 /// * Address is added to white list, and942 /// * This method was called with True parameter943 /// 944 /// # Permissions945 /// * Collection Owner946 ///947 /// # Arguments948 /// 949 /// * collection_id.950 /// 951 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.952 #[weight = <T as Config>::WeightInfo::set_mint_permission()]953 #[transactional]954 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult955 {956 let sender = ensure_signed(origin)?;957958 let mut target_collection = Self::get_collection(collection_id)?;959 Self::check_owner_permissions(&target_collection, &sender)?;960 target_collection.mint_mode = mint_permission;961 Self::save_collection(target_collection);962963 Ok(())964 }965966 /// Change the owner of the collection.967 /// 968 /// # Permissions969 /// 970 /// * Collection Owner.971 /// 972 /// # Arguments973 /// 974 /// * collection_id.975 /// 976 /// * new_owner.977 #[weight = <T as Config>::WeightInfo::change_collection_owner()]978 #[transactional]979 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {980981 let sender = ensure_signed(origin)?;982 let mut target_collection = Self::get_collection(collection_id)?;983 Self::check_owner_permissions(&target_collection, &sender)?;984 target_collection.owner = new_owner;985 Self::save_collection(target_collection);986987 Ok(())988 }989990 /// Adds an admin of the Collection.991 /// 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. 992 /// 993 /// # Permissions994 /// 995 /// * Collection Owner.996 /// * Collection Admin.997 /// 998 /// # Arguments999 /// 1000 /// * collection_id: ID of the Collection to add admin for.1001 /// 1002 /// * new_admin_id: Address of new admin to add.1003 #[weight = <T as Config>::WeightInfo::add_collection_admin()]1004 #[transactional]1005 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {1006 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1007 let collection = Self::get_collection(collection_id)?;1008 Self::check_owner_or_admin_permissions(&collection, &sender)?;1009 let mut admin_arr = <AdminList<T>>::get(collection_id);10101011 match admin_arr.binary_search(&new_admin_id) {1012 Ok(_) => {},1013 Err(idx) => {1014 let limits = ChainLimit::get();1015 ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);1016 admin_arr.insert(idx, new_admin_id);1017 <AdminList<T>>::insert(collection_id, admin_arr);1018 }1019 }1020 Ok(())1021 }10221023 /// 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.1024 ///1025 /// # Permissions1026 /// 1027 /// * Collection Owner.1028 /// * Collection Admin.1029 /// 1030 /// # Arguments1031 /// 1032 /// * collection_id: ID of the Collection to remove admin for.1033 /// 1034 /// * account_id: Address of admin to remove.1035 #[weight = <T as Config>::WeightInfo::remove_collection_admin()]1036 #[transactional]1037 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {1038 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1039 let collection = Self::get_collection(collection_id)?;1040 Self::check_owner_or_admin_permissions(&collection, &sender)?;1041 let mut admin_arr = <AdminList<T>>::get(collection_id);10421043 match admin_arr.binary_search(&account_id) {1044 Ok(idx) => {1045 admin_arr.remove(idx);1046 <AdminList<T>>::insert(collection_id, admin_arr);1047 },1048 Err(_) => {}1049 }1050 Ok(())1051 }10521053 /// # Permissions1054 /// 1055 /// * Collection Owner1056 /// 1057 /// # Arguments1058 /// 1059 /// * collection_id.1060 /// 1061 /// * new_sponsor.1062 #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]1063 #[transactional]1064 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {1065 let sender = ensure_signed(origin)?;1066 let mut target_collection = Self::get_collection(collection_id)?;1067 Self::check_owner_permissions(&target_collection, &sender)?;10681069 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);1070 Self::save_collection(target_collection);10711072 Ok(())1073 }10741075 /// # Permissions1076 /// 1077 /// * Sponsor.1078 /// 1079 /// # Arguments1080 /// 1081 /// * collection_id.1082 #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]1083 #[transactional]1084 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {1085 let sender = ensure_signed(origin)?;10861087 let mut target_collection = Self::get_collection(collection_id)?;1088 ensure!(1089 target_collection.sponsorship.pending_sponsor() == Some(&sender),1090 Error::<T>::ConfirmUnsetSponsorFail1091 );10921093 target_collection.sponsorship = SponsorshipState::Confirmed(sender);1094 Self::save_collection(target_collection);10951096 Ok(())1097 }10981099 /// Switch back to pay-per-own-transaction model.1100 ///1101 /// # Permissions1102 ///1103 /// * Collection owner.1104 /// 1105 /// # Arguments1106 /// 1107 /// * collection_id.1108 #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]1109 #[transactional]1110 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {1111 let sender = ensure_signed(origin)?;11121113 let mut target_collection = Self::get_collection(collection_id)?;1114 Self::check_owner_permissions(&target_collection, &sender)?;11151116 target_collection.sponsorship = SponsorshipState::Disabled;1117 Self::save_collection(target_collection);11181119 Ok(())1120 }11211122 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.1123 /// 1124 /// # Permissions1125 /// 1126 /// * Collection Owner.1127 /// * Collection Admin.1128 /// * Anyone if1129 /// * White List is enabled, and1130 /// * Address is added to white list, and1131 /// * MintPermission is enabled (see SetMintPermission method)1132 /// 1133 /// # Arguments1134 /// 1135 /// * collection_id: ID of the collection.1136 /// 1137 /// * owner: Address, initial owner of the NFT.1138 ///1139 /// * data: Token data to store on chain.1140 // #[weight =1141 // (130_000_000 as Weight)1142 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))1143 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))1144 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]11451146 #[weight = <T as Config>::WeightInfo::create_item(data.len())]1147 #[transactional]1148 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {1149 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1150 let collection = Self::get_collection(collection_id)?;11511152 Self::create_item_internal(&sender, &collection, &owner, data)?;11531154 Self::submit_logs(collection)?;1155 Ok(())1156 }11571158 /// This method creates multiple items in a collection created with CreateCollection method.1159 /// 1160 /// # Permissions1161 /// 1162 /// * Collection Owner.1163 /// * Collection Admin.1164 /// * Anyone if1165 /// * White List is enabled, and1166 /// * Address is added to white list, and1167 /// * MintPermission is enabled (see SetMintPermission method)1168 /// 1169 /// # Arguments1170 /// 1171 /// * collection_id: ID of the collection.1172 /// 1173 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].1174 /// 1175 /// * owner: Address, initial owner of the NFT.1176 #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()1177 .map(|data| { data.len() })1178 .sum())]1179 #[transactional]1180 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {11811182 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);1183 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1184 let collection = Self::get_collection(collection_id)?;11851186 Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;11871188 Self::submit_logs(collection)?;1189 Ok(())1190 }11911192 /// Destroys a concrete instance of NFT.1193 /// 1194 /// # Permissions1195 /// 1196 /// * Collection Owner.1197 /// * Collection Admin.1198 /// * Current NFT Owner.1199 /// 1200 /// # Arguments1201 /// 1202 /// * collection_id: ID of the collection.1203 /// 1204 /// * item_id: ID of NFT to burn.1205 #[weight = <T as Config>::WeightInfo::burn_item()]1206 #[transactional]1207 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {12081209 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1210 let target_collection = Self::get_collection(collection_id)?;12111212 Self::burn_item_internal(&sender, &target_collection, item_id, value)?;12131214 Self::submit_logs(target_collection)?;1215 Ok(())1216 }12171218 /// Change ownership of the token.1219 /// 1220 /// # Permissions1221 /// 1222 /// * Collection Owner1223 /// * Collection Admin1224 /// * Current NFT owner1225 ///1226 /// # Arguments1227 /// 1228 /// * recipient: Address of token recipient.1229 /// 1230 /// * collection_id.1231 /// 1232 /// * item_id: ID of the item1233 /// * Non-Fungible Mode: Required.1234 /// * Fungible Mode: Ignored.1235 /// * Re-Fungible Mode: Required.1236 /// 1237 /// * value: Amount to transfer.1238 /// * Non-Fungible Mode: Ignored1239 /// * Fungible Mode: Must specify transferred amount1240 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1241 #[weight = <T as Config>::WeightInfo::transfer()]1242 #[transactional]1243 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {1244 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1245 let collection = Self::get_collection(collection_id)?;12461247 Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;12481249 Self::submit_logs(collection)?;1250 Ok(())1251 }12521253 /// Set, change, or remove approved address to transfer the ownership of the NFT.1254 /// 1255 /// # Permissions1256 /// 1257 /// * Collection Owner1258 /// * Collection Admin1259 /// * Current NFT owner1260 /// 1261 /// # Arguments1262 /// 1263 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1264 /// 1265 /// * collection_id.1266 /// 1267 /// * item_id: ID of the item.1268 #[weight = <T as Config>::WeightInfo::approve()]1269 #[transactional]1270 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1271 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1272 let collection = Self::get_collection(collection_id)?;12731274 Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;12751276 Self::submit_logs(collection)?;1277 Ok(())1278 }1279 1280 /// 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.1281 /// 1282 /// # Permissions1283 /// * Collection Owner1284 /// * Collection Admin1285 /// * Current NFT owner1286 /// * Address approved by current NFT owner1287 /// 1288 /// # Arguments1289 /// 1290 /// * from: Address that owns token.1291 /// 1292 /// * recipient: Address of token recipient.1293 /// 1294 /// * collection_id.1295 /// 1296 /// * item_id: ID of the item.1297 /// 1298 /// * value: Amount to transfer.1299 #[weight = <T as Config>::WeightInfo::transfer_from()]1300 #[transactional]1301 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1302 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1303 let collection = Self::get_collection(collection_id)?;13041305 Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;13061307 Self::submit_logs(collection)?;1308 Ok(())1309 }1310 // #[weight = 0]1311 // // let no_perm_mes = "You do not have permissions to modify this collection";1312 // // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1313 // // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1314 // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);13151316 // // // on_nft_received call13171318 // // Self::transfer(origin, collection_id, item_id, new_owner)?;13191320 // Ok(())1321 // }13221323 /// Set off-chain data schema.1324 /// 1325 /// # Permissions1326 /// 1327 /// * Collection Owner1328 /// * Collection Admin1329 /// 1330 /// # Arguments1331 /// 1332 /// * collection_id.1333 /// 1334 /// * schema: String representing the offchain data schema.1335 #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1336 #[transactional]1337 pub fn set_variable_meta_data (1338 origin,1339 collection_id: CollectionId,1340 item_id: TokenId,1341 data: Vec<u8>1342 ) -> DispatchResult {1343 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1344 1345 let collection = Self::get_collection(collection_id)?;13461347 Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;13481349 Ok(())1350 }1351 1352 /// Set schema standard1353 /// ImageURL1354 /// Unique1355 /// 1356 /// # Permissions1357 /// 1358 /// * Collection Owner1359 /// * Collection Admin1360 /// 1361 /// # Arguments1362 /// 1363 /// * collection_id.1364 /// 1365 /// * schema: SchemaVersion: enum1366 #[weight = <T as Config>::WeightInfo::set_schema_version()]1367 #[transactional]1368 pub fn set_schema_version(1369 origin,1370 collection_id: CollectionId,1371 version: SchemaVersion1372 ) -> DispatchResult {1373 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1374 let mut target_collection = Self::get_collection(collection_id)?;1375 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1376 target_collection.schema_version = version;1377 Self::save_collection(target_collection);13781379 Ok(())1380 }13811382 /// Set off-chain data schema.1383 /// 1384 /// # Permissions1385 /// 1386 /// * Collection Owner1387 /// * Collection Admin1388 /// 1389 /// # Arguments1390 /// 1391 /// * collection_id.1392 /// 1393 /// * schema: String representing the offchain data schema.1394 #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1395 #[transactional]1396 pub fn set_offchain_schema(1397 origin,1398 collection_id: CollectionId,1399 schema: Vec<u8>1400 ) -> DispatchResult {1401 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1402 let mut target_collection = Self::get_collection(collection_id)?;1403 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;14041405 // check schema limit1406 ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");14071408 target_collection.offchain_schema = schema;1409 Self::save_collection(target_collection);14101411 Ok(())1412 }14131414 /// Set const on-chain data schema.1415 /// 1416 /// # Permissions1417 /// 1418 /// * Collection Owner1419 /// * Collection Admin1420 /// 1421 /// # Arguments1422 /// 1423 /// * collection_id.1424 /// 1425 /// * schema: String representing the const on-chain data schema.1426 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1427 #[transactional]1428 pub fn set_const_on_chain_schema (1429 origin,1430 collection_id: CollectionId,1431 schema: Vec<u8>1432 ) -> DispatchResult {1433 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1434 let mut target_collection = Self::get_collection(collection_id)?;1435 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;14361437 // check schema limit1438 ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");14391440 target_collection.const_on_chain_schema = schema;1441 Self::save_collection(target_collection);14421443 Ok(())1444 }14451446 /// Set variable on-chain data schema.1447 /// 1448 /// # Permissions1449 /// 1450 /// * Collection Owner1451 /// * Collection Admin1452 /// 1453 /// # Arguments1454 /// 1455 /// * collection_id.1456 /// 1457 /// * schema: String representing the variable on-chain data schema.1458 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1459 #[transactional]1460 pub fn set_variable_on_chain_schema (1461 origin,1462 collection_id: CollectionId,1463 schema: Vec<u8>1464 ) -> DispatchResult {1465 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1466 let mut target_collection = Self::get_collection(collection_id)?;1467 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;14681469 // check schema limit1470 ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");14711472 target_collection.variable_on_chain_schema = schema;1473 Self::save_collection(target_collection);14741475 Ok(())1476 }14771478 // Sudo permissions function1479 #[weight = <T as Config>::WeightInfo::set_chain_limits()]1480 #[transactional]1481 pub fn set_chain_limits(1482 origin,1483 limits: ChainLimits1484 ) -> DispatchResult {14851486 #[cfg(not(feature = "runtime-benchmarks"))]1487 ensure_root(origin)?;14881489 <ChainLimit>::put(limits);1490 Ok(())1491 }14921493 /// Enable smart contract self-sponsoring.1494 /// 1495 /// # Permissions1496 /// 1497 /// * Contract Owner1498 /// 1499 /// # Arguments1500 /// 1501 /// * contract address1502 /// * enable flag1503 /// 1504 #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1505 #[transactional]1506 pub fn enable_contract_sponsoring(1507 origin,1508 contract_address: T::AccountId,1509 enable: bool1510 ) -> DispatchResult {15111512 let sender = ensure_signed(origin)?;15131514 #[cfg(feature = "runtime-benchmarks")]1515 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15161517 Self::ensure_contract_owned(sender, &contract_address)?;15181519 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1520 Ok(())1521 }15221523 /// Set the rate limit for contract sponsoring to specified number of blocks.1524 /// 1525 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1526 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1527 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1528 /// from contract endowment if there are at least B blocks between such transactions. 1529 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1530 /// 1531 /// # Permissions1532 /// 1533 /// * Contract Owner1534 /// 1535 /// # Arguments1536 /// 1537 /// -`contract_address`: Address of the contract to sponsor1538 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1539 /// 1540 #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1541 #[transactional]1542 pub fn set_contract_sponsoring_rate_limit(1543 origin,1544 contract_address: T::AccountId,1545 rate_limit: T::BlockNumber1546 ) -> DispatchResult {1547 let sender = ensure_signed(origin)?;15481549 #[cfg(feature = "runtime-benchmarks")]1550 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15511552 Self::ensure_contract_owned(sender, &contract_address)?;1553 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1554 Ok(())1555 }15561557 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1558 /// 1559 /// # Permissions1560 /// 1561 /// * Address that deployed smart contract.1562 /// 1563 /// # Arguments1564 /// 1565 /// -`contract_address`: Address of the contract.1566 /// 1567 /// - `enable`: . 1568 #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1569 #[transactional]1570 pub fn toggle_contract_white_list(1571 origin,1572 contract_address: T::AccountId,1573 enable: bool1574 ) -> DispatchResult {1575 let sender = ensure_signed(origin)?;15761577 #[cfg(feature = "runtime-benchmarks")]1578 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15791580 Self::ensure_contract_owned(sender, &contract_address)?;1581 if enable {1582 <ContractWhiteListEnabled<T>>::insert(contract_address, true);1583 } else {1584 <ContractWhiteListEnabled<T>>::remove(contract_address);1585 }1586 Ok(())1587 }1588 1589 /// Add an address to smart contract white list.1590 /// 1591 /// # Permissions1592 /// 1593 /// * Address that deployed smart contract.1594 /// 1595 /// # Arguments1596 /// 1597 /// -`contract_address`: Address of the contract.1598 ///1599 /// -`account_address`: Address to add.1600 #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1601 #[transactional]1602 pub fn add_to_contract_white_list(1603 origin,1604 contract_address: T::AccountId,1605 account_address: T::AccountId1606 ) -> DispatchResult {1607 let sender = ensure_signed(origin)?;16081609 #[cfg(feature = "runtime-benchmarks")]1610 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1611 1612 Self::ensure_contract_owned(sender, &contract_address)?; 1613 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1614 Ok(())1615 }16161617 /// Remove an address from smart contract white list.1618 /// 1619 /// # Permissions1620 /// 1621 /// * Address that deployed smart contract.1622 /// 1623 /// # Arguments1624 /// 1625 /// -`contract_address`: Address of the contract.1626 ///1627 /// -`account_address`: Address to remove.1628 #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1629 #[transactional]1630 pub fn remove_from_contract_white_list(1631 origin,1632 contract_address: T::AccountId,1633 account_address: T::AccountId1634 ) -> DispatchResult {1635 let sender = ensure_signed(origin)?;16361637 #[cfg(feature = "runtime-benchmarks")]1638 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());16391640 Self::ensure_contract_owned(sender, &contract_address)?;1641 <ContractWhiteList<T>>::remove(contract_address, account_address);1642 Ok(())1643 }16441645 #[weight = <T as Config>::WeightInfo::set_collection_limits()]1646 #[transactional]1647 pub fn set_collection_limits(1648 origin,1649 collection_id: u32,1650 new_limits: CollectionLimits<T::BlockNumber>,1651 ) -> DispatchResult {1652 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1653 let mut target_collection = Self::get_collection(collection_id)?;1654 Self::check_owner_permissions(&target_collection, &sender.as_sub())?;1655 let old_limits = &target_collection.limits;1656 let chain_limits = ChainLimit::get();16571658 // collection bounds1659 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1660 new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && 1661 new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1662 Error::<T>::CollectionLimitBoundsExceeded);16631664 // token_limit check prev1665 ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1666 ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);16671668 ensure!(1669 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1670 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1671 Error::<T>::OwnerPermissionsCantBeReverted,1672 );16731674 target_collection.limits = new_limits;1675 Self::save_collection(target_collection);16761677 Ok(())1678 } 1679 }1680}16811682impl<T: Config> Module<T> {1683 pub fn create_item_internal(sender: &T::CrossAccountId, collection: &CollectionHandle<T>, owner: &T::CrossAccountId, data: CreateItemData) -> DispatchResult {1684 Self::can_create_items_in_collection(&collection, &sender, &owner, 1)?;1685 Self::validate_create_item_args(&collection, &data)?;1686 Self::create_item_no_validation(&collection, owner, data)?;16871688 Ok(())1689 }16901691 pub fn transfer_internal(sender: &T::CrossAccountId, recipient: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId, value: u128) -> DispatchResult {1692 // Limits check1693 Self::is_correct_transfer(target_collection, &recipient)?;16941695 // Transfer permissions check1696 ensure!(Self::is_item_owner(&sender, target_collection, item_id) ||1697 Self::is_owner_or_admin_permissions(target_collection, &sender),1698 Error::<T>::NoPermission);16991700 if target_collection.access == AccessMode::WhiteList {1701 Self::check_white_list(target_collection, &sender)?;1702 Self::check_white_list(target_collection, &recipient)?;1703 }17041705 match target_collection.mode1706 {1707 CollectionMode::NFT => Self::transfer_nft(target_collection, item_id, sender.clone(), recipient.clone())?,1708 CollectionMode::Fungible(_) => Self::transfer_fungible(target_collection, value, &sender, &recipient)?,1709 CollectionMode::ReFungible => Self::transfer_refungible(target_collection, item_id, value, sender.clone(), recipient.clone())?,1710 _ => ()1711 };17121713 Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, sender.clone(), recipient.clone(), value));17141715 Ok(())1716 }17171718 pub fn approve_internal(1719 sender: &T::CrossAccountId,1720 spender: &T::CrossAccountId,1721 collection: &CollectionHandle<T>,1722 item_id: TokenId,1723 amount: u1281724 ) -> DispatchResult {1725 Self::token_exists(&collection, item_id)?;17261727 // Transfer permissions check1728 let bypasses_limits = collection.limits.owner_can_transfer &&1729 Self::is_owner_or_admin_permissions(1730 &collection,1731 &sender,1732 );17331734 let allowance_limit = if bypasses_limits {1735 None1736 } else if let Some(amount) = Self::owned_amount(1737 &sender,1738 &collection,1739 item_id,1740 ) {1741 Some(amount)1742 } else {1743 fail!(Error::<T>::NoPermission);1744 };17451746 if collection.access == AccessMode::WhiteList {1747 Self::check_white_list(&collection, &sender)?;1748 Self::check_white_list(&collection, &spender)?;1749 }17501751 let allowance: u128 = amount1752 .checked_add(<Allowances<T>>::get(collection.id, (item_id, sender.as_sub(), spender.as_sub())))1753 .ok_or(Error::<T>::NumOverflow)?;1754 if let Some(limit) = allowance_limit {1755 ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1756 }1757 <Allowances<T>>::insert(collection.id, (item_id, sender.as_sub(), spender.as_sub()), allowance);17581759 if matches!(collection.mode, CollectionMode::NFT) {1760 // TODO: NFT: only one owner may exist for token in ERC7211761 collection.log(1762 Vec::from([1763 eth::APPROVAL_NFT_TOPIC,1764 eth::address_to_topic(sender.as_eth()),1765 eth::address_to_topic(spender.as_eth()),1766 eth::u32_to_topic(item_id),1767 ]),1768 abi_encode!(),1769 );1770 }17711772 if matches!(collection.mode, CollectionMode::Fungible(_)) {1773 // TODO: NFT: only one owner may exist for token in ERC201774 collection.log(1775 Vec::from([1776 eth::APPROVAL_FUNGIBLE_TOPIC,1777 eth::address_to_topic(sender.as_eth()),1778 eth::address_to_topic(spender.as_eth()),1779 ]),1780 abi_encode!(uint256(allowance.into())),1781 );1782 }17831784 Self::deposit_event(RawEvent::Approved(collection.id, item_id, sender.clone(), spender.clone(), allowance));1785 Ok(())1786 }17871788 pub fn transfer_from_internal(1789 sender: &T::CrossAccountId,1790 from: &T::CrossAccountId,1791 recipient: &T::CrossAccountId,1792 collection: &CollectionHandle<T>,1793 item_id: TokenId,1794 amount: u128,1795 ) -> DispatchResult {1796 // Check approval1797 let approval: u128 = <Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));17981799 // Limits check1800 Self::is_correct_transfer(&collection, &recipient)?;18011802 // Transfer permissions check1803 ensure!(1804 approval >= amount || 1805 (1806 collection.limits.owner_can_transfer &&1807 Self::is_owner_or_admin_permissions(&collection, &sender)1808 ),1809 Error::<T>::NoPermission1810 );18111812 if collection.access == AccessMode::WhiteList {1813 Self::check_white_list(&collection, &sender)?;1814 Self::check_white_list(&collection, &recipient)?;1815 }18161817 // Reduce approval by transferred amount or remove if remaining approval drops to 01818 let allowance = approval.saturating_sub(amount);1819 if allowance > 0 {1820 <Allowances<T>>::insert(collection.id, (item_id, from.as_sub(), sender.as_sub()), allowance);1821 } else {1822 <Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1823 }18241825 match collection.mode {1826 CollectionMode::NFT => {1827 Self::transfer_nft(&collection, item_id, from.clone(), recipient.clone())?1828 }1829 CollectionMode::Fungible(_) => {1830 Self::transfer_fungible(&collection, amount, &from, &recipient)?1831 }1832 CollectionMode::ReFungible => {1833 Self::transfer_refungible(&collection, item_id, amount, from.clone(), recipient.clone())?1834 }1835 _ => ()1836 };18371838 if matches!(collection.mode, CollectionMode::Fungible(_)) {1839 collection.log(1840 Vec::from([1841 eth::APPROVAL_FUNGIBLE_TOPIC,1842 eth::address_to_topic(from.as_eth()),1843 eth::address_to_topic(sender.as_eth()),1844 ]),1845 abi_encode!(uint256(allowance.into())),1846 );1847 }18481849 Ok(())1850 }18511852 pub fn set_variable_meta_data_internal(1853 sender: &T::CrossAccountId,1854 collection: &CollectionHandle<T>, 1855 item_id: TokenId,1856 data: Vec<u8>,1857 ) -> DispatchResult {1858 Self::token_exists(&collection, item_id)?;18591860 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);18611862 // Modify permissions check1863 ensure!(Self::is_item_owner(&sender, &collection, item_id) ||1864 Self::is_owner_or_admin_permissions(&collection, &sender),1865 Error::<T>::NoPermission);18661867 match collection.mode1868 {1869 CollectionMode::NFT => Self::set_nft_variable_data(&collection, item_id, data)?,1870 CollectionMode::ReFungible => Self::set_re_fungible_variable_data(&collection, item_id, data)?,1871 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1872 _ => fail!(Error::<T>::UnexpectedCollectionType)1873 };18741875 Ok(())1876 }18771878 pub fn create_multiple_items_internal(1879 sender: &T::CrossAccountId,1880 collection: &CollectionHandle<T>,1881 owner: &T::CrossAccountId,1882 items_data: Vec<CreateItemData>,1883 ) -> DispatchResult {1884 Self::can_create_items_in_collection(&collection, &sender, &owner, items_data.len() as u32)?;18851886 for data in &items_data {1887 Self::validate_create_item_args(&collection, data)?;1888 }1889 for data in &items_data {1890 Self::create_item_no_validation(&collection, owner, data.clone())?;1891 }18921893 Ok(())1894 }18951896 pub fn burn_item_internal(1897 sender: &T::CrossAccountId,1898 collection: &CollectionHandle<T>,1899 item_id: TokenId,1900 value: u128,1901 ) -> DispatchResult {1902 ensure!(1903 Self::is_item_owner(&sender, &collection, item_id) ||1904 (1905 collection.limits.owner_can_transfer &&1906 Self::is_owner_or_admin_permissions(&collection, &sender)1907 ),1908 Error::<T>::NoPermission1909 );19101911 if collection.access == AccessMode::WhiteList {1912 Self::check_white_list(&collection, &sender)?;1913 }19141915 match collection.mode1916 {1917 CollectionMode::NFT => Self::burn_nft_item(&collection, item_id)?,1918 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, &collection, value)?,1919 CollectionMode::ReFungible => Self::burn_refungible_item(&collection, item_id, &sender)?,1920 _ => ()1921 };19221923 Ok(())1924 }19251926 pub fn toggle_white_list_internal(1927 sender: &T::CrossAccountId,1928 collection: &CollectionHandle<T>,1929 address: &T::CrossAccountId,1930 whitelisted: bool,1931 ) -> DispatchResult {1932 Self::check_owner_or_admin_permissions(&collection, &sender)?;19331934 if whitelisted {1935 <WhiteList<T>>::insert(collection.id, address.as_sub(), true);1936 } else {1937 <WhiteList<T>>::remove(collection.id, address.as_sub());1938 }19391940 Ok(())1941 }19421943 fn is_correct_transfer(collection: &CollectionHandle<T>, recipient: &T::CrossAccountId) -> DispatchResult {1944 let collection_id = collection.id;19451946 // check token limit and account token limit1947 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1948 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1949 1950 Ok(())1951 }19521953 fn can_create_items_in_collection(collection: &CollectionHandle<T>, sender: &T::CrossAccountId, owner: &T::CrossAccountId, amount: u32) -> DispatchResult {1954 let collection_id = collection.id;19551956 // check token limit and account token limit1957 let total_items: u32 = ItemListIndex::get(collection_id)1958 .checked_add(amount)1959 .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1960 let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len() as u32)1961 .checked_add(amount)1962 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1963 ensure!(collection.limits.token_limit >= total_items, Error::<T>::CollectionTokenLimitExceeded);1964 ensure!(collection.limits.account_token_ownership_limit >= account_items, Error::<T>::AccountTokenLimitExceeded);19651966 if !Self::is_owner_or_admin_permissions(collection, &sender) {1967 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1968 Self::check_white_list(collection, owner)?;1969 Self::check_white_list(collection, sender)?;1970 }19711972 Ok(())1973 }19741975 fn validate_create_item_args(target_collection: &CollectionHandle<T>, data: &CreateItemData) -> DispatchResult {1976 match target_collection.mode1977 {1978 CollectionMode::NFT => {1979 if let CreateItemData::NFT(data) = data {1980 // check sizes1981 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1982 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1983 } else {1984 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1985 }1986 },1987 CollectionMode::Fungible(_) => {1988 if let CreateItemData::Fungible(_) = data {1989 } else {1990 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1991 }1992 },1993 CollectionMode::ReFungible => {1994 if let CreateItemData::ReFungible(data) = data {19951996 // check sizes1997 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1998 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);19992000 // Check refungibility limits2001 ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);2002 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);2003 } else {2004 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);2005 }2006 },2007 _ => { fail!(Error::<T>::UnexpectedCollectionType); }2008 };20092010 Ok(())2011 }20122013 fn create_item_no_validation(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, data: CreateItemData) -> DispatchResult {2014 match data2015 {2016 CreateItemData::NFT(data) => {2017 let item = NftItemType {2018 owner: owner.clone(),2019 const_data: data.const_data,2020 variable_data: data.variable_data2021 };20222023 Self::add_nft_item(collection, item)?;2024 },2025 CreateItemData::Fungible(data) => {2026 Self::add_fungible_item(collection, &owner, data.value)?;2027 },2028 CreateItemData::ReFungible(data) => {2029 let mut owner_list = Vec::new();2030 owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});20312032 let item = ReFungibleItemType {2033 owner: owner_list,2034 const_data: data.const_data,2035 variable_data: data.variable_data2036 };20372038 Self::add_refungible_item(collection, item)?;2039 }2040 };20412042 Ok(())2043 }20442045 fn add_fungible_item(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, value: u128) -> DispatchResult {2046 let collection_id = collection.id;20472048 // Does new owner already have an account?2049 let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;20502051 // Mint 2052 let item = FungibleItemType {2053 value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,2054 };2055 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);20562057 // Update balance2058 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2059 .checked_add(value)2060 .ok_or(Error::<T>::NumOverflow)?;2061 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);20622063 Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));2064 Ok(())2065 }20662067 fn add_refungible_item(collection: &CollectionHandle<T>, item: ReFungibleItemType<T::CrossAccountId>) -> DispatchResult {2068 let collection_id = collection.id;20692070 let current_index = <ItemListIndex>::get(collection_id)2071 .checked_add(1)2072 .ok_or(Error::<T>::NumOverflow)?;2073 let itemcopy = item.clone();20742075 ensure!(2076 item.owner.len() == 1,2077 Error::<T>::BadCreateRefungibleCall,2078 );2079 let item_owner = item.owner.first().expect("only one owner is defined");20802081 let value = item_owner.fraction;2082 let owner = item_owner.owner.clone();20832084 Self::add_token_index(collection_id, current_index, &owner)?;20852086 <ItemListIndex>::insert(collection_id, current_index);2087 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);20882089 // Update balance2090 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2091 .checked_add(value)2092 .ok_or(Error::<T>::NumOverflow)?;2093 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);20942095 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));2096 Ok(())2097 }20982099 fn add_nft_item(collection: &CollectionHandle<T>, item: NftItemType<T::CrossAccountId>) -> DispatchResult {2100 let collection_id = collection.id;21012102 let current_index = <ItemListIndex>::get(collection_id)2103 .checked_add(1)2104 .ok_or(Error::<T>::NumOverflow)?;21052106 let item_owner = item.owner.clone();2107 Self::add_token_index(collection_id, current_index, &item.owner)?;21082109 <ItemListIndex>::insert(collection_id, current_index);2110 <NftItemList<T>>::insert(collection_id, current_index, item);21112112 // Update balance2113 let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())2114 .checked_add(1)2115 .ok_or(Error::<T>::NumOverflow)?;2116 <Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);21172118 collection.log(2119 Vec::from([2120 eth::TRANSFER_NFT_TOPIC,2121 eth::address_to_topic(&H160::default()),2122 eth::address_to_topic(item_owner.as_eth()),2123 eth::u32_to_topic(current_index),2124 ]),2125 abi_encode!(),2126 );2127 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, item_owner));2128 Ok(())2129 }21302131 fn burn_refungible_item(2132 collection: &CollectionHandle<T>,2133 item_id: TokenId,2134 owner: &T::CrossAccountId,2135 ) -> DispatchResult {2136 let collection_id = collection.id;21372138 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)2139 .ok_or(Error::<T>::TokenNotFound)?;2140 let rft_balance = token2141 .owner2142 .iter()2143 .find(|&i| i.owner == *owner)2144 .ok_or(Error::<T>::TokenNotFound)?;2145 Self::remove_token_index(collection_id, item_id, owner)?;21462147 // update balance2148 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())2149 .checked_sub(rft_balance.fraction)2150 .ok_or(Error::<T>::NumOverflow)?;2151 <Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);21522153 // Re-create owners list with sender removed2154 let index = token2155 .owner2156 .iter()2157 .position(|i| i.owner == *owner)2158 .expect("owned item is exists");2159 token.owner.remove(index);2160 let owner_count = token.owner.len();21612162 // Burn the token completely if this was the last (only) owner2163 if owner_count == 0 {2164 <ReFungibleItemList<T>>::remove(collection_id, item_id);2165 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);2166 }2167 else {2168 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);2169 }21702171 Ok(())2172 }21732174 fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {2175 let collection_id = collection.id;21762177 let item = <NftItemList<T>>::get(collection_id, item_id)2178 .ok_or(Error::<T>::TokenNotFound)?;2179 Self::remove_token_index(collection_id, item_id, &item.owner)?;21802181 // update balance2182 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2183 .checked_sub(1)2184 .ok_or(Error::<T>::NumOverflow)?;2185 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2186 <NftItemList<T>>::remove(collection_id, item_id);2187 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);21882189 Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));2190 Ok(())2191 }21922193 fn burn_fungible_item(owner: &T::CrossAccountId, collection: &CollectionHandle<T>, value: u128) -> DispatchResult {2194 let collection_id = collection.id;21952196 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());2197 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);21982199 // update balance2200 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2201 .checked_sub(value)2202 .ok_or(Error::<T>::NumOverflow)?;2203 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);22042205 if balance.value - value > 0 {2206 balance.value -= value;2207 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2208 }2209 else {2210 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());2211 }22122213 collection.log(2214 Vec::from([2215 eth::TRANSFER_FUNGIBLE_TOPIC,2216 eth::address_to_topic(owner.as_eth()),2217 eth::address_to_topic(&H160::default()),2218 ]),2219 abi_encode!(uint256(value.into())),2220 );2221 Ok(())2222 }22232224 pub fn get_collection(collection_id: CollectionId) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {2225 Ok(<CollectionHandle<T>>::get(collection_id)2226 .ok_or(Error::<T>::CollectionNotFound)?)2227 }22282229 fn save_collection(collection: CollectionHandle<T>) {2230 <CollectionById<T>>::insert(collection.id, collection.into_inner());2231 }22322233 pub fn submit_logs(collection: CollectionHandle<T>) -> DispatchResult {2234 if collection.logs.is_empty() {2235 return Ok(())2236 }2237 T::EthereumTransactionSender::submit_logs_transaction(2238 eth::generate_transaction(collection.id, T::EthereumChainId::get()),2239 collection.logs.retrieve_logs_for_contract(eth::collection_id_to_address(collection.id)),2240 )2241 }22422243 fn check_owner_permissions(target_collection: &CollectionHandle<T>, subject: &T::AccountId) -> DispatchResult {2244 ensure!(2245 *subject == target_collection.owner,2246 Error::<T>::NoPermission2247 );22482249 Ok(())2250 }22512252 fn is_owner_or_admin_permissions(collection: &CollectionHandle<T>, subject: &T::CrossAccountId) -> bool {2253 *subject.as_sub() == collection.owner || <AdminList<T>>::get(collection.id).contains(&subject)2254 }22552256 fn check_owner_or_admin_permissions(2257 collection: &CollectionHandle<T>,2258 subject: &T::CrossAccountId,2259 ) -> DispatchResult {2260 ensure!(Self::is_owner_or_admin_permissions(collection, subject), Error::<T>::NoPermission);22612262 Ok(())2263 }22642265 fn owned_amount(2266 subject: &T::CrossAccountId,2267 target_collection: &CollectionHandle<T>,2268 item_id: TokenId,2269 ) -> Option<u128> {2270 let collection_id = target_collection.id;22712272 match target_collection.mode {2273 CollectionMode::NFT => (<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject)2274 .then(|| 1),2275 CollectionMode::Fungible(_) => Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub())2276 .value),2277 CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?2278 .owner2279 .iter()2280 .find(|i| i.owner == *subject)2281 .map(|i| i.fraction),2282 CollectionMode::Invalid => None,2283 }2284 }22852286 fn is_item_owner(subject: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId) -> bool {2287 match target_collection.mode {2288 CollectionMode::Fungible(_) => true,2289 _ => Self::owned_amount(&subject, target_collection, item_id).is_some(),2290 }2291 }22922293 fn check_white_list(collection: &CollectionHandle<T>, address: &T::CrossAccountId) -> DispatchResult {2294 let collection_id = collection.id;22952296 let mes = Error::<T>::AddresNotInWhiteList;2297 ensure!(<WhiteList<T>>::contains_key(collection_id, address.as_sub()), mes);22982299 Ok(())2300 }23012302 /// Check if token exists. In case of Fungible, check if there is an entry for 2303 /// the owner in fungible balances double map2304 fn token_exists(2305 target_collection: &CollectionHandle<T>,2306 item_id: TokenId,2307 ) -> DispatchResult {2308 let collection_id = target_collection.id;2309 let exists = match target_collection.mode2310 {2311 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),2312 CollectionMode::Fungible(_) => true,2313 CollectionMode::ReFungible => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),2314 _ => false2315 };23162317 ensure!(exists == true, Error::<T>::TokenNotFound);2318 Ok(())2319 }23202321 fn transfer_fungible(2322 collection: &CollectionHandle<T>,2323 value: u128,2324 owner: &T::CrossAccountId,2325 recipient: &T::CrossAccountId,2326 ) -> DispatchResult {2327 let collection_id = collection.id;23282329 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());2330 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);23312332 // Send balance to recipient (updates balanceOf of recipient)2333 Self::add_fungible_item(collection, recipient, value)?;23342335 // update balanceOf of sender2336 <Balance<T>>::insert(collection_id, owner.as_sub(), balance.value - value);23372338 // Reduce or remove sender2339 if balance.value == value {2340 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());2341 }2342 else {2343 balance.value -= value;2344 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2345 }23462347 collection.log(2348 Vec::from([2349 eth::TRANSFER_FUNGIBLE_TOPIC,2350 eth::address_to_topic(owner.as_eth()),2351 eth::address_to_topic(recipient.as_eth()),2352 ]),2353 abi_encode!(uint256(value.into())),2354 );2355 Self::deposit_event(RawEvent::Transfer(collection.id, 1, owner.clone(), recipient.clone(), value));23562357 Ok(())2358 }23592360 fn transfer_refungible(2361 collection: &CollectionHandle<T>,2362 item_id: TokenId,2363 value: u128,2364 owner: T::CrossAccountId,2365 new_owner: T::CrossAccountId,2366 ) -> DispatchResult {2367 let collection_id = collection.id;2368 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2369 .ok_or(Error::<T>::TokenNotFound)?;23702371 let item = full_item2372 .owner2373 .iter()2374 .filter(|i| i.owner == owner)2375 .next()2376 .ok_or(Error::<T>::TokenNotFound)?;2377 let amount = item.fraction;23782379 ensure!(amount >= value, Error::<T>::TokenValueTooLow);23802381 // update balance2382 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2383 .checked_sub(value)2384 .ok_or(Error::<T>::NumOverflow)?;2385 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);23862387 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2388 .checked_add(value)2389 .ok_or(Error::<T>::NumOverflow)?;2390 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);23912392 let old_owner = item.owner.clone();2393 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);23942395 // transfer2396 if amount == value && !new_owner_has_account {2397 // change owner2398 // new owner do not have account2399 let mut new_full_item = full_item.clone();2400 new_full_item2401 .owner2402 .iter_mut()2403 .find(|i| i.owner == owner)2404 .expect("old owner does present in refungible")2405 .owner = new_owner.clone();2406 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);24072408 // update index collection2409 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2410 } else {2411 let mut new_full_item = full_item.clone();2412 new_full_item2413 .owner2414 .iter_mut()2415 .find(|i| i.owner == owner)2416 .expect("old owner does present in refungible")2417 .fraction -= value;24182419 // separate amount2420 if new_owner_has_account {2421 // new owner has account2422 new_full_item2423 .owner2424 .iter_mut()2425 .find(|i| i.owner == new_owner)2426 .expect("new owner has account")2427 .fraction += value;2428 } else {2429 // new owner do not have account2430 new_full_item.owner.push(Ownership {2431 owner: new_owner.clone(),2432 fraction: value,2433 });2434 Self::add_token_index(collection_id, item_id, &new_owner)?;2435 }24362437 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2438 }24392440 Self::deposit_event(RawEvent::Transfer(collection.id, item_id, owner, new_owner, amount));24412442 Ok(())2443 }24442445 fn transfer_nft(2446 collection: &CollectionHandle<T>,2447 item_id: TokenId,2448 sender: T::CrossAccountId,2449 new_owner: T::CrossAccountId,2450 ) -> DispatchResult {2451 let collection_id = collection.id;2452 let mut item = <NftItemList<T>>::get(collection_id, item_id)2453 .ok_or(Error::<T>::TokenNotFound)?;24542455 ensure!(2456 sender == item.owner,2457 Error::<T>::MustBeTokenOwner2458 );24592460 // update balance2461 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2462 .checked_sub(1)2463 .ok_or(Error::<T>::NumOverflow)?;2464 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);24652466 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2467 .checked_add(1)2468 .ok_or(Error::<T>::NumOverflow)?;2469 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);24702471 // change owner2472 let old_owner = item.owner.clone();2473 item.owner = new_owner.clone();2474 <NftItemList<T>>::insert(collection_id, item_id, item);24752476 // update index collection2477 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;24782479 collection.log(2480 Vec::from([2481 eth::TRANSFER_NFT_TOPIC,2482 eth::address_to_topic(sender.as_eth()),2483 eth::address_to_topic(new_owner.as_eth()),2484 eth::u32_to_topic(item_id),2485 ]),2486 abi_encode!(),2487 );2488 Self::deposit_event(RawEvent::Transfer(collection.id, item_id, sender, new_owner, 1));24892490 Ok(())2491 }2492 2493 fn set_re_fungible_variable_data(2494 collection: &CollectionHandle<T>,2495 item_id: TokenId,2496 data: Vec<u8>2497 ) -> DispatchResult {2498 let collection_id = collection.id;2499 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2500 .ok_or(Error::<T>::TokenNotFound)?;25012502 item.variable_data = data;25032504 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);25052506 Ok(())2507 }25082509 fn set_nft_variable_data(2510 collection: &CollectionHandle<T>,2511 item_id: TokenId,2512 data: Vec<u8>2513 ) -> DispatchResult {2514 let collection_id = collection.id;2515 let mut item = <NftItemList<T>>::get(collection_id, item_id)2516 .ok_or(Error::<T>::TokenNotFound)?;2517 2518 item.variable_data = data;25192520 <NftItemList<T>>::insert(collection_id, item_id, item);2521 2522 Ok(())2523 }25242525 fn init_collection(item: &Collection<T>) {2526 // check params2527 assert!(2528 item.decimal_points <= MAX_DECIMAL_POINTS,2529 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2530 );2531 assert!(2532 item.name.len() <= 64,2533 "Collection name can not be longer than 63 char"2534 );2535 assert!(2536 item.name.len() <= 256,2537 "Collection description can not be longer than 255 char"2538 );2539 assert!(2540 item.token_prefix.len() <= 16,2541 "Token prefix can not be longer than 15 char"2542 );25432544 // Generate next collection ID2545 let next_id = CreatedCollectionCount::get()2546 .checked_add(1)2547 .unwrap();25482549 CreatedCollectionCount::put(next_id);2550 }25512552 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2553 let current_index = <ItemListIndex>::get(collection_id)2554 .checked_add(1)2555 .unwrap();25562557 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();25582559 <ItemListIndex>::insert(collection_id, current_index);25602561 // Update balance2562 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2563 .checked_add(1)2564 .unwrap();2565 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2566 }25672568 fn init_fungible_token(collection_id: CollectionId, owner: &T::CrossAccountId, item: &FungibleItemType) {2569 let current_index = <ItemListIndex>::get(collection_id)2570 .checked_add(1)2571 .unwrap();25722573 Self::add_token_index(collection_id, current_index, owner).unwrap();25742575 <ItemListIndex>::insert(collection_id, current_index);25762577 // Update balance2578 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2579 .checked_add(item.value)2580 .unwrap();2581 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2582 }25832584 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::CrossAccountId>) {2585 let current_index = <ItemListIndex>::get(collection_id)2586 .checked_add(1)2587 .unwrap();25882589 let value = item.owner.first().unwrap().fraction;2590 let owner = item.owner.first().unwrap().owner.clone();25912592 Self::add_token_index(collection_id, current_index, &owner).unwrap();25932594 <ItemListIndex>::insert(collection_id, current_index);25952596 // Update balance2597 let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2598 .checked_add(value)2599 .unwrap();2600 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2601 }26022603 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::CrossAccountId) -> DispatchResult {2604 // add to account limit2605 if <AccountItemCount<T>>::contains_key(owner.as_sub()) {26062607 // bound Owned tokens by a single address2608 let count = <AccountItemCount<T>>::get(owner.as_sub());2609 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);26102611 <AccountItemCount<T>>::insert(owner.as_sub(), count2612 .checked_add(1)2613 .ok_or(Error::<T>::NumOverflow)?);2614 }2615 else {2616 <AccountItemCount<T>>::insert(owner.as_sub(), 1);2617 }26182619 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2620 if list_exists {2621 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2622 let item_contains = list.contains(&item_index.clone());26232624 if !item_contains {2625 list.push(item_index.clone());2626 }26272628 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2629 } else {2630 let mut itm = Vec::new();2631 itm.push(item_index.clone());2632 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), itm);2633 }26342635 Ok(())2636 }26372638 fn remove_token_index(2639 collection_id: CollectionId,2640 item_index: TokenId,2641 owner: &T::CrossAccountId,2642 ) -> DispatchResult {26432644 // update counter2645 <AccountItemCount<T>>::insert(owner.as_sub(), 2646 <AccountItemCount<T>>::get(owner.as_sub())2647 .checked_sub(1)2648 .ok_or(Error::<T>::NumOverflow)?);264926502651 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2652 if list_exists {2653 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2654 let item_contains = list.contains(&item_index.clone());26552656 if item_contains {2657 list.retain(|&item| item != item_index);2658 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2659 }2660 }26612662 Ok(())2663 }26642665 fn move_token_index(2666 collection_id: CollectionId,2667 item_index: TokenId,2668 old_owner: &T::CrossAccountId,2669 new_owner: &T::CrossAccountId,2670 ) -> DispatchResult {2671 Self::remove_token_index(collection_id, item_index, old_owner)?;2672 Self::add_token_index(collection_id, item_index, new_owner)?;26732674 Ok(())2675 }2676 2677 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2678 ensure!(<ContractOwner<T>>::get(contract) == Some(account), Error::<T>::NoPermission);26792680 Ok(())2681 }2682}26832684////////////////////////////////////////////////////////////////////////////////////////////////////2685// Economic models2686// #region26872688/// Fee multiplier.2689pub type Multiplier = FixedU128;26902691type BalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::Balance;26922693/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2694/// in the queue.2695#[derive(Encode, Decode, Clone, Eq, PartialEq)]2696pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);26972698impl<T: Config + Send + Sync> sp_std::fmt::Debug 2699 for ChargeTransactionPayment<T>2700{2701 #[cfg(feature = "std")]2702 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2703 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2704 }2705 #[cfg(not(feature = "std"))]2706 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2707 Ok(())2708 }2709}27102711impl<T: Config> ChargeTransactionPayment<T>2712where2713 T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2714 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2715 T::AccountId: AsRef<[u8]>,2716 T::AccountId: UncheckedFrom<T::Hash>,2717{2718 fn traditional_fee(2719 len: usize,2720 info: &DispatchInfoOf<T::Call>,2721 tip: BalanceOf<T>,2722 ) -> BalanceOf<T>2723 where2724 T::Call: Dispatchable<Info = DispatchInfo>,2725 {2726 <pallet_transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2727 }27282729 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2730 let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);2731 let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);2732 let len_saturation = max_block_length as u64 / (len as u64).max(1);2733 let coefficient: BalanceOf<T> = weight_saturation2734 .min(len_saturation)2735 .saturated_into::<BalanceOf<T>>();2736 final_fee2737 .saturating_mul(coefficient)2738 .saturated_into::<TransactionPriority>()2739 }27402741 fn withdraw_fee(2742 &self,2743 who: &T::AccountId,2744 call: &T::Call,2745 info: &DispatchInfoOf<T::Call>,2746 len: usize,2747 ) -> Result<2748 (2749 BalanceOf<T>,2750 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2751 ),2752 TransactionValidityError,2753 > {2754 let tip = self.0;27552756 let fee = Self::traditional_fee(len, info, tip);27572758 // Only mess with balances if fee is not zero.2759 if fee.is_zero() {2760 return <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)2761 .map(|i| (fee, i));2762 }27632764 // Determine who is paying transaction fee based on ecnomic model2765 // Parse call to extract collection ID and access collection sponsor2766 let mut sponsor: Option<T::AccountId> = (|| match IsSubType::<Call<T>>::is_sub_type(call) {2767 Some(Call::create_item(collection_id, _owner, _properties)) => {2768 let collection = <CollectionById<T>>::get(collection_id)?;27692770 // sponsor timeout2771 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;27722773 let limit = collection.limits.sponsor_transfer_timeout;2774 if <CreateItemBasket<T>>::contains_key((collection_id, &who)) {2775 let last_tx_block = <CreateItemBasket<T>>::get((collection_id, &who));2776 let limit_time = last_tx_block + limit.into();2777 if block_number <= limit_time {2778 return None;2779 }2780 }2781 <CreateItemBasket<T>>::insert((collection_id, who.clone()), block_number);27822783 // check free create limit2784 if collection.limits.sponsored_data_size >= (_properties.len() as u32) {2785 collection.sponsorship.sponsor()2786 .cloned()2787 } else {2788 None2789 }2790 }2791 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2792 let collection = <CollectionById<T>>::get(collection_id)?;2793 2794 let mut sponsor_transfer = false;2795 if collection.sponsorship.confirmed() {27962797 let collection_limits = collection.limits;2798 let collection_mode = collection.mode;2799 2800 // sponsor timeout2801 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2802 sponsor_transfer = match collection_mode {2803 CollectionMode::NFT => {2804 2805 // get correct limit2806 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2807 collection_limits.sponsor_transfer_timeout2808 } else {2809 ChainLimit::get().nft_sponsor_transfer_timeout2810 };2811 2812 let mut sponsored = true;2813 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2814 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2815 let limit_time = last_tx_block + limit.into();2816 if block_number <= limit_time {2817 sponsored = false;2818 }2819 }2820 if sponsored {2821 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2822 }28232824 sponsored2825 }2826 CollectionMode::Fungible(_) => {2827 2828 // get correct limit2829 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2830 collection_limits.sponsor_transfer_timeout2831 } else {2832 ChainLimit::get().fungible_sponsor_transfer_timeout2833 };2834 2835 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2836 let mut sponsored = true;2837 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2838 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2839 let limit_time = last_tx_block + limit.into();2840 if block_number <= limit_time {2841 sponsored = false;2842 }2843 }2844 if sponsored {2845 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2846 }28472848 sponsored2849 }2850 CollectionMode::ReFungible => {2851 2852 // get correct limit2853 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2854 collection_limits.sponsor_transfer_timeout2855 } else {2856 ChainLimit::get().refungible_sponsor_transfer_timeout2857 };2858 2859 let mut sponsored = true;2860 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2861 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2862 let limit_time = last_tx_block + limit.into();2863 if block_number <= limit_time {2864 sponsored = false;2865 }2866 }2867 if sponsored {2868 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2869 }28702871 sponsored2872 }2873 _ => {2874 false2875 },2876 };2877 }28782879 if !sponsor_transfer {2880 None2881 } else {2882 collection.sponsorship.sponsor()2883 .cloned()2884 }2885 }28862887 Some(Call::set_variable_meta_data(collection_id, item_id, data)) => {2888 let mut sponsor_metadata_changes = false;28892890 let collection = <CollectionById<T>>::get(collection_id)?;28912892 if2893 collection.sponsorship.confirmed() &&2894 // Can't sponsor fungible collection, this tx will be rejected2895 // as invalid2896 !matches!(collection.mode, CollectionMode::Fungible(_)) &&2897 data.len() <= collection.limits.sponsored_data_size as usize2898 {2899 if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit {2900 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;29012902 if <VariableMetaDataBasket<T>>::get(collection_id, item_id)2903 .map(|last_block| block_number - last_block > rate_limit)2904 .unwrap_or(true) 2905 {2906 sponsor_metadata_changes = true;2907 <VariableMetaDataBasket<T>>::insert(collection_id, item_id, block_number);2908 }2909 }2910 }29112912 if !sponsor_metadata_changes {2913 None2914 } else {2915 collection.sponsorship.sponsor().cloned()2916 }2917 }29182919 _ => None,2920 })();29212922 match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {2923 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {29242925 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());29262927 let owned_contract = <ContractOwner<T>>::get(called_contract.clone()).as_ref() == Some(who);2928 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone());2929 2930 if !owned_contract && white_list_enabled {2931 if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2932 return Err(InvalidTransaction::Call.into());2933 }2934 }2935 },2936 _ => {},2937 }29382939 // Sponsor smart contracts2940 sponsor = sponsor.or_else(|| match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {29412942 // On instantiation: set the contract owner2943 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {29442945 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2946 &who,2947 code_hash,2948 salt,2949 );2950 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());29512952 None2953 },29542955 // On instantiation with code: set the contract owner2956 Some(pallet_contracts::Call::instantiate_with_code(_endowment, _gas_limit, _code, _data, _salt)) => {29572958 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2959 &who,2960 &T::Hashing::hash(&_code),2961 _salt,2962 );29632964 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());29652966 None2967 }29682969 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2970 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {29712972 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());29732974 let mut sponsor_transfer = false;2975 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2976 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2977 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2978 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2979 let limit_time = last_tx_block + rate_limit;29802981 if block_number >= limit_time {2982 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2983 sponsor_transfer = true;2984 }2985 } else {2986 sponsor_transfer = false;2987 }2988 2989 if sponsor_transfer {2990 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2991 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2992 return Some(called_contract);2993 }2994 }2995 }29962997 None2998 },29993000 _ => None,3001 });30023003 let who_pays_fee = sponsor.unwrap_or_else(|| who.clone());30043005 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(&who_pays_fee, call, info, fee, tip)3006 .map(|i| (fee, i))3007 }3008}300930103011impl<T: Config + Send + Sync> SignedExtension3012 for ChargeTransactionPayment<T>3013where3014 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,3015 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,3016 T::AccountId: AsRef<[u8]>,3017 T::AccountId: UncheckedFrom<T::Hash>,3018{3019 const IDENTIFIER: &'static str = "ChargeTransactionPayment";3020 type AccountId = T::AccountId;3021 type Call = T::Call;3022 type AdditionalSigned = ();3023 type Pre = (3024 // tip3025 BalanceOf<T>,3026 // who pays fee3027 Self::AccountId,3028 // imbalance resulting from withdrawing the fee3029 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,3030 );3031 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {3032 Ok(())3033 }30343035 fn validate(3036 &self,3037 who: &Self::AccountId,3038 call: &Self::Call,3039 info: &DispatchInfoOf<Self::Call>,3040 len: usize,3041 ) -> TransactionValidity {3042 let (fee, _) = self.withdraw_fee(who, call, info, len)?;3043 Ok(ValidTransaction {3044 priority: Self::get_priority(len, info, fee),3045 ..Default::default()3046 })3047 }30483049 fn pre_dispatch(3050 self,3051 who: &Self::AccountId,3052 call: &Self::Call,3053 info: &DispatchInfoOf<Self::Call>,3054 len: usize,3055 ) -> Result<Self::Pre, TransactionValidityError> {3056 let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;3057 Ok((self.0, who.clone(), imbalance))3058 }30593060 fn post_dispatch(3061 pre: Self::Pre,3062 info: &DispatchInfoOf<Self::Call>,3063 post_info: &PostDispatchInfoOf<Self::Call>,3064 len: usize,3065 _result: &DispatchResult,3066 ) -> Result<(), TransactionValidityError> {3067 let (tip, who, imbalance) = pre;3068 let actual_fee = pallet_transaction_payment::Module::<T>::compute_actual_fee(3069 len as u32,3070 info,3071 post_info,3072 tip,3073 );3074 <T as pallet_transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;3075 Ok(())3076 }3077}30783079// #endregion30803081sp_api::decl_runtime_apis! {3082 pub trait NftApi {3083 /// Used for ethereum integration3084 fn eth_contract_code(account: H160) -> Option<Vec<u8>>;3085 }3086}runtime_types.jsondiffbeforeafterboth--- a/runtime_types.json
+++ b/runtime_types.json
@@ -45,7 +45,7 @@
}
},
"Collection": {
- "Owner": "CrossAccountId",
+ "Owner": "AccountId",
"Mode": "CollectionMode",
"Access": "AccessMode",
"DecimalPoints": "DecimalPoints",
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -37,7 +37,7 @@
}
return input;
}
-export function toSubstrateAddress(input: CrossAccountId): string {
+export function toSubstrateAddress(input: string | CrossAccountId | IKeyringPair): string {
input = normalizeAccountId(input);
if ('substrate' in input) {
return input.substrate;
@@ -273,7 +273,7 @@
// tslint:disable-next-line:no-unused-expression
expect(collection).to.be.not.null;
expect(BcollectionCount).to.be.equal(AcollectionCount + 1, 'Error: NFT collection NOT created.');
- expect(collection.Owner).to.be.deep.equal(normalizeAccountId(alicesPublicKey));
+ expect(collection.Owner).to.be.equal(toSubstrateAddress(alicesPublicKey));
expect(utf16ToStr(collection.Name)).to.be.equal(name);
expect(utf16ToStr(collection.Description)).to.be.equal(description);
expect(hexToStr(collection.TokenPrefix)).to.be.equal(tokenPrefix);