difftreelog
Refactor fungible logic, add positive tests for sponsorship confirmation
in: master
4 files changed
pallets/nft/src/lib.rsdiffbeforeafterboth--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -152,8 +152,7 @@
#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct FungibleItemType<AccountId> {
- pub owner: AccountId,
+pub struct FungibleItemType {
pub value: u128,
}
@@ -165,13 +164,6 @@
pub variable_data: Vec<u8>,
}
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct ApprovePermissions<AccountId> {
- pub approved: AccountId,
- pub amount: u128,
-}
-
// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
// pub struct VestingItem<AccountId, Moment> {
@@ -182,13 +174,6 @@
// pub amount: u64,
// pub vesting_date: Moment,
// }
-
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct BasketItem<AccountId, BlockNumber> {
- pub address: AccountId,
- pub start_block: BlockNumber,
-}
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
@@ -260,6 +245,7 @@
#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct CreateFungibleData {
+ pub value: u128,
}
#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
@@ -414,12 +400,12 @@
/// Balance owner per collection map
pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;
- /// second parameter: item id + owner account id
- pub ApprovedList get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;
+ /// second parameter: item id + owner account id + spender account id
+ pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;
/// Item collections
pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;
- pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => FungibleItemType<T::AccountId>;
+ pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;
pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;
/// Index list
@@ -427,7 +413,7 @@
/// Tokens transfer baskets
pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;
- pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => Vec<BasketItem<T::AccountId, T::BlockNumber>>;
+ pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;
pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;
// Contract Sponsorship and Ownership
@@ -447,8 +433,8 @@
<Module<T>>::init_nft_token(*_c, _i);
}
- for (_num, _c, _i) in &config.fungible_item_id {
- <Module<T>>::init_fungible_token(*_c, _i);
+ for (collection_id, account_id, fungible_item) in &config.fungible_item_id {
+ <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);
}
for (_num, _c, _i) in &config.refungible_item_id {
@@ -619,7 +605,7 @@
Self::check_owner_permissions(collection_id, sender)?;
<AddressTokens<T>>::remove_prefix(collection_id);
- <ApprovedList<T>>::remove_prefix(collection_id);
+ <Allowances<T>>::remove_prefix(collection_id);
<Balance<T>>::remove_prefix(collection_id);
<ItemListIndex>::remove(collection_id);
<AdminList<T>>::remove(collection_id);
@@ -1015,7 +1001,7 @@
///
/// * item_id: ID of NFT to burn.
#[weight = T::WeightInfo::burn_item()]
- pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {
+ pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {
let sender = ensure_signed(origin)?;
Self::collection_exists(collection_id)?;
@@ -1033,7 +1019,7 @@
match target_collection.mode
{
CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,
- CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,
+ CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, collection_id, value)?,
CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,
_ => ()
};
@@ -1089,7 +1075,7 @@
match target_collection.mode
{
CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,
- CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,
+ CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,
CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,
_ => ()
};
@@ -1113,7 +1099,7 @@
///
/// * item_id: ID of the item.
#[weight = T::WeightInfo::approve()]
- pub fn approve(origin, approved: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {
+ pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {
let sender = ensure_signed(origin)?;
@@ -1125,28 +1111,15 @@
if target_collection.access == AccessMode::WhiteList {
Self::check_white_list(collection_id, &sender)?;
- Self::check_white_list(collection_id, &approved)?;
+ Self::check_white_list(collection_id, &spender)?;
}
- // amount param stub
- let amount = 100000000;
-
- let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));
- if list_exists {
-
- let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));
- let item_contains = list.iter().any(|i| i.approved == approved);
-
- if !item_contains {
- list.push(ApprovePermissions { approved: approved.clone(), amount: amount });
- <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);
- }
- } else {
-
- let mut list = Vec::new();
- list.push(ApprovePermissions { approved: approved.clone(), amount: amount });
- <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);
+ let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));
+ let mut allowance: u128 = amount;
+ if allowance_exists {
+ allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));
}
+ <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);
Ok(())
}
@@ -1176,15 +1149,12 @@
let sender = ensure_signed(origin)?;
let mut appoved_transfer = false;
- // Check approve
- if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {
- let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));
- let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());
- if opt_item.is_some()
- {
- appoved_transfer = true;
- ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);
- }
+ // Check approval
+ let mut approval: u128 = 0;
+ if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &recipient)) {
+ approval = <Allowances<T>>::get(collection_id, (item_id, &from, &recipient));
+ ensure!(approval >= value, Error::<T>::TokenValueNotEnough);
+ appoved_transfer = true;
}
let target_collection = <Collection<T>>::get(collection_id);
@@ -1194,23 +1164,25 @@
// Transfer permissions check
ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),
- Error::<T>::NoPermission);
+ Error::<T>::NoPermission);
if target_collection.access == AccessMode::WhiteList {
Self::check_white_list(collection_id, &sender)?;
Self::check_white_list(collection_id, &recipient)?;
}
- // remove approve
- let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))
- .into_iter().filter(|i| i.approved != sender.clone()).collect();
- <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);
-
+ // Reduce approval by transferred amount or remove if remaining approval drops to 0
+ if approval - value > 0 {
+ <Allowances<T>>::insert(collection_id, (item_id, &from, &recipient), approval - value);
+ }
+ else {
+ <Allowances<T>>::remove(collection_id, (item_id, &from, &recipient));
+ }
match target_collection.mode
{
CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,
- CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,
+ CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &from, &recipient)?,
CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,
_ => ()
};
@@ -1574,13 +1546,8 @@
Self::add_nft_item(collection_id, item)?;
},
- CreateItemData::Fungible(_) => {
- let item = FungibleItemType {
- owner,
- value: (10 as u128).pow(collection.decimal_points as u32)
- };
-
- Self::add_fungible_item(collection_id, item)?;
+ CreateItemData::Fungible(data) => {
+ Self::add_fungible_item(collection_id, &owner, data.value)?;
},
CreateItemData::ReFungible(data) => {
let mut owner_list = Vec::new();
@@ -1603,27 +1570,25 @@
Ok(())
}
- fn add_fungible_item(collection_id: CollectionId, item: FungibleItemType<T::AccountId>) -> DispatchResult {
- let current_index = <ItemListIndex>::get(collection_id)
- .checked_add(1)
- .ok_or(Error::<T>::NumOverflow)?;
- let itemcopy = item.clone();
- let owner = item.owner.clone();
+ fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {
- Self::add_token_index(collection_id, current_index, owner.clone())?;
+ // Does new owner already have an account?
+ let mut balance: u128 = 0;
+ if <FungibleItemList<T>>::contains_key(collection_id, owner) {
+ balance = <FungibleItemList<T>>::get(collection_id, owner).value;
+ }
- <ItemListIndex>::insert(collection_id, current_index);
- <FungibleItemList<T>>::insert(collection_id, current_index, itemcopy);
+ // Mint
+ let item = FungibleItemType {
+ value: balance + value
+ };
+ <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);
- // Add current block
- let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();
- <FungibleTransferBasket<T>>::insert(collection_id, current_index, v);
-
// Update balance
- let new_balance = <Balance<T>>::get(collection_id, owner.clone())
- .checked_add(item.value)
+ let new_balance = <Balance<T>>::get(collection_id, owner)
+ .checked_add(value)
.ok_or(Error::<T>::NumOverflow)?;
- <Balance<T>>::insert(collection_id, owner.clone(), new_balance);
+ <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);
Ok(())
}
@@ -1642,10 +1607,6 @@
<ItemListIndex>::insert(collection_id, current_index);
<ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);
- // Add current block
- let block_number: T::BlockNumber = 0.into();
- <ReFungibleTransferBasket<T>>::insert(collection_id, current_index, block_number);
-
// Update balance
let new_balance = <Balance<T>>::get(collection_id, owner.clone())
.checked_add(value)
@@ -1665,10 +1626,6 @@
<ItemListIndex>::insert(collection_id, current_index);
<NftItemList<T>>::insert(collection_id, current_index, item);
-
- // Add current block
- let block_number: T::BlockNumber = 0.into();
- <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);
// Update balance
let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())
@@ -1697,9 +1654,6 @@
.unwrap();
Self::remove_token_index(collection_id, item_id, owner.clone())?;
- // remove approve list
- <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));
-
// update balance
let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())
.checked_sub(item.fraction)
@@ -1718,9 +1672,6 @@
);
let item = <NftItemList<T>>::get(collection_id, item_id);
Self::remove_token_index(collection_id, item_id, item.owner.clone())?;
-
- // remove approve list
- <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));
// update balance
let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())
@@ -1732,24 +1683,27 @@
Ok(())
}
- fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {
+ fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {
ensure!(
- <FungibleItemList<T>>::contains_key(collection_id, item_id),
+ <FungibleItemList<T>>::contains_key(collection_id, owner),
Error::<T>::TokenNotFound
);
- let item = <FungibleItemList<T>>::get(collection_id, item_id);
- Self::remove_token_index(collection_id, item_id, item.owner.clone())?;
-
- // remove approve list
- <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));
+ let mut balance = <FungibleItemList<T>>::get(collection_id, owner);
+ ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);
// update balance
- let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())
- .checked_sub(item.value)
+ let new_balance = <Balance<T>>::get(collection_id, owner)
+ .checked_sub(value)
.ok_or(Error::<T>::NumOverflow)?;
- <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);
+ <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);
- <FungibleItemList<T>>::remove(collection_id, item_id);
+ if balance.value - value > 0 {
+ balance.value -= value;
+ <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);
+ }
+ else {
+ <FungibleItemList<T>>::remove(collection_id, owner);
+ }
Ok(())
}
@@ -1810,7 +1764,7 @@
<NftItemList<T>>::get(collection_id, item_id).owner == subject
}
CollectionMode::Fungible(_) => {
- <FungibleItemList<T>>::get(collection_id, item_id).owner == subject
+ <FungibleItemList<T>>::contains_key(collection_id, &subject)
}
CollectionMode::ReFungible(_) => {
<ReFungibleItemList<T>>::get(collection_id, item_id)
@@ -1833,85 +1787,31 @@
fn transfer_fungible(
collection_id: CollectionId,
- item_id: TokenId,
value: u128,
- owner: T::AccountId,
- new_owner: T::AccountId,
+ owner: &T::AccountId,
+ recipient: &T::AccountId,
) -> DispatchResult {
ensure!(
- <FungibleItemList<T>>::contains_key(collection_id, item_id),
+ <FungibleItemList<T>>::contains_key(collection_id, owner),
Error::<T>::TokenNotFound
);
- let full_item = <FungibleItemList<T>>::get(collection_id, item_id);
- let amount = full_item.value;
+ let mut balance = <FungibleItemList<T>>::get(collection_id, owner);
+ ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);
- ensure!(amount >= value, Error::<T>::TokenValueTooLow);
+ // Send balance to recipient (updates balanceOf of recipient)
+ Self::add_fungible_item(collection_id, recipient, value)?;
- // update balance
- let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())
- .checked_sub(value)
- .ok_or(Error::<T>::NumOverflow)?;
- <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);
+ // update balanceOf of sender
+ <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);
- let mut new_owner_account_id = 0;
- let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());
- if new_owner_items.len() > 0 {
- new_owner_account_id = new_owner_items[0];
+ // Reduce or remove sender
+ if balance.value == value {
+ <FungibleItemList<T>>::remove(collection_id, owner);
}
-
- // transfer
- if amount == value && new_owner_account_id == 0 {
- // change owner
- // new owner do not have account
- let mut new_full_item = full_item.clone();
- new_full_item.owner = new_owner.clone();
- <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);
-
- // update balance
- let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())
- .checked_add(value)
- .ok_or(Error::<T>::NumOverflow)?;
- <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);
-
- // update index collection
- Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;
- } else {
- let mut new_full_item = full_item.clone();
- new_full_item.value -= value;
-
- // separate amount
- if new_owner_account_id > 0 {
- // new owner has account
- let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);
- item.value += value;
-
- // update balance
- let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())
- .checked_add(value)
- .ok_or(Error::<T>::NumOverflow)?;
- <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);
-
- <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);
- } else {
- // new owner do not have account
- let item = FungibleItemType {
- owner: new_owner.clone(),
- value
- };
-
- Self::add_fungible_item(collection_id, item)?;
- }
-
- if amount == value {
- Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;
-
- // remove approve list
- <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));
- <FungibleItemList<T>>::remove(collection_id, item_id);
- }
-
- <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);
+ else {
+ balance.value -= value;
+ <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);
}
Ok(())
@@ -2039,8 +1939,6 @@
// update index collection
Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;
- // reset approved list
- <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));
Ok(())
}
@@ -2052,7 +1950,6 @@
match mode {
CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),
CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),
- CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),
_ => ()
};
@@ -2131,21 +2028,20 @@
<Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);
}
- fn init_fungible_token(collection_id: CollectionId, item: &FungibleItemType<T::AccountId>) {
+ fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {
let current_index = <ItemListIndex>::get(collection_id)
.checked_add(1)
.unwrap();
- let owner = item.owner.clone();
- Self::add_token_index(collection_id, current_index, owner.clone()).unwrap();
+ Self::add_token_index(collection_id, current_index, (*owner).clone()).unwrap();
<ItemListIndex>::insert(collection_id, current_index);
// Update balance
- let new_balance = <Balance<T>>::get(collection_id, owner.clone())
+ let new_balance = <Balance<T>>::get(collection_id, owner)
.checked_add(item.value)
.unwrap();
- <Balance<T>>::insert(collection_id, owner.clone(), new_balance);
+ <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);
}
fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {
@@ -2343,7 +2239,7 @@
T::AccountId::default()
}
}
- Some(Call::transfer(new_owner, collection_id, item_id, _value)) => {
+ Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {
let mut sponsor_transfer = false;
if <Collection<T>>::get(collection_id).sponsor_confirmed {
@@ -2352,6 +2248,7 @@
let collection_mode = <Collection<T>>::get(collection_id).mode;
// sponsor timeout
+ let block_number = <system::Module<T>>::block_number() as T::BlockNumber;
sponsor_transfer = match collection_mode {
CollectionMode::NFT => {
@@ -2362,16 +2259,19 @@
ChainLimit::get().nft_sponsor_transfer_timeout
};
- let basket = <NftTransferBasket<T>>::get(collection_id, item_id);
- let block_number = <system::Module<T>>::block_number() as T::BlockNumber;
- let limit_time = basket + limit.into();
- if block_number >= limit_time {
- <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);
- true
+ let mut sponsored = true;
+ if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {
+ let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);
+ let limit_time = last_tx_block + limit.into();
+ if block_number <= limit_time {
+ sponsored = false;
+ }
}
- else {
- false
+ if sponsored {
+ <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);
}
+
+ sponsored
}
CollectionMode::Fungible(_) => {
@@ -2382,26 +2282,20 @@
ChainLimit::get().fungible_sponsor_transfer_timeout
};
- let mut basket = <FungibleTransferBasket<T>>::get(collection_id, item_id);
let block_number = <system::Module<T>>::block_number() as T::BlockNumber;
- if basket.iter().any(|i| i.address == new_owner.clone())
- {
- let item = basket.iter_mut().find(|i| i.address == new_owner.clone()).unwrap().clone();
- let limit_time = item.start_block + limit.into();
- if block_number >= limit_time {
- basket.retain(|x| x.address == item.address);
- basket.push(BasketItem { start_block: block_number, address: new_owner.clone() });
- <FungibleTransferBasket<T>>::insert(collection_id, item_id, basket);
- true
- }
- else {
- false
+ let mut sponsored = true;
+ if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {
+ let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);
+ let limit_time = last_tx_block + limit.into();
+ if block_number <= limit_time {
+ sponsored = false;
}
}
- else {
- basket.push(BasketItem { start_block: block_number, address: new_owner.clone()});
- true
+ if sponsored {
+ <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);
}
+
+ sponsored
}
CollectionMode::ReFungible(_) => {
@@ -2412,15 +2306,19 @@
ChainLimit::get().refungible_sponsor_transfer_timeout
};
- let basket = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);
- let block_number = <system::Module<T>>::block_number() as T::BlockNumber;
- let limit_time = basket + limit.into();
- if block_number >= limit_time {
+ let mut sponsored = true;
+ if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {
+ let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);
+ let limit_time = last_tx_block + limit.into();
+ if block_number <= limit_time {
+ sponsored = false;
+ }
+ }
+ if sponsored {
<ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);
- true
- } else {
- false
}
+
+ sponsored
}
_ => {
false
runtime_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",
tests/src/confirmSponsorship.test.tsdiffbeforeafterboth16 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 address73 const itemId = await createItemExpectSuccess(collectionId, 'NFT', zeroBalance.address, '//Alice');74 const itemId = await createItemExpectSuccess(collectionId, 'NFT', zeroBalance.address, '//Alice');747575 // Transfer this token from unused address to Alice76 // Transfer this tokens from unused address to Alice76 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 });878888 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');9394 await usingApi(async (api) => {95 const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());9697 // Find unused address98 const zeroBalance = await findUnusedAddress(api);99100 // Mint token for unused address101 const itemId = await createItemExpectSuccess(collectionId, 'Fungible', zeroBalance.address, '//Alice');102103 // Transfer this tokens from unused address to Alice104 const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);105 const events1 = await submitTransactionAsync(zeroBalance, zeroToAlice);106 const result1 = getGenericResult(events1);107108 const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());10989 expect(false).to.be.true;110 expect(result1.success).to.be.true;111 expect(BsponsorBalance.lt(AsponsorBalance)).to.be.true;112 });90 });113 });9111492 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');119120 await usingApi(async (api) => {121 const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());122123 // Find unused address124 const zeroBalance = await findUnusedAddress(api);125126 // Mint token for unused address127 const itemId = await createItemExpectSuccess(collectionId, 'ReFungible', zeroBalance.address, '//Alice');128129 // Transfer this tokens from unused address to Alice130 const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);131 const events1 = await submitTransactionAsync(zeroBalance, zeroToAlice);132 const result1 = getGenericResult(events1);133134 const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());13593 expect(false).to.be.true;136 expect(result1.success).to.be.true;137 expect(BsponsorBalance.lt(AsponsorBalance)).to.be.true;138 });94 });139 });9514096 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');145146 // Enable collection white list 147 await enableWhiteListExpectSuccess(collectionId);148149 // Enable public minting150151 // Create Item152153154101 });155 });102156103 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');161162 await usingApi(async (api) => {163 // Find unused address164 const zeroBalance = await findUnusedAddress(api);165166 // Mint token for alice167 const itemId = await createItemExpectSuccess(collectionId, 'NFT', alice.address, '//Alice');168169 // Transfer this token from Alice to unused address and back170 // Alice to Zero gets sponsored171 const aliceToZero = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 0);172 const events1 = await submitTransactionAsync(alice, aliceToZero);173 const result1 = getGenericResult(events1);174175 // Second transfer should fail176 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());187188 // Try again after Zero gets some balance - now it should succeed189 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);193194 expect(result1.success).to.be.true;195 expect(result2.success).to.be.true;196 expect(BsponsorBalance.isEqualTo(AsponsorBalance)).to.be.true;197 });105 });198 });106199107 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');204205 await usingApi(async (api) => {206 // Find unused address207 const zeroBalance = await findUnusedAddress(api);208209 // Mint token for unused address210 const itemId = await createItemExpectSuccess(collectionId, 'Fungible', zeroBalance.address, '//Alice');211212 // Transfer this tokens in parts from unused address to Alice213 const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);214 const events1 = await submitTransactionAsync(zeroBalance, zeroToAlice);215 const result1 = getGenericResult(events1);216217 const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());218219 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 };226227 const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());228229 // Try again after Zero gets some balance - now it should succeed230 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);234108 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 });110240111 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');245246 await usingApi(async (api) => {247 // Find unused address248 const zeroBalance = await findUnusedAddress(api);249250 // Mint token for alice251 const itemId = await createItemExpectSuccess(collectionId, 'ReFungible', alice.address, '//Alice');252253 // Transfer this token from Alice to unused address and back254 // Alice to Zero gets sponsored255 const aliceToZero = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);256 const events1 = await submitTransactionAsync(alice, aliceToZero);257 const result1 = getGenericResult(events1);258259 // Second transfer should fail260 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());271272 // Try again after Zero gets some balance - now it should succeed273 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);277278 expect(result1.success).to.be.true;279 expect(result2.success).to.be.true;280 expect(BsponsorBalance.isEqualTo(AsponsorBalance)).to.be.true;281 });113 });282 });114283115});284});tests/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');
+ });
+}