From ff4b3341c8c46f5ee0d8998d72ea24bec10a98d7 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Tue, 02 Mar 2021 08:01:30 +0000 Subject: [PATCH] Merge pull request #119 from usetech-llc/fix/merge-master-to-develop Merge master to develop --- --- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -194,7 +194,7 @@ CollectionLimits { account_token_ownership_limit: 10_000_000, token_limit: u32::max_value(), - sponsored_data_size: u32::max_value(), + sponsored_data_size: ChainLimit::get().custom_data_limit, sponsor_transfer_timeout: 14400, owner_can_transfer: true, owner_can_destroy: true @@ -1110,14 +1110,23 @@ // Transfer permissions check let target_collection = >::get(collection_id); - ensure!( - Self::is_item_owner(sender.clone(), collection_id, item_id) || - ( - target_collection.limits.owner_can_transfer && - Self::is_owner_or_admin_permissions(collection_id, sender.clone()) - ), - Error::::NoPermission - ); + let allowance_limit = if ( + target_collection.limits.owner_can_transfer && + Self::is_owner_or_admin_permissions( + collection_id, + sender.clone(), + ) + ) { + None + } else if let Some(amount) = Self::owned_amount( + sender.clone(), + collection_id, + item_id, + ) { + Some(amount) + } else { + fail!(Error::::NoPermission); + }; if target_collection.access == AccessMode::WhiteList { Self::check_white_list(collection_id, &sender)?; @@ -1129,6 +1138,9 @@ if allowance_exists { allowance += >::get(collection_id, (item_id, &sender, &spender)); } + if let Some(limit) = allowance_limit { + ensure!(limit >= allowance, Error::::TokenValueTooLow); + } >::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance); Ok(()) @@ -1912,6 +1924,36 @@ Ok(()) } + fn owned_amount( + subject: T::AccountId, + collection_id: CollectionId, + item_id: TokenId, + ) -> Option { + let target_collection = >::get(collection_id); + + match target_collection.mode { + CollectionMode::NFT => { + if >::get(collection_id, item_id).owner == subject { + return Some(1) + } + None + }, + CollectionMode::Fungible(_) => { + if >::contains_key(collection_id, &subject) { + return Some(>::get(collection_id, &subject) + .value); + } + None + }, + CollectionMode::ReFungible => >::get(collection_id, item_id) + .owner + .iter() + .find(|i| i.owner == subject) + .map(|i| i.fraction), + CollectionMode::Invalid => None, + } + } + fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool { let target_collection = >::get(collection_id); --- a/tests/src/approve.test.ts +++ b/tests/src/approve.test.ts @@ -2,9 +2,8 @@ // This file is subject to the terms and conditions defined in // file 'LICENSE', which is part of this source code package. // -import { ApiPromise } from '@polkadot/api'; import { IKeyringPair } from '@polkadot/types/types'; -import BN from 'bn.js'; +import { ApiPromise } from '@polkadot/api'; import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; import privateKey from './substrate/privateKey'; @@ -13,16 +12,13 @@ approveExpectFail, approveExpectSuccess, createCollectionExpectSuccess, - createFungibleItemExpectSuccess, createItemExpectSuccess, destroyCollectionExpectSuccess, setCollectionLimitsExpectSuccess, - transferFromExpectSuccess, - U128_MAX, + transferExpectSuccess, } from './util/helpers'; chai.use(chaiAsPromised); -const expect = chai.expect; describe('Integration Test approve(spender, collection_id, item_id, amount):', () => { let Alice: IKeyringPair; @@ -30,7 +26,7 @@ let Charlie: IKeyringPair; before(async () => { - await usingApi(async (api) => { + await usingApi(async () => { Alice = privateKey('//Alice'); Bob = privateKey('//Bob'); Charlie = privateKey('//Charlie'); @@ -38,42 +34,38 @@ }); it('Execute the extrinsic and check approvedList', async () => { - await usingApi(async (api: ApiPromise) => { - const nftCollectionId = await createCollectionExpectSuccess(); - // nft - const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT'); - await approveExpectSuccess(nftCollectionId, newNftTokenId, Alice, Bob); - // fungible - const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); - const newFungibleTokenId = await createItemExpectSuccess(Alice, fungibleCollectionId, 'Fungible'); - await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, Alice, Bob); - // reFungible - const reFungibleCollectionId = - await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); - const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible'); - await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, Alice, Bob); - }); + const nftCollectionId = await createCollectionExpectSuccess(); + // nft + const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT'); + await approveExpectSuccess(nftCollectionId, newNftTokenId, Alice, Bob); + // fungible + const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); + const newFungibleTokenId = await createItemExpectSuccess(Alice, fungibleCollectionId, 'Fungible'); + await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, Alice, Bob); + // reFungible + const reFungibleCollectionId = + await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); + const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible'); + await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, Alice, Bob); }); it('Remove approval by using 0 amount', async () => { - await usingApi(async (api: ApiPromise) => { - const nftCollectionId = await createCollectionExpectSuccess(); - // nft - const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT'); - await approveExpectSuccess(nftCollectionId, newNftTokenId, Alice, Bob, 1); - await approveExpectSuccess(nftCollectionId, newNftTokenId, Alice, Bob, 0); - // fungible - const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); - const newFungibleTokenId = await createItemExpectSuccess(Alice, fungibleCollectionId, 'Fungible'); - await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, Alice, Bob, 1); - await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, Alice, Bob, 0); - // reFungible - const reFungibleCollectionId = - await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); - const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible'); - await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, Alice, Bob, 1); - await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, Alice, Bob, 0); - }); + const nftCollectionId = await createCollectionExpectSuccess(); + // nft + const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT'); + await approveExpectSuccess(nftCollectionId, newNftTokenId, Alice, Bob, 1); + await approveExpectSuccess(nftCollectionId, newNftTokenId, Alice, Bob, 0); + // fungible + const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); + const newFungibleTokenId = await createItemExpectSuccess(Alice, fungibleCollectionId, 'Fungible'); + await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, Alice, Bob, 1); + await approveExpectSuccess(fungibleCollectionId, newFungibleTokenId, Alice, Bob, 0); + // reFungible + const reFungibleCollectionId = + await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); + const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible'); + await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, Alice, Bob, 1); + await approveExpectSuccess(reFungibleCollectionId, newReFungibleTokenId, Alice, Bob, 0); }); it('can be called by collection owner on non-owned item when OwnerCanTransfer == true', async () => { @@ -112,54 +104,72 @@ }); it('Approve for a collection that was destroyed', async () => { - await usingApi(async (api: ApiPromise) => { - // nft - const nftCollectionId = await createCollectionExpectSuccess(); - await destroyCollectionExpectSuccess(nftCollectionId); - await approveExpectFail(nftCollectionId, 1, Alice, Bob); - // fungible - const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); - await destroyCollectionExpectSuccess(fungibleCollectionId); - await approveExpectFail(fungibleCollectionId, 1, Alice, Bob); - // reFungible - const reFungibleCollectionId = - await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); - await destroyCollectionExpectSuccess(reFungibleCollectionId); - await approveExpectFail(reFungibleCollectionId, 1, Alice, Bob); - }); + // nft + const nftCollectionId = await createCollectionExpectSuccess(); + await destroyCollectionExpectSuccess(nftCollectionId); + await approveExpectFail(nftCollectionId, 1, Alice, Bob); + // fungible + const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); + await destroyCollectionExpectSuccess(fungibleCollectionId); + await approveExpectFail(fungibleCollectionId, 1, Alice, Bob); + // reFungible + const reFungibleCollectionId = + await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); + await destroyCollectionExpectSuccess(reFungibleCollectionId); + await approveExpectFail(reFungibleCollectionId, 1, Alice, Bob); }); it('Approve transfer of a token that does not exist', async () => { - await usingApi(async (api: ApiPromise) => { - // nft - const nftCollectionId = await createCollectionExpectSuccess(); - await approveExpectFail(nftCollectionId, 2, Alice, Bob); - // fungible - const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); - await approveExpectFail(fungibleCollectionId, 2, Alice, Bob); - // reFungible - const reFungibleCollectionId = - await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); - await approveExpectFail(reFungibleCollectionId, 2, Alice, Bob); - }); + // nft + const nftCollectionId = await createCollectionExpectSuccess(); + await approveExpectFail(nftCollectionId, 2, Alice, Bob); + // fungible + const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); + await approveExpectFail(fungibleCollectionId, 2, Alice, Bob); + // reFungible + const reFungibleCollectionId = + await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); + await approveExpectFail(reFungibleCollectionId, 2, Alice, Bob); }); it('Approve using the address that does not own the approved token', async () => { - await usingApi(async (api: ApiPromise) => { - const nftCollectionId = await createCollectionExpectSuccess(); - // nft - const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT'); - await approveExpectFail(nftCollectionId, newNftTokenId, Bob, Alice); - // fungible - const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); - const newFungibleTokenId = await createItemExpectSuccess(Alice, fungibleCollectionId, 'Fungible'); - await approveExpectFail(fungibleCollectionId, newFungibleTokenId, Bob, Alice); - // reFungible - const reFungibleCollectionId = - await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); - const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible'); - await approveExpectFail(reFungibleCollectionId, newReFungibleTokenId, Bob, Alice); - }); + const nftCollectionId = await createCollectionExpectSuccess(); + // nft + const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT'); + await approveExpectFail(nftCollectionId, newNftTokenId, Bob, Alice); + // fungible + const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); + const newFungibleTokenId = await createItemExpectSuccess(Alice, fungibleCollectionId, 'Fungible'); + await approveExpectFail(fungibleCollectionId, newFungibleTokenId, Bob, Alice); + // reFungible + const reFungibleCollectionId = + await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); + const newReFungibleTokenId = await createItemExpectSuccess(Alice, reFungibleCollectionId, 'ReFungible'); + await approveExpectFail(reFungibleCollectionId, newReFungibleTokenId, Bob, Alice); + }); + + it('should fail if approved more NFTs than owned', async () => { + const nftCollectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } }); + const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT'); + await transferExpectSuccess(nftCollectionId, newNftTokenId, Alice, Bob, 1, 'NFT'); + await approveExpectSuccess(nftCollectionId, newNftTokenId, Bob, Alice); + await approveExpectFail(nftCollectionId, newNftTokenId, Bob, Alice); + }); + + it('should fail if approved more ReFungibles than owned', async () => { + const nftCollectionId = await createCollectionExpectSuccess({ mode: { type: 'ReFungible' } }); + const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'ReFungible'); + await transferExpectSuccess(nftCollectionId, newNftTokenId, Alice, Bob, 100, 'ReFungible'); + await approveExpectSuccess(nftCollectionId, newNftTokenId, Bob, Alice, 100); + await approveExpectFail(nftCollectionId, newNftTokenId, Bob, Alice, 1); + }); + + it('should fail if approved more Fungibles than owned', async () => { + const nftCollectionId = await createCollectionExpectSuccess({ mode: { type: 'Fungible', decimalPoints: 0 } }); + const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'Fungible'); + await transferExpectSuccess(nftCollectionId, newNftTokenId, Alice, Bob, 10, 'Fungible'); + await approveExpectSuccess(nftCollectionId, newNftTokenId, Bob, Alice, 10); + await approveExpectFail(nftCollectionId, newNftTokenId, Bob, Alice, 1); }); it('fails when called by collection owner on non-owned item when OwnerCanTransfer == false', async () => { -- gitstuff