difftreelog
Merge pull request #113 from usetech-llc/feature/NFTPAR-196_sponsor_setVariableMetadata
in: master
Sponsored setVariableMetadata
19 files changed
node/src/chain_spec.rsdiffbeforeafterboth--- a/node/src/chain_spec.rs
+++ b/node/src/chain_spec.rs
@@ -180,9 +180,9 @@
.collect(),
}),
pallet_nft: Some(NftConfig {
- collection: vec![(
+ collection_id: vec![(
1,
- CollectionType {
+ Collection {
owner: get_account_id_from_seed::<sr25519::Public>("Alice"),
mode: CollectionMode::NFT,
access: AccessMode::Normal,
pallets/nft/src/default_weights.rsdiffbeforeafterboth--- a/pallets/nft/src/default_weights.rs
+++ b/pallets/nft/src/default_weights.rs
@@ -127,6 +127,11 @@
.saturating_add(DbWeight::get().reads(0 as Weight))
.saturating_add(DbWeight::get().writes(2 as Weight))
}
+ fn set_variable_meta_data_sponsoring_rate_limit() -> Weight {
+ (3_500_000 as Weight)
+ .saturating_add(DbWeight::get().reads(2 as Weight))
+ .saturating_add(DbWeight::get().writes(1 as Weight))
+ }
fn toggle_contract_white_list() -> Weight {
(3_000_000 as Weight)
.saturating_add(DbWeight::get().reads(0 as Weight))
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, IsSubType, WithdrawReasons,24 },25 weights::{26 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28 WeightToFeePolynomial, DispatchClass,29 },30 StorageValue,31 transactional,32};3334use frame_system::{self as system, ensure_signed, ensure_root};35use sp_runtime::sp_std::prelude::Vec;36use sp_runtime::{37 traits::{38 Hash, DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,39 },40 transaction_validity::{41 TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,42 },43 FixedPointOperand, FixedU128,44};45use sp_runtime::traits::StaticLookup;46use pallet_contracts::chain_extension::UncheckedFrom;47use pallet_transaction_payment::OnChargeTransaction;4849#[cfg(test)]50mod mock;5152#[cfg(test)]53mod tests;5455mod default_weights;5657pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;58pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;59pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;60pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;6162// Structs63// #region6465pub type CollectionId = u32;66pub type TokenId = u32;67pub type DecimalPoints = u8;6869#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]70#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]71pub enum CollectionMode {72 Invalid,73 NFT,74 // decimal points75 Fungible(DecimalPoints),76 ReFungible,77}7879impl Default for CollectionMode {80 fn default() -> Self {81 Self::Invalid82 }83}8485impl Into<u8> for CollectionMode {86 fn into(self) -> u8 {87 match self {88 CollectionMode::Invalid => 0,89 CollectionMode::NFT => 1,90 CollectionMode::Fungible(_) => 2,91 CollectionMode::ReFungible => 3,92 }93 }94}9596#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]97#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]98pub enum AccessMode {99 Normal,100 WhiteList,101}102impl Default for AccessMode {103 fn default() -> Self {104 Self::Normal105 }106}107108#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]109#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]110pub enum SchemaVersion {111 ImageURL,112 Unique,113}114impl Default for SchemaVersion {115 fn default() -> Self {116 Self::ImageURL117 }118}119120#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]121#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]122pub struct Ownership<AccountId> {123 pub owner: AccountId,124 pub fraction: u128,125}126127#[derive(Encode, Decode, Debug, Clone, PartialEq)]128#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]129pub enum SponsorshipState<AccountId> {130 /// The fees are applied to the transaction sender131 Disabled,132 Unconfirmed(AccountId),133 /// Transactions are sponsored by specified account134 Confirmed(AccountId),135}136137impl<AccountId> SponsorshipState<AccountId> {138 fn sponsor(&self) -> Option<&AccountId> {139 match self {140 Self::Confirmed(sponsor) => Some(sponsor),141 _ => None,142 }143 }144145 fn pending_sponsor(&self) -> Option<&AccountId> {146 match self {147 Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),148 _ => None,149 }150 }151152 fn confirmed(&self) -> bool {153 matches!(self, Self::Confirmed(_))154 }155}156157impl<T> Default for SponsorshipState<T> {158 fn default() -> Self {159 Self::Disabled160 }161}162163#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]164#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]165pub struct CollectionType<AccountId> {166 pub owner: AccountId,167 pub mode: CollectionMode,168 pub access: AccessMode,169 pub decimal_points: DecimalPoints,170 pub name: Vec<u16>, // 64 include null escape char171 pub description: Vec<u16>, // 256 include null escape char172 pub token_prefix: Vec<u8>, // 16 include null escape char173 pub mint_mode: bool,174 pub offchain_schema: Vec<u8>,175 pub schema_version: SchemaVersion,176 pub sponsorship: SponsorshipState<AccountId>,177 pub limits: CollectionLimits, // Collection private restrictions 178 pub variable_on_chain_schema: Vec<u8>, //179 pub const_on_chain_schema: Vec<u8>, //180}181182#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]183#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]184pub struct NftItemType<AccountId> {185 pub owner: AccountId,186 pub const_data: Vec<u8>,187 pub variable_data: Vec<u8>,188}189190#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]191#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]192pub struct FungibleItemType {193 pub value: u128,194}195196#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]197#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]198pub struct ReFungibleItemType<AccountId> {199 pub owner: Vec<Ownership<AccountId>>,200 pub const_data: Vec<u8>,201 pub variable_data: Vec<u8>,202}203204// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]205// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]206// pub struct VestingItem<AccountId, Moment> {207// pub sender: AccountId,208// pub recipient: AccountId,209// pub collection_id: CollectionId,210// pub item_id: TokenId,211// pub amount: u64,212// pub vesting_date: Moment,213// }214215#[derive(Encode, Decode, Debug, Clone, PartialEq)]216#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]217pub struct CollectionLimits {218 pub account_token_ownership_limit: u32,219 pub sponsored_data_size: u32,220 pub token_limit: u32,221222 // Timeouts for item types in passed blocks223 pub sponsor_transfer_timeout: u32,224 pub owner_can_transfer: bool,225 pub owner_can_destroy: bool,226}227228impl Default for CollectionLimits {229 fn default() -> CollectionLimits {230 CollectionLimits { 231 account_token_ownership_limit: 10_000_000, 232 token_limit: u32::max_value(),233 sponsored_data_size: u32::MAX,234 sponsor_transfer_timeout: 14400,235 owner_can_transfer: true,236 owner_can_destroy: true237 }238 }239}240241#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]242#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]243pub struct ChainLimits {244 pub collection_numbers_limit: u32,245 pub account_token_ownership_limit: u32,246 pub collections_admins_limit: u64,247 pub custom_data_limit: u32,248249 // Timeouts for item types in passed blocks250 pub nft_sponsor_transfer_timeout: u32,251 pub fungible_sponsor_transfer_timeout: u32,252 pub refungible_sponsor_transfer_timeout: u32,253254 // Schema limits255 pub offchain_schema_limit: u32,256 pub variable_on_chain_schema_limit: u32,257 pub const_on_chain_schema_limit: u32,258}259260pub trait WeightInfo {261 fn create_collection() -> Weight;262 fn destroy_collection() -> Weight;263 fn add_to_white_list() -> Weight;264 fn remove_from_white_list() -> Weight;265 fn set_public_access_mode() -> Weight;266 fn set_mint_permission() -> Weight;267 fn change_collection_owner() -> Weight;268 fn add_collection_admin() -> Weight;269 fn remove_collection_admin() -> Weight;270 fn set_collection_sponsor() -> Weight;271 fn confirm_sponsorship() -> Weight;272 fn remove_collection_sponsor() -> Weight;273 fn create_item(s: usize) -> Weight;274 fn burn_item() -> Weight;275 fn transfer() -> Weight;276 fn approve() -> Weight;277 fn transfer_from() -> Weight;278 fn set_offchain_schema() -> Weight;279 fn set_const_on_chain_schema() -> Weight;280 fn set_variable_on_chain_schema() -> Weight;281 fn set_variable_meta_data() -> Weight;282 fn enable_contract_sponsoring() -> Weight;283 fn set_schema_version() -> Weight;284 fn set_chain_limits() -> Weight;285 fn set_contract_sponsoring_rate_limit() -> Weight;286 fn toggle_contract_white_list() -> Weight;287 fn add_to_contract_white_list() -> Weight;288 fn remove_from_contract_white_list() -> Weight;289 fn set_collection_limits() -> Weight;290}291292#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]293#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]294pub struct CreateNftData {295 pub const_data: Vec<u8>,296 pub variable_data: Vec<u8>,297}298299#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]300#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]301pub struct CreateFungibleData {302 pub value: u128,303}304305#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]306#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]307pub struct CreateReFungibleData {308 pub const_data: Vec<u8>,309 pub variable_data: Vec<u8>,310 pub pieces: u128,311}312313#[derive(Encode, Decode, Debug, Clone, PartialEq)]314#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]315pub enum CreateItemData {316 NFT(CreateNftData),317 Fungible(CreateFungibleData),318 ReFungible(CreateReFungibleData),319}320321impl CreateItemData {322 pub fn len(&self) -> usize {323 let len = match self {324 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),325 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),326 _ => 0327 };328 329 return len;330 }331}332333impl From<CreateNftData> for CreateItemData {334 fn from(item: CreateNftData) -> Self {335 CreateItemData::NFT(item)336 }337}338339impl From<CreateReFungibleData> for CreateItemData {340 fn from(item: CreateReFungibleData) -> Self {341 CreateItemData::ReFungible(item)342 }343}344345impl From<CreateFungibleData> for CreateItemData {346 fn from(item: CreateFungibleData) -> Self {347 CreateItemData::Fungible(item)348 }349}350351352decl_error! {353 /// Error for non-fungible-token module.354 pub enum Error for Module<T: Config> {355 /// Total collections bound exceeded.356 TotalCollectionsLimitExceeded,357 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.358 CollectionDecimalPointLimitExceeded, 359 /// Collection name can not be longer than 63 char.360 CollectionNameLimitExceeded, 361 /// Collection description can not be longer than 255 char.362 CollectionDescriptionLimitExceeded, 363 /// Token prefix can not be longer than 15 char.364 CollectionTokenPrefixLimitExceeded,365 /// This collection does not exist.366 CollectionNotFound,367 /// Item not exists.368 TokenNotFound,369 /// Admin not found370 AdminNotFound,371 /// Arithmetic calculation overflow.372 NumOverflow, 373 /// Account already has admin role.374 AlreadyAdmin, 375 /// You do not own this collection.376 NoPermission,377 /// This address is not set as sponsor, use setCollectionSponsor first.378 ConfirmUnsetSponsorFail,379 /// Collection is not in mint mode.380 PublicMintingNotAllowed,381 /// Sender parameter and item owner must be equal.382 MustBeTokenOwner,383 /// Item balance not enough.384 TokenValueTooLow,385 /// Size of item is too large.386 NftSizeLimitExceeded,387 /// No approve found388 ApproveNotFound,389 /// Requested value more than approved.390 TokenValueNotEnough,391 /// Only approved addresses can call this method.392 ApproveRequired,393 /// Address is not in white list.394 AddresNotInWhiteList,395 /// Number of collection admins bound exceeded.396 CollectionAdminsLimitExceeded,397 /// Owned tokens by a single address bound exceeded.398 AddressOwnershipLimitExceeded,399 /// Length of items properties must be greater than 0.400 EmptyArgument,401 /// const_data exceeded data limit.402 TokenConstDataLimitExceeded,403 /// variable_data exceeded data limit.404 TokenVariableDataLimitExceeded,405 /// Not NFT item data used to mint in NFT collection.406 NotNftDataUsedToMintNftCollectionToken,407 /// Not Fungible item data used to mint in Fungible collection.408 NotFungibleDataUsedToMintFungibleCollectionToken,409 /// Not Re Fungible item data used to mint in Re Fungible collection.410 NotReFungibleDataUsedToMintReFungibleCollectionToken,411 /// Unexpected collection type.412 UnexpectedCollectionType,413 /// Can't store metadata in fungible tokens.414 CantStoreMetadataInFungibleTokens,415 /// Collection token limit exceeded416 CollectionTokenLimitExceeded,417 /// Account token limit exceeded per collection418 AccountTokenLimitExceeded,419 /// Collection limit bounds per collection exceeded420 CollectionLimitBoundsExceeded,421 /// Tried to enable permissions which are only permitted to be disabled422 OwnerPermissionsCantBeReverted,423 /// Schema data size limit bound exceeded424 SchemaDataLimitExceeded,425 /// Maximum refungibility exceeded426 WrongRefungiblePieces,427 /// createRefungible should be called with one owner428 BadCreateRefungibleCall,429 }430}431432pub trait Config: system::Config + Sized + pallet_transaction_payment::Config + pallet_contracts::Config {433 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;434435 /// Weight information for extrinsics in this pallet.436 type WeightInfo: WeightInfo;437438 type Currency: Currency<Self::AccountId>;439 type CollectionCreationPrice: Get<<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance>;440 type TreasuryAccountId: Get<Self::AccountId>;441}442443#[cfg(feature = "runtime-benchmarks")]444mod benchmarking;445446// #endregion447448// # Used definitions449//450// ## User control levels451//452// chain-controlled - key is uncontrolled by user453// i.e autoincrementing index454// can use non-cryptographic hash455// real - key is controlled by user456// but it is hard to generate enough colliding values, i.e owner of signed txs457// can use non-cryptographic hash458// controlled - key is completly controlled by users459// i.e maps with mutable keys460// should use cryptographic hash461//462// ## User control level downgrade reasons463//464// ?1 - chain-controlled -> controlled465// collections/tokens can be destroyed, resulting in massive holes466// ?2 - chain-controlled -> controlled467// same as ?1, but can be only added, resulting in easier exploitation468// ?3 - real -> controlled469// no confirmation required, so addresses can be easily generated470decl_storage! {471 trait Store for Module<T: Config> as Nft {472473 //#region Private members474 /// Id of next collection475 CreatedCollectionCount: u32;476 /// Used for migrations477 ChainVersion: u64;478 /// Id of last collection token479 /// Collection id (controlled?1)480 ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;481 //#endregion482483 //#region Chain limits struct484 pub ChainLimit get(fn chain_limit) config(): ChainLimits;485 //#endregion486487 //#region Bound counters488 /// Amount of collections destroyed, used for total amount tracking with489 /// CreatedCollectionCount490 DestroyedCollectionCount: u32;491 /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)492 /// Account id (real)493 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;494 //#endregion495496 //#region Basic collections497 /// Collection info498 /// Collection id (controlled?1)499 pub Collection get(fn collection) config(): map hasher(blake2_128_concat) CollectionId => CollectionType<T::AccountId>;500 /// List of collection admins501 /// Collection id (controlled?2)502 pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::AccountId>;503 /// Whitelisted collection users504 /// Collection id (controlled?2), user id (controlled?3)505 pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;506 //#endregion507508 /// How many of collection items user have509 /// Collection id (controlled?2), account id (real)510 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;511512 /// Amount of items which spender can transfer out of owners account (via transferFrom)513 /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))514 pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;515516 //#region Item collections517 /// Collection id (controlled?2), token id (controlled?1)518 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => NftItemType<T::AccountId>;519 /// Collection id (controlled?2), owner (controlled?2)520 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;521 /// Collection id (controlled?2), token id (controlled?1)522 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => ReFungibleItemType<T::AccountId>;523 //#endregion524525 //#region Index list526 /// Collection id (controlled?2), tokens owner (controlled?2)527 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;528 //#endregion529530 //#region Tokens transfer rate limit baskets531 /// (Collection id (controlled?2), who created (real))532 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;533 /// Collection id (controlled?2), token id (controlled?2)534 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;535 /// Collection id (controlled?2), owning user (real)536 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;537 /// Collection id (controlled?2), token id (controlled?2)538 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;539 //#endregion540541 //#region Contract Sponsorship and Ownership542 /// Contract address (real)543 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;544 /// Contract address (real)545 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;546 /// (Contract address(real), caller (real))547 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;548 /// Contract address (real)549 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;550 /// Contract address (real)551 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 552 /// Contract address (real) => Whitelisted user (controlled?3)553 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(blake2_128_concat) T::AccountId => bool; 554 //#endregion555 }556 add_extra_genesis {557 build(|config: &GenesisConfig<T>| {558 // Modification of storage559 for (_num, _c) in &config.collection {560 <Module<T>>::init_collection(_c);561 }562563 for (_num, _c, _i) in &config.nft_item_id {564 <Module<T>>::init_nft_token(*_c, _i);565 }566567 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {568 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);569 }570571 for (_num, _c, _i) in &config.refungible_item_id {572 <Module<T>>::init_refungible_token(*_c, _i);573 }574 })575 }576}577578decl_event!(579 pub enum Event<T>580 where581 AccountId = <T as system::Config>::AccountId,582 {583 /// New collection was created584 /// 585 /// # Arguments586 /// 587 /// * collection_id: Globally unique identifier of newly created collection.588 /// 589 /// * mode: [CollectionMode] converted into u8.590 /// 591 /// * account_id: Collection owner.592 Created(CollectionId, u8, AccountId),593594 /// New item was created.595 /// 596 /// # Arguments597 /// 598 /// * collection_id: Id of the collection where item was created.599 /// 600 /// * item_id: Id of an item. Unique within the collection.601 ///602 /// * recipient: Owner of newly created item 603 ItemCreated(CollectionId, TokenId, AccountId),604605 /// Collection item was burned.606 /// 607 /// # Arguments608 /// 609 /// collection_id.610 /// 611 /// item_id: Identifier of burned NFT.612 ItemDestroyed(CollectionId, TokenId),613614 /// Item was transferred615 ///616 /// * collection_id: Id of collection to which item is belong617 ///618 /// * item_id: Id of an item619 ///620 /// * sender: Original owner of item621 ///622 /// * recipient: New owner of item623 ///624 /// * amount: Always 1 for NFT625 Transfer(CollectionId, TokenId, AccountId, AccountId, u128),626 }627);628629decl_module! {630 pub struct Module<T: Config> for enum Call 631 where 632 origin: T::Origin633 {634 fn deposit_event() = default;635 type Error = Error<T>;636637 fn on_initialize(now: T::BlockNumber) -> Weight {638 0639 }640641 /// 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.642 /// 643 /// # Permissions644 /// 645 /// * Anyone.646 /// 647 /// # Arguments648 /// 649 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.650 /// 651 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.652 /// 653 /// * token_prefix: UTF-8 string with token prefix.654 /// 655 /// * mode: [CollectionMode] collection type and type dependent data.656 // returns collection ID657 #[weight = <T as Config>::WeightInfo::create_collection()]658 #[transactional]659 pub fn create_collection(origin,660 collection_name: Vec<u16>,661 collection_description: Vec<u16>,662 token_prefix: Vec<u8>,663 mode: CollectionMode) -> DispatchResult {664665 // Anyone can create a collection666 let who = ensure_signed(origin)?;667668 // Take a (non-refundable) deposit of collection creation669 let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();670 imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(671 &T::TreasuryAccountId::get(),672 T::CollectionCreationPrice::get(),673 ));674 <T as Config>::Currency::settle(675 &who,676 imbalance,677 WithdrawReasons::TRANSFER,678 ExistenceRequirement::KeepAlive,679 ).map_err(|_| Error::<T>::NoPermission)?;680681 let decimal_points = match mode {682 CollectionMode::Fungible(points) => points,683 _ => 0684 };685686 let chain_limit = ChainLimit::get();687688 let created_count = CreatedCollectionCount::get();689 let destroyed_count = DestroyedCollectionCount::get();690691 // bound Total number of collections692 ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);693694 // check params695 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);696 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);697 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);698 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);699700 // Generate next collection ID701 let next_id = created_count702 .checked_add(1)703 .ok_or(Error::<T>::NumOverflow)?;704705 CreatedCollectionCount::put(next_id);706707 let limits = CollectionLimits {708 sponsored_data_size: chain_limit.custom_data_limit,709 ..Default::default()710 };711712 // Create new collection713 let new_collection = CollectionType {714 owner: who.clone(),715 name: collection_name,716 mode: mode.clone(),717 mint_mode: false,718 access: AccessMode::Normal,719 description: collection_description,720 decimal_points: decimal_points,721 token_prefix: token_prefix,722 offchain_schema: Vec::new(),723 schema_version: SchemaVersion::ImageURL,724 sponsorship: SponsorshipState::Disabled,725 variable_on_chain_schema: Vec::new(),726 const_on_chain_schema: Vec::new(),727 limits,728 };729730 // Add new collection to map731 <Collection<T>>::insert(next_id, new_collection);732733 // call event734 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));735736 Ok(())737 }738739 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.740 /// 741 /// # Permissions742 /// 743 /// * Collection Owner.744 /// 745 /// # Arguments746 /// 747 /// * collection_id: collection to destroy.748 #[weight = <T as Config>::WeightInfo::destroy_collection()]749 #[transactional]750 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {751752 let sender = ensure_signed(origin)?;753 Self::check_owner_permissions(collection_id, sender)?;754755 let target_collection = <Collection<T>>::get(collection_id);756 if !target_collection.limits.owner_can_destroy {757 fail!(Error::<T>::NoPermission);758 }759760 <AddressTokens<T>>::remove_prefix(collection_id);761 <Allowances<T>>::remove_prefix(collection_id);762 <Balance<T>>::remove_prefix(collection_id);763 <ItemListIndex>::remove(collection_id);764 <AdminList<T>>::remove(collection_id);765 <Collection<T>>::remove(collection_id);766 <WhiteList<T>>::remove_prefix(collection_id);767768 <NftItemList<T>>::remove_prefix(collection_id);769 <FungibleItemList<T>>::remove_prefix(collection_id);770 <ReFungibleItemList<T>>::remove_prefix(collection_id);771772 <NftTransferBasket<T>>::remove_prefix(collection_id);773 <FungibleTransferBasket<T>>::remove_prefix(collection_id);774 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);775776 DestroyedCollectionCount::put(DestroyedCollectionCount::get()777 .checked_add(1)778 .ok_or(Error::<T>::NumOverflow)?);779780 Ok(())781 }782783 /// Add an address to white list.784 /// 785 /// # Permissions786 /// 787 /// * Collection Owner788 /// * Collection Admin789 /// 790 /// # Arguments791 /// 792 /// * collection_id.793 /// 794 /// * address.795 #[weight = <T as Config>::WeightInfo::add_to_white_list()]796 #[transactional]797 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{798799 let sender = ensure_signed(origin)?;800 Self::check_owner_or_admin_permissions(collection_id, sender)?;801802 <WhiteList<T>>::insert(collection_id, address, true);803 804 Ok(())805 }806807 /// Remove an address from white list.808 /// 809 /// # Permissions810 /// 811 /// * Collection Owner812 /// * Collection Admin813 /// 814 /// # Arguments815 /// 816 /// * collection_id.817 /// 818 /// * address.819 #[weight = <T as Config>::WeightInfo::remove_from_white_list()]820 #[transactional]821 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{822823 let sender = ensure_signed(origin)?;824 Self::check_owner_or_admin_permissions(collection_id, sender)?;825826 <WhiteList<T>>::remove(collection_id, address);827828 Ok(())829 }830831 /// Toggle between normal and white list access for the methods with access for `Anyone`.832 /// 833 /// # Permissions834 /// 835 /// * Collection Owner.836 /// 837 /// # Arguments838 /// 839 /// * collection_id.840 /// 841 /// * mode: [AccessMode]842 #[weight = <T as Config>::WeightInfo::set_public_access_mode()]843 #[transactional]844 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult845 {846 let sender = ensure_signed(origin)?;847848 Self::check_owner_permissions(collection_id, sender)?;849 let mut target_collection = <Collection<T>>::get(collection_id);850 target_collection.access = mode;851 <Collection<T>>::insert(collection_id, target_collection);852853 Ok(())854 }855856 /// Allows Anyone to create tokens if:857 /// * White List is enabled, and858 /// * Address is added to white list, and859 /// * This method was called with True parameter860 /// 861 /// # Permissions862 /// * Collection Owner863 ///864 /// # Arguments865 /// 866 /// * collection_id.867 /// 868 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.869 #[weight = <T as Config>::WeightInfo::set_mint_permission()]870 #[transactional]871 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult872 {873 let sender = ensure_signed(origin)?;874875 Self::check_owner_permissions(collection_id, sender)?;876 let mut target_collection = <Collection<T>>::get(collection_id);877 target_collection.mint_mode = mint_permission;878 <Collection<T>>::insert(collection_id, target_collection);879880 Ok(())881 }882883 /// Change the owner of the collection.884 /// 885 /// # Permissions886 /// 887 /// * Collection Owner.888 /// 889 /// # Arguments890 /// 891 /// * collection_id.892 /// 893 /// * new_owner.894 #[weight = <T as Config>::WeightInfo::change_collection_owner()]895 #[transactional]896 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {897898 let sender = ensure_signed(origin)?;899 Self::check_owner_permissions(collection_id, sender)?;900 let mut target_collection = <Collection<T>>::get(collection_id);901 target_collection.owner = new_owner;902 <Collection<T>>::insert(collection_id, target_collection);903904 Ok(())905 }906907 /// Adds an admin of the Collection.908 /// 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. 909 /// 910 /// # Permissions911 /// 912 /// * Collection Owner.913 /// * Collection Admin.914 /// 915 /// # Arguments916 /// 917 /// * collection_id: ID of the Collection to add admin for.918 /// 919 /// * new_admin_id: Address of new admin to add.920 #[weight = <T as Config>::WeightInfo::add_collection_admin()]921 #[transactional]922 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {923924 let sender = ensure_signed(origin)?;925 Self::check_owner_or_admin_permissions(collection_id, sender)?;926 let mut admin_arr: Vec<T::AccountId> = Vec::new();927928 if <AdminList<T>>::contains_key(collection_id)929 {930 admin_arr = <AdminList<T>>::get(collection_id);931 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);932 }933934 // Number of collection admins935 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);936937 admin_arr.push(new_admin_id);938 <AdminList<T>>::insert(collection_id, admin_arr);939940 Ok(())941 }942943 /// 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.944 ///945 /// # Permissions946 /// 947 /// * Collection Owner.948 /// * Collection Admin.949 /// 950 /// # Arguments951 /// 952 /// * collection_id: ID of the Collection to remove admin for.953 /// 954 /// * account_id: Address of admin to remove.955 #[weight = <T as Config>::WeightInfo::remove_collection_admin()]956 #[transactional]957 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {958959 let sender = ensure_signed(origin)?;960 Self::check_owner_or_admin_permissions(collection_id, sender)?;961 ensure!(<AdminList<T>>::contains_key(collection_id), Error::<T>::AdminNotFound);962963 let mut admin_arr = <AdminList<T>>::get(collection_id);964 admin_arr.retain(|i| *i != account_id);965 <AdminList<T>>::insert(collection_id, admin_arr);966967 Ok(())968 }969970 /// # Permissions971 /// 972 /// * Collection Owner973 /// 974 /// # Arguments975 /// 976 /// * collection_id.977 /// 978 /// * new_sponsor.979 #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]980 #[transactional]981 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {982983 let sender = ensure_signed(origin)?;984 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);985986 let mut target_collection = <Collection<T>>::get(collection_id);987 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);988989 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);990 <Collection<T>>::insert(collection_id, target_collection);991992 Ok(())993 }994995 /// # Permissions996 /// 997 /// * Sponsor.998 /// 999 /// # Arguments1000 /// 1001 /// * collection_id.1002 #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]1003 #[transactional]1004 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {10051006 let sender = ensure_signed(origin)?;1007 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);10081009 let mut target_collection = <Collection<T>>::get(collection_id);1010 ensure!(1011 target_collection.sponsorship.pending_sponsor() == Some(&sender),1012 Error::<T>::ConfirmUnsetSponsorFail1013 );10141015 target_collection.sponsorship = SponsorshipState::Confirmed(sender);1016 <Collection<T>>::insert(collection_id, target_collection);10171018 Ok(())1019 }10201021 /// Switch back to pay-per-own-transaction model.1022 ///1023 /// # Permissions1024 ///1025 /// * Collection owner.1026 /// 1027 /// # Arguments1028 /// 1029 /// * collection_id.1030 #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]1031 #[transactional]1032 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {10331034 let sender = ensure_signed(origin)?;1035 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);10361037 let mut target_collection = <Collection<T>>::get(collection_id);1038 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);10391040 target_collection.sponsorship = SponsorshipState::Disabled;1041 <Collection<T>>::insert(collection_id, target_collection);10421043 Ok(())1044 }10451046 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.1047 /// 1048 /// # Permissions1049 /// 1050 /// * Collection Owner.1051 /// * Collection Admin.1052 /// * Anyone if1053 /// * White List is enabled, and1054 /// * Address is added to white list, and1055 /// * MintPermission is enabled (see SetMintPermission method)1056 /// 1057 /// # Arguments1058 /// 1059 /// * collection_id: ID of the collection.1060 /// 1061 /// * owner: Address, initial owner of the NFT.1062 ///1063 /// * data: Token data to store on chain.1064 // #[weight =1065 // (130_000_000 as Weight)1066 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))1067 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))1068 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]10691070 #[weight = <T as Config>::WeightInfo::create_item(data.len())]1071 #[transactional]1072 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {10731074 let sender = ensure_signed(origin)?;10751076 Self::collection_exists(collection_id)?;10771078 let target_collection = <Collection<T>>::get(collection_id);10791080 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner, 1)?;1081 Self::validate_create_item_args(&target_collection, &data)?;1082 Self::create_item_no_validation(collection_id, owner, data)?;10831084 Ok(())1085 }10861087 /// This method creates multiple items in a collection created with CreateCollection method.1088 /// 1089 /// # Permissions1090 /// 1091 /// * Collection Owner.1092 /// * Collection Admin.1093 /// * Anyone if1094 /// * White List is enabled, and1095 /// * Address is added to white list, and1096 /// * MintPermission is enabled (see SetMintPermission method)1097 /// 1098 /// # Arguments1099 /// 1100 /// * collection_id: ID of the collection.1101 /// 1102 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].1103 /// 1104 /// * owner: Address, initial owner of the NFT.1105 #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()1106 .map(|data| { data.len() })1107 .sum())]1108 #[transactional]1109 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {11101111 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);1112 let sender = ensure_signed(origin)?;11131114 Self::collection_exists(collection_id)?;1115 let target_collection = <Collection<T>>::get(collection_id);11161117 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner, items_data.len() as u32)?;11181119 for data in &items_data {1120 Self::validate_create_item_args(&target_collection, data)?;1121 }1122 for data in &items_data {1123 Self::create_item_no_validation(collection_id, owner.clone(), data.clone())?;1124 }11251126 Ok(())1127 }11281129 /// Destroys a concrete instance of NFT.1130 /// 1131 /// # Permissions1132 /// 1133 /// * Collection Owner.1134 /// * Collection Admin.1135 /// * Current NFT Owner.1136 /// 1137 /// # Arguments1138 /// 1139 /// * collection_id: ID of the collection.1140 /// 1141 /// * item_id: ID of NFT to burn.1142 #[weight = <T as Config>::WeightInfo::burn_item()]1143 #[transactional]1144 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {11451146 let sender = ensure_signed(origin)?;1147 Self::collection_exists(collection_id)?;11481149 // Transfer permissions check1150 let target_collection = <Collection<T>>::get(collection_id);1151 ensure!(1152 Self::is_item_owner(sender.clone(), collection_id, item_id) ||1153 (1154 target_collection.limits.owner_can_transfer &&1155 Self::is_owner_or_admin_permissions(collection_id, sender.clone())1156 ),1157 Error::<T>::NoPermission1158 );11591160 if target_collection.access == AccessMode::WhiteList {1161 Self::check_white_list(collection_id, &sender)?;1162 }11631164 match target_collection.mode1165 {1166 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1167 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, collection_id, value)?,1168 CollectionMode::ReFungible => Self::burn_refungible_item(collection_id, item_id, &sender)?,1169 _ => ()1170 };11711172 // call event1173 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));11741175 Ok(())1176 }11771178 /// Change ownership of the token.1179 /// 1180 /// # Permissions1181 /// 1182 /// * Collection Owner1183 /// * Collection Admin1184 /// * Current NFT owner1185 ///1186 /// # Arguments1187 /// 1188 /// * recipient: Address of token recipient.1189 /// 1190 /// * collection_id.1191 /// 1192 /// * item_id: ID of the item1193 /// * Non-Fungible Mode: Required.1194 /// * Fungible Mode: Ignored.1195 /// * Re-Fungible Mode: Required.1196 /// 1197 /// * value: Amount to transfer.1198 /// * Non-Fungible Mode: Ignored1199 /// * Fungible Mode: Must specify transferred amount1200 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1201 #[weight = <T as Config>::WeightInfo::transfer()]1202 #[transactional]1203 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {1204 let sender = ensure_signed(origin)?;1205 Self::transfer_internal(sender, recipient, collection_id, item_id, value)1206 }12071208 /// Set, change, or remove approved address to transfer the ownership of the NFT.1209 /// 1210 /// # Permissions1211 /// 1212 /// * Collection Owner1213 /// * Collection Admin1214 /// * Current NFT owner1215 /// 1216 /// # Arguments1217 /// 1218 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1219 /// 1220 /// * collection_id.1221 /// 1222 /// * item_id: ID of the item.1223 #[weight = <T as Config>::WeightInfo::approve()]1224 #[transactional]1225 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {12261227 let sender = ensure_signed(origin)?;12281229 Self::collection_exists(collection_id)?;1230 Self::token_exists(collection_id, item_id, &sender)?;12311232 // Transfer permissions check1233 let target_collection = <Collection<T>>::get(collection_id);12341235 let bypasses_limits = target_collection.limits.owner_can_transfer &&1236 Self::is_owner_or_admin_permissions(1237 collection_id,1238 sender.clone(),1239 );12401241 let allowance_limit = if bypasses_limits {1242 None1243 } else if let Some(amount) = Self::owned_amount(1244 sender.clone(),1245 collection_id,1246 item_id,1247 ) {1248 Some(amount)1249 } else {1250 fail!(Error::<T>::NoPermission);1251 };12521253 if target_collection.access == AccessMode::WhiteList {1254 Self::check_white_list(collection_id, &sender)?;1255 Self::check_white_list(collection_id, &spender)?;1256 }12571258 let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1259 let mut allowance: u128 = amount;1260 if allowance_exists {1261 allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1262 }1263 if let Some(limit) = allowance_limit {1264 ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1265 }1266 <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);12671268 Ok(())1269 }1270 1271 /// 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.1272 /// 1273 /// # Permissions1274 /// * Collection Owner1275 /// * Collection Admin1276 /// * Current NFT owner1277 /// * Address approved by current NFT owner1278 /// 1279 /// # Arguments1280 /// 1281 /// * from: Address that owns token.1282 /// 1283 /// * recipient: Address of token recipient.1284 /// 1285 /// * collection_id.1286 /// 1287 /// * item_id: ID of the item.1288 /// 1289 /// * value: Amount to transfer.1290 #[weight = <T as Config>::WeightInfo::transfer_from()]1291 #[transactional]1292 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {12931294 let sender = ensure_signed(origin)?;1295 let mut appoved_transfer = false;12961297 // Check approval1298 let mut approval: u128 = 0;1299 if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &sender)) {1300 approval = <Allowances<T>>::get(collection_id, (item_id, &from, &sender));1301 ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1302 appoved_transfer = true;1303 }13041305 let target_collection = <Collection<T>>::get(collection_id);13061307 // Limits check1308 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;13091310 // Transfer permissions check 1311 ensure!(1312 appoved_transfer || 1313 (1314 target_collection.limits.owner_can_transfer &&1315 Self::is_owner_or_admin_permissions(collection_id, sender.clone())1316 ),1317 Error::<T>::NoPermission1318 );13191320 if target_collection.access == AccessMode::WhiteList {1321 Self::check_white_list(collection_id, &sender)?;1322 Self::check_white_list(collection_id, &recipient)?;1323 }13241325 // Reduce approval by transferred amount or remove if remaining approval drops to 01326 if approval.saturating_sub(value) > 0 {1327 <Allowances<T>>::insert(collection_id, (item_id, &from, &sender), approval - value);1328 }1329 else {1330 <Allowances<T>>::remove(collection_id, (item_id, &from, &sender));1331 }13321333 match target_collection.mode1334 {1335 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1336 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1337 CollectionMode::ReFungible => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1338 _ => ()1339 };13401341 Ok(())1342 }13431344 // #[weight = 0]1345 // pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {13461347 // // let no_perm_mes = "You do not have permissions to modify this collection";1348 // // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1349 // // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1350 // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);13511352 // // // on_nft_received call13531354 // // Self::transfer(origin, collection_id, item_id, new_owner)?;13551356 // Ok(())1357 // }13581359 /// Set off-chain data schema.1360 /// 1361 /// # Permissions1362 /// 1363 /// * Collection Owner1364 /// * Collection Admin1365 /// 1366 /// # Arguments1367 /// 1368 /// * collection_id.1369 /// 1370 /// * schema: String representing the offchain data schema.1371 #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1372 #[transactional]1373 pub fn set_variable_meta_data (1374 origin,1375 collection_id: CollectionId,1376 item_id: TokenId,1377 data: Vec<u8>1378 ) -> DispatchResult {1379 let sender = ensure_signed(origin)?;1380 1381 Self::collection_exists(collection_id)?;1382 Self::token_exists(collection_id, item_id, &sender)?;13831384 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);13851386 // Modify permissions check1387 let target_collection = <Collection<T>>::get(collection_id);1388 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1389 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1390 Error::<T>::NoPermission);13911392 match target_collection.mode1393 {1394 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1395 CollectionMode::ReFungible => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1396 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1397 _ => fail!(Error::<T>::UnexpectedCollectionType)1398 };13991400 Ok(())1401 }1402 1403 /// Set schema standard1404 /// ImageURL1405 /// Unique1406 /// 1407 /// # Permissions1408 /// 1409 /// * Collection Owner1410 /// * Collection Admin1411 /// 1412 /// # Arguments1413 /// 1414 /// * collection_id.1415 /// 1416 /// * schema: SchemaVersion: enum1417 #[weight = <T as Config>::WeightInfo::set_schema_version()]1418 #[transactional]1419 pub fn set_schema_version(1420 origin,1421 collection_id: CollectionId,1422 version: SchemaVersion1423 ) -> DispatchResult {1424 let sender = ensure_signed(origin)?;1425 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1426 let mut target_collection = <Collection<T>>::get(collection_id);1427 target_collection.schema_version = version;1428 <Collection<T>>::insert(collection_id, target_collection);14291430 Ok(())1431 }14321433 /// Set off-chain data schema.1434 /// 1435 /// # Permissions1436 /// 1437 /// * Collection Owner1438 /// * Collection Admin1439 /// 1440 /// # Arguments1441 /// 1442 /// * collection_id.1443 /// 1444 /// * schema: String representing the offchain data schema.1445 #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1446 #[transactional]1447 pub fn set_offchain_schema(1448 origin,1449 collection_id: CollectionId,1450 schema: Vec<u8>1451 ) -> DispatchResult {1452 let sender = ensure_signed(origin)?;1453 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;14541455 // check schema limit1456 ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");14571458 let mut target_collection = <Collection<T>>::get(collection_id);1459 target_collection.offchain_schema = schema;1460 <Collection<T>>::insert(collection_id, target_collection);14611462 Ok(())1463 }14641465 /// Set const on-chain data schema.1466 /// 1467 /// # Permissions1468 /// 1469 /// * Collection Owner1470 /// * Collection Admin1471 /// 1472 /// # Arguments1473 /// 1474 /// * collection_id.1475 /// 1476 /// * schema: String representing the const on-chain data schema.1477 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1478 #[transactional]1479 pub fn set_const_on_chain_schema (1480 origin,1481 collection_id: CollectionId,1482 schema: Vec<u8>1483 ) -> DispatchResult {1484 let sender = ensure_signed(origin)?;1485 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;14861487 // check schema limit1488 ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");14891490 let mut target_collection = <Collection<T>>::get(collection_id);1491 target_collection.const_on_chain_schema = schema;1492 <Collection<T>>::insert(collection_id, target_collection);14931494 Ok(())1495 }14961497 /// Set variable on-chain data schema.1498 /// 1499 /// # Permissions1500 /// 1501 /// * Collection Owner1502 /// * Collection Admin1503 /// 1504 /// # Arguments1505 /// 1506 /// * collection_id.1507 /// 1508 /// * schema: String representing the variable on-chain data schema.1509 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1510 #[transactional]1511 pub fn set_variable_on_chain_schema (1512 origin,1513 collection_id: CollectionId,1514 schema: Vec<u8>1515 ) -> DispatchResult {1516 let sender = ensure_signed(origin)?;1517 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;15181519 // check schema limit1520 ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");15211522 let mut target_collection = <Collection<T>>::get(collection_id);1523 target_collection.variable_on_chain_schema = schema;1524 <Collection<T>>::insert(collection_id, target_collection);15251526 Ok(())1527 }15281529 // Sudo permissions function1530 #[weight = <T as Config>::WeightInfo::set_chain_limits()]1531 #[transactional]1532 pub fn set_chain_limits(1533 origin,1534 limits: ChainLimits1535 ) -> DispatchResult {15361537 #[cfg(not(feature = "runtime-benchmarks"))]1538 ensure_root(origin)?;15391540 <ChainLimit>::put(limits);1541 Ok(())1542 }15431544 /// Enable smart contract self-sponsoring.1545 /// 1546 /// # Permissions1547 /// 1548 /// * Contract Owner1549 /// 1550 /// # Arguments1551 /// 1552 /// * contract address1553 /// * enable flag1554 /// 1555 #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1556 #[transactional]1557 pub fn enable_contract_sponsoring(1558 origin,1559 contract_address: T::AccountId,1560 enable: bool1561 ) -> DispatchResult {15621563 let sender = ensure_signed(origin)?;15641565 #[cfg(feature = "runtime-benchmarks")]1566 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15671568 Self::ensure_contract_owned(sender, &contract_address)?;15691570 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1571 Ok(())1572 }15731574 /// Set the rate limit for contract sponsoring to specified number of blocks.1575 /// 1576 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1577 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1578 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1579 /// from contract endowment if there are at least B blocks between such transactions. 1580 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1581 /// 1582 /// # Permissions1583 /// 1584 /// * Contract Owner1585 /// 1586 /// # Arguments1587 /// 1588 /// -`contract_address`: Address of the contract to sponsor1589 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1590 /// 1591 #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1592 #[transactional]1593 pub fn set_contract_sponsoring_rate_limit(1594 origin,1595 contract_address: T::AccountId,1596 rate_limit: T::BlockNumber1597 ) -> DispatchResult {1598 let sender = ensure_signed(origin)?;15991600 #[cfg(feature = "runtime-benchmarks")]1601 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());16021603 Self::ensure_contract_owned(sender, &contract_address)?;1604 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1605 Ok(())1606 }16071608 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1609 /// 1610 /// # Permissions1611 /// 1612 /// * Address that deployed smart contract.1613 /// 1614 /// # Arguments1615 /// 1616 /// -`contract_address`: Address of the contract.1617 /// 1618 /// - `enable`: . 1619 #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1620 #[transactional]1621 pub fn toggle_contract_white_list(1622 origin,1623 contract_address: T::AccountId,1624 enable: bool1625 ) -> DispatchResult {1626 let sender = ensure_signed(origin)?;16271628 #[cfg(feature = "runtime-benchmarks")]1629 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());16301631 Self::ensure_contract_owned(sender, &contract_address)?;1632 <ContractWhiteListEnabled<T>>::insert(contract_address, enable);1633 Ok(())1634 }1635 1636 /// Add an address to smart contract white list.1637 /// 1638 /// # Permissions1639 /// 1640 /// * Address that deployed smart contract.1641 /// 1642 /// # Arguments1643 /// 1644 /// -`contract_address`: Address of the contract.1645 ///1646 /// -`account_address`: Address to add.1647 #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1648 #[transactional]1649 pub fn add_to_contract_white_list(1650 origin,1651 contract_address: T::AccountId,1652 account_address: T::AccountId1653 ) -> DispatchResult {1654 let sender = ensure_signed(origin)?;16551656 #[cfg(feature = "runtime-benchmarks")]1657 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1658 1659 Self::ensure_contract_owned(sender, &contract_address)?; 1660 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1661 Ok(())1662 }16631664 /// Remove an address from smart contract white list.1665 /// 1666 /// # Permissions1667 /// 1668 /// * Address that deployed smart contract.1669 /// 1670 /// # Arguments1671 /// 1672 /// -`contract_address`: Address of the contract.1673 ///1674 /// -`account_address`: Address to remove.1675 #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1676 #[transactional]1677 pub fn remove_from_contract_white_list(1678 origin,1679 contract_address: T::AccountId,1680 account_address: T::AccountId1681 ) -> DispatchResult {1682 let sender = ensure_signed(origin)?;16831684 #[cfg(feature = "runtime-benchmarks")]1685 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());16861687 Self::ensure_contract_owned(sender, &contract_address)?;1688 <ContractWhiteList<T>>::remove(contract_address, account_address);1689 Ok(())1690 }16911692 #[weight = <T as Config>::WeightInfo::set_collection_limits()]1693 #[transactional]1694 pub fn set_collection_limits(1695 origin,1696 collection_id: u32,1697 new_limits: CollectionLimits,1698 ) -> DispatchResult {1699 let sender = ensure_signed(origin)?;1700 Self::check_owner_permissions(collection_id, sender.clone())?;1701 let mut target_collection = <Collection<T>>::get(collection_id);1702 let old_limits = target_collection.limits;1703 let chain_limits = ChainLimit::get();17041705 // collection bounds1706 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1707 new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && 1708 new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1709 Error::<T>::CollectionLimitBoundsExceeded);17101711 // token_limit check prev1712 ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1713 ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);17141715 ensure!(1716 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1717 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1718 Error::<T>::OwnerPermissionsCantBeReverted,1719 );17201721 target_collection.limits = new_limits;1722 <Collection<T>>::insert(collection_id, target_collection);17231724 Ok(())1725 } 1726 }1727}17281729impl<T: Config> Module<T> {17301731 pub fn transfer_internal(sender: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {17321733 let target_collection = <Collection<T>>::get(collection_id);17341735 // Limits check1736 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;17371738 // Transfer permissions check1739 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1740 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1741 Error::<T>::NoPermission);17421743 if target_collection.access == AccessMode::WhiteList {1744 Self::check_white_list(collection_id, &sender)?;1745 Self::check_white_list(collection_id, &recipient)?;1746 }17471748 match target_collection.mode1749 {1750 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient.clone())?,1751 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1752 CollectionMode::ReFungible => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient.clone())?,1753 _ => ()1754 };17551756 Self::deposit_event(RawEvent::Transfer(collection_id, item_id, sender, recipient, value));17571758 Ok(())1759 }176017611762 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {17631764 // check token limit and account token limit1765 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1766 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1767 1768 Ok(())1769 }17701771 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId, amount: u32) -> DispatchResult {17721773 // check token limit and account token limit1774 let total_items: u32 = ItemListIndex::get(collection_id)1775 .checked_add(amount)1776 .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1777 let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner).len() as u32)1778 .checked_add(amount)1779 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1780 ensure!(collection.limits.token_limit >= total_items, Error::<T>::CollectionTokenLimitExceeded);1781 ensure!(collection.limits.account_token_ownership_limit >= account_items, Error::<T>::AccountTokenLimitExceeded);17821783 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1784 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1785 Self::check_white_list(collection_id, owner)?;1786 Self::check_white_list(collection_id, sender)?;1787 }17881789 Ok(())1790 }17911792 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1793 match target_collection.mode1794 {1795 CollectionMode::NFT => {1796 if let CreateItemData::NFT(data) = data {1797 // check sizes1798 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1799 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1800 } else {1801 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1802 }1803 },1804 CollectionMode::Fungible(_) => {1805 if let CreateItemData::Fungible(_) = data {1806 } else {1807 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1808 }1809 },1810 CollectionMode::ReFungible => {1811 if let CreateItemData::ReFungible(data) = data {18121813 // check sizes1814 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1815 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);18161817 // Check refungibility limits1818 ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1819 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1820 } else {1821 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1822 }1823 },1824 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1825 };18261827 Ok(())1828 }18291830 fn create_item_no_validation(collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1831 match data1832 {1833 CreateItemData::NFT(data) => {1834 let item = NftItemType {1835 owner: owner.clone(),1836 const_data: data.const_data,1837 variable_data: data.variable_data1838 };18391840 Self::add_nft_item(collection_id, item)?;1841 },1842 CreateItemData::Fungible(data) => {1843 Self::add_fungible_item(collection_id, &owner, data.value)?;1844 },1845 CreateItemData::ReFungible(data) => {1846 let mut owner_list = Vec::new();1847 owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});18481849 let item = ReFungibleItemType {1850 owner: owner_list,1851 const_data: data.const_data,1852 variable_data: data.variable_data1853 };18541855 Self::add_refungible_item(collection_id, item)?;1856 }1857 };18581859 Ok(())1860 }18611862 fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {18631864 // Does new owner already have an account?1865 let mut balance: u128 = 0;1866 if <FungibleItemList<T>>::contains_key(collection_id, owner) {1867 balance = <FungibleItemList<T>>::get(collection_id, owner).value;1868 } 18691870 // Mint 1871 let item = FungibleItemType {1872 value: balance + value1873 };1874 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);18751876 // Update balance1877 let new_balance = <Balance<T>>::get(collection_id, owner)1878 .checked_add(value)1879 .ok_or(Error::<T>::NumOverflow)?;1880 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);18811882 Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1883 Ok(())1884 }18851886 fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1887 let current_index = <ItemListIndex>::get(collection_id)1888 .checked_add(1)1889 .ok_or(Error::<T>::NumOverflow)?;1890 let itemcopy = item.clone();18911892 ensure!(1893 item.owner.len() == 1,1894 Error::<T>::BadCreateRefungibleCall,1895 );1896 let item_owner = item.owner.first().expect("only one owner is defined");18971898 let value = item_owner.fraction;1899 let owner = item_owner.owner.clone();19001901 Self::add_token_index(collection_id, current_index, &owner)?;19021903 <ItemListIndex>::insert(collection_id, current_index);1904 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);19051906 // Update balance1907 let new_balance = <Balance<T>>::get(collection_id, &owner)1908 .checked_add(value)1909 .ok_or(Error::<T>::NumOverflow)?;1910 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);19111912 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1913 Ok(())1914 }19151916 fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1917 let current_index = <ItemListIndex>::get(collection_id)1918 .checked_add(1)1919 .ok_or(Error::<T>::NumOverflow)?;19201921 let item_owner = item.owner.clone();1922 Self::add_token_index(collection_id, current_index, &item.owner)?;19231924 <ItemListIndex>::insert(collection_id, current_index);1925 <NftItemList<T>>::insert(collection_id, current_index, item);19261927 // Update balance1928 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1929 .checked_add(1)1930 .ok_or(Error::<T>::NumOverflow)?;1931 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);19321933 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, item_owner));1934 Ok(())1935 }19361937 fn burn_refungible_item(1938 collection_id: CollectionId,1939 item_id: TokenId,1940 owner: &T::AccountId,1941 ) -> DispatchResult {1942 ensure!(1943 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1944 Error::<T>::TokenNotFound1945 );1946 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id);1947 let rft_balance = token1948 .owner1949 .iter()1950 .find(|&i| i.owner == *owner)1951 .ok_or(Error::<T>::TokenNotFound)?;1952 Self::remove_token_index(collection_id, item_id, owner)?;19531954 // update balance1955 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.clone())1956 .checked_sub(rft_balance.fraction)1957 .ok_or(Error::<T>::NumOverflow)?;1958 <Balance<T>>::insert(collection_id, rft_balance.owner.clone(), new_balance);19591960 // Re-create owners list with sender removed1961 let index = token1962 .owner1963 .iter()1964 .position(|i| i.owner == *owner)1965 .expect("owned item is exists");1966 token.owner.remove(index);1967 let owner_count = token.owner.len();19681969 // Burn the token completely if this was the last (only) owner1970 if owner_count == 0 {1971 <ReFungibleItemList<T>>::remove(collection_id, item_id);1972 }1973 else {1974 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1975 }19761977 Ok(())1978 }19791980 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1981 ensure!(1982 <NftItemList<T>>::contains_key(collection_id, item_id),1983 Error::<T>::TokenNotFound1984 );1985 let item = <NftItemList<T>>::get(collection_id, item_id);1986 Self::remove_token_index(collection_id, item_id, &item.owner)?;19871988 // update balance1989 let new_balance = <Balance<T>>::get(collection_id, &item.owner)1990 .checked_sub(1)1991 .ok_or(Error::<T>::NumOverflow)?;1992 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1993 <NftItemList<T>>::remove(collection_id, item_id);19941995 Ok(())1996 }19971998 fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1999 ensure!(2000 <FungibleItemList<T>>::contains_key(collection_id, owner),2001 Error::<T>::TokenNotFound2002 );2003 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);2004 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);20052006 // update balance2007 let new_balance = <Balance<T>>::get(collection_id, owner)2008 .checked_sub(value)2009 .ok_or(Error::<T>::NumOverflow)?;2010 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);20112012 if balance.value - value > 0 {2013 balance.value -= value;2014 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);2015 }2016 else {2017 <FungibleItemList<T>>::remove(collection_id, owner);2018 }20192020 Ok(())2021 }20222023 fn collection_exists(collection_id: CollectionId) -> DispatchResult {2024 ensure!(2025 <Collection<T>>::contains_key(collection_id),2026 Error::<T>::CollectionNotFound2027 );2028 Ok(())2029 }20302031 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {2032 Self::collection_exists(collection_id)?;20332034 let target_collection = <Collection<T>>::get(collection_id);2035 ensure!(2036 subject == target_collection.owner,2037 Error::<T>::NoPermission2038 );20392040 Ok(())2041 }20422043 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {2044 let target_collection = <Collection<T>>::get(collection_id);2045 let mut result: bool = subject == target_collection.owner;2046 let exists = <AdminList<T>>::contains_key(collection_id);20472048 if !result & exists {2049 if <AdminList<T>>::get(collection_id).contains(&subject) {2050 result = true2051 }2052 }20532054 result2055 }20562057 fn check_owner_or_admin_permissions(2058 collection_id: CollectionId,2059 subject: T::AccountId,2060 ) -> DispatchResult {2061 Self::collection_exists(collection_id)?;2062 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());20632064 ensure!(2065 result,2066 Error::<T>::NoPermission2067 );2068 Ok(())2069 }20702071 fn owned_amount(2072 subject: T::AccountId,2073 collection_id: CollectionId,2074 item_id: TokenId,2075 ) -> Option<u128> {2076 let target_collection = <Collection<T>>::get(collection_id);20772078 match target_collection.mode {2079 CollectionMode::NFT => {2080 if <NftItemList<T>>::get(collection_id, item_id).owner == subject {2081 return Some(1)2082 }2083 None2084 },2085 CollectionMode::Fungible(_) => {2086 if <FungibleItemList<T>>::contains_key(collection_id, &subject) {2087 return Some(<FungibleItemList<T>>::get(collection_id, &subject)2088 .value);2089 }2090 None2091 },2092 CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)2093 .owner2094 .iter()2095 .find(|i| i.owner == subject)2096 .map(|i| i.fraction),2097 CollectionMode::Invalid => None,2098 }2099 }21002101 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {2102 let target_collection = <Collection<T>>::get(collection_id);21032104 match target_collection.mode {2105 CollectionMode::NFT => {2106 <NftItemList<T>>::get(collection_id, item_id).owner == subject2107 }2108 CollectionMode::Fungible(_) => {2109 <FungibleItemList<T>>::contains_key(collection_id, &subject)2110 }2111 CollectionMode::ReFungible => {2112 <ReFungibleItemList<T>>::get(collection_id, item_id)2113 .owner2114 .iter()2115 .any(|i| i.owner == subject)2116 }2117 CollectionMode::Invalid => false,2118 }2119 }21202121 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {2122 let mes = Error::<T>::AddresNotInWhiteList;2123 ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);21242125 Ok(())2126 }21272128 /// Check if token exists. In case of Fungible, check if there is an entry for 2129 /// the owner in fungible balances double map2130 fn token_exists(2131 collection_id: CollectionId,2132 item_id: TokenId,2133 owner: &T::AccountId2134 ) -> DispatchResult {2135 let target_collection = <Collection<T>>::get(collection_id);2136 let exists = match target_collection.mode2137 {2138 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),2139 CollectionMode::Fungible(_) => <FungibleItemList<T>>::contains_key(collection_id, owner),2140 CollectionMode::ReFungible => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),2141 _ => false2142 };21432144 ensure!(exists == true, Error::<T>::TokenNotFound);2145 Ok(())2146 }21472148 fn transfer_fungible(2149 collection_id: CollectionId,2150 value: u128,2151 owner: &T::AccountId,2152 recipient: &T::AccountId,2153 ) -> DispatchResult {2154 Self::token_exists(collection_id, 0, owner)?;21552156 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);2157 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);21582159 // Send balance to recipient (updates balanceOf of recipient)2160 Self::add_fungible_item(collection_id, recipient, value)?;21612162 // update balanceOf of sender2163 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);21642165 // Reduce or remove sender2166 if balance.value == value {2167 <FungibleItemList<T>>::remove(collection_id, owner);2168 }2169 else {2170 balance.value -= value;2171 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);2172 }21732174 Ok(())2175 }21762177 fn transfer_refungible(2178 collection_id: CollectionId,2179 item_id: TokenId,2180 value: u128,2181 owner: T::AccountId,2182 new_owner: T::AccountId,2183 ) -> DispatchResult {2184 Self::token_exists(collection_id, item_id, &owner)?;21852186 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);2187 let item = full_item2188 .owner2189 .iter()2190 .filter(|i| i.owner == owner)2191 .next()2192 .ok_or(Error::<T>::TokenNotFound)?;2193 let amount = item.fraction;21942195 ensure!(amount >= value, Error::<T>::TokenValueTooLow);21962197 // update balance2198 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2199 .checked_sub(value)2200 .ok_or(Error::<T>::NumOverflow)?;2201 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);22022203 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())2204 .checked_add(value)2205 .ok_or(Error::<T>::NumOverflow)?;2206 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);22072208 let old_owner = item.owner.clone();2209 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);22102211 // transfer2212 if amount == value && !new_owner_has_account {2213 // change owner2214 // new owner do not have account2215 let mut new_full_item = full_item.clone();2216 new_full_item2217 .owner2218 .iter_mut()2219 .find(|i| i.owner == owner)2220 .expect("old owner does present in refungible")2221 .owner = new_owner.clone();2222 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);22232224 // update index collection2225 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2226 } else {2227 let mut new_full_item = full_item.clone();2228 new_full_item2229 .owner2230 .iter_mut()2231 .find(|i| i.owner == owner)2232 .expect("old owner does present in refungible")2233 .fraction -= value;22342235 // separate amount2236 if new_owner_has_account {2237 // new owner has account2238 new_full_item2239 .owner2240 .iter_mut()2241 .find(|i| i.owner == new_owner)2242 .expect("new owner has account")2243 .fraction += value;2244 } else {2245 // new owner do not have account2246 new_full_item.owner.push(Ownership {2247 owner: new_owner.clone(),2248 fraction: value,2249 });2250 Self::add_token_index(collection_id, item_id, &new_owner)?;2251 }22522253 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2254 }22552256 Ok(())2257 }22582259 fn transfer_nft(2260 collection_id: CollectionId,2261 item_id: TokenId,2262 sender: T::AccountId,2263 new_owner: T::AccountId,2264 ) -> DispatchResult {2265 Self::token_exists(collection_id, item_id, &sender)?;22662267 let mut item = <NftItemList<T>>::get(collection_id, item_id);22682269 ensure!(2270 sender == item.owner,2271 Error::<T>::MustBeTokenOwner2272 );22732274 // update balance2275 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2276 .checked_sub(1)2277 .ok_or(Error::<T>::NumOverflow)?;2278 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);22792280 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2281 .checked_add(1)2282 .ok_or(Error::<T>::NumOverflow)?;2283 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);22842285 // change owner2286 let old_owner = item.owner.clone();2287 item.owner = new_owner.clone();2288 <NftItemList<T>>::insert(collection_id, item_id, item);22892290 // update index collection2291 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;22922293 Ok(())2294 }2295 2296 fn set_re_fungible_variable_data(2297 collection_id: CollectionId,2298 item_id: TokenId,2299 data: Vec<u8>2300 ) -> DispatchResult {2301 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);23022303 item.variable_data = data;23042305 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);23062307 Ok(())2308 }23092310 fn set_nft_variable_data(2311 collection_id: CollectionId,2312 item_id: TokenId,2313 data: Vec<u8>2314 ) -> DispatchResult {2315 let mut item = <NftItemList<T>>::get(collection_id, item_id);2316 2317 item.variable_data = data;23182319 <NftItemList<T>>::insert(collection_id, item_id, item);2320 2321 Ok(())2322 }23232324 fn init_collection(item: &CollectionType<T::AccountId>) {2325 // check params2326 assert!(2327 item.decimal_points <= MAX_DECIMAL_POINTS,2328 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2329 );2330 assert!(2331 item.name.len() <= 64,2332 "Collection name can not be longer than 63 char"2333 );2334 assert!(2335 item.name.len() <= 256,2336 "Collection description can not be longer than 255 char"2337 );2338 assert!(2339 item.token_prefix.len() <= 16,2340 "Token prefix can not be longer than 15 char"2341 );23422343 // Generate next collection ID2344 let next_id = CreatedCollectionCount::get()2345 .checked_add(1)2346 .unwrap();23472348 CreatedCollectionCount::put(next_id);2349 }23502351 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2352 let current_index = <ItemListIndex>::get(collection_id)2353 .checked_add(1)2354 .unwrap();23552356 let item_owner = item.owner.clone();2357 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();23582359 <ItemListIndex>::insert(collection_id, current_index);23602361 // Update balance2362 let new_balance = <Balance<T>>::get(collection_id, &item_owner)2363 .checked_add(1)2364 .unwrap();2365 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2366 }23672368 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2369 let current_index = <ItemListIndex>::get(collection_id)2370 .checked_add(1)2371 .unwrap();23722373 Self::add_token_index(collection_id, current_index, owner).unwrap();23742375 <ItemListIndex>::insert(collection_id, current_index);23762377 // Update balance2378 let new_balance = <Balance<T>>::get(collection_id, owner)2379 .checked_add(item.value)2380 .unwrap();2381 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2382 }23832384 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2385 let current_index = <ItemListIndex>::get(collection_id)2386 .checked_add(1)2387 .unwrap();23882389 let value = item.owner.first().unwrap().fraction;2390 let owner = item.owner.first().unwrap().owner.clone();23912392 Self::add_token_index(collection_id, current_index, &owner).unwrap();23932394 <ItemListIndex>::insert(collection_id, current_index);23952396 // Update balance2397 let new_balance = <Balance<T>>::get(collection_id, &owner)2398 .checked_add(value)2399 .unwrap();2400 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2401 }24022403 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::AccountId) -> DispatchResult {24042405 // add to account limit2406 if <AccountItemCount<T>>::contains_key(owner) {24072408 // bound Owned tokens by a single address2409 let count = <AccountItemCount<T>>::get(owner);2410 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);24112412 <AccountItemCount<T>>::insert(owner.clone(), count2413 .checked_add(1)2414 .ok_or(Error::<T>::NumOverflow)?);2415 }2416 else {2417 <AccountItemCount<T>>::insert(owner.clone(), 1);2418 }24192420 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2421 if list_exists {2422 let mut list = <AddressTokens<T>>::get(collection_id, owner);2423 let item_contains = list.contains(&item_index.clone());24242425 if !item_contains {2426 list.push(item_index.clone());2427 }24282429 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2430 } else {2431 let mut itm = Vec::new();2432 itm.push(item_index.clone());2433 <AddressTokens<T>>::insert(collection_id, owner.clone(), itm);2434 }24352436 Ok(())2437 }24382439 fn remove_token_index(2440 collection_id: CollectionId,2441 item_index: TokenId,2442 owner: &T::AccountId,2443 ) -> DispatchResult {24442445 // update counter2446 <AccountItemCount<T>>::insert(owner.clone(), 2447 <AccountItemCount<T>>::get(owner)2448 .checked_sub(1)2449 .ok_or(Error::<T>::NumOverflow)?);245024512452 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2453 if list_exists {2454 let mut list = <AddressTokens<T>>::get(collection_id, owner);2455 let item_contains = list.contains(&item_index.clone());24562457 if item_contains {2458 list.retain(|&item| item != item_index);2459 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2460 }2461 }24622463 Ok(())2464 }24652466 fn move_token_index(2467 collection_id: CollectionId,2468 item_index: TokenId,2469 old_owner: &T::AccountId,2470 new_owner: &T::AccountId,2471 ) -> DispatchResult {2472 Self::remove_token_index(collection_id, item_index, old_owner)?;2473 Self::add_token_index(collection_id, item_index, new_owner)?;24742475 Ok(())2476 }2477 2478 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2479 if <ContractOwner<T>>::contains_key(contract.clone()) {2480 let owner = <ContractOwner<T>>::get(contract);2481 ensure!(account == owner, Error::<T>::NoPermission);2482 } else {2483 fail!(Error::<T>::NoPermission);2484 }24852486 Ok(())2487 }2488}24892490////////////////////////////////////////////////////////////////////////////////////////////////////2491// Economic models2492// #region24932494/// Fee multiplier.2495pub type Multiplier = FixedU128;24962497type BalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::Balance;24982499/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2500/// in the queue.2501#[derive(Encode, Decode, Clone, Eq, PartialEq)]2502pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);25032504impl<T: Config + Send + Sync> sp_std::fmt::Debug 2505 for ChargeTransactionPayment<T>2506{2507 #[cfg(feature = "std")]2508 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2509 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2510 }2511 #[cfg(not(feature = "std"))]2512 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2513 Ok(())2514 }2515}25162517impl<T: Config> ChargeTransactionPayment<T>2518where2519 T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2520 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2521 T::AccountId: AsRef<[u8]>,2522 T::AccountId: UncheckedFrom<T::Hash>,2523{2524 fn traditional_fee(2525 len: usize,2526 info: &DispatchInfoOf<T::Call>,2527 tip: BalanceOf<T>,2528 ) -> BalanceOf<T>2529 where2530 T::Call: Dispatchable<Info = DispatchInfo>,2531 {2532 <pallet_transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2533 }25342535 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2536 let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);2537 let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);2538 let len_saturation = max_block_length as u64 / (len as u64).max(1);2539 let coefficient: BalanceOf<T> = weight_saturation2540 .min(len_saturation)2541 .saturated_into::<BalanceOf<T>>();2542 final_fee2543 .saturating_mul(coefficient)2544 .saturated_into::<TransactionPriority>()2545 }25462547 fn withdraw_fee(2548 &self,2549 who: &T::AccountId,2550 call: &T::Call,2551 info: &DispatchInfoOf<T::Call>,2552 len: usize,2553 ) -> Result<2554 (2555 BalanceOf<T>,2556 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2557 ),2558 TransactionValidityError,2559 > {2560 let tip = self.0;25612562 let fee = Self::traditional_fee(len, info, tip);25632564 // Only mess with balances if fee is not zero.2565 if fee.is_zero() {2566 return <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)2567 .map(|i| (fee, i));2568 }25692570 // Determine who is paying transaction fee based on ecnomic model2571 // Parse call to extract collection ID and access collection sponsor2572 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2573 Some(Call::create_item(collection_id, _owner, _properties)) => {25742575 // sponsor timeout2576 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;25772578 let collection = <Collection<T>>::get(collection_id);25792580 let limit = collection.limits.sponsor_transfer_timeout;2581 let mut sponsored = true;2582 if <CreateItemBasket<T>>::contains_key((collection_id, &who)) {2583 let last_tx_block = <CreateItemBasket<T>>::get((collection_id, &who));2584 let limit_time = last_tx_block + limit.into();2585 if block_number <= limit_time {2586 sponsored = false;2587 }2588 }2589 if sponsored {2590 <CreateItemBasket<T>>::insert((collection_id, who.clone()), block_number);2591 }25922593 // check free create limit2594 if (collection.limits.sponsored_data_size >= (_properties.len() as u32)) &&2595 (sponsored)2596 {2597 collection.sponsorship.sponsor()2598 .cloned()2599 .unwrap_or_default()2600 } else {2601 T::AccountId::default()2602 }2603 }2604 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2605 2606 let mut sponsor_transfer = false;2607 if <Collection<T>>::get(collection_id).sponsorship.confirmed() {26082609 let collection_limits = <Collection<T>>::get(collection_id).limits;2610 let collection_mode = <Collection<T>>::get(collection_id).mode;2611 2612 // sponsor timeout2613 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2614 sponsor_transfer = match collection_mode {2615 CollectionMode::NFT => {2616 2617 // get correct limit2618 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2619 collection_limits.sponsor_transfer_timeout2620 } else {2621 ChainLimit::get().nft_sponsor_transfer_timeout2622 };2623 2624 let mut sponsored = true;2625 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2626 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2627 let limit_time = last_tx_block + limit.into();2628 if block_number <= limit_time {2629 sponsored = false;2630 }2631 }2632 if sponsored {2633 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2634 }26352636 sponsored2637 }2638 CollectionMode::Fungible(_) => {2639 2640 // get correct limit2641 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2642 collection_limits.sponsor_transfer_timeout2643 } else {2644 ChainLimit::get().fungible_sponsor_transfer_timeout2645 };2646 2647 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2648 let mut sponsored = true;2649 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2650 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2651 let limit_time = last_tx_block + limit.into();2652 if block_number <= limit_time {2653 sponsored = false;2654 }2655 }2656 if sponsored {2657 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2658 }26592660 sponsored2661 }2662 CollectionMode::ReFungible => {2663 2664 // get correct limit2665 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2666 collection_limits.sponsor_transfer_timeout2667 } else {2668 ChainLimit::get().refungible_sponsor_transfer_timeout2669 };2670 2671 let mut sponsored = true;2672 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2673 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2674 let limit_time = last_tx_block + limit.into();2675 if block_number <= limit_time {2676 sponsored = false;2677 }2678 }2679 if sponsored {2680 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2681 }26822683 sponsored2684 }2685 _ => {2686 false2687 },2688 };2689 }26902691 if !sponsor_transfer {2692 T::AccountId::default()2693 } else {2694 <Collection<T>>::get(collection_id).sponsorship.sponsor()2695 .cloned()2696 .unwrap_or_default()2697 }2698 }26992700 _ => T::AccountId::default(),2701 };27022703 // Sponsor smart contracts2704 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {27052706 // On instantiation: set the contract owner2707 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {27082709 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2710 &who,2711 code_hash,2712 salt,2713 );2714 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());27152716 T::AccountId::default()2717 },27182719 // On instantiation with code: set the contract owner2720 Some(pallet_contracts::Call::instantiate_with_code(_endowment, _gas_limit, _code, _data, _salt)) => {27212722 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2723 &who,2724 &T::Hashing::hash(&_code),2725 _salt,2726 );27272728 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());27292730 T::AccountId::default()2731 }27322733 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2734 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {27352736 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());27372738 let owned_contract = <ContractOwner<T>>::contains_key(called_contract.clone())2739 && <ContractOwner<T>>::get(called_contract.clone()) == *who;2740 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone()) && <ContractWhiteListEnabled<T>>::get(called_contract.clone());2741 2742 if !owned_contract && white_list_enabled {2743 if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2744 return Err(InvalidTransaction::Call.into());2745 }2746 }27472748 let mut sponsor_transfer = false;2749 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2750 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2751 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2752 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2753 let limit_time = last_tx_block + rate_limit;27542755 if block_number >= limit_time {2756 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2757 sponsor_transfer = true;2758 }2759 } else {2760 sponsor_transfer = false;2761 }2762 2763 2764 let mut sp = T::AccountId::default();2765 if sponsor_transfer {2766 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2767 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2768 sp = called_contract;2769 }2770 }2771 }27722773 sp2774 },27752776 _ => sponsor,2777 };27782779 let mut who_pays_fee: T::AccountId = sponsor.clone();2780 if sponsor == T::AccountId::default() {2781 who_pays_fee = who.clone();2782 }27832784 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(&who_pays_fee, call, info, fee, tip)2785 .map(|i| (fee, i))2786 }2787}278827892790impl<T: Config + Send + Sync> SignedExtension2791 for ChargeTransactionPayment<T>2792where2793 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2794 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2795 T::AccountId: AsRef<[u8]>,2796 T::AccountId: UncheckedFrom<T::Hash>,2797{2798 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2799 type AccountId = T::AccountId;2800 type Call = T::Call;2801 type AdditionalSigned = ();2802 type Pre = (2803 // tip2804 BalanceOf<T>,2805 // who pays fee2806 Self::AccountId,2807 // imbalance resulting from withdrawing the fee2808 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2809 );2810 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2811 Ok(())2812 }28132814 fn validate(2815 &self,2816 who: &Self::AccountId,2817 call: &Self::Call,2818 info: &DispatchInfoOf<Self::Call>,2819 len: usize,2820 ) -> TransactionValidity {2821 let (fee, _) = self.withdraw_fee(who, call, info, len)?;2822 Ok(ValidTransaction {2823 priority: Self::get_priority(len, info, fee),2824 ..Default::default()2825 })2826 }28272828 fn pre_dispatch(2829 self,2830 who: &Self::AccountId,2831 call: &Self::Call,2832 info: &DispatchInfoOf<Self::Call>,2833 len: usize,2834 ) -> Result<Self::Pre, TransactionValidityError> {2835 let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2836 Ok((self.0, who.clone(), imbalance))2837 }28382839 fn post_dispatch(2840 pre: Self::Pre,2841 info: &DispatchInfoOf<Self::Call>,2842 post_info: &PostDispatchInfoOf<Self::Call>,2843 len: usize,2844 _result: &DispatchResult,2845 ) -> Result<(), TransactionValidityError> {2846 let (tip, who, imbalance) = pre;2847 let actual_fee = pallet_transaction_payment::Module::<T>::compute_actual_fee(2848 len as u32,2849 info,2850 post_info,2851 tip,2852 );2853 <T as pallet_transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;2854 Ok(())2855 }2856}28572858// #endregionpallets/nft/src/tests.rsdiffbeforeafterboth--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -55,11 +55,11 @@
let saved_col_name: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
let saved_description: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
let saved_prefix: Vec<u8> = b"token_prefix1\0".to_vec();
- assert_eq!(TemplateModule::collection(id).owner, owner);
- assert_eq!(TemplateModule::collection(id).name, saved_col_name);
- assert_eq!(TemplateModule::collection(id).mode, *mode);
- assert_eq!(TemplateModule::collection(id).description, saved_description);
- assert_eq!(TemplateModule::collection(id).token_prefix, saved_prefix);
+ assert_eq!(TemplateModule::collection_id(id).unwrap().owner, owner);
+ assert_eq!(TemplateModule::collection_id(id).unwrap().name, saved_col_name);
+ assert_eq!(TemplateModule::collection_id(id).unwrap().mode, *mode);
+ assert_eq!(TemplateModule::collection_id(id).unwrap().description, saved_description);
+ assert_eq!(TemplateModule::collection_id(id).unwrap().token_prefix, saved_prefix);
id
}
@@ -89,7 +89,7 @@
let collection_id = create_test_collection(&CollectionMode::NFT, 1);
assert_ok!(TemplateModule::set_schema_version(origin1, collection_id, SchemaVersion::Unique));
- assert_eq!(TemplateModule::collection(collection_id).schema_version, SchemaVersion::Unique);
+ assert_eq!(TemplateModule::collection_id(collection_id).unwrap().schema_version, SchemaVersion::Unique);
});
}
@@ -622,7 +622,7 @@
collection_id,
2
));
- assert_eq!(TemplateModule::collection(collection_id).owner, 2);
+ assert_eq!(TemplateModule::collection_id(collection_id).unwrap().owner, 2);
});
}
@@ -1841,8 +1841,8 @@
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::set_const_on_chain_schema(origin1, collection_id, b"test const on chain schema".to_vec()));
- assert_eq!(TemplateModule::collection(collection_id).const_on_chain_schema, b"test const on chain schema".to_vec());
- assert_eq!(TemplateModule::collection(collection_id).variable_on_chain_schema, b"".to_vec());
+ assert_eq!(TemplateModule::collection_id(collection_id).unwrap().const_on_chain_schema, b"test const on chain schema".to_vec());
+ assert_eq!(TemplateModule::collection_id(collection_id).unwrap().variable_on_chain_schema, b"".to_vec());
});
}
@@ -1856,8 +1856,8 @@
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::set_variable_on_chain_schema(origin1, collection_id, b"test variable on chain schema".to_vec()));
- assert_eq!(TemplateModule::collection(collection_id).const_on_chain_schema, b"".to_vec());
- assert_eq!(TemplateModule::collection(collection_id).variable_on_chain_schema, b"test variable on chain schema".to_vec());
+ assert_eq!(TemplateModule::collection_id(collection_id).unwrap().const_on_chain_schema, b"".to_vec());
+ assert_eq!(TemplateModule::collection_id(collection_id).unwrap().variable_on_chain_schema, b"test variable on chain schema".to_vec());
});
}
runtime/src/chain_extension.rsdiffbeforeafterboth--- a/runtime/src/chain_extension.rs
+++ b/runtime/src/chain_extension.rs
@@ -60,7 +60,9 @@
}
let recipient = AccountId32::from(bytes_rec);
- match pallet_nft::Module::<Runtime>::transfer_internal(sender, recipient, input.collection_id, input.token_id, input.amount) {
+ let collection = pallet_nft::Module::<Runtime>::get_collection(input.collection_id)?;
+
+ match pallet_nft::Module::<Runtime>::transfer_internal(sender, recipient, &collection, input.token_id, input.amount) {
Ok(_) => Ok(RetVal::Converging(func_id)),
_ => Err(DispatchError::Other("Transfer error"))
}
runtime/src/nft_weights.rsdiffbeforeafterboth--- a/runtime/src/nft_weights.rs
+++ b/runtime/src/nft_weights.rs
@@ -133,6 +133,11 @@
.saturating_add(DbWeight::get().reads(0 as Weight))
.saturating_add(DbWeight::get().writes(2 as Weight))
}
+ fn set_variable_meta_data_sponsoring_rate_limit() -> Weight {
+ (3_500_000 as Weight)
+ .saturating_add(DbWeight::get().reads(1 as Weight))
+ .saturating_add(DbWeight::get().writes(2 as Weight))
+ }
fn toggle_contract_white_list() -> Weight {
(3_000_000 as Weight)
.saturating_add(DbWeight::get().reads(0 as Weight))
runtime_types.jsondiffbeforeafterboth--- a/runtime_types.json
+++ b/runtime_types.json
@@ -38,7 +38,7 @@
"Confirmed": "AccountId"
}
},
- "CollectionType": {
+ "Collection": {
"Owner": "AccountId",
"Mode": "CollectionMode",
"Access": "AccessMode",
@@ -99,7 +99,8 @@
},
"CollectionLimits": {
"AccountTokenOwnershipLimit": "u32",
- "SponsoredMintSize": "u32",
+ "SponsoredDataSize": "u32",
+ "SponsoredDataRateLimit": "Option<BlockNumber>",
"TokenLimit": "u32",
"SponsorTimeout": "u32",
"OwnerCanTransfer": "bool",
tests/package.jsondiffbeforeafterboth--- a/tests/package.json
+++ b/tests/package.json
@@ -46,7 +46,8 @@
"testEnableContractSponsoring": "mocha --timeout 9999999 -r ts-node/register ./**/enableContractSponsoring.test.ts",
"testSetContractSponsoringRateLimit": "mocha --timeout 9999999 -r ts-node/register ./**/setContractSponsoringRateLimit.test.ts",
"testSetOffchainSchema": "mocha --timeout 9999999 -r ts-node/register ./**/setOffchainSchema.test.ts",
- "testOverflow": "mocha --timeout 9999999 -r ts-node/register ./**/overflow.test.ts"
+ "testOverflow": "mocha --timeout 9999999 -r ts-node/register ./**/overflow.test.ts",
+ "testSetVariableMetadataSponsoringRateLimit": "mocha --timeout 9999999 -r ts-node/register ./**/setVariableMetadataSponsoringRateLimit.test.ts"
},
"author": "",
"license": "SEE LICENSE IN ../LICENSE",
tests/src/addCollectionAdmin.test.tsdiffbeforeafterboth--- a/tests/src/addCollectionAdmin.test.ts
+++ b/tests/src/addCollectionAdmin.test.ts
@@ -1,135 +1,135 @@
-//
-// 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 BN from 'bn.js';
-import chai from 'chai';
-import chaiAsPromised from 'chai-as-promised';
-import privateKey from './substrate/privateKey';
-import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from './substrate/substrate-api';
-import {createCollectionExpectSuccess, destroyCollectionExpectSuccess} from './util/helpers';
-
-chai.use(chaiAsPromised);
-const expect = chai.expect;
-
-describe('Integration Test addCollectionAdmin(collection_id, new_admin_id):', () => {
- it('Add collection admin.', async () => {
- await usingApi(async (api) => {
- const collectionId = await createCollectionExpectSuccess();
- const alice = privateKey('//Alice');
- const bob = privateKey('//Bob');
-
- const collection: any = (await api.query.nft.collection(collectionId));
- expect(collection.Owner.toString()).to.be.eq(alice.address);
-
- const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, bob.address);
- await submitTransactionAsync(alice, changeAdminTx);
-
- const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId));
- expect(adminListAfterAddAdmin).to.be.contains(bob.address);
- });
- });
-
- it('Add admin using added collection admin.', async () => {
- await usingApi(async (api) => {
- const collectionId = await createCollectionExpectSuccess();
- const Alice = privateKey('//Alice');
- const Bob = privateKey('//Bob');
- const Charlie = privateKey('//CHARLIE');
-
- const collection: any = (await api.query.nft.collection(collectionId));
- expect(collection.Owner.toString()).to.be.eq(Alice.address);
-
- const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, Bob.address);
- await submitTransactionAsync(Alice, changeAdminTx);
-
- const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId));
- expect(adminListAfterAddAdmin).to.be.contains(Bob.address);
-
- const changeAdminTxCharlie = api.tx.nft.addCollectionAdmin(collectionId, Charlie.address);
- await submitTransactionAsync(Bob, changeAdminTxCharlie);
- const adminListAfterAddNewAdmin: any = (await api.query.nft.adminList(collectionId));
- expect(adminListAfterAddNewAdmin).to.be.contains(Bob.address);
- expect(adminListAfterAddNewAdmin).to.be.contains(Charlie.address);
- });
- });
-});
-
-describe('Negative Integration Test addCollectionAdmin(collection_id, new_admin_id):', () => {
- it("Not owner can't add collection admin.", async () => {
- await usingApi(async (api) => {
- const collectionId = await createCollectionExpectSuccess();
- const alice = privateKey('//Alice');
- const nonOwner = privateKey('//Bob_stash');
-
- const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, alice.address);
- await expect(submitTransactionExpectFailAsync(nonOwner, changeAdminTx)).to.be.rejected;
-
- const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId));
- expect(adminListAfterAddAdmin).not.to.be.contains(alice.address);
-
- // Verifying that nothing bad happened (network is live, new collections can be created, etc.)
- await createCollectionExpectSuccess();
- });
- });
- it("Can't add collection admin of not existing collection.", async () => {
- await usingApi(async (api) => {
- // tslint:disable-next-line: no-bitwise
- const collectionId = (1 << 32) - 1;
- const alice = privateKey('//Alice');
- const bob = privateKey('//Bob');
-
- const changeOwnerTx = api.tx.nft.addCollectionAdmin(collectionId, bob.address);
- await expect(submitTransactionExpectFailAsync(alice, changeOwnerTx)).to.be.rejected;
-
- // Verifying that nothing bad happened (network is live, new collections can be created, etc.)
- await createCollectionExpectSuccess();
- });
- });
-
- it("Can't add an admin to a destroyed collection.", async () => {
- await usingApi(async (api) => {
- const collectionId = await createCollectionExpectSuccess();
- const Alice = privateKey('//Alice');
- const Bob = privateKey('//Bob');
- await destroyCollectionExpectSuccess(collectionId);
- const changeOwnerTx = api.tx.nft.addCollectionAdmin(collectionId, Bob.address);
- await expect(submitTransactionExpectFailAsync(Alice, changeOwnerTx)).to.be.rejected;
-
- // Verifying that nothing bad happened (network is live, new collections can be created, etc.)
- await createCollectionExpectSuccess();
- });
- });
-
- it('Add an admin to a collection that has reached the maximum number of admins limit', async () => {
- await usingApi(async (api: ApiPromise) => {
- const Alice = privateKey('//Alice');
- const accounts = [
- 'GsvVmjr1CBHwQHw84pPHMDxgNY3iBLz6Qn7qS3CH8qPhrHz',
- 'FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP',
- 'JKspFU6ohf1Grg3Phdzj2pSgWvsYWzSfKghhfzMbdhNBWs5',
- 'Fr4NzY1udSFFLzb2R3qxVQkwz9cZraWkyfH4h3mVVk7BK7P',
- 'DfnTB4z7eUvYRqcGtTpFsLC69o6tvBSC1pEv8vWPZFtCkaK',
- 'HnMAUz7r2G8G3hB27SYNyit5aJmh2a5P4eMdDtACtMFDbam',
- 'DE14BzQ1bDXWPKeLoAqdLAm1GpyAWaWF1knF74cEZeomTBM',
- ];
- const collectionId = await createCollectionExpectSuccess();
-
- const chainLimit = await api.query.nft.chainLimit() as unknown as { CollectionAdminsLimit: BN };
- const chainAdminLimit = chainLimit.CollectionAdminsLimit.toNumber();
- expect(chainAdminLimit).to.be.equal(5);
-
- for (let i = 0; i < chainAdminLimit; i++) {
- const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, accounts[i]);
- await submitTransactionAsync(Alice, changeAdminTx);
- const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId));
- expect(adminListAfterAddAdmin).to.be.contains(accounts[i]);
- }
-
- const tx = api.tx.nft.addCollectionAdmin(collectionId, accounts[chainAdminLimit]);
- await expect(submitTransactionExpectFailAsync(Alice, tx)).to.be.rejected;
- });
- });
-});
+//
+// 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 BN from 'bn.js';
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import privateKey from './substrate/privateKey';
+import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from './substrate/substrate-api';
+import {createCollectionExpectSuccess, destroyCollectionExpectSuccess} from './util/helpers';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+describe('Integration Test addCollectionAdmin(collection_id, new_admin_id):', () => {
+ it('Add collection admin.', async () => {
+ await usingApi(async (api) => {
+ const collectionId = await createCollectionExpectSuccess();
+ const alice = privateKey('//Alice');
+ const bob = privateKey('//Bob');
+
+ const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON();
+ expect(collection.Owner.toString()).to.be.eq(alice.address);
+
+ const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, bob.address);
+ await submitTransactionAsync(alice, changeAdminTx);
+
+ const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId));
+ expect(adminListAfterAddAdmin).to.be.contains(bob.address);
+ });
+ });
+
+ it('Add admin using added collection admin.', async () => {
+ await usingApi(async (api) => {
+ const collectionId = await createCollectionExpectSuccess();
+ const Alice = privateKey('//Alice');
+ const Bob = privateKey('//Bob');
+ const Charlie = privateKey('//CHARLIE');
+
+ const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON();
+ expect(collection.Owner.toString()).to.be.eq(Alice.address);
+
+ const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, Bob.address);
+ await submitTransactionAsync(Alice, changeAdminTx);
+
+ const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId));
+ expect(adminListAfterAddAdmin).to.be.contains(Bob.address);
+
+ const changeAdminTxCharlie = api.tx.nft.addCollectionAdmin(collectionId, Charlie.address);
+ await submitTransactionAsync(Bob, changeAdminTxCharlie);
+ const adminListAfterAddNewAdmin: any = (await api.query.nft.adminList(collectionId));
+ expect(adminListAfterAddNewAdmin).to.be.contains(Bob.address);
+ expect(adminListAfterAddNewAdmin).to.be.contains(Charlie.address);
+ });
+ });
+});
+
+describe('Negative Integration Test addCollectionAdmin(collection_id, new_admin_id):', () => {
+ it("Not owner can't add collection admin.", async () => {
+ await usingApi(async (api) => {
+ const collectionId = await createCollectionExpectSuccess();
+ const alice = privateKey('//Alice');
+ const nonOwner = privateKey('//Bob_stash');
+
+ const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, alice.address);
+ await expect(submitTransactionExpectFailAsync(nonOwner, changeAdminTx)).to.be.rejected;
+
+ const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId));
+ expect(adminListAfterAddAdmin).not.to.be.contains(alice.address);
+
+ // Verifying that nothing bad happened (network is live, new collections can be created, etc.)
+ await createCollectionExpectSuccess();
+ });
+ });
+ it("Can't add collection admin of not existing collection.", async () => {
+ await usingApi(async (api) => {
+ // tslint:disable-next-line: no-bitwise
+ const collectionId = (1 << 32) - 1;
+ const alice = privateKey('//Alice');
+ const bob = privateKey('//Bob');
+
+ const changeOwnerTx = api.tx.nft.addCollectionAdmin(collectionId, bob.address);
+ await expect(submitTransactionExpectFailAsync(alice, changeOwnerTx)).to.be.rejected;
+
+ // Verifying that nothing bad happened (network is live, new collections can be created, etc.)
+ await createCollectionExpectSuccess();
+ });
+ });
+
+ it("Can't add an admin to a destroyed collection.", async () => {
+ await usingApi(async (api) => {
+ const collectionId = await createCollectionExpectSuccess();
+ const Alice = privateKey('//Alice');
+ const Bob = privateKey('//Bob');
+ await destroyCollectionExpectSuccess(collectionId);
+ const changeOwnerTx = api.tx.nft.addCollectionAdmin(collectionId, Bob.address);
+ await expect(submitTransactionExpectFailAsync(Alice, changeOwnerTx)).to.be.rejected;
+
+ // Verifying that nothing bad happened (network is live, new collections can be created, etc.)
+ await createCollectionExpectSuccess();
+ });
+ });
+
+ it('Add an admin to a collection that has reached the maximum number of admins limit', async () => {
+ await usingApi(async (api: ApiPromise) => {
+ const Alice = privateKey('//Alice');
+ const accounts = [
+ 'GsvVmjr1CBHwQHw84pPHMDxgNY3iBLz6Qn7qS3CH8qPhrHz',
+ 'FoQJpPyadYccjavVdTWxpxU7rUEaYhfLCPwXgkfD6Zat9QP',
+ 'JKspFU6ohf1Grg3Phdzj2pSgWvsYWzSfKghhfzMbdhNBWs5',
+ 'Fr4NzY1udSFFLzb2R3qxVQkwz9cZraWkyfH4h3mVVk7BK7P',
+ 'DfnTB4z7eUvYRqcGtTpFsLC69o6tvBSC1pEv8vWPZFtCkaK',
+ 'HnMAUz7r2G8G3hB27SYNyit5aJmh2a5P4eMdDtACtMFDbam',
+ 'DE14BzQ1bDXWPKeLoAqdLAm1GpyAWaWF1knF74cEZeomTBM',
+ ];
+ const collectionId = await createCollectionExpectSuccess();
+
+ const chainLimit = await api.query.nft.chainLimit() as unknown as { CollectionAdminsLimit: BN };
+ const chainAdminLimit = chainLimit.CollectionAdminsLimit.toNumber();
+ expect(chainAdminLimit).to.be.equal(5);
+
+ for (let i = 0; i < chainAdminLimit; i++) {
+ const changeAdminTx = api.tx.nft.addCollectionAdmin(collectionId, accounts[i]);
+ await submitTransactionAsync(Alice, changeAdminTx);
+ const adminListAfterAddAdmin: any = (await api.query.nft.adminList(collectionId));
+ expect(adminListAfterAddAdmin).to.be.contains(accounts[i]);
+ }
+
+ const tx = api.tx.nft.addCollectionAdmin(collectionId, accounts[chainAdminLimit]);
+ await expect(submitTransactionExpectFailAsync(Alice, tx)).to.be.rejected;
+ });
+ });
+});
tests/src/change-collection-owner.test.tsdiffbeforeafterboth--- a/tests/src/change-collection-owner.test.ts
+++ b/tests/src/change-collection-owner.test.ts
@@ -19,13 +19,13 @@
const alice = privateKey('//Alice');
const bob = privateKey('//Bob');
- const collection: any = (await api.query.nft.collection(collectionId));
+ const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON();
expect(collection.Owner.toString()).to.be.eq(alice.address);
const changeOwnerTx = api.tx.nft.changeCollectionOwner(collectionId, bob.address);
await submitTransactionAsync(alice, changeOwnerTx);
- const collectionAfterOwnerChange: any = (await api.query.nft.collection(collectionId));
+ const collectionAfterOwnerChange: any = (await api.query.nft.collectionById(collectionId)).toJSON();
expect(collectionAfterOwnerChange.Owner.toString()).to.be.eq(bob.address);
});
});
@@ -41,7 +41,7 @@
const changeOwnerTx = api.tx.nft.changeCollectionOwner(collectionId, bob.address);
await expect(submitTransactionExpectFailAsync(bob, changeOwnerTx)).to.be.rejected;
- const collectionAfterOwnerChange: any = (await api.query.nft.collection(collectionId));
+ const collectionAfterOwnerChange: any = (await api.query.nft.collectionById(collectionId)).toJSON();
expect(collectionAfterOwnerChange.Owner.toString()).to.be.eq(alice.address);
// Verifying that nothing bad happened (network is live, new collections can be created, etc.)
tests/src/removeCollectionAdmin.test.tsdiffbeforeafterboth--- a/tests/src/removeCollectionAdmin.test.ts
+++ b/tests/src/removeCollectionAdmin.test.ts
@@ -19,7 +19,7 @@
const collectionId = await createCollectionExpectSuccess();
const Alice = privateKey('//Alice');
const Bob = privateKey('//Bob');
- const collection: any = (await api.query.nft.collection(collectionId));
+ const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON();
expect(collection.Owner.toString()).to.be.eq(Alice.address);
// first - add collection admin Bob
const addAdminTx = api.tx.nft.addCollectionAdmin(collectionId, Bob.address);
tests/src/setCollectionLimits.test.tsdiffbeforeafterboth--- a/tests/src/setCollectionLimits.test.ts
+++ b/tests/src/setCollectionLimits.test.ts
@@ -61,12 +61,12 @@
// tslint:disable-next-line:no-unused-expression
expect(result.success).to.be.true;
- expect(collectionInfo.Limits.AccountTokenOwnershipLimit.toNumber()).to.be.equal(accountTokenOwnershipLimit);
- expect(collectionInfo.Limits.SponsoredMintSize.toNumber()).to.be.equal(sponsoredDataSize);
- expect(collectionInfo.Limits.TokenLimit.toNumber()).to.be.equal(tokenLimit);
- expect(collectionInfo.Limits.SponsorTimeout.toNumber()).to.be.equal(sponsorTimeout);
- expect(collectionInfo.Limits.OwnerCanTransfer.valueOf()).to.be.true;
- expect(collectionInfo.Limits.OwnerCanDestroy.valueOf()).to.be.true;
+ expect(collectionInfo.Limits.AccountTokenOwnershipLimit).to.be.equal(accountTokenOwnershipLimit);
+ expect(collectionInfo.Limits.SponsoredDataSize).to.be.equal(sponsoredDataSize);
+ expect(collectionInfo.Limits.TokenLimit).to.be.equal(tokenLimit);
+ expect(collectionInfo.Limits.SponsorTimeout).to.be.equal(sponsorTimeout);
+ expect(collectionInfo.Limits.OwnerCanTransfer).to.be.true;
+ expect(collectionInfo.Limits.OwnerCanDestroy).to.be.true;
});
});
});
tests/src/setConstOnChainSchema.test.tsdiffbeforeafterboth--- a/tests/src/setConstOnChainSchema.test.ts
+++ b/tests/src/setConstOnChainSchema.test.ts
@@ -36,7 +36,7 @@
it('Run extrinsic with parameters of the collection id, set the scheme', async () => {
await usingApi(async (api) => {
const collectionId = await createCollectionExpectSuccess();
- const collection: any = (await api.query.nft.collection(collectionId));
+ const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON();
expect(collection.Owner.toString()).to.be.eq(Alice.address);
const setShema = api.tx.nft.setConstOnChainSchema(collectionId, Shema);
await submitTransactionAsync(Alice, setShema);
@@ -48,7 +48,7 @@
const collectionId = await createCollectionExpectSuccess();
const setShema = api.tx.nft.setConstOnChainSchema(collectionId, Shema);
await submitTransactionAsync(Alice, setShema);
- const collection: any = (await api.query.nft.collection(collectionId));
+ const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON();
expect(collection.ConstOnChainSchema.toString()).to.be.eq(Shema);
});
@@ -86,7 +86,7 @@
it('Execute method not on behalf of the collection owner', async () => {
await usingApi(async (api) => {
const collectionId = await createCollectionExpectSuccess();
- const collection: any = (await api.query.nft.collection(collectionId));
+ const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON();
expect(collection.Owner.toString()).to.be.eq(Alice.address);
const setShema = api.tx.nft.setConstOnChainSchema(collectionId, Shema);
await expect(submitTransactionExpectFailAsync(Bob, setShema)).to.be.rejected;
tests/src/setOffchainSchema.test.tsdiffbeforeafterboth--- a/tests/src/setOffchainSchema.test.ts
+++ b/tests/src/setOffchainSchema.test.ts
@@ -36,7 +36,7 @@
await setOffchainSchemaExpectSuccess(alice, collectionId, DATA);
const collection = await queryCollectionExpectSuccess(collectionId);
- expect(Array.from(collection.OffchainSchema)).to.be.deep.equal(DATA);
+ expect(collection.OffchainSchema).to.be.equal('0x' + Buffer.from(DATA).toString('hex'));
});
});
tests/src/setVariableMetadataSponsoringRateLimit.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/setVariableMetadataSponsoringRateLimit.test.ts
@@ -0,0 +1,80 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import { IKeyringPair } from '@polkadot/types/types';
+import privateKey from './substrate/privateKey';
+import usingApi from './substrate/substrate-api';
+import {
+ confirmSponsorshipExpectSuccess,
+ createCollectionExpectSuccess,
+ createItemExpectSuccess,
+ findUnusedAddress,
+ setCollectionLimitsExpectSuccess,
+ setCollectionSponsorExpectSuccess,
+ setVariableMetaDataExpectFailure,
+ setVariableMetaDataExpectSuccess,
+} from './util/helpers';
+
+describe('Integration Test setVariableMetadataSponsoringRateLimit', () => {
+ let alice: IKeyringPair;
+ let userWithNoBalance: IKeyringPair;
+
+ before(async () => {
+ await usingApi(async (api) => {
+ alice = privateKey('//Alice');
+ userWithNoBalance = await findUnusedAddress(api);
+ });
+ });
+
+ it('sponsored setVariableMetaData can be called twice with pause for free', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionSponsorExpectSuccess(collectionId, alice.address);
+ await confirmSponsorshipExpectSuccess(collectionId);
+ await setCollectionLimitsExpectSuccess(alice, collectionId, {
+ SponsoredDataRateLimit: 0,
+ });
+
+ const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', userWithNoBalance.address);
+ await setVariableMetaDataExpectSuccess(userWithNoBalance, collectionId, itemId, [1, 2, 3]);
+ await setVariableMetaDataExpectSuccess(userWithNoBalance, collectionId, itemId, [1, 2, 3]);
+ });
+
+ it('sponsored setVariableMetaData can\'t be called twice without pause for free', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionSponsorExpectSuccess(collectionId, alice.address);
+ await confirmSponsorshipExpectSuccess(collectionId);
+ await setCollectionLimitsExpectSuccess(alice, collectionId, {
+ SponsoredDataRateLimit: 10,
+ });
+
+ const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', userWithNoBalance.address);
+ await setVariableMetaDataExpectSuccess(userWithNoBalance, collectionId, itemId, [1, 2, 3]);
+ await setVariableMetaDataExpectFailure(userWithNoBalance, collectionId, itemId, [1, 2, 3]);
+ });
+
+ it('sponsored setVariableMetaData can\'t be called for free with variable metadata above collection limits', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionSponsorExpectSuccess(collectionId, alice.address);
+ await confirmSponsorshipExpectSuccess(collectionId);
+ await setCollectionLimitsExpectSuccess(alice, collectionId, {
+ SponsoredDataRateLimit: 0,
+ SponsoredDataSize: 1,
+ });
+ const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', userWithNoBalance.address);
+
+ await setVariableMetaDataExpectSuccess(userWithNoBalance, collectionId, itemId, [1]);
+ await setVariableMetaDataExpectFailure(userWithNoBalance, collectionId, itemId, [1, 2]);
+ });
+
+ it.only('Default value of rate limit does not sponsor setting variable metadata', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionSponsorExpectSuccess(collectionId, alice.address);
+ await confirmSponsorshipExpectSuccess(collectionId);
+
+ const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', userWithNoBalance.address);
+ await setVariableMetaDataExpectFailure(userWithNoBalance, collectionId, itemId, [1]);
+ });
+
+});
tests/src/setVariableOnChainSchema.test.tsdiffbeforeafterboth--- a/tests/src/setVariableOnChainSchema.test.ts
+++ b/tests/src/setVariableOnChainSchema.test.ts
@@ -1,96 +1,96 @@
-//
-// This file is subject to the terms and conditions defined in
-// file 'LICENSE', which is part of this source code package.
-//
-
-import { Keyring } from '@polkadot/api';
-import { IKeyringPair } from '@polkadot/types/types';
-import chai from 'chai';
-import chaiAsPromised from 'chai-as-promised';
-import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from './substrate/substrate-api';
-import {
- createCollectionExpectSuccess,
- destroyCollectionExpectSuccess,
-} from './util/helpers';
-
-chai.use(chaiAsPromised);
-const expect = chai.expect;
-
-let Alice: IKeyringPair;
-let Bob: IKeyringPair;
-let Schema: any;
-let largeSchema: any;
-
-before(async () => {
- await usingApi(async (api) => {
- const keyring = new Keyring({ type: 'sr25519' });
- Alice = keyring.addFromUri('//Alice');
- Bob = keyring.addFromUri('//Bob');
- Schema = '0x31';
- largeSchema = new Array(4097).fill(0xff);
-
- });
-});
-describe('Integration Test ext. setVariableOnChainSchema()', () => {
-
- it('Run extrinsic with parameters of the collection id, set the scheme', async () => {
- await usingApi(async (api) => {
- const collectionId = await createCollectionExpectSuccess();
- const collection: any = (await api.query.nft.collection(collectionId));
- expect(collection.Owner.toString()).to.be.eq(Alice.address);
- const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema);
- await submitTransactionAsync(Alice, setSchema);
- });
- });
-
- it('Checking collection data using the setVariableOnChainSchema parameter', async () => {
- await usingApi(async (api) => {
- const collectionId = await createCollectionExpectSuccess();
- const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema);
- await submitTransactionAsync(Alice, setSchema);
- const collection: any = (await api.query.nft.collection(collectionId));
- expect(collection.VariableOnChainSchema.toString()).to.be.eq(Schema);
-
- });
- });
-});
-
-describe('Negative Integration Test ext. setVariableOnChainSchema()', () => {
-
- it('Set a non-existent collection', async () => {
- await usingApi(async (api) => {
- // tslint:disable-next-line: radix
- const collectionId = parseInt((await api.query.nft.createdCollectionCount()).toString()) + 1;
- const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema);
- await expect(submitTransactionExpectFailAsync(Alice, setSchema)).to.be.rejected;
- });
- });
-
- it('Set a previously deleted collection', async () => {
- await usingApi(async (api) => {
- const collectionId = await createCollectionExpectSuccess();
- await destroyCollectionExpectSuccess(collectionId);
- const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema);
- await expect(submitTransactionExpectFailAsync(Alice, setSchema)).to.be.rejected;
- });
- });
-
- it('Set invalid data in schema (size too large:> 1024b)', async () => {
- await usingApi(async (api) => {
- const collectionId = await createCollectionExpectSuccess();
- const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, largeSchema);
- await expect(submitTransactionExpectFailAsync(Alice, setSchema)).to.be.rejected;
- });
- });
-
- it('Execute method not on behalf of the collection owner', async () => {
- await usingApi(async (api) => {
- const collectionId = await createCollectionExpectSuccess();
- const collection: any = (await api.query.nft.collection(collectionId));
- expect(collection.Owner.toString()).to.be.eq(Alice.address);
- const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema);
- await expect(submitTransactionExpectFailAsync(Bob, setSchema)).to.be.rejected;
- });
- });
-
-});
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import { Keyring } from '@polkadot/api';
+import { IKeyringPair } from '@polkadot/types/types';
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from './substrate/substrate-api';
+import {
+ createCollectionExpectSuccess,
+ destroyCollectionExpectSuccess,
+} from './util/helpers';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+let Alice: IKeyringPair;
+let Bob: IKeyringPair;
+let Schema: any;
+let largeSchema: any;
+
+before(async () => {
+ await usingApi(async (api) => {
+ const keyring = new Keyring({ type: 'sr25519' });
+ Alice = keyring.addFromUri('//Alice');
+ Bob = keyring.addFromUri('//Bob');
+ Schema = '0x31';
+ largeSchema = new Array(4097).fill(0xff);
+
+ });
+});
+describe('Integration Test ext. setVariableOnChainSchema()', () => {
+
+ it('Run extrinsic with parameters of the collection id, set the scheme', async () => {
+ await usingApi(async (api) => {
+ const collectionId = await createCollectionExpectSuccess();
+ const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON();
+ expect(collection.Owner.toString()).to.be.eq(Alice.address);
+ const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema);
+ await submitTransactionAsync(Alice, setSchema);
+ });
+ });
+
+ it('Checking collection data using the setVariableOnChainSchema parameter', async () => {
+ await usingApi(async (api) => {
+ const collectionId = await createCollectionExpectSuccess();
+ const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema);
+ await submitTransactionAsync(Alice, setSchema);
+ const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON();
+ expect(collection.VariableOnChainSchema.toString()).to.be.eq(Schema);
+
+ });
+ });
+});
+
+describe('Negative Integration Test ext. setVariableOnChainSchema()', () => {
+
+ it('Set a non-existent collection', async () => {
+ await usingApi(async (api) => {
+ // tslint:disable-next-line: radix
+ const collectionId = parseInt((await api.query.nft.createdCollectionCount()).toString()) + 1;
+ const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema);
+ await expect(submitTransactionExpectFailAsync(Alice, setSchema)).to.be.rejected;
+ });
+ });
+
+ it('Set a previously deleted collection', async () => {
+ await usingApi(async (api) => {
+ const collectionId = await createCollectionExpectSuccess();
+ await destroyCollectionExpectSuccess(collectionId);
+ const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema);
+ await expect(submitTransactionExpectFailAsync(Alice, setSchema)).to.be.rejected;
+ });
+ });
+
+ it('Set invalid data in schema (size too large:> 1024b)', async () => {
+ await usingApi(async (api) => {
+ const collectionId = await createCollectionExpectSuccess();
+ const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, largeSchema);
+ await expect(submitTransactionExpectFailAsync(Alice, setSchema)).to.be.rejected;
+ });
+ });
+
+ it('Execute method not on behalf of the collection owner', async () => {
+ await usingApi(async (api) => {
+ const collectionId = await createCollectionExpectSuccess();
+ const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON();
+ expect(collection.Owner.toString()).to.be.eq(Alice.address);
+ const setSchema = api.tx.nft.setVariableOnChainSchema(collectionId, Schema);
+ await expect(submitTransactionExpectFailAsync(Bob, setSchema)).to.be.rejected;
+ });
+ });
+
+});
tests/src/types.tsdiffbeforeafterboth--- a/tests/src/types.ts
+++ b/tests/src/types.ts
@@ -13,10 +13,11 @@
Description: [BN, BN]; // utf16
isReFungible: boolean;
Limits: {
- AccountTokenOwnershipLimit: BN;
- SponsoredMintSize: BN;
- TokenLimit: BN;
- SponsorTimeout: BN;
+ AccountTokenOwnershipLimit: number;
+ SponsoredDataSize: number;
+ SponsoredDataRateLimit?: number,
+ TokenLimit: number;
+ SponsorTimeout: number;
OwnerCanTransfer: boolean;
OwnerCanDestroy: boolean;
};
tests/src/util/contracthelpers.tsdiffbeforeafterboth--- a/tests/src/util/contracthelpers.ts
+++ b/tests/src/util/contracthelpers.ts
@@ -22,9 +22,9 @@
function deployContract(alice: IKeyringPair, code: CodePromise, constructor: string = 'default', ...args: any[]): Promise<Contract> {
return new Promise<Contract>(async (resolve, reject) => {
- const unsub = await code
+ const unsub = await (code as any)
.tx[constructor]({value: endowment, gasLimit}, ...args)
- .signAndSend(alice, (result) => {
+ .signAndSend(alice, (result: any) => {
if (result.status.isInBlock || result.status.isFinalized) {
// here we have an additional field in the result, containing the blueprint
resolve((result as any).contract);
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -212,7 +212,7 @@
const BcollectionCount = parseInt((await api.query.nft.createdCollectionCount()).toString(), 10);
// Get the collection
- const collection: any = (await api.query.nft.collection(result.collectionId)).toJSON();
+ const collection: any = (await api.query.nft.collectionById(result.collectionId)).toJSON();
// What to expect
// tslint:disable-next-line:no-unused-expression
@@ -324,18 +324,17 @@
const result = getDestroyResult(events);
// Get the collection
- const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+ const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON();
// What to expect
expect(result).to.be.true;
- expect(collection).to.be.not.null;
- expect(collection.Owner).to.be.equal(nullPublicKey);
+ expect(collection).to.be.null;
});
}
export async function queryCollectionLimits(collectionId: number) {
return await usingApi(async (api) => {
- return ((await api.query.nft.collection(collectionId)).toJSON() as any).Limits;
+ return ((await api.query.nft.collectionById(collectionId)).toJSON() as any).Limits;
});
}
@@ -373,7 +372,7 @@
const result = getGenericResult(events);
// Get the collection
- const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+ const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON();
// What to expect
expect(result.success).to.be.true;
@@ -393,7 +392,7 @@
const result = getGenericResult(events);
// Get the collection
- const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+ const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON();
// What to expect
expect(result.success).to.be.true;
@@ -431,7 +430,7 @@
const result = getGenericResult(events);
// Get the collection
- const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+ const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON();
// What to expect
expect(result.success).to.be.true;
@@ -839,7 +838,7 @@
const result = getGenericResult(events);
// Get the collection
- const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+ const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON();
// What to expect
// tslint:disable-next-line:no-unused-expression
@@ -865,7 +864,7 @@
const result = getGenericResult(events);
// Get the collection
- const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+ const collection: any = (await api.query.nft.collectionById(collectionId)).toJSON();
// What to expect
// tslint:disable-next-line:no-unused-expression
@@ -947,7 +946,7 @@
export const getDetailedCollectionInfo = async (api: ApiPromise, collectionId: number)
: Promise<ICollectionInterface | null> => {
- return await api.query.nft.collection(collectionId) as unknown as ICollectionInterface;
+ return (await api.query.nft.collectionById(collectionId)).toJSON() as unknown as ICollectionInterface;
};
export const getCreatedCollectionCount = async (api: ApiPromise): Promise<number> => {
@@ -957,6 +956,6 @@
export async function queryCollectionExpectSuccess(collectionId: number): Promise<ICollectionInterface> {
return await usingApi(async (api) => {
- return (await api.query.nft.collection(collectionId)) as unknown as ICollectionInterface;
+ return (await api.query.nft.collectionById(collectionId)).toJSON() as unknown as ICollectionInterface;
});
}