From 008dfe41f9df81170fc514f7a72379eabb50c7d7 Mon Sep 17 00:00:00 2001 From: Trubnikov Sergey Date: Wed, 10 May 2023 06:25:41 +0000 Subject: [PATCH] feat: erc nesting tests + some fixes --- --- a/pallets/balances-adapter/src/lib.rs +++ b/pallets/balances-adapter/src/lib.rs @@ -104,22 +104,20 @@ from: &T::CrossAccountId, nesting_budget: &dyn Budget, ) -> Result { - if spender.conv_eq(from) { - return Ok(0); - } - - if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) { + if let Some((collection_id, token_id)) = + T::CrossTokenAddressMapping::address_to_token(from) + { ensure!( >::check_indirectly_owned( spender.clone(), - source.0, - source.1, + collection_id, + token_id, None, nesting_budget )?, >::ApprovedValueTooLow, ); - } else if spender != from { + } else if !spender.conv_eq(from) { return Ok(0); } @@ -183,7 +181,7 @@ amount: u128, nesting_budget: &dyn Budget, ) -> DispatchResultWithPostInfo { - let allowance = Self::check_allowed(collection, spender, from, amount, nesting_budget)?; + let allowance = Self::check_allowed(spender, from, nesting_budget)?; if allowance < amount { return Err(>::ApprovedValueTooLow.into()); } --- a/pallets/fungible/src/erc.rs +++ b/pallets/fungible/src/erc.rs @@ -109,7 +109,8 @@ .recorder .weight_calls_budget(>::find_parent()); - >::transfer(self, &caller, &to, amount, &budget).map_err(|_| "transfer error")?; + >::transfer(self, &caller, &to, amount, &budget) + .map_err(|e| dispatch_to_evm::(e.error))?; Ok(true) } --- a/tests/src/eth/nesting/nest.test.ts +++ b/tests/src/eth/nesting/nest.test.ts @@ -187,4 +187,80 @@ .call()).to.be.rejectedWith('SourceCollectionIsNotAllowedToNest'); }); }); + + describe('Fungible', () => { + async function createFungibleCollection(helper: EthUniqueHelper, owner: string, mode: 'ft' | 'native ft') { + if (mode === 'ft') { + const {collectionAddress} = await helper.eth.createFungibleCollection(owner, '', 18, '', ''); + const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner); + await contract.methods.mint(owner, 100n).send({from: owner}); + return {collectionAddress, contract}; + } + + // native ft + const collectionAddress = helper.ethAddress.fromCollectionId(0); + const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner); + return {collectionAddress, contract}; + } + + [ + {mode: 'ft' as const}, + {mode: 'native ft' as const}, + ].map(testCase => { + itEth(`Allow nest [${testCase.mode}]`, async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner); + const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode); + + const mintingTargetTokenIdResult = await targetContract.methods.mint(owner).send({from: owner}); + const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId; + const targetTokenAddress = helper.ethAddress.fromTokenId(targetCollectionId, targetTokenId); + + await ftContract.methods.transfer(targetTokenAddress, 10n).send({from: owner}); + expect(await ftContract.methods.balanceOf(targetTokenAddress).call({from: owner})).to.be.equal('10'); + }); + }); + + [ + {mode: 'ft' as const}, + {mode: 'native ft' as const}, + ].map(testCase => { + itEth(`Allow partial/full unnest [${testCase.mode}]`, async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner); + const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode); + + const mintingTargetTokenIdResult = await targetContract.methods.mint(owner).send({from: owner}); + const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId; + const targetTokenAddress = helper.ethAddress.fromTokenId(targetCollectionId, targetTokenId); + + await ftContract.methods.transfer(targetTokenAddress, 10n).send({from: owner}); + + await ftContract.methods.transferFrom(targetTokenAddress, owner, 5n).send({from: owner}); + expect(await ftContract.methods.balanceOf(targetTokenAddress).call({from: owner})).to.be.equal('5'); + + await ftContract.methods.transferFrom(targetTokenAddress, owner, 5n).send({from: owner}); + expect(await ftContract.methods.balanceOf(targetTokenAddress).call({from: owner})).to.be.equal('0'); + }); + }); + + [ + {mode: 'ft' as const}, + {mode: 'native ft' as const}, + ].map(testCase => { + itEth(`Disallow nest into collection without nesting permission [${testCase.mode}]`, async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner); + await targetContract.methods.setCollectionNesting(false).send({from: owner}); + + const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode); + + const mintingTargetTokenIdResult = await targetContract.methods.mint(owner).send({from: owner}); + const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId; + const targetTokenAddress = helper.ethAddress.fromTokenId(targetCollectionId, targetTokenId); + + await expect(ftContract.methods.transfer(targetTokenAddress, 10n).call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest'); + }); + }); + }); }); --- a/tests/src/nativeFungible.test.ts +++ b/tests/src/nativeFungible.test.ts @@ -207,7 +207,7 @@ )).to.be.rejectedWith('BadOrigin'); }); - itSub.only('Nest into NFT token()', async ({helper}) => { + itSub('Nest into NFT token()', async ({helper}) => { const nftCollection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true}}}); const targetToken = await nftCollection.mintToken(alice); --- a/tests/src/sub/nesting/unnesting.negative.test.ts +++ b/tests/src/sub/nesting/unnesting.negative.test.ts @@ -58,7 +58,7 @@ {mode: md.mode, restrictedMode: true}, {mode: md.mode, restrictedMode: false}, ].map(testCase => { - itSub.only(`Fungible: disallows a non-Owner to unnest someone else's token [${testCase.mode}${testCase.restrictedMode ? ' (Restricted nesting)' : ''}]`, async ({helper}) => { + itSub(`Fungible: disallows a non-Owner to unnest someone else's token [${testCase.mode}${testCase.restrictedMode ? ' (Restricted nesting)' : ''}]`, async ({helper}) => { const collectionNFT = await helper.nft.mintCollection(alice); const collectionFT = await ( testCase.mode === 'ft' -- gitstuff