difftreelog
NFTPAR-231 Smart Contract White List. Integration tests for smart contract white list. Also changed white list to double map.
in: master
4 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 std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516use codec::{Decode, Encode};17pub use frame_support::{18 construct_runtime, decl_event, decl_module, decl_storage, decl_error,19 dispatch::DispatchResult,20 ensure, fail, parameter_types,21 traits::{22 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,23 Randomness, WithdrawReason,24 },25 weights::{26 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28 WeightToFeePolynomial,29 },30 IsSubType, StorageValue,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36 traits::{37 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38 },39 transaction_validity::{40 TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41 },42 FixedPointOperand, FixedU128,43};44use pallet_contracts::ContractAddressFor;45use sp_runtime::traits::StaticLookup;4647#[cfg(test)]48mod mock;4950#[cfg(test)]51mod tests;5253mod default_weights;5455pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;56pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;57pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;5859// Structs60// #region6162pub type CollectionId = u32;63pub type TokenId = u32;64pub type DecimalPoints = u8;6566#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]67#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]68pub enum CollectionMode {69 Invalid,70 NFT,71 // decimal points72 Fungible(DecimalPoints),73 // decimal points74 ReFungible(DecimalPoints),75}7677impl Into<u8> for CollectionMode {78 fn into(self) -> u8 {79 match self {80 CollectionMode::Invalid => 0,81 CollectionMode::NFT => 1,82 CollectionMode::Fungible(_) => 2,83 CollectionMode::ReFungible(_) => 3,84 }85 }86}8788#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]89#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]90pub enum AccessMode {91 Normal,92 WhiteList,93}94impl Default for AccessMode {95 fn default() -> Self {96 Self::Normal97 }98}99100impl Default for CollectionMode {101 fn default() -> Self {102 Self::Invalid103 }104}105106#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]107#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]108pub enum SchemaVersion {109 ImageURL,110 Unique,111}112impl Default for SchemaVersion {113 fn default() -> Self {114 Self::ImageURL115 }116}117118#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]119#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]120pub struct Ownership<AccountId> {121 pub owner: AccountId,122 pub fraction: u128,123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct CollectionType<AccountId> {128 pub owner: AccountId,129 pub mode: CollectionMode,130 pub access: AccessMode,131 pub decimal_points: DecimalPoints,132 pub name: Vec<u16>, // 64 include null escape char133 pub description: Vec<u16>, // 256 include null escape char134 pub token_prefix: Vec<u8>, // 16 include null escape char135 pub mint_mode: bool,136 pub offchain_schema: Vec<u8>,137 pub schema_version: SchemaVersion,138 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender139 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship140 pub limits: CollectionLimits, // Collection private restrictions 141 pub variable_on_chain_schema: Vec<u8>, //142 pub const_on_chain_schema: Vec<u8>, //143}144145#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]146#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]147pub struct NftItemType<AccountId> {148 pub collection: CollectionId,149 pub owner: AccountId,150 pub const_data: Vec<u8>,151 pub variable_data: Vec<u8>,152}153154#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]155#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]156pub struct FungibleItemType<AccountId> {157 pub collection: CollectionId,158 pub owner: AccountId,159 pub value: u128,160}161162#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]163#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]164pub struct ReFungibleItemType<AccountId> {165 pub collection: CollectionId,166 pub owner: Vec<Ownership<AccountId>>,167 pub const_data: Vec<u8>,168 pub variable_data: Vec<u8>,169}170171#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]172#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]173pub struct ApprovePermissions<AccountId> {174 pub approved: AccountId,175 pub amount: u128,176}177178#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]180pub struct VestingItem<AccountId, Moment> {181 pub sender: AccountId,182 pub recipient: AccountId,183 pub collection_id: CollectionId,184 pub item_id: TokenId,185 pub amount: u64,186 pub vesting_date: Moment,187}188189#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]190#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]191pub struct BasketItem<AccountId, BlockNumber> {192 pub address: AccountId,193 pub start_block: BlockNumber,194}195196#[derive(Encode, Decode, Debug, Clone, PartialEq)]197#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]198pub struct CollectionLimits {199 pub account_token_ownership_limit: u32,200 pub sponsored_data_size: u32,201 pub token_limit: u32,202203 // Timeouts for item types in passed blocks204 pub sponsor_transfer_timeout: u32,205}206207impl Default for CollectionLimits {208 fn default() -> CollectionLimits {209 CollectionLimits { 210 account_token_ownership_limit: 10_000_000, 211 token_limit: u32::max_value(),212 sponsored_data_size: u32::max_value(), 213 sponsor_transfer_timeout: 14400 }214 }215}216217#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]218#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]219pub struct ChainLimits {220 pub collection_numbers_limit: u32,221 pub account_token_ownership_limit: u32,222 pub collections_admins_limit: u64,223 pub custom_data_limit: u32,224225 // Timeouts for item types in passed blocks226 pub nft_sponsor_transfer_timeout: u32,227 pub fungible_sponsor_transfer_timeout: u32,228 pub refungible_sponsor_transfer_timeout: u32,229}230231pub trait WeightInfo {232 fn create_collection() -> Weight;233 fn destroy_collection() -> Weight;234 fn add_to_white_list() -> Weight;235 fn remove_from_white_list() -> Weight;236 fn set_public_access_mode() -> Weight;237 fn set_mint_permission() -> Weight;238 fn change_collection_owner() -> Weight;239 fn add_collection_admin() -> Weight;240 fn remove_collection_admin() -> Weight;241 fn set_collection_sponsor() -> Weight;242 fn confirm_sponsorship() -> Weight;243 fn remove_collection_sponsor() -> Weight;244 fn create_item(s: usize) -> Weight;245 fn burn_item() -> Weight;246 fn transfer() -> Weight;247 fn approve() -> Weight;248 fn transfer_from() -> Weight;249 fn set_offchain_schema() -> Weight;250 fn set_const_on_chain_schema() -> Weight;251 fn set_variable_on_chain_schema() -> Weight;252 fn set_variable_meta_data() -> Weight;253 fn enable_contract_sponsoring() -> Weight;254}255256#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]257#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]258pub struct CreateNftData {259 pub const_data: Vec<u8>,260 pub variable_data: Vec<u8>,261}262263#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]264#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]265pub struct CreateFungibleData {266}267268#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]269#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]270pub struct CreateReFungibleData {271 pub const_data: Vec<u8>,272 pub variable_data: Vec<u8>,273}274275#[derive(Encode, Decode, Debug, Clone, PartialEq)]276#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]277pub enum CreateItemData {278 NFT(CreateNftData),279 Fungible(CreateFungibleData),280 ReFungible(CreateReFungibleData),281}282283impl CreateItemData {284 pub fn len(&self) -> usize {285 let len = match self {286 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),287 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),288 _ => 0289 };290 291 return len;292 }293}294295impl From<CreateNftData> for CreateItemData {296 fn from(item: CreateNftData) -> Self {297 CreateItemData::NFT(item)298 }299}300301impl From<CreateReFungibleData> for CreateItemData {302 fn from(item: CreateReFungibleData) -> Self {303 CreateItemData::ReFungible(item)304 }305}306307impl From<CreateFungibleData> for CreateItemData {308 fn from(item: CreateFungibleData) -> Self {309 CreateItemData::Fungible(item)310 }311}312313314decl_error! {315 /// Error for non-fungible-token module.316 pub enum Error for Module<T: Trait> {317 /// Total collections bound exceeded.318 TotalCollectionsLimitExceeded,319 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.320 CollectionDecimalPointLimitExceeded, 321 /// Collection name can not be longer than 63 char.322 CollectionNameLimitExceeded, 323 /// Collection description can not be longer than 255 char.324 CollectionDescriptionLimitExceeded, 325 /// Token prefix can not be longer than 15 char.326 CollectionTokenPrefixLimitExceeded,327 /// This collection does not exist.328 CollectionNotFound,329 /// Item not exists.330 TokenNotFound,331 /// Arithmetic calculation overflow.332 NumOverflow, 333 /// Account already has admin role.334 AlreadyAdmin, 335 /// You do not own this collection.336 NoPermission,337 /// This address is not set as sponsor, use setCollectionSponsor first.338 ConfirmUnsetSponsorFail,339 /// Collection is not in mint mode.340 PublicMintingNotAllowed,341 /// Sender parameter and item owner must be equal.342 MustBeTokenOwner,343 /// Item balance not enough.344 TokenValueTooLow,345 /// Size of item is too large.346 NftSizeLimitExceeded,347 /// No approve found348 ApproveNotFound,349 /// Requested value more than approved.350 TokenValueNotEnough,351 /// Only approved addresses can call this method.352 ApproveRequired,353 /// Address is not in white list.354 AddresNotInWhiteList,355 /// Number of collection admins bound exceeded.356 CollectionAdminsLimitExceeded,357 /// Owned tokens by a single address bound exceeded.358 AddressOwnershipLimitExceeded,359 /// Length of items properties must be greater than 0.360 EmptyArgument,361 /// const_data exceeded data limit.362 TokenConstDataLimitExceeded,363 /// variable_data exceeded data limit.364 TokenVariableDataLimitExceeded,365 /// Not NFT item data used to mint in NFT collection.366 NotNftDataUsedToMintNftCollectionToken,367 /// Not Fungible item data used to mint in Fungible collection.368 NotFungibleDataUsedToMintFungibleCollectionToken,369 /// Not Re Fungible item data used to mint in Re Fungible collection.370 NotReFungibleDataUsedToMintReFungibleCollectionToken,371 /// Unexpected collection type.372 UnexpectedCollectionType,373 /// Can't store metadata in fungible tokens.374 CantStoreMetadataInFungibleTokens,375 /// Collection token limit exceeded376 CollectionTokenLimitExceeded,377 /// Account token limit exceeded per collection378 AccountTokenLimitExceeded,379 /// Collection limit bounds per collection exceeded380 CollectionLimitBoundsExceeded381 }382}383384pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {385 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;386387 /// Weight information for extrinsics in this pallet.388 type WeightInfo: WeightInfo;389}390391#[cfg(feature = "runtime-benchmarks")]392mod benchmarking;393394// #endregion395396decl_storage! {397 trait Store for Module<T: Trait> as Nft {398399 // Private members400 NextCollectionID: CollectionId;401 CreatedCollectionCount: u32;402 ChainVersion: u64;403 ItemListIndex: map hasher(identity) CollectionId => TokenId;404405 // Chain limits struct406 pub ChainLimit get(fn chain_limit) config(): ChainLimits;407408 // Bound counters409 CollectionCount: u32;410 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;411412 // Basic collections413 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;414 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;415 pub WhiteList get(fn white_list): map hasher(identity) CollectionId => Vec<T::AccountId>;416417 /// Balance owner per collection map418 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;419420 /// second parameter: item id + owner account id421 pub ApprovedList get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;422423 /// Item collections424 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;425 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => FungibleItemType<T::AccountId>;426 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;427428 /// Index list429 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;430431 /// Tokens transfer baskets432 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;433 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => Vec<BasketItem<T::AccountId, T::BlockNumber>>;434 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;435436 // Contract Sponsorship and Ownership437 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;438 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;439 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;440 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;441 }442 add_extra_genesis {443 build(|config: &GenesisConfig<T>| {444 // Modification of storage445 for (_num, _c) in &config.collection {446 <Module<T>>::init_collection(_c);447 }448449 for (_num, _q, _i) in &config.nft_item_id {450 <Module<T>>::init_nft_token(_i);451 }452453 for (_num, _q, _i) in &config.fungible_item_id {454 <Module<T>>::init_fungible_token(_i);455 }456457 for (_num, _q, _i) in &config.refungible_item_id {458 <Module<T>>::init_refungible_token(_i);459 }460 })461 }462}463464decl_event!(465 pub enum Event<T>466 where467 AccountId = <T as system::Trait>::AccountId,468 {469 /// New collection was created470 /// 471 /// # Arguments472 /// 473 /// * collection_id: Globally unique identifier of newly created collection.474 /// 475 /// * mode: [CollectionMode] converted into u8.476 /// 477 /// * account_id: Collection owner.478 Created(CollectionId, u8, AccountId),479480 /// New item was created.481 /// 482 /// # Arguments483 /// 484 /// * collection_id: Id of the collection where item was created.485 /// 486 /// * item_id: Id of an item. Unique within the collection.487 ItemCreated(CollectionId, TokenId),488489 /// Collection item was burned.490 /// 491 /// # Arguments492 /// 493 /// collection_id.494 /// 495 /// item_id: Identifier of burned NFT.496 ItemDestroyed(CollectionId, TokenId),497 }498);499500decl_module! {501 pub struct Module<T: Trait> for enum Call where origin: T::Origin {502503 fn deposit_event() = default;504 type Error = Error<T>;505506 fn on_initialize(now: T::BlockNumber) -> Weight {507508 if ChainVersion::get() < 2509 {510 let value = NextCollectionID::get();511 CreatedCollectionCount::put(value);512 ChainVersion::put(2);513 }514515 0516 }517518 /// 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.519 /// 520 /// # Permissions521 /// 522 /// * Anyone.523 /// 524 /// # Arguments525 /// 526 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.527 /// 528 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.529 /// 530 /// * token_prefix: UTF-8 string with token prefix.531 /// 532 /// * mode: [CollectionMode] collection type and type dependent data.533 // returns collection ID534 #[weight = T::WeightInfo::create_collection()]535 pub fn create_collection(origin,536 collection_name: Vec<u16>,537 collection_description: Vec<u16>,538 token_prefix: Vec<u8>,539 mode: CollectionMode) -> DispatchResult {540541 // Anyone can create a collection542 let who = ensure_signed(origin)?;543544 let decimal_points = match mode {545 CollectionMode::Fungible(points) => points,546 CollectionMode::ReFungible(points) => points,547 _ => 0548 };549550 // bound Total number of collections551 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);552553 // check params554 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);555556 let mut name = collection_name.to_vec();557 name.push(0);558 ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);559560 let mut description = collection_description.to_vec();561 description.push(0);562 ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);563564 let mut prefix = token_prefix.to_vec();565 prefix.push(0);566 ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);567568 // Generate next collection ID569 let next_id = CreatedCollectionCount::get()570 .checked_add(1)571 .ok_or(Error::<T>::NumOverflow)?;572573 // bound counter574 let total = CollectionCount::get()575 .checked_add(1)576 .ok_or(Error::<T>::NumOverflow)?;577578 CreatedCollectionCount::put(next_id);579 CollectionCount::put(total);580581 // Create new collection582 let new_collection = CollectionType {583 owner: who.clone(),584 name: name,585 mode: mode.clone(),586 mint_mode: false,587 access: AccessMode::Normal,588 description: description,589 decimal_points: decimal_points,590 token_prefix: prefix,591 offchain_schema: Vec::new(),592 schema_version: SchemaVersion::ImageURL,593 sponsor: T::AccountId::default(),594 unconfirmed_sponsor: T::AccountId::default(),595 variable_on_chain_schema: Vec::new(),596 const_on_chain_schema: Vec::new(),597 limits: CollectionLimits::default(),598 };599600 // Add new collection to map601 <Collection<T>>::insert(next_id, new_collection);602603 // call event604 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));605606 Ok(())607 }608609 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.610 /// 611 /// # Permissions612 /// 613 /// * Collection Owner.614 /// 615 /// # Arguments616 /// 617 /// * collection_id: collection to destroy.618 #[weight = T::WeightInfo::destroy_collection()]619 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {620621 let sender = ensure_signed(origin)?;622 Self::check_owner_permissions(collection_id, sender)?;623624 <AddressTokens<T>>::remove_prefix(collection_id);625 <ApprovedList<T>>::remove_prefix(collection_id);626 <Balance<T>>::remove_prefix(collection_id);627 <ItemListIndex>::remove(collection_id);628 <AdminList<T>>::remove(collection_id);629 <Collection<T>>::remove(collection_id);630 <WhiteList<T>>::remove(collection_id);631632 <NftItemList<T>>::remove_prefix(collection_id);633 <FungibleItemList<T>>::remove_prefix(collection_id);634 <ReFungibleItemList<T>>::remove_prefix(collection_id);635636 <NftTransferBasket<T>>::remove_prefix(collection_id);637 <FungibleTransferBasket<T>>::remove_prefix(collection_id);638 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);639640 if CollectionCount::get() > 0641 {642 // bound couter643 let total = CollectionCount::get()644 .checked_sub(1)645 .ok_or(Error::<T>::NumOverflow)?;646647 CollectionCount::put(total);648 }649650 Ok(())651 }652653 /// Add an address to white list.654 /// 655 /// # Permissions656 /// 657 /// * Collection Owner658 /// * Collection Admin659 /// 660 /// # Arguments661 /// 662 /// * collection_id.663 /// 664 /// * address.665 #[weight = T::WeightInfo::add_to_white_list()]666 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{667668 let sender = ensure_signed(origin)?;669 Self::check_owner_or_admin_permissions(collection_id, sender)?;670671 let mut white_list_collection: Vec<T::AccountId>;672 if <WhiteList<T>>::contains_key(collection_id) {673 white_list_collection = <WhiteList<T>>::get(collection_id);674 if !white_list_collection.contains(&address.clone())675 {676 white_list_collection.push(address.clone());677 }678 }679 else {680 white_list_collection = Vec::new();681 white_list_collection.push(address.clone());682 }683684 <WhiteList<T>>::insert(collection_id, white_list_collection);685 Ok(())686 }687688 /// Remove an address from white list.689 /// 690 /// # Permissions691 /// 692 /// * Collection Owner693 /// * Collection Admin694 /// 695 /// # Arguments696 /// 697 /// * collection_id.698 /// 699 /// * address.700 #[weight = T::WeightInfo::remove_from_white_list()]701 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{702703 let sender = ensure_signed(origin)?;704 Self::check_owner_or_admin_permissions(collection_id, sender)?;705706 if <WhiteList<T>>::contains_key(collection_id) {707 let mut white_list_collection = <WhiteList<T>>::get(collection_id);708 if white_list_collection.contains(&address.clone())709 {710 white_list_collection.retain(|i| *i != address.clone());711 <WhiteList<T>>::insert(collection_id, white_list_collection);712 }713 }714715 Ok(())716 }717718 /// Toggle between normal and white list access for the methods with access for `Anyone`.719 /// 720 /// # Permissions721 /// 722 /// * Collection Owner.723 /// 724 /// # Arguments725 /// 726 /// * collection_id.727 /// 728 /// * mode: [AccessMode]729 #[weight = T::WeightInfo::set_public_access_mode()]730 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult731 {732 let sender = ensure_signed(origin)?;733734 Self::check_owner_permissions(collection_id, sender)?;735 let mut target_collection = <Collection<T>>::get(collection_id);736 target_collection.access = mode;737 <Collection<T>>::insert(collection_id, target_collection);738739 Ok(())740 }741742 /// Allows Anyone to create tokens if:743 /// * White List is enabled, and744 /// * Address is added to white list, and745 /// * This method was called with True parameter746 /// 747 /// # Permissions748 /// * Collection Owner749 ///750 /// # Arguments751 /// 752 /// * collection_id.753 /// 754 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.755 #[weight = T::WeightInfo::set_mint_permission()]756 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult757 {758 let sender = ensure_signed(origin)?;759760 Self::check_owner_permissions(collection_id, sender)?;761 let mut target_collection = <Collection<T>>::get(collection_id);762 target_collection.mint_mode = mint_permission;763 <Collection<T>>::insert(collection_id, target_collection);764765 Ok(())766 }767768 /// Change the owner of the collection.769 /// 770 /// # Permissions771 /// 772 /// * Collection Owner.773 /// 774 /// # Arguments775 /// 776 /// * collection_id.777 /// 778 /// * new_owner.779 #[weight = T::WeightInfo::change_collection_owner()]780 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {781782 let sender = ensure_signed(origin)?;783 Self::check_owner_permissions(collection_id, sender)?;784 let mut target_collection = <Collection<T>>::get(collection_id);785 target_collection.owner = new_owner;786 <Collection<T>>::insert(collection_id, target_collection);787788 Ok(())789 }790791 /// Adds an admin of the Collection.792 /// 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. 793 /// 794 /// # Permissions795 /// 796 /// * Collection Owner.797 /// * Collection Admin.798 /// 799 /// # Arguments800 /// 801 /// * collection_id: ID of the Collection to add admin for.802 /// 803 /// * new_admin_id: Address of new admin to add.804 #[weight = T::WeightInfo::add_collection_admin()]805 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {806807 let sender = ensure_signed(origin)?;808 Self::check_owner_or_admin_permissions(collection_id, sender)?;809 let mut admin_arr: Vec<T::AccountId> = Vec::new();810811 if <AdminList<T>>::contains_key(collection_id)812 {813 admin_arr = <AdminList<T>>::get(collection_id);814 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);815 }816817 // Number of collection admins818 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);819820 admin_arr.push(new_admin_id);821 <AdminList<T>>::insert(collection_id, admin_arr);822823 Ok(())824 }825826 /// 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.827 ///828 /// # Permissions829 /// 830 /// * Collection Owner.831 /// * Collection Admin.832 /// 833 /// # Arguments834 /// 835 /// * collection_id: ID of the Collection to remove admin for.836 /// 837 /// * account_id: Address of admin to remove.838 #[weight = T::WeightInfo::remove_collection_admin()]839 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {840841 let sender = ensure_signed(origin)?;842 Self::check_owner_or_admin_permissions(collection_id, sender)?;843844 if <AdminList<T>>::contains_key(collection_id)845 {846 let mut admin_arr = <AdminList<T>>::get(collection_id);847 admin_arr.retain(|i| *i != account_id);848 <AdminList<T>>::insert(collection_id, admin_arr);849 }850851 Ok(())852 }853854 /// # Permissions855 /// 856 /// * Collection Owner857 /// 858 /// # Arguments859 /// 860 /// * collection_id.861 /// 862 /// * new_sponsor.863 #[weight = T::WeightInfo::set_collection_sponsor()]864 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {865866 let sender = ensure_signed(origin)?;867 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);868869 let mut target_collection = <Collection<T>>::get(collection_id);870 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);871872 target_collection.unconfirmed_sponsor = new_sponsor;873 <Collection<T>>::insert(collection_id, target_collection);874875 Ok(())876 }877878 /// # Permissions879 /// 880 /// * Sponsor.881 /// 882 /// # Arguments883 /// 884 /// * collection_id.885 #[weight = T::WeightInfo::confirm_sponsorship()]886 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {887888 let sender = ensure_signed(origin)?;889 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);890891 let mut target_collection = <Collection<T>>::get(collection_id);892 ensure!(sender == target_collection.unconfirmed_sponsor, Error::<T>::ConfirmUnsetSponsorFail);893894 target_collection.sponsor = target_collection.unconfirmed_sponsor;895 target_collection.unconfirmed_sponsor = T::AccountId::default();896 <Collection<T>>::insert(collection_id, target_collection);897898 Ok(())899 }900901 /// Switch back to pay-per-own-transaction model.902 ///903 /// # Permissions904 ///905 /// * Collection owner.906 /// 907 /// # Arguments908 /// 909 /// * collection_id.910 #[weight = T::WeightInfo::remove_collection_sponsor()]911 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {912913 let sender = ensure_signed(origin)?;914 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);915916 let mut target_collection = <Collection<T>>::get(collection_id);917 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);918919 target_collection.sponsor = T::AccountId::default();920 <Collection<T>>::insert(collection_id, target_collection);921922 Ok(())923 }924925 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.926 /// 927 /// # Permissions928 /// 929 /// * Collection Owner.930 /// * Collection Admin.931 /// * Anyone if932 /// * White List is enabled, and933 /// * Address is added to white list, and934 /// * MintPermission is enabled (see SetMintPermission method)935 /// 936 /// # Arguments937 /// 938 /// * collection_id: ID of the collection.939 /// 940 /// * owner: Address, initial owner of the NFT.941 ///942 /// * data: Token data to store on chain.943 // #[weight =944 // (130_000_000 as Weight)945 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))946 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))947 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]948949 #[weight = T::WeightInfo::create_item(data.len())]950 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {951952 let sender = ensure_signed(origin)?;953954 Self::collection_exists(collection_id)?;955956 let target_collection = <Collection<T>>::get(collection_id);957958 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;959 Self::validate_create_item_args(&target_collection, &data)?;960 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;961962 Ok(())963 }964965 /// This method creates multiple instances of NFT Collection created with CreateCollection method.966 /// 967 /// # Permissions968 /// 969 /// * Collection Owner.970 /// * Collection Admin.971 /// * Anyone if972 /// * White List is enabled, and973 /// * Address is added to white list, and974 /// * MintPermission is enabled (see SetMintPermission method)975 /// 976 /// # Arguments977 /// 978 /// * collection_id: ID of the collection.979 /// 980 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].981 /// 982 /// * owner: Address, initial owner of the NFT.983 #[weight = T::WeightInfo::create_item(items_data.into_iter()984 .map(|data| { data.len() })985 .sum())]986 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {987988 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);989 let sender = ensure_signed(origin)?;990991 Self::collection_exists(collection_id)?;992 let target_collection = <Collection<T>>::get(collection_id);993994 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;995996 for data in &items_data {997 Self::validate_create_item_args(&target_collection, data)?;998 }999 for data in &items_data {1000 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;1001 }10021003 Ok(())1004 }10051006 /// Destroys a concrete instance of NFT.1007 /// 1008 /// # Permissions1009 /// 1010 /// * Collection Owner.1011 /// * Collection Admin.1012 /// * Current NFT Owner.1013 /// 1014 /// # Arguments1015 /// 1016 /// * collection_id: ID of the collection.1017 /// 1018 /// * item_id: ID of NFT to burn.1019 #[weight = T::WeightInfo::burn_item()]1020 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {10211022 let sender = ensure_signed(origin)?;1023 Self::collection_exists(collection_id)?;10241025 // Transfer permissions check1026 let target_collection = <Collection<T>>::get(collection_id);1027 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1028 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1029 Error::<T>::NoPermission);10301031 if target_collection.access == AccessMode::WhiteList {1032 Self::check_white_list(collection_id, &sender)?;1033 }10341035 match target_collection.mode1036 {1037 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1038 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,1039 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1040 _ => ()1041 };10421043 // call event1044 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10451046 Ok(())1047 }10481049 /// Change ownership of the token.1050 /// 1051 /// # Permissions1052 /// 1053 /// * Collection Owner1054 /// * Collection Admin1055 /// * Current NFT owner1056 ///1057 /// # Arguments1058 /// 1059 /// * recipient: Address of token recipient.1060 /// 1061 /// * collection_id.1062 /// 1063 /// * item_id: ID of the item1064 /// * Non-Fungible Mode: Required.1065 /// * Fungible Mode: Ignored.1066 /// * Re-Fungible Mode: Required.1067 /// 1068 /// * value: Amount to transfer.1069 /// * Non-Fungible Mode: Ignored1070 /// * Fungible Mode: Must specify transferred amount1071 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1072 #[weight = T::WeightInfo::transfer()]1073 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10741075 let sender = ensure_signed(origin)?;1076 let target_collection = <Collection<T>>::get(collection_id);10771078 // Limits check1079 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10801081 // Transfer permissions check1082 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1083 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1084 Error::<T>::NoPermission);10851086 if target_collection.access == AccessMode::WhiteList {1087 Self::check_white_list(collection_id, &sender)?;1088 Self::check_white_list(collection_id, &recipient)?;1089 }10901091 match target_collection.mode1092 {1093 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1094 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1095 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1096 _ => ()1097 };10981099 Ok(())1100 }11011102 /// Set, change, or remove approved address to transfer the ownership of the NFT.1103 /// 1104 /// # Permissions1105 /// 1106 /// * Collection Owner1107 /// * Collection Admin1108 /// * Current NFT owner1109 /// 1110 /// # Arguments1111 /// 1112 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1113 /// 1114 /// * collection_id.1115 /// 1116 /// * item_id: ID of the item.1117 #[weight = T::WeightInfo::approve()]1118 pub fn approve(origin, approved: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {11191120 let sender = ensure_signed(origin)?;11211122 // Transfer permissions check1123 let target_collection = <Collection<T>>::get(collection_id);1124 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1125 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1126 Error::<T>::NoPermission);11271128 if target_collection.access == AccessMode::WhiteList {1129 Self::check_white_list(collection_id, &sender)?;1130 Self::check_white_list(collection_id, &approved)?;1131 }11321133 // amount param stub1134 let amount = 100000000;11351136 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1137 if list_exists {11381139 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1140 let item_contains = list.iter().any(|i| i.approved == approved);11411142 if !item_contains {1143 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1144 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1145 }1146 } else {11471148 let mut list = Vec::new();1149 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1150 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1151 }11521153 Ok(())1154 }1155 1156 /// 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.1157 /// 1158 /// # Permissions1159 /// * Collection Owner1160 /// * Collection Admin1161 /// * Current NFT owner1162 /// * Address approved by current NFT owner1163 /// 1164 /// # Arguments1165 /// 1166 /// * from: Address that owns token.1167 /// 1168 /// * recipient: Address of token recipient.1169 /// 1170 /// * collection_id.1171 /// 1172 /// * item_id: ID of the item.1173 /// 1174 /// * value: Amount to transfer.1175 #[weight = T::WeightInfo::transfer_from()]1176 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11771178 let sender = ensure_signed(origin)?;1179 let mut appoved_transfer = false;11801181 // Check approve1182 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1183 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1184 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1185 if opt_item.is_some()1186 {1187 appoved_transfer = true;1188 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1189 }1190 }11911192 let target_collection = <Collection<T>>::get(collection_id);11931194 // Limits check1195 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11961197 // Transfer permissions check 1198 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1199 Error::<T>::NoPermission);12001201 if target_collection.access == AccessMode::WhiteList {1202 Self::check_white_list(collection_id, &sender)?;1203 Self::check_white_list(collection_id, &recipient)?;1204 }12051206 // remove approve1207 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1208 .into_iter().filter(|i| i.approved != sender.clone()).collect();1209 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);121012111212 match target_collection.mode1213 {1214 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1215 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1216 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1217 _ => ()1218 };12191220 Ok(())1221 }12221223 #[weight = 0]1224 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {12251226 // let no_perm_mes = "You do not have permissions to modify this collection";1227 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1228 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1229 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);12301231 // // on_nft_received call12321233 // Self::transfer(origin, collection_id, item_id, new_owner)?;12341235 Ok(())1236 }12371238 /// Set off-chain data schema.1239 /// 1240 /// # Permissions1241 /// 1242 /// * Collection Owner1243 /// * Collection Admin1244 /// 1245 /// # Arguments1246 /// 1247 /// * collection_id.1248 /// 1249 /// * schema: String representing the offchain data schema.1250 #[weight = T::WeightInfo::set_variable_meta_data()]1251 pub fn set_variable_meta_data (1252 origin,1253 collection_id: CollectionId,1254 item_id: TokenId,1255 data: Vec<u8>1256 ) -> DispatchResult {1257 let sender = ensure_signed(origin)?;1258 1259 Self::collection_exists(collection_id)?;1260 1261 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12621263 // Modify permissions check1264 let target_collection = <Collection<T>>::get(collection_id);1265 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1266 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1267 Error::<T>::NoPermission);12681269 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12701271 match target_collection.mode1272 {1273 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1274 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1275 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1276 _ => fail!(Error::<T>::UnexpectedCollectionType)1277 };12781279 Ok(())1280 }1281 1282 /// Set schema standard1283 /// ImageURL1284 /// Unique1285 /// 1286 /// # Permissions1287 /// 1288 /// * Collection Owner1289 /// * Collection Admin1290 /// 1291 /// # Arguments1292 /// 1293 /// * collection_id.1294 /// 1295 /// * schema: SchemaVersion: enum1296 #[weight = 0]1297 pub fn set_schema_version(1298 origin,1299 collection_id: CollectionId,1300 version: SchemaVersion1301 ) -> DispatchResult {1302 let sender = ensure_signed(origin)?;1303 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1304 let mut target_collection = <Collection<T>>::get(collection_id);1305 target_collection.schema_version = version;1306 <Collection<T>>::insert(collection_id, target_collection);13071308 Ok(())1309 }13101311 /// Set off-chain data schema.1312 /// 1313 /// # Permissions1314 /// 1315 /// * Collection Owner1316 /// * Collection Admin1317 /// 1318 /// # Arguments1319 /// 1320 /// * collection_id.1321 /// 1322 /// * schema: String representing the offchain data schema.1323 #[weight = T::WeightInfo::set_offchain_schema()]1324 pub fn set_offchain_schema(1325 origin,1326 collection_id: CollectionId,1327 schema: Vec<u8>1328 ) -> DispatchResult {1329 let sender = ensure_signed(origin)?;1330 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13311332 let mut target_collection = <Collection<T>>::get(collection_id);1333 target_collection.offchain_schema = schema;1334 <Collection<T>>::insert(collection_id, target_collection);13351336 Ok(())1337 }13381339 /// Set const on-chain data schema.1340 /// 1341 /// # Permissions1342 /// 1343 /// * Collection Owner1344 /// * Collection Admin1345 /// 1346 /// # Arguments1347 /// 1348 /// * collection_id.1349 /// 1350 /// * schema: String representing the const on-chain data schema.1351 #[weight = T::WeightInfo::set_const_on_chain_schema()]1352 pub fn set_const_on_chain_schema (1353 origin,1354 collection_id: CollectionId,1355 schema: Vec<u8>1356 ) -> DispatchResult {1357 let sender = ensure_signed(origin)?;1358 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13591360 let mut target_collection = <Collection<T>>::get(collection_id);1361 target_collection.const_on_chain_schema = schema;1362 <Collection<T>>::insert(collection_id, target_collection);13631364 Ok(())1365 }13661367 /// Set variable on-chain data schema.1368 /// 1369 /// # Permissions1370 /// 1371 /// * Collection Owner1372 /// * Collection Admin1373 /// 1374 /// # Arguments1375 /// 1376 /// * collection_id.1377 /// 1378 /// * schema: String representing the variable on-chain data schema.1379 #[weight = T::WeightInfo::set_const_on_chain_schema()]1380 pub fn set_variable_on_chain_schema (1381 origin,1382 collection_id: CollectionId,1383 schema: Vec<u8>1384 ) -> DispatchResult {1385 let sender = ensure_signed(origin)?;1386 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13871388 let mut target_collection = <Collection<T>>::get(collection_id);1389 target_collection.variable_on_chain_schema = schema;1390 <Collection<T>>::insert(collection_id, target_collection);13911392 Ok(())1393 }13941395 // Sudo permissions function1396 #[weight = 0]1397 pub fn set_chain_limits(1398 origin,1399 limits: ChainLimits1400 ) -> DispatchResult {1401 ensure_root(origin)?;1402 <ChainLimit>::put(limits);1403 Ok(())1404 }14051406 /// Enable smart contract self-sponsoring.1407 /// 1408 /// # Permissions1409 /// 1410 /// * Contract Owner1411 /// 1412 /// # Arguments1413 /// 1414 /// * contract address1415 /// * enable flag1416 /// 1417 #[weight = T::WeightInfo::enable_contract_sponsoring()]1418 pub fn enable_contract_sponsoring(1419 origin,1420 contract_address: T::AccountId,1421 enable: bool1422 ) -> DispatchResult {14231424 let sender = ensure_signed(origin)?;14251426 #[cfg(feature = "runtime-benchmarks")]1427 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14281429 let mut is_owner = false;1430 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1431 let owner = <ContractOwner<T>>::get(&contract_address);1432 is_owner = sender == owner;1433 }1434 ensure!(is_owner, Error::<T>::NoPermission);14351436 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1437 Ok(())1438 }14391440 /// Set the rate limit for contract sponsoring to specified number of blocks.1441 /// 1442 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1443 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1444 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1445 /// from contract endowment if there are at least B blocks between such transactions. 1446 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1447 /// 1448 /// # Permissions1449 /// 1450 /// * Contract Owner1451 /// 1452 /// # Arguments1453 /// 1454 /// -`contract_address`: Address of the contract to sponsor1455 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1456 /// 1457 #[weight = 0]1458 pub fn set_contract_sponsoring_rate_limit(1459 origin,1460 contract_address: T::AccountId,1461 rate_limit: T::BlockNumber1462 ) -> DispatchResult {1463 let sender = ensure_signed(origin)?;1464 let mut is_owner = false;1465 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1466 let owner = <ContractOwner<T>>::get(&contract_address);1467 is_owner = sender == owner;1468 }1469 ensure!(is_owner, Error::<T>::NoPermission);14701471 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1472 Ok(())1473 }14741475 #[weight = 0]1476 pub fn set_collection_limits(1477 origin,1478 collection_id: u32,1479 limits: CollectionLimits,1480 ) -> DispatchResult {1481 let sender = ensure_signed(origin)?;1482 Self::check_owner_permissions(collection_id, sender.clone())?;1483 let mut target_collection = <Collection<T>>::get(collection_id);1484 let chain_limits = ChainLimit::get();1485 let climits = target_collection.limits;14861487 // collection bounds1488 ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1489 limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP, 1490 Error::<T>::CollectionLimitBoundsExceeded);14911492 // token_limit check prev1493 ensure!(climits.token_limit > limits.token_limit && 1494 limits.token_limit <= chain_limits.account_token_ownership_limit, 1495 Error::<T>::AccountTokenLimitExceeded);14961497 target_collection.limits = limits;1498 <Collection<T>>::insert(collection_id, target_collection);14991500 Ok(())1501 } 1502 }1503}15041505impl<T: Trait> Module<T> {15061507 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15081509 // check token limit and account token limit1510 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1511 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1512 1513 Ok(())1514 }15151516 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15171518 // check token limit and account token limit1519 let total_items: u32 = ItemListIndex::get(collection_id);1520 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1521 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1522 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);15231524 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1525 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1526 Self::check_white_list(collection_id, owner)?;1527 Self::check_white_list(collection_id, sender)?;1528 }15291530 Ok(())1531 }15321533 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1534 match target_collection.mode1535 {1536 CollectionMode::NFT => {1537 if let CreateItemData::NFT(data) = data {1538 // check sizes1539 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1540 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1541 } else {1542 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1543 }1544 },1545 CollectionMode::Fungible(_) => {1546 if let CreateItemData::Fungible(_) = data {1547 } else {1548 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1549 }1550 },1551 CollectionMode::ReFungible(_) => {1552 if let CreateItemData::ReFungible(data) = data {15531554 // check sizes1555 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1556 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1557 } else {1558 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1559 }1560 },1561 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1562 };15631564 Ok(())1565 }15661567 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1568 match data1569 {1570 CreateItemData::NFT(data) => {1571 let item = NftItemType {1572 collection: collection_id,1573 owner,1574 const_data: data.const_data,1575 variable_data: data.variable_data1576 };15771578 Self::add_nft_item(item)?;1579 },1580 CreateItemData::Fungible(_) => {1581 let item = FungibleItemType {1582 collection: collection_id,1583 owner,1584 value: (10 as u128).pow(collection.decimal_points as u32)1585 };15861587 Self::add_fungible_item(item)?;1588 },1589 CreateItemData::ReFungible(data) => {1590 let mut owner_list = Vec::new();1591 let value = (10 as u128).pow(collection.decimal_points as u32);1592 owner_list.push(Ownership {owner: owner.clone(), fraction: value});15931594 let item = ReFungibleItemType {1595 collection: collection_id,1596 owner: owner_list,1597 const_data: data.const_data,1598 variable_data: data.variable_data1599 };16001601 Self::add_refungible_item(item)?;1602 }1603 };16041605 // call event1606 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));16071608 Ok(())1609 }16101611 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1612 let current_index = <ItemListIndex>::get(item.collection)1613 .checked_add(1)1614 .ok_or(Error::<T>::NumOverflow)?;1615 let itemcopy = item.clone();1616 let owner = item.owner.clone();16171618 Self::add_token_index(item.collection, current_index, owner.clone())?;16191620 <ItemListIndex>::insert(item.collection, current_index);1621 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);16221623 // Add current block1624 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1625 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1626 1627 // Update balance1628 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1629 .checked_add(item.value)1630 .ok_or(Error::<T>::NumOverflow)?;1631 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);16321633 Ok(())1634 }16351636 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1637 let current_index = <ItemListIndex>::get(item.collection)1638 .checked_add(1)1639 .ok_or(Error::<T>::NumOverflow)?;1640 let itemcopy = item.clone();16411642 let value = item.owner.first().unwrap().fraction;1643 let owner = item.owner.first().unwrap().owner.clone();16441645 Self::add_token_index(item.collection, current_index, owner.clone())?;16461647 <ItemListIndex>::insert(item.collection, current_index);1648 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);16491650 // Add current block1651 let block_number: T::BlockNumber = 0.into();1652 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);16531654 // Update balance1655 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1656 .checked_add(value)1657 .ok_or(Error::<T>::NumOverflow)?;1658 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);16591660 Ok(())1661 }16621663 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1664 let current_index = <ItemListIndex>::get(item.collection)1665 .checked_add(1)1666 .ok_or(Error::<T>::NumOverflow)?;16671668 let item_owner = item.owner.clone();1669 let collection_id = item.collection.clone();1670 Self::add_token_index(collection_id, current_index, item.owner.clone())?;16711672 <ItemListIndex>::insert(collection_id, current_index);1673 <NftItemList<T>>::insert(collection_id, current_index, item);16741675 // Add current block1676 let block_number: T::BlockNumber = 0.into();1677 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);16781679 // Update balance1680 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1681 .checked_add(1)1682 .ok_or(Error::<T>::NumOverflow)?;1683 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);16841685 Ok(())1686 }16871688 fn burn_refungible_item(1689 collection_id: CollectionId,1690 item_id: TokenId,1691 owner: T::AccountId,1692 ) -> DispatchResult {1693 ensure!(1694 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1695 Error::<T>::TokenNotFound1696 );1697 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1698 let item = collection1699 .owner1700 .iter()1701 .filter(|&i| i.owner == owner)1702 .next()1703 .unwrap();1704 Self::remove_token_index(collection_id, item_id, owner.clone())?;17051706 // remove approve list1707 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));17081709 // update balance1710 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1711 .checked_sub(item.fraction)1712 .ok_or(Error::<T>::NumOverflow)?;1713 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17141715 <ReFungibleItemList<T>>::remove(collection_id, item_id);17161717 Ok(())1718 }17191720 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1721 ensure!(1722 <NftItemList<T>>::contains_key(collection_id, item_id),1723 Error::<T>::TokenNotFound1724 );1725 let item = <NftItemList<T>>::get(collection_id, item_id);1726 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17271728 // remove approve list1729 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17301731 // update balance1732 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1733 .checked_sub(1)1734 .ok_or(Error::<T>::NumOverflow)?;1735 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1736 <NftItemList<T>>::remove(collection_id, item_id);17371738 Ok(())1739 }17401741 fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1742 ensure!(1743 <FungibleItemList<T>>::contains_key(collection_id, item_id),1744 Error::<T>::TokenNotFound1745 );1746 let item = <FungibleItemList<T>>::get(collection_id, item_id);1747 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17481749 // remove approve list1750 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17511752 // update balance1753 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1754 .checked_sub(item.value)1755 .ok_or(Error::<T>::NumOverflow)?;1756 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17571758 <FungibleItemList<T>>::remove(collection_id, item_id);17591760 Ok(())1761 }17621763 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1764 ensure!(1765 <Collection<T>>::contains_key(collection_id),1766 Error::<T>::CollectionNotFound1767 );1768 Ok(())1769 }17701771 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1772 Self::collection_exists(collection_id)?;17731774 let target_collection = <Collection<T>>::get(collection_id);1775 ensure!(1776 subject == target_collection.owner,1777 Error::<T>::NoPermission1778 );17791780 Ok(())1781 }17821783 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1784 let target_collection = <Collection<T>>::get(collection_id);1785 let mut result: bool = subject == target_collection.owner;1786 let exists = <AdminList<T>>::contains_key(collection_id);17871788 if !result & exists {1789 if <AdminList<T>>::get(collection_id).contains(&subject) {1790 result = true1791 }1792 }17931794 result1795 }17961797 fn check_owner_or_admin_permissions(1798 collection_id: CollectionId,1799 subject: T::AccountId,1800 ) -> DispatchResult {1801 Self::collection_exists(collection_id)?;1802 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());18031804 ensure!(1805 result,1806 Error::<T>::NoPermission1807 );1808 Ok(())1809 }18101811 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1812 let target_collection = <Collection<T>>::get(collection_id);18131814 match target_collection.mode {1815 CollectionMode::NFT => {1816 <NftItemList<T>>::get(collection_id, item_id).owner == subject1817 }1818 CollectionMode::Fungible(_) => {1819 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1820 }1821 CollectionMode::ReFungible(_) => {1822 <ReFungibleItemList<T>>::get(collection_id, item_id)1823 .owner1824 .iter()1825 .any(|i| i.owner == subject)1826 }1827 CollectionMode::Invalid => false,1828 }1829 }18301831 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1832 let mes = Error::<T>::AddresNotInWhiteList;1833 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1834 let wl = <WhiteList<T>>::get(collection_id);1835 ensure!(wl.contains(address), mes);18361837 Ok(())1838 }18391840 fn transfer_fungible(1841 collection_id: CollectionId,1842 item_id: TokenId,1843 value: u128,1844 owner: T::AccountId,1845 new_owner: T::AccountId,1846 ) -> DispatchResult {1847 ensure!(1848 <FungibleItemList<T>>::contains_key(collection_id, item_id),1849 Error::<T>::TokenNotFound1850 );18511852 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1853 let amount = full_item.value;18541855 ensure!(amount >= value, Error::<T>::TokenValueTooLow);18561857 // update balance1858 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1859 .checked_sub(value)1860 .ok_or(Error::<T>::NumOverflow)?;1861 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);18621863 let mut new_owner_account_id = 0;1864 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1865 if new_owner_items.len() > 0 {1866 new_owner_account_id = new_owner_items[0];1867 }18681869 // transfer1870 if amount == value && new_owner_account_id == 0 {1871 // change owner1872 // new owner do not have account1873 let mut new_full_item = full_item.clone();1874 new_full_item.owner = new_owner.clone();1875 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);18761877 // update balance1878 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1879 .checked_add(value)1880 .ok_or(Error::<T>::NumOverflow)?;1881 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18821883 // update index collection1884 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1885 } else {1886 let mut new_full_item = full_item.clone();1887 new_full_item.value -= value;18881889 // separate amount1890 if new_owner_account_id > 0 {1891 // new owner has account1892 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1893 item.value += value;18941895 // update balance1896 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1897 .checked_add(value)1898 .ok_or(Error::<T>::NumOverflow)?;1899 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19001901 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1902 } else {1903 // new owner do not have account1904 let item = FungibleItemType {1905 collection: collection_id,1906 owner: new_owner.clone(),1907 value1908 };19091910 Self::add_fungible_item(item)?;1911 }19121913 if amount == value {1914 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;19151916 // remove approve list1917 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1918 <FungibleItemList<T>>::remove(collection_id, item_id);1919 }19201921 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1922 }19231924 Ok(())1925 }19261927 fn transfer_refungible(1928 collection_id: CollectionId,1929 item_id: TokenId,1930 value: u128,1931 owner: T::AccountId,1932 new_owner: T::AccountId,1933 ) -> DispatchResult {1934 ensure!(1935 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1936 Error::<T>::TokenNotFound1937 );19381939 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1940 let item = full_item1941 .owner1942 .iter()1943 .filter(|i| i.owner == owner)1944 .next()1945 .ok_or(Error::<T>::NumOverflow)?;1946 let amount = item.fraction;19471948 ensure!(amount >= value, Error::<T>::TokenValueTooLow);19491950 // update balance1951 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1952 .checked_sub(value)1953 .ok_or(Error::<T>::NumOverflow)?;1954 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19551956 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1957 .checked_add(value)1958 .ok_or(Error::<T>::NumOverflow)?;1959 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19601961 let old_owner = item.owner.clone();1962 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19631964 // transfer1965 if amount == value && !new_owner_has_account {1966 // change owner1967 // new owner do not have account1968 let mut new_full_item = full_item.clone();1969 new_full_item1970 .owner1971 .iter_mut()1972 .find(|i| i.owner == owner)1973 .unwrap()1974 .owner = new_owner.clone();1975 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19761977 // update index collection1978 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1979 } else {1980 let mut new_full_item = full_item.clone();1981 new_full_item1982 .owner1983 .iter_mut()1984 .find(|i| i.owner == owner)1985 .unwrap()1986 .fraction -= value;19871988 // separate amount1989 if new_owner_has_account {1990 // new owner has account1991 new_full_item1992 .owner1993 .iter_mut()1994 .find(|i| i.owner == new_owner)1995 .unwrap()1996 .fraction += value;1997 } else {1998 // new owner do not have account1999 new_full_item.owner.push(Ownership {2000 owner: new_owner.clone(),2001 fraction: value,2002 });2003 Self::add_token_index(collection_id, item_id, new_owner.clone())?;2004 }20052006 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2007 }20082009 Ok(())2010 }20112012 fn transfer_nft(2013 collection_id: CollectionId,2014 item_id: TokenId,2015 sender: T::AccountId,2016 new_owner: T::AccountId,2017 ) -> DispatchResult {2018 ensure!(2019 <NftItemList<T>>::contains_key(collection_id, item_id),2020 Error::<T>::TokenNotFound2021 );20222023 let mut item = <NftItemList<T>>::get(collection_id, item_id);20242025 ensure!(2026 sender == item.owner,2027 Error::<T>::MustBeTokenOwner2028 );20292030 // update balance2031 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2032 .checked_sub(1)2033 .ok_or(Error::<T>::NumOverflow)?;2034 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20352036 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())2037 .checked_add(1)2038 .ok_or(Error::<T>::NumOverflow)?;2039 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);20402041 // change owner2042 let old_owner = item.owner.clone();2043 item.owner = new_owner.clone();2044 <NftItemList<T>>::insert(collection_id, item_id, item);20452046 // update index collection2047 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;20482049 // reset approved list2050 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));2051 Ok(())2052 }2053 2054 fn item_exists(2055 collection_id: CollectionId,2056 item_id: TokenId,2057 mode: &CollectionMode2058 ) -> DispatchResult {2059 match mode {2060 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2061 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2062 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2063 _ => ()2064 };2065 2066 Ok(())2067 }20682069 fn set_re_fungible_variable_data(2070 collection_id: CollectionId,2071 item_id: TokenId,2072 data: Vec<u8>2073 ) -> DispatchResult {2074 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20752076 item.variable_data = data;20772078 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20792080 Ok(())2081 }20822083 fn set_nft_variable_data(2084 collection_id: CollectionId,2085 item_id: TokenId,2086 data: Vec<u8>2087 ) -> DispatchResult {2088 let mut item = <NftItemList<T>>::get(collection_id, item_id);2089 2090 item.variable_data = data;20912092 <NftItemList<T>>::insert(collection_id, item_id, item);2093 2094 Ok(())2095 }20962097 fn init_collection(item: &CollectionType<T::AccountId>) {2098 // check params2099 assert!(2100 item.decimal_points <= MAX_DECIMAL_POINTS,2101 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2102 );2103 assert!(2104 item.name.len() <= 64,2105 "Collection name can not be longer than 63 char"2106 );2107 assert!(2108 item.name.len() <= 256,2109 "Collection description can not be longer than 255 char"2110 );2111 assert!(2112 item.token_prefix.len() <= 16,2113 "Token prefix can not be longer than 15 char"2114 );21152116 // Generate next collection ID2117 let next_id = CreatedCollectionCount::get()2118 .checked_add(1)2119 .unwrap();21202121 CreatedCollectionCount::put(next_id);2122 }21232124 fn init_nft_token(item: &NftItemType<T::AccountId>) {2125 let current_index = <ItemListIndex>::get(item.collection)2126 .checked_add(1)2127 .unwrap();21282129 let item_owner = item.owner.clone();2130 let collection_id = item.collection.clone();2131 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();21322133 <ItemListIndex>::insert(collection_id, current_index);21342135 // Update balance2136 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2137 .checked_add(1)2138 .unwrap();2139 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2140 }21412142 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {2143 let current_index = <ItemListIndex>::get(item.collection)2144 .checked_add(1)2145 .unwrap();2146 let owner = item.owner.clone();21472148 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();21492150 <ItemListIndex>::insert(item.collection, current_index);21512152 // Update balance2153 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2154 .checked_add(item.value)2155 .unwrap();2156 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2157 }21582159 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {2160 let current_index = <ItemListIndex>::get(item.collection)2161 .checked_add(1)2162 .unwrap();21632164 let value = item.owner.first().unwrap().fraction;2165 let owner = item.owner.first().unwrap().owner.clone();21662167 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();21682169 <ItemListIndex>::insert(item.collection, current_index);21702171 // Update balance2172 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2173 .checked_add(value)2174 .unwrap();2175 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2176 }21772178 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {21792180 // add to account limit2181 if <AccountItemCount<T>>::contains_key(owner.clone()) {21822183 // bound Owned tokens by a single address2184 let count = <AccountItemCount<T>>::get(owner.clone());2185 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21862187 <AccountItemCount<T>>::insert(owner.clone(), count2188 .checked_add(1)2189 .ok_or(Error::<T>::NumOverflow)?);2190 }2191 else {2192 <AccountItemCount<T>>::insert(owner.clone(), 1);2193 }21942195 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2196 if list_exists {2197 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2198 let item_contains = list.contains(&item_index.clone());21992200 if !item_contains {2201 list.push(item_index.clone());2202 }22032204 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2205 } else {2206 let mut itm = Vec::new();2207 itm.push(item_index.clone());2208 <AddressTokens<T>>::insert(collection_id, owner, itm);2209 2210 }22112212 Ok(())2213 }22142215 fn remove_token_index(2216 collection_id: CollectionId,2217 item_index: TokenId,2218 owner: T::AccountId,2219 ) -> DispatchResult {22202221 // update counter2222 <AccountItemCount<T>>::insert(owner.clone(), 2223 <AccountItemCount<T>>::get(owner.clone())2224 .checked_sub(1)2225 .ok_or(Error::<T>::NumOverflow)?);222622272228 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2229 if list_exists {2230 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2231 let item_contains = list.contains(&item_index.clone());22322233 if item_contains {2234 list.retain(|&item| item != item_index);2235 <AddressTokens<T>>::insert(collection_id, owner, list);2236 }2237 }22382239 Ok(())2240 }22412242 fn move_token_index(2243 collection_id: CollectionId,2244 item_index: TokenId,2245 old_owner: T::AccountId,2246 new_owner: T::AccountId,2247 ) -> DispatchResult {2248 Self::remove_token_index(collection_id, item_index, old_owner)?;2249 Self::add_token_index(collection_id, item_index, new_owner)?;22502251 Ok(())2252 }2253}22542255////////////////////////////////////////////////////////////////////////////////////////////////////2256// Economic models2257// #region22582259/// Fee multiplier.2260pub type Multiplier = FixedU128;22612262type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2263 <T as system::Trait>::AccountId,2264>>::Balance;2265type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2266 <T as system::Trait>::AccountId,2267>>::NegativeImbalance;22682269/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2270/// in the queue.2271#[derive(Encode, Decode, Clone, Eq, PartialEq)]2272pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2273 #[codec(compact)] BalanceOf<T>2274);22752276impl<T: Trait + Send + Sync> sp_std::fmt::Debug2277 for ChargeTransactionPayment<T>2278{2279 #[cfg(feature = "std")]2280 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2281 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2282 }2283 #[cfg(not(feature = "std"))]2284 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2285 Ok(())2286 }2287}22882289impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2290where2291 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2292 BalanceOf<T>: Send + Sync + FixedPointOperand,2293{2294 /// utility constructor. Used only in client/factory code.2295 pub fn from(fee: BalanceOf<T>) -> Self {2296 Self(fee)2297 }22982299 pub fn traditional_fee(2300 len: usize,2301 info: &DispatchInfoOf<T::Call>,2302 tip: BalanceOf<T>,2303 ) -> BalanceOf<T>2304 where2305 T::Call: Dispatchable<Info = DispatchInfo>,2306 {2307 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2308 }23092310 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2311 let weight_saturation = T::MaximumBlockWeight::get() / info.weight.max(1);2312 let len_saturation = T::MaximumBlockLength::get() as u64 / (len as u64).max(1);2313 let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();2314 final_fee.saturating_mul(coefficient).saturated_into::<TransactionPriority>()2315 }23162317 fn withdraw_fee(2318 &self,2319 who: &T::AccountId,2320 call: &T::Call,2321 info: &DispatchInfoOf<T::Call>,2322 len: usize,2323 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2324 let tip = self.0;23252326 // Set fee based on call type. Creating collection costs 1 Unique.2327 // All other transactions have traditional fees so far2328 // let fee = match call.is_sub_type() {2329 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2330 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2331 // // _ => <BalanceOf<T>>::from(100)2332 // };2333 let fee = Self::traditional_fee(len, info, tip);23342335 // Determine who is paying transaction fee based on ecnomic model2336 // Parse call to extract collection ID and access collection sponsor2337 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2338 Some(Call::create_item(collection_id, _owner, _properties)) => {23392340 // check free create limit2341 if <Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)2342 {2343 <Collection<T>>::get(collection_id).sponsor2344 } else {2345 T::AccountId::default()2346 }2347 }2348 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2349 2350 let _collection_limits = <Collection<T>>::get(collection_id).limits;2351 let _collection_mode = <Collection<T>>::get(collection_id).mode;23522353 // sponsor timeout2354 let sponsor_transfer = match _collection_mode {2355 CollectionMode::NFT => {23562357 // get correct limit2358 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2359 _collection_limits.sponsor_transfer_timeout2360 } else {2361 ChainLimit::get().nft_sponsor_transfer_timeout2362 };23632364 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2365 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2366 let limit_time = basket + limit.into();2367 if block_number >= limit_time {2368 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2369 true2370 }2371 else {2372 false2373 }2374 }2375 CollectionMode::Fungible(_) => {23762377 // get correct limit2378 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2379 _collection_limits.sponsor_transfer_timeout2380 } else {2381 ChainLimit::get().fungible_sponsor_transfer_timeout2382 };23832384 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2385 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2386 if basket.iter().any(|i| i.address == _new_owner.clone())2387 {2388 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2389 let limit_time = item.start_block + limit.into();2390 if block_number >= limit_time {2391 basket.retain(|x| x.address == item.address);2392 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2393 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2394 true2395 }2396 else {2397 false2398 }2399 }2400 else {2401 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2402 true2403 }2404 }2405 CollectionMode::ReFungible(_) => {24062407 // get correct limit2408 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2409 _collection_limits.sponsor_transfer_timeout2410 } else {2411 ChainLimit::get().refungible_sponsor_transfer_timeout2412 };24132414 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2415 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2416 let limit_time = basket + limit.into();2417 if block_number >= limit_time {2418 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2419 true2420 } else {2421 false2422 }2423 }2424 _ => {2425 false2426 },2427 };24282429 if !sponsor_transfer {2430 T::AccountId::default()2431 } else {2432 <Collection<T>>::get(collection_id).sponsor2433 }2434 }24352436 _ => T::AccountId::default(),2437 };24382439 // Sponsor smart contracts2440 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {24412442 // On instantiation: set the contract owner2443 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {24442445 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2446 code_hash,2447 &data,2448 &who,2449 );2450 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());24512452 T::AccountId::default()2453 },24542455 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2456 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {24572458 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());24592460 let mut sponsor_transfer = false;2461 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2462 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2463 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2464 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2465 let limit_time = last_tx_block + rate_limit;24662467 if block_number >= limit_time {2468 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2469 sponsor_transfer = true;2470 }2471 } else {2472 sponsor_transfer = false;2473 }2474 2475 2476 let mut sp = T::AccountId::default();2477 if sponsor_transfer {2478 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2479 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2480 sp = called_contract;2481 }2482 }2483 }24842485 sp2486 },24872488 _ => sponsor,2489 };24902491 let mut who_pays_fee: T::AccountId = sponsor.clone();2492 if sponsor == T::AccountId::default() {2493 who_pays_fee = who.clone();2494 }24952496 // Only mess with balances if fee is not zero.2497 if fee.is_zero() {2498 return Ok((fee, None));2499 }25002501 match <T as transaction_payment::Trait>::Currency::withdraw(2502 &who_pays_fee,2503 fee,2504 if tip.is_zero() {2505 WithdrawReason::TransactionPayment.into()2506 } else {2507 WithdrawReason::TransactionPayment | WithdrawReason::Tip2508 },2509 ExistenceRequirement::KeepAlive,2510 ) {2511 Ok(imbalance) => Ok((fee, Some(imbalance))),2512 Err(_) => Err(InvalidTransaction::Payment.into()),2513 }2514 }2515}251625172518impl<T: Trait + Send + Sync> SignedExtension2519 for ChargeTransactionPayment<T>2520where2521 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2522 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2523{2524 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2525 type AccountId = T::AccountId;2526 type Call = T::Call;2527 type AdditionalSigned = ();2528 type Pre = (2529 BalanceOf<T>,2530 Self::AccountId,2531 Option<NegativeImbalanceOf<T>>,2532 BalanceOf<T>,2533 );2534 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2535 Ok(())2536 }25372538 fn validate(2539 &self,2540 who: &Self::AccountId,2541 call: &Self::Call,2542 info: &DispatchInfoOf<Self::Call>,2543 len: usize,2544 ) -> TransactionValidity {2545 let (fee, _) = self.withdraw_fee(who, call, info, len)?;2546 Ok(ValidTransaction {2547 priority: Self::get_priority(len, info, fee),2548 ..Default::default()2549 })2550 }25512552 fn pre_dispatch(2553 self,2554 who: &Self::AccountId,2555 call: &Self::Call,2556 info: &DispatchInfoOf<Self::Call>,2557 len: usize,2558 ) -> Result<Self::Pre, TransactionValidityError> {2559 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2560 Ok((self.0, who.clone(), imbalance, fee))2561 }25622563 fn post_dispatch(2564 pre: Self::Pre,2565 info: &DispatchInfoOf<Self::Call>,2566 post_info: &PostDispatchInfoOf<Self::Call>,2567 len: usize,2568 _result: &DispatchResult,2569 ) -> Result<(), TransactionValidityError> {2570 let (tip, who, imbalance, fee) = pre;2571 if let Some(payed) = imbalance {2572 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2573 len as u32, info, post_info, tip,2574 );2575 let refund = fee.saturating_sub(actual_fee);2576 let actual_payment =2577 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2578 &who, refund,2579 ) {2580 Ok(refund_imbalance) => {2581 // The refund cannot be larger than the up front payed max weight.2582 // `PostDispatchInfo::calc_unspent` guards against such a case.2583 match payed.offset(refund_imbalance) {2584 Ok(actual_payment) => actual_payment,2585 Err(_) => return Err(InvalidTransaction::Payment.into()),2586 }2587 }2588 // We do not recreate the account using the refund. The up front payment2589 // is gone in that case.2590 Err(_) => payed,2591 };2592 let imbalances = actual_payment.split(tip);2593 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2594 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2595 );2596 }2597 Ok(())2598 }2599}26002601// #endregion1//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 std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516use codec::{Decode, Encode};17pub use frame_support::{18 construct_runtime, decl_event, decl_module, decl_storage, decl_error,19 dispatch::DispatchResult,20 ensure, fail, parameter_types,21 traits::{22 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,23 Randomness, WithdrawReason,24 },25 weights::{26 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28 WeightToFeePolynomial,29 },30 IsSubType, StorageValue,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36 traits::{37 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38 },39 transaction_validity::{40 TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41 },42 FixedPointOperand, FixedU128,43};44use pallet_contracts::ContractAddressFor;45use sp_runtime::traits::StaticLookup;4647#[cfg(test)]48mod mock;4950#[cfg(test)]51mod tests;5253mod default_weights;5455pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;56pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;57pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;5859// Structs60// #region6162pub type CollectionId = u32;63pub type TokenId = u32;64pub type DecimalPoints = u8;6566#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]67#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]68pub enum CollectionMode {69 Invalid,70 NFT,71 // decimal points72 Fungible(DecimalPoints),73 // decimal points74 ReFungible(DecimalPoints),75}7677impl Into<u8> for CollectionMode {78 fn into(self) -> u8 {79 match self {80 CollectionMode::Invalid => 0,81 CollectionMode::NFT => 1,82 CollectionMode::Fungible(_) => 2,83 CollectionMode::ReFungible(_) => 3,84 }85 }86}8788#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]89#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]90pub enum AccessMode {91 Normal,92 WhiteList,93}94impl Default for AccessMode {95 fn default() -> Self {96 Self::Normal97 }98}99100impl Default for CollectionMode {101 fn default() -> Self {102 Self::Invalid103 }104}105106#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]107#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]108pub enum SchemaVersion {109 ImageURL,110 Unique,111}112impl Default for SchemaVersion {113 fn default() -> Self {114 Self::ImageURL115 }116}117118#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]119#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]120pub struct Ownership<AccountId> {121 pub owner: AccountId,122 pub fraction: u128,123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct CollectionType<AccountId> {128 pub owner: AccountId,129 pub mode: CollectionMode,130 pub access: AccessMode,131 pub decimal_points: DecimalPoints,132 pub name: Vec<u16>, // 64 include null escape char133 pub description: Vec<u16>, // 256 include null escape char134 pub token_prefix: Vec<u8>, // 16 include null escape char135 pub mint_mode: bool,136 pub offchain_schema: Vec<u8>,137 pub schema_version: SchemaVersion,138 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender139 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship140 pub limits: CollectionLimits, // Collection private restrictions 141 pub variable_on_chain_schema: Vec<u8>, //142 pub const_on_chain_schema: Vec<u8>, //143}144145#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]146#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]147pub struct NftItemType<AccountId> {148 pub collection: CollectionId,149 pub owner: AccountId,150 pub const_data: Vec<u8>,151 pub variable_data: Vec<u8>,152}153154#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]155#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]156pub struct FungibleItemType<AccountId> {157 pub collection: CollectionId,158 pub owner: AccountId,159 pub value: u128,160}161162#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]163#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]164pub struct ReFungibleItemType<AccountId> {165 pub collection: CollectionId,166 pub owner: Vec<Ownership<AccountId>>,167 pub const_data: Vec<u8>,168 pub variable_data: Vec<u8>,169}170171#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]172#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]173pub struct ApprovePermissions<AccountId> {174 pub approved: AccountId,175 pub amount: u128,176}177178#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]180pub struct VestingItem<AccountId, Moment> {181 pub sender: AccountId,182 pub recipient: AccountId,183 pub collection_id: CollectionId,184 pub item_id: TokenId,185 pub amount: u64,186 pub vesting_date: Moment,187}188189#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]190#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]191pub struct BasketItem<AccountId, BlockNumber> {192 pub address: AccountId,193 pub start_block: BlockNumber,194}195196#[derive(Encode, Decode, Debug, Clone, PartialEq)]197#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]198pub struct CollectionLimits {199 pub account_token_ownership_limit: u32,200 pub sponsored_data_size: u32,201 pub token_limit: u32,202203 // Timeouts for item types in passed blocks204 pub sponsor_transfer_timeout: u32,205}206207impl Default for CollectionLimits {208 fn default() -> CollectionLimits {209 CollectionLimits { 210 account_token_ownership_limit: 10_000_000, 211 token_limit: u32::max_value(),212 sponsored_data_size: u32::max_value(), 213 sponsor_transfer_timeout: 14400 }214 }215}216217#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]218#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]219pub struct ChainLimits {220 pub collection_numbers_limit: u32,221 pub account_token_ownership_limit: u32,222 pub collections_admins_limit: u64,223 pub custom_data_limit: u32,224225 // Timeouts for item types in passed blocks226 pub nft_sponsor_transfer_timeout: u32,227 pub fungible_sponsor_transfer_timeout: u32,228 pub refungible_sponsor_transfer_timeout: u32,229}230231pub trait WeightInfo {232 fn create_collection() -> Weight;233 fn destroy_collection() -> Weight;234 fn add_to_white_list() -> Weight;235 fn remove_from_white_list() -> Weight;236 fn set_public_access_mode() -> Weight;237 fn set_mint_permission() -> Weight;238 fn change_collection_owner() -> Weight;239 fn add_collection_admin() -> Weight;240 fn remove_collection_admin() -> Weight;241 fn set_collection_sponsor() -> Weight;242 fn confirm_sponsorship() -> Weight;243 fn remove_collection_sponsor() -> Weight;244 fn create_item(s: usize) -> Weight;245 fn burn_item() -> Weight;246 fn transfer() -> Weight;247 fn approve() -> Weight;248 fn transfer_from() -> Weight;249 fn set_offchain_schema() -> Weight;250 fn set_const_on_chain_schema() -> Weight;251 fn set_variable_on_chain_schema() -> Weight;252 fn set_variable_meta_data() -> Weight;253 fn enable_contract_sponsoring() -> Weight;254}255256#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]257#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]258pub struct CreateNftData {259 pub const_data: Vec<u8>,260 pub variable_data: Vec<u8>,261}262263#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]264#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]265pub struct CreateFungibleData {266}267268#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]269#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]270pub struct CreateReFungibleData {271 pub const_data: Vec<u8>,272 pub variable_data: Vec<u8>,273}274275#[derive(Encode, Decode, Debug, Clone, PartialEq)]276#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]277pub enum CreateItemData {278 NFT(CreateNftData),279 Fungible(CreateFungibleData),280 ReFungible(CreateReFungibleData),281}282283impl CreateItemData {284 pub fn len(&self) -> usize {285 let len = match self {286 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),287 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),288 _ => 0289 };290 291 return len;292 }293}294295impl From<CreateNftData> for CreateItemData {296 fn from(item: CreateNftData) -> Self {297 CreateItemData::NFT(item)298 }299}300301impl From<CreateReFungibleData> for CreateItemData {302 fn from(item: CreateReFungibleData) -> Self {303 CreateItemData::ReFungible(item)304 }305}306307impl From<CreateFungibleData> for CreateItemData {308 fn from(item: CreateFungibleData) -> Self {309 CreateItemData::Fungible(item)310 }311}312313314decl_error! {315 /// Error for non-fungible-token module.316 pub enum Error for Module<T: Trait> {317 /// Total collections bound exceeded.318 TotalCollectionsLimitExceeded,319 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.320 CollectionDecimalPointLimitExceeded, 321 /// Collection name can not be longer than 63 char.322 CollectionNameLimitExceeded, 323 /// Collection description can not be longer than 255 char.324 CollectionDescriptionLimitExceeded, 325 /// Token prefix can not be longer than 15 char.326 CollectionTokenPrefixLimitExceeded,327 /// This collection does not exist.328 CollectionNotFound,329 /// Item not exists.330 TokenNotFound,331 /// Arithmetic calculation overflow.332 NumOverflow, 333 /// Account already has admin role.334 AlreadyAdmin, 335 /// You do not own this collection.336 NoPermission,337 /// This address is not set as sponsor, use setCollectionSponsor first.338 ConfirmUnsetSponsorFail,339 /// Collection is not in mint mode.340 PublicMintingNotAllowed,341 /// Sender parameter and item owner must be equal.342 MustBeTokenOwner,343 /// Item balance not enough.344 TokenValueTooLow,345 /// Size of item is too large.346 NftSizeLimitExceeded,347 /// No approve found348 ApproveNotFound,349 /// Requested value more than approved.350 TokenValueNotEnough,351 /// Only approved addresses can call this method.352 ApproveRequired,353 /// Address is not in white list.354 AddresNotInWhiteList,355 /// Number of collection admins bound exceeded.356 CollectionAdminsLimitExceeded,357 /// Owned tokens by a single address bound exceeded.358 AddressOwnershipLimitExceeded,359 /// Length of items properties must be greater than 0.360 EmptyArgument,361 /// const_data exceeded data limit.362 TokenConstDataLimitExceeded,363 /// variable_data exceeded data limit.364 TokenVariableDataLimitExceeded,365 /// Not NFT item data used to mint in NFT collection.366 NotNftDataUsedToMintNftCollectionToken,367 /// Not Fungible item data used to mint in Fungible collection.368 NotFungibleDataUsedToMintFungibleCollectionToken,369 /// Not Re Fungible item data used to mint in Re Fungible collection.370 NotReFungibleDataUsedToMintReFungibleCollectionToken,371 /// Unexpected collection type.372 UnexpectedCollectionType,373 /// Can't store metadata in fungible tokens.374 CantStoreMetadataInFungibleTokens,375 /// Collection token limit exceeded376 CollectionTokenLimitExceeded,377 /// Account token limit exceeded per collection378 AccountTokenLimitExceeded,379 /// Collection limit bounds per collection exceeded380 CollectionLimitBoundsExceeded381 }382}383384pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {385 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;386387 /// Weight information for extrinsics in this pallet.388 type WeightInfo: WeightInfo;389}390391#[cfg(feature = "runtime-benchmarks")]392mod benchmarking;393394// #endregion395396decl_storage! {397 trait Store for Module<T: Trait> as Nft {398399 // Private members400 NextCollectionID: CollectionId;401 CreatedCollectionCount: u32;402 ChainVersion: u64;403 ItemListIndex: map hasher(identity) CollectionId => TokenId;404405 // Chain limits struct406 pub ChainLimit get(fn chain_limit) config(): ChainLimits;407408 // Bound counters409 CollectionCount: u32;410 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;411412 // Basic collections413 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;414 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;415 pub WhiteList get(fn white_list): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => bool;416417 /// Balance owner per collection map418 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;419420 /// second parameter: item id + owner account id421 pub ApprovedList get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;422423 /// Item collections424 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;425 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => FungibleItemType<T::AccountId>;426 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;427428 /// Index list429 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;430431 /// Tokens transfer baskets432 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;433 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => Vec<BasketItem<T::AccountId, T::BlockNumber>>;434 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;435436 // Contract Sponsorship and Ownership437 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;438 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;439 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;440 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;441 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 442 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::AccountId => bool; 443 }444 add_extra_genesis {445 build(|config: &GenesisConfig<T>| {446 // Modification of storage447 for (_num, _c) in &config.collection {448 <Module<T>>::init_collection(_c);449 }450451 for (_num, _q, _i) in &config.nft_item_id {452 <Module<T>>::init_nft_token(_i);453 }454455 for (_num, _q, _i) in &config.fungible_item_id {456 <Module<T>>::init_fungible_token(_i);457 }458459 for (_num, _q, _i) in &config.refungible_item_id {460 <Module<T>>::init_refungible_token(_i);461 }462 })463 }464}465466decl_event!(467 pub enum Event<T>468 where469 AccountId = <T as system::Trait>::AccountId,470 {471 /// New collection was created472 /// 473 /// # Arguments474 /// 475 /// * collection_id: Globally unique identifier of newly created collection.476 /// 477 /// * mode: [CollectionMode] converted into u8.478 /// 479 /// * account_id: Collection owner.480 Created(CollectionId, u8, AccountId),481482 /// New item was created.483 /// 484 /// # Arguments485 /// 486 /// * collection_id: Id of the collection where item was created.487 /// 488 /// * item_id: Id of an item. Unique within the collection.489 ItemCreated(CollectionId, TokenId),490491 /// Collection item was burned.492 /// 493 /// # Arguments494 /// 495 /// collection_id.496 /// 497 /// item_id: Identifier of burned NFT.498 ItemDestroyed(CollectionId, TokenId),499 }500);501502decl_module! {503 pub struct Module<T: Trait> for enum Call where origin: T::Origin {504505 fn deposit_event() = default;506 type Error = Error<T>;507508 fn on_initialize(now: T::BlockNumber) -> Weight {509510 if ChainVersion::get() < 2511 {512 let value = NextCollectionID::get();513 CreatedCollectionCount::put(value);514 ChainVersion::put(2);515 }516517 0518 }519520 /// 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.521 /// 522 /// # Permissions523 /// 524 /// * Anyone.525 /// 526 /// # Arguments527 /// 528 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.529 /// 530 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.531 /// 532 /// * token_prefix: UTF-8 string with token prefix.533 /// 534 /// * mode: [CollectionMode] collection type and type dependent data.535 // returns collection ID536 #[weight = T::WeightInfo::create_collection()]537 pub fn create_collection(origin,538 collection_name: Vec<u16>,539 collection_description: Vec<u16>,540 token_prefix: Vec<u8>,541 mode: CollectionMode) -> DispatchResult {542543 // Anyone can create a collection544 let who = ensure_signed(origin)?;545546 let decimal_points = match mode {547 CollectionMode::Fungible(points) => points,548 CollectionMode::ReFungible(points) => points,549 _ => 0550 };551552 // bound Total number of collections553 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);554555 // check params556 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);557558 let mut name = collection_name.to_vec();559 name.push(0);560 ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);561562 let mut description = collection_description.to_vec();563 description.push(0);564 ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);565566 let mut prefix = token_prefix.to_vec();567 prefix.push(0);568 ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);569570 // Generate next collection ID571 let next_id = CreatedCollectionCount::get()572 .checked_add(1)573 .ok_or(Error::<T>::NumOverflow)?;574575 // bound counter576 let total = CollectionCount::get()577 .checked_add(1)578 .ok_or(Error::<T>::NumOverflow)?;579580 CreatedCollectionCount::put(next_id);581 CollectionCount::put(total);582583 // Create new collection584 let new_collection = CollectionType {585 owner: who.clone(),586 name: name,587 mode: mode.clone(),588 mint_mode: false,589 access: AccessMode::Normal,590 description: description,591 decimal_points: decimal_points,592 token_prefix: prefix,593 offchain_schema: Vec::new(),594 schema_version: SchemaVersion::ImageURL,595 sponsor: T::AccountId::default(),596 unconfirmed_sponsor: T::AccountId::default(),597 variable_on_chain_schema: Vec::new(),598 const_on_chain_schema: Vec::new(),599 limits: CollectionLimits::default(),600 };601602 // Add new collection to map603 <Collection<T>>::insert(next_id, new_collection);604605 // call event606 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));607608 Ok(())609 }610611 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.612 /// 613 /// # Permissions614 /// 615 /// * Collection Owner.616 /// 617 /// # Arguments618 /// 619 /// * collection_id: collection to destroy.620 #[weight = T::WeightInfo::destroy_collection()]621 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {622623 let sender = ensure_signed(origin)?;624 Self::check_owner_permissions(collection_id, sender)?;625626 <AddressTokens<T>>::remove_prefix(collection_id);627 <ApprovedList<T>>::remove_prefix(collection_id);628 <Balance<T>>::remove_prefix(collection_id);629 <ItemListIndex>::remove(collection_id);630 <AdminList<T>>::remove(collection_id);631 <Collection<T>>::remove(collection_id);632 <WhiteList<T>>::remove_prefix(collection_id);633634 <NftItemList<T>>::remove_prefix(collection_id);635 <FungibleItemList<T>>::remove_prefix(collection_id);636 <ReFungibleItemList<T>>::remove_prefix(collection_id);637638 <NftTransferBasket<T>>::remove_prefix(collection_id);639 <FungibleTransferBasket<T>>::remove_prefix(collection_id);640 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);641642 if CollectionCount::get() > 0643 {644 // bound couter645 let total = CollectionCount::get()646 .checked_sub(1)647 .ok_or(Error::<T>::NumOverflow)?;648649 CollectionCount::put(total);650 }651652 Ok(())653 }654655 /// Add an address to white list.656 /// 657 /// # Permissions658 /// 659 /// * Collection Owner660 /// * Collection Admin661 /// 662 /// # Arguments663 /// 664 /// * collection_id.665 /// 666 /// * address.667 #[weight = T::WeightInfo::add_to_white_list()]668 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{669670 let sender = ensure_signed(origin)?;671 Self::check_owner_or_admin_permissions(collection_id, sender)?;672673 <WhiteList<T>>::insert(collection_id, address, true);674 675 Ok(())676 }677678 /// Remove an address from white list.679 /// 680 /// # Permissions681 /// 682 /// * Collection Owner683 /// * Collection Admin684 /// 685 /// # Arguments686 /// 687 /// * collection_id.688 /// 689 /// * address.690 #[weight = T::WeightInfo::remove_from_white_list()]691 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{692693 let sender = ensure_signed(origin)?;694 Self::check_owner_or_admin_permissions(collection_id, sender)?;695696 <WhiteList<T>>::remove(collection_id, address);697698 Ok(())699 }700701 /// Toggle between normal and white list access for the methods with access for `Anyone`.702 /// 703 /// # Permissions704 /// 705 /// * Collection Owner.706 /// 707 /// # Arguments708 /// 709 /// * collection_id.710 /// 711 /// * mode: [AccessMode]712 #[weight = T::WeightInfo::set_public_access_mode()]713 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult714 {715 let sender = ensure_signed(origin)?;716717 Self::check_owner_permissions(collection_id, sender)?;718 let mut target_collection = <Collection<T>>::get(collection_id);719 target_collection.access = mode;720 <Collection<T>>::insert(collection_id, target_collection);721722 Ok(())723 }724725 /// Allows Anyone to create tokens if:726 /// * White List is enabled, and727 /// * Address is added to white list, and728 /// * This method was called with True parameter729 /// 730 /// # Permissions731 /// * Collection Owner732 ///733 /// # Arguments734 /// 735 /// * collection_id.736 /// 737 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.738 #[weight = T::WeightInfo::set_mint_permission()]739 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult740 {741 let sender = ensure_signed(origin)?;742743 Self::check_owner_permissions(collection_id, sender)?;744 let mut target_collection = <Collection<T>>::get(collection_id);745 target_collection.mint_mode = mint_permission;746 <Collection<T>>::insert(collection_id, target_collection);747748 Ok(())749 }750751 /// Change the owner of the collection.752 /// 753 /// # Permissions754 /// 755 /// * Collection Owner.756 /// 757 /// # Arguments758 /// 759 /// * collection_id.760 /// 761 /// * new_owner.762 #[weight = T::WeightInfo::change_collection_owner()]763 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {764765 let sender = ensure_signed(origin)?;766 Self::check_owner_permissions(collection_id, sender)?;767 let mut target_collection = <Collection<T>>::get(collection_id);768 target_collection.owner = new_owner;769 <Collection<T>>::insert(collection_id, target_collection);770771 Ok(())772 }773774 /// Adds an admin of the Collection.775 /// 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. 776 /// 777 /// # Permissions778 /// 779 /// * Collection Owner.780 /// * Collection Admin.781 /// 782 /// # Arguments783 /// 784 /// * collection_id: ID of the Collection to add admin for.785 /// 786 /// * new_admin_id: Address of new admin to add.787 #[weight = T::WeightInfo::add_collection_admin()]788 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {789790 let sender = ensure_signed(origin)?;791 Self::check_owner_or_admin_permissions(collection_id, sender)?;792 let mut admin_arr: Vec<T::AccountId> = Vec::new();793794 if <AdminList<T>>::contains_key(collection_id)795 {796 admin_arr = <AdminList<T>>::get(collection_id);797 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);798 }799800 // Number of collection admins801 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);802803 admin_arr.push(new_admin_id);804 <AdminList<T>>::insert(collection_id, admin_arr);805806 Ok(())807 }808809 /// 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.810 ///811 /// # Permissions812 /// 813 /// * Collection Owner.814 /// * Collection Admin.815 /// 816 /// # Arguments817 /// 818 /// * collection_id: ID of the Collection to remove admin for.819 /// 820 /// * account_id: Address of admin to remove.821 #[weight = T::WeightInfo::remove_collection_admin()]822 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {823824 let sender = ensure_signed(origin)?;825 Self::check_owner_or_admin_permissions(collection_id, sender)?;826827 if <AdminList<T>>::contains_key(collection_id)828 {829 let mut admin_arr = <AdminList<T>>::get(collection_id);830 admin_arr.retain(|i| *i != account_id);831 <AdminList<T>>::insert(collection_id, admin_arr);832 }833834 Ok(())835 }836837 /// # Permissions838 /// 839 /// * Collection Owner840 /// 841 /// # Arguments842 /// 843 /// * collection_id.844 /// 845 /// * new_sponsor.846 #[weight = T::WeightInfo::set_collection_sponsor()]847 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {848849 let sender = ensure_signed(origin)?;850 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);851852 let mut target_collection = <Collection<T>>::get(collection_id);853 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);854855 target_collection.unconfirmed_sponsor = new_sponsor;856 <Collection<T>>::insert(collection_id, target_collection);857858 Ok(())859 }860861 /// # Permissions862 /// 863 /// * Sponsor.864 /// 865 /// # Arguments866 /// 867 /// * collection_id.868 #[weight = T::WeightInfo::confirm_sponsorship()]869 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {870871 let sender = ensure_signed(origin)?;872 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);873874 let mut target_collection = <Collection<T>>::get(collection_id);875 ensure!(sender == target_collection.unconfirmed_sponsor, Error::<T>::ConfirmUnsetSponsorFail);876877 target_collection.sponsor = target_collection.unconfirmed_sponsor;878 target_collection.unconfirmed_sponsor = T::AccountId::default();879 <Collection<T>>::insert(collection_id, target_collection);880881 Ok(())882 }883884 /// Switch back to pay-per-own-transaction model.885 ///886 /// # Permissions887 ///888 /// * Collection owner.889 /// 890 /// # Arguments891 /// 892 /// * collection_id.893 #[weight = T::WeightInfo::remove_collection_sponsor()]894 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {895896 let sender = ensure_signed(origin)?;897 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);898899 let mut target_collection = <Collection<T>>::get(collection_id);900 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);901902 target_collection.sponsor = T::AccountId::default();903 <Collection<T>>::insert(collection_id, target_collection);904905 Ok(())906 }907908 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.909 /// 910 /// # Permissions911 /// 912 /// * Collection Owner.913 /// * Collection Admin.914 /// * Anyone if915 /// * White List is enabled, and916 /// * Address is added to white list, and917 /// * MintPermission is enabled (see SetMintPermission method)918 /// 919 /// # Arguments920 /// 921 /// * collection_id: ID of the collection.922 /// 923 /// * owner: Address, initial owner of the NFT.924 ///925 /// * data: Token data to store on chain.926 // #[weight =927 // (130_000_000 as Weight)928 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))929 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))930 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]931932 #[weight = T::WeightInfo::create_item(data.len())]933 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {934935 let sender = ensure_signed(origin)?;936937 Self::collection_exists(collection_id)?;938939 let target_collection = <Collection<T>>::get(collection_id);940941 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;942 Self::validate_create_item_args(&target_collection, &data)?;943 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;944945 Ok(())946 }947948 /// This method creates multiple instances of NFT Collection created with CreateCollection method.949 /// 950 /// # Permissions951 /// 952 /// * Collection Owner.953 /// * Collection Admin.954 /// * Anyone if955 /// * White List is enabled, and956 /// * Address is added to white list, and957 /// * MintPermission is enabled (see SetMintPermission method)958 /// 959 /// # Arguments960 /// 961 /// * collection_id: ID of the collection.962 /// 963 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].964 /// 965 /// * owner: Address, initial owner of the NFT.966 #[weight = T::WeightInfo::create_item(items_data.into_iter()967 .map(|data| { data.len() })968 .sum())]969 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {970971 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);972 let sender = ensure_signed(origin)?;973974 Self::collection_exists(collection_id)?;975 let target_collection = <Collection<T>>::get(collection_id);976977 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;978979 for data in &items_data {980 Self::validate_create_item_args(&target_collection, data)?;981 }982 for data in &items_data {983 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;984 }985986 Ok(())987 }988989 /// Destroys a concrete instance of NFT.990 /// 991 /// # Permissions992 /// 993 /// * Collection Owner.994 /// * Collection Admin.995 /// * Current NFT Owner.996 /// 997 /// # Arguments998 /// 999 /// * collection_id: ID of the collection.1000 /// 1001 /// * item_id: ID of NFT to burn.1002 #[weight = T::WeightInfo::burn_item()]1003 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {10041005 let sender = ensure_signed(origin)?;1006 Self::collection_exists(collection_id)?;10071008 // Transfer permissions check1009 let target_collection = <Collection<T>>::get(collection_id);1010 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1011 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1012 Error::<T>::NoPermission);10131014 if target_collection.access == AccessMode::WhiteList {1015 Self::check_white_list(collection_id, &sender)?;1016 }10171018 match target_collection.mode1019 {1020 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1021 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,1022 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1023 _ => ()1024 };10251026 // call event1027 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10281029 Ok(())1030 }10311032 /// Change ownership of the token.1033 /// 1034 /// # Permissions1035 /// 1036 /// * Collection Owner1037 /// * Collection Admin1038 /// * Current NFT owner1039 ///1040 /// # Arguments1041 /// 1042 /// * recipient: Address of token recipient.1043 /// 1044 /// * collection_id.1045 /// 1046 /// * item_id: ID of the item1047 /// * Non-Fungible Mode: Required.1048 /// * Fungible Mode: Ignored.1049 /// * Re-Fungible Mode: Required.1050 /// 1051 /// * value: Amount to transfer.1052 /// * Non-Fungible Mode: Ignored1053 /// * Fungible Mode: Must specify transferred amount1054 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1055 #[weight = T::WeightInfo::transfer()]1056 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10571058 let sender = ensure_signed(origin)?;1059 let target_collection = <Collection<T>>::get(collection_id);10601061 // Limits check1062 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10631064 // Transfer permissions check1065 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1066 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1067 Error::<T>::NoPermission);10681069 if target_collection.access == AccessMode::WhiteList {1070 Self::check_white_list(collection_id, &sender)?;1071 Self::check_white_list(collection_id, &recipient)?;1072 }10731074 match target_collection.mode1075 {1076 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1077 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1078 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1079 _ => ()1080 };10811082 Ok(())1083 }10841085 /// Set, change, or remove approved address to transfer the ownership of the NFT.1086 /// 1087 /// # Permissions1088 /// 1089 /// * Collection Owner1090 /// * Collection Admin1091 /// * Current NFT owner1092 /// 1093 /// # Arguments1094 /// 1095 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1096 /// 1097 /// * collection_id.1098 /// 1099 /// * item_id: ID of the item.1100 #[weight = T::WeightInfo::approve()]1101 pub fn approve(origin, approved: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {11021103 let sender = ensure_signed(origin)?;11041105 // Transfer permissions check1106 let target_collection = <Collection<T>>::get(collection_id);1107 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1108 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1109 Error::<T>::NoPermission);11101111 if target_collection.access == AccessMode::WhiteList {1112 Self::check_white_list(collection_id, &sender)?;1113 Self::check_white_list(collection_id, &approved)?;1114 }11151116 // amount param stub1117 let amount = 100000000;11181119 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1120 if list_exists {11211122 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1123 let item_contains = list.iter().any(|i| i.approved == approved);11241125 if !item_contains {1126 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1127 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1128 }1129 } else {11301131 let mut list = Vec::new();1132 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1133 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1134 }11351136 Ok(())1137 }1138 1139 /// 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.1140 /// 1141 /// # Permissions1142 /// * Collection Owner1143 /// * Collection Admin1144 /// * Current NFT owner1145 /// * Address approved by current NFT owner1146 /// 1147 /// # Arguments1148 /// 1149 /// * from: Address that owns token.1150 /// 1151 /// * recipient: Address of token recipient.1152 /// 1153 /// * collection_id.1154 /// 1155 /// * item_id: ID of the item.1156 /// 1157 /// * value: Amount to transfer.1158 #[weight = T::WeightInfo::transfer_from()]1159 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11601161 let sender = ensure_signed(origin)?;1162 let mut appoved_transfer = false;11631164 // Check approve1165 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1166 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1167 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1168 if opt_item.is_some()1169 {1170 appoved_transfer = true;1171 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1172 }1173 }11741175 let target_collection = <Collection<T>>::get(collection_id);11761177 // Limits check1178 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11791180 // Transfer permissions check 1181 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1182 Error::<T>::NoPermission);11831184 if target_collection.access == AccessMode::WhiteList {1185 Self::check_white_list(collection_id, &sender)?;1186 Self::check_white_list(collection_id, &recipient)?;1187 }11881189 // remove approve1190 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1191 .into_iter().filter(|i| i.approved != sender.clone()).collect();1192 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);119311941195 match target_collection.mode1196 {1197 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1198 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1199 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1200 _ => ()1201 };12021203 Ok(())1204 }12051206 #[weight = 0]1207 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {12081209 // let no_perm_mes = "You do not have permissions to modify this collection";1210 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1211 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1212 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);12131214 // // on_nft_received call12151216 // Self::transfer(origin, collection_id, item_id, new_owner)?;12171218 Ok(())1219 }12201221 /// Set off-chain data schema.1222 /// 1223 /// # Permissions1224 /// 1225 /// * Collection Owner1226 /// * Collection Admin1227 /// 1228 /// # Arguments1229 /// 1230 /// * collection_id.1231 /// 1232 /// * schema: String representing the offchain data schema.1233 #[weight = T::WeightInfo::set_variable_meta_data()]1234 pub fn set_variable_meta_data (1235 origin,1236 collection_id: CollectionId,1237 item_id: TokenId,1238 data: Vec<u8>1239 ) -> DispatchResult {1240 let sender = ensure_signed(origin)?;1241 1242 Self::collection_exists(collection_id)?;1243 1244 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12451246 // Modify permissions check1247 let target_collection = <Collection<T>>::get(collection_id);1248 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1249 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1250 Error::<T>::NoPermission);12511252 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12531254 match target_collection.mode1255 {1256 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1257 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1258 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1259 _ => fail!(Error::<T>::UnexpectedCollectionType)1260 };12611262 Ok(())1263 }1264 1265 /// Set schema standard1266 /// ImageURL1267 /// Unique1268 /// 1269 /// # Permissions1270 /// 1271 /// * Collection Owner1272 /// * Collection Admin1273 /// 1274 /// # Arguments1275 /// 1276 /// * collection_id.1277 /// 1278 /// * schema: SchemaVersion: enum1279 #[weight = 0]1280 pub fn set_schema_version(1281 origin,1282 collection_id: CollectionId,1283 version: SchemaVersion1284 ) -> DispatchResult {1285 let sender = ensure_signed(origin)?;1286 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1287 let mut target_collection = <Collection<T>>::get(collection_id);1288 target_collection.schema_version = version;1289 <Collection<T>>::insert(collection_id, target_collection);12901291 Ok(())1292 }12931294 /// Set off-chain data schema.1295 /// 1296 /// # Permissions1297 /// 1298 /// * Collection Owner1299 /// * Collection Admin1300 /// 1301 /// # Arguments1302 /// 1303 /// * collection_id.1304 /// 1305 /// * schema: String representing the offchain data schema.1306 #[weight = T::WeightInfo::set_offchain_schema()]1307 pub fn set_offchain_schema(1308 origin,1309 collection_id: CollectionId,1310 schema: Vec<u8>1311 ) -> DispatchResult {1312 let sender = ensure_signed(origin)?;1313 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13141315 let mut target_collection = <Collection<T>>::get(collection_id);1316 target_collection.offchain_schema = schema;1317 <Collection<T>>::insert(collection_id, target_collection);13181319 Ok(())1320 }13211322 /// Set const on-chain data schema.1323 /// 1324 /// # Permissions1325 /// 1326 /// * Collection Owner1327 /// * Collection Admin1328 /// 1329 /// # Arguments1330 /// 1331 /// * collection_id.1332 /// 1333 /// * schema: String representing the const on-chain data schema.1334 #[weight = T::WeightInfo::set_const_on_chain_schema()]1335 pub fn set_const_on_chain_schema (1336 origin,1337 collection_id: CollectionId,1338 schema: Vec<u8>1339 ) -> DispatchResult {1340 let sender = ensure_signed(origin)?;1341 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13421343 let mut target_collection = <Collection<T>>::get(collection_id);1344 target_collection.const_on_chain_schema = schema;1345 <Collection<T>>::insert(collection_id, target_collection);13461347 Ok(())1348 }13491350 /// Set variable on-chain data schema.1351 /// 1352 /// # Permissions1353 /// 1354 /// * Collection Owner1355 /// * Collection Admin1356 /// 1357 /// # Arguments1358 /// 1359 /// * collection_id.1360 /// 1361 /// * schema: String representing the variable on-chain data schema.1362 #[weight = T::WeightInfo::set_const_on_chain_schema()]1363 pub fn set_variable_on_chain_schema (1364 origin,1365 collection_id: CollectionId,1366 schema: Vec<u8>1367 ) -> DispatchResult {1368 let sender = ensure_signed(origin)?;1369 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13701371 let mut target_collection = <Collection<T>>::get(collection_id);1372 target_collection.variable_on_chain_schema = schema;1373 <Collection<T>>::insert(collection_id, target_collection);13741375 Ok(())1376 }13771378 // Sudo permissions function1379 #[weight = 0]1380 pub fn set_chain_limits(1381 origin,1382 limits: ChainLimits1383 ) -> DispatchResult {1384 ensure_root(origin)?;1385 <ChainLimit>::put(limits);1386 Ok(())1387 }13881389 /// Enable smart contract self-sponsoring.1390 /// 1391 /// # Permissions1392 /// 1393 /// * Contract Owner1394 /// 1395 /// # Arguments1396 /// 1397 /// * contract address1398 /// * enable flag1399 /// 1400 #[weight = T::WeightInfo::enable_contract_sponsoring()]1401 pub fn enable_contract_sponsoring(1402 origin,1403 contract_address: T::AccountId,1404 enable: bool1405 ) -> DispatchResult {14061407 let sender = ensure_signed(origin)?;14081409 #[cfg(feature = "runtime-benchmarks")]1410 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14111412 Self::ensure_contract_owned(sender, &contract_address)?;14131414 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1415 Ok(())1416 }14171418 /// Set the rate limit for contract sponsoring to specified number of blocks.1419 /// 1420 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1421 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1422 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1423 /// from contract endowment if there are at least B blocks between such transactions. 1424 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1425 /// 1426 /// # Permissions1427 /// 1428 /// * Contract Owner1429 /// 1430 /// # Arguments1431 /// 1432 /// -`contract_address`: Address of the contract to sponsor1433 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1434 /// 1435 #[weight = 0]1436 pub fn set_contract_sponsoring_rate_limit(1437 origin,1438 contract_address: T::AccountId,1439 rate_limit: T::BlockNumber1440 ) -> DispatchResult {1441 let sender = ensure_signed(origin)?;1442 Self::ensure_contract_owned(sender, &contract_address)?;14431444 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1445 Ok(())1446 }14471448 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1449 /// 1450 /// # Permissions1451 /// 1452 /// * Address that deployed smart contract.1453 /// 1454 /// # Arguments1455 /// 1456 /// -`contract_address`: Address of the contract.1457 /// 1458 /// - `enable`: . 1459 #[weight = 0]1460 pub fn toggle_contract_white_list(1461 origin,1462 contract_address: T::AccountId,1463 enable: bool1464 ) -> DispatchResult {1465 let sender = ensure_signed(origin)?;1466 Self::ensure_contract_owned(sender, &contract_address)?;14671468 <ContractWhiteListEnabled<T>>::insert(contract_address, enable);1469 Ok(())1470 }1471 1472 /// Add an address to smart contract white list.1473 /// 1474 /// # Permissions1475 /// 1476 /// * Address that deployed smart contract.1477 /// 1478 /// # Arguments1479 /// 1480 /// -`contract_address`: Address of the contract.1481 ///1482 /// -`account_address`: Address to add.1483 #[weight = 0]1484 pub fn add_to_contract_white_list(1485 origin,1486 contract_address: T::AccountId,1487 account_address: T::AccountId1488 ) -> DispatchResult {1489 let sender = ensure_signed(origin)?;1490 Self::ensure_contract_owned(sender, &contract_address)?;1491 1492 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1493 Ok(())1494 }14951496 /// Remove an address from smart contract white list.1497 /// 1498 /// # Permissions1499 /// 1500 /// * Address that deployed smart contract.1501 /// 1502 /// # Arguments1503 /// 1504 /// -`contract_address`: Address of the contract.1505 ///1506 /// -`account_address`: Address to remove.1507 #[weight = 0]1508 pub fn remove_from_contract_white_list(1509 origin,1510 contract_address: T::AccountId,1511 account_address: T::AccountId1512 ) -> DispatchResult {1513 let sender = ensure_signed(origin)?;1514 Self::ensure_contract_owned(sender, &contract_address)?;1515 1516 <ContractWhiteList<T>>::remove(contract_address, account_address);1517 Ok(())1518 }15191520 #[weight = 0]1521 pub fn set_collection_limits(1522 origin,1523 collection_id: u32,1524 limits: CollectionLimits,1525 ) -> DispatchResult {1526 let sender = ensure_signed(origin)?;1527 Self::check_owner_permissions(collection_id, sender.clone())?;1528 let mut target_collection = <Collection<T>>::get(collection_id);1529 let chain_limits = ChainLimit::get();1530 let climits = target_collection.limits;15311532 // collection bounds1533 ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1534 limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP, 1535 Error::<T>::CollectionLimitBoundsExceeded);15361537 // token_limit check prev1538 ensure!(climits.token_limit > limits.token_limit && 1539 limits.token_limit <= chain_limits.account_token_ownership_limit, 1540 Error::<T>::AccountTokenLimitExceeded);15411542 target_collection.limits = limits;1543 <Collection<T>>::insert(collection_id, target_collection);15441545 Ok(())1546 } 1547 }1548}15491550impl<T: Trait> Module<T> {15511552 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15531554 // check token limit and account token limit1555 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1556 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1557 1558 Ok(())1559 }15601561 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15621563 // check token limit and account token limit1564 let total_items: u32 = ItemListIndex::get(collection_id);1565 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1566 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1567 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);15681569 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1570 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1571 Self::check_white_list(collection_id, owner)?;1572 Self::check_white_list(collection_id, sender)?;1573 }15741575 Ok(())1576 }15771578 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1579 match target_collection.mode1580 {1581 CollectionMode::NFT => {1582 if let CreateItemData::NFT(data) = data {1583 // check sizes1584 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1585 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1586 } else {1587 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1588 }1589 },1590 CollectionMode::Fungible(_) => {1591 if let CreateItemData::Fungible(_) = data {1592 } else {1593 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1594 }1595 },1596 CollectionMode::ReFungible(_) => {1597 if let CreateItemData::ReFungible(data) = data {15981599 // check sizes1600 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1601 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1602 } else {1603 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1604 }1605 },1606 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1607 };16081609 Ok(())1610 }16111612 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1613 match data1614 {1615 CreateItemData::NFT(data) => {1616 let item = NftItemType {1617 collection: collection_id,1618 owner,1619 const_data: data.const_data,1620 variable_data: data.variable_data1621 };16221623 Self::add_nft_item(item)?;1624 },1625 CreateItemData::Fungible(_) => {1626 let item = FungibleItemType {1627 collection: collection_id,1628 owner,1629 value: (10 as u128).pow(collection.decimal_points as u32)1630 };16311632 Self::add_fungible_item(item)?;1633 },1634 CreateItemData::ReFungible(data) => {1635 let mut owner_list = Vec::new();1636 let value = (10 as u128).pow(collection.decimal_points as u32);1637 owner_list.push(Ownership {owner: owner.clone(), fraction: value});16381639 let item = ReFungibleItemType {1640 collection: collection_id,1641 owner: owner_list,1642 const_data: data.const_data,1643 variable_data: data.variable_data1644 };16451646 Self::add_refungible_item(item)?;1647 }1648 };16491650 // call event1651 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));16521653 Ok(())1654 }16551656 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1657 let current_index = <ItemListIndex>::get(item.collection)1658 .checked_add(1)1659 .ok_or(Error::<T>::NumOverflow)?;1660 let itemcopy = item.clone();1661 let owner = item.owner.clone();16621663 Self::add_token_index(item.collection, current_index, owner.clone())?;16641665 <ItemListIndex>::insert(item.collection, current_index);1666 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);16671668 // Add current block1669 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1670 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1671 1672 // Update balance1673 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1674 .checked_add(item.value)1675 .ok_or(Error::<T>::NumOverflow)?;1676 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);16771678 Ok(())1679 }16801681 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1682 let current_index = <ItemListIndex>::get(item.collection)1683 .checked_add(1)1684 .ok_or(Error::<T>::NumOverflow)?;1685 let itemcopy = item.clone();16861687 let value = item.owner.first().unwrap().fraction;1688 let owner = item.owner.first().unwrap().owner.clone();16891690 Self::add_token_index(item.collection, current_index, owner.clone())?;16911692 <ItemListIndex>::insert(item.collection, current_index);1693 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);16941695 // Add current block1696 let block_number: T::BlockNumber = 0.into();1697 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);16981699 // Update balance1700 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1701 .checked_add(value)1702 .ok_or(Error::<T>::NumOverflow)?;1703 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);17041705 Ok(())1706 }17071708 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1709 let current_index = <ItemListIndex>::get(item.collection)1710 .checked_add(1)1711 .ok_or(Error::<T>::NumOverflow)?;17121713 let item_owner = item.owner.clone();1714 let collection_id = item.collection.clone();1715 Self::add_token_index(collection_id, current_index, item.owner.clone())?;17161717 <ItemListIndex>::insert(collection_id, current_index);1718 <NftItemList<T>>::insert(collection_id, current_index, item);17191720 // Add current block1721 let block_number: T::BlockNumber = 0.into();1722 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);17231724 // Update balance1725 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1726 .checked_add(1)1727 .ok_or(Error::<T>::NumOverflow)?;1728 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);17291730 Ok(())1731 }17321733 fn burn_refungible_item(1734 collection_id: CollectionId,1735 item_id: TokenId,1736 owner: T::AccountId,1737 ) -> DispatchResult {1738 ensure!(1739 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1740 Error::<T>::TokenNotFound1741 );1742 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1743 let item = collection1744 .owner1745 .iter()1746 .filter(|&i| i.owner == owner)1747 .next()1748 .unwrap();1749 Self::remove_token_index(collection_id, item_id, owner.clone())?;17501751 // remove approve list1752 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));17531754 // update balance1755 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1756 .checked_sub(item.fraction)1757 .ok_or(Error::<T>::NumOverflow)?;1758 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17591760 <ReFungibleItemList<T>>::remove(collection_id, item_id);17611762 Ok(())1763 }17641765 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1766 ensure!(1767 <NftItemList<T>>::contains_key(collection_id, item_id),1768 Error::<T>::TokenNotFound1769 );1770 let item = <NftItemList<T>>::get(collection_id, item_id);1771 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17721773 // remove approve list1774 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17751776 // update balance1777 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1778 .checked_sub(1)1779 .ok_or(Error::<T>::NumOverflow)?;1780 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1781 <NftItemList<T>>::remove(collection_id, item_id);17821783 Ok(())1784 }17851786 fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1787 ensure!(1788 <FungibleItemList<T>>::contains_key(collection_id, item_id),1789 Error::<T>::TokenNotFound1790 );1791 let item = <FungibleItemList<T>>::get(collection_id, item_id);1792 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17931794 // remove approve list1795 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17961797 // update balance1798 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1799 .checked_sub(item.value)1800 .ok_or(Error::<T>::NumOverflow)?;1801 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);18021803 <FungibleItemList<T>>::remove(collection_id, item_id);18041805 Ok(())1806 }18071808 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1809 ensure!(1810 <Collection<T>>::contains_key(collection_id),1811 Error::<T>::CollectionNotFound1812 );1813 Ok(())1814 }18151816 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1817 Self::collection_exists(collection_id)?;18181819 let target_collection = <Collection<T>>::get(collection_id);1820 ensure!(1821 subject == target_collection.owner,1822 Error::<T>::NoPermission1823 );18241825 Ok(())1826 }18271828 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1829 let target_collection = <Collection<T>>::get(collection_id);1830 let mut result: bool = subject == target_collection.owner;1831 let exists = <AdminList<T>>::contains_key(collection_id);18321833 if !result & exists {1834 if <AdminList<T>>::get(collection_id).contains(&subject) {1835 result = true1836 }1837 }18381839 result1840 }18411842 fn check_owner_or_admin_permissions(1843 collection_id: CollectionId,1844 subject: T::AccountId,1845 ) -> DispatchResult {1846 Self::collection_exists(collection_id)?;1847 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());18481849 ensure!(1850 result,1851 Error::<T>::NoPermission1852 );1853 Ok(())1854 }18551856 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1857 let target_collection = <Collection<T>>::get(collection_id);18581859 match target_collection.mode {1860 CollectionMode::NFT => {1861 <NftItemList<T>>::get(collection_id, item_id).owner == subject1862 }1863 CollectionMode::Fungible(_) => {1864 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1865 }1866 CollectionMode::ReFungible(_) => {1867 <ReFungibleItemList<T>>::get(collection_id, item_id)1868 .owner1869 .iter()1870 .any(|i| i.owner == subject)1871 }1872 CollectionMode::Invalid => false,1873 }1874 }18751876 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1877 let mes = Error::<T>::AddresNotInWhiteList;1878 ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);18791880 Ok(())1881 }18821883 fn transfer_fungible(1884 collection_id: CollectionId,1885 item_id: TokenId,1886 value: u128,1887 owner: T::AccountId,1888 new_owner: T::AccountId,1889 ) -> DispatchResult {1890 ensure!(1891 <FungibleItemList<T>>::contains_key(collection_id, item_id),1892 Error::<T>::TokenNotFound1893 );18941895 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1896 let amount = full_item.value;18971898 ensure!(amount >= value, Error::<T>::TokenValueTooLow);18991900 // update balance1901 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1902 .checked_sub(value)1903 .ok_or(Error::<T>::NumOverflow)?;1904 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);19051906 let mut new_owner_account_id = 0;1907 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1908 if new_owner_items.len() > 0 {1909 new_owner_account_id = new_owner_items[0];1910 }19111912 // transfer1913 if amount == value && new_owner_account_id == 0 {1914 // change owner1915 // new owner do not have account1916 let mut new_full_item = full_item.clone();1917 new_full_item.owner = new_owner.clone();1918 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19191920 // update balance1921 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1922 .checked_add(value)1923 .ok_or(Error::<T>::NumOverflow)?;1924 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19251926 // update index collection1927 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1928 } else {1929 let mut new_full_item = full_item.clone();1930 new_full_item.value -= value;19311932 // separate amount1933 if new_owner_account_id > 0 {1934 // new owner has account1935 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1936 item.value += value;19371938 // update balance1939 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1940 .checked_add(value)1941 .ok_or(Error::<T>::NumOverflow)?;1942 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19431944 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1945 } else {1946 // new owner do not have account1947 let item = FungibleItemType {1948 collection: collection_id,1949 owner: new_owner.clone(),1950 value1951 };19521953 Self::add_fungible_item(item)?;1954 }19551956 if amount == value {1957 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;19581959 // remove approve list1960 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1961 <FungibleItemList<T>>::remove(collection_id, item_id);1962 }19631964 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1965 }19661967 Ok(())1968 }19691970 fn transfer_refungible(1971 collection_id: CollectionId,1972 item_id: TokenId,1973 value: u128,1974 owner: T::AccountId,1975 new_owner: T::AccountId,1976 ) -> DispatchResult {1977 ensure!(1978 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1979 Error::<T>::TokenNotFound1980 );19811982 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1983 let item = full_item1984 .owner1985 .iter()1986 .filter(|i| i.owner == owner)1987 .next()1988 .ok_or(Error::<T>::NumOverflow)?;1989 let amount = item.fraction;19901991 ensure!(amount >= value, Error::<T>::TokenValueTooLow);19921993 // update balance1994 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1995 .checked_sub(value)1996 .ok_or(Error::<T>::NumOverflow)?;1997 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19981999 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())2000 .checked_add(value)2001 .ok_or(Error::<T>::NumOverflow)?;2002 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);20032004 let old_owner = item.owner.clone();2005 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);20062007 // transfer2008 if amount == value && !new_owner_has_account {2009 // change owner2010 // new owner do not have account2011 let mut new_full_item = full_item.clone();2012 new_full_item2013 .owner2014 .iter_mut()2015 .find(|i| i.owner == owner)2016 .unwrap()2017 .owner = new_owner.clone();2018 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);20192020 // update index collection2021 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;2022 } else {2023 let mut new_full_item = full_item.clone();2024 new_full_item2025 .owner2026 .iter_mut()2027 .find(|i| i.owner == owner)2028 .unwrap()2029 .fraction -= value;20302031 // separate amount2032 if new_owner_has_account {2033 // new owner has account2034 new_full_item2035 .owner2036 .iter_mut()2037 .find(|i| i.owner == new_owner)2038 .unwrap()2039 .fraction += value;2040 } else {2041 // new owner do not have account2042 new_full_item.owner.push(Ownership {2043 owner: new_owner.clone(),2044 fraction: value,2045 });2046 Self::add_token_index(collection_id, item_id, new_owner.clone())?;2047 }20482049 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2050 }20512052 Ok(())2053 }20542055 fn transfer_nft(2056 collection_id: CollectionId,2057 item_id: TokenId,2058 sender: T::AccountId,2059 new_owner: T::AccountId,2060 ) -> DispatchResult {2061 ensure!(2062 <NftItemList<T>>::contains_key(collection_id, item_id),2063 Error::<T>::TokenNotFound2064 );20652066 let mut item = <NftItemList<T>>::get(collection_id, item_id);20672068 ensure!(2069 sender == item.owner,2070 Error::<T>::MustBeTokenOwner2071 );20722073 // update balance2074 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2075 .checked_sub(1)2076 .ok_or(Error::<T>::NumOverflow)?;2077 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20782079 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())2080 .checked_add(1)2081 .ok_or(Error::<T>::NumOverflow)?;2082 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);20832084 // change owner2085 let old_owner = item.owner.clone();2086 item.owner = new_owner.clone();2087 <NftItemList<T>>::insert(collection_id, item_id, item);20882089 // update index collection2090 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;20912092 // reset approved list2093 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));2094 Ok(())2095 }2096 2097 fn item_exists(2098 collection_id: CollectionId,2099 item_id: TokenId,2100 mode: &CollectionMode2101 ) -> DispatchResult {2102 match mode {2103 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2104 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2105 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2106 _ => ()2107 };2108 2109 Ok(())2110 }21112112 fn set_re_fungible_variable_data(2113 collection_id: CollectionId,2114 item_id: TokenId,2115 data: Vec<u8>2116 ) -> DispatchResult {2117 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);21182119 item.variable_data = data;21202121 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);21222123 Ok(())2124 }21252126 fn set_nft_variable_data(2127 collection_id: CollectionId,2128 item_id: TokenId,2129 data: Vec<u8>2130 ) -> DispatchResult {2131 let mut item = <NftItemList<T>>::get(collection_id, item_id);2132 2133 item.variable_data = data;21342135 <NftItemList<T>>::insert(collection_id, item_id, item);2136 2137 Ok(())2138 }21392140 fn init_collection(item: &CollectionType<T::AccountId>) {2141 // check params2142 assert!(2143 item.decimal_points <= MAX_DECIMAL_POINTS,2144 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2145 );2146 assert!(2147 item.name.len() <= 64,2148 "Collection name can not be longer than 63 char"2149 );2150 assert!(2151 item.name.len() <= 256,2152 "Collection description can not be longer than 255 char"2153 );2154 assert!(2155 item.token_prefix.len() <= 16,2156 "Token prefix can not be longer than 15 char"2157 );21582159 // Generate next collection ID2160 let next_id = CreatedCollectionCount::get()2161 .checked_add(1)2162 .unwrap();21632164 CreatedCollectionCount::put(next_id);2165 }21662167 fn init_nft_token(item: &NftItemType<T::AccountId>) {2168 let current_index = <ItemListIndex>::get(item.collection)2169 .checked_add(1)2170 .unwrap();21712172 let item_owner = item.owner.clone();2173 let collection_id = item.collection.clone();2174 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();21752176 <ItemListIndex>::insert(collection_id, current_index);21772178 // Update balance2179 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2180 .checked_add(1)2181 .unwrap();2182 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2183 }21842185 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {2186 let current_index = <ItemListIndex>::get(item.collection)2187 .checked_add(1)2188 .unwrap();2189 let owner = item.owner.clone();21902191 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();21922193 <ItemListIndex>::insert(item.collection, current_index);21942195 // Update balance2196 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2197 .checked_add(item.value)2198 .unwrap();2199 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2200 }22012202 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {2203 let current_index = <ItemListIndex>::get(item.collection)2204 .checked_add(1)2205 .unwrap();22062207 let value = item.owner.first().unwrap().fraction;2208 let owner = item.owner.first().unwrap().owner.clone();22092210 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();22112212 <ItemListIndex>::insert(item.collection, current_index);22132214 // Update balance2215 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2216 .checked_add(value)2217 .unwrap();2218 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2219 }22202221 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {22222223 // add to account limit2224 if <AccountItemCount<T>>::contains_key(owner.clone()) {22252226 // bound Owned tokens by a single address2227 let count = <AccountItemCount<T>>::get(owner.clone());2228 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);22292230 <AccountItemCount<T>>::insert(owner.clone(), count2231 .checked_add(1)2232 .ok_or(Error::<T>::NumOverflow)?);2233 }2234 else {2235 <AccountItemCount<T>>::insert(owner.clone(), 1);2236 }22372238 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2239 if list_exists {2240 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2241 let item_contains = list.contains(&item_index.clone());22422243 if !item_contains {2244 list.push(item_index.clone());2245 }22462247 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2248 } else {2249 let mut itm = Vec::new();2250 itm.push(item_index.clone());2251 <AddressTokens<T>>::insert(collection_id, owner, itm);2252 2253 }22542255 Ok(())2256 }22572258 fn remove_token_index(2259 collection_id: CollectionId,2260 item_index: TokenId,2261 owner: T::AccountId,2262 ) -> DispatchResult {22632264 // update counter2265 <AccountItemCount<T>>::insert(owner.clone(), 2266 <AccountItemCount<T>>::get(owner.clone())2267 .checked_sub(1)2268 .ok_or(Error::<T>::NumOverflow)?);226922702271 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2272 if list_exists {2273 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2274 let item_contains = list.contains(&item_index.clone());22752276 if item_contains {2277 list.retain(|&item| item != item_index);2278 <AddressTokens<T>>::insert(collection_id, owner, list);2279 }2280 }22812282 Ok(())2283 }22842285 fn move_token_index(2286 collection_id: CollectionId,2287 item_index: TokenId,2288 old_owner: T::AccountId,2289 new_owner: T::AccountId,2290 ) -> DispatchResult {2291 Self::remove_token_index(collection_id, item_index, old_owner)?;2292 Self::add_token_index(collection_id, item_index, new_owner)?;22932294 Ok(())2295 }2296 2297 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2298 if <ContractOwner<T>>::contains_key(contract.clone()) {2299 let owner = <ContractOwner<T>>::get(contract);2300 ensure!(account == owner, Error::<T>::NoPermission);2301 } else {2302 fail!(Error::<T>::NoPermission);2303 }23042305 Ok(())2306 }2307}23082309////////////////////////////////////////////////////////////////////////////////////////////////////2310// Economic models2311// #region23122313/// Fee multiplier.2314pub type Multiplier = FixedU128;23152316type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2317 <T as system::Trait>::AccountId,2318>>::Balance;2319type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2320 <T as system::Trait>::AccountId,2321>>::NegativeImbalance;23222323/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2324/// in the queue.2325#[derive(Encode, Decode, Clone, Eq, PartialEq)]2326pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2327 #[codec(compact)] BalanceOf<T>2328);23292330impl<T: Trait + Send + Sync> sp_std::fmt::Debug2331 for ChargeTransactionPayment<T>2332{2333 #[cfg(feature = "std")]2334 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2335 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2336 }2337 #[cfg(not(feature = "std"))]2338 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2339 Ok(())2340 }2341}23422343impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2344where2345 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2346 BalanceOf<T>: Send + Sync + FixedPointOperand,2347{2348 /// utility constructor. Used only in client/factory code.2349 pub fn from(fee: BalanceOf<T>) -> Self {2350 Self(fee)2351 }23522353 pub fn traditional_fee(2354 len: usize,2355 info: &DispatchInfoOf<T::Call>,2356 tip: BalanceOf<T>,2357 ) -> BalanceOf<T>2358 where2359 T::Call: Dispatchable<Info = DispatchInfo>,2360 {2361 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2362 }23632364 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2365 let weight_saturation = T::MaximumBlockWeight::get() / info.weight.max(1);2366 let len_saturation = T::MaximumBlockLength::get() as u64 / (len as u64).max(1);2367 let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();2368 final_fee.saturating_mul(coefficient).saturated_into::<TransactionPriority>()2369 }23702371 fn withdraw_fee(2372 &self,2373 who: &T::AccountId,2374 call: &T::Call,2375 info: &DispatchInfoOf<T::Call>,2376 len: usize,2377 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2378 let tip = self.0;23792380 // Set fee based on call type. Creating collection costs 1 Unique.2381 // All other transactions have traditional fees so far2382 // let fee = match call.is_sub_type() {2383 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2384 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2385 // // _ => <BalanceOf<T>>::from(100)2386 // };2387 let fee = Self::traditional_fee(len, info, tip);23882389 // Determine who is paying transaction fee based on ecnomic model2390 // Parse call to extract collection ID and access collection sponsor2391 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2392 Some(Call::create_item(collection_id, _owner, _properties)) => {23932394 // check free create limit2395 if <Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)2396 {2397 <Collection<T>>::get(collection_id).sponsor2398 } else {2399 T::AccountId::default()2400 }2401 }2402 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2403 2404 let _collection_limits = <Collection<T>>::get(collection_id).limits;2405 let _collection_mode = <Collection<T>>::get(collection_id).mode;24062407 // sponsor timeout2408 let sponsor_transfer = match _collection_mode {2409 CollectionMode::NFT => {24102411 // get correct limit2412 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2413 _collection_limits.sponsor_transfer_timeout2414 } else {2415 ChainLimit::get().nft_sponsor_transfer_timeout2416 };24172418 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2419 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2420 let limit_time = basket + limit.into();2421 if block_number >= limit_time {2422 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2423 true2424 }2425 else {2426 false2427 }2428 }2429 CollectionMode::Fungible(_) => {24302431 // get correct limit2432 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2433 _collection_limits.sponsor_transfer_timeout2434 } else {2435 ChainLimit::get().fungible_sponsor_transfer_timeout2436 };24372438 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2439 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2440 if basket.iter().any(|i| i.address == _new_owner.clone())2441 {2442 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2443 let limit_time = item.start_block + limit.into();2444 if block_number >= limit_time {2445 basket.retain(|x| x.address == item.address);2446 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2447 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2448 true2449 }2450 else {2451 false2452 }2453 }2454 else {2455 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2456 true2457 }2458 }2459 CollectionMode::ReFungible(_) => {24602461 // get correct limit2462 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2463 _collection_limits.sponsor_transfer_timeout2464 } else {2465 ChainLimit::get().refungible_sponsor_transfer_timeout2466 };24672468 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2469 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2470 let limit_time = basket + limit.into();2471 if block_number >= limit_time {2472 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2473 true2474 } else {2475 false2476 }2477 }2478 _ => {2479 false2480 },2481 };24822483 if !sponsor_transfer {2484 T::AccountId::default()2485 } else {2486 <Collection<T>>::get(collection_id).sponsor2487 }2488 }24892490 _ => T::AccountId::default(),2491 };24922493 // Sponsor smart contracts2494 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {24952496 // On instantiation: set the contract owner2497 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {24982499 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2500 code_hash,2501 &data,2502 &who,2503 );2504 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());25052506 T::AccountId::default()2507 },25082509 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2510 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {25112512 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());25132514 let owned_contract = <ContractOwner<T>>::contains_key(called_contract.clone())2515 && <ContractOwner<T>>::get(called_contract.clone()) == *who;2516 2517 if !owned_contract {2518 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone()) && <ContractWhiteListEnabled<T>>::get(called_contract.clone());2519 if !white_list_enabled || !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2520 return Err(InvalidTransaction::Call.into());2521 }2522 }25232524 let mut sponsor_transfer = false;2525 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2526 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2527 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2528 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2529 let limit_time = last_tx_block + rate_limit;25302531 if block_number >= limit_time {2532 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2533 sponsor_transfer = true;2534 }2535 } else {2536 sponsor_transfer = false;2537 }2538 2539 2540 let mut sp = T::AccountId::default();2541 if sponsor_transfer {2542 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2543 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2544 sp = called_contract;2545 }2546 }2547 }25482549 sp2550 },25512552 _ => sponsor,2553 };25542555 let mut who_pays_fee: T::AccountId = sponsor.clone();2556 if sponsor == T::AccountId::default() {2557 who_pays_fee = who.clone();2558 }25592560 // Only mess with balances if fee is not zero.2561 if fee.is_zero() {2562 return Ok((fee, None));2563 }25642565 match <T as transaction_payment::Trait>::Currency::withdraw(2566 &who_pays_fee,2567 fee,2568 if tip.is_zero() {2569 WithdrawReason::TransactionPayment.into()2570 } else {2571 WithdrawReason::TransactionPayment | WithdrawReason::Tip2572 },2573 ExistenceRequirement::KeepAlive,2574 ) {2575 Ok(imbalance) => Ok((fee, Some(imbalance))),2576 Err(_) => Err(InvalidTransaction::Payment.into()),2577 }2578 }2579}258025812582impl<T: Trait + Send + Sync> SignedExtension2583 for ChargeTransactionPayment<T>2584where2585 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2586 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2587{2588 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2589 type AccountId = T::AccountId;2590 type Call = T::Call;2591 type AdditionalSigned = ();2592 type Pre = (2593 BalanceOf<T>,2594 Self::AccountId,2595 Option<NegativeImbalanceOf<T>>,2596 BalanceOf<T>,2597 );2598 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2599 Ok(())2600 }26012602 fn validate(2603 &self,2604 who: &Self::AccountId,2605 call: &Self::Call,2606 info: &DispatchInfoOf<Self::Call>,2607 len: usize,2608 ) -> TransactionValidity {2609 let (fee, _) = self.withdraw_fee(who, call, info, len)?;2610 Ok(ValidTransaction {2611 priority: Self::get_priority(len, info, fee),2612 ..Default::default()2613 })2614 }26152616 fn pre_dispatch(2617 self,2618 who: &Self::AccountId,2619 call: &Self::Call,2620 info: &DispatchInfoOf<Self::Call>,2621 len: usize,2622 ) -> Result<Self::Pre, TransactionValidityError> {2623 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2624 Ok((self.0, who.clone(), imbalance, fee))2625 }26262627 fn post_dispatch(2628 pre: Self::Pre,2629 info: &DispatchInfoOf<Self::Call>,2630 post_info: &PostDispatchInfoOf<Self::Call>,2631 len: usize,2632 _result: &DispatchResult,2633 ) -> Result<(), TransactionValidityError> {2634 let (tip, who, imbalance, fee) = pre;2635 if let Some(payed) = imbalance {2636 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2637 len as u32, info, post_info, tip,2638 );2639 let refund = fee.saturating_sub(actual_fee);2640 let actual_payment =2641 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2642 &who, refund,2643 ) {2644 Ok(refund_imbalance) => {2645 // The refund cannot be larger than the up front payed max weight.2646 // `PostDispatchInfo::calc_unspent` guards against such a case.2647 match payed.offset(refund_imbalance) {2648 Ok(actual_payment) => actual_payment,2649 Err(_) => return Err(InvalidTransaction::Payment.into()),2650 }2651 }2652 // We do not recreate the account using the refund. The up front payment2653 // is gone in that case.2654 Err(_) => payed,2655 };2656 let imbalances = actual_payment.split(tip);2657 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2658 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2659 );2660 }2661 Ok(())2662 }2663}26642665// #endregionpallets/nft/src/tests.rsdiffbeforeafterboth--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -960,7 +960,7 @@
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), collection_id, 2));
- assert_eq!(TemplateModule::white_list(collection_id)[0], 2);
+ assert_eq!(TemplateModule::white_list(collection_id, 2), true);
});
}
@@ -975,7 +975,7 @@
assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), collection_id, 2));
assert_ok!(TemplateModule::add_to_white_list(origin2.clone(), collection_id, 3));
- assert_eq!(TemplateModule::white_list(collection_id)[0], 3);
+ assert_eq!(TemplateModule::white_list(collection_id, 3), true);
});
}
@@ -1035,8 +1035,7 @@
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), collection_id, 2));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), collection_id, 2));
- assert_eq!(TemplateModule::white_list(collection_id)[0], 2);
- assert_eq!(TemplateModule::white_list(collection_id).len(), 1);
+ assert_eq!(TemplateModule::white_list(collection_id, 2), true);
});
}
@@ -1054,7 +1053,7 @@
collection_id,
2
));
- assert_eq!(TemplateModule::white_list(collection_id).len(), 0);
+ assert_eq!(TemplateModule::white_list(collection_id, 2), false);
});
}
@@ -1075,7 +1074,7 @@
collection_id,
3
));
- assert_eq!(TemplateModule::white_list(collection_id).len(), 0);
+ assert_eq!(TemplateModule::white_list(collection_id, 3), false);
});
}
@@ -1093,7 +1092,7 @@
TemplateModule::remove_from_white_list(origin2.clone(), collection_id, 2),
Error::<Test>::NoPermission
);
- assert_eq!(TemplateModule::white_list(collection_id)[0], 2);
+ assert_eq!(TemplateModule::white_list(collection_id, 2), true);
});
}
@@ -1125,7 +1124,7 @@
TemplateModule::remove_from_white_list(origin2.clone(), collection_id, 2),
Error::<Test>::CollectionNotFound
);
- assert_eq!(TemplateModule::white_list(collection_id).len(), 0);
+ assert_eq!(TemplateModule::white_list(collection_id, 2), false);
});
}
@@ -1149,7 +1148,7 @@
collection_id,
2
));
- assert_eq!(TemplateModule::white_list(collection_id).len(), 0);
+ assert_eq!(TemplateModule::white_list(collection_id, 2), false);
});
}
tests/src/contracts.test.tsdiffbeforeafterboth--- a/tests/src/contracts.test.ts
+++ b/tests/src/contracts.test.ts
@@ -1,16 +1,15 @@
-//
-// This file is subject to the terms and conditions defined in
-// file 'LICENSE', which is part of this source code package.
-//
-
-import { ApiPromise } from "@polkadot/api";
-import { expect } from "chai";
-import { default as usingApi, submitTransactionAsync } from "./substrate/substrate-api";
+import chai from "chai";
+import chaiAsPromised from 'chai-as-promised';
+import usingApi, { submitTransactionAsync, submitTransactionExpectFailAsync } from "./substrate/substrate-api";
import fs from "fs";
-import { Abi, BlueprintPromise, CodePromise } from "@polkadot/api-contract";
+import { Abi, BlueprintPromise as Blueprint, CodePromise, ContractPromise as Contract } from "@polkadot/api-contract";
import { IKeyringPair } from "@polkadot/types/types";
-import { Keyring } from "@polkadot/api";
+import { ApiPromise, Keyring } from "@polkadot/api";
import { ApiTypes, SubmittableExtrinsic } from "@polkadot/api/types";
+import privateKey from "./substrate/privateKey";
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
import { BigNumber } from 'bignumber.js';
import { findUnusedAddress } from './util/helpers'
@@ -18,8 +17,8 @@
const gasLimit = 3000n * 1000000n;
const endowment = `1000000000000000`;
-function deployBlueprint(alice: IKeyringPair, code: CodePromise): Promise<BlueprintPromise> {
- return new Promise<BlueprintPromise>(async (resolve, reject) => {
+function deployBlueprint(alice: IKeyringPair, code: CodePromise): Promise<Blueprint> {
+ return new Promise<Blueprint>(async (resolve, reject) => {
const unsub = await code
.createBlueprint()
.signAndSend(alice, (result) => {
@@ -32,7 +31,7 @@
});
}
-function deployContract(alice: IKeyringPair, blueprint: BlueprintPromise) : Promise<any> {
+function deployContract(alice: IKeyringPair, blueprint: Blueprint) : Promise<any> {
return new Promise<any>(async (resolve, reject) => {
const initValue = true;
@@ -62,39 +61,119 @@
return deployer;
}
+async function deployFlipper(api: ApiPromise): Promise<[Contract, IKeyringPair]> {
+ const metadata = JSON.parse(fs.readFileSync('./src/flipper/metadata.json').toString('utf-8'));
+ const abi = new Abi(metadata);
+
+ const deployer = await prepareDeployer(api);
+
+ const wasm = fs.readFileSync('./src/flipper/flipper.wasm');
+
+ const code = new CodePromise(api, abi, wasm);
+
+ const blueprint = await deployBlueprint(deployer, code);
+ const contract = (await deployContract(deployer, blueprint))['contract'] as Contract;
+
+ const initialGetResponse = await getFlipValue(contract, deployer);
+ expect(initialGetResponse).to.be.true;
+
+ return [contract, deployer];
+}
+
+async function getFlipValue(contract: Contract, deployer: IKeyringPair) {
+ const result = await contract.query.get(deployer.address, value, gasLimit);
+
+ if(!result.result.isSuccess) {
+ throw `Failed to get flipper value`;
+ }
+ return (result.result.asSuccess.data[0] == 0x00) ? false : true;
+}
+
describe('Contracts smoke test', () => {
it(`Can deploy smart contract Flipper, instantiate it and call it's get and flip messages.`, async () => {
await usingApi(async api => {
- const deployer = await prepareDeployer(api);
-
- const wasm = fs.readFileSync('./src/flipper/flipper.wasm');
+ const [contract, deployer] = await deployFlipper(api);
+ const initialGetResponse = await getFlipValue(contract, deployer);
+
+ const flip = contract.exec('flip', value, gasLimit);
+ await submitTransactionAsync(deployer, flip);
+
+ const afterFlipGetResponse = await getFlipValue(contract, deployer);
+ expect(afterFlipGetResponse).not.to.be.eq(initialGetResponse, 'Flipping should change value.');
+ });
+ });
+
+ it(`Whitelisted account can call contract.`, async () => {
+ await usingApi(async api => {
+ const bob = privateKey("//Bob");
- const metadata = JSON.parse(fs.readFileSync('./src/flipper/metadata.json').toString('utf-8'));
- const abi = new Abi(metadata);
+ const [contract, deployer] = await deployFlipper(api);
+ const consoleError = console.error;
+ console.error = (...data: any[]) => {
+ };
- const code = new CodePromise(api, abi, wasm);
+ let expectedFlipValue = await getFlipValue(contract, deployer);
- const blueprint = await deployBlueprint(deployer, code);
- const contract = (await deployContract(deployer, blueprint))['contract'];
+ const flip = contract.exec('flip', value, gasLimit);
+ await expect(submitTransactionExpectFailAsync(bob, flip)).to.be.rejected;
+ const firstFailResponse = await getFlipValue(contract, deployer);
+ expect(firstFailResponse).to.be.eq(expectedFlipValue, `Only account who deployed contract can flip value.`);
- const getFlipValue = async () => {
- const result = await contract.query.get(deployer.address, value, gasLimit);
+ const deployerCanFlip = async () => {
+ expectedFlipValue = !expectedFlipValue;
+ const deployerFlip = contract.exec('flip', value, gasLimit);
+ await submitTransactionAsync(deployer, deployerFlip);
+ const aliceFlip1Response = await getFlipValue(contract, deployer);
+ expect(aliceFlip1Response).to.be.eq(expectedFlipValue, `Deployer always can flip.`);
+ };
+ await deployerCanFlip();
- if(!result.result.isSuccess) {
- throw `Failed to get flipper value`;
- }
- return (result.result.asSuccess.data[0] == 0x00) ? false : true;
- }
+ const enableWhiteListTx = api.tx.nft.toggleContractWhiteList(contract.address, true);
+ const enableResult = await submitTransactionAsync(deployer, enableWhiteListTx);
+ const flipWithEnabledWhiteList = contract.exec('flip', value, gasLimit);
+ await expect(submitTransactionExpectFailAsync(bob, flipWithEnabledWhiteList)).to.be.rejected;
+ const flipValueAfterEnableWhiteList = await getFlipValue(contract, deployer);
+ expect(flipValueAfterEnableWhiteList).to.be.eq(expectedFlipValue, `Enabling whitelist doesn't make it possible to call contract for everyone.`);
- const initialGetResponse = await getFlipValue();
- expect(initialGetResponse).to.be.true;
+ await deployerCanFlip();
- const flip = contract.exec('flip', value, gasLimit);
- await submitTransactionAsync(deployer, flip);
+ const addBobToWhiteListTx = api.tx.nft.addToContractWhiteList(contract.address, bob.address);
+ const addBobResult = await submitTransactionAsync(deployer, addBobToWhiteListTx);
+ const flipWithWhitelistedBob = contract.exec('flip', value, gasLimit);
+ await submitTransactionAsync(bob, flipWithWhitelistedBob);
+ expectedFlipValue = !expectedFlipValue;
+ const flipAfterWhiteListed = await getFlipValue(contract,deployer);
+ expect(flipAfterWhiteListed).to.be.eq(expectedFlipValue, `Bob was whitelisted, now he can flip.`);
+
+ await deployerCanFlip();
+
+ const disableWhiteListTx = api.tx.nft.toggleContractWhiteList(contract.address, false);
+ const disableeResult = await submitTransactionAsync(deployer, disableWhiteListTx);
+ const flipWithDisabledWhitelist = contract.exec('flip', value, gasLimit);
+ await expect(submitTransactionExpectFailAsync(bob, flipWithDisabledWhitelist)).to.be.rejected;
+ const flipWithDisabledWhiteList = await getFlipValue(contract, deployer);
+ expect(flipWithDisabledWhiteList).to.be.eq(expectedFlipValue, `Bob can't flip when whitelist is disabled, even tho he is in whitelist.`);
+
+ await deployerCanFlip();
+
+ const enableWhiteListOneMoreTimeTx = api.tx.nft.toggleContractWhiteList(contract.address, true);
+ const enableOneMoreTimeResult = await submitTransactionAsync(deployer, enableWhiteListOneMoreTimeTx);
+
+ await deployerCanFlip();
+
+ const removeBobFromWhiteListTx = api.tx.nft.removeFromContractWhiteList(contract.address, bob.address);
+ const removeBobResult = await submitTransactionAsync(deployer, removeBobFromWhiteListTx);
+ const bobRemoved = contract.exec('flip', value, gasLimit);
+ await expect(submitTransactionExpectFailAsync(bob, bobRemoved)).to.be.rejected;
+ const afterBobRemoved = await getFlipValue(contract, deployer);
+ expect(afterBobRemoved).to.be.eq(expectedFlipValue, `Enabling whitelist doesn't make it possible to call contract for everyone.`);
- const afterFlipGetResponse = await getFlipValue();
+ await deployerCanFlip();
- expect(afterFlipGetResponse).to.be.false;
+ const cleanupTx = api.tx.nft.toggleContractWhiteList(contract.address, false);
+ const cleanupResult = await submitTransactionAsync(deployer, cleanupTx);
+
+ console.error = consoleError;
});
});
tests/src/substrate/substrate-api.tsdiffbeforeafterboth--- a/tests/src/substrate/substrate-api.ts
+++ b/tests/src/substrate/substrate-api.ts
@@ -56,3 +56,25 @@
}
});
}
+
+export function submitTransactionExpectFailAsync(sender: IKeyringPair, transaction: SubmittableExtrinsic<ApiTypes>): Promise<EventRecord[]> {
+ return new Promise(async function(resolve, reject) {
+ try {
+ await transaction.signAndSend(sender, ({ events = [], status }) => {
+ if (status.isReady) {
+ // nothing to do
+ // console.log(`Current tx status is Ready`);
+ } else if (status.isBroadcast) {
+ // nothing to do
+ // console.log(`Current tx status is Broadcast`);
+ } else if (status.isInBlock || status.isFinalized) {
+ resolve(events);
+ } else {
+ reject("Transaction failed");
+ }
+ });
+ } catch (e) {
+ reject(e);
+ }
+ });
+}
\ No newline at end of file