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
--- a/runtime_types.json
+++ b/runtime_types.json
@@ -42,7 +42,6 @@
       "Fraction": "u128"
     },
     "FungibleItemType": {
-      "Owner": "AccountId",
       "Value": "u128"
     },
     "NftItemType": {
@@ -72,10 +71,6 @@
       "VariableOnChainSchema": "Vec<u8>",
       "ConstOnChainSchema": "Vec<u8>"
     },
-    "ApprovePermissions": {
-      "Approved": "AccountId",
-      "Amount": "u128"
-    },
     "RawData": "Vec<u8>",
     "Address": "AccountId",
     "LookupSource": "AccountId",
@@ -84,7 +79,9 @@
       "const_data": "Vec<u8>",
       "variable_data": "Vec<u8>" 
     },
-    "CreateFungibleData": {},
+    "CreateFungibleData": {
+      "value": "u128"
+    },
     "CreateReFungibleData": {
       "const_data": "Vec<u8>",
       "variable_data": "Vec<u8>" 
@@ -104,10 +101,6 @@
     },
     "CollectionId": "u32",
     "TokenId": "u32",
-    "BasketItem": {
-      "Address": "AccountId",
-      "start_block": "BlockNumber"
-    },
     "ChainLimits": {
       "collection_numbers_limit": "u32",
       "account_token_ownership_limit": "u32",
modifiedtests/src/confirmSponsorship.test.tsdiffbeforeafterboth
--- a/tests/src/confirmSponsorship.test.ts
+++ b/tests/src/confirmSponsorship.test.ts
@@ -16,6 +16,7 @@
   createItemExpectSuccess,
   findUnusedAddress,
   getGenericResult,
+  enableWhiteListExpectSuccess,
 } from "./util/helpers";
 import { Keyring } from "@polkadot/api";
 import { IKeyringPair } from "@polkadot/types/types";
@@ -72,7 +73,7 @@
       // Mint token for unused address
       const itemId = await createItemExpectSuccess(collectionId, 'NFT', zeroBalance.address, '//Alice');
 
-      // Transfer this token from unused address to Alice
+      // 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);
@@ -86,30 +87,198 @@
   });
 
   it('Fungible: Transfer fees are paid by the sponsor after confirmation', async () => {
-    expect(false).to.be.true;
+    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(collectionId, 'Fungible', zeroBalance.address, '//Alice');
+
+      // 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 () => {
-    expect(false).to.be.true;
+    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(collectionId, 'ReFungible', zeroBalance.address, '//Alice');
+
+      // 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.skip('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');
-    expect(false).to.be.true;
+  it.only('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(collectionId);
+
+    // Enable public minting
+
+    // Create Item
+
+
+
   });
 
   it('NFT: Sponsoring is rate limited', async () => {
-    expect(false).to.be.true;
+    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(collectionId, 'NFT', alice.address, '//Alice');
+
+      // 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 () => {
-    expect(false).to.be.true;
+    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(collectionId, 'Fungible', zeroBalance.address, '//Alice');
+
+      // 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 () => {
-    expect(false).to.be.true;
+    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(collectionId, 'ReFungible', alice.address, '//Alice');
+
+      // 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;
+    });
   });
 
 });
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -13,6 +13,8 @@
 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;
@@ -258,25 +260,73 @@
   });
 }
 
+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(collectionId: number, createMode: string, owner: string = '', senderSeed: string = '//Alice') {
   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);
 
     const sender = privateKey(senderSeed);
     if (owner === '') owner = sender.address;
-    const tx = api.tx.nft.createItem(collectionId, owner, createMode);
+
+    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(collectionId: number, senderSeed: string = '//Alice') {
+  await usingApi(async (api) => {
+
+    // Run the transaction
+    const sender = privateKey(senderSeed);
+    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');
+  });
+}