difftreelog
NFTPAR-164. Schema version
in: master
4 files changed
node/Cargo.tomldiffbeforeafterboth--- a/node/Cargo.toml
+++ b/node/Cargo.toml
@@ -23,6 +23,7 @@
[dependencies]
futures = '0.3.4'
log = '0.4.8'
+flexi_logger = "0.15.7"
parking_lot = '0.10.0'
structopt = '0.3.8'
jsonrpc-core = '15.0.0'
node/src/chain_spec.rsdiffbeforeafterboth--- a/node/src/chain_spec.rs
+++ b/node/src/chain_spec.rs
@@ -167,7 +167,8 @@
description: vec![],
token_prefix: vec![],
mint_mode: false,
- offchain_schema: vec![],
+ offchain_schema: vec![],
+ schema_version: SchemaVersion::default(),
sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
unconfirmed_sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
const_on_chain_schema: vec![],
pallets/nft/src/lib.rsdiffbeforeafterboth1#![cfg_attr(not(feature = "std"), no_std)]23#[cfg(feature = "std")]4pub use std::*;56#[cfg(feature = "std")]7pub use serde::*;89use codec::{Decode, Encode};10pub use frame_support::{11 construct_runtime, decl_event, decl_module, decl_storage, decl_error,12 dispatch::DispatchResult,13 ensure, fail, parameter_types,14 traits::{15 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,16 Randomness, WithdrawReason,17 },18 weights::{19 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},20 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,21 WeightToFeePolynomial,22 },23 IsSubType, StorageValue,24};2526use frame_system::{self as system, ensure_signed, ensure_root};27use sp_runtime::sp_std::prelude::Vec;28use sp_runtime::{29 traits::{30 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SignedExtension, Zero,31 },32 transaction_validity::{33 InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,34 },35 FixedPointOperand, FixedU128,36};37use pallet_contracts::ContractAddressFor;38use sp_runtime::traits::StaticLookup;3940#[cfg(test)]41mod mock;4243#[cfg(test)]44mod tests;4546mod default_weights;4748pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;49pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;50pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;5152// Structs53// #region5455pub type CollectionId = u32;56pub type TokenId = u32;5758pub type DecimalPoints = u8;5960#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]61#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]62pub enum CollectionMode {63 Invalid,64 NFT,65 // decimal points66 Fungible(DecimalPoints),67 // decimal points68 ReFungible(DecimalPoints),69}7071impl Into<u8> for CollectionMode {72 fn into(self) -> u8 {73 match self {74 CollectionMode::Invalid => 0,75 CollectionMode::NFT => 1,76 CollectionMode::Fungible(_) => 2,77 CollectionMode::ReFungible(_) => 3,78 }79 }80}8182#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]83#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]84pub enum AccessMode {85 Normal,86 WhiteList,87}88impl Default for AccessMode {89 fn default() -> Self {90 Self::Normal91 }92}9394impl Default for CollectionMode {95 fn default() -> Self {96 Self::Invalid97 }98}99100#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]101#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]102pub struct Ownership<AccountId> {103 pub owner: AccountId,104 pub fraction: u128,105}106107#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]108#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]109pub struct CollectionType<AccountId> {110 pub owner: AccountId,111 pub mode: CollectionMode,112 pub access: AccessMode,113 pub decimal_points: DecimalPoints,114 pub name: Vec<u16>, // 64 include null escape char115 pub description: Vec<u16>, // 256 include null escape char116 pub token_prefix: Vec<u8>, // 16 include null escape char117 pub mint_mode: bool,118 pub offchain_schema: Vec<u8>,119 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender120 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship121 pub limits: CollectionLimits, // Collection private restrictions 122 pub variable_on_chain_schema: Vec<u8>, //123 pub const_on_chain_schema: Vec<u8>, //124}125126#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]127#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]128pub struct NftItemType<AccountId> {129 pub collection: CollectionId,130 pub owner: AccountId,131 pub const_data: Vec<u8>,132 pub variable_data: Vec<u8>,133}134135#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]136#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]137pub struct FungibleItemType<AccountId> {138 pub collection: CollectionId,139 pub owner: AccountId,140 pub value: u128,141}142143#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]144#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]145pub struct ReFungibleItemType<AccountId> {146 pub collection: CollectionId,147 pub owner: Vec<Ownership<AccountId>>,148 pub const_data: Vec<u8>,149 pub variable_data: Vec<u8>,150}151152#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]153#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]154pub struct ApprovePermissions<AccountId> {155 pub approved: AccountId,156 pub amount: u128,157}158159#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]160#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]161pub struct VestingItem<AccountId, Moment> {162 pub sender: AccountId,163 pub recipient: AccountId,164 pub collection_id: CollectionId,165 pub item_id: TokenId,166 pub amount: u64,167 pub vesting_date: Moment,168}169170#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]171#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]172pub struct BasketItem<AccountId, BlockNumber> {173 pub address: AccountId,174 pub start_block: BlockNumber,175}176177#[derive(Encode, Decode, Debug, Clone, PartialEq)]178#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]179pub struct CollectionLimits {180 pub account_token_ownership_limit: u32,181 pub sponsored_data_size: u32,182 pub token_limit: u32,183184 // Timeouts for item types in passed blocks185 pub sponsor_transfer_timeout: u32,186}187188impl Default for CollectionLimits {189 fn default() -> CollectionLimits {190 CollectionLimits { 191 account_token_ownership_limit: 10_000_000, 192 token_limit: u32::max_value(),193 sponsored_data_size: u32::max_value(), 194 sponsor_transfer_timeout: 14400 }195 }196}197198#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]199#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]200pub struct ChainLimits {201 pub collection_numbers_limit: u32,202 pub account_token_ownership_limit: u32,203 pub collections_admins_limit: u64,204 pub custom_data_limit: u32,205206 // Timeouts for item types in passed blocks207 pub nft_sponsor_transfer_timeout: u32,208 pub fungible_sponsor_transfer_timeout: u32,209 pub refungible_sponsor_transfer_timeout: u32,210}211212pub trait WeightInfo {213 fn create_collection() -> Weight;214 fn destroy_collection() -> Weight;215 fn add_to_white_list() -> Weight;216 fn remove_from_white_list() -> Weight;217 fn set_public_access_mode() -> Weight;218 fn set_mint_permission() -> Weight;219 fn change_collection_owner() -> Weight;220 fn add_collection_admin() -> Weight;221 fn remove_collection_admin() -> Weight;222 fn set_collection_sponsor() -> Weight;223 fn confirm_sponsorship() -> Weight;224 fn remove_collection_sponsor() -> Weight;225 fn create_item(s: usize) -> Weight;226 fn burn_item() -> Weight;227 fn transfer() -> Weight;228 fn approve() -> Weight;229 fn transfer_from() -> Weight;230 fn set_offchain_schema() -> Weight;231 fn set_const_on_chain_schema() -> Weight;232 fn set_variable_on_chain_schema() -> Weight;233 fn set_variable_meta_data() -> Weight;234 fn enable_contract_sponsoring() -> Weight;235}236237#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]238#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]239pub struct CreateNftData {240 pub const_data: Vec<u8>,241 pub variable_data: Vec<u8>,242}243244#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]245#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]246pub struct CreateFungibleData {247}248249#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]250#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]251pub struct CreateReFungibleData {252 pub const_data: Vec<u8>,253 pub variable_data: Vec<u8>,254}255256#[derive(Encode, Decode, Debug, Clone, PartialEq)]257#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]258pub enum CreateItemData {259 NFT(CreateNftData),260 Fungible(CreateFungibleData),261 ReFungible(CreateReFungibleData)262}263264impl CreateItemData {265 pub fn len(&self) -> usize {266 let len = match self {267 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),268 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),269 _ => 0270 };271 272 return len;273 }274}275276impl From<CreateNftData> for CreateItemData {277 fn from(item: CreateNftData) -> Self {278 CreateItemData::NFT(item)279 }280}281282impl From<CreateReFungibleData> for CreateItemData {283 fn from(item: CreateReFungibleData) -> Self {284 CreateItemData::ReFungible(item)285 }286}287288impl From<CreateFungibleData> for CreateItemData {289 fn from(item: CreateFungibleData) -> Self {290 CreateItemData::Fungible(item)291 }292}293294295decl_error! {296 /// Error for non-fungible-token module.297 pub enum Error for Module<T: Trait> {298 /// Total collections bound exceeded.299 TotalCollectionsLimitExceeded,300 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.301 CollectionDecimalPointLimitExceeded, 302 /// Collection name can not be longer than 63 char.303 CollectionNameLimitExceeded, 304 /// Collection description can not be longer than 255 char.305 CollectionDescriptionLimitExceeded, 306 /// Token prefix can not be longer than 15 char.307 CollectionTokenPrefixLimitExceeded,308 /// This collection does not exist.309 CollectionNotFound,310 /// Item not exists.311 TokenNotFound,312 /// Arithmetic calculation overflow.313 NumOverflow, 314 /// Account already has admin role.315 AlreadyAdmin, 316 /// You do not own this collection.317 NoPermission,318 /// This address is not set as sponsor, use setCollectionSponsor first.319 ConfirmUnsetSponsorFail,320 /// Collection is not in mint mode.321 PublicMintingNotAllowed,322 /// Sender parameter and item owner must be equal.323 MustBeTokenOwner,324 /// Item balance not enough.325 TokenValueTooLow,326 /// Size of item is too large.327 NftSizeLimitExceeded,328 /// No approve found329 ApproveNotFound,330 /// Requested value more than approved.331 TokenValueNotEnough,332 /// Only approved addresses can call this method.333 ApproveRequired,334 /// Address is not in white list.335 AddresNotInWhiteList,336 /// Number of collection admins bound exceeded.337 CollectionAdminsLimitExceeded,338 /// Owned tokens by a single address bound exceeded.339 AddressOwnershipLimitExceeded,340 /// Length of items properties must be greater than 0.341 EmptyArgument,342 /// const_data exceeded data limit.343 TokenConstDataLimitExceeded,344 /// variable_data exceeded data limit.345 TokenVariableDataLimitExceeded,346 /// Not NFT item data used to mint in NFT collection.347 NotNftDataUsedToMintNftCollectionToken,348 /// Not Fungible item data used to mint in Fungible collection.349 NotFungibleDataUsedToMintFungibleCollectionToken,350 /// Not Re Fungible item data used to mint in Re Fungible collection.351 NotReFungibleDataUsedToMintReFungibleCollectionToken,352 /// Unexpected collection type.353 UnexpectedCollectionType,354 /// Can't store metadata in fungible tokens.355 CantStoreMetadataInFungibleTokens,356 /// Collection token limit exceeded357 CollectionTokenLimitExceeded,358 /// Account token limit exceeded per collection359 AccountTokenLimitExceeded,360 /// Collection limit bounds per collection exceeded361 CollectionLimitBoundsExceeded362 }363}364365pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {366 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;367368 /// Weight information for extrinsics in this pallet.369 type WeightInfo: WeightInfo;370}371372#[cfg(feature = "runtime-benchmarks")]373mod benchmarking;374375// #endregion376377decl_storage! {378 trait Store for Module<T: Trait> as Nft {379380 // Private members381 NextCollectionID: CollectionId;382 CreatedCollectionCount: u32;383 ChainVersion: u64;384 ItemListIndex: map hasher(identity) CollectionId => TokenId;385386 // Chain limits struct387 pub ChainLimit get(fn chain_limit) config(): ChainLimits;388389 // Bound counters390 CollectionCount: u32;391 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;392393 // Basic collections394 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;395 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;396 pub WhiteList get(fn white_list): map hasher(identity) CollectionId => Vec<T::AccountId>;397398 /// Balance owner per collection map399 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;400401 /// second parameter: item id + owner account id402 pub ApprovedList get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;403404 /// Item collections405 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;406 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => FungibleItemType<T::AccountId>;407 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;408409 /// Index list410 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;411412 /// Tokens transfer baskets413 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;414 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => Vec<BasketItem<T::AccountId, T::BlockNumber>>;415 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;416417 // Contract Sponsorship and Ownership418 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;419 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;420 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;421 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;422 }423 add_extra_genesis {424 build(|config: &GenesisConfig<T>| {425 // Modification of storage426 for (_num, _c) in &config.collection {427 <Module<T>>::init_collection(_c);428 }429430 for (_num, _q, _i) in &config.nft_item_id {431 <Module<T>>::init_nft_token(_i);432 }433434 for (_num, _q, _i) in &config.fungible_item_id {435 <Module<T>>::init_fungible_token(_i);436 }437438 for (_num, _q, _i) in &config.refungible_item_id {439 <Module<T>>::init_refungible_token(_i);440 }441 })442 }443}444445decl_event!(446 pub enum Event<T>447 where448 AccountId = <T as system::Trait>::AccountId,449 {450 /// New collection was created451 /// 452 /// # Arguments453 /// 454 /// * collection_id: Globally unique identifier of newly created collection.455 /// 456 /// * mode: [CollectionMode] converted into u8.457 /// 458 /// * account_id: Collection owner.459 Created(CollectionId, u8, AccountId),460461 /// New item was created.462 /// 463 /// # Arguments464 /// 465 /// * collection_id: Id of the collection where item was created.466 /// 467 /// * item_id: Id of an item. Unique within the collection.468 ItemCreated(CollectionId, TokenId),469470 /// Collection item was burned.471 /// 472 /// # Arguments473 /// 474 /// collection_id.475 /// 476 /// item_id: Identifier of burned NFT.477 ItemDestroyed(CollectionId, TokenId),478 }479);480481decl_module! {482 pub struct Module<T: Trait> for enum Call where origin: T::Origin {483484 fn deposit_event() = default;485 type Error = Error<T>;486487 fn on_initialize(now: T::BlockNumber) -> Weight {488489 if ChainVersion::get() < 2490 {491 let value = NextCollectionID::get();492 CreatedCollectionCount::put(value);493 ChainVersion::put(2);494 }495496 0497 }498499 /// 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.500 /// 501 /// # Permissions502 /// 503 /// * Anyone.504 /// 505 /// # Arguments506 /// 507 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.508 /// 509 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.510 /// 511 /// * token_prefix: UTF-8 string with token prefix.512 /// 513 /// * mode: [CollectionMode] collection type and type dependent data.514 // returns collection ID515 #[weight = T::WeightInfo::create_collection()]516 pub fn create_collection(origin,517 collection_name: Vec<u16>,518 collection_description: Vec<u16>,519 token_prefix: Vec<u8>,520 mode: CollectionMode) -> DispatchResult {521522 // Anyone can create a collection523 let who = ensure_signed(origin)?;524525 let decimal_points = match mode {526 CollectionMode::Fungible(points) => points,527 CollectionMode::ReFungible(points) => points,528 _ => 0529 };530531 // bound Total number of collections532 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);533534 // check params535 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);536537 let mut name = collection_name.to_vec();538 name.push(0);539 ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);540541 let mut description = collection_description.to_vec();542 description.push(0);543 ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);544545 let mut prefix = token_prefix.to_vec();546 prefix.push(0);547 ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);548549 // Generate next collection ID550 let next_id = CreatedCollectionCount::get()551 .checked_add(1)552 .ok_or(Error::<T>::NumOverflow)?;553554 // bound counter555 let total = CollectionCount::get()556 .checked_add(1)557 .ok_or(Error::<T>::NumOverflow)?;558559 CreatedCollectionCount::put(next_id);560 CollectionCount::put(total);561562 // Create new collection563 let new_collection = CollectionType {564 owner: who.clone(),565 name: name,566 mode: mode.clone(),567 mint_mode: false,568 access: AccessMode::Normal,569 description: description,570 decimal_points: decimal_points,571 token_prefix: prefix,572 offchain_schema: Vec::new(),573 sponsor: T::AccountId::default(),574 unconfirmed_sponsor: T::AccountId::default(),575 variable_on_chain_schema: Vec::new(),576 const_on_chain_schema: Vec::new(),577 limits: CollectionLimits::default(),578 };579580 // Add new collection to map581 <Collection<T>>::insert(next_id, new_collection);582583 // call event584 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));585586 Ok(())587 }588589 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.590 /// 591 /// # Permissions592 /// 593 /// * Collection Owner.594 /// 595 /// # Arguments596 /// 597 /// * collection_id: collection to destroy.598 #[weight = T::WeightInfo::destroy_collection()]599 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {600601 let sender = ensure_signed(origin)?;602 Self::check_owner_permissions(collection_id, sender)?;603604 <AddressTokens<T>>::remove_prefix(collection_id);605 <ApprovedList<T>>::remove_prefix(collection_id);606 <Balance<T>>::remove_prefix(collection_id);607 <ItemListIndex>::remove(collection_id);608 <AdminList<T>>::remove(collection_id);609 <Collection<T>>::remove(collection_id);610 <WhiteList<T>>::remove(collection_id);611612 <NftItemList<T>>::remove_prefix(collection_id);613 <FungibleItemList<T>>::remove_prefix(collection_id);614 <ReFungibleItemList<T>>::remove_prefix(collection_id);615616 <NftTransferBasket<T>>::remove_prefix(collection_id);617 <FungibleTransferBasket<T>>::remove_prefix(collection_id);618 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);619620 if CollectionCount::get() > 0621 {622 // bound couter623 let total = CollectionCount::get()624 .checked_sub(1)625 .ok_or(Error::<T>::NumOverflow)?;626627 CollectionCount::put(total);628 }629630 Ok(())631 }632633 /// Add an address to white list.634 /// 635 /// # Permissions636 /// 637 /// * Collection Owner638 /// * Collection Admin639 /// 640 /// # Arguments641 /// 642 /// * collection_id.643 /// 644 /// * address.645 #[weight = T::WeightInfo::add_to_white_list()]646 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{647648 let sender = ensure_signed(origin)?;649 Self::check_owner_or_admin_permissions(collection_id, sender)?;650651 let mut white_list_collection: Vec<T::AccountId>;652 if <WhiteList<T>>::contains_key(collection_id) {653 white_list_collection = <WhiteList<T>>::get(collection_id);654 if !white_list_collection.contains(&address.clone())655 {656 white_list_collection.push(address.clone());657 }658 }659 else {660 white_list_collection = Vec::new();661 white_list_collection.push(address.clone());662 }663664 <WhiteList<T>>::insert(collection_id, white_list_collection);665 Ok(())666 }667668 /// Remove an address from white list.669 /// 670 /// # Permissions671 /// 672 /// * Collection Owner673 /// * Collection Admin674 /// 675 /// # Arguments676 /// 677 /// * collection_id.678 /// 679 /// * address.680 #[weight = T::WeightInfo::remove_from_white_list()]681 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{682683 let sender = ensure_signed(origin)?;684 Self::check_owner_or_admin_permissions(collection_id, sender)?;685686 if <WhiteList<T>>::contains_key(collection_id) {687 let mut white_list_collection = <WhiteList<T>>::get(collection_id);688 if white_list_collection.contains(&address.clone())689 {690 white_list_collection.retain(|i| *i != address.clone());691 <WhiteList<T>>::insert(collection_id, white_list_collection);692 }693 }694695 Ok(())696 }697698 /// Toggle between normal and white list access for the methods with access for `Anyone`.699 /// 700 /// # Permissions701 /// 702 /// * Collection Owner.703 /// 704 /// # Arguments705 /// 706 /// * collection_id.707 /// 708 /// * mode: [AccessMode]709 #[weight = T::WeightInfo::set_public_access_mode()]710 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult711 {712 let sender = ensure_signed(origin)?;713714 Self::check_owner_permissions(collection_id, sender)?;715 let mut target_collection = <Collection<T>>::get(collection_id);716 target_collection.access = mode;717 <Collection<T>>::insert(collection_id, target_collection);718719 Ok(())720 }721722 /// Allows Anyone to create tokens if:723 /// * White List is enabled, and724 /// * Address is added to white list, and725 /// * This method was called with True parameter726 /// 727 /// # Permissions728 /// * Collection Owner729 ///730 /// # Arguments731 /// 732 /// * collection_id.733 /// 734 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.735 #[weight = T::WeightInfo::set_mint_permission()]736 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult737 {738 let sender = ensure_signed(origin)?;739740 Self::check_owner_permissions(collection_id, sender)?;741 let mut target_collection = <Collection<T>>::get(collection_id);742 target_collection.mint_mode = mint_permission;743 <Collection<T>>::insert(collection_id, target_collection);744745 Ok(())746 }747748 /// Change the owner of the collection.749 /// 750 /// # Permissions751 /// 752 /// * Collection Owner.753 /// 754 /// # Arguments755 /// 756 /// * collection_id.757 /// 758 /// * new_owner.759 #[weight = T::WeightInfo::change_collection_owner()]760 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {761762 let sender = ensure_signed(origin)?;763 Self::check_owner_permissions(collection_id, sender)?;764 let mut target_collection = <Collection<T>>::get(collection_id);765 target_collection.owner = new_owner;766 <Collection<T>>::insert(collection_id, target_collection);767768 Ok(())769 }770771 /// Adds an admin of the Collection.772 /// 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. 773 /// 774 /// # Permissions775 /// 776 /// * Collection Owner.777 /// * Collection Admin.778 /// 779 /// # Arguments780 /// 781 /// * collection_id: ID of the Collection to add admin for.782 /// 783 /// * new_admin_id: Address of new admin to add.784 #[weight = T::WeightInfo::add_collection_admin()]785 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {786787 let sender = ensure_signed(origin)?;788 Self::check_owner_or_admin_permissions(collection_id, sender)?;789 let mut admin_arr: Vec<T::AccountId> = Vec::new();790791 if <AdminList<T>>::contains_key(collection_id)792 {793 admin_arr = <AdminList<T>>::get(collection_id);794 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);795 }796797 // Number of collection admins798 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);799800 admin_arr.push(new_admin_id);801 <AdminList<T>>::insert(collection_id, admin_arr);802803 Ok(())804 }805806 /// 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.807 ///808 /// # Permissions809 /// 810 /// * Collection Owner.811 /// * Collection Admin.812 /// 813 /// # Arguments814 /// 815 /// * collection_id: ID of the Collection to remove admin for.816 /// 817 /// * account_id: Address of admin to remove.818 #[weight = T::WeightInfo::remove_collection_admin()]819 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {820821 let sender = ensure_signed(origin)?;822 Self::check_owner_or_admin_permissions(collection_id, sender)?;823824 if <AdminList<T>>::contains_key(collection_id)825 {826 let mut admin_arr = <AdminList<T>>::get(collection_id);827 admin_arr.retain(|i| *i != account_id);828 <AdminList<T>>::insert(collection_id, admin_arr);829 }830831 Ok(())832 }833834 /// # Permissions835 /// 836 /// * Collection Owner837 /// 838 /// # Arguments839 /// 840 /// * collection_id.841 /// 842 /// * new_sponsor.843 #[weight = T::WeightInfo::set_collection_sponsor()]844 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {845846 let sender = ensure_signed(origin)?;847 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);848849 let mut target_collection = <Collection<T>>::get(collection_id);850 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);851852 target_collection.unconfirmed_sponsor = new_sponsor;853 <Collection<T>>::insert(collection_id, target_collection);854855 Ok(())856 }857858 /// # Permissions859 /// 860 /// * Sponsor.861 /// 862 /// # Arguments863 /// 864 /// * collection_id.865 #[weight = T::WeightInfo::confirm_sponsorship()]866 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {867868 let sender = ensure_signed(origin)?;869 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);870871 let mut target_collection = <Collection<T>>::get(collection_id);872 ensure!(sender == target_collection.unconfirmed_sponsor, Error::<T>::ConfirmUnsetSponsorFail);873874 target_collection.sponsor = target_collection.unconfirmed_sponsor;875 target_collection.unconfirmed_sponsor = T::AccountId::default();876 <Collection<T>>::insert(collection_id, target_collection);877878 Ok(())879 }880881 /// Switch back to pay-per-own-transaction model.882 ///883 /// # Permissions884 ///885 /// * Collection owner.886 /// 887 /// # Arguments888 /// 889 /// * collection_id.890 #[weight = T::WeightInfo::remove_collection_sponsor()]891 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {892893 let sender = ensure_signed(origin)?;894 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);895896 let mut target_collection = <Collection<T>>::get(collection_id);897 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);898899 target_collection.sponsor = T::AccountId::default();900 <Collection<T>>::insert(collection_id, target_collection);901902 Ok(())903 }904905 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.906 /// 907 /// # Permissions908 /// 909 /// * Collection Owner.910 /// * Collection Admin.911 /// * Anyone if912 /// * White List is enabled, and913 /// * Address is added to white list, and914 /// * MintPermission is enabled (see SetMintPermission method)915 /// 916 /// # Arguments917 /// 918 /// * collection_id: ID of the collection.919 /// 920 /// * owner: Address, initial owner of the NFT.921 ///922 /// * data: Token data to store on chain.923 // #[weight =924 // (130_000_000 as Weight)925 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))926 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))927 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]928929 #[weight = T::WeightInfo::create_item(data.len())]930 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {931932 let sender = ensure_signed(origin)?;933934 Self::collection_exists(collection_id)?;935936 let target_collection = <Collection<T>>::get(collection_id);937938 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;939 Self::validate_create_item_args(&target_collection, &data)?;940 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;941942 Ok(())943 }944945 /// This method creates multiple instances of NFT Collection created with CreateCollection method.946 /// 947 /// # Permissions948 /// 949 /// * Collection Owner.950 /// * Collection Admin.951 /// * Anyone if952 /// * White List is enabled, and953 /// * Address is added to white list, and954 /// * MintPermission is enabled (see SetMintPermission method)955 /// 956 /// # Arguments957 /// 958 /// * collection_id: ID of the collection.959 /// 960 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].961 /// 962 /// * owner: Address, initial owner of the NFT.963 #[weight = T::WeightInfo::create_item(items_data.into_iter()964 .map(|data| { data.len() })965 .sum())]966 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {967968 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);969 let sender = ensure_signed(origin)?;970971 Self::collection_exists(collection_id)?;972 let target_collection = <Collection<T>>::get(collection_id);973974 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;975976 for data in &items_data {977 Self::validate_create_item_args(&target_collection, data)?;978 }979 for data in &items_data {980 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;981 }982983 Ok(())984 }985986 /// Destroys a concrete instance of NFT.987 /// 988 /// # Permissions989 /// 990 /// * Collection Owner.991 /// * Collection Admin.992 /// * Current NFT Owner.993 /// 994 /// # Arguments995 /// 996 /// * collection_id: ID of the collection.997 /// 998 /// * item_id: ID of NFT to burn.999 #[weight = T::WeightInfo::burn_item()]1000 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {10011002 let sender = ensure_signed(origin)?;1003 Self::collection_exists(collection_id)?;10041005 // Transfer permissions check1006 let target_collection = <Collection<T>>::get(collection_id);1007 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1008 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1009 Error::<T>::NoPermission);10101011 if target_collection.access == AccessMode::WhiteList {1012 Self::check_white_list(collection_id, &sender)?;1013 }10141015 match target_collection.mode1016 {1017 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1018 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,1019 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1020 _ => ()1021 };10221023 // call event1024 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10251026 Ok(())1027 }10281029 /// Change ownership of the token.1030 /// 1031 /// # Permissions1032 /// 1033 /// * Collection Owner1034 /// * Collection Admin1035 /// * Current NFT owner1036 ///1037 /// # Arguments1038 /// 1039 /// * recipient: Address of token recipient.1040 /// 1041 /// * collection_id.1042 /// 1043 /// * item_id: ID of the item1044 /// * Non-Fungible Mode: Required.1045 /// * Fungible Mode: Ignored.1046 /// * Re-Fungible Mode: Required.1047 /// 1048 /// * value: Amount to transfer.1049 /// * Non-Fungible Mode: Ignored1050 /// * Fungible Mode: Must specify transferred amount1051 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1052 #[weight = T::WeightInfo::transfer()]1053 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10541055 let sender = ensure_signed(origin)?;1056 let target_collection = <Collection<T>>::get(collection_id);10571058 // Limits check1059 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10601061 // Transfer permissions check1062 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1063 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1064 Error::<T>::NoPermission);10651066 if target_collection.access == AccessMode::WhiteList {1067 Self::check_white_list(collection_id, &sender)?;1068 Self::check_white_list(collection_id, &recipient)?;1069 }10701071 match target_collection.mode1072 {1073 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1074 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1075 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1076 _ => ()1077 };10781079 Ok(())1080 }10811082 /// Set, change, or remove approved address to transfer the ownership of the NFT.1083 /// 1084 /// # Permissions1085 /// 1086 /// * Collection Owner1087 /// * Collection Admin1088 /// * Current NFT owner1089 /// 1090 /// # Arguments1091 /// 1092 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1093 /// 1094 /// * collection_id.1095 /// 1096 /// * item_id: ID of the item.1097 #[weight = T::WeightInfo::approve()]1098 pub fn approve(origin, approved: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {10991100 let sender = ensure_signed(origin)?;11011102 // Transfer permissions check1103 let target_collection = <Collection<T>>::get(collection_id);1104 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1105 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1106 Error::<T>::NoPermission);11071108 if target_collection.access == AccessMode::WhiteList {1109 Self::check_white_list(collection_id, &sender)?;1110 Self::check_white_list(collection_id, &approved)?;1111 }11121113 // amount param stub1114 let amount = 100000000;11151116 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1117 if list_exists {11181119 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1120 let item_contains = list.iter().any(|i| i.approved == approved);11211122 if !item_contains {1123 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1124 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1125 }1126 } else {11271128 let mut list = Vec::new();1129 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1130 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1131 }11321133 Ok(())1134 }1135 1136 /// 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.1137 /// 1138 /// # Permissions1139 /// * Collection Owner1140 /// * Collection Admin1141 /// * Current NFT owner1142 /// * Address approved by current NFT owner1143 /// 1144 /// # Arguments1145 /// 1146 /// * from: Address that owns token.1147 /// 1148 /// * recipient: Address of token recipient.1149 /// 1150 /// * collection_id.1151 /// 1152 /// * item_id: ID of the item.1153 /// 1154 /// * value: Amount to transfer.1155 #[weight = T::WeightInfo::transfer_from()]1156 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11571158 let sender = ensure_signed(origin)?;1159 let mut appoved_transfer = false;11601161 // Check approve1162 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1163 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1164 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1165 if opt_item.is_some()1166 {1167 appoved_transfer = true;1168 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1169 }1170 }11711172 let target_collection = <Collection<T>>::get(collection_id);11731174 // Limits check1175 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11761177 // Transfer permissions check 1178 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1179 Error::<T>::NoPermission);11801181 if target_collection.access == AccessMode::WhiteList {1182 Self::check_white_list(collection_id, &sender)?;1183 Self::check_white_list(collection_id, &recipient)?;1184 }11851186 // remove approve1187 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1188 .into_iter().filter(|i| i.approved != sender.clone()).collect();1189 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);119011911192 match target_collection.mode1193 {1194 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1195 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1196 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1197 _ => ()1198 };11991200 Ok(())1201 }12021203 ///1204 #[weight = 0]1205 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {12061207 // let no_perm_mes = "You do not have permissions to modify this collection";1208 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1209 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1210 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);12111212 // // on_nft_received call12131214 // Self::transfer(origin, collection_id, item_id, new_owner)?;12151216 Ok(())1217 }12181219 /// Set off-chain data schema.1220 /// 1221 /// # Permissions1222 /// 1223 /// * Collection Owner1224 /// * Collection Admin1225 /// 1226 /// # Arguments1227 /// 1228 /// * collection_id.1229 /// 1230 /// * schema: String representing the offchain data schema.1231 #[weight = T::WeightInfo::set_variable_meta_data()]1232 pub fn set_variable_meta_data (1233 origin,1234 collection_id: CollectionId,1235 item_id: TokenId,1236 data: Vec<u8>1237 ) -> DispatchResult {1238 let sender = ensure_signed(origin)?;1239 1240 Self::collection_exists(collection_id)?;1241 1242 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12431244 // Modify permissions check1245 let target_collection = <Collection<T>>::get(collection_id);1246 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1247 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1248 Error::<T>::NoPermission);12491250 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12511252 match target_collection.mode1253 {1254 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1255 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1256 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1257 _ => fail!(Error::<T>::UnexpectedCollectionType)1258 };12591260 Ok(())1261 }1262 12631264 /// Set off-chain data schema.1265 /// 1266 /// # Permissions1267 /// 1268 /// * Collection Owner1269 /// * Collection Admin1270 /// 1271 /// # Arguments1272 /// 1273 /// * collection_id.1274 /// 1275 /// * schema: String representing the offchain data schema.1276 #[weight = T::WeightInfo::set_offchain_schema()]1277 pub fn set_offchain_schema(1278 origin,1279 collection_id: CollectionId,1280 schema: Vec<u8>1281 ) -> DispatchResult {1282 let sender = ensure_signed(origin)?;1283 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12841285 let mut target_collection = <Collection<T>>::get(collection_id);1286 target_collection.offchain_schema = schema;1287 <Collection<T>>::insert(collection_id, target_collection);12881289 Ok(())1290 }12911292 /// Set const on-chain data schema.1293 /// 1294 /// # Permissions1295 /// 1296 /// * Collection Owner1297 /// * Collection Admin1298 /// 1299 /// # Arguments1300 /// 1301 /// * collection_id.1302 /// 1303 /// * schema: String representing the const on-chain data schema.1304 #[weight = T::WeightInfo::set_const_on_chain_schema()]1305 pub fn set_const_on_chain_schema (1306 origin,1307 collection_id: CollectionId,1308 schema: Vec<u8>1309 ) -> DispatchResult {1310 let sender = ensure_signed(origin)?;1311 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13121313 let mut target_collection = <Collection<T>>::get(collection_id);1314 target_collection.const_on_chain_schema = schema;1315 <Collection<T>>::insert(collection_id, target_collection);13161317 Ok(())1318 }13191320 /// Set variable on-chain data schema.1321 /// 1322 /// # Permissions1323 /// 1324 /// * Collection Owner1325 /// * Collection Admin1326 /// 1327 /// # Arguments1328 /// 1329 /// * collection_id.1330 /// 1331 /// * schema: String representing the variable on-chain data schema.1332 #[weight = T::WeightInfo::set_const_on_chain_schema()]1333 pub fn set_variable_on_chain_schema (1334 origin,1335 collection_id: CollectionId,1336 schema: Vec<u8>1337 ) -> DispatchResult {1338 let sender = ensure_signed(origin)?;1339 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13401341 let mut target_collection = <Collection<T>>::get(collection_id);1342 target_collection.variable_on_chain_schema = schema;1343 <Collection<T>>::insert(collection_id, target_collection);13441345 Ok(())1346 }13471348 // Sudo permissions function1349 #[weight = 0]1350 pub fn set_chain_limits(1351 origin,1352 limits: ChainLimits1353 ) -> DispatchResult {1354 ensure_root(origin)?;1355 <ChainLimit>::put(limits);1356 Ok(())1357 }13581359 /// Enable smart contract self-sponsoring.1360 /// 1361 /// # Permissions1362 /// 1363 /// * Contract Owner1364 /// 1365 /// # Arguments1366 /// 1367 /// * contract address1368 /// * enable flag1369 /// 1370 #[weight = T::WeightInfo::enable_contract_sponsoring()]1371 pub fn enable_contract_sponsoring(1372 origin,1373 contract_address: T::AccountId,1374 enable: bool1375 ) -> DispatchResult {13761377 let sender = ensure_signed(origin)?;13781379 #[cfg(feature = "runtime-benchmarks")]1380 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13811382 let mut is_owner = false;1383 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1384 let owner = <ContractOwner<T>>::get(&contract_address);1385 is_owner = sender == owner;1386 }1387 ensure!(is_owner, Error::<T>::NoPermission);13881389 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1390 Ok(())1391 }13921393 /// Set the rate limit for contract sponsoring to specified number of blocks.1394 /// 1395 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1396 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1397 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1398 /// from contract endowment if there are at least B blocks between such transactions. 1399 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1400 /// 1401 /// # Permissions1402 /// 1403 /// * Contract Owner1404 /// 1405 /// # Arguments1406 /// 1407 /// -`contract_address`: Address of the contract to sponsor1408 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1409 /// 1410 #[weight = 0]1411 pub fn set_contract_sponsoring_rate_limit(1412 origin,1413 contract_address: T::AccountId,1414 rate_limit: T::BlockNumber1415 ) -> DispatchResult {1416 let sender = ensure_signed(origin)?;1417 let mut is_owner = false;1418 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1419 let owner = <ContractOwner<T>>::get(&contract_address);1420 is_owner = sender == owner;1421 }1422 ensure!(is_owner, Error::<T>::NoPermission);14231424 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1425 Ok(())1426 }14271428 #[weight = 0]1429 pub fn set_collection_limits(1430 origin,1431 collection_id: u32,1432 limits: CollectionLimits,1433 ) -> DispatchResult {1434 let sender = ensure_signed(origin)?;1435 Self::check_owner_permissions(collection_id, sender.clone())?;1436 let mut target_collection = <Collection<T>>::get(collection_id);1437 let chain_limits = ChainLimit::get();1438 let climits = target_collection.limits;14391440 // collection bounds1441 ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1442 limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP, 1443 Error::<T>::CollectionLimitBoundsExceeded);14441445 // token_limit check prev1446 ensure!(climits.token_limit > limits.token_limit && 1447 limits.token_limit <= chain_limits.account_token_ownership_limit, 1448 Error::<T>::AccountTokenLimitExceeded);14491450 target_collection.limits = limits;1451 <Collection<T>>::insert(collection_id, target_collection);14521453 Ok(())1454 } 1455 }1456}14571458impl<T: Trait> Module<T> {14591460 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {1461 1462 // check token limit and account token limit1463 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1464 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1465 1466 Ok(())1467 }14681469 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {14701471 // check token limit and account token limit1472 let total_items: u32 = ItemListIndex::get(collection_id);1473 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1474 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1475 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);14761477 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1478 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1479 Self::check_white_list(collection_id, owner)?;1480 Self::check_white_list(collection_id, sender)?;1481 }14821483 Ok(())1484 }14851486 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1487 match target_collection.mode1488 {1489 CollectionMode::NFT => {1490 if let CreateItemData::NFT(data) = data {1491 // check sizes1492 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1493 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1494 } else {1495 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1496 }1497 },1498 CollectionMode::Fungible(_) => {1499 if let CreateItemData::Fungible(_) = data {1500 } else {1501 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1502 }1503 },1504 CollectionMode::ReFungible(_) => {1505 if let CreateItemData::ReFungible(data) = data {15061507 // check sizes1508 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1509 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1510 } else {1511 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1512 }1513 },1514 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1515 };15161517 Ok(())1518 }15191520 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1521 match data1522 {1523 CreateItemData::NFT(data) => {1524 let item = NftItemType {1525 collection: collection_id,1526 owner,1527 const_data: data.const_data,1528 variable_data: data.variable_data1529 };15301531 Self::add_nft_item(item)?;1532 },1533 CreateItemData::Fungible(_) => {1534 let item = FungibleItemType {1535 collection: collection_id,1536 owner,1537 value: (10 as u128).pow(collection.decimal_points as u32)1538 };15391540 Self::add_fungible_item(item)?;1541 },1542 CreateItemData::ReFungible(data) => {1543 let mut owner_list = Vec::new();1544 let value = (10 as u128).pow(collection.decimal_points as u32);1545 owner_list.push(Ownership {owner: owner.clone(), fraction: value});15461547 let item = ReFungibleItemType {1548 collection: collection_id,1549 owner: owner_list,1550 const_data: data.const_data,1551 variable_data: data.variable_data1552 };15531554 Self::add_refungible_item(item)?;1555 }1556 };15571558 // call event1559 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));15601561 Ok(())1562 }15631564 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1565 let current_index = <ItemListIndex>::get(item.collection)1566 .checked_add(1)1567 .ok_or(Error::<T>::NumOverflow)?;1568 let itemcopy = item.clone();1569 let owner = item.owner.clone();15701571 Self::add_token_index(item.collection, current_index, owner.clone())?;15721573 <ItemListIndex>::insert(item.collection, current_index);1574 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);15751576 // Add current block1577 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1578 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1579 1580 // Update balance1581 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1582 .checked_add(item.value)1583 .ok_or(Error::<T>::NumOverflow)?;1584 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);15851586 Ok(())1587 }15881589 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1590 let current_index = <ItemListIndex>::get(item.collection)1591 .checked_add(1)1592 .ok_or(Error::<T>::NumOverflow)?;1593 let itemcopy = item.clone();15941595 let value = item.owner.first().unwrap().fraction;1596 let owner = item.owner.first().unwrap().owner.clone();15971598 Self::add_token_index(item.collection, current_index, owner.clone())?;15991600 <ItemListIndex>::insert(item.collection, current_index);1601 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);16021603 // Add current block1604 let block_number: T::BlockNumber = 0.into();1605 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);16061607 // Update balance1608 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1609 .checked_add(value)1610 .ok_or(Error::<T>::NumOverflow)?;1611 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);16121613 Ok(())1614 }16151616 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1617 let current_index = <ItemListIndex>::get(item.collection)1618 .checked_add(1)1619 .ok_or(Error::<T>::NumOverflow)?;16201621 let item_owner = item.owner.clone();1622 let collection_id = item.collection.clone();1623 Self::add_token_index(collection_id, current_index, item.owner.clone())?;16241625 <ItemListIndex>::insert(collection_id, current_index);1626 <NftItemList<T>>::insert(collection_id, current_index, item);16271628 // Add current block1629 let block_number: T::BlockNumber = 0.into();1630 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);16311632 // Update balance1633 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1634 .checked_add(1)1635 .ok_or(Error::<T>::NumOverflow)?;1636 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);16371638 Ok(())1639 }16401641 fn burn_refungible_item(1642 collection_id: CollectionId,1643 item_id: TokenId,1644 owner: T::AccountId,1645 ) -> DispatchResult {1646 ensure!(1647 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1648 Error::<T>::TokenNotFound1649 );1650 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1651 let item = collection1652 .owner1653 .iter()1654 .filter(|&i| i.owner == owner)1655 .next()1656 .unwrap();1657 Self::remove_token_index(collection_id, item_id, owner.clone())?;16581659 // remove approve list1660 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));16611662 // update balance1663 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1664 .checked_sub(item.fraction)1665 .ok_or(Error::<T>::NumOverflow)?;1666 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);16671668 <ReFungibleItemList<T>>::remove(collection_id, item_id);16691670 Ok(())1671 }16721673 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1674 ensure!(1675 <NftItemList<T>>::contains_key(collection_id, item_id),1676 Error::<T>::TokenNotFound1677 );1678 let item = <NftItemList<T>>::get(collection_id, item_id);1679 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;16801681 // remove approve list1682 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));16831684 // update balance1685 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1686 .checked_sub(1)1687 .ok_or(Error::<T>::NumOverflow)?;1688 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1689 <NftItemList<T>>::remove(collection_id, item_id);16901691 Ok(())1692 }16931694 fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1695 ensure!(1696 <FungibleItemList<T>>::contains_key(collection_id, item_id),1697 Error::<T>::TokenNotFound1698 );1699 let item = <FungibleItemList<T>>::get(collection_id, item_id);1700 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17011702 // remove approve list1703 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17041705 // update balance1706 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1707 .checked_sub(item.value)1708 .ok_or(Error::<T>::NumOverflow)?;1709 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17101711 <FungibleItemList<T>>::remove(collection_id, item_id);17121713 Ok(())1714 }17151716 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1717 ensure!(1718 <Collection<T>>::contains_key(collection_id),1719 Error::<T>::CollectionNotFound1720 );1721 Ok(())1722 }17231724 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1725 Self::collection_exists(collection_id)?;17261727 let target_collection = <Collection<T>>::get(collection_id);1728 ensure!(1729 subject == target_collection.owner,1730 Error::<T>::NoPermission1731 );17321733 Ok(())1734 }17351736 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1737 let target_collection = <Collection<T>>::get(collection_id);1738 let mut result: bool = subject == target_collection.owner;1739 let exists = <AdminList<T>>::contains_key(collection_id);17401741 if !result & exists {1742 if <AdminList<T>>::get(collection_id).contains(&subject) {1743 result = true1744 }1745 }17461747 result1748 }17491750 fn check_owner_or_admin_permissions(1751 collection_id: CollectionId,1752 subject: T::AccountId,1753 ) -> DispatchResult {1754 Self::collection_exists(collection_id)?;1755 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());17561757 ensure!(1758 result,1759 Error::<T>::NoPermission1760 );1761 Ok(())1762 }17631764 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1765 let target_collection = <Collection<T>>::get(collection_id);17661767 match target_collection.mode {1768 CollectionMode::NFT => {1769 <NftItemList<T>>::get(collection_id, item_id).owner == subject1770 }1771 CollectionMode::Fungible(_) => {1772 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1773 }1774 CollectionMode::ReFungible(_) => {1775 <ReFungibleItemList<T>>::get(collection_id, item_id)1776 .owner1777 .iter()1778 .any(|i| i.owner == subject)1779 }1780 CollectionMode::Invalid => false,1781 }1782 }17831784 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1785 let mes = Error::<T>::AddresNotInWhiteList;1786 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1787 let wl = <WhiteList<T>>::get(collection_id);1788 ensure!(wl.contains(address), mes);17891790 Ok(())1791 }17921793 fn transfer_fungible(1794 collection_id: CollectionId,1795 item_id: TokenId,1796 value: u128,1797 owner: T::AccountId,1798 new_owner: T::AccountId,1799 ) -> DispatchResult {1800 ensure!(1801 <FungibleItemList<T>>::contains_key(collection_id, item_id),1802 Error::<T>::TokenNotFound1803 );18041805 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1806 let amount = full_item.value;18071808 ensure!(amount >= value, Error::<T>::TokenValueTooLow);18091810 // update balance1811 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1812 .checked_sub(value)1813 .ok_or(Error::<T>::NumOverflow)?;1814 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);18151816 let mut new_owner_account_id = 0;1817 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1818 if new_owner_items.len() > 0 {1819 new_owner_account_id = new_owner_items[0];1820 }18211822 // transfer1823 if amount == value && new_owner_account_id == 0 {1824 // change owner1825 // new owner do not have account1826 let mut new_full_item = full_item.clone();1827 new_full_item.owner = new_owner.clone();1828 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);18291830 // update balance1831 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1832 .checked_add(value)1833 .ok_or(Error::<T>::NumOverflow)?;1834 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18351836 // update index collection1837 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1838 } else {1839 let mut new_full_item = full_item.clone();1840 new_full_item.value -= value;18411842 // separate amount1843 if new_owner_account_id > 0 {1844 // new owner has account1845 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1846 item.value += value;18471848 // update balance1849 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1850 .checked_add(value)1851 .ok_or(Error::<T>::NumOverflow)?;1852 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18531854 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1855 } else {1856 // new owner do not have account1857 let item = FungibleItemType {1858 collection: collection_id,1859 owner: new_owner.clone(),1860 value1861 };18621863 Self::add_fungible_item(item)?;1864 }18651866 if amount == value {1867 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;18681869 // remove approve list1870 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1871 <FungibleItemList<T>>::remove(collection_id, item_id);1872 }18731874 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1875 }18761877 Ok(())1878 }18791880 fn transfer_refungible(1881 collection_id: CollectionId,1882 item_id: TokenId,1883 value: u128,1884 owner: T::AccountId,1885 new_owner: T::AccountId,1886 ) -> DispatchResult {1887 ensure!(1888 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1889 Error::<T>::TokenNotFound1890 );18911892 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1893 let item = full_item1894 .owner1895 .iter()1896 .filter(|i| i.owner == owner)1897 .next()1898 .ok_or(Error::<T>::NumOverflow)?;1899 let amount = item.fraction;19001901 ensure!(amount >= value, Error::<T>::TokenValueTooLow);19021903 // update balance1904 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1905 .checked_sub(value)1906 .ok_or(Error::<T>::NumOverflow)?;1907 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19081909 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1910 .checked_add(value)1911 .ok_or(Error::<T>::NumOverflow)?;1912 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19131914 let old_owner = item.owner.clone();1915 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19161917 // transfer1918 if amount == value && !new_owner_has_account {1919 // change owner1920 // new owner do not have account1921 let mut new_full_item = full_item.clone();1922 new_full_item1923 .owner1924 .iter_mut()1925 .find(|i| i.owner == owner)1926 .unwrap()1927 .owner = new_owner.clone();1928 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19291930 // update index collection1931 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1932 } else {1933 let mut new_full_item = full_item.clone();1934 new_full_item1935 .owner1936 .iter_mut()1937 .find(|i| i.owner == owner)1938 .unwrap()1939 .fraction -= value;19401941 // separate amount1942 if new_owner_has_account {1943 // new owner has account1944 new_full_item1945 .owner1946 .iter_mut()1947 .find(|i| i.owner == new_owner)1948 .unwrap()1949 .fraction += value;1950 } else {1951 // new owner do not have account1952 new_full_item.owner.push(Ownership {1953 owner: new_owner.clone(),1954 fraction: value,1955 });1956 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1957 }19581959 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1960 }19611962 Ok(())1963 }19641965 fn transfer_nft(1966 collection_id: CollectionId,1967 item_id: TokenId,1968 sender: T::AccountId,1969 new_owner: T::AccountId,1970 ) -> DispatchResult {1971 ensure!(1972 <NftItemList<T>>::contains_key(collection_id, item_id),1973 Error::<T>::TokenNotFound1974 );19751976 let mut item = <NftItemList<T>>::get(collection_id, item_id);19771978 ensure!(1979 sender == item.owner,1980 Error::<T>::MustBeTokenOwner1981 );19821983 // update balance1984 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1985 .checked_sub(1)1986 .ok_or(Error::<T>::NumOverflow)?;1987 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19881989 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1990 .checked_add(1)1991 .ok_or(Error::<T>::NumOverflow)?;1992 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19931994 // change owner1995 let old_owner = item.owner.clone();1996 item.owner = new_owner.clone();1997 <NftItemList<T>>::insert(collection_id, item_id, item);19981999 // update index collection2000 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;20012002 // reset approved list2003 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));2004 Ok(())2005 }2006 2007 fn item_exists(2008 collection_id: CollectionId,2009 item_id: TokenId,2010 mode: &CollectionMode2011 ) -> DispatchResult {2012 match mode {2013 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2014 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2015 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2016 _ => ()2017 };2018 2019 Ok(())2020 }20212022 fn set_re_fungible_variable_data(2023 collection_id: CollectionId,2024 item_id: TokenId,2025 data: Vec<u8>2026 ) -> DispatchResult {2027 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20282029 item.variable_data = data;20302031 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20322033 Ok(())2034 }20352036 fn set_nft_variable_data(2037 collection_id: CollectionId,2038 item_id: TokenId,2039 data: Vec<u8>2040 ) -> DispatchResult {2041 let mut item = <NftItemList<T>>::get(collection_id, item_id);2042 2043 item.variable_data = data;20442045 <NftItemList<T>>::insert(collection_id, item_id, item);2046 2047 Ok(())2048 }20492050 fn init_collection(item: &CollectionType<T::AccountId>) {2051 // check params2052 assert!(2053 item.decimal_points <= MAX_DECIMAL_POINTS,2054 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2055 );2056 assert!(2057 item.name.len() <= 64,2058 "Collection name can not be longer than 63 char"2059 );2060 assert!(2061 item.name.len() <= 256,2062 "Collection description can not be longer than 255 char"2063 );2064 assert!(2065 item.token_prefix.len() <= 16,2066 "Token prefix can not be longer than 15 char"2067 );20682069 // Generate next collection ID2070 let next_id = CreatedCollectionCount::get()2071 .checked_add(1)2072 .unwrap();20732074 CreatedCollectionCount::put(next_id);2075 }20762077 fn init_nft_token(item: &NftItemType<T::AccountId>) {2078 let current_index = <ItemListIndex>::get(item.collection)2079 .checked_add(1)2080 .unwrap();20812082 let item_owner = item.owner.clone();2083 let collection_id = item.collection.clone();2084 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();20852086 <ItemListIndex>::insert(collection_id, current_index);20872088 // Update balance2089 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2090 .checked_add(1)2091 .unwrap();2092 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2093 }20942095 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {2096 let current_index = <ItemListIndex>::get(item.collection)2097 .checked_add(1)2098 .unwrap();2099 let owner = item.owner.clone();21002101 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();21022103 <ItemListIndex>::insert(item.collection, current_index);21042105 // Update balance2106 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2107 .checked_add(item.value)2108 .unwrap();2109 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2110 }21112112 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {2113 let current_index = <ItemListIndex>::get(item.collection)2114 .checked_add(1)2115 .unwrap();21162117 let value = item.owner.first().unwrap().fraction;2118 let owner = item.owner.first().unwrap().owner.clone();21192120 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();21212122 <ItemListIndex>::insert(item.collection, current_index);21232124 // Update balance2125 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2126 .checked_add(value)2127 .unwrap();2128 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2129 }21302131 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {21322133 // add to account limit2134 if <AccountItemCount<T>>::contains_key(owner.clone()) {21352136 // bound Owned tokens by a single address2137 let count = <AccountItemCount<T>>::get(owner.clone());2138 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21392140 <AccountItemCount<T>>::insert(owner.clone(), count2141 .checked_add(1)2142 .ok_or(Error::<T>::NumOverflow)?);2143 }2144 else {2145 <AccountItemCount<T>>::insert(owner.clone(), 1);2146 }21472148 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2149 if list_exists {2150 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2151 let item_contains = list.contains(&item_index.clone());21522153 if !item_contains {2154 list.push(item_index.clone());2155 }21562157 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2158 } else {2159 let mut itm = Vec::new();2160 itm.push(item_index.clone());2161 <AddressTokens<T>>::insert(collection_id, owner, itm);2162 2163 }21642165 Ok(())2166 }21672168 fn remove_token_index(2169 collection_id: CollectionId,2170 item_index: TokenId,2171 owner: T::AccountId,2172 ) -> DispatchResult {21732174 // update counter2175 <AccountItemCount<T>>::insert(owner.clone(), 2176 <AccountItemCount<T>>::get(owner.clone())2177 .checked_sub(1)2178 .ok_or(Error::<T>::NumOverflow)?);217921802181 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2182 if list_exists {2183 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2184 let item_contains = list.contains(&item_index.clone());21852186 if item_contains {2187 list.retain(|&item| item != item_index);2188 <AddressTokens<T>>::insert(collection_id, owner, list);2189 }2190 }21912192 Ok(())2193 }21942195 fn move_token_index(2196 collection_id: CollectionId,2197 item_index: TokenId,2198 old_owner: T::AccountId,2199 new_owner: T::AccountId,2200 ) -> DispatchResult {2201 Self::remove_token_index(collection_id, item_index, old_owner)?;2202 Self::add_token_index(collection_id, item_index, new_owner)?;22032204 Ok(())2205 }2206}22072208////////////////////////////////////////////////////////////////////////////////////////////////////2209// Economic models2210// #region22112212/// Fee multiplier.2213pub type Multiplier = FixedU128;22142215type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2216 <T as system::Trait>::AccountId,2217>>::Balance;2218type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2219 <T as system::Trait>::AccountId,2220>>::NegativeImbalance;22212222/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2223/// in the queue.2224#[derive(Encode, Decode, Clone, Eq, PartialEq)]2225pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2226 #[codec(compact)] BalanceOf<T>2227);22282229impl<T: Trait + Send + Sync> sp_std::fmt::Debug2230 for ChargeTransactionPayment<T>2231{2232 #[cfg(feature = "std")]2233 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2234 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2235 }2236 #[cfg(not(feature = "std"))]2237 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2238 Ok(())2239 }2240}22412242impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2243where2244 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2245 BalanceOf<T>: Send + Sync + FixedPointOperand,2246{2247 /// utility constructor. Used only in client/factory code.2248 pub fn from(fee: BalanceOf<T>) -> Self {2249 Self(fee)2250 }22512252 pub fn traditional_fee(2253 len: usize,2254 info: &DispatchInfoOf<T::Call>,2255 tip: BalanceOf<T>,2256 ) -> BalanceOf<T>2257 where2258 T::Call: Dispatchable<Info = DispatchInfo>,2259 {2260 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2261 }22622263 fn withdraw_fee(2264 &self,2265 who: &T::AccountId,2266 call: &T::Call,2267 info: &DispatchInfoOf<T::Call>,2268 len: usize,2269 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2270 let tip = self.0;22712272 // Set fee based on call type. Creating collection costs 1 Unique.2273 // All other transactions have traditional fees so far2274 // let fee = match call.is_sub_type() {2275 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2276 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2277 // // _ => <BalanceOf<T>>::from(100)2278 // };2279 let fee = Self::traditional_fee(len, info, tip);22802281 // Determine who is paying transaction fee based on ecnomic model2282 // Parse call to extract collection ID and access collection sponsor2283 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2284 Some(Call::create_item(collection_id, _owner, _properties)) => {22852286 // check free create limit2287 if <Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)2288 {2289 <Collection<T>>::get(collection_id).sponsor2290 } else {2291 T::AccountId::default()2292 }2293 }2294 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2295 2296 let _collection_limits = <Collection<T>>::get(collection_id).limits;2297 let _collection_mode = <Collection<T>>::get(collection_id).mode;22982299 // sponsor timeout2300 let sponsor_transfer = match _collection_mode {2301 CollectionMode::NFT => {23022303 // get correct limit2304 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2305 _collection_limits.sponsor_transfer_timeout2306 } else {2307 ChainLimit::get().nft_sponsor_transfer_timeout2308 };23092310 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2311 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2312 let limit_time = basket + limit.into();2313 if block_number >= limit_time {2314 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2315 true2316 }2317 else {2318 false2319 }2320 }2321 CollectionMode::Fungible(_) => {23222323 // get correct limit2324 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2325 _collection_limits.sponsor_transfer_timeout2326 } else {2327 ChainLimit::get().fungible_sponsor_transfer_timeout2328 };23292330 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2331 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2332 if basket.iter().any(|i| i.address == _new_owner.clone())2333 {2334 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2335 let limit_time = item.start_block + limit.into();2336 if block_number >= limit_time {2337 basket.retain(|x| x.address == item.address);2338 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2339 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2340 true2341 }2342 else {2343 false2344 }2345 }2346 else {2347 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2348 true2349 }2350 }2351 CollectionMode::ReFungible(_) => {23522353 // get correct limit2354 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2355 _collection_limits.sponsor_transfer_timeout2356 } else {2357 ChainLimit::get().refungible_sponsor_transfer_timeout2358 };23592360 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2361 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2362 let limit_time = basket + limit.into();2363 if block_number >= limit_time {2364 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2365 true2366 } else {2367 false2368 }2369 }2370 _ => {2371 false2372 },2373 };23742375 if !sponsor_transfer {2376 T::AccountId::default()2377 } else {2378 <Collection<T>>::get(collection_id).sponsor2379 }2380 }23812382 _ => T::AccountId::default(),2383 };23842385 // Sponsor smart contracts2386 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {23872388 // On instantiation: set the contract owner2389 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {23902391 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2392 code_hash,2393 &data,2394 &who,2395 );2396 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());23972398 T::AccountId::default()2399 },24002401 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2402 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {24032404 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());24052406 let mut sponsor_transfer = false;2407 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2408 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2409 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2410 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2411 let limit_time = last_tx_block + rate_limit;24122413 if block_number >= limit_time {2414 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2415 sponsor_transfer = true;2416 }2417 } else {2418 sponsor_transfer = false;2419 }2420 2421 2422 let mut sp = T::AccountId::default();2423 if sponsor_transfer {2424 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2425 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2426 sp = called_contract;2427 }2428 }2429 }24302431 sp2432 },24332434 _ => sponsor,2435 };24362437 let mut who_pays_fee: T::AccountId = sponsor.clone();2438 if sponsor == T::AccountId::default() {2439 who_pays_fee = who.clone();2440 }24412442 // Only mess with balances if fee is not zero.2443 if fee.is_zero() {2444 return Ok((fee, None));2445 }24462447 match <T as transaction_payment::Trait>::Currency::withdraw(2448 &who_pays_fee,2449 fee,2450 if tip.is_zero() {2451 WithdrawReason::TransactionPayment.into()2452 } else {2453 WithdrawReason::TransactionPayment | WithdrawReason::Tip2454 },2455 ExistenceRequirement::KeepAlive,2456 ) {2457 Ok(imbalance) => Ok((fee, Some(imbalance))),2458 Err(_) => Err(InvalidTransaction::Payment.into()),2459 }2460 }2461}246224632464impl<T: Trait + Send + Sync> SignedExtension2465 for ChargeTransactionPayment<T>2466where2467 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2468 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2469{2470 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2471 type AccountId = T::AccountId;2472 type Call = T::Call;2473 type AdditionalSigned = ();2474 type Pre = (2475 BalanceOf<T>,2476 Self::AccountId,2477 Option<NegativeImbalanceOf<T>>,2478 BalanceOf<T>,2479 );2480 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2481 Ok(())2482 }24832484 fn validate(2485 &self,2486 _who: &Self::AccountId,2487 _call: &Self::Call,2488 _info: &DispatchInfoOf<Self::Call>,2489 _len: usize,2490 ) -> TransactionValidity {2491 Ok(ValidTransaction::default())2492 }24932494 fn pre_dispatch(2495 self,2496 who: &Self::AccountId,2497 call: &Self::Call,2498 info: &DispatchInfoOf<Self::Call>,2499 len: usize,2500 ) -> Result<Self::Pre, TransactionValidityError> {2501 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2502 Ok((self.0, who.clone(), imbalance, fee))2503 }25042505 fn post_dispatch(2506 pre: Self::Pre,2507 info: &DispatchInfoOf<Self::Call>,2508 post_info: &PostDispatchInfoOf<Self::Call>,2509 len: usize,2510 _result: &DispatchResult,2511 ) -> Result<(), TransactionValidityError> {2512 let (tip, who, imbalance, fee) = pre;2513 if let Some(payed) = imbalance {2514 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2515 len as u32, info, post_info, tip,2516 );2517 let refund = fee.saturating_sub(actual_fee);2518 let actual_payment =2519 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2520 &who, refund,2521 ) {2522 Ok(refund_imbalance) => {2523 // The refund cannot be larger than the up front payed max weight.2524 // `PostDispatchInfo::calc_unspent` guards against such a case.2525 match payed.offset(refund_imbalance) {2526 Ok(actual_payment) => actual_payment,2527 Err(_) => return Err(InvalidTransaction::Payment.into()),2528 }2529 }2530 // We do not recreate the account using the refund. The up front payment2531 // is gone in that case.2532 Err(_) => payed,2533 };2534 let imbalances = actual_payment.split(tip);2535 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2536 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2537 );2538 }2539 Ok(())2540 }2541}25422543// #endregion1#![recursion_limit = "1024"]23#![cfg_attr(not(feature = "std"), no_std)]45#[cfg(feature = "std")]6pub use std::*;78#[cfg(feature = "std")]9pub use serde::*;1011use codec::{Decode, Encode};12pub use frame_support::{13 construct_runtime, decl_event, decl_module, decl_storage, decl_error,14 dispatch::DispatchResult,15 ensure, fail, parameter_types,16 traits::{17 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,18 Randomness, WithdrawReason,19 },20 weights::{21 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},22 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,23 WeightToFeePolynomial,24 },25 IsSubType, StorageValue,26};2728use frame_system::{self as system, ensure_signed, ensure_root};29use sp_runtime::sp_std::prelude::Vec;30use sp_runtime::{31 traits::{32 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SignedExtension, Zero,33 },34 transaction_validity::{35 InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,36 },37 FixedPointOperand, FixedU128,38};39use pallet_contracts::ContractAddressFor;40use sp_runtime::traits::StaticLookup;4142#[cfg(test)]43mod mock;4445#[cfg(test)]46mod tests;4748mod default_weights;4950pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;51pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;52pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;5354// Structs55// #region5657pub type CollectionId = u32;58pub type TokenId = u32;59pub type DecimalPoints = u8;6061#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]62#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]63pub enum CollectionMode {64 Invalid,65 NFT,66 // decimal points67 Fungible(DecimalPoints),68 // decimal points69 ReFungible(DecimalPoints),70}7172impl Into<u8> for CollectionMode {73 fn into(self) -> u8 {74 match self {75 CollectionMode::Invalid => 0,76 CollectionMode::NFT => 1,77 CollectionMode::Fungible(_) => 2,78 CollectionMode::ReFungible(_) => 3,79 }80 }81}8283#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]84#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]85pub enum AccessMode {86 Normal,87 WhiteList,88}89impl Default for AccessMode {90 fn default() -> Self {91 Self::Normal92 }93}9495impl Default for CollectionMode {96 fn default() -> Self {97 Self::Invalid98 }99}100101#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]102#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]103pub enum SchemaVersion {104 ImageURL,105 Unique,106}107impl Default for SchemaVersion {108 fn default() -> Self {109 Self::ImageURL110 }111}112113#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]114#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]115pub struct Ownership<AccountId> {116 pub owner: AccountId,117 pub fraction: u128,118}119120#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]121#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]122pub struct CollectionType<AccountId> {123 pub owner: AccountId,124 pub mode: CollectionMode,125 pub access: AccessMode,126 pub decimal_points: DecimalPoints,127 pub name: Vec<u16>, // 64 include null escape char128 pub description: Vec<u16>, // 256 include null escape char129 pub token_prefix: Vec<u8>, // 16 include null escape char130 pub mint_mode: bool,131 pub offchain_schema: Vec<u8>,132 pub schema_version: SchemaVersion,133 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender134 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship135 pub limits: CollectionLimits, // Collection private restrictions 136 pub variable_on_chain_schema: Vec<u8>, //137 pub const_on_chain_schema: Vec<u8>, //138}139140#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]141#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]142pub struct NftItemType<AccountId> {143 pub collection: CollectionId,144 pub owner: AccountId,145 pub const_data: Vec<u8>,146 pub variable_data: Vec<u8>,147}148149#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]150#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]151pub struct FungibleItemType<AccountId> {152 pub collection: CollectionId,153 pub owner: AccountId,154 pub value: u128,155}156157#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]158#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]159pub struct ReFungibleItemType<AccountId> {160 pub collection: CollectionId,161 pub owner: Vec<Ownership<AccountId>>,162 pub const_data: Vec<u8>,163 pub variable_data: Vec<u8>,164}165166#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]167#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]168pub struct ApprovePermissions<AccountId> {169 pub approved: AccountId,170 pub amount: u128,171}172173#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]174#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]175pub struct VestingItem<AccountId, Moment> {176 pub sender: AccountId,177 pub recipient: AccountId,178 pub collection_id: CollectionId,179 pub item_id: TokenId,180 pub amount: u64,181 pub vesting_date: Moment,182}183184#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]185#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]186pub struct BasketItem<AccountId, BlockNumber> {187 pub address: AccountId,188 pub start_block: BlockNumber,189}190191#[derive(Encode, Decode, Debug, Clone, PartialEq)]192#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]193pub struct CollectionLimits {194 pub account_token_ownership_limit: u32,195 pub sponsored_data_size: u32,196 pub token_limit: u32,197198 // Timeouts for item types in passed blocks199 pub sponsor_transfer_timeout: u32,200}201202impl Default for CollectionLimits {203 fn default() -> CollectionLimits {204 CollectionLimits { 205 account_token_ownership_limit: 10_000_000, 206 token_limit: u32::max_value(),207 sponsored_data_size: u32::max_value(), 208 sponsor_transfer_timeout: 14400 }209 }210}211212#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]213#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]214pub struct ChainLimits {215 pub collection_numbers_limit: u32,216 pub account_token_ownership_limit: u32,217 pub collections_admins_limit: u64,218 pub custom_data_limit: u32,219220 // Timeouts for item types in passed blocks221 pub nft_sponsor_transfer_timeout: u32,222 pub fungible_sponsor_transfer_timeout: u32,223 pub refungible_sponsor_transfer_timeout: u32,224}225226pub trait WeightInfo {227 fn create_collection() -> Weight;228 fn destroy_collection() -> Weight;229 fn add_to_white_list() -> Weight;230 fn remove_from_white_list() -> Weight;231 fn set_public_access_mode() -> Weight;232 fn set_mint_permission() -> Weight;233 fn change_collection_owner() -> Weight;234 fn add_collection_admin() -> Weight;235 fn remove_collection_admin() -> Weight;236 fn set_collection_sponsor() -> Weight;237 fn confirm_sponsorship() -> Weight;238 fn remove_collection_sponsor() -> Weight;239 fn create_item(s: usize) -> Weight;240 fn burn_item() -> Weight;241 fn transfer() -> Weight;242 fn approve() -> Weight;243 fn transfer_from() -> Weight;244 fn set_offchain_schema() -> Weight;245 fn set_const_on_chain_schema() -> Weight;246 fn set_variable_on_chain_schema() -> Weight;247 fn set_variable_meta_data() -> Weight;248 fn enable_contract_sponsoring() -> Weight;249}250251#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]252#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]253pub struct CreateNftData {254 pub const_data: Vec<u8>,255 pub variable_data: Vec<u8>,256}257258#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]259#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]260pub struct CreateFungibleData {261}262263#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]264#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]265pub struct CreateReFungibleData {266 pub const_data: Vec<u8>,267 pub variable_data: Vec<u8>,268}269270#[derive(Encode, Decode, Debug, Clone, PartialEq)]271#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]272pub enum CreateItemData {273 NFT(CreateNftData),274 Fungible(CreateFungibleData),275 ReFungible(CreateReFungibleData),276}277278impl CreateItemData {279 pub fn len(&self) -> usize {280 let len = match self {281 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),282 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),283 _ => 0284 };285 286 return len;287 }288}289290impl From<CreateNftData> for CreateItemData {291 fn from(item: CreateNftData) -> Self {292 CreateItemData::NFT(item)293 }294}295296impl From<CreateReFungibleData> for CreateItemData {297 fn from(item: CreateReFungibleData) -> Self {298 CreateItemData::ReFungible(item)299 }300}301302impl From<CreateFungibleData> for CreateItemData {303 fn from(item: CreateFungibleData) -> Self {304 CreateItemData::Fungible(item)305 }306}307308309decl_error! {310 /// Error for non-fungible-token module.311 pub enum Error for Module<T: Trait> {312 /// Total collections bound exceeded.313 TotalCollectionsLimitExceeded,314 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.315 CollectionDecimalPointLimitExceeded, 316 /// Collection name can not be longer than 63 char.317 CollectionNameLimitExceeded, 318 /// Collection description can not be longer than 255 char.319 CollectionDescriptionLimitExceeded, 320 /// Token prefix can not be longer than 15 char.321 CollectionTokenPrefixLimitExceeded,322 /// This collection does not exist.323 CollectionNotFound,324 /// Item not exists.325 TokenNotFound,326 /// Arithmetic calculation overflow.327 NumOverflow, 328 /// Account already has admin role.329 AlreadyAdmin, 330 /// You do not own this collection.331 NoPermission,332 /// This address is not set as sponsor, use setCollectionSponsor first.333 ConfirmUnsetSponsorFail,334 /// Collection is not in mint mode.335 PublicMintingNotAllowed,336 /// Sender parameter and item owner must be equal.337 MustBeTokenOwner,338 /// Item balance not enough.339 TokenValueTooLow,340 /// Size of item is too large.341 NftSizeLimitExceeded,342 /// No approve found343 ApproveNotFound,344 /// Requested value more than approved.345 TokenValueNotEnough,346 /// Only approved addresses can call this method.347 ApproveRequired,348 /// Address is not in white list.349 AddresNotInWhiteList,350 /// Number of collection admins bound exceeded.351 CollectionAdminsLimitExceeded,352 /// Owned tokens by a single address bound exceeded.353 AddressOwnershipLimitExceeded,354 /// Length of items properties must be greater than 0.355 EmptyArgument,356 /// const_data exceeded data limit.357 TokenConstDataLimitExceeded,358 /// variable_data exceeded data limit.359 TokenVariableDataLimitExceeded,360 /// Not NFT item data used to mint in NFT collection.361 NotNftDataUsedToMintNftCollectionToken,362 /// Not Fungible item data used to mint in Fungible collection.363 NotFungibleDataUsedToMintFungibleCollectionToken,364 /// Not Re Fungible item data used to mint in Re Fungible collection.365 NotReFungibleDataUsedToMintReFungibleCollectionToken,366 /// Unexpected collection type.367 UnexpectedCollectionType,368 /// Can't store metadata in fungible tokens.369 CantStoreMetadataInFungibleTokens,370 /// Collection token limit exceeded371 CollectionTokenLimitExceeded,372 /// Account token limit exceeded per collection373 AccountTokenLimitExceeded,374 /// Collection limit bounds per collection exceeded375 CollectionLimitBoundsExceeded376 }377}378379pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {380 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;381382 /// Weight information for extrinsics in this pallet.383 type WeightInfo: WeightInfo;384}385386#[cfg(feature = "runtime-benchmarks")]387mod benchmarking;388389// #endregion390391decl_storage! {392 trait Store for Module<T: Trait> as Nft {393394 // Private members395 NextCollectionID: CollectionId;396 CreatedCollectionCount: u32;397 ChainVersion: u64;398 ItemListIndex: map hasher(identity) CollectionId => TokenId;399400 // Chain limits struct401 pub ChainLimit get(fn chain_limit) config(): ChainLimits;402403 // Bound counters404 CollectionCount: u32;405 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;406407 // Basic collections408 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;409 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;410 pub WhiteList get(fn white_list): map hasher(identity) CollectionId => Vec<T::AccountId>;411412 /// Balance owner per collection map413 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;414415 /// second parameter: item id + owner account id416 pub ApprovedList get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;417418 /// Item collections419 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;420 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => FungibleItemType<T::AccountId>;421 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;422423 /// Index list424 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;425426 /// Tokens transfer baskets427 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;428 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => Vec<BasketItem<T::AccountId, T::BlockNumber>>;429 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;430431 // Contract Sponsorship and Ownership432 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;433 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;434 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;435 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;436 }437 add_extra_genesis {438 build(|config: &GenesisConfig<T>| {439 // Modification of storage440 for (_num, _c) in &config.collection {441 <Module<T>>::init_collection(_c);442 }443444 for (_num, _q, _i) in &config.nft_item_id {445 <Module<T>>::init_nft_token(_i);446 }447448 for (_num, _q, _i) in &config.fungible_item_id {449 <Module<T>>::init_fungible_token(_i);450 }451452 for (_num, _q, _i) in &config.refungible_item_id {453 <Module<T>>::init_refungible_token(_i);454 }455 })456 }457}458459decl_event!(460 pub enum Event<T>461 where462 AccountId = <T as system::Trait>::AccountId,463 {464 /// New collection was created465 /// 466 /// # Arguments467 /// 468 /// * collection_id: Globally unique identifier of newly created collection.469 /// 470 /// * mode: [CollectionMode] converted into u8.471 /// 472 /// * account_id: Collection owner.473 Created(CollectionId, u8, AccountId),474475 /// New item was created.476 /// 477 /// # Arguments478 /// 479 /// * collection_id: Id of the collection where item was created.480 /// 481 /// * item_id: Id of an item. Unique within the collection.482 ItemCreated(CollectionId, TokenId),483484 /// Collection item was burned.485 /// 486 /// # Arguments487 /// 488 /// collection_id.489 /// 490 /// item_id: Identifier of burned NFT.491 ItemDestroyed(CollectionId, TokenId),492 }493);494495decl_module! {496 pub struct Module<T: Trait> for enum Call where origin: T::Origin {497498 fn deposit_event() = default;499 type Error = Error<T>;500501 fn on_initialize(now: T::BlockNumber) -> Weight {502503 if ChainVersion::get() < 2504 {505 let value = NextCollectionID::get();506 CreatedCollectionCount::put(value);507 ChainVersion::put(2);508 }509510 0511 }512513 /// 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.514 /// 515 /// # Permissions516 /// 517 /// * Anyone.518 /// 519 /// # Arguments520 /// 521 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.522 /// 523 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.524 /// 525 /// * token_prefix: UTF-8 string with token prefix.526 /// 527 /// * mode: [CollectionMode] collection type and type dependent data.528 // returns collection ID529 #[weight = T::WeightInfo::create_collection()]530 pub fn create_collection(origin,531 collection_name: Vec<u16>,532 collection_description: Vec<u16>,533 token_prefix: Vec<u8>,534 mode: CollectionMode) -> DispatchResult {535536 // Anyone can create a collection537 let who = ensure_signed(origin)?;538539 let decimal_points = match mode {540 CollectionMode::Fungible(points) => points,541 CollectionMode::ReFungible(points) => points,542 _ => 0543 };544545 // bound Total number of collections546 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);547548 // check params549 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);550551 let mut name = collection_name.to_vec();552 name.push(0);553 ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);554555 let mut description = collection_description.to_vec();556 description.push(0);557 ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);558559 let mut prefix = token_prefix.to_vec();560 prefix.push(0);561 ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);562563 // Generate next collection ID564 let next_id = CreatedCollectionCount::get()565 .checked_add(1)566 .ok_or(Error::<T>::NumOverflow)?;567568 // bound counter569 let total = CollectionCount::get()570 .checked_add(1)571 .ok_or(Error::<T>::NumOverflow)?;572573 CreatedCollectionCount::put(next_id);574 CollectionCount::put(total);575576 // Create new collection577 let new_collection = CollectionType {578 owner: who.clone(),579 name: name,580 mode: mode.clone(),581 mint_mode: false,582 access: AccessMode::Normal,583 description: description,584 decimal_points: decimal_points,585 token_prefix: prefix,586 offchain_schema: Vec::new(),587 schema_version: SchemaVersion::ImageURL,588 sponsor: T::AccountId::default(),589 unconfirmed_sponsor: T::AccountId::default(),590 variable_on_chain_schema: Vec::new(),591 const_on_chain_schema: Vec::new(),592 limits: CollectionLimits::default(),593 };594595 // Add new collection to map596 <Collection<T>>::insert(next_id, new_collection);597598 // call event599 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));600601 Ok(())602 }603604 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.605 /// 606 /// # Permissions607 /// 608 /// * Collection Owner.609 /// 610 /// # Arguments611 /// 612 /// * collection_id: collection to destroy.613 #[weight = T::WeightInfo::destroy_collection()]614 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {615616 let sender = ensure_signed(origin)?;617 Self::check_owner_permissions(collection_id, sender)?;618619 <AddressTokens<T>>::remove_prefix(collection_id);620 <ApprovedList<T>>::remove_prefix(collection_id);621 <Balance<T>>::remove_prefix(collection_id);622 <ItemListIndex>::remove(collection_id);623 <AdminList<T>>::remove(collection_id);624 <Collection<T>>::remove(collection_id);625 <WhiteList<T>>::remove(collection_id);626627 <NftItemList<T>>::remove_prefix(collection_id);628 <FungibleItemList<T>>::remove_prefix(collection_id);629 <ReFungibleItemList<T>>::remove_prefix(collection_id);630631 <NftTransferBasket<T>>::remove_prefix(collection_id);632 <FungibleTransferBasket<T>>::remove_prefix(collection_id);633 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);634635 if CollectionCount::get() > 0636 {637 // bound couter638 let total = CollectionCount::get()639 .checked_sub(1)640 .ok_or(Error::<T>::NumOverflow)?;641642 CollectionCount::put(total);643 }644645 Ok(())646 }647648 /// Add an address to white list.649 /// 650 /// # Permissions651 /// 652 /// * Collection Owner653 /// * Collection Admin654 /// 655 /// # Arguments656 /// 657 /// * collection_id.658 /// 659 /// * address.660 #[weight = T::WeightInfo::add_to_white_list()]661 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{662663 let sender = ensure_signed(origin)?;664 Self::check_owner_or_admin_permissions(collection_id, sender)?;665666 let mut white_list_collection: Vec<T::AccountId>;667 if <WhiteList<T>>::contains_key(collection_id) {668 white_list_collection = <WhiteList<T>>::get(collection_id);669 if !white_list_collection.contains(&address.clone())670 {671 white_list_collection.push(address.clone());672 }673 }674 else {675 white_list_collection = Vec::new();676 white_list_collection.push(address.clone());677 }678679 <WhiteList<T>>::insert(collection_id, white_list_collection);680 Ok(())681 }682683 /// Remove an address from white list.684 /// 685 /// # Permissions686 /// 687 /// * Collection Owner688 /// * Collection Admin689 /// 690 /// # Arguments691 /// 692 /// * collection_id.693 /// 694 /// * address.695 #[weight = T::WeightInfo::remove_from_white_list()]696 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{697698 let sender = ensure_signed(origin)?;699 Self::check_owner_or_admin_permissions(collection_id, sender)?;700701 if <WhiteList<T>>::contains_key(collection_id) {702 let mut white_list_collection = <WhiteList<T>>::get(collection_id);703 if white_list_collection.contains(&address.clone())704 {705 white_list_collection.retain(|i| *i != address.clone());706 <WhiteList<T>>::insert(collection_id, white_list_collection);707 }708 }709710 Ok(())711 }712713 /// Toggle between normal and white list access for the methods with access for `Anyone`.714 /// 715 /// # Permissions716 /// 717 /// * Collection Owner.718 /// 719 /// # Arguments720 /// 721 /// * collection_id.722 /// 723 /// * mode: [AccessMode]724 #[weight = T::WeightInfo::set_public_access_mode()]725 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult726 {727 let sender = ensure_signed(origin)?;728729 Self::check_owner_permissions(collection_id, sender)?;730 let mut target_collection = <Collection<T>>::get(collection_id);731 target_collection.access = mode;732 <Collection<T>>::insert(collection_id, target_collection);733734 Ok(())735 }736737 /// Allows Anyone to create tokens if:738 /// * White List is enabled, and739 /// * Address is added to white list, and740 /// * This method was called with True parameter741 /// 742 /// # Permissions743 /// * Collection Owner744 ///745 /// # Arguments746 /// 747 /// * collection_id.748 /// 749 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.750 #[weight = T::WeightInfo::set_mint_permission()]751 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult752 {753 let sender = ensure_signed(origin)?;754755 Self::check_owner_permissions(collection_id, sender)?;756 let mut target_collection = <Collection<T>>::get(collection_id);757 target_collection.mint_mode = mint_permission;758 <Collection<T>>::insert(collection_id, target_collection);759760 Ok(())761 }762763 /// Change the owner of the collection.764 /// 765 /// # Permissions766 /// 767 /// * Collection Owner.768 /// 769 /// # Arguments770 /// 771 /// * collection_id.772 /// 773 /// * new_owner.774 #[weight = T::WeightInfo::change_collection_owner()]775 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {776777 let sender = ensure_signed(origin)?;778 Self::check_owner_permissions(collection_id, sender)?;779 let mut target_collection = <Collection<T>>::get(collection_id);780 target_collection.owner = new_owner;781 <Collection<T>>::insert(collection_id, target_collection);782783 Ok(())784 }785786 /// Adds an admin of the Collection.787 /// 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. 788 /// 789 /// # Permissions790 /// 791 /// * Collection Owner.792 /// * Collection Admin.793 /// 794 /// # Arguments795 /// 796 /// * collection_id: ID of the Collection to add admin for.797 /// 798 /// * new_admin_id: Address of new admin to add.799 #[weight = T::WeightInfo::add_collection_admin()]800 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {801802 let sender = ensure_signed(origin)?;803 Self::check_owner_or_admin_permissions(collection_id, sender)?;804 let mut admin_arr: Vec<T::AccountId> = Vec::new();805806 if <AdminList<T>>::contains_key(collection_id)807 {808 admin_arr = <AdminList<T>>::get(collection_id);809 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);810 }811812 // Number of collection admins813 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);814815 admin_arr.push(new_admin_id);816 <AdminList<T>>::insert(collection_id, admin_arr);817818 Ok(())819 }820821 /// 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.822 ///823 /// # Permissions824 /// 825 /// * Collection Owner.826 /// * Collection Admin.827 /// 828 /// # Arguments829 /// 830 /// * collection_id: ID of the Collection to remove admin for.831 /// 832 /// * account_id: Address of admin to remove.833 #[weight = T::WeightInfo::remove_collection_admin()]834 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {835836 let sender = ensure_signed(origin)?;837 Self::check_owner_or_admin_permissions(collection_id, sender)?;838839 if <AdminList<T>>::contains_key(collection_id)840 {841 let mut admin_arr = <AdminList<T>>::get(collection_id);842 admin_arr.retain(|i| *i != account_id);843 <AdminList<T>>::insert(collection_id, admin_arr);844 }845846 Ok(())847 }848849 /// # Permissions850 /// 851 /// * Collection Owner852 /// 853 /// # Arguments854 /// 855 /// * collection_id.856 /// 857 /// * new_sponsor.858 #[weight = T::WeightInfo::set_collection_sponsor()]859 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {860861 let sender = ensure_signed(origin)?;862 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);863864 let mut target_collection = <Collection<T>>::get(collection_id);865 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);866867 target_collection.unconfirmed_sponsor = new_sponsor;868 <Collection<T>>::insert(collection_id, target_collection);869870 Ok(())871 }872873 /// # Permissions874 /// 875 /// * Sponsor.876 /// 877 /// # Arguments878 /// 879 /// * collection_id.880 #[weight = T::WeightInfo::confirm_sponsorship()]881 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {882883 let sender = ensure_signed(origin)?;884 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);885886 let mut target_collection = <Collection<T>>::get(collection_id);887 ensure!(sender == target_collection.unconfirmed_sponsor, Error::<T>::ConfirmUnsetSponsorFail);888889 target_collection.sponsor = target_collection.unconfirmed_sponsor;890 target_collection.unconfirmed_sponsor = T::AccountId::default();891 <Collection<T>>::insert(collection_id, target_collection);892893 Ok(())894 }895896 /// Switch back to pay-per-own-transaction model.897 ///898 /// # Permissions899 ///900 /// * Collection owner.901 /// 902 /// # Arguments903 /// 904 /// * collection_id.905 #[weight = T::WeightInfo::remove_collection_sponsor()]906 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {907908 let sender = ensure_signed(origin)?;909 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);910911 let mut target_collection = <Collection<T>>::get(collection_id);912 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);913914 target_collection.sponsor = T::AccountId::default();915 <Collection<T>>::insert(collection_id, target_collection);916917 Ok(())918 }919920 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.921 /// 922 /// # Permissions923 /// 924 /// * Collection Owner.925 /// * Collection Admin.926 /// * Anyone if927 /// * White List is enabled, and928 /// * Address is added to white list, and929 /// * MintPermission is enabled (see SetMintPermission method)930 /// 931 /// # Arguments932 /// 933 /// * collection_id: ID of the collection.934 /// 935 /// * owner: Address, initial owner of the NFT.936 ///937 /// * data: Token data to store on chain.938 // #[weight =939 // (130_000_000 as Weight)940 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))941 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))942 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]943944 #[weight = T::WeightInfo::create_item(data.len())]945 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {946947 let sender = ensure_signed(origin)?;948949 Self::collection_exists(collection_id)?;950951 let target_collection = <Collection<T>>::get(collection_id);952953 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;954 Self::validate_create_item_args(&target_collection, &data)?;955 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;956957 Ok(())958 }959960 /// This method creates multiple instances of NFT Collection created with CreateCollection method.961 /// 962 /// # Permissions963 /// 964 /// * Collection Owner.965 /// * Collection Admin.966 /// * Anyone if967 /// * White List is enabled, and968 /// * Address is added to white list, and969 /// * MintPermission is enabled (see SetMintPermission method)970 /// 971 /// # Arguments972 /// 973 /// * collection_id: ID of the collection.974 /// 975 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].976 /// 977 /// * owner: Address, initial owner of the NFT.978 #[weight = T::WeightInfo::create_item(items_data.into_iter()979 .map(|data| { data.len() })980 .sum())]981 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {982983 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);984 let sender = ensure_signed(origin)?;985986 Self::collection_exists(collection_id)?;987 let target_collection = <Collection<T>>::get(collection_id);988989 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;990991 for data in &items_data {992 Self::validate_create_item_args(&target_collection, data)?;993 }994 for data in &items_data {995 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;996 }997998 Ok(())999 }10001001 /// Destroys a concrete instance of NFT.1002 /// 1003 /// # Permissions1004 /// 1005 /// * Collection Owner.1006 /// * Collection Admin.1007 /// * Current NFT Owner.1008 /// 1009 /// # Arguments1010 /// 1011 /// * collection_id: ID of the collection.1012 /// 1013 /// * item_id: ID of NFT to burn.1014 #[weight = T::WeightInfo::burn_item()]1015 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {10161017 let sender = ensure_signed(origin)?;1018 Self::collection_exists(collection_id)?;10191020 // Transfer permissions check1021 let target_collection = <Collection<T>>::get(collection_id);1022 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1023 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1024 Error::<T>::NoPermission);10251026 if target_collection.access == AccessMode::WhiteList {1027 Self::check_white_list(collection_id, &sender)?;1028 }10291030 match target_collection.mode1031 {1032 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1033 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,1034 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1035 _ => ()1036 };10371038 // call event1039 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10401041 Ok(())1042 }10431044 /// Change ownership of the token.1045 /// 1046 /// # Permissions1047 /// 1048 /// * Collection Owner1049 /// * Collection Admin1050 /// * Current NFT owner1051 ///1052 /// # Arguments1053 /// 1054 /// * recipient: Address of token recipient.1055 /// 1056 /// * collection_id.1057 /// 1058 /// * item_id: ID of the item1059 /// * Non-Fungible Mode: Required.1060 /// * Fungible Mode: Ignored.1061 /// * Re-Fungible Mode: Required.1062 /// 1063 /// * value: Amount to transfer.1064 /// * Non-Fungible Mode: Ignored1065 /// * Fungible Mode: Must specify transferred amount1066 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1067 #[weight = T::WeightInfo::transfer()]1068 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10691070 let sender = ensure_signed(origin)?;1071 let target_collection = <Collection<T>>::get(collection_id);10721073 // Limits check1074 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10751076 // Transfer permissions check1077 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1078 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1079 Error::<T>::NoPermission);10801081 if target_collection.access == AccessMode::WhiteList {1082 Self::check_white_list(collection_id, &sender)?;1083 Self::check_white_list(collection_id, &recipient)?;1084 }10851086 match target_collection.mode1087 {1088 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1089 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1090 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1091 _ => ()1092 };10931094 Ok(())1095 }10961097 /// Set, change, or remove approved address to transfer the ownership of the NFT.1098 /// 1099 /// # Permissions1100 /// 1101 /// * Collection Owner1102 /// * Collection Admin1103 /// * Current NFT owner1104 /// 1105 /// # Arguments1106 /// 1107 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1108 /// 1109 /// * collection_id.1110 /// 1111 /// * item_id: ID of the item.1112 #[weight = T::WeightInfo::approve()]1113 pub fn approve(origin, approved: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {11141115 let sender = ensure_signed(origin)?;11161117 // Transfer permissions check1118 let target_collection = <Collection<T>>::get(collection_id);1119 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1120 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1121 Error::<T>::NoPermission);11221123 if target_collection.access == AccessMode::WhiteList {1124 Self::check_white_list(collection_id, &sender)?;1125 Self::check_white_list(collection_id, &approved)?;1126 }11271128 // amount param stub1129 let amount = 100000000;11301131 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1132 if list_exists {11331134 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1135 let item_contains = list.iter().any(|i| i.approved == approved);11361137 if !item_contains {1138 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1139 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1140 }1141 } else {11421143 let mut list = Vec::new();1144 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1145 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1146 }11471148 Ok(())1149 }1150 1151 /// 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.1152 /// 1153 /// # Permissions1154 /// * Collection Owner1155 /// * Collection Admin1156 /// * Current NFT owner1157 /// * Address approved by current NFT owner1158 /// 1159 /// # Arguments1160 /// 1161 /// * from: Address that owns token.1162 /// 1163 /// * recipient: Address of token recipient.1164 /// 1165 /// * collection_id.1166 /// 1167 /// * item_id: ID of the item.1168 /// 1169 /// * value: Amount to transfer.1170 #[weight = T::WeightInfo::transfer_from()]1171 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11721173 let sender = ensure_signed(origin)?;1174 let mut appoved_transfer = false;11751176 // Check approve1177 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1178 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1179 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1180 if opt_item.is_some()1181 {1182 appoved_transfer = true;1183 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1184 }1185 }11861187 let target_collection = <Collection<T>>::get(collection_id);11881189 // Limits check1190 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11911192 // Transfer permissions check 1193 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1194 Error::<T>::NoPermission);11951196 if target_collection.access == AccessMode::WhiteList {1197 Self::check_white_list(collection_id, &sender)?;1198 Self::check_white_list(collection_id, &recipient)?;1199 }12001201 // remove approve1202 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1203 .into_iter().filter(|i| i.approved != sender.clone()).collect();1204 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);120512061207 match target_collection.mode1208 {1209 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1210 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1211 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1212 _ => ()1213 };12141215 Ok(())1216 }12171218 #[weight = 0]1219 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {12201221 // let no_perm_mes = "You do not have permissions to modify this collection";1222 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1223 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1224 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);12251226 // // on_nft_received call12271228 // Self::transfer(origin, collection_id, item_id, new_owner)?;12291230 Ok(())1231 }12321233 /// Set off-chain data schema.1234 /// 1235 /// # Permissions1236 /// 1237 /// * Collection Owner1238 /// * Collection Admin1239 /// 1240 /// # Arguments1241 /// 1242 /// * collection_id.1243 /// 1244 /// * schema: String representing the offchain data schema.1245 #[weight = T::WeightInfo::set_variable_meta_data()]1246 pub fn set_variable_meta_data (1247 origin,1248 collection_id: CollectionId,1249 item_id: TokenId,1250 data: Vec<u8>1251 ) -> DispatchResult {1252 let sender = ensure_signed(origin)?;1253 1254 Self::collection_exists(collection_id)?;1255 1256 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12571258 // Modify permissions check1259 let target_collection = <Collection<T>>::get(collection_id);1260 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1261 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1262 Error::<T>::NoPermission);12631264 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12651266 match target_collection.mode1267 {1268 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1269 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1270 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1271 _ => fail!(Error::<T>::UnexpectedCollectionType)1272 };12731274 Ok(())1275 }1276 1277 /// Set schema standard1278 /// ImageURL1279 /// Unique1280 /// 1281 /// # Permissions1282 /// 1283 /// * Collection Owner1284 /// * Collection Admin1285 /// 1286 /// # Arguments1287 /// 1288 /// * collection_id.1289 /// 1290 /// * schema: SchemaVersion: enum1291 #[weight = 0]1292 pub fn set_schema_version(1293 origin,1294 collection_id: CollectionId,1295 version: SchemaVersion1296 ) -> DispatchResult {1297 let sender = ensure_signed(origin)?;1298 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1299 let mut target_collection = <Collection<T>>::get(collection_id);1300 target_collection.schema_version = version;1301 <Collection<T>>::insert(collection_id, target_collection);13021303 Ok(())1304 }13051306 /// Set off-chain data schema.1307 /// 1308 /// # Permissions1309 /// 1310 /// * Collection Owner1311 /// * Collection Admin1312 /// 1313 /// # Arguments1314 /// 1315 /// * collection_id.1316 /// 1317 /// * schema: String representing the offchain data schema.1318 #[weight = T::WeightInfo::set_offchain_schema()]1319 pub fn set_offchain_schema(1320 origin,1321 collection_id: CollectionId,1322 schema: Vec<u8>1323 ) -> DispatchResult {1324 let sender = ensure_signed(origin)?;1325 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13261327 let mut target_collection = <Collection<T>>::get(collection_id);1328 target_collection.offchain_schema = schema;1329 <Collection<T>>::insert(collection_id, target_collection);13301331 Ok(())1332 }13331334 /// Set const on-chain data schema.1335 /// 1336 /// # Permissions1337 /// 1338 /// * Collection Owner1339 /// * Collection Admin1340 /// 1341 /// # Arguments1342 /// 1343 /// * collection_id.1344 /// 1345 /// * schema: String representing the const on-chain data schema.1346 #[weight = T::WeightInfo::set_const_on_chain_schema()]1347 pub fn set_const_on_chain_schema (1348 origin,1349 collection_id: CollectionId,1350 schema: Vec<u8>1351 ) -> DispatchResult {1352 let sender = ensure_signed(origin)?;1353 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13541355 let mut target_collection = <Collection<T>>::get(collection_id);1356 target_collection.const_on_chain_schema = schema;1357 <Collection<T>>::insert(collection_id, target_collection);13581359 Ok(())1360 }13611362 /// Set variable on-chain data schema.1363 /// 1364 /// # Permissions1365 /// 1366 /// * Collection Owner1367 /// * Collection Admin1368 /// 1369 /// # Arguments1370 /// 1371 /// * collection_id.1372 /// 1373 /// * schema: String representing the variable on-chain data schema.1374 #[weight = T::WeightInfo::set_const_on_chain_schema()]1375 pub fn set_variable_on_chain_schema (1376 origin,1377 collection_id: CollectionId,1378 schema: Vec<u8>1379 ) -> DispatchResult {1380 let sender = ensure_signed(origin)?;1381 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13821383 let mut target_collection = <Collection<T>>::get(collection_id);1384 target_collection.variable_on_chain_schema = schema;1385 <Collection<T>>::insert(collection_id, target_collection);13861387 Ok(())1388 }13891390 // Sudo permissions function1391 #[weight = 0]1392 pub fn set_chain_limits(1393 origin,1394 limits: ChainLimits1395 ) -> DispatchResult {1396 ensure_root(origin)?;1397 <ChainLimit>::put(limits);1398 Ok(())1399 }14001401 /// Enable smart contract self-sponsoring.1402 /// 1403 /// # Permissions1404 /// 1405 /// * Contract Owner1406 /// 1407 /// # Arguments1408 /// 1409 /// * contract address1410 /// * enable flag1411 /// 1412 #[weight = T::WeightInfo::enable_contract_sponsoring()]1413 pub fn enable_contract_sponsoring(1414 origin,1415 contract_address: T::AccountId,1416 enable: bool1417 ) -> DispatchResult {14181419 let sender = ensure_signed(origin)?;14201421 #[cfg(feature = "runtime-benchmarks")]1422 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14231424 let mut is_owner = false;1425 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1426 let owner = <ContractOwner<T>>::get(&contract_address);1427 is_owner = sender == owner;1428 }1429 ensure!(is_owner, Error::<T>::NoPermission);14301431 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1432 Ok(())1433 }14341435 /// Set the rate limit for contract sponsoring to specified number of blocks.1436 /// 1437 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1438 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1439 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1440 /// from contract endowment if there are at least B blocks between such transactions. 1441 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1442 /// 1443 /// # Permissions1444 /// 1445 /// * Contract Owner1446 /// 1447 /// # Arguments1448 /// 1449 /// -`contract_address`: Address of the contract to sponsor1450 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1451 /// 1452 #[weight = 0]1453 pub fn set_contract_sponsoring_rate_limit(1454 origin,1455 contract_address: T::AccountId,1456 rate_limit: T::BlockNumber1457 ) -> DispatchResult {1458 let sender = ensure_signed(origin)?;1459 let mut is_owner = false;1460 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1461 let owner = <ContractOwner<T>>::get(&contract_address);1462 is_owner = sender == owner;1463 }1464 ensure!(is_owner, Error::<T>::NoPermission);14651466 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1467 Ok(())1468 }14691470 #[weight = 0]1471 pub fn set_collection_limits(1472 origin,1473 collection_id: u32,1474 limits: CollectionLimits,1475 ) -> DispatchResult {1476 let sender = ensure_signed(origin)?;1477 Self::check_owner_permissions(collection_id, sender.clone())?;1478 let mut target_collection = <Collection<T>>::get(collection_id);1479 let chain_limits = ChainLimit::get();1480 let climits = target_collection.limits;14811482 // collection bounds1483 ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1484 limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP, 1485 Error::<T>::CollectionLimitBoundsExceeded);14861487 // token_limit check prev1488 ensure!(climits.token_limit > limits.token_limit && 1489 limits.token_limit <= chain_limits.account_token_ownership_limit, 1490 Error::<T>::AccountTokenLimitExceeded);14911492 target_collection.limits = limits;1493 <Collection<T>>::insert(collection_id, target_collection);14941495 Ok(())1496 } 1497 }1498}14991500impl<T: Trait> Module<T> {15011502 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15031504 // check token limit and account token limit1505 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1506 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1507 1508 Ok(())1509 }15101511 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15121513 // check token limit and account token limit1514 let total_items: u32 = ItemListIndex::get(collection_id);1515 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1516 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1517 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);15181519 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1520 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1521 Self::check_white_list(collection_id, owner)?;1522 Self::check_white_list(collection_id, sender)?;1523 }15241525 Ok(())1526 }15271528 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1529 match target_collection.mode1530 {1531 CollectionMode::NFT => {1532 if let CreateItemData::NFT(data) = data {1533 // check sizes1534 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1535 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1536 } else {1537 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1538 }1539 },1540 CollectionMode::Fungible(_) => {1541 if let CreateItemData::Fungible(_) = data {1542 } else {1543 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1544 }1545 },1546 CollectionMode::ReFungible(_) => {1547 if let CreateItemData::ReFungible(data) = data {15481549 // check sizes1550 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1551 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1552 } else {1553 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1554 }1555 },1556 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1557 };15581559 Ok(())1560 }15611562 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1563 match data1564 {1565 CreateItemData::NFT(data) => {1566 let item = NftItemType {1567 collection: collection_id,1568 owner,1569 const_data: data.const_data,1570 variable_data: data.variable_data1571 };15721573 Self::add_nft_item(item)?;1574 },1575 CreateItemData::Fungible(_) => {1576 let item = FungibleItemType {1577 collection: collection_id,1578 owner,1579 value: (10 as u128).pow(collection.decimal_points as u32)1580 };15811582 Self::add_fungible_item(item)?;1583 },1584 CreateItemData::ReFungible(data) => {1585 let mut owner_list = Vec::new();1586 let value = (10 as u128).pow(collection.decimal_points as u32);1587 owner_list.push(Ownership {owner: owner.clone(), fraction: value});15881589 let item = ReFungibleItemType {1590 collection: collection_id,1591 owner: owner_list,1592 const_data: data.const_data,1593 variable_data: data.variable_data1594 };15951596 Self::add_refungible_item(item)?;1597 }1598 };15991600 // call event1601 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));16021603 Ok(())1604 }16051606 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1607 let current_index = <ItemListIndex>::get(item.collection)1608 .checked_add(1)1609 .ok_or(Error::<T>::NumOverflow)?;1610 let itemcopy = item.clone();1611 let owner = item.owner.clone();16121613 Self::add_token_index(item.collection, current_index, owner.clone())?;16141615 <ItemListIndex>::insert(item.collection, current_index);1616 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);16171618 // Add current block1619 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1620 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1621 1622 // Update balance1623 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1624 .checked_add(item.value)1625 .ok_or(Error::<T>::NumOverflow)?;1626 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);16271628 Ok(())1629 }16301631 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1632 let current_index = <ItemListIndex>::get(item.collection)1633 .checked_add(1)1634 .ok_or(Error::<T>::NumOverflow)?;1635 let itemcopy = item.clone();16361637 let value = item.owner.first().unwrap().fraction;1638 let owner = item.owner.first().unwrap().owner.clone();16391640 Self::add_token_index(item.collection, current_index, owner.clone())?;16411642 <ItemListIndex>::insert(item.collection, current_index);1643 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);16441645 // Add current block1646 let block_number: T::BlockNumber = 0.into();1647 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);16481649 // Update balance1650 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1651 .checked_add(value)1652 .ok_or(Error::<T>::NumOverflow)?;1653 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);16541655 Ok(())1656 }16571658 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1659 let current_index = <ItemListIndex>::get(item.collection)1660 .checked_add(1)1661 .ok_or(Error::<T>::NumOverflow)?;16621663 let item_owner = item.owner.clone();1664 let collection_id = item.collection.clone();1665 Self::add_token_index(collection_id, current_index, item.owner.clone())?;16661667 <ItemListIndex>::insert(collection_id, current_index);1668 <NftItemList<T>>::insert(collection_id, current_index, item);16691670 // Add current block1671 let block_number: T::BlockNumber = 0.into();1672 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);16731674 // Update balance1675 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1676 .checked_add(1)1677 .ok_or(Error::<T>::NumOverflow)?;1678 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);16791680 Ok(())1681 }16821683 fn burn_refungible_item(1684 collection_id: CollectionId,1685 item_id: TokenId,1686 owner: T::AccountId,1687 ) -> DispatchResult {1688 ensure!(1689 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1690 Error::<T>::TokenNotFound1691 );1692 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1693 let item = collection1694 .owner1695 .iter()1696 .filter(|&i| i.owner == owner)1697 .next()1698 .unwrap();1699 Self::remove_token_index(collection_id, item_id, owner.clone())?;17001701 // remove approve list1702 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));17031704 // update balance1705 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1706 .checked_sub(item.fraction)1707 .ok_or(Error::<T>::NumOverflow)?;1708 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17091710 <ReFungibleItemList<T>>::remove(collection_id, item_id);17111712 Ok(())1713 }17141715 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1716 ensure!(1717 <NftItemList<T>>::contains_key(collection_id, item_id),1718 Error::<T>::TokenNotFound1719 );1720 let item = <NftItemList<T>>::get(collection_id, item_id);1721 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17221723 // remove approve list1724 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17251726 // update balance1727 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1728 .checked_sub(1)1729 .ok_or(Error::<T>::NumOverflow)?;1730 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1731 <NftItemList<T>>::remove(collection_id, item_id);17321733 Ok(())1734 }17351736 fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1737 ensure!(1738 <FungibleItemList<T>>::contains_key(collection_id, item_id),1739 Error::<T>::TokenNotFound1740 );1741 let item = <FungibleItemList<T>>::get(collection_id, item_id);1742 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17431744 // remove approve list1745 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17461747 // update balance1748 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1749 .checked_sub(item.value)1750 .ok_or(Error::<T>::NumOverflow)?;1751 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17521753 <FungibleItemList<T>>::remove(collection_id, item_id);17541755 Ok(())1756 }17571758 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1759 ensure!(1760 <Collection<T>>::contains_key(collection_id),1761 Error::<T>::CollectionNotFound1762 );1763 Ok(())1764 }17651766 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1767 Self::collection_exists(collection_id)?;17681769 let target_collection = <Collection<T>>::get(collection_id);1770 ensure!(1771 subject == target_collection.owner,1772 Error::<T>::NoPermission1773 );17741775 Ok(())1776 }17771778 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1779 let target_collection = <Collection<T>>::get(collection_id);1780 let mut result: bool = subject == target_collection.owner;1781 let exists = <AdminList<T>>::contains_key(collection_id);17821783 if !result & exists {1784 if <AdminList<T>>::get(collection_id).contains(&subject) {1785 result = true1786 }1787 }17881789 result1790 }17911792 fn check_owner_or_admin_permissions(1793 collection_id: CollectionId,1794 subject: T::AccountId,1795 ) -> DispatchResult {1796 Self::collection_exists(collection_id)?;1797 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());17981799 ensure!(1800 result,1801 Error::<T>::NoPermission1802 );1803 Ok(())1804 }18051806 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1807 let target_collection = <Collection<T>>::get(collection_id);18081809 match target_collection.mode {1810 CollectionMode::NFT => {1811 <NftItemList<T>>::get(collection_id, item_id).owner == subject1812 }1813 CollectionMode::Fungible(_) => {1814 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1815 }1816 CollectionMode::ReFungible(_) => {1817 <ReFungibleItemList<T>>::get(collection_id, item_id)1818 .owner1819 .iter()1820 .any(|i| i.owner == subject)1821 }1822 CollectionMode::Invalid => false,1823 }1824 }18251826 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1827 let mes = Error::<T>::AddresNotInWhiteList;1828 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1829 let wl = <WhiteList<T>>::get(collection_id);1830 ensure!(wl.contains(address), mes);18311832 Ok(())1833 }18341835 fn transfer_fungible(1836 collection_id: CollectionId,1837 item_id: TokenId,1838 value: u128,1839 owner: T::AccountId,1840 new_owner: T::AccountId,1841 ) -> DispatchResult {1842 ensure!(1843 <FungibleItemList<T>>::contains_key(collection_id, item_id),1844 Error::<T>::TokenNotFound1845 );18461847 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1848 let amount = full_item.value;18491850 ensure!(amount >= value, Error::<T>::TokenValueTooLow);18511852 // update balance1853 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1854 .checked_sub(value)1855 .ok_or(Error::<T>::NumOverflow)?;1856 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);18571858 let mut new_owner_account_id = 0;1859 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1860 if new_owner_items.len() > 0 {1861 new_owner_account_id = new_owner_items[0];1862 }18631864 // transfer1865 if amount == value && new_owner_account_id == 0 {1866 // change owner1867 // new owner do not have account1868 let mut new_full_item = full_item.clone();1869 new_full_item.owner = new_owner.clone();1870 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);18711872 // update balance1873 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1874 .checked_add(value)1875 .ok_or(Error::<T>::NumOverflow)?;1876 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18771878 // update index collection1879 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1880 } else {1881 let mut new_full_item = full_item.clone();1882 new_full_item.value -= value;18831884 // separate amount1885 if new_owner_account_id > 0 {1886 // new owner has account1887 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1888 item.value += value;18891890 // update balance1891 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1892 .checked_add(value)1893 .ok_or(Error::<T>::NumOverflow)?;1894 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18951896 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1897 } else {1898 // new owner do not have account1899 let item = FungibleItemType {1900 collection: collection_id,1901 owner: new_owner.clone(),1902 value1903 };19041905 Self::add_fungible_item(item)?;1906 }19071908 if amount == value {1909 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;19101911 // remove approve list1912 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1913 <FungibleItemList<T>>::remove(collection_id, item_id);1914 }19151916 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1917 }19181919 Ok(())1920 }19211922 fn transfer_refungible(1923 collection_id: CollectionId,1924 item_id: TokenId,1925 value: u128,1926 owner: T::AccountId,1927 new_owner: T::AccountId,1928 ) -> DispatchResult {1929 ensure!(1930 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1931 Error::<T>::TokenNotFound1932 );19331934 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1935 let item = full_item1936 .owner1937 .iter()1938 .filter(|i| i.owner == owner)1939 .next()1940 .ok_or(Error::<T>::NumOverflow)?;1941 let amount = item.fraction;19421943 ensure!(amount >= value, Error::<T>::TokenValueTooLow);19441945 // update balance1946 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1947 .checked_sub(value)1948 .ok_or(Error::<T>::NumOverflow)?;1949 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19501951 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1952 .checked_add(value)1953 .ok_or(Error::<T>::NumOverflow)?;1954 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19551956 let old_owner = item.owner.clone();1957 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19581959 // transfer1960 if amount == value && !new_owner_has_account {1961 // change owner1962 // new owner do not have account1963 let mut new_full_item = full_item.clone();1964 new_full_item1965 .owner1966 .iter_mut()1967 .find(|i| i.owner == owner)1968 .unwrap()1969 .owner = new_owner.clone();1970 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19711972 // update index collection1973 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1974 } else {1975 let mut new_full_item = full_item.clone();1976 new_full_item1977 .owner1978 .iter_mut()1979 .find(|i| i.owner == owner)1980 .unwrap()1981 .fraction -= value;19821983 // separate amount1984 if new_owner_has_account {1985 // new owner has account1986 new_full_item1987 .owner1988 .iter_mut()1989 .find(|i| i.owner == new_owner)1990 .unwrap()1991 .fraction += value;1992 } else {1993 // new owner do not have account1994 new_full_item.owner.push(Ownership {1995 owner: new_owner.clone(),1996 fraction: value,1997 });1998 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1999 }20002001 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2002 }20032004 Ok(())2005 }20062007 fn transfer_nft(2008 collection_id: CollectionId,2009 item_id: TokenId,2010 sender: T::AccountId,2011 new_owner: T::AccountId,2012 ) -> DispatchResult {2013 ensure!(2014 <NftItemList<T>>::contains_key(collection_id, item_id),2015 Error::<T>::TokenNotFound2016 );20172018 let mut item = <NftItemList<T>>::get(collection_id, item_id);20192020 ensure!(2021 sender == item.owner,2022 Error::<T>::MustBeTokenOwner2023 );20242025 // update balance2026 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2027 .checked_sub(1)2028 .ok_or(Error::<T>::NumOverflow)?;2029 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20302031 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())2032 .checked_add(1)2033 .ok_or(Error::<T>::NumOverflow)?;2034 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);20352036 // change owner2037 let old_owner = item.owner.clone();2038 item.owner = new_owner.clone();2039 <NftItemList<T>>::insert(collection_id, item_id, item);20402041 // update index collection2042 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;20432044 // reset approved list2045 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));2046 Ok(())2047 }2048 2049 fn item_exists(2050 collection_id: CollectionId,2051 item_id: TokenId,2052 mode: &CollectionMode2053 ) -> DispatchResult {2054 match mode {2055 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2056 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2057 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2058 _ => ()2059 };2060 2061 Ok(())2062 }20632064 fn set_re_fungible_variable_data(2065 collection_id: CollectionId,2066 item_id: TokenId,2067 data: Vec<u8>2068 ) -> DispatchResult {2069 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20702071 item.variable_data = data;20722073 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20742075 Ok(())2076 }20772078 fn set_nft_variable_data(2079 collection_id: CollectionId,2080 item_id: TokenId,2081 data: Vec<u8>2082 ) -> DispatchResult {2083 let mut item = <NftItemList<T>>::get(collection_id, item_id);2084 2085 item.variable_data = data;20862087 <NftItemList<T>>::insert(collection_id, item_id, item);2088 2089 Ok(())2090 }20912092 fn init_collection(item: &CollectionType<T::AccountId>) {2093 // check params2094 assert!(2095 item.decimal_points <= MAX_DECIMAL_POINTS,2096 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2097 );2098 assert!(2099 item.name.len() <= 64,2100 "Collection name can not be longer than 63 char"2101 );2102 assert!(2103 item.name.len() <= 256,2104 "Collection description can not be longer than 255 char"2105 );2106 assert!(2107 item.token_prefix.len() <= 16,2108 "Token prefix can not be longer than 15 char"2109 );21102111 // Generate next collection ID2112 let next_id = CreatedCollectionCount::get()2113 .checked_add(1)2114 .unwrap();21152116 CreatedCollectionCount::put(next_id);2117 }21182119 fn init_nft_token(item: &NftItemType<T::AccountId>) {2120 let current_index = <ItemListIndex>::get(item.collection)2121 .checked_add(1)2122 .unwrap();21232124 let item_owner = item.owner.clone();2125 let collection_id = item.collection.clone();2126 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();21272128 <ItemListIndex>::insert(collection_id, current_index);21292130 // Update balance2131 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2132 .checked_add(1)2133 .unwrap();2134 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2135 }21362137 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {2138 let current_index = <ItemListIndex>::get(item.collection)2139 .checked_add(1)2140 .unwrap();2141 let owner = item.owner.clone();21422143 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();21442145 <ItemListIndex>::insert(item.collection, current_index);21462147 // Update balance2148 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2149 .checked_add(item.value)2150 .unwrap();2151 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2152 }21532154 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {2155 let current_index = <ItemListIndex>::get(item.collection)2156 .checked_add(1)2157 .unwrap();21582159 let value = item.owner.first().unwrap().fraction;2160 let owner = item.owner.first().unwrap().owner.clone();21612162 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();21632164 <ItemListIndex>::insert(item.collection, current_index);21652166 // Update balance2167 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2168 .checked_add(value)2169 .unwrap();2170 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2171 }21722173 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {21742175 // add to account limit2176 if <AccountItemCount<T>>::contains_key(owner.clone()) {21772178 // bound Owned tokens by a single address2179 let count = <AccountItemCount<T>>::get(owner.clone());2180 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21812182 <AccountItemCount<T>>::insert(owner.clone(), count2183 .checked_add(1)2184 .ok_or(Error::<T>::NumOverflow)?);2185 }2186 else {2187 <AccountItemCount<T>>::insert(owner.clone(), 1);2188 }21892190 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2191 if list_exists {2192 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2193 let item_contains = list.contains(&item_index.clone());21942195 if !item_contains {2196 list.push(item_index.clone());2197 }21982199 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2200 } else {2201 let mut itm = Vec::new();2202 itm.push(item_index.clone());2203 <AddressTokens<T>>::insert(collection_id, owner, itm);2204 2205 }22062207 Ok(())2208 }22092210 fn remove_token_index(2211 collection_id: CollectionId,2212 item_index: TokenId,2213 owner: T::AccountId,2214 ) -> DispatchResult {22152216 // update counter2217 <AccountItemCount<T>>::insert(owner.clone(), 2218 <AccountItemCount<T>>::get(owner.clone())2219 .checked_sub(1)2220 .ok_or(Error::<T>::NumOverflow)?);222122222223 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2224 if list_exists {2225 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2226 let item_contains = list.contains(&item_index.clone());22272228 if item_contains {2229 list.retain(|&item| item != item_index);2230 <AddressTokens<T>>::insert(collection_id, owner, list);2231 }2232 }22332234 Ok(())2235 }22362237 fn move_token_index(2238 collection_id: CollectionId,2239 item_index: TokenId,2240 old_owner: T::AccountId,2241 new_owner: T::AccountId,2242 ) -> DispatchResult {2243 Self::remove_token_index(collection_id, item_index, old_owner)?;2244 Self::add_token_index(collection_id, item_index, new_owner)?;22452246 Ok(())2247 }2248}22492250////////////////////////////////////////////////////////////////////////////////////////////////////2251// Economic models2252// #region22532254/// Fee multiplier.2255pub type Multiplier = FixedU128;22562257type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2258 <T as system::Trait>::AccountId,2259>>::Balance;2260type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2261 <T as system::Trait>::AccountId,2262>>::NegativeImbalance;22632264/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2265/// in the queue.2266#[derive(Encode, Decode, Clone, Eq, PartialEq)]2267pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2268 #[codec(compact)] BalanceOf<T>2269);22702271impl<T: Trait + Send + Sync> sp_std::fmt::Debug2272 for ChargeTransactionPayment<T>2273{2274 #[cfg(feature = "std")]2275 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2276 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2277 }2278 #[cfg(not(feature = "std"))]2279 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2280 Ok(())2281 }2282}22832284impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2285where2286 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2287 BalanceOf<T>: Send + Sync + FixedPointOperand,2288{2289 /// utility constructor. Used only in client/factory code.2290 pub fn from(fee: BalanceOf<T>) -> Self {2291 Self(fee)2292 }22932294 pub fn traditional_fee(2295 len: usize,2296 info: &DispatchInfoOf<T::Call>,2297 tip: BalanceOf<T>,2298 ) -> BalanceOf<T>2299 where2300 T::Call: Dispatchable<Info = DispatchInfo>,2301 {2302 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2303 }23042305 fn withdraw_fee(2306 &self,2307 who: &T::AccountId,2308 call: &T::Call,2309 info: &DispatchInfoOf<T::Call>,2310 len: usize,2311 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2312 let tip = self.0;23132314 // Set fee based on call type. Creating collection costs 1 Unique.2315 // All other transactions have traditional fees so far2316 // let fee = match call.is_sub_type() {2317 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2318 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2319 // // _ => <BalanceOf<T>>::from(100)2320 // };2321 let fee = Self::traditional_fee(len, info, tip);23222323 // Determine who is paying transaction fee based on ecnomic model2324 // Parse call to extract collection ID and access collection sponsor2325 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2326 Some(Call::create_item(collection_id, _owner, _properties)) => {23272328 // check free create limit2329 if <Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)2330 {2331 <Collection<T>>::get(collection_id).sponsor2332 } else {2333 T::AccountId::default()2334 }2335 }2336 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2337 2338 let _collection_limits = <Collection<T>>::get(collection_id).limits;2339 let _collection_mode = <Collection<T>>::get(collection_id).mode;23402341 // sponsor timeout2342 let sponsor_transfer = match _collection_mode {2343 CollectionMode::NFT => {23442345 // get correct limit2346 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2347 _collection_limits.sponsor_transfer_timeout2348 } else {2349 ChainLimit::get().nft_sponsor_transfer_timeout2350 };23512352 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2353 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2354 let limit_time = basket + limit.into();2355 if block_number >= limit_time {2356 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2357 true2358 }2359 else {2360 false2361 }2362 }2363 CollectionMode::Fungible(_) => {23642365 // get correct limit2366 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2367 _collection_limits.sponsor_transfer_timeout2368 } else {2369 ChainLimit::get().fungible_sponsor_transfer_timeout2370 };23712372 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2373 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2374 if basket.iter().any(|i| i.address == _new_owner.clone())2375 {2376 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2377 let limit_time = item.start_block + limit.into();2378 if block_number >= limit_time {2379 basket.retain(|x| x.address == item.address);2380 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2381 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2382 true2383 }2384 else {2385 false2386 }2387 }2388 else {2389 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2390 true2391 }2392 }2393 CollectionMode::ReFungible(_) => {23942395 // get correct limit2396 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2397 _collection_limits.sponsor_transfer_timeout2398 } else {2399 ChainLimit::get().refungible_sponsor_transfer_timeout2400 };24012402 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2403 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2404 let limit_time = basket + limit.into();2405 if block_number >= limit_time {2406 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2407 true2408 } else {2409 false2410 }2411 }2412 _ => {2413 false2414 },2415 };24162417 if !sponsor_transfer {2418 T::AccountId::default()2419 } else {2420 <Collection<T>>::get(collection_id).sponsor2421 }2422 }24232424 _ => T::AccountId::default(),2425 };24262427 // Sponsor smart contracts2428 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {24292430 // On instantiation: set the contract owner2431 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {24322433 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2434 code_hash,2435 &data,2436 &who,2437 );2438 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());24392440 T::AccountId::default()2441 },24422443 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2444 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {24452446 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());24472448 let mut sponsor_transfer = false;2449 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2450 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2451 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2452 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2453 let limit_time = last_tx_block + rate_limit;24542455 if block_number >= limit_time {2456 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2457 sponsor_transfer = true;2458 }2459 } else {2460 sponsor_transfer = false;2461 }2462 2463 2464 let mut sp = T::AccountId::default();2465 if sponsor_transfer {2466 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2467 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2468 sp = called_contract;2469 }2470 }2471 }24722473 sp2474 },24752476 _ => sponsor,2477 };24782479 let mut who_pays_fee: T::AccountId = sponsor.clone();2480 if sponsor == T::AccountId::default() {2481 who_pays_fee = who.clone();2482 }24832484 // Only mess with balances if fee is not zero.2485 if fee.is_zero() {2486 return Ok((fee, None));2487 }24882489 match <T as transaction_payment::Trait>::Currency::withdraw(2490 &who_pays_fee,2491 fee,2492 if tip.is_zero() {2493 WithdrawReason::TransactionPayment.into()2494 } else {2495 WithdrawReason::TransactionPayment | WithdrawReason::Tip2496 },2497 ExistenceRequirement::KeepAlive,2498 ) {2499 Ok(imbalance) => Ok((fee, Some(imbalance))),2500 Err(_) => Err(InvalidTransaction::Payment.into()),2501 }2502 }2503}250425052506impl<T: Trait + Send + Sync> SignedExtension2507 for ChargeTransactionPayment<T>2508where2509 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2510 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2511{2512 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2513 type AccountId = T::AccountId;2514 type Call = T::Call;2515 type AdditionalSigned = ();2516 type Pre = (2517 BalanceOf<T>,2518 Self::AccountId,2519 Option<NegativeImbalanceOf<T>>,2520 BalanceOf<T>,2521 );2522 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2523 Ok(())2524 }25252526 fn validate(2527 &self,2528 _who: &Self::AccountId,2529 _call: &Self::Call,2530 _info: &DispatchInfoOf<Self::Call>,2531 _len: usize,2532 ) -> TransactionValidity {2533 Ok(ValidTransaction::default())2534 }25352536 fn pre_dispatch(2537 self,2538 who: &Self::AccountId,2539 call: &Self::Call,2540 info: &DispatchInfoOf<Self::Call>,2541 len: usize,2542 ) -> Result<Self::Pre, TransactionValidityError> {2543 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2544 Ok((self.0, who.clone(), imbalance, fee))2545 }25462547 fn post_dispatch(2548 pre: Self::Pre,2549 info: &DispatchInfoOf<Self::Call>,2550 post_info: &PostDispatchInfoOf<Self::Call>,2551 len: usize,2552 _result: &DispatchResult,2553 ) -> Result<(), TransactionValidityError> {2554 let (tip, who, imbalance, fee) = pre;2555 if let Some(payed) = imbalance {2556 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2557 len as u32, info, post_info, tip,2558 );2559 let refund = fee.saturating_sub(actual_fee);2560 let actual_payment =2561 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2562 &who, refund,2563 ) {2564 Ok(refund_imbalance) => {2565 // The refund cannot be larger than the up front payed max weight.2566 // `PostDispatchInfo::calc_unspent` guards against such a case.2567 match payed.offset(refund_imbalance) {2568 Ok(actual_payment) => actual_payment,2569 Err(_) => return Err(InvalidTransaction::Payment.into()),2570 }2571 }2572 // We do not recreate the account using the refund. The up front payment2573 // is gone in that case.2574 Err(_) => payed,2575 };2576 let imbalances = actual_payment.split(tip);2577 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2578 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2579 );2580 }2581 Ok(())2582 }2583}25842585// #endregionpallets/nft/src/tests.rsdiffbeforeafterboth--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -77,6 +77,19 @@
// Use cases tests region
// #region
+
+#[test]
+fn set_version_schema() {
+ new_test_ext().execute_with(|| {
+ default_limits();
+ let origin1 = Origin::signed(1);
+ 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);
+ });
+}
+
#[test]
fn create_fungible_collection_fails_with_large_decimal_numbers() {
new_test_ext().execute_with(|| {