difftreelog
Merge pull request #14 from usetech-llc/feature/NFTPAR-109-110_contract_sponsoring
in: master
Feature/nftpar 109 110 contract sponsoring
4 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3737,6 +3737,7 @@
"frame-support",
"frame-system",
"log",
+ "pallet-contracts",
"pallet-transaction-payment",
"parity-scale-codec",
"serde",
pallets/nft/Cargo.tomldiffbeforeafterboth--- a/pallets/nft/Cargo.toml
+++ b/pallets/nft/Cargo.toml
@@ -73,6 +73,13 @@
branch = 'v2.0.0_release'
optional = true
+[dependencies.pallet-contracts]
+default-features = false
+git = 'https://github.com/usetech-llc/substrate.git'
+package = 'pallet-contracts'
+branch = 'v2.0.0_release'
+version = '2.0.0'
+
[features]
default = ['std']
std = [
pallets/nft/src/default_weights.rsdiffbeforeafterboth--- a/pallets/nft/src/default_weights.rs
+++ b/pallets/nft/src/default_weights.rs
@@ -92,4 +92,9 @@
.saturating_add(DbWeight::get().reads(2 as Weight))
.saturating_add(DbWeight::get().writes(1 as Weight))
}
+ // fn enable_contract_sponsoring() -> Weight {
+ // (0 as Weight)
+ // .saturating_add(DbWeight::get().reads(1 as Weight))
+ // .saturating_add(DbWeight::get().writes(1 as Weight))
+ // }
}
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,12 dispatch::DispatchResult,13 ensure, 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};25// use frame_support::weights::{Weight, constants::RocksDbWeight as DbWeight};2627use frame_system::{self as system, ensure_signed, ensure_root};28use sp_runtime::sp_std::prelude::Vec;29use sp_runtime::{30 traits::{31 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SaturatedConversion, Saturating,32 SignedExtension, Zero,33 },34 transaction_validity::{35 InvalidTransaction, TransactionPriority, TransactionValidity, TransactionValidityError,36 ValidTransaction,37 },38 FixedPointOperand, FixedU128,39};4041#[cfg(test)]42mod mock;4344#[cfg(test)]45mod tests;4647mod default_weights;4849// Structs50// #region5152#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]53#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]54pub enum CollectionMode {55 Invalid,56 // custom data size57 NFT(u32),58 // decimal points59 Fungible(u32),60 // custom data size and decimal points61 ReFungible(u32, u32),62}6364impl Into<u8> for CollectionMode {65 fn into(self) -> u8 {66 match self {67 CollectionMode::Invalid => 0,68 CollectionMode::NFT(_) => 1,69 CollectionMode::Fungible(_) => 2,70 CollectionMode::ReFungible(_, _) => 3,71 }72 }73}7475#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]76#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]77pub enum AccessMode {78 Normal,79 WhiteList,80}81impl Default for AccessMode {82 fn default() -> Self {83 Self::Normal84 }85}8687impl Default for CollectionMode {88 fn default() -> Self {89 Self::Invalid90 }91}9293#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]94#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]95pub struct Ownership<AccountId> {96 pub owner: AccountId,97 pub fraction: u128,98}99100#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]101#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]102pub struct CollectionType<AccountId> {103 pub owner: AccountId,104 pub mode: CollectionMode,105 pub access: AccessMode,106 pub decimal_points: u32,107 pub name: Vec<u16>, // 64 include null escape char108 pub description: Vec<u16>, // 256 include null escape char109 pub token_prefix: Vec<u8>, // 16 include null escape char110 pub custom_data_size: u32,111 pub mint_mode: bool,112 pub offchain_schema: Vec<u8>,113 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender114 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship115}116117#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]118#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]119pub struct CollectionAdminsType<AccountId> {120 pub admin: AccountId,121 pub collection_id: u64,122}123124#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]125#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]126pub struct NftItemType<AccountId> {127 pub collection: u64,128 pub owner: AccountId,129 pub data: Vec<u8>,130}131132#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]133#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]134pub struct FungibleItemType<AccountId> {135 pub collection: u64,136 pub owner: AccountId,137 pub value: u128,138}139140#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]141#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]142pub struct ReFungibleItemType<AccountId> {143 pub collection: u64,144 pub owner: Vec<Ownership<AccountId>>,145 pub data: Vec<u8>,146}147148#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]149#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]150pub struct ApprovePermissions<AccountId> {151 pub approved: AccountId,152 pub amount: u64,153}154155#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]156#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]157pub struct VestingItem<AccountId, Moment> {158 pub sender: AccountId,159 pub recipient: AccountId,160 pub collection_id: u64,161 pub item_id: u64,162 pub amount: u64,163 pub vesting_date: Moment,164}165166#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]167#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]168pub struct BasketItem<AccountId, BlockNumber> {169 pub address: AccountId,170 pub start_block: BlockNumber,171}172173#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]174#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]175pub struct ChainLimits {176 pub collection_numbers_limit: u64,177 pub account_token_ownership_limit: u64,178 pub collections_admins_limit: u64,179 pub custom_data_limit: u32,180181 // Timeouts for item types in passed blocks182 pub nft_sponsor_transfer_timeout: u32,183 pub fungible_sponsor_transfer_timeout: u32,184 pub refungible_sponsor_transfer_timeout: u32,185}186187pub trait WeightInfo {188 fn create_collection() -> Weight;189 fn destroy_collection() -> Weight;190 fn add_to_white_list() -> Weight;191 fn remove_from_white_list() -> Weight;192 fn set_public_access_mode() -> Weight;193 fn set_mint_permission() -> Weight;194 fn change_collection_owner() -> Weight;195 fn add_collection_admin() -> Weight;196 fn remove_collection_admin() -> Weight;197 fn set_collection_sponsor() -> Weight;198 fn confirm_sponsorship() -> Weight;199 fn remove_collection_sponsor() -> Weight;200 fn create_item(s: usize, ) -> Weight;201 fn burn_item() -> Weight;202 fn transfer() -> Weight;203 fn approve() -> Weight;204 fn transfer_from() -> Weight;205 fn set_offchain_schema() -> Weight;206}207208pub trait Trait: system::Trait + Sized {209 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;210211 /// Weight information for extrinsics in this pallet.212 type WeightInfo: WeightInfo;213}214215#[cfg(feature = "runtime-benchmarks")]216mod benchmarking;217218// #endregion219220decl_storage! {221 trait Store for Module<T: Trait> as Nft {222223 // Private members224 NextCollectionID: u64;225 CreatedCollectionCount: u64;226 ChainVersion: u64;227 ItemListIndex: map hasher(blake2_128_concat) u64 => u64;228229 // Chain limits struct230 pub ChainLimit get(fn chain_limit) config(): ChainLimits;231232 // Bound counters233 CollectionCount: u64;234 pub AccountItemCount get(fn account_item_count): map hasher(identity) T::AccountId => u64;235236 // Basic collections237 pub Collection get(fn collection) config(): map hasher(identity) u64 => CollectionType<T::AccountId>;238 pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec<T::AccountId>;239 pub WhiteList get(fn white_list): map hasher(identity) u64 => Vec<T::AccountId>;240241 /// Balance owner per collection map242 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => u64;243244 /// second parameter: item id + owner account id245 pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) (u64, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;246247 /// Item collections248 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType<T::AccountId>;249 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType<T::AccountId>;250 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType<T::AccountId>;251252 /// Index list253 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec<u64>;254255 /// Tokens transfer baskets256 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;257 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => Vec<BasketItem<T::AccountId, T::BlockNumber>>;258 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;259260 // Sponsorship261 pub ContractSponsor get(fn contract_sponsor): map hasher(identity) T::AccountId => T::AccountId;262 pub UnconfirmedContractSponsor get(fn unconfirmed_contract_sponsor): map hasher(identity) T::AccountId => T::AccountId;263 }264 add_extra_genesis {265 build(|config: &GenesisConfig<T>| {266 // Modification of storage267 for (_num, _c) in &config.collection {268 <Module<T>>::init_collection(_c);269 }270271 for (_num, _q, _i) in &config.nft_item_id {272 <Module<T>>::init_nft_token(_i);273 }274275 for (_num, _q, _i) in &config.fungible_item_id {276 <Module<T>>::init_fungible_token(_i);277 }278279 for (_num, _q, _i) in &config.refungible_item_id {280 <Module<T>>::init_refungible_token(_i);281 }282 })283 }284}285286decl_event!(287 pub enum Event<T>288 where289 AccountId = <T as system::Trait>::AccountId,290 {291 /// New collection was created292 /// 293 /// # Arguments294 /// 295 /// * collection_id: Globally unique identifier of newly created collection.296 /// 297 /// * mode: [CollectionMode] converted into u8.298 /// 299 /// * account_id: Collection owner.300 Created(u64, u8, AccountId),301302 /// New item was created.303 /// 304 /// # Arguments305 /// 306 /// * collection_id: Id of the collection where item was created.307 /// 308 /// * item_id: Id of an item. Unique within the collection.309 ItemCreated(u64, u64),310311 /// Collection item was burned.312 /// 313 /// # Arguments314 /// 315 /// collection_id.316 /// 317 /// item_id: Identifier of burned NFT.318 ItemDestroyed(u64, u64),319 }320);321322decl_module! {323 pub struct Module<T: Trait> for enum Call where origin: T::Origin {324325 fn deposit_event() = default;326327 fn on_initialize(now: T::BlockNumber) -> Weight {328329 if ChainVersion::get() < 2330 {331 let value = NextCollectionID::get();332 CreatedCollectionCount::put(value);333 ChainVersion::put(2);334 }335336 0337 }338339 /// 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.340 /// 341 /// # Permissions342 /// 343 /// * Anyone.344 /// 345 /// # Arguments346 /// 347 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.348 /// 349 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.350 /// 351 /// * token_prefix: UTF-8 string with token prefix.352 /// 353 /// * mode: [CollectionMode] collection type and type dependent data.354 // returns collection ID355 #[weight = T::WeightInfo::create_collection()]356 pub fn create_collection(origin,357 collection_name: Vec<u16>,358 collection_description: Vec<u16>,359 token_prefix: Vec<u8>,360 mode: CollectionMode) -> DispatchResult {361362 // Anyone can create a collection363 let who = ensure_signed(origin)?;364 let custom_data_size = match mode {365 CollectionMode::NFT(size) => {366367 // bound Custom data size368 ensure!(size < ChainLimit::get().custom_data_limit, "Custom data size bound exceeded");369 size370 },371 CollectionMode::ReFungible(size, _) => {372373 // bound Custom data size374 ensure!(size < ChainLimit::get().custom_data_limit, "Custom data size bound exceeded");375 size376 },377 _ => 0378 };379380 let decimal_points = match mode {381 CollectionMode::Fungible(points) => points,382 CollectionMode::ReFungible(_, points) => points,383 _ => 0384 };385386 // bound Total number of collections387 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, "Total collections bound exceeded");388389 // check params390 ensure!(decimal_points <= 4, "decimal_points parameter must be lower than 4");391392 let mut name = collection_name.to_vec();393 name.push(0);394 ensure!(name.len() <= 64, "Collection name can not be longer than 63 char");395396 let mut description = collection_description.to_vec();397 description.push(0);398 ensure!(name.len() <= 256, "Collection description can not be longer than 255 char");399400 let mut prefix = token_prefix.to_vec();401 prefix.push(0);402 ensure!(prefix.len() <= 16, "Token prefix can not be longer than 15 char");403404 // Generate next collection ID405 let next_id = CreatedCollectionCount::get()406 .checked_add(1)407 .expect("collection id error");408409 // bound counter410 let total = CollectionCount::get()411 .checked_add(1)412 .expect("collection counter error");413414 CreatedCollectionCount::put(next_id);415 CollectionCount::put(total);416417 // Create new collection418 let new_collection = CollectionType {419 owner: who.clone(),420 name: name,421 mode: mode.clone(),422 mint_mode: false,423 access: AccessMode::Normal,424 description: description,425 decimal_points: decimal_points,426 token_prefix: prefix,427 offchain_schema: Vec::new(),428 custom_data_size: custom_data_size,429 sponsor: T::AccountId::default(),430 unconfirmed_sponsor: T::AccountId::default(),431 };432433 // Add new collection to map434 <Collection<T>>::insert(next_id, new_collection);435436 // call event437 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));438439 Ok(())440 }441442 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.443 /// 444 /// # Permissions445 /// 446 /// * Collection Owner.447 /// 448 /// # Arguments449 /// 450 /// * collection_id: collection to destroy.451 #[weight = T::WeightInfo::destroy_collection()]452 pub fn destroy_collection(origin, collection_id: u64) -> DispatchResult {453454 let sender = ensure_signed(origin)?;455 Self::check_owner_permissions(collection_id, sender)?;456457 <AddressTokens<T>>::remove_prefix(collection_id);458 <ApprovedList<T>>::remove_prefix(collection_id);459 <Balance<T>>::remove_prefix(collection_id);460 <ItemListIndex>::remove(collection_id);461 <AdminList<T>>::remove(collection_id);462 <Collection<T>>::remove(collection_id);463 <WhiteList<T>>::remove(collection_id);464465 <NftItemList<T>>::remove_prefix(collection_id);466 <FungibleItemList<T>>::remove_prefix(collection_id);467 <ReFungibleItemList<T>>::remove_prefix(collection_id);468469 <NftTransferBasket<T>>::remove_prefix(collection_id);470 <FungibleTransferBasket<T>>::remove_prefix(collection_id);471 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);472473 if CollectionCount::get() > 0474 {475 // bound couter476 let total = CollectionCount::get()477 .checked_sub(1)478 .expect("collection counter error");479480 CollectionCount::put(total);481 }482483 Ok(())484 }485486 /// Add an address to white list.487 /// 488 /// # Permissions489 /// 490 /// * Collection Owner491 /// * Collection Admin492 /// 493 /// # Arguments494 /// 495 /// * collection_id.496 /// 497 /// * address.498 #[weight = T::WeightInfo::add_to_white_list()]499 pub fn add_to_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{500501 let sender = ensure_signed(origin)?;502 Self::check_owner_or_admin_permissions(collection_id, sender)?;503504 let mut white_list_collection: Vec<T::AccountId>;505 if <WhiteList<T>>::contains_key(collection_id) {506 white_list_collection = <WhiteList<T>>::get(collection_id);507 if !white_list_collection.contains(&address.clone())508 {509 white_list_collection.push(address.clone());510 }511 }512 else {513 white_list_collection = Vec::new();514 white_list_collection.push(address.clone());515 }516517 <WhiteList<T>>::insert(collection_id, white_list_collection);518 Ok(())519 }520521 /// Remove an address from white list.522 /// 523 /// # Permissions524 /// 525 /// * Collection Owner526 /// * Collection Admin527 /// 528 /// # Arguments529 /// 530 /// * collection_id.531 /// 532 /// * address.533 #[weight = T::WeightInfo::remove_from_white_list()]534 pub fn remove_from_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{535536 let sender = ensure_signed(origin)?;537 Self::check_owner_or_admin_permissions(collection_id, sender)?;538539 if <WhiteList<T>>::contains_key(collection_id) {540 let mut white_list_collection = <WhiteList<T>>::get(collection_id);541 if white_list_collection.contains(&address.clone())542 {543 white_list_collection.retain(|i| *i != address.clone());544 <WhiteList<T>>::insert(collection_id, white_list_collection);545 }546 }547548 Ok(())549 }550551 /// Toggle between normal and white list access for the methods with access for `Anyone`.552 /// 553 /// # Permissions554 /// 555 /// * Collection Owner.556 /// 557 /// # Arguments558 /// 559 /// * collection_id.560 /// 561 /// * mode: [AccessMode]562 #[weight = T::WeightInfo::set_public_access_mode()]563 pub fn set_public_access_mode(origin, collection_id: u64, mode: AccessMode) -> DispatchResult564 {565 let sender = ensure_signed(origin)?;566567 Self::check_owner_permissions(collection_id, sender)?;568 let mut target_collection = <Collection<T>>::get(collection_id);569 target_collection.access = mode;570 <Collection<T>>::insert(collection_id, target_collection);571572 Ok(())573 }574575 /// Allows Anyone to create tokens if:576 /// * White List is enabled, and577 /// * Address is added to white list, and578 /// * This method was called with True parameter579 /// 580 /// # Permissions581 /// * Collection Owner582 ///583 /// # Arguments584 /// 585 /// * collection_id.586 /// 587 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.588 #[weight = T::WeightInfo::set_mint_permission()]589 pub fn set_mint_permission(origin, collection_id: u64, mint_permission: bool) -> DispatchResult590 {591 let sender = ensure_signed(origin)?;592593 Self::check_owner_permissions(collection_id, sender)?;594 let mut target_collection = <Collection<T>>::get(collection_id);595 target_collection.mint_mode = mint_permission;596 <Collection<T>>::insert(collection_id, target_collection);597598 Ok(())599 }600601 /// Change the owner of the collection.602 /// 603 /// # Permissions604 /// 605 /// * Collection Owner.606 /// 607 /// # Arguments608 /// 609 /// * collection_id.610 /// 611 /// * new_owner.612 #[weight = T::WeightInfo::change_collection_owner()]613 pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult {614615 let sender = ensure_signed(origin)?;616 Self::check_owner_permissions(collection_id, sender)?;617 let mut target_collection = <Collection<T>>::get(collection_id);618 target_collection.owner = new_owner;619 <Collection<T>>::insert(collection_id, target_collection);620621 Ok(())622 }623624 /// Adds an admin of the Collection.625 /// 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. 626 /// 627 /// # Permissions628 /// 629 /// * Collection Owner.630 /// * Collection Admin.631 /// 632 /// # Arguments633 /// 634 /// * collection_id: ID of the Collection to add admin for.635 /// 636 /// * new_admin_id: Address of new admin to add.637 #[weight = T::WeightInfo::add_collection_admin()]638 pub fn add_collection_admin(origin, collection_id: u64, new_admin_id: T::AccountId) -> DispatchResult {639640 let sender = ensure_signed(origin)?;641 Self::check_owner_or_admin_permissions(collection_id, sender)?;642 let mut admin_arr: Vec<T::AccountId> = Vec::new();643644 if <AdminList<T>>::contains_key(collection_id)645 {646 admin_arr = <AdminList<T>>::get(collection_id);647 ensure!(!admin_arr.contains(&new_admin_id), "Account already has admin role");648 }649650 // Number of collection admins651 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, "Number of collection admins bound exceeded");652653 admin_arr.push(new_admin_id);654 <AdminList<T>>::insert(collection_id, admin_arr);655656 Ok(())657 }658659 /// 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.660 ///661 /// # Permissions662 /// 663 /// * Collection Owner.664 /// * Collection Admin.665 /// 666 /// # Arguments667 /// 668 /// * collection_id: ID of the Collection to remove admin for.669 /// 670 /// * account_id: Address of admin to remove.671 #[weight = T::WeightInfo::remove_collection_admin()]672 pub fn remove_collection_admin(origin, collection_id: u64, account_id: T::AccountId) -> DispatchResult {673674 let sender = ensure_signed(origin)?;675 Self::check_owner_or_admin_permissions(collection_id, sender)?;676677 if <AdminList<T>>::contains_key(collection_id)678 {679 let mut admin_arr = <AdminList<T>>::get(collection_id);680 admin_arr.retain(|i| *i != account_id);681 <AdminList<T>>::insert(collection_id, admin_arr);682 }683684 Ok(())685 }686687 /// # Permissions688 /// 689 /// * Collection Owner690 /// 691 /// # Arguments692 /// 693 /// * collection_id.694 /// 695 /// * new_sponsor.696 #[weight = T::WeightInfo::set_collection_sponsor()]697 pub fn set_collection_sponsor(origin, collection_id: u64, new_sponsor: T::AccountId) -> DispatchResult {698699 let sender = ensure_signed(origin)?;700 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");701702 let mut target_collection = <Collection<T>>::get(collection_id);703 ensure!(sender == target_collection.owner, "You do not own this collection");704705 target_collection.unconfirmed_sponsor = new_sponsor;706 <Collection<T>>::insert(collection_id, target_collection);707708 Ok(())709 }710711 /// # Permissions712 /// 713 /// * Sponsor.714 /// 715 /// # Arguments716 /// 717 /// * collection_id.718 #[weight = T::WeightInfo::confirm_sponsorship()]719 pub fn confirm_sponsorship(origin, collection_id: u64) -> DispatchResult {720721 let sender = ensure_signed(origin)?;722 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");723724 let mut target_collection = <Collection<T>>::get(collection_id);725 ensure!(sender == target_collection.unconfirmed_sponsor, "This address is not set as sponsor, use setCollectionSponsor first");726727 target_collection.sponsor = target_collection.unconfirmed_sponsor;728 target_collection.unconfirmed_sponsor = T::AccountId::default();729 <Collection<T>>::insert(collection_id, target_collection);730731 Ok(())732 }733734 /// Switch back to pay-per-own-transaction model.735 ///736 /// # Permissions737 ///738 /// * Collection owner.739 /// 740 /// # Arguments741 /// 742 /// * collection_id.743 #[weight = T::WeightInfo::remove_collection_sponsor()]744 pub fn remove_collection_sponsor(origin, collection_id: u64) -> DispatchResult {745746 let sender = ensure_signed(origin)?;747 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");748749 let mut target_collection = <Collection<T>>::get(collection_id);750 ensure!(sender == target_collection.owner, "You do not own this collection");751752 target_collection.sponsor = T::AccountId::default();753 <Collection<T>>::insert(collection_id, target_collection);754755 Ok(())756 }757758 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.759 /// 760 /// # Permissions761 /// 762 /// * Collection Owner.763 /// * Collection Admin.764 /// * Anyone if765 /// * White List is enabled, and766 /// * Address is added to white list, and767 /// * MintPermission is enabled (see SetMintPermission method)768 /// 769 /// # Arguments770 /// 771 /// * collection_id: ID of the collection.772 /// 773 /// * properties: Array of bytes that contains NFT properties. Since NFT Module is agnostic of properties meaning, it is treated purely as an array of bytes.774 /// 775 /// * owner: Address, initial owner of the NFT.776 // #[weight =777 // (130_000_000 as Weight)778 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))779 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))780 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]781782 #[weight = T::WeightInfo::create_item(properties.len())]783 pub fn create_item(origin, collection_id: u64, properties: Vec<u8>, owner: T::AccountId) -> DispatchResult {784785 let sender = ensure_signed(origin)?;786 Self::collection_exists(collection_id)?;787 let target_collection = <Collection<T>>::get(collection_id);788789 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {790 ensure!(target_collection.mint_mode == true, "Public minting is not allowed for this collection");791 Self::check_white_list(collection_id, &owner)?;792 Self::check_white_list(collection_id, &sender)?;793 }794795 match target_collection.mode796 {797 CollectionMode::NFT(_) => {798799 // check size800 ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large");801802 // Create nft item803 let item = NftItemType {804 collection: collection_id,805 owner: owner,806 data: properties.clone(),807 };808809 Self::add_nft_item(item)?;810811 },812 CollectionMode::Fungible(_) => {813814 // check size815 ensure!(properties.len() as u32 == 0, "Size of item must be 0 with fungible type");816817 let item = FungibleItemType {818 collection: collection_id,819 owner: owner,820 value: (10 as u128).pow(target_collection.decimal_points)821 };822823 Self::add_fungible_item(item)?;824 },825 CollectionMode::ReFungible(_, _) => {826827 // check size828 ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large");829830 let mut owner_list = Vec::new();831 let value = (10 as u128).pow(target_collection.decimal_points);832 owner_list.push(Ownership {owner: owner.clone(), fraction: value});833834 let item = ReFungibleItemType {835 collection: collection_id,836 owner: owner_list,837 data: properties.clone()838 };839840 Self::add_refungible_item(item)?;841 },842 _ => { ensure!(1 == 0,"just error"); }843844 };845846 // call event847 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));848849 Ok(())850 }851852 /// Destroys a concrete instance of NFT.853 /// 854 /// # Permissions855 /// 856 /// * Collection Owner.857 /// * Collection Admin.858 /// * Current NFT Owner.859 /// 860 /// # Arguments861 /// 862 /// * collection_id: ID of the collection.863 /// 864 /// * item_id: ID of NFT to burn.865 #[weight = T::WeightInfo::burn_item()]866 pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult {867868 let sender = ensure_signed(origin)?;869 Self::collection_exists(collection_id)?;870871 // Transfer permissions check872 let target_collection = <Collection<T>>::get(collection_id);873 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||874 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),875 "Only item owner, collection owner and admins can modify item");876877 if target_collection.access == AccessMode::WhiteList {878 Self::check_white_list(collection_id, &sender)?;879 }880881 match target_collection.mode882 {883 CollectionMode::NFT(_) => Self::burn_nft_item(collection_id, item_id)?,884 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,885 CollectionMode::ReFungible(_, _) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,886 _ => ()887 };888889 // call event890 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));891892 Ok(())893 }894895 /// Change ownership of the token.896 /// 897 /// # Permissions898 /// 899 /// * Collection Owner900 /// * Collection Admin901 /// * Current NFT owner902 ///903 /// # Arguments904 /// 905 /// * recipient: Address of token recipient.906 /// 907 /// * collection_id.908 /// 909 /// * item_id: ID of the item910 /// * Non-Fungible Mode: Required.911 /// * Fungible Mode: Ignored.912 /// * Re-Fungible Mode: Required.913 /// 914 /// * value: Amount to transfer.915 /// * Non-Fungible Mode: Ignored916 /// * Fungible Mode: Must specify transferred amount917 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)918 #[weight = T::WeightInfo::transfer()]919 pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult {920921 let sender = ensure_signed(origin)?;922923 // Transfer permissions check924 let target_collection = <Collection<T>>::get(collection_id);925 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||926 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),927 "Only item owner, collection owner and admins can modify item");928929 if target_collection.access == AccessMode::WhiteList {930 Self::check_white_list(collection_id, &sender)?;931 Self::check_white_list(collection_id, &recipient)?;932 }933934 match target_collection.mode935 {936 CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,937 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,938 CollectionMode::ReFungible(_, _) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,939 _ => ()940 };941942 Ok(())943 }944945 /// Set, change, or remove approved address to transfer the ownership of the NFT.946 /// 947 /// # Permissions948 /// 949 /// * Collection Owner950 /// * Collection Admin951 /// * Current NFT owner952 /// 953 /// # Arguments954 /// 955 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).956 /// 957 /// * collection_id.958 /// 959 /// * item_id: ID of the item.960 #[weight = T::WeightInfo::approve()]961 pub fn approve(origin, approved: T::AccountId, collection_id: u64, item_id: u64) -> DispatchResult {962963 let sender = ensure_signed(origin)?;964965 // Transfer permissions check966 let target_collection = <Collection<T>>::get(collection_id);967 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||968 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),969 "Only item owner, collection owner and admins can approve");970971 if target_collection.access == AccessMode::WhiteList {972 Self::check_white_list(collection_id, &sender)?;973 Self::check_white_list(collection_id, &approved)?;974 }975976 // amount param stub977 let amount = 100000000;978979 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));980 if list_exists {981982 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));983 let item_contains = list.iter().any(|i| i.approved == approved);984985 if !item_contains {986 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });987 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);988 }989 } else {990991 let mut list = Vec::new();992 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });993 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);994 }995996 Ok(())997 }998 999 /// 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.1000 /// 1001 /// # Permissions1002 /// * Collection Owner1003 /// * Collection Admin1004 /// * Current NFT owner1005 /// * Address approved by current NFT owner1006 /// 1007 /// # Arguments1008 /// 1009 /// * from: Address that owns token.1010 /// 1011 /// * recipient: Address of token recipient.1012 /// 1013 /// * collection_id.1014 /// 1015 /// * item_id: ID of the item.1016 /// 1017 /// * value: Amount to transfer.1018 #[weight = T::WeightInfo::transfer_from()]1019 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64 ) -> DispatchResult {10201021 let sender = ensure_signed(origin)?;1022 let mut appoved_transfer = false;10231024 // Check approve1025 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1026 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1027 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1028 if opt_item.is_some()1029 {1030 appoved_transfer = true;1031 ensure!(opt_item.unwrap().amount >= value, "Requested value more than approved");1032 }1033 }10341035 // Transfer permissions check1036 let target_collection = <Collection<T>>::get(collection_id);1037 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1038 "Only item owner, collection owner and admins can modify items");10391040 if target_collection.access == AccessMode::WhiteList {1041 Self::check_white_list(collection_id, &sender)?;1042 Self::check_white_list(collection_id, &recipient)?;1043 }10441045 // remove approve1046 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1047 .into_iter().filter(|i| i.approved != sender.clone()).collect();1048 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);104910501051 match target_collection.mode1052 {1053 CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, from, recipient)?,1054 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1055 CollectionMode::ReFungible(_, _) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1056 _ => ()1057 };10581059 Ok(())1060 }10611062 ///1063 #[weight = 0]1064 pub fn safe_transfer_from(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult {10651066 // let no_perm_mes = "You do not have permissions to modify this collection";1067 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1068 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1069 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10701071 // // on_nft_received call10721073 // Self::transfer(origin, collection_id, item_id, new_owner)?;10741075 Ok(())1076 }10771078 /// Set off-chain data schema.1079 /// 1080 /// # Permissions1081 /// 1082 /// * Collection Owner1083 /// * Collection Admin1084 /// 1085 /// # Arguments1086 /// 1087 /// * collection_id.1088 /// 1089 /// * schema: String representing the offchain data schema.1090 #[weight = T::WeightInfo::set_offchain_schema()]1091 pub fn set_offchain_schema(1092 origin,1093 collection_id: u64,1094 schema: Vec<u8>1095 ) -> DispatchResult {1096 let sender = ensure_signed(origin)?;1097 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;10981099 let mut target_collection = <Collection<T>>::get(collection_id);1100 target_collection.offchain_schema = schema;1101 <Collection<T>>::insert(collection_id, target_collection);11021103 Ok(())1104 }11051106 // Sudo permissions function1107 #[weight = 0]1108 pub fn set_chain_limits(1109 origin,1110 limits: ChainLimits1111 ) -> DispatchResult {1112 ensure_root(origin)?;1113 <ChainLimit>::put(limits);1114 Ok(())1115 } 1116 }1117}11181119impl<T: Trait> Module<T> {1120 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1121 let current_index = <ItemListIndex>::get(item.collection)1122 .checked_add(1)1123 .expect("Item list index id error");1124 let itemcopy = item.clone();1125 let owner = item.owner.clone();1126 let value = item.value as u64;11271128 Self::add_token_index(item.collection, current_index, owner.clone())?;11291130 <ItemListIndex>::insert(item.collection, current_index);1131 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);11321133 // Add current block1134 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1135 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1136 1137 // Update balance1138 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1139 .checked_add(value)1140 .unwrap();1141 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);11421143 Ok(())1144 }11451146 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1147 let current_index = <ItemListIndex>::get(item.collection)1148 .checked_add(1)1149 .expect("Item list index id error");1150 let itemcopy = item.clone();11511152 let value = item.owner.first().unwrap().fraction as u64;1153 let owner = item.owner.first().unwrap().owner.clone();11541155 Self::add_token_index(item.collection, current_index, owner.clone())?;11561157 <ItemListIndex>::insert(item.collection, current_index);1158 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);11591160 // Add current block1161 let block_number: T::BlockNumber = 0.into();1162 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);11631164 // Update balance1165 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1166 .checked_add(value)1167 .unwrap();1168 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);11691170 Ok(())1171 }11721173 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1174 let current_index = <ItemListIndex>::get(item.collection)1175 .checked_add(1)1176 .expect("Item list index id error");11771178 let item_owner = item.owner.clone();1179 let collection_id = item.collection.clone();1180 Self::add_token_index(collection_id, current_index, item.owner.clone())?;11811182 <ItemListIndex>::insert(collection_id, current_index);1183 <NftItemList<T>>::insert(collection_id, current_index, item);11841185 // Add current block1186 let block_number: T::BlockNumber = 0.into();1187 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);11881189 // Update balance1190 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1191 .checked_add(1)1192 .unwrap();1193 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);11941195 Ok(())1196 }11971198 fn burn_refungible_item(1199 collection_id: u64,1200 item_id: u64,1201 owner: T::AccountId,1202 ) -> DispatchResult {1203 ensure!(1204 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1205 "Item does not exists"1206 );1207 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1208 let item = collection1209 .owner1210 .iter()1211 .filter(|&i| i.owner == owner)1212 .next()1213 .unwrap();1214 Self::remove_token_index(collection_id, item_id, owner.clone())?;12151216 // remove approve list1217 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));12181219 // update balance1220 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1221 .checked_sub(item.fraction as u64)1222 .unwrap();1223 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);12241225 <ReFungibleItemList<T>>::remove(collection_id, item_id);12261227 Ok(())1228 }12291230 fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult {1231 ensure!(1232 <NftItemList<T>>::contains_key(collection_id, item_id),1233 "Item does not exists"1234 );1235 let item = <NftItemList<T>>::get(collection_id, item_id);1236 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;12371238 // remove approve list1239 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));12401241 // update balance1242 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1243 .checked_sub(1)1244 .unwrap();1245 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1246 <NftItemList<T>>::remove(collection_id, item_id);12471248 Ok(())1249 }12501251 fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult {1252 ensure!(1253 <FungibleItemList<T>>::contains_key(collection_id, item_id),1254 "Item does not exists"1255 );1256 let item = <FungibleItemList<T>>::get(collection_id, item_id);1257 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;12581259 // remove approve list1260 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));12611262 // update balance1263 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1264 .checked_sub(item.value as u64)1265 .unwrap();1266 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);12671268 <FungibleItemList<T>>::remove(collection_id, item_id);12691270 Ok(())1271 }12721273 fn collection_exists(collection_id: u64) -> DispatchResult {1274 ensure!(1275 <Collection<T>>::contains_key(collection_id),1276 "This collection does not exist"1277 );1278 Ok(())1279 }12801281 fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {1282 Self::collection_exists(collection_id)?;12831284 let target_collection = <Collection<T>>::get(collection_id);1285 ensure!(1286 subject == target_collection.owner,1287 "You do not own this collection"1288 );12891290 Ok(())1291 }12921293 fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool {1294 let target_collection = <Collection<T>>::get(collection_id);1295 let mut result: bool = subject == target_collection.owner;1296 let exists = <AdminList<T>>::contains_key(collection_id);12971298 if !result & exists {1299 if <AdminList<T>>::get(collection_id).contains(&subject) {1300 result = true1301 }1302 }13031304 result1305 }13061307 fn check_owner_or_admin_permissions(1308 collection_id: u64,1309 subject: T::AccountId,1310 ) -> DispatchResult {1311 Self::collection_exists(collection_id)?;1312 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());13131314 ensure!(1315 result,1316 "You do not have permissions to modify this collection"1317 );1318 Ok(())1319 }13201321 fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool {1322 let target_collection = <Collection<T>>::get(collection_id);13231324 match target_collection.mode {1325 CollectionMode::NFT(_) => {1326 <NftItemList<T>>::get(collection_id, item_id).owner == subject1327 }1328 CollectionMode::Fungible(_) => {1329 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1330 }1331 CollectionMode::ReFungible(_, _) => {1332 <ReFungibleItemList<T>>::get(collection_id, item_id)1333 .owner1334 .iter()1335 .any(|i| i.owner == subject)1336 }1337 CollectionMode::Invalid => false,1338 }1339 }13401341 fn check_white_list(collection_id: u64, address: &T::AccountId) -> DispatchResult {1342 let mes = "Address is not in white list";1343 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1344 let wl = <WhiteList<T>>::get(collection_id);1345 ensure!(wl.contains(address), mes);13461347 Ok(())1348 }13491350 fn transfer_fungible(1351 collection_id: u64,1352 item_id: u64,1353 value: u64,1354 owner: T::AccountId,1355 new_owner: T::AccountId,1356 ) -> DispatchResult {1357 ensure!(1358 <FungibleItemList<T>>::contains_key(collection_id, item_id),1359 "Item not exists"1360 );13611362 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1363 let amount = full_item.value;13641365 ensure!(amount >= value.into(), "Item balance not enouth");13661367 // update balance1368 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1369 .checked_sub(value)1370 .unwrap();1371 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);13721373 let mut new_owner_account_id = 0;1374 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1375 if new_owner_items.len() > 0 {1376 new_owner_account_id = new_owner_items[0];1377 }13781379 let val64 = value.into();13801381 // transfer1382 if amount == val64 && new_owner_account_id == 0 {1383 // change owner1384 // new owner do not have account1385 let mut new_full_item = full_item.clone();1386 new_full_item.owner = new_owner.clone();1387 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);13881389 // update balance1390 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1391 .checked_add(value)1392 .unwrap();1393 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);13941395 // update index collection1396 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1397 } else {1398 let mut new_full_item = full_item.clone();1399 new_full_item.value -= val64;14001401 // separate amount1402 if new_owner_account_id > 0 {1403 // new owner has account1404 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1405 item.value += val64;14061407 // update balance1408 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1409 .checked_add(value)1410 .unwrap();1411 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);14121413 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1414 } else {1415 // new owner do not have account1416 let item = FungibleItemType {1417 collection: collection_id,1418 owner: new_owner.clone(),1419 value: val64,1420 };14211422 Self::add_fungible_item(item)?;1423 }14241425 if amount == val64 {1426 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;14271428 // remove approve list1429 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1430 <FungibleItemList<T>>::remove(collection_id, item_id);1431 }14321433 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1434 }14351436 Ok(())1437 }14381439 fn transfer_refungible(1440 collection_id: u64,1441 item_id: u64,1442 value: u64,1443 owner: T::AccountId,1444 new_owner: T::AccountId,1445 ) -> DispatchResult {1446 ensure!(1447 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1448 "Item not exists"1449 );14501451 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1452 let item = full_item1453 .owner1454 .iter()1455 .filter(|i| i.owner == owner)1456 .next()1457 .unwrap();1458 let amount = item.fraction;14591460 ensure!(amount >= value.into(), "Item balance not enouth");14611462 // update balance1463 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1464 .checked_sub(value)1465 .unwrap();1466 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);14671468 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1469 .checked_add(value)1470 .unwrap();1471 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);14721473 let old_owner = item.owner.clone();1474 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1475 let val64 = value.into();14761477 // transfer1478 if amount == val64 && !new_owner_has_account {1479 // change owner1480 // new owner do not have account1481 let mut new_full_item = full_item.clone();1482 new_full_item1483 .owner1484 .iter_mut()1485 .find(|i| i.owner == owner)1486 .unwrap()1487 .owner = new_owner.clone();1488 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);14891490 // update index collection1491 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1492 } else {1493 let mut new_full_item = full_item.clone();1494 new_full_item1495 .owner1496 .iter_mut()1497 .find(|i| i.owner == owner)1498 .unwrap()1499 .fraction -= val64;15001501 // separate amount1502 if new_owner_has_account {1503 // new owner has account1504 new_full_item1505 .owner1506 .iter_mut()1507 .find(|i| i.owner == new_owner)1508 .unwrap()1509 .fraction += val64;1510 } else {1511 // new owner do not have account1512 new_full_item.owner.push(Ownership {1513 owner: new_owner.clone(),1514 fraction: val64,1515 });1516 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1517 }15181519 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1520 }15211522 Ok(())1523 }15241525 fn transfer_nft(1526 collection_id: u64,1527 item_id: u64,1528 sender: T::AccountId,1529 new_owner: T::AccountId,1530 ) -> DispatchResult {1531 ensure!(1532 <NftItemList<T>>::contains_key(collection_id, item_id),1533 "Item not exists"1534 );15351536 let mut item = <NftItemList<T>>::get(collection_id, item_id);15371538 ensure!(1539 sender == item.owner,1540 "sender parameter and item owner must be equal"1541 );15421543 // update balance1544 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1545 .checked_sub(1)1546 .unwrap();1547 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);15481549 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1550 .checked_add(1)1551 .unwrap();1552 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);15531554 // change owner1555 let old_owner = item.owner.clone();1556 item.owner = new_owner.clone();1557 <NftItemList<T>>::insert(collection_id, item_id, item);15581559 // update index collection1560 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;15611562 // reset approved list1563 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1564 Ok(())1565 }15661567 fn init_collection(item: &CollectionType<T::AccountId>) {1568 // check params1569 assert!(1570 item.decimal_points <= 4,1571 "decimal_points parameter must be lower than 4"1572 );1573 assert!(1574 item.name.len() <= 64,1575 "Collection name can not be longer than 63 char"1576 );1577 assert!(1578 item.name.len() <= 256,1579 "Collection description can not be longer than 255 char"1580 );1581 assert!(1582 item.token_prefix.len() <= 16,1583 "Token prefix can not be longer than 15 char"1584 );15851586 // Generate next collection ID1587 let next_id = CreatedCollectionCount::get()1588 .checked_add(1)1589 .expect("collection id error");15901591 CreatedCollectionCount::put(next_id);1592 }15931594 fn init_nft_token(item: &NftItemType<T::AccountId>) {1595 let current_index = <ItemListIndex>::get(item.collection)1596 .checked_add(1)1597 .expect("Item list index id error");15981599 let item_owner = item.owner.clone();1600 let collection_id = item.collection.clone();1601 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();16021603 <ItemListIndex>::insert(collection_id, current_index);16041605 // Update balance1606 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1607 .checked_add(1)1608 .unwrap();1609 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);1610 }16111612 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {1613 let current_index = <ItemListIndex>::get(item.collection)1614 .checked_add(1)1615 .expect("Item list index id error");1616 let owner = item.owner.clone();1617 let value = item.value as u64;16181619 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();16201621 <ItemListIndex>::insert(item.collection, current_index);16221623 // Update balance1624 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1625 .checked_add(value)1626 .unwrap();1627 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1628 }16291630 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {1631 let current_index = <ItemListIndex>::get(item.collection)1632 .checked_add(1)1633 .expect("Item list index id error");16341635 let value = item.owner.first().unwrap().fraction as u64;1636 let owner = item.owner.first().unwrap().owner.clone();16371638 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();16391640 <ItemListIndex>::insert(item.collection, current_index);16411642 // Update balance1643 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1644 .checked_add(value)1645 .unwrap();1646 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1647 }16481649 fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {16501651 // add to account limit1652 if <AccountItemCount<T>>::contains_key(owner.clone()) {16531654 // bound Owned tokens by a single address1655 let count = <AccountItemCount<T>>::get(owner.clone());1656 ensure!(count < ChainLimit::get().account_token_ownership_limit, "Owned tokens by a single address bound exceeded");16571658 <AccountItemCount<T>>::insert(owner.clone(), 1659 count.checked_add(1).unwrap());1660 }1661 else {1662 <AccountItemCount<T>>::insert(owner.clone(), 1);1663 }16641665 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1666 if list_exists {1667 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1668 let item_contains = list.contains(&item_index.clone());16691670 if !item_contains {1671 list.push(item_index.clone());1672 }16731674 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);1675 } else {1676 let mut itm = Vec::new();1677 itm.push(item_index.clone());1678 <AddressTokens<T>>::insert(collection_id, owner, itm);1679 1680 }16811682 Ok(())1683 }16841685 fn remove_token_index(1686 collection_id: u64,1687 item_index: u64,1688 owner: T::AccountId,1689 ) -> DispatchResult {16901691 // update counter1692 <AccountItemCount<T>>::insert(owner.clone(), 1693 <AccountItemCount<T>>::get(owner.clone()).checked_sub(1).unwrap());169416951696 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1697 if list_exists {1698 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1699 let item_contains = list.contains(&item_index.clone());17001701 if item_contains {1702 list.retain(|&item| item != item_index);1703 <AddressTokens<T>>::insert(collection_id, owner, list);1704 }1705 }17061707 Ok(())1708 }17091710 fn move_token_index(1711 collection_id: u64,1712 item_index: u64,1713 old_owner: T::AccountId,1714 new_owner: T::AccountId,1715 ) -> DispatchResult {1716 Self::remove_token_index(collection_id, item_index, old_owner)?;1717 Self::add_token_index(collection_id, item_index, new_owner)?;17181719 Ok(())1720 }1721}17221723////////////////////////////////////////////////////////////////////////////////////////////////////1724// Economic models1725// #region17261727/// Fee multiplier.1728pub type Multiplier = FixedU128;17291730type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<1731 <T as system::Trait>::AccountId,1732>>::Balance;1733type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<1734 <T as system::Trait>::AccountId,1735>>::NegativeImbalance;17361737/// Require the transactor pay for themselves and maybe include a tip to gain additional priority1738/// in the queue.1739#[derive(Encode, Decode, Clone, Eq, PartialEq)]1740pub struct ChargeTransactionPayment<T: transaction_payment::Trait + Send + Sync>(1741 #[codec(compact)] BalanceOf<T>,1742);17431744impl<T: Trait + transaction_payment::Trait + Send + Sync> sp_std::fmt::Debug1745 for ChargeTransactionPayment<T>1746{1747 #[cfg(feature = "std")]1748 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {1749 write!(f, "ChargeTransactionPayment<{:?}>", self.0)1750 }1751 #[cfg(not(feature = "std"))]1752 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {1753 Ok(())1754 }1755}17561757impl<T: Trait + transaction_payment::Trait + Send + Sync> ChargeTransactionPayment<T>1758where1759 T::Call:1760 Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>>,1761 BalanceOf<T>: Send + Sync + FixedPointOperand,1762{1763 /// utility constructor. Used only in client/factory code.1764 pub fn from(fee: BalanceOf<T>) -> Self {1765 Self(fee)1766 }17671768 pub fn traditional_fee(1769 len: usize,1770 info: &DispatchInfoOf<T::Call>,1771 tip: BalanceOf<T>,1772 ) -> BalanceOf<T>1773 where1774 T::Call: Dispatchable<Info = DispatchInfo>,1775 {1776 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)1777 }17781779 fn withdraw_fee(1780 &self,1781 who: &T::AccountId,1782 call: &T::Call,1783 info: &DispatchInfoOf<T::Call>,1784 len: usize,1785 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {1786 let tip = self.0;17871788 // Set fee based on call type. Creating collection costs 1 Unique.1789 // All other transactions have traditional fees so far1790 let fee = match call.is_sub_type() {1791 Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),1792 _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes1793 // _ => <BalanceOf<T>>::from(100)1794 };17951796 // Determine who is paying transaction fee based on ecnomic model1797 // Parse call to extract collection ID and access collection sponsor1798 let sponsor: T::AccountId = match call.is_sub_type() {1799 Some(Call::create_item(collection_id, _properties, _owner)) => {1800 <Collection<T>>::get(collection_id).sponsor1801 }1802 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {1803 let _collection_mode = <Collection<T>>::get(collection_id).mode;18041805 // sponsor timeout1806 let sponsor_transfer = match _collection_mode {1807 CollectionMode::NFT(_) => {1808 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);1809 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;1810 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();1811 if block_number >= limit_time {1812 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);1813 true1814 }1815 else {1816 false1817 }1818 }1819 CollectionMode::Fungible(_) => {1820 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);1821 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;1822 if basket.iter().any(|i| i.address == _new_owner.clone())1823 {1824 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();1825 let limit_time = item.start_block + ChainLimit::get().fungible_sponsor_transfer_timeout.into();1826 if block_number >= limit_time {1827 basket.retain(|x| x.address == item.address);1828 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });1829 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);1830 true1831 }1832 else {1833 false1834 }1835 }1836 else {1837 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});1838 true1839 }1840 }1841 CollectionMode::ReFungible(_, _) => {1842 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);1843 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;1844 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();1845 if block_number >= limit_time {1846 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);1847 true1848 } else {1849 false1850 }1851 }1852 _ => {1853 false1854 },1855 };18561857 if !sponsor_transfer {1858 T::AccountId::default()1859 } else {1860 <Collection<T>>::get(collection_id).sponsor1861 }1862 }18631864 _ => T::AccountId::default(),1865 };18661867 let mut who_pays_fee: T::AccountId = sponsor.clone();1868 if sponsor == T::AccountId::default() {1869 who_pays_fee = who.clone();1870 }18711872 // Only mess with balances if fee is not zero.1873 if fee.is_zero() {1874 return Ok((fee, None));1875 }18761877 match <T as transaction_payment::Trait>::Currency::withdraw(1878 &who_pays_fee,1879 fee,1880 if tip.is_zero() {1881 WithdrawReason::TransactionPayment.into()1882 } else {1883 WithdrawReason::TransactionPayment | WithdrawReason::Tip1884 },1885 ExistenceRequirement::KeepAlive,1886 ) {1887 Ok(imbalance) => Ok((fee, Some(imbalance))),1888 Err(_) => Err(InvalidTransaction::Payment.into()),1889 }1890 }1891}18921893impl<T: Trait + transaction_payment::Trait + Send + Sync> SignedExtension1894 for ChargeTransactionPayment<T>1895where1896 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,1897 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>>,1898{1899 const IDENTIFIER: &'static str = "ChargeTransactionPayment";1900 type AccountId = T::AccountId;1901 type Call = T::Call;1902 type AdditionalSigned = ();1903 type Pre = (1904 BalanceOf<T>,1905 Self::AccountId,1906 Option<NegativeImbalanceOf<T>>,1907 BalanceOf<T>,1908 );1909 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {1910 Ok(())1911 }19121913 fn validate(1914 &self,1915 who: &Self::AccountId,1916 call: &Self::Call,1917 info: &DispatchInfoOf<Self::Call>,1918 len: usize,1919 ) -> TransactionValidity {1920 let (fee, _) = self.withdraw_fee(who, call, info, len)?;19211922 let mut r = ValidTransaction::default();1923 // NOTE: we probably want to maximize the _fee (of any type) per weight unit_ here, which1924 // will be a bit more than setting the priority to tip. For now, this is enough.1925 r.priority = fee.saturated_into::<TransactionPriority>();1926 Ok(r)1927 }19281929 fn pre_dispatch(1930 self,1931 who: &Self::AccountId,1932 call: &Self::Call,1933 info: &DispatchInfoOf<Self::Call>,1934 len: usize,1935 ) -> Result<Self::Pre, TransactionValidityError> {1936 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;1937 Ok((self.0, who.clone(), imbalance, fee))1938 }19391940 fn post_dispatch(1941 pre: Self::Pre,1942 info: &DispatchInfoOf<Self::Call>,1943 post_info: &PostDispatchInfoOf<Self::Call>,1944 len: usize,1945 _result: &DispatchResult,1946 ) -> Result<(), TransactionValidityError> {1947 let (tip, who, imbalance, fee) = pre;1948 if let Some(payed) = imbalance {1949 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(1950 len as u32, info, post_info, tip,1951 );1952 let refund = fee.saturating_sub(actual_fee);1953 let actual_payment =1954 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(1955 &who, refund,1956 ) {1957 Ok(refund_imbalance) => {1958 // The refund cannot be larger than the up front payed max weight.1959 // `PostDispatchInfo::calc_unspent` guards against such a case.1960 match payed.offset(refund_imbalance) {1961 Ok(actual_payment) => actual_payment,1962 Err(_) => return Err(InvalidTransaction::Payment.into()),1963 }1964 }1965 // We do not recreate the account using the refund. The up front payment1966 // is gone in that case.1967 Err(_) => payed,1968 };1969 let imbalances = actual_payment.split(tip);1970 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(1971 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),1972 );1973 }1974 Ok(())1975 }1976}1977// #endregion197819791#![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,12 dispatch::DispatchResult,13 ensure, 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};25// use frame_support::weights::{Weight, constants::RocksDbWeight as DbWeight};2627use frame_system::{self as system, ensure_signed, ensure_root};28use sp_runtime::sp_std::prelude::Vec;29use sp_runtime::{30 traits::{31 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SignedExtension, Zero,32 },33 transaction_validity::{34 InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,35 },36 FixedPointOperand, FixedU128,37};38use pallet_contracts::ContractAddressFor;39use sp_runtime::traits::StaticLookup;4041#[cfg(test)]42mod mock;4344#[cfg(test)]45mod tests;4647mod default_weights;4849// Structs50// #region5152#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]53#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]54pub enum CollectionMode {55 Invalid,56 // custom data size57 NFT(u32),58 // decimal points59 Fungible(u32),60 // custom data size and decimal points61 ReFungible(u32, u32),62}6364impl Into<u8> for CollectionMode {65 fn into(self) -> u8 {66 match self {67 CollectionMode::Invalid => 0,68 CollectionMode::NFT(_) => 1,69 CollectionMode::Fungible(_) => 2,70 CollectionMode::ReFungible(_, _) => 3,71 }72 }73}7475#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]76#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]77pub enum AccessMode {78 Normal,79 WhiteList,80}81impl Default for AccessMode {82 fn default() -> Self {83 Self::Normal84 }85}8687impl Default for CollectionMode {88 fn default() -> Self {89 Self::Invalid90 }91}9293#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]94#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]95pub struct Ownership<AccountId> {96 pub owner: AccountId,97 pub fraction: u128,98}99100#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]101#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]102pub struct CollectionType<AccountId> {103 pub owner: AccountId,104 pub mode: CollectionMode,105 pub access: AccessMode,106 pub decimal_points: u32,107 pub name: Vec<u16>, // 64 include null escape char108 pub description: Vec<u16>, // 256 include null escape char109 pub token_prefix: Vec<u8>, // 16 include null escape char110 pub custom_data_size: u32,111 pub mint_mode: bool,112 pub offchain_schema: Vec<u8>,113 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender114 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship115}116117#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]118#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]119pub struct CollectionAdminsType<AccountId> {120 pub admin: AccountId,121 pub collection_id: u64,122}123124#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]125#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]126pub struct NftItemType<AccountId> {127 pub collection: u64,128 pub owner: AccountId,129 pub data: Vec<u8>,130}131132#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]133#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]134pub struct FungibleItemType<AccountId> {135 pub collection: u64,136 pub owner: AccountId,137 pub value: u128,138}139140#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]141#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]142pub struct ReFungibleItemType<AccountId> {143 pub collection: u64,144 pub owner: Vec<Ownership<AccountId>>,145 pub data: Vec<u8>,146}147148#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]149#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]150pub struct ApprovePermissions<AccountId> {151 pub approved: AccountId,152 pub amount: u64,153}154155#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]156#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]157pub struct VestingItem<AccountId, Moment> {158 pub sender: AccountId,159 pub recipient: AccountId,160 pub collection_id: u64,161 pub item_id: u64,162 pub amount: u64,163 pub vesting_date: Moment,164}165166#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]167#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]168pub struct BasketItem<AccountId, BlockNumber> {169 pub address: AccountId,170 pub start_block: BlockNumber,171}172173#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]174#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]175pub struct ChainLimits {176 pub collection_numbers_limit: u64,177 pub account_token_ownership_limit: u64,178 pub collections_admins_limit: u64,179 pub custom_data_limit: u32,180181 // Timeouts for item types in passed blocks182 pub nft_sponsor_transfer_timeout: u32,183 pub fungible_sponsor_transfer_timeout: u32,184 pub refungible_sponsor_transfer_timeout: u32,185}186187pub trait WeightInfo {188 fn create_collection() -> Weight;189 fn destroy_collection() -> Weight;190 fn add_to_white_list() -> Weight;191 fn remove_from_white_list() -> Weight;192 fn set_public_access_mode() -> Weight;193 fn set_mint_permission() -> Weight;194 fn change_collection_owner() -> Weight;195 fn add_collection_admin() -> Weight;196 fn remove_collection_admin() -> Weight;197 fn set_collection_sponsor() -> Weight;198 fn confirm_sponsorship() -> Weight;199 fn remove_collection_sponsor() -> Weight;200 fn create_item(s: usize, ) -> Weight;201 fn burn_item() -> Weight;202 fn transfer() -> Weight;203 fn approve() -> Weight;204 fn transfer_from() -> Weight;205 fn set_offchain_schema() -> Weight;206 // fn enable_contract_sponsoring() -> Weight;207}208209pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {210 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;211212 /// Weight information for extrinsics in this pallet.213 type WeightInfo: WeightInfo;214}215216#[cfg(feature = "runtime-benchmarks")]217mod benchmarking;218219// #endregion220221decl_storage! {222 trait Store for Module<T: Trait> as Nft {223224 // Private members225 NextCollectionID: u64;226 CreatedCollectionCount: u64;227 ChainVersion: u64;228 ItemListIndex: map hasher(blake2_128_concat) u64 => u64;229230 // Chain limits struct231 pub ChainLimit get(fn chain_limit) config(): ChainLimits;232233 // Bound counters234 CollectionCount: u64;235 pub AccountItemCount get(fn account_item_count): map hasher(identity) T::AccountId => u64;236237 // Basic collections238 pub Collection get(fn collection) config(): map hasher(identity) u64 => CollectionType<T::AccountId>;239 pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec<T::AccountId>;240 pub WhiteList get(fn white_list): map hasher(identity) u64 => Vec<T::AccountId>;241242 /// Balance owner per collection map243 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => u64;244245 /// second parameter: item id + owner account id246 pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) (u64, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;247248 /// Item collections249 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType<T::AccountId>;250 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType<T::AccountId>;251 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType<T::AccountId>;252253 /// Index list254 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec<u64>;255256 /// Tokens transfer baskets257 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;258 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => Vec<BasketItem<T::AccountId, T::BlockNumber>>;259 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;260261 // Contract Sponsorship and Ownership262 pub ContractOwner get(fn contract_owner): map hasher(identity) T::AccountId => T::AccountId;263 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(identity) T::AccountId => bool;264 }265 add_extra_genesis {266 build(|config: &GenesisConfig<T>| {267 // Modification of storage268 for (_num, _c) in &config.collection {269 <Module<T>>::init_collection(_c);270 }271272 for (_num, _q, _i) in &config.nft_item_id {273 <Module<T>>::init_nft_token(_i);274 }275276 for (_num, _q, _i) in &config.fungible_item_id {277 <Module<T>>::init_fungible_token(_i);278 }279280 for (_num, _q, _i) in &config.refungible_item_id {281 <Module<T>>::init_refungible_token(_i);282 }283 })284 }285}286287decl_event!(288 pub enum Event<T>289 where290 AccountId = <T as system::Trait>::AccountId,291 {292 /// New collection was created293 /// 294 /// # Arguments295 /// 296 /// * collection_id: Globally unique identifier of newly created collection.297 /// 298 /// * mode: [CollectionMode] converted into u8.299 /// 300 /// * account_id: Collection owner.301 Created(u64, u8, AccountId),302303 /// New item was created.304 /// 305 /// # Arguments306 /// 307 /// * collection_id: Id of the collection where item was created.308 /// 309 /// * item_id: Id of an item. Unique within the collection.310 ItemCreated(u64, u64),311312 /// Collection item was burned.313 /// 314 /// # Arguments315 /// 316 /// collection_id.317 /// 318 /// item_id: Identifier of burned NFT.319 ItemDestroyed(u64, u64),320 }321);322323decl_module! {324 pub struct Module<T: Trait> for enum Call where origin: T::Origin {325326 fn deposit_event() = default;327328 fn on_initialize(now: T::BlockNumber) -> Weight {329330 if ChainVersion::get() < 2331 {332 let value = NextCollectionID::get();333 CreatedCollectionCount::put(value);334 ChainVersion::put(2);335 }336337 0338 }339340 /// 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.341 /// 342 /// # Permissions343 /// 344 /// * Anyone.345 /// 346 /// # Arguments347 /// 348 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.349 /// 350 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.351 /// 352 /// * token_prefix: UTF-8 string with token prefix.353 /// 354 /// * mode: [CollectionMode] collection type and type dependent data.355 // returns collection ID356 #[weight = T::WeightInfo::create_collection()]357 pub fn create_collection(origin,358 collection_name: Vec<u16>,359 collection_description: Vec<u16>,360 token_prefix: Vec<u8>,361 mode: CollectionMode) -> DispatchResult {362363 // Anyone can create a collection364 let who = ensure_signed(origin)?;365 let custom_data_size = match mode {366 CollectionMode::NFT(size) => {367368 // bound Custom data size369 ensure!(size < ChainLimit::get().custom_data_limit, "Custom data size bound exceeded");370 size371 },372 CollectionMode::ReFungible(size, _) => {373374 // bound Custom data size375 ensure!(size < ChainLimit::get().custom_data_limit, "Custom data size bound exceeded");376 size377 },378 _ => 0379 };380381 let decimal_points = match mode {382 CollectionMode::Fungible(points) => points,383 CollectionMode::ReFungible(_, points) => points,384 _ => 0385 };386387 // bound Total number of collections388 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, "Total collections bound exceeded");389390 // check params391 ensure!(decimal_points <= 4, "decimal_points parameter must be lower than 4");392393 let mut name = collection_name.to_vec();394 name.push(0);395 ensure!(name.len() <= 64, "Collection name can not be longer than 63 char");396397 let mut description = collection_description.to_vec();398 description.push(0);399 ensure!(name.len() <= 256, "Collection description can not be longer than 255 char");400401 let mut prefix = token_prefix.to_vec();402 prefix.push(0);403 ensure!(prefix.len() <= 16, "Token prefix can not be longer than 15 char");404405 // Generate next collection ID406 let next_id = CreatedCollectionCount::get()407 .checked_add(1)408 .expect("collection id error");409410 // bound counter411 let total = CollectionCount::get()412 .checked_add(1)413 .expect("collection counter error");414415 CreatedCollectionCount::put(next_id);416 CollectionCount::put(total);417418 // Create new collection419 let new_collection = CollectionType {420 owner: who.clone(),421 name: name,422 mode: mode.clone(),423 mint_mode: false,424 access: AccessMode::Normal,425 description: description,426 decimal_points: decimal_points,427 token_prefix: prefix,428 offchain_schema: Vec::new(),429 custom_data_size: custom_data_size,430 sponsor: T::AccountId::default(),431 unconfirmed_sponsor: T::AccountId::default(),432 };433434 // Add new collection to map435 <Collection<T>>::insert(next_id, new_collection);436437 // call event438 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));439440 Ok(())441 }442443 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.444 /// 445 /// # Permissions446 /// 447 /// * Collection Owner.448 /// 449 /// # Arguments450 /// 451 /// * collection_id: collection to destroy.452 #[weight = T::WeightInfo::destroy_collection()]453 pub fn destroy_collection(origin, collection_id: u64) -> DispatchResult {454455 let sender = ensure_signed(origin)?;456 Self::check_owner_permissions(collection_id, sender)?;457458 <AddressTokens<T>>::remove_prefix(collection_id);459 <ApprovedList<T>>::remove_prefix(collection_id);460 <Balance<T>>::remove_prefix(collection_id);461 <ItemListIndex>::remove(collection_id);462 <AdminList<T>>::remove(collection_id);463 <Collection<T>>::remove(collection_id);464 <WhiteList<T>>::remove(collection_id);465466 <NftItemList<T>>::remove_prefix(collection_id);467 <FungibleItemList<T>>::remove_prefix(collection_id);468 <ReFungibleItemList<T>>::remove_prefix(collection_id);469470 <NftTransferBasket<T>>::remove_prefix(collection_id);471 <FungibleTransferBasket<T>>::remove_prefix(collection_id);472 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);473474 if CollectionCount::get() > 0475 {476 // bound couter477 let total = CollectionCount::get()478 .checked_sub(1)479 .expect("collection counter error");480481 CollectionCount::put(total);482 }483484 Ok(())485 }486487 /// Add an address to white list.488 /// 489 /// # Permissions490 /// 491 /// * Collection Owner492 /// * Collection Admin493 /// 494 /// # Arguments495 /// 496 /// * collection_id.497 /// 498 /// * address.499 #[weight = T::WeightInfo::add_to_white_list()]500 pub fn add_to_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{501502 let sender = ensure_signed(origin)?;503 Self::check_owner_or_admin_permissions(collection_id, sender)?;504505 let mut white_list_collection: Vec<T::AccountId>;506 if <WhiteList<T>>::contains_key(collection_id) {507 white_list_collection = <WhiteList<T>>::get(collection_id);508 if !white_list_collection.contains(&address.clone())509 {510 white_list_collection.push(address.clone());511 }512 }513 else {514 white_list_collection = Vec::new();515 white_list_collection.push(address.clone());516 }517518 <WhiteList<T>>::insert(collection_id, white_list_collection);519 Ok(())520 }521522 /// Remove an address from white list.523 /// 524 /// # Permissions525 /// 526 /// * Collection Owner527 /// * Collection Admin528 /// 529 /// # Arguments530 /// 531 /// * collection_id.532 /// 533 /// * address.534 #[weight = T::WeightInfo::remove_from_white_list()]535 pub fn remove_from_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{536537 let sender = ensure_signed(origin)?;538 Self::check_owner_or_admin_permissions(collection_id, sender)?;539540 if <WhiteList<T>>::contains_key(collection_id) {541 let mut white_list_collection = <WhiteList<T>>::get(collection_id);542 if white_list_collection.contains(&address.clone())543 {544 white_list_collection.retain(|i| *i != address.clone());545 <WhiteList<T>>::insert(collection_id, white_list_collection);546 }547 }548549 Ok(())550 }551552 /// Toggle between normal and white list access for the methods with access for `Anyone`.553 /// 554 /// # Permissions555 /// 556 /// * Collection Owner.557 /// 558 /// # Arguments559 /// 560 /// * collection_id.561 /// 562 /// * mode: [AccessMode]563 #[weight = T::WeightInfo::set_public_access_mode()]564 pub fn set_public_access_mode(origin, collection_id: u64, mode: AccessMode) -> DispatchResult565 {566 let sender = ensure_signed(origin)?;567568 Self::check_owner_permissions(collection_id, sender)?;569 let mut target_collection = <Collection<T>>::get(collection_id);570 target_collection.access = mode;571 <Collection<T>>::insert(collection_id, target_collection);572573 Ok(())574 }575576 /// Allows Anyone to create tokens if:577 /// * White List is enabled, and578 /// * Address is added to white list, and579 /// * This method was called with True parameter580 /// 581 /// # Permissions582 /// * Collection Owner583 ///584 /// # Arguments585 /// 586 /// * collection_id.587 /// 588 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.589 #[weight = T::WeightInfo::set_mint_permission()]590 pub fn set_mint_permission(origin, collection_id: u64, mint_permission: bool) -> DispatchResult591 {592 let sender = ensure_signed(origin)?;593594 Self::check_owner_permissions(collection_id, sender)?;595 let mut target_collection = <Collection<T>>::get(collection_id);596 target_collection.mint_mode = mint_permission;597 <Collection<T>>::insert(collection_id, target_collection);598599 Ok(())600 }601602 /// Change the owner of the collection.603 /// 604 /// # Permissions605 /// 606 /// * Collection Owner.607 /// 608 /// # Arguments609 /// 610 /// * collection_id.611 /// 612 /// * new_owner.613 #[weight = T::WeightInfo::change_collection_owner()]614 pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult {615616 let sender = ensure_signed(origin)?;617 Self::check_owner_permissions(collection_id, sender)?;618 let mut target_collection = <Collection<T>>::get(collection_id);619 target_collection.owner = new_owner;620 <Collection<T>>::insert(collection_id, target_collection);621622 Ok(())623 }624625 /// Adds an admin of the Collection.626 /// 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. 627 /// 628 /// # Permissions629 /// 630 /// * Collection Owner.631 /// * Collection Admin.632 /// 633 /// # Arguments634 /// 635 /// * collection_id: ID of the Collection to add admin for.636 /// 637 /// * new_admin_id: Address of new admin to add.638 #[weight = T::WeightInfo::add_collection_admin()]639 pub fn add_collection_admin(origin, collection_id: u64, new_admin_id: T::AccountId) -> DispatchResult {640641 let sender = ensure_signed(origin)?;642 Self::check_owner_or_admin_permissions(collection_id, sender)?;643 let mut admin_arr: Vec<T::AccountId> = Vec::new();644645 if <AdminList<T>>::contains_key(collection_id)646 {647 admin_arr = <AdminList<T>>::get(collection_id);648 ensure!(!admin_arr.contains(&new_admin_id), "Account already has admin role");649 }650651 // Number of collection admins652 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, "Number of collection admins bound exceeded");653654 admin_arr.push(new_admin_id);655 <AdminList<T>>::insert(collection_id, admin_arr);656657 Ok(())658 }659660 /// 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.661 ///662 /// # Permissions663 /// 664 /// * Collection Owner.665 /// * Collection Admin.666 /// 667 /// # Arguments668 /// 669 /// * collection_id: ID of the Collection to remove admin for.670 /// 671 /// * account_id: Address of admin to remove.672 #[weight = T::WeightInfo::remove_collection_admin()]673 pub fn remove_collection_admin(origin, collection_id: u64, account_id: T::AccountId) -> DispatchResult {674675 let sender = ensure_signed(origin)?;676 Self::check_owner_or_admin_permissions(collection_id, sender)?;677678 if <AdminList<T>>::contains_key(collection_id)679 {680 let mut admin_arr = <AdminList<T>>::get(collection_id);681 admin_arr.retain(|i| *i != account_id);682 <AdminList<T>>::insert(collection_id, admin_arr);683 }684685 Ok(())686 }687688 /// # Permissions689 /// 690 /// * Collection Owner691 /// 692 /// # Arguments693 /// 694 /// * collection_id.695 /// 696 /// * new_sponsor.697 #[weight = T::WeightInfo::set_collection_sponsor()]698 pub fn set_collection_sponsor(origin, collection_id: u64, new_sponsor: T::AccountId) -> DispatchResult {699700 let sender = ensure_signed(origin)?;701 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");702703 let mut target_collection = <Collection<T>>::get(collection_id);704 ensure!(sender == target_collection.owner, "You do not own this collection");705706 target_collection.unconfirmed_sponsor = new_sponsor;707 <Collection<T>>::insert(collection_id, target_collection);708709 Ok(())710 }711712 /// # Permissions713 /// 714 /// * Sponsor.715 /// 716 /// # Arguments717 /// 718 /// * collection_id.719 #[weight = T::WeightInfo::confirm_sponsorship()]720 pub fn confirm_sponsorship(origin, collection_id: u64) -> DispatchResult {721722 let sender = ensure_signed(origin)?;723 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");724725 let mut target_collection = <Collection<T>>::get(collection_id);726 ensure!(sender == target_collection.unconfirmed_sponsor, "This address is not set as sponsor, use setCollectionSponsor first");727728 target_collection.sponsor = target_collection.unconfirmed_sponsor;729 target_collection.unconfirmed_sponsor = T::AccountId::default();730 <Collection<T>>::insert(collection_id, target_collection);731732 Ok(())733 }734735 /// Switch back to pay-per-own-transaction model.736 ///737 /// # Permissions738 ///739 /// * Collection owner.740 /// 741 /// # Arguments742 /// 743 /// * collection_id.744 #[weight = T::WeightInfo::remove_collection_sponsor()]745 pub fn remove_collection_sponsor(origin, collection_id: u64) -> DispatchResult {746747 let sender = ensure_signed(origin)?;748 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");749750 let mut target_collection = <Collection<T>>::get(collection_id);751 ensure!(sender == target_collection.owner, "You do not own this collection");752753 target_collection.sponsor = T::AccountId::default();754 <Collection<T>>::insert(collection_id, target_collection);755756 Ok(())757 }758759 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.760 /// 761 /// # Permissions762 /// 763 /// * Collection Owner.764 /// * Collection Admin.765 /// * Anyone if766 /// * White List is enabled, and767 /// * Address is added to white list, and768 /// * MintPermission is enabled (see SetMintPermission method)769 /// 770 /// # Arguments771 /// 772 /// * collection_id: ID of the collection.773 /// 774 /// * properties: Array of bytes that contains NFT properties. Since NFT Module is agnostic of properties meaning, it is treated purely as an array of bytes.775 /// 776 /// * owner: Address, initial owner of the NFT.777 // #[weight =778 // (130_000_000 as Weight)779 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))780 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))781 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]782783 #[weight = T::WeightInfo::create_item(properties.len())]784 pub fn create_item(origin, collection_id: u64, properties: Vec<u8>, owner: T::AccountId) -> DispatchResult {785786 let sender = ensure_signed(origin)?;787 Self::collection_exists(collection_id)?;788 let target_collection = <Collection<T>>::get(collection_id);789790 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {791 ensure!(target_collection.mint_mode == true, "Public minting is not allowed for this collection");792 Self::check_white_list(collection_id, &owner)?;793 Self::check_white_list(collection_id, &sender)?;794 }795796 match target_collection.mode797 {798 CollectionMode::NFT(_) => {799800 // check size801 ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large");802803 // Create nft item804 let item = NftItemType {805 collection: collection_id,806 owner: owner,807 data: properties.clone(),808 };809810 Self::add_nft_item(item)?;811812 },813 CollectionMode::Fungible(_) => {814815 // check size816 ensure!(properties.len() as u32 == 0, "Size of item must be 0 with fungible type");817818 let item = FungibleItemType {819 collection: collection_id,820 owner: owner,821 value: (10 as u128).pow(target_collection.decimal_points)822 };823824 Self::add_fungible_item(item)?;825 },826 CollectionMode::ReFungible(_, _) => {827828 // check size829 ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large");830831 let mut owner_list = Vec::new();832 let value = (10 as u128).pow(target_collection.decimal_points);833 owner_list.push(Ownership {owner: owner.clone(), fraction: value});834835 let item = ReFungibleItemType {836 collection: collection_id,837 owner: owner_list,838 data: properties.clone()839 };840841 Self::add_refungible_item(item)?;842 },843 _ => { ensure!(1 == 0,"just error"); }844845 };846847 // call event848 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));849850 Ok(())851 }852853 /// Destroys a concrete instance of NFT.854 /// 855 /// # Permissions856 /// 857 /// * Collection Owner.858 /// * Collection Admin.859 /// * Current NFT Owner.860 /// 861 /// # Arguments862 /// 863 /// * collection_id: ID of the collection.864 /// 865 /// * item_id: ID of NFT to burn.866 #[weight = T::WeightInfo::burn_item()]867 pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult {868869 let sender = ensure_signed(origin)?;870 Self::collection_exists(collection_id)?;871872 // Transfer permissions check873 let target_collection = <Collection<T>>::get(collection_id);874 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||875 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),876 "Only item owner, collection owner and admins can modify item");877878 if target_collection.access == AccessMode::WhiteList {879 Self::check_white_list(collection_id, &sender)?;880 }881882 match target_collection.mode883 {884 CollectionMode::NFT(_) => Self::burn_nft_item(collection_id, item_id)?,885 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,886 CollectionMode::ReFungible(_, _) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,887 _ => ()888 };889890 // call event891 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));892893 Ok(())894 }895896 /// Change ownership of the token.897 /// 898 /// # Permissions899 /// 900 /// * Collection Owner901 /// * Collection Admin902 /// * Current NFT owner903 ///904 /// # Arguments905 /// 906 /// * recipient: Address of token recipient.907 /// 908 /// * collection_id.909 /// 910 /// * item_id: ID of the item911 /// * Non-Fungible Mode: Required.912 /// * Fungible Mode: Ignored.913 /// * Re-Fungible Mode: Required.914 /// 915 /// * value: Amount to transfer.916 /// * Non-Fungible Mode: Ignored917 /// * Fungible Mode: Must specify transferred amount918 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)919 #[weight = T::WeightInfo::transfer()]920 pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult {921922 let sender = ensure_signed(origin)?;923924 // Transfer permissions check925 let target_collection = <Collection<T>>::get(collection_id);926 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||927 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),928 "Only item owner, collection owner and admins can modify item");929930 if target_collection.access == AccessMode::WhiteList {931 Self::check_white_list(collection_id, &sender)?;932 Self::check_white_list(collection_id, &recipient)?;933 }934935 match target_collection.mode936 {937 CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,938 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,939 CollectionMode::ReFungible(_, _) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,940 _ => ()941 };942943 Ok(())944 }945946 /// Set, change, or remove approved address to transfer the ownership of the NFT.947 /// 948 /// # Permissions949 /// 950 /// * Collection Owner951 /// * Collection Admin952 /// * Current NFT owner953 /// 954 /// # Arguments955 /// 956 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).957 /// 958 /// * collection_id.959 /// 960 /// * item_id: ID of the item.961 #[weight = T::WeightInfo::approve()]962 pub fn approve(origin, approved: T::AccountId, collection_id: u64, item_id: u64) -> DispatchResult {963964 let sender = ensure_signed(origin)?;965966 // Transfer permissions check967 let target_collection = <Collection<T>>::get(collection_id);968 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||969 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),970 "Only item owner, collection owner and admins can approve");971972 if target_collection.access == AccessMode::WhiteList {973 Self::check_white_list(collection_id, &sender)?;974 Self::check_white_list(collection_id, &approved)?;975 }976977 // amount param stub978 let amount = 100000000;979980 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));981 if list_exists {982983 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));984 let item_contains = list.iter().any(|i| i.approved == approved);985986 if !item_contains {987 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });988 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);989 }990 } else {991992 let mut list = Vec::new();993 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });994 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);995 }996997 Ok(())998 }999 1000 /// 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.1001 /// 1002 /// # Permissions1003 /// * Collection Owner1004 /// * Collection Admin1005 /// * Current NFT owner1006 /// * Address approved by current NFT owner1007 /// 1008 /// # Arguments1009 /// 1010 /// * from: Address that owns token.1011 /// 1012 /// * recipient: Address of token recipient.1013 /// 1014 /// * collection_id.1015 /// 1016 /// * item_id: ID of the item.1017 /// 1018 /// * value: Amount to transfer.1019 #[weight = T::WeightInfo::transfer_from()]1020 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64 ) -> DispatchResult {10211022 let sender = ensure_signed(origin)?;1023 let mut appoved_transfer = false;10241025 // Check approve1026 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1027 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1028 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1029 if opt_item.is_some()1030 {1031 appoved_transfer = true;1032 ensure!(opt_item.unwrap().amount >= value, "Requested value more than approved");1033 }1034 }10351036 // Transfer permissions check1037 let target_collection = <Collection<T>>::get(collection_id);1038 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1039 "Only item owner, collection owner and admins can modify items");10401041 if target_collection.access == AccessMode::WhiteList {1042 Self::check_white_list(collection_id, &sender)?;1043 Self::check_white_list(collection_id, &recipient)?;1044 }10451046 // remove approve1047 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1048 .into_iter().filter(|i| i.approved != sender.clone()).collect();1049 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);105010511052 match target_collection.mode1053 {1054 CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, from, recipient)?,1055 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1056 CollectionMode::ReFungible(_, _) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1057 _ => ()1058 };10591060 Ok(())1061 }10621063 ///1064 #[weight = 0]1065 pub fn safe_transfer_from(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult {10661067 // let no_perm_mes = "You do not have permissions to modify this collection";1068 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1069 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1070 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10711072 // // on_nft_received call10731074 // Self::transfer(origin, collection_id, item_id, new_owner)?;10751076 Ok(())1077 }10781079 /// Set off-chain data schema.1080 /// 1081 /// # Permissions1082 /// 1083 /// * Collection Owner1084 /// * Collection Admin1085 /// 1086 /// # Arguments1087 /// 1088 /// * collection_id.1089 /// 1090 /// * schema: String representing the offchain data schema.1091 #[weight = T::WeightInfo::set_offchain_schema()]1092 pub fn set_offchain_schema(1093 origin,1094 collection_id: u64,1095 schema: Vec<u8>1096 ) -> DispatchResult {1097 let sender = ensure_signed(origin)?;1098 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;10991100 let mut target_collection = <Collection<T>>::get(collection_id);1101 target_collection.offchain_schema = schema;1102 <Collection<T>>::insert(collection_id, target_collection);11031104 Ok(())1105 }11061107 // Sudo permissions function1108 #[weight = 0]1109 pub fn set_chain_limits(1110 origin,1111 limits: ChainLimits1112 ) -> DispatchResult {1113 ensure_root(origin)?;1114 <ChainLimit>::put(limits);1115 Ok(())1116 }11171118 /// Enable smart contract self-sponsoring.1119 /// 1120 /// # Permissions1121 /// 1122 /// * Contract Owner1123 /// 1124 /// # Arguments1125 /// 1126 /// * contract address1127 /// * enable flag1128 /// 1129 #[weight = 0]1130 pub fn enable_contract_sponsoring(1131 origin,1132 contract_address: T::AccountId,1133 enable: bool1134 ) -> DispatchResult {1135 let sender = ensure_signed(origin)?;1136 let mut is_owner = false;1137 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1138 let owner = <ContractOwner<T>>::get(&contract_address);1139 is_owner = sender == owner;1140 }1141 ensure!(is_owner, "Only contract owner may call this method");11421143 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1144 Ok(())1145 }11461147 }1148}11491150impl<T: Trait> Module<T> {1151 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1152 let current_index = <ItemListIndex>::get(item.collection)1153 .checked_add(1)1154 .expect("Item list index id error");1155 let itemcopy = item.clone();1156 let owner = item.owner.clone();1157 let value = item.value as u64;11581159 Self::add_token_index(item.collection, current_index, owner.clone())?;11601161 <ItemListIndex>::insert(item.collection, current_index);1162 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);11631164 // Add current block1165 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1166 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1167 1168 // Update balance1169 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1170 .checked_add(value)1171 .unwrap();1172 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);11731174 Ok(())1175 }11761177 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1178 let current_index = <ItemListIndex>::get(item.collection)1179 .checked_add(1)1180 .expect("Item list index id error");1181 let itemcopy = item.clone();11821183 let value = item.owner.first().unwrap().fraction as u64;1184 let owner = item.owner.first().unwrap().owner.clone();11851186 Self::add_token_index(item.collection, current_index, owner.clone())?;11871188 <ItemListIndex>::insert(item.collection, current_index);1189 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);11901191 // Add current block1192 let block_number: T::BlockNumber = 0.into();1193 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);11941195 // Update balance1196 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1197 .checked_add(value)1198 .unwrap();1199 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);12001201 Ok(())1202 }12031204 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1205 let current_index = <ItemListIndex>::get(item.collection)1206 .checked_add(1)1207 .expect("Item list index id error");12081209 let item_owner = item.owner.clone();1210 let collection_id = item.collection.clone();1211 Self::add_token_index(collection_id, current_index, item.owner.clone())?;12121213 <ItemListIndex>::insert(collection_id, current_index);1214 <NftItemList<T>>::insert(collection_id, current_index, item);12151216 // Add current block1217 let block_number: T::BlockNumber = 0.into();1218 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);12191220 // Update balance1221 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1222 .checked_add(1)1223 .unwrap();1224 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);12251226 Ok(())1227 }12281229 fn burn_refungible_item(1230 collection_id: u64,1231 item_id: u64,1232 owner: T::AccountId,1233 ) -> DispatchResult {1234 ensure!(1235 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1236 "Item does not exists"1237 );1238 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1239 let item = collection1240 .owner1241 .iter()1242 .filter(|&i| i.owner == owner)1243 .next()1244 .unwrap();1245 Self::remove_token_index(collection_id, item_id, owner.clone())?;12461247 // remove approve list1248 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));12491250 // update balance1251 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1252 .checked_sub(item.fraction as u64)1253 .unwrap();1254 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);12551256 <ReFungibleItemList<T>>::remove(collection_id, item_id);12571258 Ok(())1259 }12601261 fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult {1262 ensure!(1263 <NftItemList<T>>::contains_key(collection_id, item_id),1264 "Item does not exists"1265 );1266 let item = <NftItemList<T>>::get(collection_id, item_id);1267 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;12681269 // remove approve list1270 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));12711272 // update balance1273 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1274 .checked_sub(1)1275 .unwrap();1276 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1277 <NftItemList<T>>::remove(collection_id, item_id);12781279 Ok(())1280 }12811282 fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult {1283 ensure!(1284 <FungibleItemList<T>>::contains_key(collection_id, item_id),1285 "Item does not exists"1286 );1287 let item = <FungibleItemList<T>>::get(collection_id, item_id);1288 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;12891290 // remove approve list1291 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));12921293 // update balance1294 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1295 .checked_sub(item.value as u64)1296 .unwrap();1297 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);12981299 <FungibleItemList<T>>::remove(collection_id, item_id);13001301 Ok(())1302 }13031304 fn collection_exists(collection_id: u64) -> DispatchResult {1305 ensure!(1306 <Collection<T>>::contains_key(collection_id),1307 "This collection does not exist"1308 );1309 Ok(())1310 }13111312 fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {1313 Self::collection_exists(collection_id)?;13141315 let target_collection = <Collection<T>>::get(collection_id);1316 ensure!(1317 subject == target_collection.owner,1318 "You do not own this collection"1319 );13201321 Ok(())1322 }13231324 fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool {1325 let target_collection = <Collection<T>>::get(collection_id);1326 let mut result: bool = subject == target_collection.owner;1327 let exists = <AdminList<T>>::contains_key(collection_id);13281329 if !result & exists {1330 if <AdminList<T>>::get(collection_id).contains(&subject) {1331 result = true1332 }1333 }13341335 result1336 }13371338 fn check_owner_or_admin_permissions(1339 collection_id: u64,1340 subject: T::AccountId,1341 ) -> DispatchResult {1342 Self::collection_exists(collection_id)?;1343 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());13441345 ensure!(1346 result,1347 "You do not have permissions to modify this collection"1348 );1349 Ok(())1350 }13511352 fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool {1353 let target_collection = <Collection<T>>::get(collection_id);13541355 match target_collection.mode {1356 CollectionMode::NFT(_) => {1357 <NftItemList<T>>::get(collection_id, item_id).owner == subject1358 }1359 CollectionMode::Fungible(_) => {1360 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1361 }1362 CollectionMode::ReFungible(_, _) => {1363 <ReFungibleItemList<T>>::get(collection_id, item_id)1364 .owner1365 .iter()1366 .any(|i| i.owner == subject)1367 }1368 CollectionMode::Invalid => false,1369 }1370 }13711372 fn check_white_list(collection_id: u64, address: &T::AccountId) -> DispatchResult {1373 let mes = "Address is not in white list";1374 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1375 let wl = <WhiteList<T>>::get(collection_id);1376 ensure!(wl.contains(address), mes);13771378 Ok(())1379 }13801381 fn transfer_fungible(1382 collection_id: u64,1383 item_id: u64,1384 value: u64,1385 owner: T::AccountId,1386 new_owner: T::AccountId,1387 ) -> DispatchResult {1388 ensure!(1389 <FungibleItemList<T>>::contains_key(collection_id, item_id),1390 "Item not exists"1391 );13921393 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1394 let amount = full_item.value;13951396 ensure!(amount >= value.into(), "Item balance not enouth");13971398 // update balance1399 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1400 .checked_sub(value)1401 .unwrap();1402 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);14031404 let mut new_owner_account_id = 0;1405 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1406 if new_owner_items.len() > 0 {1407 new_owner_account_id = new_owner_items[0];1408 }14091410 let val64 = value.into();14111412 // transfer1413 if amount == val64 && new_owner_account_id == 0 {1414 // change owner1415 // new owner do not have account1416 let mut new_full_item = full_item.clone();1417 new_full_item.owner = new_owner.clone();1418 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);14191420 // update balance1421 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1422 .checked_add(value)1423 .unwrap();1424 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);14251426 // update index collection1427 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1428 } else {1429 let mut new_full_item = full_item.clone();1430 new_full_item.value -= val64;14311432 // separate amount1433 if new_owner_account_id > 0 {1434 // new owner has account1435 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1436 item.value += val64;14371438 // update balance1439 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1440 .checked_add(value)1441 .unwrap();1442 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);14431444 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1445 } else {1446 // new owner do not have account1447 let item = FungibleItemType {1448 collection: collection_id,1449 owner: new_owner.clone(),1450 value: val64,1451 };14521453 Self::add_fungible_item(item)?;1454 }14551456 if amount == val64 {1457 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;14581459 // remove approve list1460 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1461 <FungibleItemList<T>>::remove(collection_id, item_id);1462 }14631464 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1465 }14661467 Ok(())1468 }14691470 fn transfer_refungible(1471 collection_id: u64,1472 item_id: u64,1473 value: u64,1474 owner: T::AccountId,1475 new_owner: T::AccountId,1476 ) -> DispatchResult {1477 ensure!(1478 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1479 "Item not exists"1480 );14811482 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1483 let item = full_item1484 .owner1485 .iter()1486 .filter(|i| i.owner == owner)1487 .next()1488 .unwrap();1489 let amount = item.fraction;14901491 ensure!(amount >= value.into(), "Item balance not enouth");14921493 // update balance1494 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1495 .checked_sub(value)1496 .unwrap();1497 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);14981499 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1500 .checked_add(value)1501 .unwrap();1502 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);15031504 let old_owner = item.owner.clone();1505 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1506 let val64 = value.into();15071508 // transfer1509 if amount == val64 && !new_owner_has_account {1510 // change owner1511 // new owner do not have account1512 let mut new_full_item = full_item.clone();1513 new_full_item1514 .owner1515 .iter_mut()1516 .find(|i| i.owner == owner)1517 .unwrap()1518 .owner = new_owner.clone();1519 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);15201521 // update index collection1522 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1523 } else {1524 let mut new_full_item = full_item.clone();1525 new_full_item1526 .owner1527 .iter_mut()1528 .find(|i| i.owner == owner)1529 .unwrap()1530 .fraction -= val64;15311532 // separate amount1533 if new_owner_has_account {1534 // new owner has account1535 new_full_item1536 .owner1537 .iter_mut()1538 .find(|i| i.owner == new_owner)1539 .unwrap()1540 .fraction += val64;1541 } else {1542 // new owner do not have account1543 new_full_item.owner.push(Ownership {1544 owner: new_owner.clone(),1545 fraction: val64,1546 });1547 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1548 }15491550 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1551 }15521553 Ok(())1554 }15551556 fn transfer_nft(1557 collection_id: u64,1558 item_id: u64,1559 sender: T::AccountId,1560 new_owner: T::AccountId,1561 ) -> DispatchResult {1562 ensure!(1563 <NftItemList<T>>::contains_key(collection_id, item_id),1564 "Item not exists"1565 );15661567 let mut item = <NftItemList<T>>::get(collection_id, item_id);15681569 ensure!(1570 sender == item.owner,1571 "sender parameter and item owner must be equal"1572 );15731574 // update balance1575 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1576 .checked_sub(1)1577 .unwrap();1578 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);15791580 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1581 .checked_add(1)1582 .unwrap();1583 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);15841585 // change owner1586 let old_owner = item.owner.clone();1587 item.owner = new_owner.clone();1588 <NftItemList<T>>::insert(collection_id, item_id, item);15891590 // update index collection1591 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;15921593 // reset approved list1594 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1595 Ok(())1596 }15971598 fn init_collection(item: &CollectionType<T::AccountId>) {1599 // check params1600 assert!(1601 item.decimal_points <= 4,1602 "decimal_points parameter must be lower than 4"1603 );1604 assert!(1605 item.name.len() <= 64,1606 "Collection name can not be longer than 63 char"1607 );1608 assert!(1609 item.name.len() <= 256,1610 "Collection description can not be longer than 255 char"1611 );1612 assert!(1613 item.token_prefix.len() <= 16,1614 "Token prefix can not be longer than 15 char"1615 );16161617 // Generate next collection ID1618 let next_id = CreatedCollectionCount::get()1619 .checked_add(1)1620 .expect("collection id error");16211622 CreatedCollectionCount::put(next_id);1623 }16241625 fn init_nft_token(item: &NftItemType<T::AccountId>) {1626 let current_index = <ItemListIndex>::get(item.collection)1627 .checked_add(1)1628 .expect("Item list index id error");16291630 let item_owner = item.owner.clone();1631 let collection_id = item.collection.clone();1632 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();16331634 <ItemListIndex>::insert(collection_id, current_index);16351636 // Update balance1637 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1638 .checked_add(1)1639 .unwrap();1640 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);1641 }16421643 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {1644 let current_index = <ItemListIndex>::get(item.collection)1645 .checked_add(1)1646 .expect("Item list index id error");1647 let owner = item.owner.clone();1648 let value = item.value as u64;16491650 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();16511652 <ItemListIndex>::insert(item.collection, current_index);16531654 // Update balance1655 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1656 .checked_add(value)1657 .unwrap();1658 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1659 }16601661 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {1662 let current_index = <ItemListIndex>::get(item.collection)1663 .checked_add(1)1664 .expect("Item list index id error");16651666 let value = item.owner.first().unwrap().fraction as u64;1667 let owner = item.owner.first().unwrap().owner.clone();16681669 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();16701671 <ItemListIndex>::insert(item.collection, current_index);16721673 // Update balance1674 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1675 .checked_add(value)1676 .unwrap();1677 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1678 }16791680 fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {16811682 // add to account limit1683 if <AccountItemCount<T>>::contains_key(owner.clone()) {16841685 // bound Owned tokens by a single address1686 let count = <AccountItemCount<T>>::get(owner.clone());1687 ensure!(count < ChainLimit::get().account_token_ownership_limit, "Owned tokens by a single address bound exceeded");16881689 <AccountItemCount<T>>::insert(owner.clone(), 1690 count.checked_add(1).unwrap());1691 }1692 else {1693 <AccountItemCount<T>>::insert(owner.clone(), 1);1694 }16951696 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1697 if list_exists {1698 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1699 let item_contains = list.contains(&item_index.clone());17001701 if !item_contains {1702 list.push(item_index.clone());1703 }17041705 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);1706 } else {1707 let mut itm = Vec::new();1708 itm.push(item_index.clone());1709 <AddressTokens<T>>::insert(collection_id, owner, itm);1710 1711 }17121713 Ok(())1714 }17151716 fn remove_token_index(1717 collection_id: u64,1718 item_index: u64,1719 owner: T::AccountId,1720 ) -> DispatchResult {17211722 // update counter1723 <AccountItemCount<T>>::insert(owner.clone(), 1724 <AccountItemCount<T>>::get(owner.clone()).checked_sub(1).unwrap());172517261727 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1728 if list_exists {1729 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1730 let item_contains = list.contains(&item_index.clone());17311732 if item_contains {1733 list.retain(|&item| item != item_index);1734 <AddressTokens<T>>::insert(collection_id, owner, list);1735 }1736 }17371738 Ok(())1739 }17401741 fn move_token_index(1742 collection_id: u64,1743 item_index: u64,1744 old_owner: T::AccountId,1745 new_owner: T::AccountId,1746 ) -> DispatchResult {1747 Self::remove_token_index(collection_id, item_index, old_owner)?;1748 Self::add_token_index(collection_id, item_index, new_owner)?;17491750 Ok(())1751 }1752}17531754////////////////////////////////////////////////////////////////////////////////////////////////////1755// Economic models1756// #region17571758/// Fee multiplier.1759pub type Multiplier = FixedU128;17601761type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<1762 <T as system::Trait>::AccountId,1763>>::Balance;1764type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<1765 <T as system::Trait>::AccountId,1766>>::NegativeImbalance;17671768/// Require the transactor pay for themselves and maybe include a tip to gain additional priority1769/// in the queue.1770#[derive(Encode, Decode, Clone, Eq, PartialEq)]1771pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(1772 #[codec(compact)] BalanceOf<T>1773);17741775impl<T: Trait + Send + Sync> sp_std::fmt::Debug1776 for ChargeTransactionPayment<T>1777{1778 #[cfg(feature = "std")]1779 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {1780 write!(f, "ChargeTransactionPayment<{:?}>", self.0)1781 }1782 #[cfg(not(feature = "std"))]1783 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {1784 Ok(())1785 }1786}17871788impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>1789where1790 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,1791 BalanceOf<T>: Send + Sync + FixedPointOperand,1792{1793 /// utility constructor. Used only in client/factory code.1794 pub fn from(fee: BalanceOf<T>) -> Self {1795 Self(fee)1796 }17971798 pub fn traditional_fee(1799 len: usize,1800 info: &DispatchInfoOf<T::Call>,1801 tip: BalanceOf<T>,1802 ) -> BalanceOf<T>1803 where1804 T::Call: Dispatchable<Info = DispatchInfo>,1805 {1806 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)1807 }18081809 fn withdraw_fee(1810 &self,1811 who: &T::AccountId,1812 call: &T::Call,1813 info: &DispatchInfoOf<T::Call>,1814 len: usize,1815 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {1816 let tip = self.0;18171818 // Set fee based on call type. Creating collection costs 1 Unique.1819 // All other transactions have traditional fees so far1820 // let fee = match call.is_sub_type() {1821 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),1822 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes1823 // // _ => <BalanceOf<T>>::from(100)1824 // };1825 let fee = Self::traditional_fee(len, info, tip);18261827 // Determine who is paying transaction fee based on ecnomic model1828 // Parse call to extract collection ID and access collection sponsor1829 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {1830 Some(Call::create_item(collection_id, _properties, _owner)) => {1831 <Collection<T>>::get(collection_id).sponsor1832 }1833 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {1834 let _collection_mode = <Collection<T>>::get(collection_id).mode;18351836 // sponsor timeout1837 let sponsor_transfer = match _collection_mode {1838 CollectionMode::NFT(_) => {1839 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);1840 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;1841 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();1842 if block_number >= limit_time {1843 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);1844 true1845 }1846 else {1847 false1848 }1849 }1850 CollectionMode::Fungible(_) => {1851 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);1852 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;1853 if basket.iter().any(|i| i.address == _new_owner.clone())1854 {1855 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();1856 let limit_time = item.start_block + ChainLimit::get().fungible_sponsor_transfer_timeout.into();1857 if block_number >= limit_time {1858 basket.retain(|x| x.address == item.address);1859 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });1860 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);1861 true1862 }1863 else {1864 false1865 }1866 }1867 else {1868 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});1869 true1870 }1871 }1872 CollectionMode::ReFungible(_, _) => {1873 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);1874 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;1875 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();1876 if block_number >= limit_time {1877 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);1878 true1879 } else {1880 false1881 }1882 }1883 _ => {1884 false1885 },1886 };18871888 if !sponsor_transfer {1889 T::AccountId::default()1890 } else {1891 <Collection<T>>::get(collection_id).sponsor1892 }1893 }18941895 _ => T::AccountId::default(),1896 };18971898 // Sponsor smart contracts1899 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {19001901 // On instantiation: set the contract owner1902 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {19031904 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(1905 code_hash,1906 &data,1907 &who,1908 );1909 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());19101911 T::AccountId::default()1912 },19131914 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is1915 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {19161917 let mut sp = T::AccountId::default();1918 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());1919 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {1920 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {1921 sp = called_contract;1922 }1923 }19241925 sp1926 },19271928 _ => sponsor,1929 };19301931 let mut who_pays_fee: T::AccountId = sponsor.clone();1932 if sponsor == T::AccountId::default() {1933 who_pays_fee = who.clone();1934 }19351936 // Only mess with balances if fee is not zero.1937 if fee.is_zero() {1938 return Ok((fee, None));1939 }19401941 match <T as transaction_payment::Trait>::Currency::withdraw(1942 &who_pays_fee,1943 fee,1944 if tip.is_zero() {1945 WithdrawReason::TransactionPayment.into()1946 } else {1947 WithdrawReason::TransactionPayment | WithdrawReason::Tip1948 },1949 ExistenceRequirement::KeepAlive,1950 ) {1951 Ok(imbalance) => Ok((fee, Some(imbalance))),1952 Err(_) => Err(InvalidTransaction::Payment.into()),1953 }1954 }1955}195619571958impl<T: Trait + Send + Sync> SignedExtension1959 for ChargeTransactionPayment<T>1960where1961 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,1962 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,1963{1964 const IDENTIFIER: &'static str = "ChargeTransactionPayment";1965 type AccountId = T::AccountId;1966 type Call = T::Call;1967 type AdditionalSigned = ();1968 type Pre = (1969 BalanceOf<T>,1970 Self::AccountId,1971 Option<NegativeImbalanceOf<T>>,1972 BalanceOf<T>,1973 );1974 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {1975 Ok(())1976 }19771978 fn validate(1979 &self,1980 _who: &Self::AccountId,1981 _call: &Self::Call,1982 _info: &DispatchInfoOf<Self::Call>,1983 _len: usize,1984 ) -> TransactionValidity {1985 Ok(ValidTransaction::default())1986 }19871988 fn pre_dispatch(1989 self,1990 who: &Self::AccountId,1991 call: &Self::Call,1992 info: &DispatchInfoOf<Self::Call>,1993 len: usize,1994 ) -> Result<Self::Pre, TransactionValidityError> {1995 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;1996 Ok((self.0, who.clone(), imbalance, fee))1997 }19981999 fn post_dispatch(2000 pre: Self::Pre,2001 info: &DispatchInfoOf<Self::Call>,2002 post_info: &PostDispatchInfoOf<Self::Call>,2003 len: usize,2004 _result: &DispatchResult,2005 ) -> Result<(), TransactionValidityError> {2006 let (tip, who, imbalance, fee) = pre;2007 if let Some(payed) = imbalance {2008 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2009 len as u32, info, post_info, tip,2010 );2011 let refund = fee.saturating_sub(actual_fee);2012 let actual_payment =2013 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2014 &who, refund,2015 ) {2016 Ok(refund_imbalance) => {2017 // The refund cannot be larger than the up front payed max weight.2018 // `PostDispatchInfo::calc_unspent` guards against such a case.2019 match payed.offset(refund_imbalance) {2020 Ok(actual_payment) => actual_payment,2021 Err(_) => return Err(InvalidTransaction::Payment.into()),2022 }2023 }2024 // We do not recreate the account using the refund. The up front payment2025 // is gone in that case.2026 Err(_) => payed,2027 };2028 let imbalances = actual_payment.split(tip);2029 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2030 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2031 );2032 }2033 Ok(())2034 }2035}20362037// #endregion20382039