difftreelog
Transfer rate limit + chain bounds
in: master
4 files changed
node/src/chain_spec.rsdiffbeforeafterboth--- a/node/src/chain_spec.rs
+++ b/node/src/chain_spec.rs
@@ -130,23 +130,35 @@
}),
sudo: Some(SudoConfig { key: root_key }),
nft: Some(NftConfig {
- collection: vec![(1, CollectionType {
- owner: get_account_id_from_seed::<sr25519::Public>("Alice"),
- mode: CollectionMode::NFT(50),
- access: AccessMode::Normal,
- decimal_points: 0,
- name: vec!(),
- description: vec!(),
- token_prefix: vec!(),
- custom_data_size: 50,
- mint_mode: false,
- offchain_schema: vec!(),
- sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
- unconfirmed_sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
- })],
- nft_item_id: vec!(),
- fungible_item_id: vec!(),
- refungible_item_id: vec!(),
+ collection: vec![(
+ 1,
+ CollectionType {
+ owner: get_account_id_from_seed::<sr25519::Public>("Alice"),
+ mode: CollectionMode::NFT(50),
+ access: AccessMode::Normal,
+ decimal_points: 0,
+ name: vec![],
+ description: vec![],
+ token_prefix: vec![],
+ custom_data_size: 50,
+ mint_mode: false,
+ offchain_schema: vec![],
+ sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
+ unconfirmed_sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
+ },
+ )],
+ nft_item_id: vec![],
+ fungible_item_id: vec![],
+ refungible_item_id: vec![],
+ chain_limit: ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ },
}),
contracts: Some(ContractsConfig {
current_schedule: ContractsSchedule {
pallets/nft/src/lib.rsdiffbeforeafterboth1#![cfg_attr(not(feature = "std"), no_std)]23#[cfg(feature = "std")]4pub use serde::*;56use codec::{Decode, Encode};7pub use frame_support::{8 construct_runtime, decl_event, decl_module, decl_storage,9 dispatch::DispatchResult,10 ensure, parameter_types,11 traits::{12 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,13 Randomness, WithdrawReason,14 },15 weights::{16 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},17 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,18 WeightToFeePolynomial,19 },20 IsSubType, StorageValue,21};2223use frame_system::{self as system, ensure_signed};24use sp_runtime::sp_std::prelude::Vec;25use sp_runtime::{26 traits::{27 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SaturatedConversion, Saturating,28 SignedExtension, Zero,29 },30 transaction_validity::{31 InvalidTransaction, TransactionPriority, TransactionValidity, TransactionValidityError,32 ValidTransaction,33 },34 FixedPointOperand, FixedU128,35};3637#[cfg(test)]38mod mock;3940#[cfg(test)]41mod tests;4243// Structs44// #region4546#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]47#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]48pub enum CollectionMode {49 Invalid,50 // custom data size51 NFT(u32),52 // decimal points53 Fungible(u32),54 // custom data size and decimal points55 ReFungible(u32, u32),56}5758impl Into<u8> for CollectionMode {59 fn into(self) -> u8 {60 match self {61 CollectionMode::Invalid => 0,62 CollectionMode::NFT(_) => 1,63 CollectionMode::Fungible(_) => 2,64 CollectionMode::ReFungible(_, _) => 3,65 }66 }67}6869#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]70#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]71pub enum AccessMode {72 Normal,73 WhiteList,74}75impl Default for AccessMode {76 fn default() -> Self {77 Self::Normal78 }79}8081impl Default for CollectionMode {82 fn default() -> Self {83 Self::Invalid84 }85}8687#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]88#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]89pub struct Ownership<AccountId> {90 pub owner: AccountId,91 pub fraction: u128,92}9394#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]95#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]96pub struct CollectionType<AccountId> {97 pub owner: AccountId,98 pub mode: CollectionMode,99 pub access: AccessMode,100 pub decimal_points: u32,101 pub name: Vec<u16>, // 64 include null escape char102 pub description: Vec<u16>, // 256 include null escape char103 pub token_prefix: Vec<u8>, // 16 include null escape char104 pub custom_data_size: u32,105 pub mint_mode: bool,106 pub offchain_schema: Vec<u8>,107 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender108 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship109}110111#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]112#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]113pub struct CollectionAdminsType<AccountId> {114 pub admin: AccountId,115 pub collection_id: u64,116}117118#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]119#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]120pub struct NftItemType<AccountId> {121 pub collection: u64,122 pub owner: AccountId,123 pub data: Vec<u8>,124}125126#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]127#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]128pub struct FungibleItemType<AccountId> {129 pub collection: u64,130 pub owner: AccountId,131 pub value: u128,132}133134#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]135#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]136pub struct ReFungibleItemType<AccountId> {137 pub collection: u64,138 pub owner: Vec<Ownership<AccountId>>,139 pub data: Vec<u8>,140}141142#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]143#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]144pub struct ApprovePermissions<AccountId> {145 pub approved: AccountId,146 pub amount: u64,147}148149#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]150#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]151pub struct VestingItem<AccountId, Moment> {152 pub sender: AccountId,153 pub recipient: AccountId,154 pub collection_id: u64,155 pub item_id: u64,156 pub amount: u64,157 pub vesting_date: Moment,158}159160pub trait Trait: system::Trait {161 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;162}163164// #endregion165166decl_storage! {167 trait Store for Module<T: Trait> as Nft {168169 // Private members170 NextCollectionID: u64;171 CreatedCollectionCount: u64;172 ChainVersion: u64;173 ItemListIndex: map hasher(blake2_128_concat) u64 => u64;174175 pub Collection get(fn collection) config(): map hasher(identity) u64 => CollectionType<T::AccountId>;176 pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec<T::AccountId>;177 pub WhiteList get(fn white_list): map hasher(identity) u64 => Vec<T::AccountId>;178179 /// Balance owner per collection map180 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => u64;181182 /// second parameter: item id + owner account id183 pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) (u64, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;184185 /// Item collections186 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType<T::AccountId>;187 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType<T::AccountId>;188 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType<T::AccountId>;189190 /// Index list191 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec<u64>;192193 // Sponsorship194 pub ContractSponsor get(fn contract_sponsor): map hasher(identity) T::AccountId => T::AccountId;195 pub UnconfirmedContractSponsor get(fn unconfirmed_contract_sponsor): map hasher(identity) T::AccountId => T::AccountId;196 }197 add_extra_genesis {198 build(|config: &GenesisConfig<T>| {199 // Modification of storage200 for (_num, _c) in &config.collection {201 <Module<T>>::init_collection(_c);202 }203204 for (_num, _q, _i) in &config.nft_item_id {205 <Module<T>>::init_nft_token(_i);206 }207208 for (_num, _q, _i) in &config.fungible_item_id {209 <Module<T>>::init_fungible_token(_i);210 }211212 for (_num, _q, _i) in &config.refungible_item_id {213 <Module<T>>::init_refungible_token(_i);214 }215 })216 }217}218219decl_event!(220 pub enum Event<T>221 where222 AccountId = <T as system::Trait>::AccountId,223 {224 Created(u64, u8, AccountId),225 ItemCreated(u64, u64),226 ItemDestroyed(u64, u64),227 }228);229230decl_module! {231 pub struct Module<T: Trait> for enum Call where origin: T::Origin {232233 fn deposit_event() = default;234235 fn on_initialize(now: T::BlockNumber) -> Weight {236237 if ChainVersion::get() < 2238 {239 let value = NextCollectionID::get();240 CreatedCollectionCount::put(value);241 ChainVersion::put(2);242 }243244 0245 }246247 // Create collection of NFT with given parameters248 //249 // @param customDataSz size of custom data in each collection item250 // returns collection ID251 #[weight = 0]252 pub fn create_collection(origin,253 collection_name: Vec<u16>,254 collection_description: Vec<u16>,255 token_prefix: Vec<u8>,256 mode: CollectionMode) -> DispatchResult {257258 // Anyone can create a collection259 let who = ensure_signed(origin)?;260 let custom_data_size = match mode {261 CollectionMode::NFT(size) => size,262 CollectionMode::ReFungible(size, _) => size,263 _ => 0264 };265266 let decimal_points = match mode {267 CollectionMode::Fungible(points) => points,268 CollectionMode::ReFungible(_, points) => points,269 _ => 0270 };271272 // check params273 ensure!(decimal_points <= 4, "decimal_points parameter must be lower than 4");274275 let mut name = collection_name.to_vec();276 name.push(0);277 ensure!(name.len() <= 64, "Collection name can not be longer than 63 char");278279 let mut description = collection_description.to_vec();280 description.push(0);281 ensure!(name.len() <= 256, "Collection description can not be longer than 255 char");282283 let mut prefix = token_prefix.to_vec();284 prefix.push(0);285 ensure!(prefix.len() <= 16, "Token prefix can not be longer than 15 char");286287 // Generate next collection ID288 let next_id = CreatedCollectionCount::get()289 .checked_add(1)290 .expect("collection id error");291292 CreatedCollectionCount::put(next_id);293294 // Create new collection295 let new_collection = CollectionType {296 owner: who.clone(),297 name: name,298 mode: mode.clone(),299 mint_mode: false,300 access: AccessMode::Normal,301 description: description,302 decimal_points: decimal_points,303 token_prefix: prefix,304 offchain_schema: Vec::new(),305 custom_data_size: custom_data_size,306 sponsor: T::AccountId::default(),307 unconfirmed_sponsor: T::AccountId::default(),308 };309310 // Add new collection to map311 <Collection<T>>::insert(next_id, new_collection);312313 // call event314 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));315316 Ok(())317 }318319 #[weight = 0]320 pub fn destroy_collection(origin, collection_id: u64) -> DispatchResult {321322 let sender = ensure_signed(origin)?;323 Self::check_owner_permissions(collection_id, sender)?;324325 // TODO Items remove326 <AddressTokens<T>>::remove_prefix(collection_id);327 <ApprovedList<T>>::remove_prefix(collection_id);328 <Balance<T>>::remove_prefix(collection_id);329 <ItemListIndex>::remove(collection_id);330 <AdminList<T>>::remove(collection_id);331 <Collection<T>>::remove(collection_id);332 <WhiteList<T>>::remove(collection_id);333334 Ok(())335 }336337 #[weight = 0]338 pub fn add_to_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{339340 let sender = ensure_signed(origin)?;341 Self::check_owner_or_admin_permissions(collection_id, sender)?;342343 let mut white_list_collection: Vec<T::AccountId>;344 if <WhiteList<T>>::contains_key(collection_id) {345 white_list_collection = <WhiteList<T>>::get(collection_id);346 if !white_list_collection.contains(&address.clone())347 {348 white_list_collection.push(address.clone());349 }350 }351 else {352 white_list_collection = Vec::new();353 white_list_collection.push(address.clone());354 }355356 <WhiteList<T>>::insert(collection_id, white_list_collection);357 Ok(())358 }359360 #[weight = 0]361 pub fn remove_from_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{362363 let sender = ensure_signed(origin)?;364 Self::check_owner_or_admin_permissions(collection_id, sender)?;365366 if <WhiteList<T>>::contains_key(collection_id) {367 let mut white_list_collection = <WhiteList<T>>::get(collection_id);368 if white_list_collection.contains(&address.clone())369 {370 white_list_collection.retain(|i| *i != address.clone());371 <WhiteList<T>>::insert(collection_id, white_list_collection);372 }373 }374375 Ok(())376 }377378 #[weight = 0]379 pub fn set_public_access_mode(origin, collection_id: u64, mode: AccessMode) -> DispatchResult380 {381 let sender = ensure_signed(origin)?;382383 Self::check_owner_permissions(collection_id, sender)?;384 let mut target_collection = <Collection<T>>::get(collection_id);385 target_collection.access = mode;386 <Collection<T>>::insert(collection_id, target_collection);387388 Ok(())389 }390391 #[weight = 0]392 pub fn set_mint_permission(origin, collection_id: u64, mint_permission: bool) -> DispatchResult393 {394 let sender = ensure_signed(origin)?;395396 Self::check_owner_permissions(collection_id, sender)?;397 let mut target_collection = <Collection<T>>::get(collection_id);398 target_collection.mint_mode = mint_permission;399 <Collection<T>>::insert(collection_id, target_collection);400401 Ok(())402 }403404 #[weight = 0]405 pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult {406407 let sender = ensure_signed(origin)?;408 Self::check_owner_permissions(collection_id, sender)?;409 let mut target_collection = <Collection<T>>::get(collection_id);410 target_collection.owner = new_owner;411 <Collection<T>>::insert(collection_id, target_collection);412413 Ok(())414 }415416 #[weight = 0]417 pub fn add_collection_admin(origin, collection_id: u64, new_admin_id: T::AccountId) -> DispatchResult {418419 let sender = ensure_signed(origin)?;420 Self::check_owner_or_admin_permissions(collection_id, sender)?;421 let mut admin_arr: Vec<T::AccountId> = Vec::new();422423 if <AdminList<T>>::contains_key(collection_id)424 {425 admin_arr = <AdminList<T>>::get(collection_id);426 ensure!(!admin_arr.contains(&new_admin_id), "Account already has admin role");427 }428429 admin_arr.push(new_admin_id);430 <AdminList<T>>::insert(collection_id, admin_arr);431432 Ok(())433 }434435 #[weight = 0]436 pub fn remove_collection_admin(origin, collection_id: u64, account_id: T::AccountId) -> DispatchResult {437438 let sender = ensure_signed(origin)?;439 Self::check_owner_or_admin_permissions(collection_id, sender)?;440441 if <AdminList<T>>::contains_key(collection_id)442 {443 let mut admin_arr = <AdminList<T>>::get(collection_id);444 admin_arr.retain(|i| *i != account_id);445 <AdminList<T>>::insert(collection_id, admin_arr);446 }447448 Ok(())449 }450451 #[weight = 0]452 pub fn set_collection_sponsor(origin, collection_id: u64, new_sponsor: T::AccountId) -> DispatchResult {453454 let sender = ensure_signed(origin)?;455 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");456457 let mut target_collection = <Collection<T>>::get(collection_id);458 ensure!(sender == target_collection.owner, "You do not own this collection");459460 target_collection.unconfirmed_sponsor = new_sponsor;461 <Collection<T>>::insert(collection_id, target_collection);462463 Ok(())464 }465466 #[weight = 0]467 pub fn confirm_sponsorship(origin, collection_id: u64) -> DispatchResult {468469 let sender = ensure_signed(origin)?;470 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");471472 let mut target_collection = <Collection<T>>::get(collection_id);473 ensure!(sender == target_collection.unconfirmed_sponsor, "This address is not set as sponsor, use setCollectionSponsor first");474475 target_collection.sponsor = target_collection.unconfirmed_sponsor;476 target_collection.unconfirmed_sponsor = T::AccountId::default();477 <Collection<T>>::insert(collection_id, target_collection);478479 Ok(())480 }481482 #[weight = 0]483 pub fn remove_collection_sponsor(origin, collection_id: u64) -> DispatchResult {484485 let sender = ensure_signed(origin)?;486 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");487488 let mut target_collection = <Collection<T>>::get(collection_id);489 ensure!(sender == target_collection.owner, "You do not own this collection");490491 target_collection.sponsor = T::AccountId::default();492 <Collection<T>>::insert(collection_id, target_collection);493494 Ok(())495 }496497 #[weight = 0]498 pub fn create_item(origin, collection_id: u64, properties: Vec<u8>, owner: T::AccountId) -> DispatchResult {499500 let sender = ensure_signed(origin)?;501 Self::collection_exists(collection_id)?;502 let target_collection = <Collection<T>>::get(collection_id);503504 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {505 ensure!(target_collection.mint_mode == true, "Collection is not in mint mode");506 Self::check_white_list(collection_id, owner.clone())?;507 }508509 match target_collection.mode510 {511 CollectionMode::NFT(_) => {512513 // check size514 ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large");515516 // Create nft item517 let item = NftItemType {518 collection: collection_id,519 owner: owner,520 data: properties,521 };522523 Self::add_nft_item(item)?;524525 },526 CollectionMode::Fungible(_) => {527528 // check size529 ensure!(properties.len() as u32 == 0, "Size of item must be 0 with fungible type");530531 let item = FungibleItemType {532 collection: collection_id,533 owner: owner,534 value: (10 as u128).pow(target_collection.decimal_points)535 };536537 Self::add_fungible_item(item)?;538 },539 CollectionMode::ReFungible(_, _) => {540541 // check size542 ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large");543544 let mut owner_list = Vec::new();545 let value = (10 as u128).pow(target_collection.decimal_points);546 owner_list.push(Ownership {owner: owner, fraction: value});547548 let item = ReFungibleItemType {549 collection: collection_id,550 owner: owner_list,551 data: properties552 };553554 Self::add_refungible_item(item)?;555 },556 _ => ()557 };558559 // call event560 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));561562 Ok(())563 }564565 #[weight = 0]566 pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult {567568 let sender = ensure_signed(origin)?;569 Self::collection_exists(collection_id)?;570571 // Transfer permissions check572 let target_collection = <Collection<T>>::get(collection_id);573 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) || 574 Self::is_owner_or_admin_permissions(collection_id, sender.clone()), 575 "Only item owner, collection owner and admins can modify item");576577 if target_collection.access == AccessMode::WhiteList {578 Self::check_white_list(collection_id, sender.clone())?;579 }580581 match target_collection.mode582 {583 CollectionMode::NFT(_) => Self::burn_nft_item(collection_id, item_id)?,584 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,585 CollectionMode::ReFungible(_, _) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,586 _ => ()587 };588589 // call event590 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));591592 Ok(())593 }594595 #[weight = 0]596 pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult {597598 let sender = ensure_signed(origin)?;599600 // Transfer permissions check601 let target_collection = <Collection<T>>::get(collection_id);602 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) || 603 Self::is_owner_or_admin_permissions(collection_id, sender.clone()), 604 "Only item owner, collection owner and admins can modify item");605606 if target_collection.access == AccessMode::WhiteList {607 Self::check_white_list(collection_id, sender.clone())?;608 Self::check_white_list(collection_id, recipient.clone())?;609 }610611 match target_collection.mode612 {613 CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,614 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,615 CollectionMode::ReFungible(_, _) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,616 _ => ()617 };618619 Ok(())620 }621622 #[weight = 0]623 pub fn approve(origin, approved: T::AccountId, collection_id: u64, item_id: u64) -> DispatchResult {624625 let sender = ensure_signed(origin)?;626627 // Transfer permissions check628 let target_collection = <Collection<T>>::get(collection_id);629 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) || 630 Self::is_owner_or_admin_permissions(collection_id, sender.clone()), 631 "Only item owner, collection owner and admins can approve");632633 if target_collection.access == AccessMode::WhiteList {634 Self::check_white_list(collection_id, sender.clone())?;635 Self::check_white_list(collection_id, approved.clone())?;636 }637638 // amount param stub639 let amount = 100000000;640641 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));642 if list_exists {643644 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));645 let item_contains = list.iter().any(|i| i.approved == approved);646647 if !item_contains {648 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });649 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);650 }651 } else {652653 let mut list = Vec::new();654 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });655 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);656 }657658 Ok(())659 }660661 #[weight = 0]662 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64 ) -> DispatchResult {663664 let sender = ensure_signed(origin)?;665 let mut appoved_transfer = false;666667 // Check approve668 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {669 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));670 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());671 appoved_transfer = opt_item.is_some();672 ensure!(opt_item.unwrap().amount >= value, "Requested value more than approved");673 }674675 // Transfer permissions check676 let target_collection = <Collection<T>>::get(collection_id);677 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()), 678 "Only item owner, collection owner and admins can modify items");679680 if target_collection.access == AccessMode::WhiteList {681 Self::check_white_list(collection_id, sender.clone())?;682 Self::check_white_list(collection_id, recipient.clone())?;683 }684685 // remove approve686 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))687 .into_iter().filter(|i| i.approved != sender.clone()).collect();688 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);689690691 match target_collection.mode692 {693 CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, from, recipient)?,694 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,695 CollectionMode::ReFungible(_, _) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,696 _ => ()697 };698699 Ok(())700 }701702 #[weight = 0]703 pub fn safe_transfer_from(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult {704705 // let no_perm_mes = "You do not have permissions to modify this collection";706 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);707 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));708 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);709710 // // on_nft_received call711712 // Self::transfer(origin, collection_id, item_id, new_owner)?;713714 Ok(())715 }716717 #[weight = 0]718 pub fn set_offchain_schema(719 origin,720 collection_id: u64,721 schema: Vec<u8>722 ) -> DispatchResult {723 let sender = ensure_signed(origin)?;724 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;725726 let mut target_collection = <Collection<T>>::get(collection_id);727 target_collection.offchain_schema = schema;728 <Collection<T>>::insert(collection_id, target_collection);729730 Ok(())731 }732 }733}734735impl<T: Trait> Module<T> {736 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {737 let current_index = <ItemListIndex>::get(item.collection)738 .checked_add(1)739 .expect("Item list index id error");740 let itemcopy = item.clone();741 let owner = item.owner.clone();742 let value = item.value as u64;743744 Self::add_token_index(item.collection, current_index, owner.clone())?;745746 <ItemListIndex>::insert(item.collection, current_index);747 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);748749 // Update balance750 let new_balance = <Balance<T>>::get(item.collection, owner.clone())751 .checked_add(value)752 .unwrap();753 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);754755 Ok(())756 }757758 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {759 let current_index = <ItemListIndex>::get(item.collection)760 .checked_add(1)761 .expect("Item list index id error");762 let itemcopy = item.clone();763764 let value = item.owner.first().unwrap().fraction as u64;765 let owner = item.owner.first().unwrap().owner.clone();766767 Self::add_token_index(item.collection, current_index, owner.clone())?;768769 <ItemListIndex>::insert(item.collection, current_index);770 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);771772 // Update balance773 let new_balance = <Balance<T>>::get(item.collection, owner.clone())774 .checked_add(value)775 .unwrap();776 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);777778 Ok(())779 }780781 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {782 let current_index = <ItemListIndex>::get(item.collection)783 .checked_add(1)784 .expect("Item list index id error");785786 let item_owner = item.owner.clone();787 let collection_id = item.collection.clone();788 Self::add_token_index(collection_id, current_index, item.owner.clone())?;789790 <ItemListIndex>::insert(collection_id, current_index);791 <NftItemList<T>>::insert(collection_id, current_index, item);792793 // Update balance794 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())795 .checked_add(1)796 .unwrap();797 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);798799 Ok(())800 }801802 fn burn_refungible_item(collection_id: u64, item_id: u64, owner: T::AccountId) -> DispatchResult {803 ensure!(804 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),805 "Item does not exists"806 );807 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);808 let item = collection809 .owner810 .iter()811 .filter(|&i| i.owner == owner)812 .next()813 .unwrap();814 Self::remove_token_index(collection_id, item_id, owner.clone())?;815816 // remove approve list817 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));818819 // update balance820 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())821 .checked_sub(item.fraction as u64)822 .unwrap();823 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);824825 <ReFungibleItemList<T>>::remove(collection_id, item_id);826827 Ok(())828 }829830 fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult {831 ensure!(832 <NftItemList<T>>::contains_key(collection_id, item_id),833 "Item does not exists"834 );835 let item = <NftItemList<T>>::get(collection_id, item_id);836 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;837838 // remove approve list839 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));840841 // update balance842 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())843 .checked_sub(1)844 .unwrap();845 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);846 <NftItemList<T>>::remove(collection_id, item_id);847848 Ok(())849 }850851 fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult {852 ensure!(853 <FungibleItemList<T>>::contains_key(collection_id, item_id),854 "Item does not exists"855 );856 let item = <FungibleItemList<T>>::get(collection_id, item_id);857 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;858859 // remove approve list860 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));861862 // update balance863 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())864 .checked_sub(item.value as u64)865 .unwrap();866 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);867868 <FungibleItemList<T>>::remove(collection_id, item_id);869870 Ok(())871 }872873 fn collection_exists(collection_id: u64) -> DispatchResult {874 ensure!(875 <Collection<T>>::contains_key(collection_id),876 "This collection does not exist"877 );878 Ok(())879 }880881 fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {882 Self::collection_exists(collection_id)?;883884 let target_collection = <Collection<T>>::get(collection_id);885 ensure!(886 subject == target_collection.owner,887 "You do not own this collection"888 );889890 Ok(())891 }892893 fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool {894895 let target_collection = <Collection<T>>::get(collection_id);896 let mut result: bool = subject == target_collection.owner;897 let exists = <AdminList<T>>::contains_key(collection_id);898899 if !result & exists {900 if <AdminList<T>>::get(collection_id).contains(&subject) {901 result = true902 }903 }904905 result906 }907908 fn check_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {909 910 Self::collection_exists(collection_id)?;911 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());912913 ensure!(result, "You do not have permissions to modify this collection");914 Ok(())915 }916917 fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool {918 let target_collection = <Collection<T>>::get(collection_id);919920 match target_collection.mode {921 CollectionMode::NFT(_) => {922 <NftItemList<T>>::get(collection_id, item_id).owner == subject923 }924 CollectionMode::Fungible(_) => {925 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject926 }927 CollectionMode::ReFungible(_, _) => {928 <ReFungibleItemList<T>>::get(collection_id, item_id)929 .owner930 .iter()931 .any(|i| i.owner == subject)932 }933 CollectionMode::Invalid => false,934 }935 }936937 fn check_white_list(collection_id: u64, address: T::AccountId) -> DispatchResult {938939 let mes = "Address is not in white list";940 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);941 let wl = <WhiteList<T>>::get(collection_id);942 ensure!(wl.contains(&address.clone()), mes);943944 Ok(())945 }946947 fn transfer_fungible(948 collection_id: u64,949 item_id: u64,950 value: u64,951 owner: T::AccountId,952 new_owner: T::AccountId,953 ) -> DispatchResult {954955 ensure!(956 <FungibleItemList<T>>::contains_key(collection_id, item_id),957 "Item not exists"958 );959960 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);961 let amount = full_item.value;962963 ensure!(amount >= value.into(), "Item balance not enouth");964965 // update balance966 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())967 .checked_sub(value)968 .unwrap();969 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);970971 let mut new_owner_account_id = 0;972 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());973 if new_owner_items.len() > 0 {974 new_owner_account_id = new_owner_items[0];975 }976977 let val64 = value.into();978979 // transfer980 if amount == val64 && new_owner_account_id == 0 {981 // change owner982 // new owner do not have account983 let mut new_full_item = full_item.clone();984 new_full_item.owner = new_owner.clone();985 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);986987 // update balance988 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())989 .checked_add(value)990 .unwrap();991 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);992993 // update index collection994 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;995 } else {996 let mut new_full_item = full_item.clone();997 new_full_item.value -= val64;998999 // separate amount1000 if new_owner_account_id > 0 {1001 // new owner has account1002 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1003 item.value += val64;10041005 // update balance1006 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1007 .checked_add(value)1008 .unwrap();1009 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);10101011 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1012 } else {1013 // new owner do not have account1014 let item = FungibleItemType {1015 collection: collection_id,1016 owner: new_owner.clone(),1017 value: val64,1018 };10191020 Self::add_fungible_item(item)?;1021 }10221023 if amount == val64 {1024 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;10251026 // remove approve list1027 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1028 <FungibleItemList<T>>::remove(collection_id, item_id);1029 }10301031 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1032 }10331034 Ok(())1035 }10361037 fn transfer_refungible(1038 collection_id: u64,1039 item_id: u64,1040 value: u64,1041 owner: T::AccountId,1042 new_owner: T::AccountId,1043 ) -> DispatchResult {10441045 ensure!(1046 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1047 "Item not exists"1048 );10491050 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1051 let item = full_item1052 .owner1053 .iter()1054 .filter(|i| i.owner == owner)1055 .next()1056 .unwrap();1057 let amount = item.fraction;10581059 ensure!(amount >= value.into(), "Item balance not enouth");10601061 // update balance1062 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1063 .checked_sub(value)1064 .unwrap();1065 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);10661067 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1068 .checked_add(value)1069 .unwrap();1070 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);10711072 let old_owner = item.owner.clone();1073 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1074 let val64 = value.into();10751076 // transfer1077 if amount == val64 && !new_owner_has_account {1078 // change owner1079 // new owner do not have account1080 let mut new_full_item = full_item.clone();1081 new_full_item1082 .owner1083 .iter_mut()1084 .find(|i| i.owner == owner)1085 .unwrap()1086 .owner = new_owner.clone();1087 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);10881089 // update index collection1090 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1091 } else {1092 let mut new_full_item = full_item.clone();1093 new_full_item1094 .owner1095 .iter_mut()1096 .find(|i| i.owner == owner)1097 .unwrap()1098 .fraction -= val64;10991100 // separate amount1101 if new_owner_has_account {1102 // new owner has account1103 new_full_item1104 .owner1105 .iter_mut()1106 .find(|i| i.owner == new_owner)1107 .unwrap()1108 .fraction += val64;1109 } else {1110 // new owner do not have account1111 new_full_item.owner.push(Ownership {1112 owner: new_owner.clone(),1113 fraction: val64,1114 });1115 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1116 }11171118 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1119 }11201121 Ok(())1122 }11231124 fn transfer_nft(1125 collection_id: u64,1126 item_id: u64,1127 sender: T::AccountId,1128 new_owner: T::AccountId,1129 ) -> DispatchResult {1130 1131 ensure!(1132 <NftItemList<T>>::contains_key(collection_id, item_id),1133 "Item not exists"1134 );11351136 let mut item = <NftItemList<T>>::get(collection_id, item_id);11371138 ensure!(1139 sender == item.owner,1140 "sender parameter and item owner must be equal"1141 );11421143 // update balance1144 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1145 .checked_sub(1)1146 .unwrap();1147 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);11481149 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1150 .checked_add(1)1151 .unwrap();1152 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);11531154 // change owner1155 let old_owner = item.owner.clone();1156 item.owner = new_owner.clone();1157 <NftItemList<T>>::insert(collection_id, item_id, item);11581159 // update index collection1160 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;11611162 // reset approved list1163 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1164 Ok(())1165 }11661167 fn init_collection(item: &CollectionType<T::AccountId>){11681169 // check params1170 assert!(item.decimal_points <= 4, "decimal_points parameter must be lower than 4");1171 assert!(item.name.len() <= 64, "Collection name can not be longer than 63 char");1172 assert!(item.name.len() <= 256, "Collection description can not be longer than 255 char");1173 assert!(item.token_prefix.len() <= 16, "Token prefix can not be longer than 15 char");1174 1175 // Generate next collection ID1176 let next_id = CreatedCollectionCount::get()1177 .checked_add(1)1178 .expect("collection id error");1179 1180 CreatedCollectionCount::put(next_id); 1181 }11821183 fn init_nft_token(item: &NftItemType<T::AccountId>){11841185 let current_index = <ItemListIndex>::get(item.collection)1186 .checked_add(1)1187 .expect("Item list index id error");11881189 let item_owner = item.owner.clone();1190 let collection_id = item.collection.clone();1191 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();11921193 <ItemListIndex>::insert(collection_id, current_index);11941195 // Update balance1196 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1197 .checked_add(1)1198 .unwrap();1199 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);1200 }12011202 fn init_fungible_token(item: &FungibleItemType<T::AccountId>){12031204 let current_index = <ItemListIndex>::get(item.collection)1205 .checked_add(1)1206 .expect("Item list index id error");1207 let owner = item.owner.clone();1208 let value = item.value as u64;12091210 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();12111212 <ItemListIndex>::insert(item.collection, current_index);12131214 // Update balance1215 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1216 .checked_add(value)1217 .unwrap();1218 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1219 }12201221 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>){12221223 let current_index = <ItemListIndex>::get(item.collection)1224 .checked_add(1)1225 .expect("Item list index id error");12261227 let value = item.owner.first().unwrap().fraction as u64;1228 let owner = item.owner.first().unwrap().owner.clone();12291230 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();12311232 <ItemListIndex>::insert(item.collection, current_index);12331234 // Update balance1235 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1236 .checked_add(value)1237 .unwrap();1238 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1239 }12401241 fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {1242 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1243 if list_exists {1244 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1245 let item_contains = list.contains(&item_index.clone());12461247 if !item_contains {1248 list.push(item_index.clone());1249 }12501251 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);1252 } else {1253 let mut itm = Vec::new();1254 itm.push(item_index.clone());1255 <AddressTokens<T>>::insert(collection_id, owner, itm);1256 }12571258 Ok(())1259 }12601261 fn remove_token_index(1262 collection_id: u64,1263 item_index: u64,1264 owner: T::AccountId,1265 ) -> DispatchResult {1266 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1267 if list_exists {1268 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1269 let item_contains = list.contains(&item_index.clone());12701271 if item_contains {1272 list.retain(|&item| item != item_index);1273 <AddressTokens<T>>::insert(collection_id, owner, list);1274 }1275 }12761277 Ok(())1278 }12791280 fn move_token_index(1281 collection_id: u64,1282 item_index: u64,1283 old_owner: T::AccountId,1284 new_owner: T::AccountId,1285 ) -> DispatchResult {1286 Self::remove_token_index(collection_id, item_index, old_owner)?;1287 Self::add_token_index(collection_id, item_index, new_owner)?;12881289 Ok(())1290 }1291}12921293////////////////////////////////////////////////////////////////////////////////////////////////////1294// Economic models1295// #region12961297/// Fee multiplier.1298pub type Multiplier = FixedU128;12991300type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<1301 <T as system::Trait>::AccountId,1302>>::Balance;1303type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<1304 <T as system::Trait>::AccountId,1305>>::NegativeImbalance;13061307/// Require the transactor pay for themselves and maybe include a tip to gain additional priority1308/// in the queue.1309#[derive(Encode, Decode, Clone, Eq, PartialEq)]1310pub struct ChargeTransactionPayment<T: transaction_payment::Trait + Send + Sync>(1311 #[codec(compact)] BalanceOf<T>,1312);13131314impl<T: Trait + transaction_payment::Trait + Send + Sync> sp_std::fmt::Debug1315 for ChargeTransactionPayment<T>1316{1317 #[cfg(feature = "std")]1318 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {1319 write!(f, "ChargeTransactionPayment<{:?}>", self.0)1320 }1321 #[cfg(not(feature = "std"))]1322 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {1323 Ok(())1324 }1325}13261327impl<T: Trait + transaction_payment::Trait + Send + Sync> ChargeTransactionPayment<T>1328where1329 T::Call:1330 Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Module<T>, T>,1331 BalanceOf<T>: Send + Sync + FixedPointOperand,1332{1333 /// utility constructor. Used only in client/factory code.1334 pub fn from(fee: BalanceOf<T>) -> Self {1335 Self(fee)1336 }13371338 pub fn traditional_fee(1339 len: usize,1340 info: &DispatchInfoOf<T::Call>,1341 tip: BalanceOf<T>,1342 ) -> BalanceOf<T>1343 where1344 T::Call: Dispatchable<Info = DispatchInfo>,1345 {1346 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)1347 }13481349 fn withdraw_fee(1350 &self,1351 who: &T::AccountId,1352 call: &T::Call,1353 info: &DispatchInfoOf<T::Call>,1354 len: usize,1355 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {1356 let tip = self.0;13571358 // Set fee based on call type. Creating collection costs 1 Unique.1359 // All other transactions have traditional fees so far1360 let fee = match call.is_sub_type() {1361 Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),1362 _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes1363 // _ => <BalanceOf<T>>::from(100)1364 };13651366 // Determine who is paying transaction fee based on ecnomic model1367 // Parse call to extract collection ID and access collection sponsor1368 let sponsor: T::AccountId = match call.is_sub_type() {1369 Some(Call::create_item(collection_id, _properties, _owner)) => {1370 <Collection<T>>::get(collection_id).sponsor1371 }1372 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {1373 <Collection<T>>::get(collection_id).sponsor1374 }13751376 _ => T::AccountId::default(),1377 };13781379 let mut who_pays_fee: T::AccountId = sponsor.clone();1380 if sponsor == T::AccountId::default() {1381 who_pays_fee = who.clone();1382 }13831384 // Only mess with balances if fee is not zero.1385 if fee.is_zero() {1386 return Ok((fee, None));1387 }13881389 match <T as transaction_payment::Trait>::Currency::withdraw(1390 &who_pays_fee,1391 fee,1392 if tip.is_zero() {1393 WithdrawReason::TransactionPayment.into()1394 } else {1395 WithdrawReason::TransactionPayment | WithdrawReason::Tip1396 },1397 ExistenceRequirement::KeepAlive,1398 ) {1399 Ok(imbalance) => Ok((fee, Some(imbalance))),1400 Err(_) => Err(InvalidTransaction::Payment.into()),1401 }1402 }1403}14041405impl<T: Trait + transaction_payment::Trait + Send + Sync> SignedExtension1406 for ChargeTransactionPayment<T>1407where1408 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,1409 T::Call:1410 Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Module<T>, T>,1411{1412 const IDENTIFIER: &'static str = "ChargeTransactionPayment";1413 type AccountId = T::AccountId;1414 type Call = T::Call;1415 type AdditionalSigned = ();1416 type Pre = (1417 BalanceOf<T>,1418 Self::AccountId,1419 Option<NegativeImbalanceOf<T>>,1420 BalanceOf<T>,1421 );1422 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {1423 Ok(())1424 }14251426 fn validate(1427 &self,1428 who: &Self::AccountId,1429 call: &Self::Call,1430 info: &DispatchInfoOf<Self::Call>,1431 len: usize,1432 ) -> TransactionValidity {1433 let (fee, _) = self.withdraw_fee(who, call, info, len)?;14341435 let mut r = ValidTransaction::default();1436 // NOTE: we probably want to maximize the _fee (of any type) per weight unit_ here, which1437 // will be a bit more than setting the priority to tip. For now, this is enough.1438 r.priority = fee.saturated_into::<TransactionPriority>();1439 Ok(r)1440 }14411442 fn pre_dispatch(1443 self,1444 who: &Self::AccountId,1445 call: &Self::Call,1446 info: &DispatchInfoOf<Self::Call>,1447 len: usize,1448 ) -> Result<Self::Pre, TransactionValidityError> {1449 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;1450 Ok((self.0, who.clone(), imbalance, fee))1451 }14521453 fn post_dispatch(1454 pre: Self::Pre,1455 info: &DispatchInfoOf<Self::Call>,1456 post_info: &PostDispatchInfoOf<Self::Call>,1457 len: usize,1458 _result: &DispatchResult,1459 ) -> Result<(), TransactionValidityError> {1460 let (tip, who, imbalance, fee) = pre;1461 if let Some(payed) = imbalance {1462 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(1463 len as u32, info, post_info, tip,1464 );1465 let refund = fee.saturating_sub(actual_fee);1466 let actual_payment =1467 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(1468 &who, refund,1469 ) {1470 Ok(refund_imbalance) => {1471 // The refund cannot be larger than the up front payed max weight.1472 // `PostDispatchInfo::calc_unspent` guards against such a case.1473 match payed.offset(refund_imbalance) {1474 Ok(actual_payment) => actual_payment,1475 Err(_) => return Err(InvalidTransaction::Payment.into()),1476 }1477 }1478 // We do not recreate the account using the refund. The up front payment1479 // is gone in that case.1480 Err(_) => payed,1481 };1482 let imbalances = actual_payment.split(tip);1483 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(1484 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),1485 );1486 }1487 Ok(())1488 }1489}1490// #endregion1#![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};2526use frame_system::{self as system, ensure_signed, ensure_root};27use sp_runtime::sp_std::prelude::Vec;28use sp_runtime::{29 traits::{30 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SaturatedConversion, Saturating,31 SignedExtension, Zero,32 },33 transaction_validity::{34 InvalidTransaction, TransactionPriority, TransactionValidity, TransactionValidityError,35 ValidTransaction,36 },37 FixedPointOperand, FixedU128,38};3940#[cfg(test)]41mod mock;4243#[cfg(test)]44mod tests;4546// Structs47// #region4849#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]50#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]51pub enum CollectionMode {52 Invalid,53 // custom data size54 NFT(u32),55 // decimal points56 Fungible(u32),57 // custom data size and decimal points58 ReFungible(u32, u32),59}6061impl Into<u8> for CollectionMode {62 fn into(self) -> u8 {63 match self {64 CollectionMode::Invalid => 0,65 CollectionMode::NFT(_) => 1,66 CollectionMode::Fungible(_) => 2,67 CollectionMode::ReFungible(_, _) => 3,68 }69 }70}7172#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]73#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]74pub enum AccessMode {75 Normal,76 WhiteList,77}78impl Default for AccessMode {79 fn default() -> Self {80 Self::Normal81 }82}8384impl Default for CollectionMode {85 fn default() -> Self {86 Self::Invalid87 }88}8990#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]91#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]92pub struct Ownership<AccountId> {93 pub owner: AccountId,94 pub fraction: u128,95}9697#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]98#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]99pub struct CollectionType<AccountId> {100 pub owner: AccountId,101 pub mode: CollectionMode,102 pub access: AccessMode,103 pub decimal_points: u32,104 pub name: Vec<u16>, // 64 include null escape char105 pub description: Vec<u16>, // 256 include null escape char106 pub token_prefix: Vec<u8>, // 16 include null escape char107 pub custom_data_size: u32,108 pub mint_mode: bool,109 pub offchain_schema: Vec<u8>,110 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender111 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship112}113114#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]115#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]116pub struct CollectionAdminsType<AccountId> {117 pub admin: AccountId,118 pub collection_id: u64,119}120121#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]122#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]123pub struct NftItemType<AccountId> {124 pub collection: u64,125 pub owner: AccountId,126 pub data: Vec<u8>,127}128129#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]130#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]131pub struct FungibleItemType<AccountId> {132 pub collection: u64,133 pub owner: AccountId,134 pub value: u128,135}136137#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]138#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]139pub struct ReFungibleItemType<AccountId> {140 pub collection: u64,141 pub owner: Vec<Ownership<AccountId>>,142 pub data: Vec<u8>,143}144145#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]146#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]147pub struct ApprovePermissions<AccountId> {148 pub approved: AccountId,149 pub amount: u64,150}151152#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]153#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]154pub struct VestingItem<AccountId, Moment> {155 pub sender: AccountId,156 pub recipient: AccountId,157 pub collection_id: u64,158 pub item_id: u64,159 pub amount: u64,160 pub vesting_date: Moment,161}162163#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]164#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]165pub struct BasketItem<AccountId, BlockNumber> {166 pub address: AccountId,167 pub start_block: BlockNumber,168}169170#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]171#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]172pub struct ChainLimits {173 pub collection_numbers_limit: u64,174 pub account_token_ownership_limit: u64,175 pub collections_admins_limit: u64,176 pub custom_data_limit: u32,177178 // Timeouts for item types in passed blocks179 pub nft_sponsor_transfer_timeout: u32,180 pub fungible_sponsor_transfer_timeout: u32,181 pub refungible_sponsor_transfer_timeout: u32,182}183184pub trait Trait: system::Trait {185 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;186}187188// #endregion189190decl_storage! {191 trait Store for Module<T: Trait> as Nft {192193 // Private members194 NextCollectionID: u64;195 CreatedCollectionCount: u64;196 ChainVersion: u64;197 ItemListIndex: map hasher(blake2_128_concat) u64 => u64;198199 // Chain limits struct200 pub ChainLimit get(fn chain_limit) config(): ChainLimits;201202 // Bound counters203 CollectionCount: u64;204 pub AccountItemCount get(fn account_item_count): map hasher(identity) T::AccountId => u64;205206 // Basic collections207 pub Collection get(fn collection) config(): map hasher(identity) u64 => CollectionType<T::AccountId>;208 pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec<T::AccountId>;209 pub WhiteList get(fn white_list): map hasher(identity) u64 => Vec<T::AccountId>;210211 /// Balance owner per collection map212 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => u64;213214 /// second parameter: item id + owner account id215 pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) (u64, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;216217 /// Item collections218 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType<T::AccountId>;219 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType<T::AccountId>;220 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType<T::AccountId>;221222 /// Index list223 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec<u64>;224225 /// Tokens transfer baskets226 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;227 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>>;228 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;229230 // Sponsorship231 pub ContractSponsor get(fn contract_sponsor): map hasher(identity) T::AccountId => T::AccountId;232 pub UnconfirmedContractSponsor get(fn unconfirmed_contract_sponsor): map hasher(identity) T::AccountId => T::AccountId;233 }234 add_extra_genesis {235 build(|config: &GenesisConfig<T>| {236 // Modification of storage237 for (_num, _c) in &config.collection {238 <Module<T>>::init_collection(_c);239 }240241 for (_num, _q, _i) in &config.nft_item_id {242 <Module<T>>::init_nft_token(_i);243 }244245 for (_num, _q, _i) in &config.fungible_item_id {246 <Module<T>>::init_fungible_token(_i);247 }248249 for (_num, _q, _i) in &config.refungible_item_id {250 <Module<T>>::init_refungible_token(_i);251 }252 })253 }254}255256decl_event!(257 pub enum Event<T>258 where259 AccountId = <T as system::Trait>::AccountId,260 {261 Created(u64, u8, AccountId),262 ItemCreated(u64, u64),263 ItemDestroyed(u64, u64),264 }265);266267decl_module! {268 pub struct Module<T: Trait> for enum Call where origin: T::Origin {269270 fn deposit_event() = default;271272 fn on_initialize(now: T::BlockNumber) -> Weight {273274 if ChainVersion::get() < 2275 {276 let value = NextCollectionID::get();277 CreatedCollectionCount::put(value);278 ChainVersion::put(2);279 }280281 0282 }283284 // Create collection of NFT with given parameters285 //286 // @param customDataSz size of custom data in each collection item287 // returns collection ID288 #[weight = 0]289 pub fn create_collection(origin,290 collection_name: Vec<u16>,291 collection_description: Vec<u16>,292 token_prefix: Vec<u8>,293 mode: CollectionMode) -> DispatchResult {294295 // Anyone can create a collection296 let who = ensure_signed(origin)?;297 let custom_data_size = match mode {298 CollectionMode::NFT(size) => {299300 // bound Custom data size301 ensure!(size < ChainLimit::get().custom_data_limit, "Custom data size bound exceeded");302 size303 },304 CollectionMode::ReFungible(size, _) => {305306 // bound Custom data size307 ensure!(size < ChainLimit::get().custom_data_limit, "Custom data size bound exceeded");308 size309 },310 _ => 0311 };312313 let decimal_points = match mode {314 CollectionMode::Fungible(points) => points,315 CollectionMode::ReFungible(_, points) => points,316 _ => 0317 };318319 // bound Total number of collections320 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, "Total collections bound exceeded");321322 // check params323 ensure!(decimal_points <= 4, "decimal_points parameter must be lower than 4");324325 let mut name = collection_name.to_vec();326 name.push(0);327 ensure!(name.len() <= 64, "Collection name can not be longer than 63 char");328329 let mut description = collection_description.to_vec();330 description.push(0);331 ensure!(name.len() <= 256, "Collection description can not be longer than 255 char");332333 let mut prefix = token_prefix.to_vec();334 prefix.push(0);335 ensure!(prefix.len() <= 16, "Token prefix can not be longer than 15 char");336337 // Generate next collection ID338 let next_id = CreatedCollectionCount::get()339 .checked_add(1)340 .expect("collection id error");341342 // bound counter343 let total = CollectionCount::get()344 .checked_add(1)345 .expect("collection counter error");346347 CreatedCollectionCount::put(next_id);348 CollectionCount::put(total);349350 // Create new collection351 let new_collection = CollectionType {352 owner: who.clone(),353 name: name,354 mode: mode.clone(),355 mint_mode: false,356 access: AccessMode::Normal,357 description: description,358 decimal_points: decimal_points,359 token_prefix: prefix,360 offchain_schema: Vec::new(),361 custom_data_size: custom_data_size,362 sponsor: T::AccountId::default(),363 unconfirmed_sponsor: T::AccountId::default(),364 };365366 // Add new collection to map367 <Collection<T>>::insert(next_id, new_collection);368369 // call event370 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));371372 Ok(())373 }374375 #[weight = 0]376 pub fn destroy_collection(origin, collection_id: u64) -> DispatchResult {377378 let sender = ensure_signed(origin)?;379 Self::check_owner_permissions(collection_id, sender)?;380381 <AddressTokens<T>>::remove_prefix(collection_id);382 <ApprovedList<T>>::remove_prefix(collection_id);383 <Balance<T>>::remove_prefix(collection_id);384 <ItemListIndex>::remove(collection_id);385 <AdminList<T>>::remove(collection_id);386 <Collection<T>>::remove(collection_id);387 <WhiteList<T>>::remove(collection_id);388389 <NftItemList<T>>::remove_prefix(collection_id);390 <FungibleItemList<T>>::remove_prefix(collection_id);391 <ReFungibleItemList<T>>::remove_prefix(collection_id);392393 <NftTransferBasket<T>>::remove_prefix(collection_id);394 <FungibleTransferBasket<T>>::remove_prefix(collection_id);395 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);396397 if CollectionCount::get() > 0398 {399 // bound couter400 let total = CollectionCount::get()401 .checked_sub(1)402 .expect("collection counter error");403404 CollectionCount::put(total);405 }406407 Ok(())408 }409410 #[weight = 0]411 pub fn add_to_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{412413 let sender = ensure_signed(origin)?;414 Self::check_owner_or_admin_permissions(collection_id, sender)?;415416 let mut white_list_collection: Vec<T::AccountId>;417 if <WhiteList<T>>::contains_key(collection_id) {418 white_list_collection = <WhiteList<T>>::get(collection_id);419 if !white_list_collection.contains(&address.clone())420 {421 white_list_collection.push(address.clone());422 }423 }424 else {425 white_list_collection = Vec::new();426 white_list_collection.push(address.clone());427 }428429 <WhiteList<T>>::insert(collection_id, white_list_collection);430 Ok(())431 }432433 #[weight = 0]434 pub fn remove_from_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{435436 let sender = ensure_signed(origin)?;437 Self::check_owner_or_admin_permissions(collection_id, sender)?;438439 if <WhiteList<T>>::contains_key(collection_id) {440 let mut white_list_collection = <WhiteList<T>>::get(collection_id);441 if white_list_collection.contains(&address.clone())442 {443 white_list_collection.retain(|i| *i != address.clone());444 <WhiteList<T>>::insert(collection_id, white_list_collection);445 }446 }447448 Ok(())449 }450451 #[weight = 0]452 pub fn set_public_access_mode(origin, collection_id: u64, mode: AccessMode) -> DispatchResult453 {454 let sender = ensure_signed(origin)?;455456 Self::check_owner_permissions(collection_id, sender)?;457 let mut target_collection = <Collection<T>>::get(collection_id);458 target_collection.access = mode;459 <Collection<T>>::insert(collection_id, target_collection);460461 Ok(())462 }463464 #[weight = 0]465 pub fn set_mint_permission(origin, collection_id: u64, mint_permission: bool) -> DispatchResult466 {467 let sender = ensure_signed(origin)?;468469 Self::check_owner_permissions(collection_id, sender)?;470 let mut target_collection = <Collection<T>>::get(collection_id);471 target_collection.mint_mode = mint_permission;472 <Collection<T>>::insert(collection_id, target_collection);473474 Ok(())475 }476477 #[weight = 0]478 pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult {479480 let sender = ensure_signed(origin)?;481 Self::check_owner_permissions(collection_id, sender)?;482 let mut target_collection = <Collection<T>>::get(collection_id);483 target_collection.owner = new_owner;484 <Collection<T>>::insert(collection_id, target_collection);485486 Ok(())487 }488489 #[weight = 0]490 pub fn add_collection_admin(origin, collection_id: u64, new_admin_id: T::AccountId) -> DispatchResult {491492 let sender = ensure_signed(origin)?;493 Self::check_owner_or_admin_permissions(collection_id, sender)?;494 let mut admin_arr: Vec<T::AccountId> = Vec::new();495496 if <AdminList<T>>::contains_key(collection_id)497 {498 admin_arr = <AdminList<T>>::get(collection_id);499 ensure!(!admin_arr.contains(&new_admin_id), "Account already has admin role");500 }501502 // Number of collection admins503 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, "Number of collection admins bound exceeded");504505 admin_arr.push(new_admin_id);506 <AdminList<T>>::insert(collection_id, admin_arr);507508 Ok(())509 }510511 #[weight = 0]512 pub fn remove_collection_admin(origin, collection_id: u64, account_id: T::AccountId) -> DispatchResult {513514 let sender = ensure_signed(origin)?;515 Self::check_owner_or_admin_permissions(collection_id, sender)?;516517 if <AdminList<T>>::contains_key(collection_id)518 {519 let mut admin_arr = <AdminList<T>>::get(collection_id);520 admin_arr.retain(|i| *i != account_id);521 <AdminList<T>>::insert(collection_id, admin_arr);522 }523524 Ok(())525 }526527 #[weight = 0]528 pub fn set_collection_sponsor(origin, collection_id: u64, new_sponsor: T::AccountId) -> DispatchResult {529530 let sender = ensure_signed(origin)?;531 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");532533 let mut target_collection = <Collection<T>>::get(collection_id);534 ensure!(sender == target_collection.owner, "You do not own this collection");535536 target_collection.unconfirmed_sponsor = new_sponsor;537 <Collection<T>>::insert(collection_id, target_collection);538539 Ok(())540 }541542 #[weight = 0]543 pub fn confirm_sponsorship(origin, collection_id: u64) -> DispatchResult {544545 let sender = ensure_signed(origin)?;546 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");547548 let mut target_collection = <Collection<T>>::get(collection_id);549 ensure!(sender == target_collection.unconfirmed_sponsor, "This address is not set as sponsor, use setCollectionSponsor first");550551 target_collection.sponsor = target_collection.unconfirmed_sponsor;552 target_collection.unconfirmed_sponsor = T::AccountId::default();553 <Collection<T>>::insert(collection_id, target_collection);554555 Ok(())556 }557558 #[weight = 0]559 pub fn remove_collection_sponsor(origin, collection_id: u64) -> DispatchResult {560561 let sender = ensure_signed(origin)?;562 ensure!(<Collection<T>>::contains_key(collection_id), "This collection does not exist");563564 let mut target_collection = <Collection<T>>::get(collection_id);565 ensure!(sender == target_collection.owner, "You do not own this collection");566567 target_collection.sponsor = T::AccountId::default();568 <Collection<T>>::insert(collection_id, target_collection);569570 Ok(())571 }572573 #[weight = 0]574 pub fn create_item(origin, collection_id: u64, properties: Vec<u8>, owner: T::AccountId) -> DispatchResult {575576 let sender = ensure_signed(origin)?;577 Self::collection_exists(collection_id)?;578 let target_collection = <Collection<T>>::get(collection_id);579580 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {581 ensure!(target_collection.mint_mode == true, "Collection is not in mint mode");582 Self::check_white_list(collection_id, owner.clone())?;583 }584585 match target_collection.mode586 {587 CollectionMode::NFT(_) => {588589 // check size590 ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large");591592 // Create nft item593 let item = NftItemType {594 collection: collection_id,595 owner: owner,596 data: properties,597 };598599 Self::add_nft_item(item)?;600601 },602 CollectionMode::Fungible(_) => {603604 // check size605 ensure!(properties.len() as u32 == 0, "Size of item must be 0 with fungible type");606607 let item = FungibleItemType {608 collection: collection_id,609 owner: owner,610 value: (10 as u128).pow(target_collection.decimal_points)611 };612613 Self::add_fungible_item(item)?;614 },615 CollectionMode::ReFungible(_, _) => {616617 // check size618 ensure!(target_collection.custom_data_size >= properties.len() as u32, "Size of item is too large");619620 let mut owner_list = Vec::new();621 let value = (10 as u128).pow(target_collection.decimal_points);622 owner_list.push(Ownership {owner: owner, fraction: value});623624 let item = ReFungibleItemType {625 collection: collection_id,626 owner: owner_list,627 data: properties628 };629630 Self::add_refungible_item(item)?;631 },632 _ => ()633 };634635 // call event636 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));637638 Ok(())639 }640641 #[weight = 0]642 pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult {643644 let sender = ensure_signed(origin)?;645 Self::collection_exists(collection_id)?;646647 // Transfer permissions check648 let target_collection = <Collection<T>>::get(collection_id);649 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||650 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),651 "Only item owner, collection owner and admins can modify item");652653 if target_collection.access == AccessMode::WhiteList {654 Self::check_white_list(collection_id, sender.clone())?;655 }656657 match target_collection.mode658 {659 CollectionMode::NFT(_) => Self::burn_nft_item(collection_id, item_id)?,660 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,661 CollectionMode::ReFungible(_, _) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,662 _ => ()663 };664665 // call event666 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));667668 Ok(())669 }670671 #[weight = 0]672 pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult {673674 let sender = ensure_signed(origin)?;675676 // Transfer permissions check677 let target_collection = <Collection<T>>::get(collection_id);678 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||679 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),680 "Only item owner, collection owner and admins can modify item");681682 if target_collection.access == AccessMode::WhiteList {683 Self::check_white_list(collection_id, sender.clone())?;684 Self::check_white_list(collection_id, recipient.clone())?;685 }686687 match target_collection.mode688 {689 CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,690 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,691 CollectionMode::ReFungible(_, _) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,692 _ => ()693 };694695 Ok(())696 }697698 #[weight = 0]699 pub fn approve(origin, approved: T::AccountId, collection_id: u64, item_id: u64) -> DispatchResult {700701 let sender = ensure_signed(origin)?;702703 // Transfer permissions check704 let target_collection = <Collection<T>>::get(collection_id);705 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||706 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),707 "Only item owner, collection owner and admins can approve");708709 if target_collection.access == AccessMode::WhiteList {710 Self::check_white_list(collection_id, sender.clone())?;711 Self::check_white_list(collection_id, approved.clone())?;712 }713714 // amount param stub715 let amount = 100000000;716717 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));718 if list_exists {719720 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));721 let item_contains = list.iter().any(|i| i.approved == approved);722723 if !item_contains {724 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });725 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);726 }727 } else {728729 let mut list = Vec::new();730 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });731 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);732 }733734 Ok(())735 }736737 #[weight = 0]738 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64 ) -> DispatchResult {739740 let sender = ensure_signed(origin)?;741 let mut appoved_transfer = false;742743 // Check approve744 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {745 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));746 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());747 appoved_transfer = opt_item.is_some();748 ensure!(opt_item.unwrap().amount >= value, "Requested value more than approved");749 }750751 // Transfer permissions check752 let target_collection = <Collection<T>>::get(collection_id);753 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),754 "Only item owner, collection owner and admins can modify items");755756 if target_collection.access == AccessMode::WhiteList {757 Self::check_white_list(collection_id, sender.clone())?;758 Self::check_white_list(collection_id, recipient.clone())?;759 }760761 // remove approve762 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))763 .into_iter().filter(|i| i.approved != sender.clone()).collect();764 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);765766767 match target_collection.mode768 {769 CollectionMode::NFT(_) => Self::transfer_nft(collection_id, item_id, from, recipient)?,770 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,771 CollectionMode::ReFungible(_, _) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,772 _ => ()773 };774775 Ok(())776 }777778 #[weight = 0]779 pub fn safe_transfer_from(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult {780781 // let no_perm_mes = "You do not have permissions to modify this collection";782 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);783 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));784 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);785786 // // on_nft_received call787788 // Self::transfer(origin, collection_id, item_id, new_owner)?;789790 Ok(())791 }792793 #[weight = 0]794 pub fn set_offchain_schema(795 origin,796 collection_id: u64,797 schema: Vec<u8>798 ) -> DispatchResult {799 let sender = ensure_signed(origin)?;800 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;801802 let mut target_collection = <Collection<T>>::get(collection_id);803 target_collection.offchain_schema = schema;804 <Collection<T>>::insert(collection_id, target_collection);805806 Ok(())807 }808809 // Sudo permissions function810 #[weight = 0]811 pub fn set_chain_limits(812 origin,813 limits: ChainLimits814 ) -> DispatchResult {815 ensure_root(origin)?;816 <ChainLimit>::put(limits);817 Ok(())818 } 819 }820}821822impl<T: Trait> Module<T> {823 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {824 let current_index = <ItemListIndex>::get(item.collection)825 .checked_add(1)826 .expect("Item list index id error");827 let itemcopy = item.clone();828 let owner = item.owner.clone();829 let value = item.value as u64;830831 Self::add_token_index(item.collection, current_index, owner.clone())?;832833 <ItemListIndex>::insert(item.collection, current_index);834 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);835836 // Update balance837 let new_balance = <Balance<T>>::get(item.collection, owner.clone())838 .checked_add(value)839 .unwrap();840 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);841842 Ok(())843 }844845 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {846 let current_index = <ItemListIndex>::get(item.collection)847 .checked_add(1)848 .expect("Item list index id error");849 let itemcopy = item.clone();850851 let value = item.owner.first().unwrap().fraction as u64;852 let owner = item.owner.first().unwrap().owner.clone();853854 Self::add_token_index(item.collection, current_index, owner.clone())?;855856 <ItemListIndex>::insert(item.collection, current_index);857 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);858859 // Update balance860 let new_balance = <Balance<T>>::get(item.collection, owner.clone())861 .checked_add(value)862 .unwrap();863 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);864865 Ok(())866 }867868 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {869 let current_index = <ItemListIndex>::get(item.collection)870 .checked_add(1)871 .expect("Item list index id error");872873 let item_owner = item.owner.clone();874 let collection_id = item.collection.clone();875 Self::add_token_index(collection_id, current_index, item.owner.clone())?;876877 <ItemListIndex>::insert(collection_id, current_index);878 <NftItemList<T>>::insert(collection_id, current_index, item);879880 // Update balance881 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())882 .checked_add(1)883 .unwrap();884 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);885886 Ok(())887 }888889 fn burn_refungible_item(890 collection_id: u64,891 item_id: u64,892 owner: T::AccountId,893 ) -> DispatchResult {894 ensure!(895 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),896 "Item does not exists"897 );898 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);899 let item = collection900 .owner901 .iter()902 .filter(|&i| i.owner == owner)903 .next()904 .unwrap();905 Self::remove_token_index(collection_id, item_id, owner.clone())?;906907 // remove approve list908 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));909910 // update balance911 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())912 .checked_sub(item.fraction as u64)913 .unwrap();914 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);915916 <ReFungibleItemList<T>>::remove(collection_id, item_id);917918 Ok(())919 }920921 fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult {922 ensure!(923 <NftItemList<T>>::contains_key(collection_id, item_id),924 "Item does not exists"925 );926 let item = <NftItemList<T>>::get(collection_id, item_id);927 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;928929 // remove approve list930 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));931932 // update balance933 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())934 .checked_sub(1)935 .unwrap();936 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);937 <NftItemList<T>>::remove(collection_id, item_id);938939 Ok(())940 }941942 fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult {943 ensure!(944 <FungibleItemList<T>>::contains_key(collection_id, item_id),945 "Item does not exists"946 );947 let item = <FungibleItemList<T>>::get(collection_id, item_id);948 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;949950 // remove approve list951 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));952953 // update balance954 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())955 .checked_sub(item.value as u64)956 .unwrap();957 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);958959 <FungibleItemList<T>>::remove(collection_id, item_id);960961 Ok(())962 }963964 fn collection_exists(collection_id: u64) -> DispatchResult {965 ensure!(966 <Collection<T>>::contains_key(collection_id),967 "This collection does not exist"968 );969 Ok(())970 }971972 fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {973 Self::collection_exists(collection_id)?;974975 let target_collection = <Collection<T>>::get(collection_id);976 ensure!(977 subject == target_collection.owner,978 "You do not own this collection"979 );980981 Ok(())982 }983984 fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool {985 let target_collection = <Collection<T>>::get(collection_id);986 let mut result: bool = subject == target_collection.owner;987 let exists = <AdminList<T>>::contains_key(collection_id);988989 if !result & exists {990 if <AdminList<T>>::get(collection_id).contains(&subject) {991 result = true992 }993 }994995 result996 }997998 fn check_owner_or_admin_permissions(999 collection_id: u64,1000 subject: T::AccountId,1001 ) -> DispatchResult {1002 Self::collection_exists(collection_id)?;1003 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());10041005 ensure!(1006 result,1007 "You do not have permissions to modify this collection"1008 );1009 Ok(())1010 }10111012 fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool {1013 let target_collection = <Collection<T>>::get(collection_id);10141015 match target_collection.mode {1016 CollectionMode::NFT(_) => {1017 <NftItemList<T>>::get(collection_id, item_id).owner == subject1018 }1019 CollectionMode::Fungible(_) => {1020 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1021 }1022 CollectionMode::ReFungible(_, _) => {1023 <ReFungibleItemList<T>>::get(collection_id, item_id)1024 .owner1025 .iter()1026 .any(|i| i.owner == subject)1027 }1028 CollectionMode::Invalid => false,1029 }1030 }10311032 fn check_white_list(collection_id: u64, address: T::AccountId) -> DispatchResult {1033 let mes = "Address is not in white list";1034 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1035 let wl = <WhiteList<T>>::get(collection_id);1036 ensure!(wl.contains(&address.clone()), mes);10371038 Ok(())1039 }10401041 fn transfer_fungible(1042 collection_id: u64,1043 item_id: u64,1044 value: u64,1045 owner: T::AccountId,1046 new_owner: T::AccountId,1047 ) -> DispatchResult {1048 ensure!(1049 <FungibleItemList<T>>::contains_key(collection_id, item_id),1050 "Item not exists"1051 );10521053 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1054 let amount = full_item.value;10551056 ensure!(amount >= value.into(), "Item balance not enouth");10571058 // update balance1059 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1060 .checked_sub(value)1061 .unwrap();1062 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);10631064 let mut new_owner_account_id = 0;1065 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1066 if new_owner_items.len() > 0 {1067 new_owner_account_id = new_owner_items[0];1068 }10691070 let val64 = value.into();10711072 // transfer1073 if amount == val64 && new_owner_account_id == 0 {1074 // change owner1075 // new owner do not have account1076 let mut new_full_item = full_item.clone();1077 new_full_item.owner = new_owner.clone();1078 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);10791080 // update balance1081 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1082 .checked_add(value)1083 .unwrap();1084 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);10851086 // update index collection1087 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1088 } else {1089 let mut new_full_item = full_item.clone();1090 new_full_item.value -= val64;10911092 // separate amount1093 if new_owner_account_id > 0 {1094 // new owner has account1095 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1096 item.value += val64;10971098 // update balance1099 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1100 .checked_add(value)1101 .unwrap();1102 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);11031104 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1105 } else {1106 // new owner do not have account1107 let item = FungibleItemType {1108 collection: collection_id,1109 owner: new_owner.clone(),1110 value: val64,1111 };11121113 Self::add_fungible_item(item)?;1114 }11151116 if amount == val64 {1117 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;11181119 // remove approve list1120 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1121 <FungibleItemList<T>>::remove(collection_id, item_id);1122 }11231124 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1125 }11261127 Ok(())1128 }11291130 fn transfer_refungible(1131 collection_id: u64,1132 item_id: u64,1133 value: u64,1134 owner: T::AccountId,1135 new_owner: T::AccountId,1136 ) -> DispatchResult {1137 ensure!(1138 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1139 "Item not exists"1140 );11411142 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1143 let item = full_item1144 .owner1145 .iter()1146 .filter(|i| i.owner == owner)1147 .next()1148 .unwrap();1149 let amount = item.fraction;11501151 ensure!(amount >= value.into(), "Item balance not enouth");11521153 // update balance1154 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1155 .checked_sub(value)1156 .unwrap();1157 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);11581159 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1160 .checked_add(value)1161 .unwrap();1162 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);11631164 let old_owner = item.owner.clone();1165 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1166 let val64 = value.into();11671168 // transfer1169 if amount == val64 && !new_owner_has_account {1170 // change owner1171 // new owner do not have account1172 let mut new_full_item = full_item.clone();1173 new_full_item1174 .owner1175 .iter_mut()1176 .find(|i| i.owner == owner)1177 .unwrap()1178 .owner = new_owner.clone();1179 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);11801181 // update index collection1182 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1183 } else {1184 let mut new_full_item = full_item.clone();1185 new_full_item1186 .owner1187 .iter_mut()1188 .find(|i| i.owner == owner)1189 .unwrap()1190 .fraction -= val64;11911192 // separate amount1193 if new_owner_has_account {1194 // new owner has account1195 new_full_item1196 .owner1197 .iter_mut()1198 .find(|i| i.owner == new_owner)1199 .unwrap()1200 .fraction += val64;1201 } else {1202 // new owner do not have account1203 new_full_item.owner.push(Ownership {1204 owner: new_owner.clone(),1205 fraction: val64,1206 });1207 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1208 }12091210 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1211 }12121213 Ok(())1214 }12151216 fn transfer_nft(1217 collection_id: u64,1218 item_id: u64,1219 sender: T::AccountId,1220 new_owner: T::AccountId,1221 ) -> DispatchResult {1222 ensure!(1223 <NftItemList<T>>::contains_key(collection_id, item_id),1224 "Item not exists"1225 );12261227 let mut item = <NftItemList<T>>::get(collection_id, item_id);12281229 ensure!(1230 sender == item.owner,1231 "sender parameter and item owner must be equal"1232 );12331234 // update balance1235 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1236 .checked_sub(1)1237 .unwrap();1238 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);12391240 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1241 .checked_add(1)1242 .unwrap();1243 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);12441245 // change owner1246 let old_owner = item.owner.clone();1247 item.owner = new_owner.clone();1248 <NftItemList<T>>::insert(collection_id, item_id, item);12491250 // update index collection1251 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;12521253 // reset approved list1254 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1255 Ok(())1256 }12571258 fn init_collection(item: &CollectionType<T::AccountId>) {1259 // check params1260 assert!(1261 item.decimal_points <= 4,1262 "decimal_points parameter must be lower than 4"1263 );1264 assert!(1265 item.name.len() <= 64,1266 "Collection name can not be longer than 63 char"1267 );1268 assert!(1269 item.name.len() <= 256,1270 "Collection description can not be longer than 255 char"1271 );1272 assert!(1273 item.token_prefix.len() <= 16,1274 "Token prefix can not be longer than 15 char"1275 );12761277 // Generate next collection ID1278 let next_id = CreatedCollectionCount::get()1279 .checked_add(1)1280 .expect("collection id error");12811282 CreatedCollectionCount::put(next_id);1283 }12841285 fn init_nft_token(item: &NftItemType<T::AccountId>) {1286 let current_index = <ItemListIndex>::get(item.collection)1287 .checked_add(1)1288 .expect("Item list index id error");12891290 let item_owner = item.owner.clone();1291 let collection_id = item.collection.clone();1292 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();12931294 <ItemListIndex>::insert(collection_id, current_index);12951296 // Update balance1297 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1298 .checked_add(1)1299 .unwrap();1300 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);1301 }13021303 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {1304 let current_index = <ItemListIndex>::get(item.collection)1305 .checked_add(1)1306 .expect("Item list index id error");1307 let owner = item.owner.clone();1308 let value = item.value as u64;13091310 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();13111312 <ItemListIndex>::insert(item.collection, current_index);13131314 // Update balance1315 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1316 .checked_add(value)1317 .unwrap();1318 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1319 }13201321 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {1322 let current_index = <ItemListIndex>::get(item.collection)1323 .checked_add(1)1324 .expect("Item list index id error");13251326 let value = item.owner.first().unwrap().fraction as u64;1327 let owner = item.owner.first().unwrap().owner.clone();13281329 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();13301331 <ItemListIndex>::insert(item.collection, current_index);13321333 // Update balance1334 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1335 .checked_add(value)1336 .unwrap();1337 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1338 }13391340 fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {13411342 // add to account limit1343 if <AccountItemCount<T>>::contains_key(owner.clone()) {13441345 // bound Owned tokens by a single address1346 let count = <AccountItemCount<T>>::get(owner.clone());1347 ensure!(count < ChainLimit::get().account_token_ownership_limit, "Owned tokens by a single address bound exceeded");13481349 <AccountItemCount<T>>::insert(owner.clone(), 1350 count.checked_add(1).unwrap());1351 }1352 else {1353 <AccountItemCount<T>>::insert(owner.clone(), 1);1354 }13551356 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1357 if list_exists {1358 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1359 let item_contains = list.contains(&item_index.clone());13601361 if !item_contains {1362 list.push(item_index.clone());1363 }13641365 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);1366 } else {1367 let mut itm = Vec::new();1368 itm.push(item_index.clone());1369 <AddressTokens<T>>::insert(collection_id, owner, itm);1370 1371 }13721373 Ok(())1374 }13751376 fn remove_token_index(1377 collection_id: u64,1378 item_index: u64,1379 owner: T::AccountId,1380 ) -> DispatchResult {13811382 // update counter1383 <AccountItemCount<T>>::insert(owner.clone(), 1384 <AccountItemCount<T>>::get(owner.clone()).checked_sub(1).unwrap());138513861387 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());1388 if list_exists {1389 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());1390 let item_contains = list.contains(&item_index.clone());13911392 if item_contains {1393 list.retain(|&item| item != item_index);1394 <AddressTokens<T>>::insert(collection_id, owner, list);1395 }1396 }13971398 Ok(())1399 }14001401 fn move_token_index(1402 collection_id: u64,1403 item_index: u64,1404 old_owner: T::AccountId,1405 new_owner: T::AccountId,1406 ) -> DispatchResult {1407 Self::remove_token_index(collection_id, item_index, old_owner)?;1408 Self::add_token_index(collection_id, item_index, new_owner)?;14091410 Ok(())1411 }1412}14131414////////////////////////////////////////////////////////////////////////////////////////////////////1415// Economic models1416// #region14171418/// Fee multiplier.1419pub type Multiplier = FixedU128;14201421type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<1422 <T as system::Trait>::AccountId,1423>>::Balance;1424type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<1425 <T as system::Trait>::AccountId,1426>>::NegativeImbalance;14271428/// Require the transactor pay for themselves and maybe include a tip to gain additional priority1429/// in the queue.1430#[derive(Encode, Decode, Clone, Eq, PartialEq)]1431pub struct ChargeTransactionPayment<T: transaction_payment::Trait + Send + Sync>(1432 #[codec(compact)] BalanceOf<T>,1433);14341435impl<T: Trait + transaction_payment::Trait + Send + Sync> sp_std::fmt::Debug1436 for ChargeTransactionPayment<T>1437{1438 #[cfg(feature = "std")]1439 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {1440 write!(f, "ChargeTransactionPayment<{:?}>", self.0)1441 }1442 #[cfg(not(feature = "std"))]1443 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {1444 Ok(())1445 }1446}14471448impl<T: Trait + transaction_payment::Trait + Send + Sync> ChargeTransactionPayment<T>1449where1450 T::Call:1451 Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Module<T>, T>,1452 BalanceOf<T>: Send + Sync + FixedPointOperand,1453{1454 /// utility constructor. Used only in client/factory code.1455 pub fn from(fee: BalanceOf<T>) -> Self {1456 Self(fee)1457 }14581459 pub fn traditional_fee(1460 len: usize,1461 info: &DispatchInfoOf<T::Call>,1462 tip: BalanceOf<T>,1463 ) -> BalanceOf<T>1464 where1465 T::Call: Dispatchable<Info = DispatchInfo>,1466 {1467 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)1468 }14691470 fn withdraw_fee(1471 &self,1472 who: &T::AccountId,1473 call: &T::Call,1474 info: &DispatchInfoOf<T::Call>,1475 len: usize,1476 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {1477 let tip = self.0;14781479 // Set fee based on call type. Creating collection costs 1 Unique.1480 // All other transactions have traditional fees so far1481 let fee = match call.is_sub_type() {1482 Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),1483 _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes1484 // _ => <BalanceOf<T>>::from(100)1485 };14861487 // Determine who is paying transaction fee based on ecnomic model1488 // Parse call to extract collection ID and access collection sponsor1489 let sponsor: T::AccountId = match call.is_sub_type() {1490 Some(Call::create_item(collection_id, _properties, _owner)) => {1491 <Collection<T>>::get(collection_id).sponsor1492 }1493 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {1494 let _collection_mode = <Collection<T>>::get(collection_id).mode;14951496 // sponsor timeout1497 let sponsor_timeout = match _collection_mode {1498 CollectionMode::NFT(_) => {1499 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);1500 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;1501 let time = basket - ChainLimit::get().nft_sponsor_transfer_timeout.into() - block_number;1502 if time <= 0.into() {1503 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);1504 }1505 time1506 }1507 CollectionMode::Fungible(_) => {1508 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);1509 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;1510 let time: T::BlockNumber;1511 if basket.iter().any(|i| i.address == _new_owner.clone())1512 {1513 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();1514 time = block_number - ChainLimit::get().fungible_sponsor_transfer_timeout.into() - item.start_block;1515 if time <= 0.into() {1516 basket.retain(|x| x.address == item.address);1517 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });1518 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);1519 }1520 }1521 else {1522 time = block_number;1523 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});1524 }15251526 time1527 }1528 CollectionMode::ReFungible(_, _) => {1529 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);1530 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;1531 let time = basket - ChainLimit::get().refungible_sponsor_transfer_timeout.into() - block_number;1532 if time <= 0.into() {1533 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);1534 }1535 time1536 }1537 _ => {1538 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;1539 block_number1540 },1541 };15421543 if sponsor_timeout > 0.into() {1544 T::AccountId::default()1545 } else {1546 <Collection<T>>::get(collection_id).sponsor1547 }1548 }15491550 _ => T::AccountId::default(),1551 };15521553 let mut who_pays_fee: T::AccountId = sponsor.clone();1554 if sponsor == T::AccountId::default() {1555 who_pays_fee = who.clone();1556 }15571558 // Only mess with balances if fee is not zero.1559 if fee.is_zero() {1560 return Ok((fee, None));1561 }15621563 match <T as transaction_payment::Trait>::Currency::withdraw(1564 &who_pays_fee,1565 fee,1566 if tip.is_zero() {1567 WithdrawReason::TransactionPayment.into()1568 } else {1569 WithdrawReason::TransactionPayment | WithdrawReason::Tip1570 },1571 ExistenceRequirement::KeepAlive,1572 ) {1573 Ok(imbalance) => Ok((fee, Some(imbalance))),1574 Err(_) => Err(InvalidTransaction::Payment.into()),1575 }1576 }1577}15781579impl<T: Trait + transaction_payment::Trait + Send + Sync> SignedExtension1580 for ChargeTransactionPayment<T>1581where1582 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,1583 T::Call:1584 Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Module<T>, T>,1585{1586 const IDENTIFIER: &'static str = "ChargeTransactionPayment";1587 type AccountId = T::AccountId;1588 type Call = T::Call;1589 type AdditionalSigned = ();1590 type Pre = (1591 BalanceOf<T>,1592 Self::AccountId,1593 Option<NegativeImbalanceOf<T>>,1594 BalanceOf<T>,1595 );1596 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {1597 Ok(())1598 }15991600 fn validate(1601 &self,1602 who: &Self::AccountId,1603 call: &Self::Call,1604 info: &DispatchInfoOf<Self::Call>,1605 len: usize,1606 ) -> TransactionValidity {1607 let (fee, _) = self.withdraw_fee(who, call, info, len)?;16081609 let mut r = ValidTransaction::default();1610 // NOTE: we probably want to maximize the _fee (of any type) per weight unit_ here, which1611 // will be a bit more than setting the priority to tip. For now, this is enough.1612 r.priority = fee.saturated_into::<TransactionPriority>();1613 Ok(r)1614 }16151616 fn pre_dispatch(1617 self,1618 who: &Self::AccountId,1619 call: &Self::Call,1620 info: &DispatchInfoOf<Self::Call>,1621 len: usize,1622 ) -> Result<Self::Pre, TransactionValidityError> {1623 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;1624 Ok((self.0, who.clone(), imbalance, fee))1625 }16261627 fn post_dispatch(1628 pre: Self::Pre,1629 info: &DispatchInfoOf<Self::Call>,1630 post_info: &PostDispatchInfoOf<Self::Call>,1631 len: usize,1632 _result: &DispatchResult,1633 ) -> Result<(), TransactionValidityError> {1634 let (tip, who, imbalance, fee) = pre;1635 if let Some(payed) = imbalance {1636 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(1637 len as u32, info, post_info, tip,1638 );1639 let refund = fee.saturating_sub(actual_fee);1640 let actual_payment =1641 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(1642 &who, refund,1643 ) {1644 Ok(refund_imbalance) => {1645 // The refund cannot be larger than the up front payed max weight.1646 // `PostDispatchInfo::calc_unspent` guards against such a case.1647 match payed.offset(refund_imbalance) {1648 Ok(actual_payment) => actual_payment,1649 Err(_) => return Err(InvalidTransaction::Payment.into()),1650 }1651 }1652 // We do not recreate the account using the refund. The up front payment1653 // is gone in that case.1654 Err(_) => payed,1655 };1656 let imbalances = actual_payment.split(tip);1657 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(1658 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),1659 );1660 }1661 Ok(())1662 }1663}1664// #endregionpallets/nft/src/mock.rsdiffbeforeafterboth--- a/pallets/nft/src/mock.rs
+++ b/pallets/nft/src/mock.rs
@@ -65,6 +65,7 @@
}
pub type TemplateModule = Module<Test>;
+
// This function basically just builds a genesis storage key/value store according to
// our desired mockup.
pub fn new_test_ext() -> sp_io::TestExternalities {
pallets/nft/src/tests.rsdiffbeforeafterboth--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -1,7 +1,8 @@
// Tests to be written here
use crate::mock::*;
-use crate::{ApprovePermissions, CollectionMode, AccessMode, Ownership};
+use crate::{AccessMode, ApprovePermissions, CollectionMode, Ownership, ChainLimits};
use frame_support::{assert_noop, assert_ok};
+use frame_system::{ RawOrigin };
// Use cases tests region
// #region
@@ -13,6 +14,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
@@ -41,6 +52,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::ReFungible(2000, 3);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
@@ -79,6 +100,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::Fungible(3);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
@@ -109,6 +140,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::Fungible(3);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
let origin2 = Origin::signed(2);
assert_ok!(TemplateModule::create_collection(
@@ -167,6 +208,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::ReFungible(2000, 3);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
let origin2 = Origin::signed(2);
assert_ok!(TemplateModule::create_collection(
@@ -264,6 +315,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
@@ -302,6 +363,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
let origin2 = Origin::signed(2);
assert_ok!(TemplateModule::create_collection(
@@ -323,8 +394,16 @@
assert_eq!(TemplateModule::balance_count(1, 1), 1);
assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
- assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true));
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::set_mint_permission(
+ origin1.clone(),
+ 1,
+ true
+ ));
+ assert_ok!(TemplateModule::set_public_access_mode(
+ origin1.clone(),
+ 1,
+ AccessMode::WhiteList
+ ));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 3));
@@ -362,6 +441,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::ReFungible(2000, 3);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
let origin2 = Origin::signed(2);
assert_ok!(TemplateModule::create_collection(
@@ -393,8 +482,16 @@
assert_eq!(TemplateModule::balance_count(1, 1), 1000);
assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
- assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true));
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::set_mint_permission(
+ origin1.clone(),
+ 1,
+ true
+ ));
+ assert_ok!(TemplateModule::set_public_access_mode(
+ origin1.clone(),
+ 1,
+ AccessMode::WhiteList
+ ));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 3));
@@ -444,6 +541,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::Fungible(3);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
let origin2 = Origin::signed(2);
assert_ok!(TemplateModule::create_collection(
@@ -465,8 +572,16 @@
assert_eq!(TemplateModule::balance_count(1, 1), 1000);
assert_eq!(TemplateModule::address_tokens(1, 1), [1]);
- assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true));
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::set_mint_permission(
+ origin1.clone(),
+ 1,
+ true
+ ));
+ assert_ok!(TemplateModule::set_public_access_mode(
+ origin1.clone(),
+ 1,
+ AccessMode::WhiteList
+ ));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 3));
@@ -532,6 +647,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
@@ -557,6 +682,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
@@ -577,6 +712,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
@@ -617,6 +762,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::Fungible(3);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
@@ -655,6 +810,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::ReFungible(200, 3);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
let origin2 = Origin::signed(2);
assert_ok!(TemplateModule::create_collection(
@@ -664,9 +829,17 @@
token_prefix1.clone(),
mode
));
-
- assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true));
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+
+ assert_ok!(TemplateModule::set_mint_permission(
+ origin1.clone(),
+ 1,
+ true
+ ));
+ assert_ok!(TemplateModule::set_public_access_mode(
+ origin1.clone(),
+ 1,
+ AccessMode::WhiteList
+ ));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1));
assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2));
@@ -704,6 +877,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
let origin2 = Origin::signed(2);
let origin3 = Origin::signed(3);
@@ -754,6 +937,16 @@
let origin2 = Origin::signed(2);
let origin3 = Origin::signed(3);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
col_name1.clone(),
@@ -809,6 +1002,16 @@
let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
col_name1.clone(),
@@ -881,6 +1084,16 @@
let nft_mode: CollectionMode = CollectionMode::NFT(2000);
let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
col_name1.clone(),
@@ -915,6 +1128,16 @@
let origin1 = Origin::signed(1);
let origin2 = Origin::signed(2);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
col_name1.clone(),
@@ -937,8 +1160,16 @@
assert_ok!(TemplateModule::approve(origin1.clone(), 2, 1, 1));
assert_eq!(TemplateModule::approved(1, (1, 1))[0].approved, 2);
- assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true));
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::set_mint_permission(
+ origin1.clone(),
+ 1,
+ true
+ ));
+ assert_ok!(TemplateModule::set_public_access_mode(
+ origin1.clone(),
+ 1,
+ AccessMode::WhiteList
+ ));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 3));
@@ -972,6 +1203,16 @@
let mode: CollectionMode = CollectionMode::NFT(2000);
let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
col_name1.clone(),
@@ -995,6 +1236,16 @@
let origin1 = Origin::signed(1);
let origin2 = Origin::signed(2);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
col_name1.clone(),
@@ -1019,6 +1270,16 @@
let origin1 = Origin::signed(1);
let origin2 = Origin::signed(2);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
col_name1.clone(),
@@ -1027,16 +1288,32 @@
mode
));
- assert_noop!(TemplateModule::add_to_white_list(origin2.clone(), 1, 3), "You do not have permissions to modify this collection");
+ assert_noop!(
+ TemplateModule::add_to_white_list(origin2.clone(), 1, 3),
+ "You do not have permissions to modify this collection"
+ );
});
}
#[test]
fn nobody_can_add_address_to_white_list_of_nonexisting_collection() {
new_test_ext().execute_with(|| {
+ let origin1 = Origin::signed(1);
+
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
- let origin1 = Origin::signed(1);
- assert_noop!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2), "This collection does not exist");
+ assert_noop!(
+ TemplateModule::add_to_white_list(origin1.clone(), 1, 2),
+ "This collection does not exist"
+ );
});
}
@@ -1049,6 +1326,16 @@
let mode: CollectionMode = CollectionMode::NFT(2000);
let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
col_name1.clone(),
@@ -1058,7 +1345,10 @@
));
assert_ok!(TemplateModule::destroy_collection(origin1.clone(), 1));
- assert_noop!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2), "This collection does not exist");
+ assert_noop!(
+ TemplateModule::add_to_white_list(origin1.clone(), 1, 2),
+ "This collection does not exist"
+ );
});
}
@@ -1066,13 +1356,22 @@
#[test]
fn address_is_already_added_to_white_list() {
new_test_ext().execute_with(|| {
-
let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
col_name1.clone(),
@@ -1091,13 +1390,22 @@
#[test]
fn owner_can_remove_address_from_white_list() {
new_test_ext().execute_with(|| {
-
let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
col_name1.clone(),
@@ -1107,7 +1415,11 @@
));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
- assert_ok!(TemplateModule::remove_from_white_list(origin1.clone(), 1, 2));
+ assert_ok!(TemplateModule::remove_from_white_list(
+ origin1.clone(),
+ 1,
+ 2
+ ));
assert_eq!(TemplateModule::white_list(1).len(), 0);
});
}
@@ -1115,7 +1427,6 @@
#[test]
fn admin_can_remove_address_from_white_list() {
new_test_ext().execute_with(|| {
-
let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
@@ -1123,6 +1434,16 @@
let origin1 = Origin::signed(1);
let origin2 = Origin::signed(2);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
col_name1.clone(),
@@ -1134,7 +1455,11 @@
assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 3));
- assert_ok!(TemplateModule::remove_from_white_list(origin2.clone(), 1, 3));
+ assert_ok!(TemplateModule::remove_from_white_list(
+ origin2.clone(),
+ 1,
+ 3
+ ));
assert_eq!(TemplateModule::white_list(1).len(), 0);
});
}
@@ -1142,7 +1467,6 @@
#[test]
fn nonprivileged_user_cannot_remove_address_from_white_list() {
new_test_ext().execute_with(|| {
-
let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
@@ -1150,6 +1474,16 @@
let origin1 = Origin::signed(1);
let origin2 = Origin::signed(2);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
col_name1.clone(),
@@ -1159,7 +1493,10 @@
));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
- assert_noop!(TemplateModule::remove_from_white_list(origin2.clone(), 1, 2), "You do not have permissions to modify this collection");
+ assert_noop!(
+ TemplateModule::remove_from_white_list(origin2.clone(), 1, 2),
+ "You do not have permissions to modify this collection"
+ );
assert_eq!(TemplateModule::white_list(1)[0], 2);
});
}
@@ -1167,16 +1504,28 @@
#[test]
fn nobody_can_remove_address_from_white_list_of_nonexisting_collection() {
new_test_ext().execute_with(|| {
+ let origin1 = Origin::signed(1);
+
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
- let origin1 = Origin::signed(1);
- assert_noop!(TemplateModule::remove_from_white_list(origin1.clone(), 1, 2), "This collection does not exist");
+ assert_noop!(
+ TemplateModule::remove_from_white_list(origin1.clone(), 1, 2),
+ "This collection does not exist"
+ );
});
}
#[test]
fn nobody_can_remove_address_from_white_list_of_deleted_collection() {
new_test_ext().execute_with(|| {
-
let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
@@ -1184,6 +1533,16 @@
let origin1 = Origin::signed(1);
let origin2 = Origin::signed(2);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
col_name1.clone(),
@@ -1194,7 +1553,10 @@
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
assert_ok!(TemplateModule::destroy_collection(origin1.clone(), 1));
- assert_noop!(TemplateModule::remove_from_white_list(origin2.clone(), 1, 2), "This collection does not exist");
+ assert_noop!(
+ TemplateModule::remove_from_white_list(origin2.clone(), 1, 2),
+ "This collection does not exist"
+ );
assert_eq!(TemplateModule::white_list(1).len(), 0);
});
}
@@ -1209,6 +1571,16 @@
let mode: CollectionMode = CollectionMode::NFT(2000);
let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
col_name1.clone(),
@@ -1218,8 +1590,16 @@
));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
- assert_ok!(TemplateModule::remove_from_white_list(origin1.clone(), 1, 2));
- assert_ok!(TemplateModule::remove_from_white_list(origin1.clone(), 1, 2));
+ assert_ok!(TemplateModule::remove_from_white_list(
+ origin1.clone(),
+ 1,
+ 2
+ ));
+ assert_ok!(TemplateModule::remove_from_white_list(
+ origin1.clone(),
+ 1,
+ 2
+ ));
assert_eq!(TemplateModule::white_list(1).len(), 0);
});
}
@@ -1233,6 +1613,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
@@ -1250,16 +1640,17 @@
1
));
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
- assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
-
- assert_noop!(TemplateModule::transfer(
+ assert_ok!(TemplateModule::set_public_access_mode(
origin1.clone(),
- 3,
1,
- 1,
- 1
- ), "Address is not in white list");
+ AccessMode::WhiteList
+ ));
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
+
+ assert_noop!(
+ TemplateModule::transfer(origin1.clone(), 3, 1, 1, 1),
+ "Address is not in white list"
+ );
});
}
@@ -1271,6 +1662,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
@@ -1288,24 +1689,28 @@
1
));
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::set_public_access_mode(
+ origin1.clone(),
+ 1,
+ AccessMode::WhiteList
+ ));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
// do approve
assert_ok!(TemplateModule::approve(origin1.clone(), 1, 1, 1));
assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 1);
-
- assert_ok!(TemplateModule::remove_from_white_list(origin1.clone(), 1, 1));
- assert_noop!(TemplateModule::transfer_from(
+ assert_ok!(TemplateModule::remove_from_white_list(
origin1.clone(),
- 1,
- 3,
1,
- 1,
1
- ), "Address is not in white list");
+ ));
+
+ assert_noop!(
+ TemplateModule::transfer_from(origin1.clone(), 1, 3, 1, 1, 1),
+ "Address is not in white list"
+ );
});
}
@@ -1318,6 +1723,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
@@ -1335,16 +1750,17 @@
1
));
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
- assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1));
-
- assert_noop!(TemplateModule::transfer(
+ assert_ok!(TemplateModule::set_public_access_mode(
origin1.clone(),
- 3,
1,
- 1,
- 1
- ), "Address is not in white list");
+ AccessMode::WhiteList
+ ));
+ assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1));
+
+ assert_noop!(
+ TemplateModule::transfer(origin1.clone(), 3, 1, 1, 1),
+ "Address is not in white list"
+ );
});
}
@@ -1356,6 +1772,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
@@ -1373,7 +1799,11 @@
1
));
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::set_public_access_mode(
+ origin1.clone(),
+ 1,
+ AccessMode::WhiteList
+ ));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
@@ -1381,16 +1811,16 @@
assert_ok!(TemplateModule::approve(origin1.clone(), 1, 1, 1));
assert_eq!(TemplateModule::approved(1, (1, 1)).len(), 1);
- assert_ok!(TemplateModule::remove_from_white_list(origin1.clone(), 1, 2));
-
- assert_noop!(TemplateModule::transfer_from(
+ assert_ok!(TemplateModule::remove_from_white_list(
origin1.clone(),
- 1,
- 3,
1,
- 1,
- 1
- ), "Address is not in white list");
+ 2
+ ));
+
+ assert_noop!(
+ TemplateModule::transfer_from(origin1.clone(), 1, 3, 1, 1, 1),
+ "Address is not in white list"
+ );
});
}
@@ -1403,6 +1833,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
@@ -1420,8 +1860,15 @@
1
));
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
- assert_noop!(TemplateModule::burn_item(origin1.clone(), 1, 1), "Address is not in white list");
+ assert_ok!(TemplateModule::set_public_access_mode(
+ origin1.clone(),
+ 1,
+ AccessMode::WhiteList
+ ));
+ assert_noop!(
+ TemplateModule::burn_item(origin1.clone(), 1, 1),
+ "Address is not in white list"
+ );
});
}
@@ -1434,6 +1881,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
@@ -1442,10 +1899,17 @@
token_prefix1.clone(),
mode
));
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::set_public_access_mode(
+ origin1.clone(),
+ 1,
+ AccessMode::WhiteList
+ ));
// do approve
- assert_noop!(TemplateModule::approve(origin1.clone(), 1, 1, 1), "Address is not in white list");
+ assert_noop!(
+ TemplateModule::approve(origin1.clone(), 1, 1, 1),
+ "Address is not in white list"
+ );
});
}
@@ -1459,6 +1923,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
@@ -1476,17 +1950,15 @@
1
));
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::set_public_access_mode(
+ origin1.clone(),
+ 1,
+ AccessMode::WhiteList
+ ));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
- assert_ok!(TemplateModule::transfer(
- origin1.clone(),
- 2,
- 1,
- 1,
- 1
- ));
+ assert_ok!(TemplateModule::transfer(origin1.clone(), 2, 1, 1, 1));
});
}
@@ -1498,6 +1970,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
@@ -1515,7 +1997,11 @@
1
));
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
+ assert_ok!(TemplateModule::set_public_access_mode(
+ origin1.clone(),
+ 1,
+ AccessMode::WhiteList
+ ));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 1));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
@@ -1543,6 +2029,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
@@ -1552,8 +2048,16 @@
mode
));
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
- assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, false));
+ assert_ok!(TemplateModule::set_public_access_mode(
+ origin1.clone(),
+ 1,
+ AccessMode::WhiteList
+ ));
+ assert_ok!(TemplateModule::set_mint_permission(
+ origin1.clone(),
+ 1,
+ false
+ ));
assert_ok!(TemplateModule::create_item(
origin1.clone(),
@@ -1573,6 +2077,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
let origin2 = Origin::signed(2);
assert_ok!(TemplateModule::create_collection(
@@ -1583,8 +2097,16 @@
mode
));
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
- assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, false));
+ assert_ok!(TemplateModule::set_public_access_mode(
+ origin1.clone(),
+ 1,
+ AccessMode::WhiteList
+ ));
+ assert_ok!(TemplateModule::set_mint_permission(
+ origin1.clone(),
+ 1,
+ false
+ ));
assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2));
@@ -1606,6 +2128,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
let origin2 = Origin::signed(2);
assert_ok!(TemplateModule::create_collection(
@@ -1616,16 +2148,22 @@
mode
));
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
- assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, false));
+ assert_ok!(TemplateModule::set_public_access_mode(
+ origin1.clone(),
+ 1,
+ AccessMode::WhiteList
+ ));
+ assert_ok!(TemplateModule::set_mint_permission(
+ origin1.clone(),
+ 1,
+ false
+ ));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
- assert_noop!(TemplateModule::create_item(
- origin2.clone(),
- 1,
- [1, 2, 3].to_vec(),
- 2
- ), "Collection is not in mint mode");
+ assert_noop!(
+ TemplateModule::create_item(origin2.clone(), 1, [1, 2, 3].to_vec(), 2),
+ "Collection is not in mint mode"
+ );
});
}
@@ -1638,6 +2176,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
let origin2 = Origin::signed(2);
assert_ok!(TemplateModule::create_collection(
@@ -1648,15 +2196,21 @@
mode
));
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
- assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, false));
+ assert_ok!(TemplateModule::set_public_access_mode(
+ origin1.clone(),
+ 1,
+ AccessMode::WhiteList
+ ));
+ assert_ok!(TemplateModule::set_mint_permission(
+ origin1.clone(),
+ 1,
+ false
+ ));
- assert_noop!(TemplateModule::create_item(
- origin2.clone(),
- 1,
- [1, 2, 3].to_vec(),
- 2
- ), "Collection is not in mint mode");
+ assert_noop!(
+ TemplateModule::create_item(origin2.clone(), 1, [1, 2, 3].to_vec(), 2),
+ "Collection is not in mint mode"
+ );
});
}
@@ -1669,6 +2223,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::create_collection(
origin1.clone(),
@@ -1678,8 +2242,16 @@
mode
));
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
- assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true));
+ assert_ok!(TemplateModule::set_public_access_mode(
+ origin1.clone(),
+ 1,
+ AccessMode::WhiteList
+ ));
+ assert_ok!(TemplateModule::set_mint_permission(
+ origin1.clone(),
+ 1,
+ true
+ ));
assert_ok!(TemplateModule::create_item(
origin1.clone(),
@@ -1699,6 +2271,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
let origin2 = Origin::signed(2);
assert_ok!(TemplateModule::create_collection(
@@ -1709,8 +2291,16 @@
mode
));
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
- assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true));
+ assert_ok!(TemplateModule::set_public_access_mode(
+ origin1.clone(),
+ 1,
+ AccessMode::WhiteList
+ ));
+ assert_ok!(TemplateModule::set_mint_permission(
+ origin1.clone(),
+ 1,
+ true
+ ));
assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2));
@@ -1732,6 +2322,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
let origin2 = Origin::signed(2);
assert_ok!(TemplateModule::create_collection(
@@ -1742,15 +2342,21 @@
mode
));
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
- assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true));
+ assert_ok!(TemplateModule::set_public_access_mode(
+ origin1.clone(),
+ 1,
+ AccessMode::WhiteList
+ ));
+ assert_ok!(TemplateModule::set_mint_permission(
+ origin1.clone(),
+ 1,
+ true
+ ));
- assert_noop!(TemplateModule::create_item(
- origin2.clone(),
- 1,
- [1, 2, 3].to_vec(),
- 2
- ), "Address is not in white list");
+ assert_noop!(
+ TemplateModule::create_item(origin2.clone(), 1, [1, 2, 3].to_vec(), 2),
+ "Address is not in white list"
+ );
});
}
@@ -1763,6 +2369,16 @@
let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
let mode: CollectionMode = CollectionMode::NFT(2000);
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
let origin1 = Origin::signed(1);
let origin2 = Origin::signed(2);
assert_ok!(TemplateModule::create_collection(
@@ -1773,8 +2389,16 @@
mode
));
- assert_ok!(TemplateModule::set_public_access_mode(origin1.clone(), 1, AccessMode::WhiteList));
- assert_ok!(TemplateModule::set_mint_permission(origin1.clone(), 1, true));
+ assert_ok!(TemplateModule::set_public_access_mode(
+ origin1.clone(),
+ 1,
+ AccessMode::WhiteList
+ ));
+ assert_ok!(TemplateModule::set_mint_permission(
+ origin1.clone(),
+ 1,
+ true
+ ));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), 1, 2));
assert_ok!(TemplateModule::create_item(
@@ -1786,4 +2410,290 @@
});
}
-// #endregion
\ No newline at end of file
+// Total number of collections. Positive test
+#[test]
+fn total_number_collections_bound() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
+ let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+ });
+}
+
+// Total number of collections. Negotive test
+#[test]
+fn total_number_collections_bound_neg() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
+ let origin1 = Origin::signed(1);
+
+ for _ in 0..10 {
+
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode.clone()
+ ));
+ }
+
+ // 11-th collection in chain. Expects error
+ assert_noop!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode.clone()
+ ), "Total collections bound exceeded");
+ });
+}
+
+// Owned tokens by a single address. Positive test
+#[test]
+fn owned_tokens_bound() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
+ let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_ok!(TemplateModule::create_item(
+ origin1.clone(),
+ 1,
+ [1, 2, 3].to_vec(),
+ 1
+ ));
+
+ assert_ok!(TemplateModule::create_item(
+ origin1.clone(),
+ 1,
+ [1, 2, 3].to_vec(),
+ 1
+ ));
+ });
+}
+
+// Owned tokens by a single address. Negotive test
+#[test]
+fn owned_tokens_bound_neg() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 1,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
+ let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_ok!(TemplateModule::create_item(
+ origin1.clone(),
+ 1,
+ [1, 2, 3].to_vec(),
+ 1
+ ));
+
+ assert_noop!(TemplateModule::create_item(
+ origin1.clone(),
+ 1,
+ [1, 2, 3].to_vec(),
+ 1
+ ), "Owned tokens by a single address bound exceeded");
+ });
+}
+
+// Number of collection admins. Positive test
+#[test]
+fn collection_admins_bound() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 2,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
+ let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2));
+ assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 3));
+ });
+}
+
+// Number of collection admins. Negotive test
+#[test]
+fn collection_admins_bound_neg() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 1,
+ collections_admins_limit: 1,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
+ let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+
+ assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), 1, 2));
+ assert_noop!(TemplateModule::add_collection_admin(origin1.clone(), 1, 3), "Number of collection admins bound exceeded");
+ });
+}
+
+// Custom data size. Positive test
+#[test]
+fn custom_data_size_bound() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 2048,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
+ let origin1 = Origin::signed(1);
+ assert_ok!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ));
+ });
+}
+
+// Custom data size. Negotive test
+#[test]
+fn custom_data_size_bound_neg() {
+ new_test_ext().execute_with(|| {
+ let col_name1: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let col_desc1: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let token_prefix1: Vec<u8> = b"token_prefix1\0".to_vec();
+ let mode: CollectionMode = CollectionMode::NFT(2000);
+
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ collection_numbers_limit: 10,
+ account_token_ownership_limit: 10,
+ collections_admins_limit: 5,
+ custom_data_limit: 200,
+ nft_sponsor_transfer_timeout: 15,
+ fungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ }));
+
+ let origin1 = Origin::signed(1);
+ assert_noop!(TemplateModule::create_collection(
+ origin1.clone(),
+ col_name1.clone(),
+ col_desc1.clone(),
+ token_prefix1.clone(),
+ mode
+ ), "Custom data size bound exceeded");
+ });
+}
+// #endregion