difftreelog
Merge pull request #50 from usetech-llc/feature/NFTPAR-250
in: master
NFTPAR-250 Tests for collection sponsoring
9 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 {263#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]245#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]264#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]246#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]265pub struct CreateFungibleData {247pub struct CreateFungibleData {248 pub value: u128,266}249}267250268#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]251#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]417 /// Balance owner per collection map400 /// Balance owner per collection map418 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;401 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;419402420 /// second parameter: item id + owner account id403 /// second parameter: item id + owner account id + spender account id421 pub ApprovedList get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;404 pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;422405423 /// Item collections406 /// Item collections424 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;407 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;425 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => FungibleItemType<T::AccountId>;408 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;426 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;409 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;427410428 /// Index list411 /// Index list429 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;412 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;430413431 /// Tokens transfer baskets414 /// Tokens transfer baskets432 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;415 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;433 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => Vec<BasketItem<T::AccountId, T::BlockNumber>>;416 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;434 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;417 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;435418436 // Contract Sponsorship and Ownership419 // Contract Sponsorship and Ownership448 <Module<T>>::init_collection(_c);431 <Module<T>>::init_collection(_c);449 }432 }450433451 for (_num, _q, _i) in &config.nft_item_id {434 for (_num, _c, _i) in &config.nft_item_id {452 <Module<T>>::init_nft_token(_i);435 <Module<T>>::init_nft_token(*_c, _i);453 }436 }454437455 for (_num, _q, _i) in &config.fungible_item_id {438 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {456 <Module<T>>::init_fungible_token(_i);439 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);457 }440 }458441459 for (_num, _q, _i) in &config.refungible_item_id {442 for (_num, _c, _i) in &config.refungible_item_id {460 <Module<T>>::init_refungible_token(_i);443 <Module<T>>::init_refungible_token(*_c, _i);461 }444 }462 })445 })463 }446 }593 offchain_schema: Vec::new(),576 offchain_schema: Vec::new(),594 schema_version: SchemaVersion::ImageURL,577 schema_version: SchemaVersion::ImageURL,595 sponsor: T::AccountId::default(),578 sponsor: T::AccountId::default(),596 unconfirmed_sponsor: T::AccountId::default(),579 sponsor_confirmed: false,597 variable_on_chain_schema: Vec::new(),580 variable_on_chain_schema: Vec::new(),598 const_on_chain_schema: Vec::new(),581 const_on_chain_schema: Vec::new(),599 limits: CollectionLimits::default(),582 limits: CollectionLimits::default(),624 Self::check_owner_permissions(collection_id, sender)?;607 Self::check_owner_permissions(collection_id, sender)?;625608626 <AddressTokens<T>>::remove_prefix(collection_id);609 <AddressTokens<T>>::remove_prefix(collection_id);627 <ApprovedList<T>>::remove_prefix(collection_id);610 <Allowances<T>>::remove_prefix(collection_id);628 <Balance<T>>::remove_prefix(collection_id);611 <Balance<T>>::remove_prefix(collection_id);629 <ItemListIndex>::remove(collection_id);612 <ItemListIndex>::remove(collection_id);630 <AdminList<T>>::remove(collection_id);613 <AdminList<T>>::remove(collection_id);852 let mut target_collection = <Collection<T>>::get(collection_id);835 let mut target_collection = <Collection<T>>::get(collection_id);853 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);836 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);854837855 target_collection.unconfirmed_sponsor = new_sponsor;838 target_collection.sponsor = new_sponsor;839 target_collection.sponsor_confirmed = false;856 <Collection<T>>::insert(collection_id, target_collection);840 <Collection<T>>::insert(collection_id, target_collection);857841858 Ok(())842 Ok(())872 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);856 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);873857874 let mut target_collection = <Collection<T>>::get(collection_id);858 let mut target_collection = <Collection<T>>::get(collection_id);875 ensure!(sender == target_collection.unconfirmed_sponsor, Error::<T>::ConfirmUnsetSponsorFail);859 ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);876860877 target_collection.sponsor = target_collection.unconfirmed_sponsor;861 target_collection.sponsor_confirmed = true;878 target_collection.unconfirmed_sponsor = T::AccountId::default();879 <Collection<T>>::insert(collection_id, target_collection);862 <Collection<T>>::insert(collection_id, target_collection);880863881 Ok(())864 Ok(())900 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);883 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);901884902 target_collection.sponsor = T::AccountId::default();885 target_collection.sponsor = T::AccountId::default();886 target_collection.sponsor_confirmed = false;903 <Collection<T>>::insert(collection_id, target_collection);887 <Collection<T>>::insert(collection_id, target_collection);904888905 Ok(())889 Ok(())1000 /// 984 /// 1001 /// * item_id: ID of NFT to burn.985 /// * item_id: ID of NFT to burn.1002 #[weight = T::WeightInfo::burn_item()]986 #[weight = T::WeightInfo::burn_item()]1003 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {987 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10049881005 let sender = ensure_signed(origin)?;989 let sender = ensure_signed(origin)?;1006 Self::collection_exists(collection_id)?;990 Self::collection_exists(collection_id)?;1018 match target_collection.mode1002 match target_collection.mode1019 {1003 {1020 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1004 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1021 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,1005 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, collection_id, value)?,1022 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1006 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1023 _ => ()1007 _ => ()1024 };1008 };1074 match target_collection.mode1058 match target_collection.mode1075 {1059 {1076 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1060 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1077 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1061 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1078 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1062 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1079 _ => ()1063 _ => ()1080 };1064 };1098 /// 1082 /// 1099 /// * item_id: ID of the item.1083 /// * item_id: ID of the item.1100 #[weight = T::WeightInfo::approve()]1084 #[weight = T::WeightInfo::approve()]1101 pub fn approve(origin, approved: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1085 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {110210861103 let sender = ensure_signed(origin)?;1087 let sender = ensure_signed(origin)?;11041088111010941111 if target_collection.access == AccessMode::WhiteList {1095 if target_collection.access == AccessMode::WhiteList {1112 Self::check_white_list(collection_id, &sender)?;1096 Self::check_white_list(collection_id, &sender)?;1113 Self::check_white_list(collection_id, &approved)?;1097 Self::check_white_list(collection_id, &spender)?;1114 }1098 }111510991116 // amount param stub1100 let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1117 let amount = 100000000;11181119 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1120 if list_exists {1101 let mut allowance: u128 = amount;11211122 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1123 let item_contains = list.iter().any(|i| i.approved == approved);11241125 if !item_contains {1102 if allowance_exists {1126 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1103 allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1127 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1128 }1129 } else {11301131 let mut list = Vec::new();1132 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1133 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1134 }1104 }1105 <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);113511061136 Ok(())1107 Ok(())1137 }1108 }1161 let sender = ensure_signed(origin)?;1132 let sender = ensure_signed(origin)?;1162 let mut appoved_transfer = false;1133 let mut appoved_transfer = false;116311341164 // Check approve1135 // Check approval1165 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1136 let mut approval: u128 = 0;1137 if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &recipient)) {1166 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1138 approval = <Allowances<T>>::get(collection_id, (item_id, &from, &recipient));1167 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1168 if opt_item.is_some()1139 ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1169 {1170 appoved_transfer = true;1171 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1172 }1140 appoved_transfer = true;1173 }1141 }117411421175 let target_collection = <Collection<T>>::get(collection_id);1143 let target_collection = <Collection<T>>::get(collection_id);117911471180 // Transfer permissions check 1148 // Transfer permissions check 1181 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1149 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1182 Error::<T>::NoPermission);1150 Error::<T>::NoPermission);118311511184 if target_collection.access == AccessMode::WhiteList {1152 if target_collection.access == AccessMode::WhiteList {1185 Self::check_white_list(collection_id, &sender)?;1153 Self::check_white_list(collection_id, &sender)?;1186 Self::check_white_list(collection_id, &recipient)?;1154 Self::check_white_list(collection_id, &recipient)?;1187 }1155 }118811561189 // remove approve1157 // Reduce approval by transferred amount or remove if remaining approval drops to 01190 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1158 if approval - value > 0 {1159 <Allowances<T>>::insert(collection_id, (item_id, &from, &recipient), approval - value);1191 .into_iter().filter(|i| i.approved != sender.clone()).collect();1160 }1161 else {1192 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);1162 <Allowances<T>>::remove(collection_id, (item_id, &from, &recipient));1163 }1193116411941195 match target_collection.mode1165 match target_collection.mode1196 {1166 {1197 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1167 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1198 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1168 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1199 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1169 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1200 _ => ()1170 _ => ()1201 };1171 };1614 {1584 {1615 CreateItemData::NFT(data) => {1585 CreateItemData::NFT(data) => {1616 let item = NftItemType {1586 let item = NftItemType {1617 collection: collection_id,1618 owner,1587 owner,1619 const_data: data.const_data,1588 const_data: data.const_data,1620 variable_data: data.variable_data1589 variable_data: data.variable_data1621 };1590 };162215911623 Self::add_nft_item(item)?;1592 Self::add_nft_item(collection_id, item)?;1624 },1593 },1625 CreateItemData::Fungible(_) => {1594 CreateItemData::Fungible(data) => {1626 let item = FungibleItemType {1595 Self::add_fungible_item(collection_id, &owner, data.value)?;1627 collection: collection_id,1628 owner,1629 value: (10 as u128).pow(collection.decimal_points as u32)1630 };16311632 Self::add_fungible_item(item)?;1633 },1596 },1634 CreateItemData::ReFungible(data) => {1597 CreateItemData::ReFungible(data) => {1635 let mut owner_list = Vec::new();1598 let mut owner_list = Vec::new();1636 let value = (10 as u128).pow(collection.decimal_points as u32);1599 let value = (10 as u128).pow(collection.decimal_points as u32);1637 owner_list.push(Ownership {owner: owner.clone(), fraction: value});1600 owner_list.push(Ownership {owner: owner.clone(), fraction: value});163816011639 let item = ReFungibleItemType {1602 let item = ReFungibleItemType {1640 collection: collection_id,1641 owner: owner_list,1603 owner: owner_list,1642 const_data: data.const_data,1604 const_data: data.const_data,1643 variable_data: data.variable_data1605 variable_data: data.variable_data1644 };1606 };164516071646 Self::add_refungible_item(item)?;1608 Self::add_refungible_item(collection_id, item)?;1647 }1609 }1648 };1610 };164916111653 Ok(())1615 Ok(())1654 }1616 }165516171656 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1618 fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {1657 let current_index = <ItemListIndex>::get(item.collection)1658 .checked_add(1)1659 .ok_or(Error::<T>::NumOverflow)?;1660 let itemcopy = item.clone();1661 let owner = item.owner.clone();166216191663 Self::add_token_index(item.collection, current_index, owner.clone())?;1620 // Does new owner already have an account?1621 let mut balance: u128 = 0;1622 if <FungibleItemList<T>>::contains_key(collection_id, owner) {1623 balance = <FungibleItemList<T>>::get(collection_id, owner).value;1624 } 166416251665 <ItemListIndex>::insert(item.collection, current_index);1626 // Mint 1627 let item = FungibleItemType {1628 value: balance + value1629 };1666 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);1630 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);166716311668 // Add current block1669 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1670 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1671 1672 // Update balance1632 // Update balance1673 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1633 let new_balance = <Balance<T>>::get(collection_id, owner)1674 .checked_add(item.value)1634 .checked_add(value)1675 .ok_or(Error::<T>::NumOverflow)?;1635 .ok_or(Error::<T>::NumOverflow)?;1676 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1636 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);167716371678 Ok(())1638 Ok(())1679 }1639 }168016401681 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1641 fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1682 let current_index = <ItemListIndex>::get(item.collection)1642 let current_index = <ItemListIndex>::get(collection_id)1683 .checked_add(1)1643 .checked_add(1)1684 .ok_or(Error::<T>::NumOverflow)?;1644 .ok_or(Error::<T>::NumOverflow)?;1685 let itemcopy = item.clone();1645 let itemcopy = item.clone();168616461687 let value = item.owner.first().unwrap().fraction;1647 let value = item.owner.first().unwrap().fraction;1688 let owner = item.owner.first().unwrap().owner.clone();1648 let owner = item.owner.first().unwrap().owner.clone();168916491690 Self::add_token_index(item.collection, current_index, owner.clone())?;1650 Self::add_token_index(collection_id, current_index, owner.clone())?;169116511692 <ItemListIndex>::insert(item.collection, current_index);1652 <ItemListIndex>::insert(collection_id, current_index);1693 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);1653 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);169416541695 // Add current block1696 let block_number: T::BlockNumber = 0.into();1697 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);16981699 // Update balance1655 // Update balance1700 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1656 let new_balance = <Balance<T>>::get(collection_id, owner.clone())1701 .checked_add(value)1657 .checked_add(value)1702 .ok_or(Error::<T>::NumOverflow)?;1658 .ok_or(Error::<T>::NumOverflow)?;1703 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1659 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);170416601705 Ok(())1661 Ok(())1706 }1662 }170716631708 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1664 fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1709 let current_index = <ItemListIndex>::get(item.collection)1665 let current_index = <ItemListIndex>::get(collection_id)1710 .checked_add(1)1666 .checked_add(1)1711 .ok_or(Error::<T>::NumOverflow)?;1667 .ok_or(Error::<T>::NumOverflow)?;171216681713 let item_owner = item.owner.clone();1669 let item_owner = item.owner.clone();1714 let collection_id = item.collection.clone();1715 Self::add_token_index(collection_id, current_index, item.owner.clone())?;1670 Self::add_token_index(collection_id, current_index, item.owner.clone())?;171616711717 <ItemListIndex>::insert(collection_id, current_index);1672 <ItemListIndex>::insert(collection_id, current_index);1718 <NftItemList<T>>::insert(collection_id, current_index, item);1673 <NftItemList<T>>::insert(collection_id, current_index, item);171916741720 // Add current block1721 let block_number: T::BlockNumber = 0.into();1722 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);17231724 // Update balance1675 // Update balance1725 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1676 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1726 .checked_add(1)1677 .checked_add(1)1748 .unwrap();1699 .unwrap();1749 Self::remove_token_index(collection_id, item_id, owner.clone())?;1700 Self::remove_token_index(collection_id, item_id, owner.clone())?;175017011751 // remove approve list1752 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));17531754 // update balance1702 // update balance1755 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1703 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1756 .checked_sub(item.fraction)1704 .checked_sub(item.fraction)1770 let item = <NftItemList<T>>::get(collection_id, item_id);1718 let item = <NftItemList<T>>::get(collection_id, item_id);1771 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;1719 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;177217201773 // remove approve list1774 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17751776 // update balance1721 // update balance1777 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1722 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1778 .checked_sub(1)1723 .checked_sub(1)1783 Ok(())1728 Ok(())1784 }1729 }178517301786 fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1731 fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1787 ensure!(1732 ensure!(1788 <FungibleItemList<T>>::contains_key(collection_id, item_id),1733 <FungibleItemList<T>>::contains_key(collection_id, owner),1789 Error::<T>::TokenNotFound1734 Error::<T>::TokenNotFound1790 );1735 );1791 let item = <FungibleItemList<T>>::get(collection_id, item_id);1736 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1792 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;1737 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);179317381794 // remove approve list1795 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17961797 // update balance1739 // update balance1798 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1740 let new_balance = <Balance<T>>::get(collection_id, owner)1799 .checked_sub(item.value)1741 .checked_sub(value)1800 .ok_or(Error::<T>::NumOverflow)?;1742 .ok_or(Error::<T>::NumOverflow)?;1801 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1743 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);180217441803 <FungibleItemList<T>>::remove(collection_id, item_id);1745 if balance.value - value > 0 {1746 balance.value -= value;1747 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1748 }1749 else {1750 <FungibleItemList<T>>::remove(collection_id, owner);1751 }180417521805 Ok(())1753 Ok(())1806 }1754 }1861 <NftItemList<T>>::get(collection_id, item_id).owner == subject1809 <NftItemList<T>>::get(collection_id, item_id).owner == subject1862 }1810 }1863 CollectionMode::Fungible(_) => {1811 CollectionMode::Fungible(_) => {1864 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1812 <FungibleItemList<T>>::contains_key(collection_id, &subject)1865 }1813 }1866 CollectionMode::ReFungible(_) => {1814 CollectionMode::ReFungible(_) => {1867 <ReFungibleItemList<T>>::get(collection_id, item_id)1815 <ReFungibleItemList<T>>::get(collection_id, item_id)188218301883 fn transfer_fungible(1831 fn transfer_fungible(1884 collection_id: CollectionId,1832 collection_id: CollectionId,1885 item_id: TokenId,1886 value: u128,1833 value: u128,1887 owner: T::AccountId,1834 owner: &T::AccountId,1888 new_owner: T::AccountId,1835 recipient: &T::AccountId,1889 ) -> DispatchResult {1836 ) -> DispatchResult {1890 ensure!(1837 ensure!(1891 <FungibleItemList<T>>::contains_key(collection_id, item_id),1838 <FungibleItemList<T>>::contains_key(collection_id, owner),1892 Error::<T>::TokenNotFound1839 Error::<T>::TokenNotFound1893 );1840 );189418411895 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1842 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1896 let amount = full_item.value;1843 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);189718441898 ensure!(amount >= value, Error::<T>::TokenValueTooLow);1845 // Send balance to recipient (updates balanceOf of recipient)1846 Self::add_fungible_item(collection_id, recipient, value)?;189918471900 // update balance1848 // update balanceOf of sender1901 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1849 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);1902 .checked_sub(value)1903 .ok_or(Error::<T>::NumOverflow)?;1904 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);190518501906 let mut new_owner_account_id = 0;1851 // Reduce or remove sender1907 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1852 if balance.value == value {1908 if new_owner_items.len() > 0 {1909 new_owner_account_id = new_owner_items[0];1853 <FungibleItemList<T>>::remove(collection_id, owner);1910 }1854 }19111855 else {1912 // transfer1856 balance.value -= value;1913 if amount == value && new_owner_account_id == 0 {1857 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1914 // change owner1915 // new owner do not have account1916 let mut new_full_item = full_item.clone();1917 new_full_item.owner = new_owner.clone();1918 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19191920 // update balance1921 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1922 .checked_add(value)1923 .ok_or(Error::<T>::NumOverflow)?;1924 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19251926 // update index collection1927 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1928 } else {1929 let mut new_full_item = full_item.clone();1930 new_full_item.value -= value;19311932 // separate amount1933 if new_owner_account_id > 0 {1934 // new owner has account1935 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1936 item.value += value;19371938 // update balance1939 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1940 .checked_add(value)1941 .ok_or(Error::<T>::NumOverflow)?;1942 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19431944 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1945 } else {1946 // new owner do not have account1947 let item = FungibleItemType {1948 collection: collection_id,1949 owner: new_owner.clone(),1950 value1951 };19521953 Self::add_fungible_item(item)?;1954 }19551956 if amount == value {1957 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;19581959 // remove approve list1960 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1961 <FungibleItemList<T>>::remove(collection_id, item_id);1962 }19631964 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1965 }1858 }196618591967 Ok(())1860 Ok(())1996 .ok_or(Error::<T>::NumOverflow)?;1889 .ok_or(Error::<T>::NumOverflow)?;1997 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);1890 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);199818911999 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1892 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2000 .checked_add(value)1893 .checked_add(value)2001 .ok_or(Error::<T>::NumOverflow)?;1894 .ok_or(Error::<T>::NumOverflow)?;2002 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);1895 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);200318962004 let old_owner = item.owner.clone();1897 let old_owner = item.owner.clone();2005 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1898 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);2076 .ok_or(Error::<T>::NumOverflow)?;1969 .ok_or(Error::<T>::NumOverflow)?;2077 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);1970 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);207819712079 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1972 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2080 .checked_add(1)1973 .checked_add(1)2081 .ok_or(Error::<T>::NumOverflow)?;1974 .ok_or(Error::<T>::NumOverflow)?;2082 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);1975 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);208319762084 // change owner1977 // change owner2085 let old_owner = item.owner.clone();1978 let old_owner = item.owner.clone();2089 // update index collection1982 // update index collection2090 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1983 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;209119842092 // reset approved list2093 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));2094 Ok(())1985 Ok(())2095 }1986 }2096 1987 2102 match mode {1993 match mode {2103 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1994 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2104 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1995 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2105 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2106 _ => ()1996 _ => ()2107 };1997 };2108 1998 2164 CreatedCollectionCount::put(next_id);2054 CreatedCollectionCount::put(next_id);2165 }2055 }216620562167 fn init_nft_token(item: &NftItemType<T::AccountId>) {2057 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2168 let current_index = <ItemListIndex>::get(item.collection)2058 let current_index = <ItemListIndex>::get(collection_id)2169 .checked_add(1)2059 .checked_add(1)2170 .unwrap();2060 .unwrap();217120612172 let item_owner = item.owner.clone();2062 let item_owner = item.owner.clone();2173 let collection_id = item.collection.clone();2174 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();2063 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();217520642176 <ItemListIndex>::insert(collection_id, current_index);2065 <ItemListIndex>::insert(collection_id, current_index);2182 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2071 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2183 }2072 }218420732185 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {2074 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2186 let current_index = <ItemListIndex>::get(item.collection)2075 let current_index = <ItemListIndex>::get(collection_id)2187 .checked_add(1)2076 .checked_add(1)2188 .unwrap();2077 .unwrap();2189 let owner = item.owner.clone();219020782191 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();2079 Self::add_token_index(collection_id, current_index, (*owner).clone()).unwrap();219220802193 <ItemListIndex>::insert(item.collection, current_index);2081 <ItemListIndex>::insert(collection_id, current_index);219420822195 // Update balance2083 // Update balance2196 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2084 let new_balance = <Balance<T>>::get(collection_id, owner)2197 .checked_add(item.value)2085 .checked_add(item.value)2198 .unwrap();2086 .unwrap();2199 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2087 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2200 }2088 }220120892202 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {2090 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2203 let current_index = <ItemListIndex>::get(item.collection)2091 let current_index = <ItemListIndex>::get(collection_id)2204 .checked_add(1)2092 .checked_add(1)2205 .unwrap();2093 .unwrap();220620942207 let value = item.owner.first().unwrap().fraction;2095 let value = item.owner.first().unwrap().fraction;2208 let owner = item.owner.first().unwrap().owner.clone();2096 let owner = item.owner.first().unwrap().owner.clone();220920972210 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();2098 Self::add_token_index(collection_id, current_index, owner.clone()).unwrap();221120992212 <ItemListIndex>::insert(item.collection, current_index);2100 <ItemListIndex>::insert(collection_id, current_index);221321012214 // Update balance2102 // Update balance2215 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2103 let new_balance = <Balance<T>>::get(collection_id, owner.clone())2216 .checked_add(value)2104 .checked_add(value)2217 .unwrap();2105 .unwrap();2218 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2106 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2219 }2107 }222021082221 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {2109 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {2386 // };2274 // };2387 let fee = Self::traditional_fee(len, info, tip);2275 let fee = Self::traditional_fee(len, info, tip);238822762277 // Only mess with balances if fee is not zero.2278 if fee.is_zero() {2279 return Ok((fee, None));2280 }22812389 // Determine who is paying transaction fee based on ecnomic model2282 // Determine who is paying transaction fee based on ecnomic model2390 // Parse call to extract collection ID and access collection sponsor2283 // Parse call to extract collection ID and access collection sponsor2391 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2284 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2392 Some(Call::create_item(collection_id, _owner, _properties)) => {2285 Some(Call::create_item(collection_id, _owner, _properties)) => {239322862394 // check free create limit2287 // check free create limit2395 if <Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)2288 if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&2289 (<Collection<T>>::get(collection_id).sponsor_confirmed)2396 {2290 {2397 <Collection<T>>::get(collection_id).sponsor2291 <Collection<T>>::get(collection_id).sponsor2398 } else {2292 } else {2399 T::AccountId::default()2293 T::AccountId::default()2400 }2294 }2401 }2295 }2402 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2296 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2403 2297 2404 let _collection_limits = <Collection<T>>::get(collection_id).limits;2298 let mut sponsor_transfer = false;2405 let _collection_mode = <Collection<T>>::get(collection_id).mode;2299 if <Collection<T>>::get(collection_id).sponsor_confirmed {240623002407 // sponsor timeout2301 let collection_limits = <Collection<T>>::get(collection_id).limits;2302 let collection_mode = <Collection<T>>::get(collection_id).mode;2303 2304 // sponsor timeout2408 let sponsor_transfer = match _collection_mode {2305 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2306 sponsor_transfer = match collection_mode {2409 CollectionMode::NFT => {2307 CollectionMode::NFT => {2308 2309 // get correct limit2310 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2311 collection_limits.sponsor_transfer_timeout2312 } else {2313 ChainLimit::get().nft_sponsor_transfer_timeout2314 };2315 2316 let mut sponsored = true;2317 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2318 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2319 let limit_time = last_tx_block + limit.into();2320 if block_number <= limit_time {2321 sponsored = false;2322 }2323 }2324 if sponsored {2325 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2326 }241023272411 // get correct limit2328 sponsored2412 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2413 _collection_limits.sponsor_transfer_timeout2414 } else {2415 ChainLimit::get().nft_sponsor_transfer_timeout2416 };24172418 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2419 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2420 let limit_time = basket + limit.into();2421 if block_number >= limit_time {2422 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2423 true2424 }2329 }2425 else {2330 CollectionMode::Fungible(_) => {2426 false2331 2427 }2428 }2429 CollectionMode::Fungible(_) => {24302431 // get correct limit2332 // get correct limit2432 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2333 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2433 _collection_limits.sponsor_transfer_timeout2334 collection_limits.sponsor_transfer_timeout2434 } else {2335 } else {2435 ChainLimit::get().fungible_sponsor_transfer_timeout2336 ChainLimit::get().fungible_sponsor_transfer_timeout2436 };2337 };24372438 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2338 2439 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2339 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2440 if basket.iter().any(|i| i.address == _new_owner.clone())2340 let mut sponsored = true;2441 {2442 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2341 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2443 let limit_time = item.start_block + limit.into();2342 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2444 if block_number >= limit_time {2343 let limit_time = last_tx_block + limit.into();2445 basket.retain(|x| x.address == item.address);2446 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2344 if block_number <= limit_time {2447 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2345 sponsored = false;2448 true2346 }2449 }2347 }2450 else {2348 if sponsored {2451 false2349 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2452 }2350 }23512352 sponsored2453 }2353 }2454 else {2354 CollectionMode::ReFungible(_) => {2455 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2355 2356 // get correct limit2357 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2358 collection_limits.sponsor_transfer_timeout2359 } else {2360 ChainLimit::get().refungible_sponsor_transfer_timeout2361 };2362 2363 let mut sponsored = true;2364 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2365 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2366 let limit_time = last_tx_block + limit.into();2456 true2367 if block_number <= limit_time {2457 }2368 sponsored = false;2369 }2458 }2370 }2459 CollectionMode::ReFungible(_) => {2371 if sponsored {2372 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2373 }246023742461 // get correct limit2375 sponsored2462 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2463 _collection_limits.sponsor_transfer_timeout2464 } else {2465 ChainLimit::get().refungible_sponsor_transfer_timeout2466 };24672468 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2469 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2470 let limit_time = basket + limit.into();2471 if block_number >= limit_time {2472 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2473 true2474 } else {2475 false2476 }2376 }2477 }2377 _ => {2478 _ => {2378 false2479 false2379 },2480 },2380 };2481 };2381 }248223822483 if !sponsor_transfer {2383 if !sponsor_transfer {2484 T::AccountId::default()2384 T::AccountId::default()2555 let mut who_pays_fee: T::AccountId = sponsor.clone();2455 let mut who_pays_fee: T::AccountId = sponsor.clone();2556 if sponsor == T::AccountId::default() {2456 if sponsor == T::AccountId::default() {2557 who_pays_fee = who.clone();2457 who_pays_fee = who.clone();2558 }25592560 // Only mess with balances if fee is not zero.2561 if fee.is_zero() {2562 return Ok((fee, None));2563 }2458 }256424592565 match <T as transaction_payment::Trait>::Currency::withdraw(2460 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/src/confirmSponsorship.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/confirmSponsorship.test.ts
@@ -0,0 +1,350 @@
+//
+// 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 } 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.only('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('A', 'B', 'C', 'NFT');
+ 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('A', 'B', 'C', 'NFT');
+ 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('A', 'B', 'C', 'NFT');
+ 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('A', 'B', 'C', 'NFT');
+ 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('A', 'B', 'C', '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('A', 'B', 'C', '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('A', 'B', 'C', 'NFT');
+ 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('A', 'B', 'C', 'NFT');
+ 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 () {
+ console.log = function () {};
+ console.error = function () {};
+ await submitTransactionAsync(zeroBalance, zeroToAlice);
+ delete console.log;
+ delete console.error;
+ };
+ 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('A', 'B', 'C', '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 () {
+ console.log = function () {};
+ console.error = function () {};
+ await submitTransactionAsync(zeroBalance, zeroToAlice);
+ delete console.log;
+ delete console.error;
+ };
+
+ 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('A', 'B', 'C', '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 () {
+ console.log = function () {};
+ console.error = function () {};
+ await submitTransactionAsync(zeroBalance, zeroToAlice);
+ delete console.log;
+ delete console.error;
+ };
+ 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.only('(!negative test!) integration test: ext. setCollectionSponsor():', () => {
+ 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('A', 'B', 'C', 'NFT');
+ 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('A', 'B', 'C', 'NFT');
+ 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('A', 'B', 'C', 'NFT');
+ await confirmSponsorshipExpectFailure(collectionId, '//Bob');
+ });
+
+ it('(!negative test!) Confirm sponsorship in a collection that was destroyed', async () => {
+ const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+ await destroyCollectionExpectSuccess(collectionId);
+ await confirmSponsorshipExpectFailure(collectionId, '//Bob');
+ });
+});
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');
+ 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');
+ 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');
+ 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/destroyCollection.test.tsdiffbeforeafterboth--- a/tests/src/destroyCollection.test.ts
+++ b/tests/src/destroyCollection.test.ts
@@ -1,55 +1,12 @@
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 () => {
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('A', 'B', 'C', 'NFT');
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ });
+ it('Set Fungible collection sponsor', async () => {
+ const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'Fungible');
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ });
+ it('Set ReFungible collection sponsor', async () => {
+ const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'ReFungible');
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ });
+
+ it('Set the same sponsor repeatedly', async () => {
+ const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ });
+ it('Replace collection sponsor', async () => {
+ const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+
+ 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('A', 'B', 'C', 'NFT');
+ 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('A', 'B', 'C', 'NFT');
+ await destroyCollectionExpectSuccess(collectionId);
+ await setCollectionSponsorExpectFailure(collectionId, bob.address);
+ });
+});
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -9,10 +9,12 @@
import { ApiPromise, Keyring } from "@polkadot/api";
import { default as usingApi, submitTransactionAsync } 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,6 +65,27 @@
return result;
}
+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 async function createCollectionExpectSuccess(name: string, description: string, tokenPrefix: string, mode: string): Promise<number> {
let collectionId: number = 0;
await usingApi(async (api) => {
@@ -123,20 +152,214 @@
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);
+ const events = await submitTransactionAsync(alicePrivateKey, tx);
+ const result = getDestroyResult(events);
+
+ // What to expect
+ expect(result).to.be.false;
+ });
+}
+
+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) => {
- const AItemCount = parseInt((await api.query.nft.itemListIndex(collectionId)).toString());
+ // 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 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);
+ const events = await submitTransactionAsync(alicePrivateKey, tx);
+ const result = getGenericResult(events);
+
+ // What to expect
+ expect(result.success).to.be.false;
+ });
+}
+
+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);
+ const events = await submitTransactionAsync(sender, tx);
+ const result = getGenericResult(events);
+
+ // What to expect
+ expect(result.success).to.be.false;
+ });
+}
+
+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;
- expect(BItemCount).to.be.equal(AItemCount+1);
+ 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(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);
+ });
+}
+