difftreelog
Merge branch 'develop' into feature/NFTPAR-268_inregration_test_broken
in: master
24 files changed
node/src/chain_spec.rsdiffbeforeafterboth--- a/node/src/chain_spec.rs
+++ b/node/src/chain_spec.rs
@@ -193,7 +193,7 @@
offchain_schema: vec![],
schema_version: SchemaVersion::default(),
sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
- unconfirmed_sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
+ sponsor_confirmed: true,
const_on_chain_schema: vec![],
variable_on_chain_schema: vec![],
limits: CollectionLimits::default()
pallets/nft/src/lib.rsdiffbeforeafterboth136 pub offchain_schema: Vec<u8>,136 pub offchain_schema: Vec<u8>,137 pub schema_version: SchemaVersion,137 pub schema_version: SchemaVersion,138 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender138 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender139 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship139 pub sponsor_confirmed: bool, // False if sponsor address has not yet confirmed sponsorship. True otherwise.140 pub limits: CollectionLimits, // Collection private restrictions 140 pub limits: CollectionLimits, // Collection private restrictions 141 pub variable_on_chain_schema: Vec<u8>, //141 pub variable_on_chain_schema: Vec<u8>, //142 pub const_on_chain_schema: Vec<u8>, //142 pub const_on_chain_schema: Vec<u8>, //145#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]145#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]146#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]146#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]147pub struct NftItemType<AccountId> {147pub struct NftItemType<AccountId> {148 pub collection: CollectionId,149 pub owner: AccountId,148 pub owner: AccountId,150 pub const_data: Vec<u8>,149 pub const_data: Vec<u8>,151 pub variable_data: Vec<u8>,150 pub variable_data: Vec<u8>,152}151}153152154#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]153#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]155#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]154#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]156pub struct FungibleItemType<AccountId> {155pub struct FungibleItemType {157 pub collection: CollectionId,158 pub owner: AccountId,159 pub value: u128,156 pub value: u128,160}157}161158162#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]159#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]163#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]160#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]164pub struct ReFungibleItemType<AccountId> {161pub struct ReFungibleItemType<AccountId> {165 pub collection: CollectionId,166 pub owner: Vec<Ownership<AccountId>>,162 pub owner: Vec<Ownership<AccountId>>,167 pub const_data: Vec<u8>,163 pub const_data: Vec<u8>,168 pub variable_data: Vec<u8>,164 pub variable_data: Vec<u8>,169}165}170166171#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]167// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]172#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]168// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]173pub struct ApprovePermissions<AccountId> {169// pub struct VestingItem<AccountId, Moment> {174 pub approved: AccountId,170// pub sender: AccountId,175 pub amount: u128,171// pub recipient: AccountId,172// pub collection_id: CollectionId,173// pub item_id: TokenId,174// pub amount: u64,175// pub vesting_date: Moment,176}176// }177177178#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]180pub struct VestingItem<AccountId, Moment> {181 pub sender: AccountId,182 pub recipient: AccountId,183 pub collection_id: CollectionId,184 pub item_id: TokenId,185 pub amount: u64,186 pub vesting_date: Moment,187}188189#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]190#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]191pub struct BasketItem<AccountId, BlockNumber> {192 pub address: AccountId,193 pub start_block: BlockNumber,194}195196#[derive(Encode, Decode, Debug, Clone, PartialEq)]178#[derive(Encode, Decode, Debug, Clone, PartialEq)]197#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]198pub struct CollectionLimits {180pub struct CollectionLimits {270#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]252#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]271#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]253#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]272pub struct CreateFungibleData {254pub struct CreateFungibleData {255 pub value: u128,273}256}274257275#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]258#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]424 /// Balance owner per collection map407 /// Balance owner per collection map425 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;408 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;426409427 /// second parameter: item id + owner account id410 /// second parameter: item id + owner account id + spender account id428 pub ApprovedList get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;411 pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;429412430 /// Item collections413 /// Item collections431 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;414 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;432 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => FungibleItemType<T::AccountId>;415 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;433 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;416 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;434417435 /// Index list418 /// Index list436 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;419 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;437420438 /// Tokens transfer baskets421 /// Tokens transfer baskets439 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;422 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;440 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => Vec<BasketItem<T::AccountId, T::BlockNumber>>;423 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;441 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;424 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;442425443 // Contract Sponsorship and Ownership426 // Contract Sponsorship and Ownership455 <Module<T>>::init_collection(_c);438 <Module<T>>::init_collection(_c);456 }439 }457440458 for (_num, _q, _i) in &config.nft_item_id {441 for (_num, _c, _i) in &config.nft_item_id {459 <Module<T>>::init_nft_token(_i);442 <Module<T>>::init_nft_token(*_c, _i);460 }443 }461444462 for (_num, _q, _i) in &config.fungible_item_id {445 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {463 <Module<T>>::init_fungible_token(_i);446 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);464 }447 }465448466 for (_num, _q, _i) in &config.refungible_item_id {449 for (_num, _c, _i) in &config.refungible_item_id {467 <Module<T>>::init_refungible_token(_i);450 <Module<T>>::init_refungible_token(*_c, _i);468 }451 }469 })452 })470 }453 }591 offchain_schema: Vec::new(),574 offchain_schema: Vec::new(),592 schema_version: SchemaVersion::ImageURL,575 schema_version: SchemaVersion::ImageURL,593 sponsor: T::AccountId::default(),576 sponsor: T::AccountId::default(),594 unconfirmed_sponsor: T::AccountId::default(),577 sponsor_confirmed: false,595 variable_on_chain_schema: Vec::new(),578 variable_on_chain_schema: Vec::new(),596 const_on_chain_schema: Vec::new(),579 const_on_chain_schema: Vec::new(),597 limits: CollectionLimits::default(),580 limits: CollectionLimits::default(),622 Self::check_owner_permissions(collection_id, sender)?;605 Self::check_owner_permissions(collection_id, sender)?;623606624 <AddressTokens<T>>::remove_prefix(collection_id);607 <AddressTokens<T>>::remove_prefix(collection_id);625 <ApprovedList<T>>::remove_prefix(collection_id);608 <Allowances<T>>::remove_prefix(collection_id);626 <Balance<T>>::remove_prefix(collection_id);609 <Balance<T>>::remove_prefix(collection_id);627 <ItemListIndex>::remove(collection_id);610 <ItemListIndex>::remove(collection_id);628 <AdminList<T>>::remove(collection_id);611 <AdminList<T>>::remove(collection_id);850 let mut target_collection = <Collection<T>>::get(collection_id);833 let mut target_collection = <Collection<T>>::get(collection_id);851 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);834 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);852835853 target_collection.unconfirmed_sponsor = new_sponsor;836 target_collection.sponsor = new_sponsor;837 target_collection.sponsor_confirmed = false;854 <Collection<T>>::insert(collection_id, target_collection);838 <Collection<T>>::insert(collection_id, target_collection);855839856 Ok(())840 Ok(())870 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);854 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);871855872 let mut target_collection = <Collection<T>>::get(collection_id);856 let mut target_collection = <Collection<T>>::get(collection_id);873 ensure!(sender == target_collection.unconfirmed_sponsor, Error::<T>::ConfirmUnsetSponsorFail);857 ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);874858875 target_collection.sponsor = target_collection.unconfirmed_sponsor;859 target_collection.sponsor_confirmed = true;876 target_collection.unconfirmed_sponsor = T::AccountId::default();877 <Collection<T>>::insert(collection_id, target_collection);860 <Collection<T>>::insert(collection_id, target_collection);878861879 Ok(())862 Ok(())898 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);881 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);899882900 target_collection.sponsor = T::AccountId::default();883 target_collection.sponsor = T::AccountId::default();884 target_collection.sponsor_confirmed = false;901 <Collection<T>>::insert(collection_id, target_collection);885 <Collection<T>>::insert(collection_id, target_collection);902886903 Ok(())887 Ok(())998 /// 982 /// 999 /// * item_id: ID of NFT to burn.983 /// * item_id: ID of NFT to burn.1000 #[weight = T::WeightInfo::burn_item()]984 #[weight = T::WeightInfo::burn_item()]1001 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {985 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10029861003 let sender = ensure_signed(origin)?;987 let sender = ensure_signed(origin)?;1004 Self::collection_exists(collection_id)?;988 Self::collection_exists(collection_id)?;1016 match target_collection.mode1000 match target_collection.mode1017 {1001 {1018 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1002 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1019 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,1003 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, collection_id, value)?,1020 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1004 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1021 _ => ()1005 _ => ()1022 };1006 };1072 match target_collection.mode1056 match target_collection.mode1073 {1057 {1074 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1058 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1075 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1059 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1076 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1060 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1077 _ => ()1061 _ => ()1078 };1062 };1096 /// 1080 /// 1097 /// * item_id: ID of the item.1081 /// * item_id: ID of the item.1098 #[weight = T::WeightInfo::approve()]1082 #[weight = T::WeightInfo::approve()]1099 pub fn approve(origin, approved: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1083 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {110010841101 let sender = ensure_signed(origin)?;1085 let sender = ensure_signed(origin)?;11021086110810921109 if target_collection.access == AccessMode::WhiteList {1093 if target_collection.access == AccessMode::WhiteList {1110 Self::check_white_list(collection_id, &sender)?;1094 Self::check_white_list(collection_id, &sender)?;1111 Self::check_white_list(collection_id, &approved)?;1095 Self::check_white_list(collection_id, &spender)?;1112 }1096 }111310971114 // amount param stub1098 let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1115 let amount = 100000000;11161117 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1118 if list_exists {1099 let mut allowance: u128 = amount;11191120 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1121 let item_contains = list.iter().any(|i| i.approved == approved);11221123 if !item_contains {1100 if allowance_exists {1124 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1101 allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1125 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1126 }1127 } else {11281129 let mut list = Vec::new();1130 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1131 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1132 }1102 }1103 <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);113311041134 Ok(())1105 Ok(())1135 }1106 }1159 let sender = ensure_signed(origin)?;1130 let sender = ensure_signed(origin)?;1160 let mut appoved_transfer = false;1131 let mut appoved_transfer = false;116111321162 // Check approve1133 // Check approval1163 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1134 let mut approval: u128 = 0;1135 if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &recipient)) {1164 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1136 approval = <Allowances<T>>::get(collection_id, (item_id, &from, &recipient));1165 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1166 if opt_item.is_some()1137 ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1167 {1168 appoved_transfer = true;1169 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1170 }1138 appoved_transfer = true;1171 }1139 }117211401173 let target_collection = <Collection<T>>::get(collection_id);1141 let target_collection = <Collection<T>>::get(collection_id);117711451178 // Transfer permissions check 1146 // Transfer permissions check 1179 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1147 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1180 Error::<T>::NoPermission);1148 Error::<T>::NoPermission);118111491182 if target_collection.access == AccessMode::WhiteList {1150 if target_collection.access == AccessMode::WhiteList {1183 Self::check_white_list(collection_id, &sender)?;1151 Self::check_white_list(collection_id, &sender)?;1184 Self::check_white_list(collection_id, &recipient)?;1152 Self::check_white_list(collection_id, &recipient)?;1185 }1153 }118611541187 // remove approve1155 // Reduce approval by transferred amount or remove if remaining approval drops to 01188 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1156 if approval - value > 0 {1157 <Allowances<T>>::insert(collection_id, (item_id, &from, &recipient), approval - value);1189 .into_iter().filter(|i| i.approved != sender.clone()).collect();1158 }1159 else {1190 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);1160 <Allowances<T>>::remove(collection_id, (item_id, &from, &recipient));1161 }1191116211921193 match target_collection.mode1163 match target_collection.mode1194 {1164 {1195 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1165 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1196 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1166 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1197 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1167 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1198 _ => ()1168 _ => ()1199 };1169 };1627 {1597 {1628 CreateItemData::NFT(data) => {1598 CreateItemData::NFT(data) => {1629 let item = NftItemType {1599 let item = NftItemType {1630 collection: collection_id,1631 owner,1600 owner,1632 const_data: data.const_data,1601 const_data: data.const_data,1633 variable_data: data.variable_data1602 variable_data: data.variable_data1634 };1603 };163516041636 Self::add_nft_item(item)?;1605 Self::add_nft_item(collection_id, item)?;1637 },1606 },1638 CreateItemData::Fungible(_) => {1607 CreateItemData::Fungible(data) => {1639 let item = FungibleItemType {1608 Self::add_fungible_item(collection_id, &owner, data.value)?;1640 collection: collection_id,1641 owner,1642 value: (10 as u128).pow(collection.decimal_points as u32)1643 };16441645 Self::add_fungible_item(item)?;1646 },1609 },1647 CreateItemData::ReFungible(data) => {1610 CreateItemData::ReFungible(data) => {1648 let mut owner_list = Vec::new();1611 let mut owner_list = Vec::new();1649 let value = (10 as u128).pow(collection.decimal_points as u32);1612 let value = (10 as u128).pow(collection.decimal_points as u32);1650 owner_list.push(Ownership {owner: owner.clone(), fraction: value});1613 owner_list.push(Ownership {owner: owner.clone(), fraction: value});165116141652 let item = ReFungibleItemType {1615 let item = ReFungibleItemType {1653 collection: collection_id,1654 owner: owner_list,1616 owner: owner_list,1655 const_data: data.const_data,1617 const_data: data.const_data,1656 variable_data: data.variable_data1618 variable_data: data.variable_data1657 };1619 };165816201659 Self::add_refungible_item(item)?;1621 Self::add_refungible_item(collection_id, item)?;1660 }1622 }1661 };1623 };166216241666 Ok(())1628 Ok(())1667 }1629 }166816301669 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1631 fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {1670 let current_index = <ItemListIndex>::get(item.collection)1671 .checked_add(1)1672 .ok_or(Error::<T>::NumOverflow)?;1673 let itemcopy = item.clone();1674 let owner = item.owner.clone();167516321676 Self::add_token_index(item.collection, current_index, owner.clone())?;1633 // Does new owner already have an account?1634 let mut balance: u128 = 0;1635 if <FungibleItemList<T>>::contains_key(collection_id, owner) {1636 balance = <FungibleItemList<T>>::get(collection_id, owner).value;1637 } 167716381678 <ItemListIndex>::insert(item.collection, current_index);1639 // Mint 1640 let item = FungibleItemType {1641 value: balance + value1642 };1679 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);1643 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);168016441681 // Add current block1682 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1683 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1684 1685 // Update balance1645 // Update balance1686 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1646 let new_balance = <Balance<T>>::get(collection_id, owner)1687 .checked_add(item.value)1647 .checked_add(value)1688 .ok_or(Error::<T>::NumOverflow)?;1648 .ok_or(Error::<T>::NumOverflow)?;1689 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1649 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);169016501691 Ok(())1651 Ok(())1692 }1652 }169316531694 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1654 fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1695 let current_index = <ItemListIndex>::get(item.collection)1655 let current_index = <ItemListIndex>::get(collection_id)1696 .checked_add(1)1656 .checked_add(1)1697 .ok_or(Error::<T>::NumOverflow)?;1657 .ok_or(Error::<T>::NumOverflow)?;1698 let itemcopy = item.clone();1658 let itemcopy = item.clone();169916591700 let value = item.owner.first().unwrap().fraction;1660 let value = item.owner.first().unwrap().fraction;1701 let owner = item.owner.first().unwrap().owner.clone();1661 let owner = item.owner.first().unwrap().owner.clone();170216621703 Self::add_token_index(item.collection, current_index, owner.clone())?;1663 Self::add_token_index(collection_id, current_index, owner.clone())?;170416641705 <ItemListIndex>::insert(item.collection, current_index);1665 <ItemListIndex>::insert(collection_id, current_index);1706 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);1666 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);170716671708 // Add current block1709 let block_number: T::BlockNumber = 0.into();1710 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);17111712 // Update balance1668 // Update balance1713 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1669 let new_balance = <Balance<T>>::get(collection_id, owner.clone())1714 .checked_add(value)1670 .checked_add(value)1715 .ok_or(Error::<T>::NumOverflow)?;1671 .ok_or(Error::<T>::NumOverflow)?;1716 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1672 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);171716731718 Ok(())1674 Ok(())1719 }1675 }172016761721 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1677 fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1722 let current_index = <ItemListIndex>::get(item.collection)1678 let current_index = <ItemListIndex>::get(collection_id)1723 .checked_add(1)1679 .checked_add(1)1724 .ok_or(Error::<T>::NumOverflow)?;1680 .ok_or(Error::<T>::NumOverflow)?;172516811726 let item_owner = item.owner.clone();1682 let item_owner = item.owner.clone();1727 let collection_id = item.collection.clone();1728 Self::add_token_index(collection_id, current_index, item.owner.clone())?;1683 Self::add_token_index(collection_id, current_index, item.owner.clone())?;172916841730 <ItemListIndex>::insert(collection_id, current_index);1685 <ItemListIndex>::insert(collection_id, current_index);1731 <NftItemList<T>>::insert(collection_id, current_index, item);1686 <NftItemList<T>>::insert(collection_id, current_index, item);173216871733 // Add current block1734 let block_number: T::BlockNumber = 0.into();1735 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);17361737 // Update balance1688 // Update balance1738 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1689 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1739 .checked_add(1)1690 .checked_add(1)1761 .unwrap();1712 .unwrap();1762 Self::remove_token_index(collection_id, item_id, owner.clone())?;1713 Self::remove_token_index(collection_id, item_id, owner.clone())?;176317141764 // remove approve list1765 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));17661767 // update balance1715 // update balance1768 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1716 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1769 .checked_sub(item.fraction)1717 .checked_sub(item.fraction)1783 let item = <NftItemList<T>>::get(collection_id, item_id);1731 let item = <NftItemList<T>>::get(collection_id, item_id);1784 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;1732 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;178517331786 // remove approve list1787 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17881789 // update balance1734 // update balance1790 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1735 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1791 .checked_sub(1)1736 .checked_sub(1)1796 Ok(())1741 Ok(())1797 }1742 }179817431799 fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1744 fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1800 ensure!(1745 ensure!(1801 <FungibleItemList<T>>::contains_key(collection_id, item_id),1746 <FungibleItemList<T>>::contains_key(collection_id, owner),1802 Error::<T>::TokenNotFound1747 Error::<T>::TokenNotFound1803 );1748 );1804 let item = <FungibleItemList<T>>::get(collection_id, item_id);1749 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1805 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;1750 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);180617511807 // remove approve list1808 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));18091810 // update balance1752 // update balance1811 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1753 let new_balance = <Balance<T>>::get(collection_id, owner)1812 .checked_sub(item.value)1754 .checked_sub(value)1813 .ok_or(Error::<T>::NumOverflow)?;1755 .ok_or(Error::<T>::NumOverflow)?;1814 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1756 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);181517571816 <FungibleItemList<T>>::remove(collection_id, item_id);1758 if balance.value - value > 0 {1759 balance.value -= value;1760 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1761 }1762 else {1763 <FungibleItemList<T>>::remove(collection_id, owner);1764 }181717651818 Ok(())1766 Ok(())1819 }1767 }1874 <NftItemList<T>>::get(collection_id, item_id).owner == subject1822 <NftItemList<T>>::get(collection_id, item_id).owner == subject1875 }1823 }1876 CollectionMode::Fungible(_) => {1824 CollectionMode::Fungible(_) => {1877 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1825 <FungibleItemList<T>>::contains_key(collection_id, &subject)1878 }1826 }1879 CollectionMode::ReFungible(_) => {1827 CollectionMode::ReFungible(_) => {1880 <ReFungibleItemList<T>>::get(collection_id, item_id)1828 <ReFungibleItemList<T>>::get(collection_id, item_id)189518431896 fn transfer_fungible(1844 fn transfer_fungible(1897 collection_id: CollectionId,1845 collection_id: CollectionId,1898 item_id: TokenId,1899 value: u128,1846 value: u128,1900 owner: T::AccountId,1847 owner: &T::AccountId,1901 new_owner: T::AccountId,1848 recipient: &T::AccountId,1902 ) -> DispatchResult {1849 ) -> DispatchResult {1903 ensure!(1850 ensure!(1904 <FungibleItemList<T>>::contains_key(collection_id, item_id),1851 <FungibleItemList<T>>::contains_key(collection_id, owner),1905 Error::<T>::TokenNotFound1852 Error::<T>::TokenNotFound1906 );1853 );190718541908 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1855 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1909 let amount = full_item.value;1856 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);191018571911 ensure!(amount >= value, Error::<T>::TokenValueTooLow);1858 // Send balance to recipient (updates balanceOf of recipient)1859 Self::add_fungible_item(collection_id, recipient, value)?;191218601913 // update balance1861 // update balanceOf of sender1914 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1862 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);1915 .checked_sub(value)1916 .ok_or(Error::<T>::NumOverflow)?;1917 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);191818631919 let mut new_owner_account_id = 0;1864 // Reduce or remove sender1920 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1865 if balance.value == value {1921 if new_owner_items.len() > 0 {1922 new_owner_account_id = new_owner_items[0];1866 <FungibleItemList<T>>::remove(collection_id, owner);1923 }1867 }19241868 else {1925 // transfer1869 balance.value -= value;1926 if amount == value && new_owner_account_id == 0 {1870 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1927 // change owner1928 // new owner do not have account1929 let mut new_full_item = full_item.clone();1930 new_full_item.owner = new_owner.clone();1931 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19321933 // update balance1934 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1935 .checked_add(value)1936 .ok_or(Error::<T>::NumOverflow)?;1937 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19381939 // update index collection1940 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1941 } else {1942 let mut new_full_item = full_item.clone();1943 new_full_item.value -= value;19441945 // separate amount1946 if new_owner_account_id > 0 {1947 // new owner has account1948 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1949 item.value += value;19501951 // update balance1952 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1953 .checked_add(value)1954 .ok_or(Error::<T>::NumOverflow)?;1955 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19561957 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1958 } else {1959 // new owner do not have account1960 let item = FungibleItemType {1961 collection: collection_id,1962 owner: new_owner.clone(),1963 value1964 };19651966 Self::add_fungible_item(item)?;1967 }19681969 if amount == value {1970 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;19711972 // remove approve list1973 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1974 <FungibleItemList<T>>::remove(collection_id, item_id);1975 }19761977 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1978 }1871 }197918721980 Ok(())1873 Ok(())2009 .ok_or(Error::<T>::NumOverflow)?;1902 .ok_or(Error::<T>::NumOverflow)?;2010 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);1903 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);201119042012 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1905 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2013 .checked_add(value)1906 .checked_add(value)2014 .ok_or(Error::<T>::NumOverflow)?;1907 .ok_or(Error::<T>::NumOverflow)?;2015 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);1908 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);201619092017 let old_owner = item.owner.clone();1910 let old_owner = item.owner.clone();2018 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1911 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);2089 .ok_or(Error::<T>::NumOverflow)?;1982 .ok_or(Error::<T>::NumOverflow)?;2090 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);1983 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);209119842092 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1985 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2093 .checked_add(1)1986 .checked_add(1)2094 .ok_or(Error::<T>::NumOverflow)?;1987 .ok_or(Error::<T>::NumOverflow)?;2095 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);1988 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);209619892097 // change owner1990 // change owner2098 let old_owner = item.owner.clone();1991 let old_owner = item.owner.clone();2102 // update index collection1995 // update index collection2103 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1996 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;210419972105 // reset approved list2106 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));2107 Ok(())1998 Ok(())2108 }1999 }2109 2000 2115 match mode {2006 match mode {2116 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2007 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2117 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2008 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2118 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2119 _ => ()2009 _ => ()2120 };2010 };2121 2011 2177 CreatedCollectionCount::put(next_id);2067 CreatedCollectionCount::put(next_id);2178 }2068 }217920692180 fn init_nft_token(item: &NftItemType<T::AccountId>) {2070 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2181 let current_index = <ItemListIndex>::get(item.collection)2071 let current_index = <ItemListIndex>::get(collection_id)2182 .checked_add(1)2072 .checked_add(1)2183 .unwrap();2073 .unwrap();218420742185 let item_owner = item.owner.clone();2075 let item_owner = item.owner.clone();2186 let collection_id = item.collection.clone();2187 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();2076 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();218820772189 <ItemListIndex>::insert(collection_id, current_index);2078 <ItemListIndex>::insert(collection_id, current_index);2195 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2084 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2196 }2085 }219720862198 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {2087 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2199 let current_index = <ItemListIndex>::get(item.collection)2088 let current_index = <ItemListIndex>::get(collection_id)2200 .checked_add(1)2089 .checked_add(1)2201 .unwrap();2090 .unwrap();2202 let owner = item.owner.clone();220320912204 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();2092 Self::add_token_index(collection_id, current_index, (*owner).clone()).unwrap();220520932206 <ItemListIndex>::insert(item.collection, current_index);2094 <ItemListIndex>::insert(collection_id, current_index);220720952208 // Update balance2096 // Update balance2209 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2097 let new_balance = <Balance<T>>::get(collection_id, owner)2210 .checked_add(item.value)2098 .checked_add(item.value)2211 .unwrap();2099 .unwrap();2212 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2100 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2213 }2101 }221421022215 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {2103 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2216 let current_index = <ItemListIndex>::get(item.collection)2104 let current_index = <ItemListIndex>::get(collection_id)2217 .checked_add(1)2105 .checked_add(1)2218 .unwrap();2106 .unwrap();221921072220 let value = item.owner.first().unwrap().fraction;2108 let value = item.owner.first().unwrap().fraction;2221 let owner = item.owner.first().unwrap().owner.clone();2109 let owner = item.owner.first().unwrap().owner.clone();222221102223 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();2111 Self::add_token_index(collection_id, current_index, owner.clone()).unwrap();222421122225 <ItemListIndex>::insert(item.collection, current_index);2113 <ItemListIndex>::insert(collection_id, current_index);222621142227 // Update balance2115 // Update balance2228 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2116 let new_balance = <Balance<T>>::get(collection_id, owner.clone())2229 .checked_add(value)2117 .checked_add(value)2230 .unwrap();2118 .unwrap();2231 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2119 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2232 }2120 }223321212234 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {2122 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {2399 // };2287 // };2400 let fee = Self::traditional_fee(len, info, tip);2288 let fee = Self::traditional_fee(len, info, tip);240122892290 // Only mess with balances if fee is not zero.2291 if fee.is_zero() {2292 return Ok((fee, None));2293 }22942402 // Determine who is paying transaction fee based on ecnomic model2295 // Determine who is paying transaction fee based on ecnomic model2403 // Parse call to extract collection ID and access collection sponsor2296 // Parse call to extract collection ID and access collection sponsor2404 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2297 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2405 Some(Call::create_item(collection_id, _owner, _properties)) => {2298 Some(Call::create_item(collection_id, _owner, _properties)) => {240622992407 // check free create limit2300 // check free create limit2408 if <Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)2301 if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&2302 (<Collection<T>>::get(collection_id).sponsor_confirmed)2409 {2303 {2410 <Collection<T>>::get(collection_id).sponsor2304 <Collection<T>>::get(collection_id).sponsor2411 } else {2305 } else {2412 T::AccountId::default()2306 T::AccountId::default()2413 }2307 }2414 }2308 }2415 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2309 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2416 2310 2417 let _collection_limits = <Collection<T>>::get(collection_id).limits;2311 let mut sponsor_transfer = false;2418 let _collection_mode = <Collection<T>>::get(collection_id).mode;2312 if <Collection<T>>::get(collection_id).sponsor_confirmed {241923132420 // sponsor timeout2314 let collection_limits = <Collection<T>>::get(collection_id).limits;2315 let collection_mode = <Collection<T>>::get(collection_id).mode;2316 2317 // sponsor timeout2421 let sponsor_transfer = match _collection_mode {2318 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2319 sponsor_transfer = match collection_mode {2422 CollectionMode::NFT => {2320 CollectionMode::NFT => {2321 2322 // get correct limit2323 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2324 collection_limits.sponsor_transfer_timeout2325 } else {2326 ChainLimit::get().nft_sponsor_transfer_timeout2327 };2328 2329 let mut sponsored = true;2330 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2331 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2332 let limit_time = last_tx_block + limit.into();2333 if block_number <= limit_time {2334 sponsored = false;2335 }2336 }2337 if sponsored {2338 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2339 }242323402424 // get correct limit2341 sponsored2425 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2426 _collection_limits.sponsor_transfer_timeout2427 } else {2428 ChainLimit::get().nft_sponsor_transfer_timeout2429 };24302431 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2432 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2433 let limit_time = basket + limit.into();2434 if block_number >= limit_time {2435 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2436 true2437 }2342 }2438 else {2343 CollectionMode::Fungible(_) => {2439 false2344 2440 }2441 }2442 CollectionMode::Fungible(_) => {24432444 // get correct limit2345 // get correct limit2445 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2346 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2446 _collection_limits.sponsor_transfer_timeout2347 collection_limits.sponsor_transfer_timeout2447 } else {2348 } else {2448 ChainLimit::get().fungible_sponsor_transfer_timeout2349 ChainLimit::get().fungible_sponsor_transfer_timeout2449 };2350 };24502451 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2351 2452 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2352 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2453 if basket.iter().any(|i| i.address == _new_owner.clone())2353 let mut sponsored = true;2454 {2455 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2354 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2456 let limit_time = item.start_block + limit.into();2355 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2457 if block_number >= limit_time {2356 let limit_time = last_tx_block + limit.into();2458 basket.retain(|x| x.address == item.address);2459 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2357 if block_number <= limit_time {2460 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2358 sponsored = false;2461 true2359 }2462 }2360 }2463 else {2361 if sponsored {2464 false2362 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2465 }2363 }23642365 sponsored2466 }2366 }2467 else {2367 CollectionMode::ReFungible(_) => {2468 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2368 2369 // get correct limit2370 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2371 collection_limits.sponsor_transfer_timeout2372 } else {2373 ChainLimit::get().refungible_sponsor_transfer_timeout2374 };2375 2376 let mut sponsored = true;2377 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2378 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2379 let limit_time = last_tx_block + limit.into();2469 true2380 if block_number <= limit_time {2470 }2381 sponsored = false;2382 }2471 }2383 }2472 CollectionMode::ReFungible(_) => {2384 if sponsored {2385 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2386 }247323872474 // get correct limit2388 sponsored2475 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2476 _collection_limits.sponsor_transfer_timeout2477 } else {2478 ChainLimit::get().refungible_sponsor_transfer_timeout2479 };24802481 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2482 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2483 let limit_time = basket + limit.into();2484 if block_number >= limit_time {2485 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2486 true2487 } else {2488 false2489 }2389 }2490 }2390 _ => {2491 _ => {2391 false2492 false2392 },2493 },2393 };2494 };2394 }249523952496 if !sponsor_transfer {2396 if !sponsor_transfer {2497 T::AccountId::default()2397 T::AccountId::default()2568 let mut who_pays_fee: T::AccountId = sponsor.clone();2468 let mut who_pays_fee: T::AccountId = sponsor.clone();2569 if sponsor == T::AccountId::default() {2469 if sponsor == T::AccountId::default() {2570 who_pays_fee = who.clone();2470 who_pays_fee = who.clone();2571 }25722573 // Only mess with balances if fee is not zero.2574 if fee.is_zero() {2575 return Ok((fee, None));2576 }2471 }257724722578 match <T as transaction_payment::Trait>::Currency::withdraw(2473 match <T as transaction_payment::Trait>::Currency::withdraw(runtime_types.jsondiffbeforeafterboth--- a/runtime_types.json
+++ b/runtime_types.json
@@ -42,18 +42,14 @@
"Fraction": "u128"
},
"FungibleItemType": {
- "Collection": "CollectionId",
- "Owner": "AccountId",
"Value": "u128"
},
"NftItemType": {
- "Collection": "CollectionId",
"Owner": "AccountId",
"ConstData": "Vec<u8>",
"VariableData": "Vec<u8>"
},
"ReFungibleItemType": {
- "Collection": "CollectionId",
"Owner": "Vec<Ownership<AccountId>>",
"ConstData": "Vec<u8>",
"VariableData": "Vec<u8>"
@@ -70,14 +66,10 @@
"OffchainSchema": "Vec<u8>",
"SchemaVersion": "SchemaVersion",
"Sponsor": "AccountId",
- "UnconfirmedSponsor": "AccountId",
+ "SponsorConfirmed": "bool",
"Limits": "CollectionLimits",
"VariableOnChainSchema": "Vec<u8>",
"ConstOnChainSchema": "Vec<u8>"
- },
- "ApprovePermissions": {
- "Approved": "AccountId",
- "Amount": "u128"
},
"RawData": "Vec<u8>",
"Address": "AccountId",
@@ -87,7 +79,9 @@
"const_data": "Vec<u8>",
"variable_data": "Vec<u8>"
},
- "CreateFungibleData": {},
+ "CreateFungibleData": {
+ "value": "u128"
+ },
"CreateReFungibleData": {
"const_data": "Vec<u8>",
"variable_data": "Vec<u8>"
@@ -107,10 +101,6 @@
},
"CollectionId": "u32",
"TokenId": "u32",
- "BasketItem": {
- "Address": "AccountId",
- "start_block": "BlockNumber"
- },
"ChainLimits": {
"collection_numbers_limit": "u32",
"account_token_ownership_limit": "u32",
tests/loadtester-src/.gitignorediffbeforeafterboth--- /dev/null
+++ b/tests/loadtester-src/.gitignore
@@ -0,0 +1,9 @@
+# Ignore build artifacts from the local tests sub-crate.
+/target/
+
+# Ignore backup files creates by cargo fmt.
+**/*.rs.bk
+
+# Remove Cargo.lock when creating an executable, leave it for libraries
+# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+Cargo.lock
\ No newline at end of file
tests/loadtester-src/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/tests/loadtester-src/Cargo.toml
@@ -0,0 +1,36 @@
+[package]
+name = "loadtester"
+version = "0.1.0"
+authors = ["[your_name] <[your_email]>"]
+edition = "2018"
+
+[dependencies]
+ink_primitives = { version = "3.0.0-rc2", default-features = false }
+ink_metadata = { version = "3.0.0-rc2", default-features = false, features = ["derive"], optional = true }
+ink_env = { version = "3.0.0-rc2", default-features = false }
+ink_storage = { version = "3.0.0-rc2", default-features = false }
+ink_lang = { version = "3.0.0-rc2", default-features = false }
+ink_prelude = { version = "3.0.0-rc2", default-features = false }
+
+scale = { package = "parity-scale-codec", version = "1.3", default-features = false, features = ["derive"] }
+scale-info = { version = "0.4.1", default-features = false, features = ["derive"], optional = true }
+
+[lib]
+name = "loadtester"
+path = "lib.rs"
+crate-type = [
+ # Used for normal contract Wasm blobs.
+ "cdylib",
+]
+
+[features]
+default = ["std"]
+std = [
+ "ink_metadata/std",
+ "ink_env/std",
+ "ink_storage/std",
+ "ink_primitives/std",
+ "scale/std",
+ "scale-info/std",
+]
+ink-as-dependency = []
tests/loadtester-src/lib.rsdiffbeforeafterboth--- /dev/null
+++ b/tests/loadtester-src/lib.rs
@@ -0,0 +1,53 @@
+#![cfg_attr(not(feature = "std"), no_std)]
+
+use ink_lang as ink;
+
+#[ink::contract]
+mod loadtester {
+ use ink_storage::collections::Vec as InkVec;
+
+ #[ink(storage)]
+ pub struct LoadTester {
+ vector: InkVec<u64>,
+ }
+
+ impl LoadTester {
+ #[ink(constructor)]
+ pub fn new() -> Self {
+ Self {
+ vector: InkVec::new(),
+ }
+ }
+
+ #[ink(message)]
+ pub fn bloat(&mut self, count: u64){
+ for i in 1..count+1 {
+ self.vector.push(i);
+ }
+ }
+
+ #[ink(message)]
+ pub fn get(&self) -> u128 {
+ let mut sum: u128 = 0;
+ for num in self.vector.iter() {
+ sum += *num as u128;
+ }
+ sum
+ }
+ }
+
+ #[cfg(test)]
+ mod tests {
+
+ use super::*;
+
+ #[test]
+ fn it_works() {
+ let mut lt = LoadTester::new();
+ lt.bloat(4);
+ assert_eq!(lt.get(), [1,2,3,4]);
+ lt.bloat(3);
+ assert_eq!(lt.get(), [1,2,3,4,1,2,3]);
+ }
+ }
+}
tests/package.jsondiffbeforeafterboth--- a/tests/package.json
+++ b/tests/package.json
@@ -16,7 +16,8 @@
"typescript": "^3.9.7"
},
"scripts": {
- "test": "mocha --timeout 9999999 -r ts-node/register ./**/*.test.ts"
+ "test": "mocha --timeout 9999999 -r ts-node/register ./**/*.test.ts",
+ "load": "mocha --timeout 9999999 -r ts-node/register ./**/*.load.ts"
},
"author": "",
"license": "Apache 2.0",
tests/src/change-collection-owner.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/change-collection-owner.test.ts
@@ -0,0 +1,59 @@
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import privateKey from './substrate/privateKey';
+import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from "./substrate/substrate-api";
+import { createCollectionExpectSuccess, createCollectionExpectFailure } from "./util/helpers";
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+describe('Integration Test changeCollectionOwner(collection_id, new_owner):', () => {
+ it('Changing owner changes owner.', async () => {
+ await usingApi(async api => {
+ const collectionId = await createCollectionExpectSuccess();
+ const alice = privateKey('//Alice');
+ const bob = privateKey('//Bob');
+
+ const collection: any = (await api.query.nft.collection(collectionId));
+ expect(collection.Owner.toString()).to.be.eq(alice.address);
+
+ const changeOwnerTx = api.tx.nft.changeCollectionOwner(collectionId, bob.address);
+ await submitTransactionAsync(alice, changeOwnerTx);
+
+ const collectionAfterOwnerChange: any = (await api.query.nft.collection(collectionId));
+ expect(collectionAfterOwnerChange.Owner.toString()).to.be.eq(bob.address);
+ });
+ });
+});
+
+describe('Negative Integration Test changeCollectionOwner(collection_id, new_owner):', () => {
+ it(`Not owner can't change owner.`, async () => {
+ await usingApi(async api => {
+ const collectionId = await createCollectionExpectSuccess();
+ const alice = privateKey('//Alice');
+ const bob = privateKey('//Bob');
+
+ const changeOwnerTx = api.tx.nft.changeCollectionOwner(collectionId, bob.address);
+ await expect(submitTransactionExpectFailAsync(bob, changeOwnerTx)).to.be.rejected;
+
+ const collectionAfterOwnerChange: any = (await api.query.nft.collection(collectionId));
+ expect(collectionAfterOwnerChange.Owner.toString()).to.be.eq(alice.address);
+
+ // Verifying that nothing bad happened (network is live, new collections can be created, etc.)
+ await createCollectionExpectSuccess();
+ });
+ });
+ it(`Can't change owner of not existing collection.`, async () => {
+ await usingApi(async api => {
+ const collectionId = (1<<32) - 1;
+ const alice = privateKey('//Alice');
+ const bob = privateKey('//Bob');
+
+ const changeOwnerTx = api.tx.nft.changeCollectionOwner(collectionId, bob.address);
+ await expect(submitTransactionExpectFailAsync(alice, changeOwnerTx)).to.be.rejected;
+
+ // Verifying that nothing bad happened (network is live, new collections can be created, etc.)
+ await createCollectionExpectSuccess();
+ });
+ });
+});
tests/src/confirmSponsorship.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/confirmSponsorship.test.ts
@@ -0,0 +1,338 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from "./substrate/substrate-api";
+import {
+ createCollectionExpectSuccess,
+ setCollectionSponsorExpectSuccess,
+ destroyCollectionExpectSuccess,
+ setCollectionSponsorExpectFailure,
+ confirmSponsorshipExpectSuccess,
+ confirmSponsorshipExpectFailure,
+ createItemExpectSuccess,
+ findUnusedAddress,
+ getGenericResult,
+ enableWhiteListExpectSuccess,
+ enablePublicMintingExpectSuccess,
+ addToWhiteListExpectSuccess,
+} from "./util/helpers";
+import { Keyring } from "@polkadot/api";
+import { IKeyringPair } from "@polkadot/types/types";
+import type { AccountId } from '@polkadot/types/interfaces';
+import { BigNumber } from 'bignumber.js';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+let alice: IKeyringPair;
+let bob: IKeyringPair;
+let charlie: IKeyringPair;
+
+describe('integration test: ext. confirmSponsorship():', () => {
+
+ before(async () => {
+ await usingApi(async (api) => {
+ const keyring = new Keyring({ type: 'sr25519' });
+ alice = keyring.addFromUri(`//Alice`);
+ bob = keyring.addFromUri(`//Bob`);
+ charlie = keyring.addFromUri(`//Charlie`);
+ });
+ });
+
+ it('Confirm collection sponsorship', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+ });
+ it('Add sponsor to a collection after the same sponsor was already added and confirmed', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ });
+ it('Add new sponsor to a collection after another sponsor was already added and confirmed', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+ await setCollectionSponsorExpectSuccess(collectionId, charlie.address);
+ });
+
+ it('NFT: Transfer fees are paid by the sponsor after confirmation', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+ await usingApi(async (api) => {
+ const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+ // Find unused address
+ const zeroBalance = await findUnusedAddress(api);
+
+ // Mint token for unused address
+ const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', zeroBalance.address);
+
+ // Transfer this tokens from unused address to Alice
+ const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 0);
+ const events = await submitTransactionAsync(zeroBalance, zeroToAlice);
+ const result = getGenericResult(events);
+
+ const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+ expect(result.success).to.be.true;
+ expect(BsponsorBalance.lt(AsponsorBalance)).to.be.true;
+ });
+
+ });
+
+ it('Fungible: Transfer fees are paid by the sponsor after confirmation', async () => {
+ const collectionId = await createCollectionExpectSuccess({mode: 'Fungible'});
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+ await usingApi(async (api) => {
+ const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+ // Find unused address
+ const zeroBalance = await findUnusedAddress(api);
+
+ // Mint token for unused address
+ const itemId = await createItemExpectSuccess(alice, collectionId, 'Fungible', zeroBalance.address);
+
+ // Transfer this tokens from unused address to Alice
+ const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);
+ const events1 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+ const result1 = getGenericResult(events1);
+
+ const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+ expect(result1.success).to.be.true;
+ expect(BsponsorBalance.lt(AsponsorBalance)).to.be.true;
+ });
+ });
+
+ it('ReFungible: Transfer fees are paid by the sponsor after confirmation', async () => {
+ const collectionId = await createCollectionExpectSuccess({mode: 'ReFungible'});
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+ await usingApi(async (api) => {
+ const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+ // Find unused address
+ const zeroBalance = await findUnusedAddress(api);
+
+ // Mint token for unused address
+ const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', zeroBalance.address);
+
+ // Transfer this tokens from unused address to Alice
+ const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);
+ const events1 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+ const result1 = getGenericResult(events1);
+
+ const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+ expect(result1.success).to.be.true;
+ expect(BsponsorBalance.lt(AsponsorBalance)).to.be.true;
+ });
+ });
+
+ it('CreateItem fees are paid by the sponsor after confirmation', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+ // Enable collection white list
+ await enableWhiteListExpectSuccess(alice, collectionId);
+
+ // Enable public minting
+ await enablePublicMintingExpectSuccess(alice, collectionId);
+
+ // Create Item
+ await usingApi(async (api) => {
+ const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+ // Find unused address
+ const zeroBalance = await findUnusedAddress(api);
+
+ // Add zeroBalance address to white list
+ await addToWhiteListExpectSuccess(alice, collectionId, zeroBalance.address);
+
+ // Mint token using unused address as signer
+ const tokenId = await createItemExpectSuccess(zeroBalance, collectionId, 'NFT', zeroBalance.address);
+
+ const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+ expect(BsponsorBalance.lt(AsponsorBalance)).to.be.true;
+ });
+ });
+
+ it('NFT: Sponsoring is rate limited', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+ await usingApi(async (api) => {
+ // Find unused address
+ const zeroBalance = await findUnusedAddress(api);
+
+ // Mint token for alice
+ const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);
+
+ // Transfer this token from Alice to unused address and back
+ // Alice to Zero gets sponsored
+ const aliceToZero = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 0);
+ const events1 = await submitTransactionAsync(alice, aliceToZero);
+ const result1 = getGenericResult(events1);
+
+ // Second transfer should fail
+ const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+ const zeroToAlice = api.tx.nft.transfer(alice.address, collectionId, itemId, 0);
+ const badTransaction = async function () {
+ await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);
+ };
+ await expect(badTransaction()).to.be.rejectedWith("Inability to pay some fees");
+ const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+ // Try again after Zero gets some balance - now it should succeed
+ const balancetx = api.tx.balances.transfer(zeroBalance.address, 1e15);
+ await submitTransactionAsync(alice, balancetx);
+ const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+ const result2 = getGenericResult(events2);
+
+ expect(result1.success).to.be.true;
+ expect(result2.success).to.be.true;
+ expect(BsponsorBalance.isEqualTo(AsponsorBalance)).to.be.true;
+ });
+ });
+
+ it('Fungible: Sponsoring is rate limited', async () => {
+ const collectionId = await createCollectionExpectSuccess({mode: 'Fungible'});
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+ await usingApi(async (api) => {
+ // Find unused address
+ const zeroBalance = await findUnusedAddress(api);
+
+ // Mint token for unused address
+ const itemId = await createItemExpectSuccess(alice, collectionId, 'Fungible', zeroBalance.address);
+
+ // Transfer this tokens in parts from unused address to Alice
+ const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);
+ const events1 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+ const result1 = getGenericResult(events1);
+
+ const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+ const badTransaction = async function () {
+ await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);
+ };
+
+ const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+ // Try again after Zero gets some balance - now it should succeed
+ const balancetx = api.tx.balances.transfer(zeroBalance.address, 1e15);
+ await submitTransactionAsync(alice, balancetx);
+ const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+ const result2 = getGenericResult(events2);
+
+ expect(result1.success).to.be.true;
+ expect(result2.success).to.be.true;
+ expect(BsponsorBalance.isEqualTo(AsponsorBalance)).to.be.true;
+ });
+ });
+
+ it('ReFungible: Sponsoring is rate limited', async () => {
+ const collectionId = await createCollectionExpectSuccess({mode: 'ReFungible'});
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+ await usingApi(async (api) => {
+ // Find unused address
+ const zeroBalance = await findUnusedAddress(api);
+
+ // Mint token for alice
+ const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', alice.address);
+
+ // Transfer this token from Alice to unused address and back
+ // Alice to Zero gets sponsored
+ const aliceToZero = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);
+ const events1 = await submitTransactionAsync(alice, aliceToZero);
+ const result1 = getGenericResult(events1);
+
+ // Second transfer should fail
+ const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+ const zeroToAlice = api.tx.nft.transfer(alice.address, collectionId, itemId, 1);
+ const badTransaction = async function () {
+ await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);
+ };
+ await expect(badTransaction()).to.be.rejectedWith("Inability to pay some fees");
+ const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+ // Try again after Zero gets some balance - now it should succeed
+ const balancetx = api.tx.balances.transfer(zeroBalance.address, 1e15);
+ await submitTransactionAsync(alice, balancetx);
+ const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+ const result2 = getGenericResult(events2);
+
+ expect(result1.success).to.be.true;
+ expect(result2.success).to.be.true;
+ expect(BsponsorBalance.isEqualTo(AsponsorBalance)).to.be.true;
+ });
+ });
+
+});
+
+describe('(!negative test!) integration test: ext. removeCollectionSponsor():', () => {
+ before(async () => {
+ await usingApi(async (api) => {
+ const keyring = new Keyring({ type: 'sr25519' });
+ alice = keyring.addFromUri(`//Alice`);
+ bob = keyring.addFromUri(`//Bob`);
+ charlie = keyring.addFromUri(`//Charlie`);
+ });
+ });
+
+ it('(!negative test!) Confirm sponsorship for a collection that never existed', async () => {
+ // Find the collection that never existed
+ const collectionId = 0;
+ await usingApi(async (api) => {
+ const collectionId = parseInt((await api.query.nft.createdCollectionCount()).toString()) + 1;
+ });
+
+ await confirmSponsorshipExpectFailure(collectionId, '//Bob');
+ });
+
+ it('(!negative test!) Confirm sponsorship using a non-sponsor address', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+
+ await usingApi(async (api) => {
+ const transfer = api.tx.balances.transfer(charlie.address, 1e15);
+ await submitTransactionAsync(alice, transfer);
+ });
+
+ await confirmSponsorshipExpectFailure(collectionId, '//Charlie');
+ });
+
+ it('(!negative test!) Confirm sponsorship using owner address', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await confirmSponsorshipExpectFailure(collectionId, '//Alice');
+ });
+
+ it('(!negative test!) Confirm sponsorship without sponsor being set with setCollectionSponsor', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await confirmSponsorshipExpectFailure(collectionId, '//Bob');
+ });
+
+ it('(!negative test!) Confirm sponsorship in a collection that was destroyed', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await destroyCollectionExpectSuccess(collectionId);
+ await confirmSponsorshipExpectFailure(collectionId, '//Bob');
+ });
+});
tests/src/connection.test.tsdiffbeforeafterboth--- a/tests/src/connection.test.ts
+++ b/tests/src/connection.test.ts
@@ -21,6 +21,8 @@
});
it('Cannot connect to 255.255.255.255', async () => {
+ const log = console.log;
+ const error = console.error;
console.log = function () {};
console.error = function () {};
@@ -31,7 +33,7 @@
}, { provider: neverConnectProvider });
})()).to.be.eventually.rejected;
- delete console.log;
- delete console.error;
+ console.log = log;
+ console.error = error;
});
});
\ No newline at end of file
tests/src/contracts.test.tsdiffbeforeafterboth--- a/tests/src/contracts.test.ts
+++ b/tests/src/contracts.test.ts
@@ -109,9 +109,6 @@
const bob = privateKey("//Bob");
const [contract, deployer] = await deployFlipper(api);
- const consoleError = console.error;
- console.error = (...data: any[]) => {
- };
let expectedFlipValue = await getFlipValue(contract, deployer);
@@ -166,7 +163,6 @@
const afterWhiteListDisabled = await getFlipValue(contract,deployer);
expect(afterWhiteListDisabled).to.be.eq(expectedFlipValue, `Anyone can call contract with disabled whitelist.`);
- console.error = consoleError;
});
});
tests/src/createCollection.test.tsdiffbeforeafterboth--- a/tests/src/createCollection.test.ts
+++ b/tests/src/createCollection.test.ts
@@ -6,37 +6,29 @@
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import { default as usingApi } from "./substrate/substrate-api";
-import { createCollectionExpectSuccess, createCollectionExpectFailure } from "./util/helpers";
+import { createCollectionExpectSuccess, createCollectionExpectFailure, CollectionMode } from "./util/helpers";
chai.use(chaiAsPromised);
const expect = chai.expect;
describe('integration test: ext. createCollection():', () => {
it('Create new NFT collection', async () => {
- await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+ await createCollectionExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: 'NFT'});
});
it('Create new NFT collection whith collection_name of maximum length (64 bytes)', async () => {
- await createCollectionExpectSuccess(
- 'ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCD',
- '1', '1', 'NFT');
+ await createCollectionExpectSuccess({name: 'A'.repeat(64)});
});
it('Create new NFT collection whith collection_description of maximum length (256 bytes)', async () => {
- await createCollectionExpectSuccess(
- 'A',
- 'ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJabcdef',
- '1', 'NFT');
+ await createCollectionExpectSuccess({description: 'A'.repeat(256)});
});
it('Create new NFT collection whith token_prefix of maximum length (16 bytes)', async () => {
- await createCollectionExpectSuccess(
- '1',
- '1',
- 'ABCDEFGHIJABCDEF', 'NFT');
+ await createCollectionExpectSuccess({tokenPrefix: 'A'.repeat(16)});
});
it('Create new Fungible collection', async () => {
- await createCollectionExpectSuccess('1', '1', '1', 'Fungible');
+ await createCollectionExpectSuccess({mode: 'Fungible'});
});
it('Create new ReFungible collection', async () => {
- await createCollectionExpectSuccess('1', '1', '1', 'ReFungible');
+ await createCollectionExpectSuccess({mode: 'ReFungible'});
});
});
@@ -46,7 +38,7 @@
const AcollectionCount = parseInt((await api.query.nft.collectionCount()).toString());
const badTransaction = async function () {
- await createCollectionExpectSuccess('1', '1', '1', 'BadMode');
+ await createCollectionExpectSuccess({mode: 'BadMode' as CollectionMode});
};
expect(badTransaction()).to.be.rejected;
@@ -55,18 +47,12 @@
});
});
it('(!negative test!) create new NFT collection whith incorrect data (collection_name)', async () => {
- await createCollectionExpectFailure(
- 'ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDE',
- '1', '1', 'NFT');
+ await createCollectionExpectFailure({name: 'A'.repeat(65)});
});
it('(!negative test!) create new NFT collection whith incorrect data (collection_description)', async () => {
- await createCollectionExpectFailure('1',
- 'ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJabcdefg',
- '1', 'NFT');
+ await createCollectionExpectFailure({description: 'A'.repeat(257)});
});
it('(!negative test!) create new NFT collection whith incorrect data (token_prefix)', async () => {
- await createCollectionExpectFailure('1', '1',
- 'ABCDEFGHIJABCDEFG',
- 'NFT');
+ await createCollectionExpectFailure({tokenPrefix: 'A'.repeat(17)});
});
});
tests/src/createItem.test.tsdiffbeforeafterboth--- a/tests/src/createItem.test.ts
+++ b/tests/src/createItem.test.ts
@@ -1,27 +1,34 @@
-import { assert } from 'chai';
-import { alicesPublicKey } from './accounts';
-import privateKey from './substrate/privateKey';
import { default as usingApi } from './substrate/substrate-api';
-import waitNewBlocks from './substrate/wait-new-blocks';
+import { Keyring } from "@polkadot/api";
+import { IKeyringPair } from "@polkadot/types/types";
import {
createCollectionExpectSuccess,
createItemExpectSuccess
} from './util/helpers';
+let alice: IKeyringPair;
+
describe('integration test: ext. createItem():', () => {
+ before(async () => {
+ await usingApi(async (api) => {
+ const keyring = new Keyring({ type: 'sr25519' });
+ alice = keyring.addFromUri(`//Alice`);
+ });
+ });
+
it('Create new item in NFT collection', async () => {
const createMode = 'NFT';
- const newCollectionID = await createCollectionExpectSuccess('0', '0', '0', createMode);
- await createItemExpectSuccess(newCollectionID, createMode, '//Alice');
+ const newCollectionID = await createCollectionExpectSuccess({mode: createMode});
+ await createItemExpectSuccess(alice, newCollectionID, createMode);
});
it('Create new item in Fungible collection', async () => {
const createMode = 'Fungible';
- const newCollectionID = await createCollectionExpectSuccess('0', '0', '0', createMode);
- await createItemExpectSuccess(newCollectionID, createMode, '//Alice');
+ const newCollectionID = await createCollectionExpectSuccess({mode: createMode});
+ await createItemExpectSuccess(alice, newCollectionID, createMode);
});
it('Create new item in ReFungible collection', async () => {
const createMode = 'ReFungible';
- const newCollectionID = await createCollectionExpectSuccess('0', '0', '0', createMode);
- await createItemExpectSuccess(newCollectionID, createMode, '//Alice');
+ const newCollectionID = await createCollectionExpectSuccess({mode: createMode});
+ await createItemExpectSuccess(alice, newCollectionID, createMode);
});
});
tests/src/createMultipleItems.test.tsdiffbeforeafterboth--- a/tests/src/createMultipleItems.test.ts
+++ b/tests/src/createMultipleItems.test.ts
@@ -11,7 +11,7 @@
const idCollection = 12;
-describe('integration test: ext. createMultipleItems():', () => {
+describe.skip('integration test: ext. createMultipleItems():', () => {
it('Create two NFT tokens in active NFT collection', async () => {
await usingApi(async (api) => {
const AitemListIndex = await api.query.nft.itemListIndex(idCollection);
tests/src/creditFeesToTreasury.test.tsdiffbeforeafterboth--- a/tests/src/creditFeesToTreasury.test.ts
+++ b/tests/src/creditFeesToTreasury.test.ts
@@ -5,7 +5,7 @@
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
-import { default as usingApi, submitTransactionAsync } from "./substrate/substrate-api";
+import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from "./substrate/substrate-api";
import { alicesPublicKey, bobsPublicKey } from "./accounts";
import privateKey from "./substrate/privateKey";
import { BigNumber } from 'bignumber.js';
@@ -63,14 +63,13 @@
const bobBalanceBefore = new BigNumber((await api.query.system.account(bobsPublicKey)).data.free.toString());
const badTx = api.tx.balances.setBalance(alicesPublicKey, 0, 0);
- const result = getGenericResult(await submitTransactionAsync(bobPrivateKey, badTx));
+ await expect(submitTransactionExpectFailAsync(bobPrivateKey, badTx)).to.be.rejected;
const treasuryBalanceAfter = new BigNumber((await api.query.system.account(Treasury)).data.free.toString());
const bobBalanceAfter = new BigNumber((await api.query.system.account(bobsPublicKey)).data.free.toString());
const fee = bobBalanceBefore.minus(bobBalanceAfter);
const treasuryIncrease = treasuryBalanceAfter.minus(treasuryBalanceBefore);
- expect(result.success).to.be.false;
expect(treasuryIncrease.toFixed()).to.be.equal(fee.toFixed());
});
});
@@ -80,7 +79,7 @@
const treasuryBalanceBefore = new BigNumber((await api.query.system.account(Treasury)).data.free.toString());
const aliceBalanceBefore = new BigNumber((await api.query.system.account(alicesPublicKey)).data.free.toString());
- await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+ await createCollectionExpectSuccess();
const treasuryBalanceAfter = new BigNumber((await api.query.system.account(Treasury)).data.free.toString());
const aliceBalanceAfter = new BigNumber((await api.query.system.account(alicesPublicKey)).data.free.toString());
@@ -96,7 +95,7 @@
const treasuryBalanceBefore = new BigNumber((await api.query.system.account(Treasury)).data.free.toString());
const aliceBalanceBefore = new BigNumber((await api.query.system.account(alicesPublicKey)).data.free.toString());
- await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+ await createCollectionExpectSuccess();
const treasuryBalanceAfter = new BigNumber((await api.query.system.account(Treasury)).data.free.toString());
const aliceBalanceAfter = new BigNumber((await api.query.system.account(alicesPublicKey)).data.free.toString());
tests/src/destroyCollection.test.tsdiffbeforeafterboth--- a/tests/src/destroyCollection.test.ts
+++ b/tests/src/destroyCollection.test.ts
@@ -1,67 +1,24 @@
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import { default as usingApi, submitTransactionAsync } from "./substrate/substrate-api";
-import { createCollectionExpectSuccess, createCollectionExpectFailure } from "./util/helpers";
+import { createCollectionExpectSuccess, createCollectionExpectFailure, destroyCollectionExpectSuccess, destroyCollectionExpectFailure } from "./util/helpers";
import type { AccountId, EventRecord } from '@polkadot/types/interfaces';
import privateKey from './substrate/privateKey';
-import { nullPublicKey } from './accounts';
chai.use(chaiAsPromised);
const expect = chai.expect;
-
-function getDestroyResult(events: EventRecord[]): boolean {
- let success: boolean = false;
- events.forEach(({ phase, event: { data, method, section } }) => {
- // console.log(` ${phase}: ${section}.${method}:: ${data}`);
- if (method == 'ExtrinsicSuccess') {
- success = true;
- }
- });
- return success;
-}
-async function destroyCollectionExpectSuccess(collectionId: number, senderSeed: string = '//Alice') {
- await usingApi(async (api) => {
- // Run the DestroyCollection transaction
- const alicePrivateKey = privateKey(senderSeed);
- const tx = api.tx.nft.destroyCollection(collectionId);
- const events = await submitTransactionAsync(alicePrivateKey, tx);
- const result = getDestroyResult(events);
-
- // Get the collection
- const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
-
- // What to expect
- expect(result).to.be.true;
- expect(collection).to.be.not.null;
- expect(collection.Owner).to.be.equal(nullPublicKey);
- });
-}
-
-async function destroyCollectionExpectFailure(collectionId: number, senderSeed: string = '//Alice') {
- await usingApi(async (api) => {
- // Run the DestroyCollection transaction
- const alicePrivateKey = privateKey(senderSeed);
- const tx = api.tx.nft.destroyCollection(collectionId);
- const events = await submitTransactionAsync(alicePrivateKey, tx);
- const result = getDestroyResult(events);
-
- // What to expect
- expect(result).to.be.false;
- });
-}
-
describe('integration test: ext. destroyCollection():', () => {
it('NFT collection can be destroyed', async () => {
- const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+ const collectionId = await createCollectionExpectSuccess();
await destroyCollectionExpectSuccess(collectionId);
});
it('Fungible collection can be destroyed', async () => {
- const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'Fungible');
+ const collectionId = await createCollectionExpectSuccess({ mode: 'Fungible' });
await destroyCollectionExpectSuccess(collectionId);
});
it('ReFungible collection can be destroyed', async () => {
- const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'ReFungible');
+ const collectionId = await createCollectionExpectSuccess({ mode: 'ReFungible' });
await destroyCollectionExpectSuccess(collectionId);
});
});
@@ -75,12 +32,12 @@
});
});
it('(!negative test!) Destroy a collection that has already been destroyed', async () => {
- const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+ const collectionId = await createCollectionExpectSuccess();
await destroyCollectionExpectSuccess(collectionId);
await destroyCollectionExpectFailure(collectionId);
});
it('(!negative test!) Destroy a collection using non-owner account', async () => {
- const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+ const collectionId = await createCollectionExpectSuccess();
await destroyCollectionExpectFailure(collectionId, '//Bob');
await destroyCollectionExpectSuccess(collectionId, '//Alice');
});
tests/src/load_test_sc/loadtester.wasmdiffbeforeafterbothbinary blob — no preview
tests/src/load_test_sc/metadata.jsondiffbeforeafterboth--- /dev/null
+++ b/tests/src/load_test_sc/metadata.json
@@ -0,0 +1,125 @@
+{
+ "metadataVersion": "0.1.0",
+ "source": {
+ "hash": "0x168cc3cba9657ad3950fb506e568751f99b90fb097685107f6101675662a8303",
+ "language": "ink! 3.0.0-rc2",
+ "compiler": "rustc 1.49.0-nightly"
+ },
+ "contract": {
+ "name": "loadtester",
+ "version": "0.1.0",
+ "authors": [
+ "[your_name] <[your_email]>"
+ ]
+ },
+ "spec": {
+ "constructors": [
+ {
+ "args": [],
+ "docs": [],
+ "name": [
+ "new"
+ ],
+ "selector": "0xd183512b"
+ }
+ ],
+ "docs": [],
+ "events": [],
+ "messages": [
+ {
+ "args": [
+ {
+ "name": "count",
+ "type": {
+ "displayName": [
+ "u64"
+ ],
+ "type": 2
+ }
+ }
+ ],
+ "docs": [],
+ "mutates": true,
+ "name": [
+ "bloat"
+ ],
+ "payable": false,
+ "returnType": null,
+ "selector": "0x49891c2a"
+ },
+ {
+ "args": [],
+ "docs": [],
+ "mutates": false,
+ "name": [
+ "get"
+ ],
+ "payable": false,
+ "returnType": {
+ "displayName": [
+ "u128"
+ ],
+ "type": 3
+ },
+ "selector": "0x1e5ca456"
+ }
+ ]
+ },
+ "storage": {
+ "struct": {
+ "fields": [
+ {
+ "layout": {
+ "struct": {
+ "fields": [
+ {
+ "layout": {
+ "cell": {
+ "key": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "ty": 1
+ }
+ },
+ "name": "len"
+ },
+ {
+ "layout": {
+ "array": {
+ "cellsPerElem": 1,
+ "layout": {
+ "cell": {
+ "key": "0x0000000001000000000000000000000000000000000000000000000000000000",
+ "ty": 2
+ }
+ },
+ "len": 4294967295,
+ "offset": "0x0100000000000000000000000000000000000000000000000000000000000000"
+ }
+ },
+ "name": "elems"
+ }
+ ]
+ }
+ },
+ "name": "vector"
+ }
+ ]
+ }
+ },
+ "types": [
+ {
+ "def": {
+ "primitive": "u32"
+ }
+ },
+ {
+ "def": {
+ "primitive": "u64"
+ }
+ },
+ {
+ "def": {
+ "primitive": "u128"
+ }
+ }
+ ]
+}
\ No newline at end of file
tests/src/removeCollectionSponsor.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/removeCollectionSponsor.test.ts
@@ -0,0 +1,137 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from "./substrate/substrate-api";
+import {
+ createCollectionExpectSuccess,
+ setCollectionSponsorExpectSuccess,
+ destroyCollectionExpectSuccess,
+ setCollectionSponsorExpectFailure,
+ confirmSponsorshipExpectSuccess,
+ confirmSponsorshipExpectFailure,
+ createItemExpectSuccess,
+ findUnusedAddress,
+ getGenericResult,
+ enableWhiteListExpectSuccess,
+ enablePublicMintingExpectSuccess,
+ addToWhiteListExpectSuccess,
+ removeCollectionSponsorExpectSuccess,
+ removeCollectionSponsorExpectFailure,
+} from "./util/helpers";
+import { Keyring } from "@polkadot/api";
+import { IKeyringPair } from "@polkadot/types/types";
+import type { AccountId } from '@polkadot/types/interfaces';
+import { BigNumber } from 'bignumber.js';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+let alice: IKeyringPair;
+let bob: IKeyringPair;
+let charlie: IKeyringPair;
+
+describe('integration test: ext. removeCollectionSponsor():', () => {
+
+ before(async () => {
+ await usingApi(async (api) => {
+ const keyring = new Keyring({ type: 'sr25519' });
+ alice = keyring.addFromUri(`//Alice`);
+ bob = keyring.addFromUri(`//Bob`);
+ charlie = keyring.addFromUri(`//Charlie`);
+ });
+ });
+
+ it('Remove NFT collection sponsor stops sponsorship', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+ await removeCollectionSponsorExpectSuccess(collectionId);
+
+ await usingApi(async (api) => {
+ // Find unused address
+ const zeroBalance = await findUnusedAddress(api);
+
+ // Mint token for unused address
+ const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', zeroBalance.address);
+
+ // Transfer this tokens from unused address to Alice - should fail
+ const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+ const zeroToAlice = api.tx.nft.transfer(alice.address, collectionId, itemId, 0);
+ const badTransaction = async function () {
+ await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);
+ };
+ await expect(badTransaction()).to.be.rejectedWith("Inability to pay some fees");
+ const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+ expect(BsponsorBalance.isEqualTo(AsponsorBalance)).to.be.true;
+ });
+ });
+
+ it('Remove a sponsor after it was already removed', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+ await removeCollectionSponsorExpectSuccess(collectionId);
+ await removeCollectionSponsorExpectSuccess(collectionId);
+ });
+
+ it('Remove sponsor in a collection that never had the sponsor set', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await removeCollectionSponsorExpectSuccess(collectionId);
+ });
+
+ it('Remove sponsor for a collection that had the sponsor set, but not confirmed', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await removeCollectionSponsorExpectSuccess(collectionId);
+ });
+
+});
+
+describe('(!negative test!) integration test: ext. removeCollectionSponsor():', () => {
+ before(async () => {
+ await usingApi(async (api) => {
+ const keyring = new Keyring({ type: 'sr25519' });
+ alice = keyring.addFromUri(`//Alice`);
+ bob = keyring.addFromUri(`//Bob`);
+ charlie = keyring.addFromUri(`//Charlie`);
+ });
+ });
+
+ it('(!negative test!) Remove sponsor for a collection that never existed', async () => {
+ // Find the collection that never existed
+ const collectionId = 0;
+ await usingApi(async (api) => {
+ const collectionId = parseInt((await api.query.nft.createdCollectionCount()).toString()) + 1;
+ });
+
+ await removeCollectionSponsorExpectFailure(collectionId);
+ });
+
+ it('(!negative test!) Remove sponsor in a destroyed collection', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await destroyCollectionExpectSuccess(collectionId);
+ await removeCollectionSponsorExpectFailure(collectionId);
+ });
+
+ it('Set - remove - confirm: fails', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await removeCollectionSponsorExpectSuccess(collectionId);
+ await confirmSponsorshipExpectFailure(collectionId, '//Bob');
+ });
+
+ it('Set - confirm - remove - confirm: Sponsor cannot come back', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+ await removeCollectionSponsorExpectSuccess(collectionId);
+ await confirmSponsorshipExpectFailure(collectionId, '//Bob');
+ });
+
+});
tests/src/rpc.load.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/rpc.load.ts
@@ -0,0 +1,145 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import { expect, assert } from "chai";
+import usingApi, { submitTransactionAsync, submitTransactionExpectFailAsync } from "./substrate/substrate-api";
+import { IKeyringPair } from "@polkadot/types/types";
+import { Abi, BlueprintPromise as Blueprint, CodePromise, ContractPromise as Contract } from "@polkadot/api-contract";
+import { ApiPromise, Keyring } from "@polkadot/api";
+import { ApiTypes, SubmittableExtrinsic } from "@polkadot/api/types";
+import { BigNumber } from 'bignumber.js';
+import { findUnusedAddress } from './util/helpers'
+import fs from "fs";
+import privateKey from "./substrate/privateKey";
+
+const value = 0;
+const gasLimit = 500000n * 1000000n;
+const endowment = `1000000000000000`;
+
+
+function deployBlueprint(alice: IKeyringPair, code: CodePromise): Promise<Blueprint> {
+ return new Promise<Blueprint>(async (resolve, reject) => {
+ const unsub = await code
+ .createBlueprint()
+ .signAndSend(alice, (result) => {
+ if (result.status.isInBlock || result.status.isFinalized) {
+ // here we have an additional field in the result, containing the blueprint
+ resolve(result.blueprint);
+ unsub();
+ }
+ })
+ });
+}
+
+function deployContract(alice: IKeyringPair, blueprint: Blueprint) : Promise<any> {
+ return new Promise<any>(async (resolve, reject) => {
+ const unsub = await blueprint.tx
+ .new(endowment, gasLimit)
+ .signAndSend(alice, (result) => {
+ if (result.status.isInBlock || result.status.isFinalized) {
+ unsub();
+ resolve(result);
+ }
+ });
+ });
+}
+
+async function prepareDeployer(api: ApiPromise) {
+ // Find unused address
+ const deployer = await findUnusedAddress(api);
+
+ // Transfer balance to it
+ const keyring = new Keyring({ type: 'sr25519' });
+ const alice = keyring.addFromUri(`//Alice`);
+ let amount = new BigNumber(endowment);
+ amount = amount.plus(1e15);
+ const tx = api.tx.balances.transfer(deployer.address, amount.toFixed());
+ await submitTransactionAsync(alice, tx);
+
+ return deployer;
+}
+
+async function deployLoadTester(api: ApiPromise): Promise<[Contract, IKeyringPair]> {
+ const metadata = JSON.parse(fs.readFileSync('./src/load_test_sc/metadata.json').toString('utf-8'));
+ const abi = new Abi(metadata);
+
+ const deployer = await prepareDeployer(api);
+
+ const wasm = fs.readFileSync('./src/load_test_sc/loadtester.wasm');
+
+ const code = new CodePromise(api, abi, wasm);
+
+ const blueprint = await deployBlueprint(deployer, code);
+ const contract = (await deployContract(deployer, blueprint))['contract'] as Contract;
+
+ return [contract, deployer];
+}
+
+async function getScData(contract: Contract, deployer: IKeyringPair) {
+ const result = await contract.query.get(deployer.address, value, gasLimit);
+
+ if(!result.result.isSuccess) {
+ throw `Failed to get value`;
+ }
+ return result.result.asSuccess.data;
+}
+
+
+describe('RPC Tests', () => {
+ it('Simple RPC Load Test', async () => {
+ await usingApi(async api => {
+ let count = 0;
+ let hrTime = process.hrtime();
+ let microsec1 = hrTime[0] * 1000000 + hrTime[1] / 1000;
+ let rate = 0;
+ const checkPoint = 1000;
+ while (true) {
+ await api.rpc.system.chain();
+ count++;
+ process.stdout.write(`RPC reads: ${count} times at rate ${rate} r/s \r`);
+
+ if (count % checkPoint == 0) {
+ hrTime = process.hrtime();
+ let microsec2 = hrTime[0] * 1000000 + hrTime[1] / 1000;
+ rate = 1000000*checkPoint/(microsec2 - microsec1);
+ microsec1 = microsec2;
+ }
+ }
+ });
+ });
+
+ it.only('Smart Contract RPC Load Test', async () => {
+ await usingApi(async api => {
+
+ // Deploy smart contract
+ const [contract, deployer] = await deployLoadTester(api);
+
+ // Fill smart contract up with data
+ const bob = privateKey("//Bob");
+ const tx = contract.tx.bloat(value, gasLimit, 200);
+ await submitTransactionAsync(bob, tx);
+
+ // Run load test
+ let count = 0;
+ let hrTime = process.hrtime();
+ let microsec1 = hrTime[0] * 1000000 + hrTime[1] / 1000;
+ let rate = 0;
+ const checkPoint = 10;
+ while (true) {
+ await getScData(contract, deployer);
+ count++;
+ process.stdout.write(`SC reads: ${count} times at rate ${rate} r/s \r`);
+
+ if (count % checkPoint == 0) {
+ hrTime = process.hrtime();
+ let microsec2 = hrTime[0] * 1000000 + hrTime[1] / 1000;
+ rate = 1000000*checkPoint/(microsec2 - microsec1);
+ microsec1 = microsec2;
+ }
+ }
+ });
+ });
+
+});
tests/src/setCollectionSponsor.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/setCollectionSponsor.test.ts
@@ -0,0 +1,82 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import { default as usingApi } from "./substrate/substrate-api";
+import { createCollectionExpectSuccess, setCollectionSponsorExpectSuccess, destroyCollectionExpectSuccess, setCollectionSponsorExpectFailure } from "./util/helpers";
+import { Keyring } from "@polkadot/api";
+import { IKeyringPair } from "@polkadot/types/types";
+import type { AccountId } from '@polkadot/types/interfaces';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+let bob: IKeyringPair;
+
+describe('integration test: ext. setCollectionSponsor():', () => {
+
+ before(async () => {
+ await usingApi(async (api) => {
+ const keyring = new Keyring({ type: 'sr25519' });
+ bob = keyring.addFromUri(`//Bob`);
+ });
+ });
+
+ it('Set NFT collection sponsor', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ });
+ it('Set Fungible collection sponsor', async () => {
+ const collectionId = await createCollectionExpectSuccess({ mode: 'Fungible' });
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ });
+ it('Set ReFungible collection sponsor', async () => {
+ const collectionId = await createCollectionExpectSuccess({ mode: 'ReFungible' });
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ });
+
+ it('Set the same sponsor repeatedly', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ });
+ it('Replace collection sponsor', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+
+ const keyring = new Keyring({ type: 'sr25519' });
+ const charlie = keyring.addFromUri(`//Charlie`);
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await setCollectionSponsorExpectSuccess(collectionId, charlie.address);
+ });
+});
+
+describe('(!negative test!) integration test: ext. setCollectionSponsor():', () => {
+ before(async () => {
+ await usingApi(async (api) => {
+ const keyring = new Keyring({ type: 'sr25519' });
+ bob = keyring.addFromUri(`//Bob`);
+ });
+ });
+
+ it('(!negative test!) Add sponsor with a non-owner', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionSponsorExpectFailure(collectionId, bob.address, '//Bob');
+ });
+ it('(!negative test!) Add sponsor to a collection that never existed', async () => {
+ // Find the collection that never existed
+ const collectionId = 0;
+ await usingApi(async (api) => {
+ const collectionId = parseInt((await api.query.nft.createdCollectionCount()).toString()) + 1;
+ });
+
+ await setCollectionSponsorExpectFailure(collectionId, bob.address);
+ });
+ it('(!negative test!) Add sponsor to a collection that was destroyed', async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await destroyCollectionExpectSuccess(collectionId);
+ await setCollectionSponsorExpectFailure(collectionId, bob.address);
+ });
+});
tests/src/substrate/substrate-api.tsdiffbeforeafterboth--- a/tests/src/substrate/substrate-api.ts
+++ b/tests/src/substrate/substrate-api.ts
@@ -33,21 +33,42 @@
}
}
+enum TransactionStatus {
+ Success,
+ Fail,
+ NotReady
+}
+
+function getTransactionStatus(events: EventRecord[], status: ExtrinsicStatus): TransactionStatus {
+ if (status.isReady) {
+ return TransactionStatus.NotReady;
+ }
+ if (status.isBroadcast) {
+ return TransactionStatus.NotReady;
+ }
+ if (status.isInBlock || status.isFinalized) {
+ if(events.filter(e => e.event.data.method === 'ExtrinsicFailed').length > 0) {
+ return TransactionStatus.Fail;
+ }
+ if(events.filter(e => e.event.data.method === 'ExtrinsicSuccess').length > 0) {
+ return TransactionStatus.Success;
+ }
+ }
+
+ return TransactionStatus.Fail;
+}
+
export function submitTransactionAsync(sender: IKeyringPair, transaction: SubmittableExtrinsic<ApiTypes>): Promise<EventRecord[]> {
return new Promise(async function(resolve, reject) {
try {
await transaction.signAndSend(sender, ({ events = [], status }) => {
- if (status.isReady) {
- // nothing to do
- // console.log(`Current tx status is Ready`);
- } else if (status.isBroadcast) {
- // nothing to do
- // console.log(`Current tx status is Broadcast`);
- } else if (status.isInBlock || status.isFinalized) {
+ const transactionStatus = getTransactionStatus(events, status);
+
+ if (transactionStatus == TransactionStatus.Success) {
resolve(events);
- } else {
+ } else if (transactionStatus == TransactionStatus.Fail) {
console.log(`Something went wrong with transaction. Status: ${status}`);
- reject("Transaction failed");
+ reject(events);
}
});
} catch (e) {
@@ -58,19 +79,35 @@
}
export function submitTransactionExpectFailAsync(sender: IKeyringPair, transaction: SubmittableExtrinsic<ApiTypes>): Promise<EventRecord[]> {
- return new Promise(async function(resolve, reject) {
+ const consoleError = console.error;
+ const consoleLog = console.log;
+ console.error = () => {};
+ console.log = () => {};
+
+ return new Promise<EventRecord[]>(async function(res, rej) {
+ const resolve = (rec: EventRecord[]) => {
+ setTimeout(() => {
+ res(rec);
+ console.error = consoleError;
+ console.log = consoleLog;
+
+ });
+ };
+ const reject = (errror: any) => {
+ setTimeout(() => {
+ rej(errror);
+ console.error = consoleError;
+ console.log = consoleLog;
+ });
+ };
try {
await transaction.signAndSend(sender, ({ events = [], status }) => {
- if (status.isReady) {
- // nothing to do
- // console.log(`Current tx status is Ready`);
- } else if (status.isBroadcast) {
- // nothing to do
- // console.log(`Current tx status is Broadcast`);
- } else if (status.isInBlock || status.isFinalized) {
+ const transactionStatus = getTransactionStatus(events, status);
+
+ if (transactionStatus == TransactionStatus.Success) {
resolve(events);
- } else {
- reject("Transaction failed");
+ } else if (transactionStatus == TransactionStatus.Fail) {
+ reject(events);
}
});
} catch (e) {
tests/src/transfer.test.tsdiffbeforeafterboth--- a/tests/src/transfer.test.ts
+++ b/tests/src/transfer.test.ts
@@ -33,6 +33,8 @@
// Find unused address
const pk = await findUnusedAddress(api);
+ const error = console.error;
+ const log = console.log;
console.log = function () {};
console.error = function () {};
@@ -42,8 +44,8 @@
};
await expect(badTransaction()).to.be.rejectedWith("Inability to pay some fees");
- delete console.log;
- delete console.error;
+ console.log = log;
+ console.error = error;
});
});
});
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -7,12 +7,14 @@
import chaiAsPromised from 'chai-as-promised';
import type { AccountId, EventRecord } from '@polkadot/types/interfaces';
import { ApiPromise, Keyring } from "@polkadot/api";
-import { default as usingApi, submitTransactionAsync } from "../substrate/substrate-api";
+import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from "../substrate/substrate-api";
import privateKey from '../substrate/privateKey';
-import { alicesPublicKey } from "../accounts";
+import { alicesPublicKey, nullPublicKey } from "../accounts";
import { strToUTF16, utf16ToStr, hexToStr } from '../util/util';
import { IKeyringPair } from "@polkadot/types/types";
import { BigNumber } from 'bignumber.js';
+import { Struct, Enum } from '@polkadot/types/codec';
+import { u128 } from '@polkadot/types/primitive';
chai.use(chaiAsPromised);
const expect = chai.expect;
@@ -26,6 +28,12 @@
collectionId: number
};
+type CreateItemResult = {
+ success: boolean,
+ collectionId: number,
+ itemId: number
+};
+
export function getGenericResult(events: EventRecord[]): GenericResult {
let result: GenericResult = {
success: false
@@ -57,8 +65,46 @@
return result;
}
-export async function createCollectionExpectSuccess(name: string, description: string, tokenPrefix: string, mode: string): Promise<number> {
+function getCreateItemResult(events: EventRecord[]): CreateItemResult {
+ let success = false;
let collectionId: number = 0;
+ let itemId: number = 0;
+ events.forEach(({ phase, event: { data, method, section } }) => {
+ // console.log(` ${phase}: ${section}.${method}:: ${data}`);
+ if (method == 'ExtrinsicSuccess') {
+ success = true;
+ } else if ((section == 'nft') && (method == 'ItemCreated')) {
+ collectionId = parseInt(data[0].toString());
+ itemId = parseInt(data[1].toString());
+ }
+ });
+ let result: CreateItemResult = {
+ success,
+ collectionId,
+ itemId
+ }
+ return result;
+}
+
+export type CollectionMode = 'NFT' | 'Fungible' | 'ReFungible';
+export type CreateCollectionParams = {
+ mode: CollectionMode,
+ name: string,
+ description: string,
+ tokenPrefix: string
+};
+
+const defaultCreateCollectionParams: CreateCollectionParams = {
+ name: 'name',
+ description: 'description',
+ mode: 'NFT',
+ tokenPrefix: 'prefix'
+}
+
+export async function createCollectionExpectSuccess(params: Partial<CreateCollectionParams> = {}): Promise<number> {
+ const {name, description, mode, tokenPrefix } = {...defaultCreateCollectionParams, ...params};
+
+ let collectionId: number = 0;
await usingApi(async (api) => {
// Get number of collections before the transaction
const AcollectionCount = parseInt((await api.query.nft.createdCollectionCount()).toString());
@@ -91,15 +137,17 @@
return collectionId;
}
-export async function createCollectionExpectFailure(name: string, description: string, tokenPrefix: string, mode: string) {
+export async function createCollectionExpectFailure(params: Partial<CreateCollectionParams> = {}) {
+ const {name, description, mode, tokenPrefix } = {...defaultCreateCollectionParams, ...params};
+
await usingApi(async (api) => {
// Get number of collections before the transaction
const AcollectionCount = parseInt((await api.query.nft.createdCollectionCount()).toString());
// Run the CreateCollection transaction
const alicePrivateKey = privateKey('//Alice');
- const tx = api.tx.nft.createCollection(name, description, tokenPrefix, mode);
- const events = await submitTransactionAsync(alicePrivateKey, tx);
+ const tx = api.tx.nft.createCollection(strToUTF16(name), strToUTF16(description), strToUTF16(tokenPrefix), mode);
+ const events = await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected;
const result = getCreateCollectionResult(events);
// Get number of collections after the transaction
@@ -123,20 +171,231 @@
return unused;
}
-export async function createItemExpectSuccess(collectionId: number, createMode: string, senderSeed: string = '//Alice') {
+function getDestroyResult(events: EventRecord[]): boolean {
+ let success: boolean = false;
+ events.forEach(({ phase, event: { data, method, section } }) => {
+ // console.log(` ${phase}: ${section}.${method}:: ${data}`);
+ if (method == 'ExtrinsicSuccess') {
+ success = true;
+ }
+ });
+ return success;
+}
+
+export async function destroyCollectionExpectFailure(collectionId: number, senderSeed: string = '//Alice') {
+ await usingApi(async (api) => {
+ // Run the DestroyCollection transaction
+ const alicePrivateKey = privateKey(senderSeed);
+ const tx = api.tx.nft.destroyCollection(collectionId);
+ await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected;
+ });
+}
+
+export async function destroyCollectionExpectSuccess(collectionId: number, senderSeed: string = '//Alice') {
+ await usingApi(async (api) => {
+ // Run the DestroyCollection transaction
+ const alicePrivateKey = privateKey(senderSeed);
+ const tx = api.tx.nft.destroyCollection(collectionId);
+ const events = await submitTransactionAsync(alicePrivateKey, tx);
+ const result = getDestroyResult(events);
+
+ // Get the collection
+ const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+ // What to expect
+ expect(result).to.be.true;
+ expect(collection).to.be.not.null;
+ expect(collection.Owner).to.be.equal(nullPublicKey);
+ });
+}
+
+export async function setCollectionSponsorExpectSuccess(collectionId: number, sponsor: string) {
+ await usingApi(async (api) => {
+
+ // Run the transaction
+ const alicePrivateKey = privateKey('//Alice');
+ const tx = api.tx.nft.setCollectionSponsor(collectionId, sponsor);
+ const events = await submitTransactionAsync(alicePrivateKey, tx);
+ const result = getGenericResult(events);
+
+ // Get the collection
+ const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+ // What to expect
+ expect(result.success).to.be.true;
+ expect(collection.Sponsor.toString()).to.be.equal(sponsor.toString());
+ expect(collection.SponsorConfirmed).to.be.false;
+ });
+}
+
+export async function removeCollectionSponsorExpectSuccess(collectionId: number) {
await usingApi(async (api) => {
- const AItemCount = parseInt((await api.query.nft.itemListIndex(collectionId)).toString());
+ // Run the transaction
+ const alicePrivateKey = privateKey('//Alice');
+ const tx = api.tx.nft.removeCollectionSponsor(collectionId);
+ const events = await submitTransactionAsync(alicePrivateKey, tx);
+ const result = getGenericResult(events);
+ // Get the collection
+ const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+ // What to expect
+ expect(result.success).to.be.true;
+ expect(collection.Sponsor).to.be.equal(nullPublicKey);
+ expect(collection.SponsorConfirmed).to.be.false;
+ });
+}
+
+export async function removeCollectionSponsorExpectFailure(collectionId: number) {
+ await usingApi(async (api) => {
+
+ // Run the transaction
+ const alicePrivateKey = privateKey('//Alice');
+ const tx = api.tx.nft.removeCollectionSponsor(collectionId);
+ await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected;
+ });
+}
+
+export async function setCollectionSponsorExpectFailure(collectionId: number, sponsor: string, senderSeed: string = '//Alice') {
+ await usingApi(async (api) => {
+
+ // Run the transaction
+ const alicePrivateKey = privateKey(senderSeed);
+ const tx = api.tx.nft.setCollectionSponsor(collectionId, sponsor);
+ await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected;
+ });
+}
+
+export async function confirmSponsorshipExpectSuccess(collectionId: number, senderSeed: string = '//Alice') {
+ await usingApi(async (api) => {
+
+ // Run the transaction
const sender = privateKey(senderSeed);
- const tx = api.tx.nft.createItem(collectionId, sender.address, createMode);
+ const tx = api.tx.nft.confirmSponsorship(collectionId);
const events = await submitTransactionAsync(sender, tx);
const result = getGenericResult(events);
-
+
+ // Get the collection
+ const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+ // What to expect
+ expect(result.success).to.be.true;
+ expect(collection.Sponsor).to.be.equal(sender.address);
+ expect(collection.SponsorConfirmed).to.be.true;
+ });
+}
+
+export async function confirmSponsorshipExpectFailure(collectionId: number, senderSeed: string = '//Alice') {
+ await usingApi(async (api) => {
+
+ // Run the transaction
+ const sender = privateKey(senderSeed);
+ const tx = api.tx.nft.confirmSponsorship(collectionId);
+ await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;
+ });
+}
+
+export interface CreateFungibleData extends Struct {
+ readonly value: u128;
+};
+
+export interface CreateReFungibleData extends Struct {};
+export interface CreateNftData extends Struct {};
+
+export interface CreateItemData extends Enum {
+ NFT: CreateNftData,
+ Fungible: CreateFungibleData,
+ ReFungible: CreateReFungibleData
+};
+
+export async function createItemExpectSuccess(sender: IKeyringPair, collectionId: number, createMode: string, owner: string = '') {
+ let newItemId: number = 0;
+ await usingApi(async (api) => {
+ const AItemCount = parseInt((await api.query.nft.itemListIndex(collectionId)).toString());
+ const Aitem: any = (await api.query.nft.fungibleItemList(collectionId, owner)).toJSON();
+ const AItemBalance = new BigNumber(Aitem.Value);
+
+ if (owner === '') owner = sender.address;
+
+ let tx;
+ if (createMode == 'Fungible') {
+ let createData = {fungible: {value: 10}};
+ tx = api.tx.nft.createItem(collectionId, owner, createData);
+ }
+ else {
+ tx = api.tx.nft.createItem(collectionId, owner, createMode);
+ }
+ const events = await submitTransactionAsync(sender, tx);
+ const result = getCreateItemResult(events);
+
const BItemCount = parseInt((await api.query.nft.itemListIndex(collectionId)).toString());
+ const Bitem: any = (await api.query.nft.fungibleItemList(collectionId, owner)).toJSON();
+ const BItemBalance = new BigNumber(Bitem.Value);
+
+ // What to expect
+ expect(result.success).to.be.true;
+ if (createMode == 'Fungible') {
+ expect(BItemBalance.minus(AItemBalance).toNumber()).to.be.equal(10);
+ }
+ else {
+ expect(BItemCount).to.be.equal(AItemCount+1);
+ }
+ expect(collectionId).to.be.equal(result.collectionId);
+ expect(BItemCount).to.be.equal(result.itemId);
+ newItemId = result.itemId;
+ });
+ return newItemId;
+}
+
+export async function enableWhiteListExpectSuccess(sender: IKeyringPair, collectionId: number) {
+ await usingApi(async (api) => {
+
+ // Run the transaction
+ const tx = api.tx.nft.setPublicAccessMode(collectionId, 'WhiteList');
+ const events = await submitTransactionAsync(sender, tx);
+ const result = getGenericResult(events);
+
+ // Get the collection
+ const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+ // What to expect
+ expect(result.success).to.be.true;
+ expect(collection.Access).to.be.equal('WhiteList');
+ });
+}
+
+export async function enablePublicMintingExpectSuccess(sender: IKeyringPair, collectionId: number) {
+ await usingApi(async (api) => {
+
+ // Run the transaction
+ const tx = api.tx.nft.setMintPermission(collectionId, true);
+ const events = await submitTransactionAsync(sender, tx);
+ const result = getGenericResult(events);
+ // Get the collection
+ const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
// What to expect
expect(result.success).to.be.true;
- expect(BItemCount).to.be.equal(AItemCount+1);
+ expect(collection.MintMode).to.be.equal(true);
});
}
+
+export async function addToWhiteListExpectSuccess(sender: IKeyringPair, collectionId: number, address: string) {
+ await usingApi(async (api) => {
+
+ // Run the transaction
+ const tx = api.tx.nft.addToWhiteList(collectionId, address);
+ const events = await submitTransactionAsync(sender, tx);
+ const result = getGenericResult(events);
+
+ // Get the collection
+ const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+ // What to expect
+ expect(result.success).to.be.true;
+ expect(collection.MintMode).to.be.equal(true);
+ });
+}
+