git.delta.rocks / unique-network / refs/commits / f7b41a8746a2

difftreelog

Refactor fungible logic, add positive tests for sponsorship confirmation

Greg Zaitsev2020-12-25parent: #e0216d4.patch.diff
in: master

4 files changed

modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
152152
153#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]153#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
154#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]154#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
155pub struct FungibleItemType<AccountId> {155pub struct FungibleItemType {
156 pub owner: AccountId,
157 pub value: u128,156 pub value: u128,
158}157}
159158
165 pub variable_data: Vec<u8>,164 pub variable_data: Vec<u8>,
166}165}
167166
168#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
169#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
170pub struct ApprovePermissions<AccountId> {
171 pub approved: AccountId,
172 pub amount: u128,
173}
174
175// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]167// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
176// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]168// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
177// pub struct VestingItem<AccountId, Moment> {169// pub struct VestingItem<AccountId, Moment> {
183// pub vesting_date: Moment,175// pub vesting_date: Moment,
184// }176// }
185177
186#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
187#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
188pub struct BasketItem<AccountId, BlockNumber> {
189 pub address: AccountId,
190 pub start_block: BlockNumber,
191}
192
193#[derive(Encode, Decode, Debug, Clone, PartialEq)]178#[derive(Encode, Decode, Debug, Clone, PartialEq)]
194#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
195pub struct CollectionLimits {180pub struct CollectionLimits {
260#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]245#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
261#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]246#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
262pub struct CreateFungibleData {247pub struct CreateFungibleData {
248 pub value: u128,
263}249}
264250
265#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]251#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
414 /// Balance owner per collection map400 /// Balance owner per collection map
415 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;
416402
417 /// second parameter: item id + owner account id403 /// second parameter: item id + owner account id + spender account id
418 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;
419405
420 /// Item collections406 /// Item collections
421 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>;
422 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;
423 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>;
424410
425 /// Index list411 /// Index list
426 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>;
427413
428 /// Tokens transfer baskets414 /// Tokens transfer baskets
429 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;
430 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;
431 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;
432418
433 // Contract Sponsorship and Ownership419 // Contract Sponsorship and Ownership
447 <Module<T>>::init_nft_token(*_c, _i);433 <Module<T>>::init_nft_token(*_c, _i);
448 }434 }
449435
450 for (_num, _c, _i) in &config.fungible_item_id {436 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {
451 <Module<T>>::init_fungible_token(*_c, _i);437 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);
452 }438 }
453439
454 for (_num, _c, _i) in &config.refungible_item_id {440 for (_num, _c, _i) in &config.refungible_item_id {
619 Self::check_owner_permissions(collection_id, sender)?;605 Self::check_owner_permissions(collection_id, sender)?;
620606
621 <AddressTokens<T>>::remove_prefix(collection_id);607 <AddressTokens<T>>::remove_prefix(collection_id);
622 <ApprovedList<T>>::remove_prefix(collection_id);608 <Allowances<T>>::remove_prefix(collection_id);
623 <Balance<T>>::remove_prefix(collection_id);609 <Balance<T>>::remove_prefix(collection_id);
624 <ItemListIndex>::remove(collection_id);610 <ItemListIndex>::remove(collection_id);
625 <AdminList<T>>::remove(collection_id);611 <AdminList<T>>::remove(collection_id);
1015 /// 1001 ///
1016 /// * item_id: ID of NFT to burn.1002 /// * item_id: ID of NFT to burn.
1017 #[weight = T::WeightInfo::burn_item()]1003 #[weight = T::WeightInfo::burn_item()]
1018 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1004 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {
10191005
1020 let sender = ensure_signed(origin)?;1006 let sender = ensure_signed(origin)?;
1021 Self::collection_exists(collection_id)?;1007 Self::collection_exists(collection_id)?;
1033 match target_collection.mode1019 match target_collection.mode
1034 {1020 {
1035 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1021 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,
1036 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,1022 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, collection_id, value)?,
1037 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1023 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,
1038 _ => ()1024 _ => ()
1039 };1025 };
1089 match target_collection.mode1075 match target_collection.mode
1090 {1076 {
1091 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1077 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,
1092 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1078 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,
1093 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1079 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,
1094 _ => ()1080 _ => ()
1095 };1081 };
1113 /// 1099 ///
1114 /// * item_id: ID of the item.1100 /// * item_id: ID of the item.
1115 #[weight = T::WeightInfo::approve()]1101 #[weight = T::WeightInfo::approve()]
1116 pub fn approve(origin, approved: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1102 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {
11171103
1118 let sender = ensure_signed(origin)?;1104 let sender = ensure_signed(origin)?;
11191105
11251111
1126 if target_collection.access == AccessMode::WhiteList {1112 if target_collection.access == AccessMode::WhiteList {
1127 Self::check_white_list(collection_id, &sender)?;1113 Self::check_white_list(collection_id, &sender)?;
1128 Self::check_white_list(collection_id, &approved)?;1114 Self::check_white_list(collection_id, &spender)?;
1129 }1115 }
11301116
1131 // amount param stub1117 let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));
1132 let amount = 100000000;
1133
1134 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));
1135 if list_exists {1118 let mut allowance: u128 = amount;
1136
1137 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));
1138 let item_contains = list.iter().any(|i| i.approved == approved);
1139
1140 if !item_contains {1119 if allowance_exists {
1141 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1120 allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));
1142 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);
1143 }
1144 } else {
1145
1146 let mut list = Vec::new();
1147 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });
1148 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);
1149 }1121 }
1122 <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);
11501123
1151 Ok(())1124 Ok(())
1152 }1125 }
1176 let sender = ensure_signed(origin)?;1149 let sender = ensure_signed(origin)?;
1177 let mut appoved_transfer = false;1150 let mut appoved_transfer = false;
11781151
1179 // Check approve1152 // Check approval
1180 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1153 let mut approval: u128 = 0;
1154 if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &recipient)) {
1181 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1155 approval = <Allowances<T>>::get(collection_id, (item_id, &from, &recipient));
1182 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());
1183 if opt_item.is_some()1156 ensure!(approval >= value, Error::<T>::TokenValueNotEnough);
1184 {
1185 appoved_transfer = true;
1186 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);
1187 }1157 appoved_transfer = true;
1188 }1158 }
11891159
1190 let target_collection = <Collection<T>>::get(collection_id);1160 let target_collection = <Collection<T>>::get(collection_id);
11941164
1195 // Transfer permissions check 1165 // Transfer permissions check
1196 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1166 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),
1197 Error::<T>::NoPermission);1167 Error::<T>::NoPermission);
11981168
1199 if target_collection.access == AccessMode::WhiteList {1169 if target_collection.access == AccessMode::WhiteList {
1200 Self::check_white_list(collection_id, &sender)?;1170 Self::check_white_list(collection_id, &sender)?;
1201 Self::check_white_list(collection_id, &recipient)?;1171 Self::check_white_list(collection_id, &recipient)?;
1202 }1172 }
12031173
1204 // remove approve1174 // Reduce approval by transferred amount or remove if remaining approval drops to 0
1205 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1175 if approval - value > 0 {
1176 <Allowances<T>>::insert(collection_id, (item_id, &from, &recipient), approval - value);
1206 .into_iter().filter(|i| i.approved != sender.clone()).collect();1177 }
1178 else {
1207 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);1179 <Allowances<T>>::remove(collection_id, (item_id, &from, &recipient));
1180 }
12081181
1209
1210 match target_collection.mode1182 match target_collection.mode
1211 {1183 {
1212 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1184 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,
1213 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1185 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &from, &recipient)?,
1214 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1186 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,
1215 _ => ()1187 _ => ()
1216 };1188 };
15741546
1575 Self::add_nft_item(collection_id, item)?;1547 Self::add_nft_item(collection_id, item)?;
1576 },1548 },
1577 CreateItemData::Fungible(_) => {1549 CreateItemData::Fungible(data) => {
1578 let item = FungibleItemType {1550 Self::add_fungible_item(collection_id, &owner, data.value)?;
1579 owner,
1580 value: (10 as u128).pow(collection.decimal_points as u32)
1581 };
1582
1583 Self::add_fungible_item(collection_id, item)?;
1584 },1551 },
1585 CreateItemData::ReFungible(data) => {1552 CreateItemData::ReFungible(data) => {
1586 let mut owner_list = Vec::new();1553 let mut owner_list = Vec::new();
1603 Ok(())1570 Ok(())
1604 }1571 }
16051572
1606 fn add_fungible_item(collection_id: CollectionId, item: FungibleItemType<T::AccountId>) -> DispatchResult {1573 fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {
1607 let current_index = <ItemListIndex>::get(collection_id)
1608 .checked_add(1)
1609 .ok_or(Error::<T>::NumOverflow)?;
1610 let itemcopy = item.clone();
1611 let owner = item.owner.clone();
16121574
1613 Self::add_token_index(collection_id, current_index, owner.clone())?;1575 // Does new owner already have an account?
1576 let mut balance: u128 = 0;
1577 if <FungibleItemList<T>>::contains_key(collection_id, owner) {
1578 balance = <FungibleItemList<T>>::get(collection_id, owner).value;
1579 }
16141580
1615 <ItemListIndex>::insert(collection_id, current_index);1581 // Mint
1582 let item = FungibleItemType {
1583 value: balance + value
1584 };
1616 <FungibleItemList<T>>::insert(collection_id, current_index, itemcopy);1585 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);
16171586
1618 // Add current block
1619 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();
1620 <FungibleTransferBasket<T>>::insert(collection_id, current_index, v);
1621
1622 // Update balance1587 // Update balance
1623 let new_balance = <Balance<T>>::get(collection_id, owner.clone())1588 let new_balance = <Balance<T>>::get(collection_id, owner)
1624 .checked_add(item.value)1589 .checked_add(value)
1625 .ok_or(Error::<T>::NumOverflow)?;1590 .ok_or(Error::<T>::NumOverflow)?;
1626 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);1591 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);
16271592
1628 Ok(())1593 Ok(())
1629 }1594 }
1642 <ItemListIndex>::insert(collection_id, current_index);1607 <ItemListIndex>::insert(collection_id, current_index);
1643 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);1608 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);
16441609
1645 // Add current block
1646 let block_number: T::BlockNumber = 0.into();
1647 <ReFungibleTransferBasket<T>>::insert(collection_id, current_index, block_number);
1648
1649 // Update balance1610 // Update balance
1650 let new_balance = <Balance<T>>::get(collection_id, owner.clone())1611 let new_balance = <Balance<T>>::get(collection_id, owner.clone())
1651 .checked_add(value)1612 .checked_add(value)
1666 <ItemListIndex>::insert(collection_id, current_index);1627 <ItemListIndex>::insert(collection_id, current_index);
1667 <NftItemList<T>>::insert(collection_id, current_index, item);1628 <NftItemList<T>>::insert(collection_id, current_index, item);
16681629
1669 // Add current block
1670 let block_number: T::BlockNumber = 0.into();
1671 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);
1672
1673 // Update balance1630 // Update balance
1674 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1631 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())
1675 .checked_add(1)1632 .checked_add(1)
1697 .unwrap();1654 .unwrap();
1698 Self::remove_token_index(collection_id, item_id, owner.clone())?;1655 Self::remove_token_index(collection_id, item_id, owner.clone())?;
16991656
1700 // remove approve list
1701 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));
1702
1703 // update balance1657 // update balance
1704 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1658 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())
1705 .checked_sub(item.fraction)1659 .checked_sub(item.fraction)
1719 let item = <NftItemList<T>>::get(collection_id, item_id);1673 let item = <NftItemList<T>>::get(collection_id, item_id);
1720 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;1674 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;
17211675
1722 // remove approve list
1723 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));
1724
1725 // update balance1676 // update balance
1726 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1677 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())
1727 .checked_sub(1)1678 .checked_sub(1)
1732 Ok(())1683 Ok(())
1733 }1684 }
17341685
1735 fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1686 fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {
1736 ensure!(1687 ensure!(
1737 <FungibleItemList<T>>::contains_key(collection_id, item_id),1688 <FungibleItemList<T>>::contains_key(collection_id, owner),
1738 Error::<T>::TokenNotFound1689 Error::<T>::TokenNotFound
1739 );1690 );
1740 let item = <FungibleItemList<T>>::get(collection_id, item_id);1691 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);
1741 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;1692 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);
17421693
1743 // remove approve list
1744 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));
1745
1746 // update balance1694 // update balance
1747 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1695 let new_balance = <Balance<T>>::get(collection_id, owner)
1748 .checked_sub(item.value)1696 .checked_sub(value)
1749 .ok_or(Error::<T>::NumOverflow)?;1697 .ok_or(Error::<T>::NumOverflow)?;
1750 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1698 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);
17511699
1752 <FungibleItemList<T>>::remove(collection_id, item_id);1700 if balance.value - value > 0 {
1701 balance.value -= value;
1702 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);
1703 }
1704 else {
1705 <FungibleItemList<T>>::remove(collection_id, owner);
1706 }
17531707
1754 Ok(())1708 Ok(())
1755 }1709 }
1810 <NftItemList<T>>::get(collection_id, item_id).owner == subject1764 <NftItemList<T>>::get(collection_id, item_id).owner == subject
1811 }1765 }
1812 CollectionMode::Fungible(_) => {1766 CollectionMode::Fungible(_) => {
1813 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1767 <FungibleItemList<T>>::contains_key(collection_id, &subject)
1814 }1768 }
1815 CollectionMode::ReFungible(_) => {1769 CollectionMode::ReFungible(_) => {
1816 <ReFungibleItemList<T>>::get(collection_id, item_id)1770 <ReFungibleItemList<T>>::get(collection_id, item_id)
18331787
1834 fn transfer_fungible(1788 fn transfer_fungible(
1835 collection_id: CollectionId,1789 collection_id: CollectionId,
1836 item_id: TokenId,
1837 value: u128,1790 value: u128,
1838 owner: T::AccountId,1791 owner: &T::AccountId,
1839 new_owner: T::AccountId,1792 recipient: &T::AccountId,
1840 ) -> DispatchResult {1793 ) -> DispatchResult {
1841 ensure!(1794 ensure!(
1842 <FungibleItemList<T>>::contains_key(collection_id, item_id),1795 <FungibleItemList<T>>::contains_key(collection_id, owner),
1843 Error::<T>::TokenNotFound1796 Error::<T>::TokenNotFound
1844 );1797 );
18451798
1846 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1799 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);
1847 let amount = full_item.value;1800 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);
18481801
1849 ensure!(amount >= value, Error::<T>::TokenValueTooLow);1802 // Send balance to recipient (updates balanceOf of recipient)
1803 Self::add_fungible_item(collection_id, recipient, value)?;
18501804
1851 // update balance1805 // update balanceOf of sender
1852 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1806 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);
1853 .checked_sub(value)
1854 .ok_or(Error::<T>::NumOverflow)?;
1855 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);
18561807
1857 let mut new_owner_account_id = 0;1808 // Reduce or remove sender
1858 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1809 if balance.value == value {
1859 if new_owner_items.len() > 0 {
1860 new_owner_account_id = new_owner_items[0];1810 <FungibleItemList<T>>::remove(collection_id, owner);
1861 }1811 }
18621812 else {
1863 // transfer1813 balance.value -= value;
1864 if amount == value && new_owner_account_id == 0 {1814 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);
1865 // change owner
1866 // new owner do not have account
1867 let mut new_full_item = full_item.clone();
1868 new_full_item.owner = new_owner.clone();
1869 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);
1870
1871 // update balance
1872 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())
1873 .checked_add(value)
1874 .ok_or(Error::<T>::NumOverflow)?;
1875 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);
1876
1877 // update index collection
1878 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;
1879 } else {
1880 let mut new_full_item = full_item.clone();
1881 new_full_item.value -= value;
1882
1883 // separate amount
1884 if new_owner_account_id > 0 {
1885 // new owner has account
1886 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);
1887 item.value += value;
1888
1889 // update balance
1890 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())
1891 .checked_add(value)
1892 .ok_or(Error::<T>::NumOverflow)?;
1893 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);
1894
1895 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);
1896 } else {
1897 // new owner do not have account
1898 let item = FungibleItemType {
1899 owner: new_owner.clone(),
1900 value
1901 };
1902
1903 Self::add_fungible_item(collection_id, item)?;
1904 }
1905
1906 if amount == value {
1907 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;
1908
1909 // remove approve list
1910 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));
1911 <FungibleItemList<T>>::remove(collection_id, item_id);
1912 }
1913
1914 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);
1915 }1815 }
19161816
1917 Ok(())1817 Ok(())
2039 // update index collection1939 // update index collection
2040 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1940 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;
20411941
2042 // reset approved list
2043 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));
2044 Ok(())1942 Ok(())
2045 }1943 }
2046 1944
2052 match mode {1950 match mode {
2053 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1951 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),
2054 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1952 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),
2055 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),
2056 _ => ()1953 _ => ()
2057 };1954 };
2058 1955
2131 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2028 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);
2132 }2029 }
21332030
2134 fn init_fungible_token(collection_id: CollectionId, item: &FungibleItemType<T::AccountId>) {2031 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {
2135 let current_index = <ItemListIndex>::get(collection_id)2032 let current_index = <ItemListIndex>::get(collection_id)
2136 .checked_add(1)2033 .checked_add(1)
2137 .unwrap();2034 .unwrap();
2138 let owner = item.owner.clone();
21392035
2140 Self::add_token_index(collection_id, current_index, owner.clone()).unwrap();2036 Self::add_token_index(collection_id, current_index, (*owner).clone()).unwrap();
21412037
2142 <ItemListIndex>::insert(collection_id, current_index);2038 <ItemListIndex>::insert(collection_id, current_index);
21432039
2144 // Update balance2040 // Update balance
2145 let new_balance = <Balance<T>>::get(collection_id, owner.clone())2041 let new_balance = <Balance<T>>::get(collection_id, owner)
2146 .checked_add(item.value)2042 .checked_add(item.value)
2147 .unwrap();2043 .unwrap();
2148 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2044 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);
2149 }2045 }
21502046
2151 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2047 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {
2343 T::AccountId::default()2239 T::AccountId::default()
2344 }2240 }
2345 }2241 }
2346 Some(Call::transfer(new_owner, collection_id, item_id, _value)) => {2242 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {
2347 2243
2348 let mut sponsor_transfer = false;2244 let mut sponsor_transfer = false;
2349 if <Collection<T>>::get(collection_id).sponsor_confirmed {2245 if <Collection<T>>::get(collection_id).sponsor_confirmed {
2352 let collection_mode = <Collection<T>>::get(collection_id).mode;2248 let collection_mode = <Collection<T>>::get(collection_id).mode;
2353 2249
2354 // sponsor timeout2250 // sponsor timeout
2251 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;
2355 sponsor_transfer = match collection_mode {2252 sponsor_transfer = match collection_mode {
2356 CollectionMode::NFT => {2253 CollectionMode::NFT => {
2357 2254
2362 ChainLimit::get().nft_sponsor_transfer_timeout2259 ChainLimit::get().nft_sponsor_transfer_timeout
2363 };2260 };
2364 2261
2365 let basket = <NftTransferBasket<T>>::get(collection_id, item_id);2262 let mut sponsored = true;
2263 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {
2366 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2264 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);
2367 let limit_time = basket + limit.into();2265 let limit_time = last_tx_block + limit.into();
2368 if block_number >= limit_time {2266 if block_number <= limit_time {
2267 sponsored = false;
2268 }
2269 }
2270 if sponsored {
2369 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2271 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);
2370 true
2371 }2272 }
2372 else {2273
2373 false
2374 }2274 sponsored
2375 }2275 }
2376 CollectionMode::Fungible(_) => {2276 CollectionMode::Fungible(_) => {
2377 2277
2382 ChainLimit::get().fungible_sponsor_transfer_timeout2282 ChainLimit::get().fungible_sponsor_transfer_timeout
2383 };2283 };
2384 2284
2385 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, item_id);
2386 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2285 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;
2387 if basket.iter().any(|i| i.address == new_owner.clone())2286 let mut sponsored = true;
2388 {2287 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {
2389 let item = basket.iter_mut().find(|i| i.address == new_owner.clone()).unwrap().clone();2288 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);
2390 let limit_time = item.start_block + limit.into();2289 let limit_time = last_tx_block + limit.into();
2391 if block_number >= limit_time {2290 if block_number <= limit_time {
2392 basket.retain(|x| x.address == item.address);2291 sponsored = false;
2393 basket.push(BasketItem { start_block: block_number, address: new_owner.clone() });
2394 <FungibleTransferBasket<T>>::insert(collection_id, item_id, basket);
2395 true
2396 }2292 }
2397 else {
2398 false
2399 }
2400 }2293 }
2401 else {2294 if sponsored {
2402 basket.push(BasketItem { start_block: block_number, address: new_owner.clone()});2295 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);
2403 true
2404 }2296 }
2297
2298 sponsored
2405 }2299 }
2406 CollectionMode::ReFungible(_) => {2300 CollectionMode::ReFungible(_) => {
2407 2301
2412 ChainLimit::get().refungible_sponsor_transfer_timeout2306 ChainLimit::get().refungible_sponsor_transfer_timeout
2413 };2307 };
2414 2308
2415 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2309 let mut sponsored = true;
2310 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {
2416 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2311 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);
2417 let limit_time = basket + limit.into();2312 let limit_time = last_tx_block + limit.into();
2418 if block_number >= limit_time {2313 if block_number <= limit_time {
2314 sponsored = false;
2315 }
2316 }
2317 if sponsored {
2419 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2318 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);
2420 true
2421 } else {
2422 false
2423 }2319 }
2320
2321 sponsored
2424 }2322 }
2425 _ => {2323 _ => {
2426 false2324 false
modifiedruntime_types.jsondiffbeforeafterboth
42 "Fraction": "u128"42 "Fraction": "u128"
43 },43 },
44 "FungibleItemType": {44 "FungibleItemType": {
45 "Owner": "AccountId",
46 "Value": "u128"45 "Value": "u128"
47 },46 },
48 "NftItemType": {47 "NftItemType": {
72 "VariableOnChainSchema": "Vec<u8>",71 "VariableOnChainSchema": "Vec<u8>",
73 "ConstOnChainSchema": "Vec<u8>"72 "ConstOnChainSchema": "Vec<u8>"
74 },73 },
75 "ApprovePermissions": {
76 "Approved": "AccountId",
77 "Amount": "u128"
78 },
79 "RawData": "Vec<u8>",74 "RawData": "Vec<u8>",
80 "Address": "AccountId",75 "Address": "AccountId",
81 "LookupSource": "AccountId",76 "LookupSource": "AccountId",
85 "variable_data": "Vec<u8>" 80 "variable_data": "Vec<u8>"
86 },81 },
87 "CreateFungibleData": {},82 "CreateFungibleData": {
83 "value": "u128"
84 },
88 "CreateReFungibleData": {85 "CreateReFungibleData": {
89 "const_data": "Vec<u8>",86 "const_data": "Vec<u8>",
104 },101 },
105 "CollectionId": "u32",102 "CollectionId": "u32",
106 "TokenId": "u32",103 "TokenId": "u32",
107 "BasketItem": {
108 "Address": "AccountId",
109 "start_block": "BlockNumber"
110 },
111 "ChainLimits": {104 "ChainLimits": {
112 "collection_numbers_limit": "u32",105 "collection_numbers_limit": "u32",
113 "account_token_ownership_limit": "u32",106 "account_token_ownership_limit": "u32",
modifiedtests/src/confirmSponsorship.test.tsdiffbeforeafterboth
16 createItemExpectSuccess,16 createItemExpectSuccess,
17 findUnusedAddress,17 findUnusedAddress,
18 getGenericResult,18 getGenericResult,
19 enableWhiteListExpectSuccess,
19} from "./util/helpers";20} from "./util/helpers";
20import { Keyring } from "@polkadot/api";21import { Keyring } from "@polkadot/api";
21import { IKeyringPair } from "@polkadot/types/types";22import { IKeyringPair } from "@polkadot/types/types";
72 // Mint token for unused address73 // Mint token for unused address
73 const itemId = await createItemExpectSuccess(collectionId, 'NFT', zeroBalance.address, '//Alice');74 const itemId = await createItemExpectSuccess(collectionId, 'NFT', zeroBalance.address, '//Alice');
7475
75 // Transfer this token from unused address to Alice76 // Transfer this tokens from unused address to Alice
76 const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 0);77 const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 0);
77 const events = await submitTransactionAsync(zeroBalance, zeroToAlice);78 const events = await submitTransactionAsync(zeroBalance, zeroToAlice);
78 const result = getGenericResult(events);79 const result = getGenericResult(events);
86 });87 });
8788
88 it('Fungible: Transfer fees are paid by the sponsor after confirmation', async () => {89 it('Fungible: Transfer fees are paid by the sponsor after confirmation', async () => {
90 const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'Fungible');
91 await setCollectionSponsorExpectSuccess(collectionId, bob.address);
92 await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
93
94 await usingApi(async (api) => {
95 const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
96
97 // Find unused address
98 const zeroBalance = await findUnusedAddress(api);
99
100 // Mint token for unused address
101 const itemId = await createItemExpectSuccess(collectionId, 'Fungible', zeroBalance.address, '//Alice');
102
103 // Transfer this tokens from unused address to Alice
104 const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);
105 const events1 = await submitTransactionAsync(zeroBalance, zeroToAlice);
106 const result1 = getGenericResult(events1);
107
108 const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
109
89 expect(false).to.be.true;110 expect(result1.success).to.be.true;
111 expect(BsponsorBalance.lt(AsponsorBalance)).to.be.true;
112 });
90 });113 });
91114
92 it('ReFungible: Transfer fees are paid by the sponsor after confirmation', async () => {115 it('ReFungible: Transfer fees are paid by the sponsor after confirmation', async () => {
116 const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'ReFungible');
117 await setCollectionSponsorExpectSuccess(collectionId, bob.address);
118 await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
119
120 await usingApi(async (api) => {
121 const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
122
123 // Find unused address
124 const zeroBalance = await findUnusedAddress(api);
125
126 // Mint token for unused address
127 const itemId = await createItemExpectSuccess(collectionId, 'ReFungible', zeroBalance.address, '//Alice');
128
129 // Transfer this tokens from unused address to Alice
130 const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);
131 const events1 = await submitTransactionAsync(zeroBalance, zeroToAlice);
132 const result1 = getGenericResult(events1);
133
134 const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
135
93 expect(false).to.be.true;136 expect(result1.success).to.be.true;
137 expect(BsponsorBalance.lt(AsponsorBalance)).to.be.true;
138 });
94 });139 });
95140
96 it.skip('CreateItem fees are paid by the sponsor after confirmation', async () => {141 it.only('CreateItem fees are paid by the sponsor after confirmation', async () => {
97 // const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
98 // await setCollectionSponsorExpectSuccess(collectionId, bob.address);
99 // await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
100 expect(false).to.be.true;142 const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
143 await setCollectionSponsorExpectSuccess(collectionId, bob.address);
144 await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
145
146 // Enable collection white list
147 await enableWhiteListExpectSuccess(collectionId);
148
149 // Enable public minting
150
151 // Create Item
152
153
154
101 });155 });
102156
103 it('NFT: Sponsoring is rate limited', async () => {157 it('NFT: Sponsoring is rate limited', async () => {
158 const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
159 await setCollectionSponsorExpectSuccess(collectionId, bob.address);
160 await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
161
162 await usingApi(async (api) => {
163 // Find unused address
164 const zeroBalance = await findUnusedAddress(api);
165
166 // Mint token for alice
167 const itemId = await createItemExpectSuccess(collectionId, 'NFT', alice.address, '//Alice');
168
169 // Transfer this token from Alice to unused address and back
170 // Alice to Zero gets sponsored
171 const aliceToZero = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 0);
172 const events1 = await submitTransactionAsync(alice, aliceToZero);
173 const result1 = getGenericResult(events1);
174
175 // Second transfer should fail
176 const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
177 const zeroToAlice = api.tx.nft.transfer(alice.address, collectionId, itemId, 0);
178 const badTransaction = async function () {
179 console.log = function () {};
180 console.error = function () {};
181 await submitTransactionAsync(zeroBalance, zeroToAlice);
182 delete console.log;
183 delete console.error;
184 };
104 expect(false).to.be.true;185 await expect(badTransaction()).to.be.rejectedWith("Inability to pay some fees");
186 const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
187
188 // Try again after Zero gets some balance - now it should succeed
189 const balancetx = api.tx.balances.transfer(zeroBalance.address, 1e15);
190 await submitTransactionAsync(alice, balancetx);
191 const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice);
192 const result2 = getGenericResult(events2);
193
194 expect(result1.success).to.be.true;
195 expect(result2.success).to.be.true;
196 expect(BsponsorBalance.isEqualTo(AsponsorBalance)).to.be.true;
197 });
105 });198 });
106199
107 it('Fungible: Sponsoring is rate limited', async () => {200 it('Fungible: Sponsoring is rate limited', async () => {
201 const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'Fungible');
202 await setCollectionSponsorExpectSuccess(collectionId, bob.address);
203 await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
204
205 await usingApi(async (api) => {
206 // Find unused address
207 const zeroBalance = await findUnusedAddress(api);
208
209 // Mint token for unused address
210 const itemId = await createItemExpectSuccess(collectionId, 'Fungible', zeroBalance.address, '//Alice');
211
212 // Transfer this tokens in parts from unused address to Alice
213 const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);
214 const events1 = await submitTransactionAsync(zeroBalance, zeroToAlice);
215 const result1 = getGenericResult(events1);
216
217 const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
218
219 const badTransaction = async function () {
220 console.log = function () {};
221 console.error = function () {};
222 await submitTransactionAsync(zeroBalance, zeroToAlice);
223 delete console.log;
224 delete console.error;
225 };
226
227 const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
228
229 // Try again after Zero gets some balance - now it should succeed
230 const balancetx = api.tx.balances.transfer(zeroBalance.address, 1e15);
231 await submitTransactionAsync(alice, balancetx);
232 const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice);
233 const result2 = getGenericResult(events2);
234
108 expect(false).to.be.true;235 expect(result1.success).to.be.true;
236 expect(result2.success).to.be.true;
237 expect(BsponsorBalance.isEqualTo(AsponsorBalance)).to.be.true;
238 });
109 });239 });
110240
111 it('ReFungible: Sponsoring is rate limited', async () => {241 it('ReFungible: Sponsoring is rate limited', async () => {
242 const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'ReFungible');
243 await setCollectionSponsorExpectSuccess(collectionId, bob.address);
244 await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
245
246 await usingApi(async (api) => {
247 // Find unused address
248 const zeroBalance = await findUnusedAddress(api);
249
250 // Mint token for alice
251 const itemId = await createItemExpectSuccess(collectionId, 'ReFungible', alice.address, '//Alice');
252
253 // Transfer this token from Alice to unused address and back
254 // Alice to Zero gets sponsored
255 const aliceToZero = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);
256 const events1 = await submitTransactionAsync(alice, aliceToZero);
257 const result1 = getGenericResult(events1);
258
259 // Second transfer should fail
260 const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
261 const zeroToAlice = api.tx.nft.transfer(alice.address, collectionId, itemId, 1);
262 const badTransaction = async function () {
263 console.log = function () {};
264 console.error = function () {};
265 await submitTransactionAsync(zeroBalance, zeroToAlice);
266 delete console.log;
267 delete console.error;
268 };
112 expect(false).to.be.true;269 await expect(badTransaction()).to.be.rejectedWith("Inability to pay some fees");
270 const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
271
272 // Try again after Zero gets some balance - now it should succeed
273 const balancetx = api.tx.balances.transfer(zeroBalance.address, 1e15);
274 await submitTransactionAsync(alice, balancetx);
275 const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice);
276 const result2 = getGenericResult(events2);
277
278 expect(result1.success).to.be.true;
279 expect(result2.success).to.be.true;
280 expect(BsponsorBalance.isEqualTo(AsponsorBalance)).to.be.true;
281 });
113 });282 });
114283
115});284});
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
13import { strToUTF16, utf16ToStr, hexToStr } from '../util/util';13import { strToUTF16, utf16ToStr, hexToStr } from '../util/util';
14import { IKeyringPair } from "@polkadot/types/types";14import { IKeyringPair } from "@polkadot/types/types";
15import { BigNumber } from 'bignumber.js';15import { BigNumber } from 'bignumber.js';
16import { Struct, Enum } from '@polkadot/types/codec';
17import { u128 } from '@polkadot/types/primitive';
1618
17chai.use(chaiAsPromised);19chai.use(chaiAsPromised);
18const expect = chai.expect;20const expect = chai.expect;
258 });260 });
259}261}
262
263export interface CreateFungibleData extends Struct {
264 readonly value: u128;
265};
266
267export interface CreateReFungibleData extends Struct {};
268export interface CreateNftData extends Struct {};
269
270export interface CreateItemData extends Enum {
271 NFT: CreateNftData,
272 Fungible: CreateFungibleData,
273 ReFungible: CreateReFungibleData
274};
260275
261export async function createItemExpectSuccess(collectionId: number, createMode: string, owner: string = '', senderSeed: string = '//Alice') {276export async function createItemExpectSuccess(collectionId: number, createMode: string, owner: string = '', senderSeed: string = '//Alice') {
262 let newItemId: number = 0;277 let newItemId: number = 0;
263 await usingApi(async (api) => {278 await usingApi(async (api) => {
264 const AItemCount = parseInt((await api.query.nft.itemListIndex(collectionId)).toString());279 const AItemCount = parseInt((await api.query.nft.itemListIndex(collectionId)).toString());
280 const Aitem: any = (await api.query.nft.fungibleItemList(collectionId, owner)).toJSON();
281 const AItemBalance = new BigNumber(Aitem.Value);
265282
266 const sender = privateKey(senderSeed);283 const sender = privateKey(senderSeed);
267 if (owner === '') owner = sender.address;284 if (owner === '') owner = sender.address;
285
286 let tx;
287 if (createMode == 'Fungible') {
288 let createData = {fungible: {value: 10}};
289 tx = api.tx.nft.createItem(collectionId, owner, createData);
290 }
291 else {
268 const tx = api.tx.nft.createItem(collectionId, owner, createMode);292 tx = api.tx.nft.createItem(collectionId, owner, createMode);
293 }
269 const events = await submitTransactionAsync(sender, tx);294 const events = await submitTransactionAsync(sender, tx);
270 const result = getCreateItemResult(events);295 const result = getCreateItemResult(events);
271 296
272 const BItemCount = parseInt((await api.query.nft.itemListIndex(collectionId)).toString());297 const BItemCount = parseInt((await api.query.nft.itemListIndex(collectionId)).toString());
298 const Bitem: any = (await api.query.nft.fungibleItemList(collectionId, owner)).toJSON();
299 const BItemBalance = new BigNumber(Bitem.Value);
273300
274 // What to expect301 // What to expect
275 expect(result.success).to.be.true;302 expect(result.success).to.be.true;
303 if (createMode == 'Fungible') {
304 expect(BItemBalance.minus(AItemBalance).toNumber()).to.be.equal(10);
305 }
306 else {
276 expect(BItemCount).to.be.equal(AItemCount+1);307 expect(BItemCount).to.be.equal(AItemCount+1);
308 }
277 expect(collectionId).to.be.equal(result.collectionId);309 expect(collectionId).to.be.equal(result.collectionId);
278 expect(BItemCount).to.be.equal(result.itemId);310 expect(BItemCount).to.be.equal(result.itemId);
279 newItemId = result.itemId;311 newItemId = result.itemId;
280 });312 });
281 return newItemId;313 return newItemId;
282}314}
315
316export async function enableWhiteListExpectSuccess(collectionId: number, senderSeed: string = '//Alice') {
317 await usingApi(async (api) => {
318
319 // Run the transaction
320 const sender = privateKey(senderSeed);
321 const tx = api.tx.nft.setPublicAccessMode(collectionId, 'WhiteList');
322 const events = await submitTransactionAsync(sender, tx);
323 const result = getGenericResult(events);
324
325 // Get the collection
326 const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
327
328 // What to expect
329 expect(result.success).to.be.true;
330 expect(collection.Access).to.be.equal('WhiteList');
331 });
332}
283333