From 012bbc1934d8c4cd12c6bb3e471596a030133646 Mon Sep 17 00:00:00 2001 From: Trubnikov Sergey Date: Wed, 17 May 2023 16:23:19 +0000 Subject: [PATCH] fix: native ft nesting --- --- a/Cargo.lock +++ b/Cargo.lock @@ -6969,6 +6969,7 @@ "frame-benchmarking", "frame-support", "frame-system", + "pallet-balances", "pallet-common", "pallet-evm", "pallet-evm-coder-substrate", --- a/pallets/balances-adapter/src/lib.rs +++ b/pallets/balances-adapter/src/lib.rs @@ -148,24 +148,6 @@ .map_err(|_| sp_runtime::ArithmeticError::Overflow)?, ExistenceRequirement::AllowDeath, )?; - - >::nest_if_sent_to_token( - from.clone(), - to, - NATIVE_FUNGIBLE_COLLECTION_ID, - TokenId::default(), - nesting_budget, - )?; - - let balance_from: u128 = - ::Currency::free_balance(from.as_sub()).into(); - if balance_from == 0 { - >::unnest_if_nested( - from, - NATIVE_FUNGIBLE_COLLECTION_ID, - TokenId::default(), - ); - } }; Ok(PostDispatchInfo { --- a/pallets/nonfungible/Cargo.toml +++ b/pallets/nonfungible/Cargo.toml @@ -12,6 +12,7 @@ frame-benchmarking = { workspace = true, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } +pallet-balances = { workspace = true } pallet-common = { workspace = true } pallet-evm = { workspace = true } pallet-evm-coder-substrate = { workspace = true } --- a/pallets/nonfungible/src/lib.rs +++ b/pallets/nonfungible/src/lib.rs @@ -166,7 +166,11 @@ #[pallet::config] pub trait Config: - frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config + frame_system::Config + + pallet_common::Config + + pallet_structure::Config + + pallet_evm::Config + + pallet_balances::Config { type WeightInfo: WeightInfo; } @@ -1325,11 +1329,15 @@ } fn nest(under: (CollectionId, TokenId), to_nest: (CollectionId, TokenId)) { - >::insert((under.0, under.1, (to_nest.0, to_nest.1)), true); + if to_nest.0 != pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID { + >::insert((under.0, under.1, to_nest), true); + } } fn unnest(under: (CollectionId, TokenId), to_unnest: (CollectionId, TokenId)) { - >::remove((under.0, under.1, to_unnest)); + if to_unnest.0 != pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID { + >::remove((under.0, under.1, to_unnest)); + } } fn collection_has_tokens(collection_id: CollectionId) -> bool { @@ -1339,18 +1347,37 @@ } fn token_has_children(collection_id: CollectionId, token_id: TokenId) -> bool { - >::iter_prefix((collection_id, token_id)) - .next() - .is_some() + let address = T::CrossTokenAddressMapping::token_to_address(collection_id, token_id); + let balance = >::free_balance(address.as_sub()); + + balance > T::Balance::default() + || >::iter_prefix((collection_id, token_id)) + .next() + .is_some() } pub fn token_children_ids(collection_id: CollectionId, token_id: TokenId) -> Vec { - >::iter_prefix((collection_id, token_id)) - .map(|((child_collection_id, child_id), _)| TokenChild { - collection: child_collection_id, - token: child_id, + let mut tokens: Vec<_> = vec![]; + + let address = T::CrossTokenAddressMapping::token_to_address(collection_id, token_id); + let balance = >::free_balance(address.as_sub()); + if balance > T::Balance::default() { + tokens.push(TokenChild { + token: TokenId(0), + collection: pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID, }) - .collect() + } + + tokens.extend( + >::iter_prefix((collection_id, token_id)).map( + |((child_collection_id, child_id), _)| TokenChild { + collection: child_collection_id, + token: child_id, + }, + ), + ); + + tokens } /// Mint single NFT token. --- a/tests/src/eth/nesting/nest.test.ts +++ b/tests/src/eth/nesting/nest.test.ts @@ -248,7 +248,7 @@ {mode: 'ft' as const}, {mode: 'native ft' as const}, ].map(testCase => { - itEth(`Disallow nest into collection without nesting permission [${testCase.mode}]`, async ({helper}) => { + itEth(`Disallow nest into collection without nesting permission [${testCase.mode}] (except for native fungible collection)`, 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}); @@ -259,7 +259,11 @@ 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'); + if (testCase.mode === 'ft') { + await expect(ftContract.methods.transfer(targetTokenAddress, 10n).call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest'); + } else { + await expect(ftContract.methods.transfer(targetTokenAddress, 10n).call({from: owner})).to.be.not.rejected; + } }); }); }); --- a/tests/src/sub/nesting/nesting.negative.test.ts +++ b/tests/src/sub/nesting/nesting.negative.test.ts @@ -58,7 +58,7 @@ {mode: 'ft'}, {mode: 'nativeFt'}, ].map(testCase => { - itSub(`Owner cannot nest [${testCase.mode}] if nesting is disabled`, async ({helper}) => { + itSub(`Owner cannot nest [${testCase.mode}] if nesting is disabled (except for native fungible collection)`, async ({helper}) => { // Create default collection, permissions are not set: const aliceNFTCollection = await helper.nft.mintCollection(alice); const targetToken = await aliceNFTCollection.mintToken(alice); @@ -66,15 +66,22 @@ const collectionForNesting = testCase.mode === 'ft' ? await helper.ft.mintCollection(alice) : helper.ft.getCollectionObject(0); // Alice cannot create immediately nested tokens: - await expect(testCase.mode === 'ft' - ? collectionForNesting.mint(alice, 100n, targetToken.nestingAccount()) - : collectionForNesting.transfer(alice, targetToken.nestingAccount(), 100n)).to.be.rejectedWith('common.UserIsNotAllowedToNest'); + if (testCase.mode === 'ft') { + await expect(collectionForNesting.mint(alice, 100n, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); + } else { + await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 100n)).to.be.not.rejected; + } // Alice can't mint and nest tokens: if (testCase.mode === 'ft') { await collectionForNesting.mint(alice, 100n); } - await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 50n)).to.be.rejectedWith('common.UserIsNotAllowedToNest'); + + if (testCase.mode === 'ft') { + await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 50n)).to.be.rejectedWith('common.UserIsNotAllowedToNest'); + } else { + await expect(collectionForNesting.transfer(alice, targetToken.nestingAccount(), 50n)).to.be.not.rejected; + } }); }); @@ -84,7 +91,7 @@ {mode: 'ft' as const}, {mode: 'native ft' as const}, ].map(testCase => { - itSub(`Non-owner and non-admin cannot nest ${testCase.mode.toUpperCase()} in someone else's tokens`, async ({helper}) => { + itSub(`Non-owner and non-admin cannot nest ${testCase.mode.toUpperCase()} in someone else's tokens (except for native fungible collection)`, async ({helper}) => { const targetCollection = await helper.nft.mintCollection(alice, {permissions: {nesting: {tokenOwner: true, collectionAdmin: true}}, }); @@ -118,7 +125,7 @@ case 'nft': case 'rft': await expect(nestedTokenBob!.transfer(bob, targetToken.nestingAccount())).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break; case 'ft': await expect((nestedCollectionBob as UniqueFTCollection).transfer(bob, targetToken.nestingAccount(), 100n)).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break; - case 'native ft': await expect((nestedCollectionBob as UniqueFTCollection).transfer(bob, targetToken.nestingAccount(), 100n)).to.be.rejectedWith('common.UserIsNotAllowedToNest'); break; + case 'native ft': await expect((nestedCollectionBob as UniqueFTCollection).transfer(bob, targetToken.nestingAccount(), 100n)).to.be.not.rejected; break; } }); }); @@ -143,7 +150,7 @@ }); }); - itSub.ifWithPallets('Cannot nest in non existing token', [Pallets.ReFungible], async ({helper}) => { + itSub.ifWithPallets('Cannot nest in non existing token (except for native fungible collection)', [Pallets.ReFungible], async ({helper}) => { const collection = await helper.nft.mintCollection(alice); // To avoid UserIsNotAllowedToNest error await helper.collection.setPermissions(alice, collection.collectionId, {nesting: {collectionAdmin: true}}); @@ -179,7 +186,7 @@ await expect(nft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error); await expect(rft.transfer(alice, testCase.token.nestingAccount())).to.be.rejectedWith(testCase.error); await expect(ftCollectionForNesting.transfer(alice, testCase.token.nestingAccount(), 50n)).to.be.rejectedWith(testCase.error); - await expect(nativeFtCollectionForNesting.transfer(alice, testCase.token.nestingAccount(), 50n)).to.be.rejectedWith(testCase.error); + await expect(nativeFtCollectionForNesting.transfer(alice, testCase.token.nestingAccount(), 50n)).to.be.not.rejected; } }); @@ -200,7 +207,7 @@ await expect(nft.transfer(alice, {Ethereum: futureCollectionAddress})).to.be.rejectedWith('CantNestTokenUnderCollection'); }); - itEth.ifWithPallets('Cannot nest in RFT or FT', [Pallets.ReFungible], async ({helper}) => { + itEth.ifWithPallets('Cannot nest in RFT or FT (except for native fungible collection)', [Pallets.ReFungible], async ({helper}) => { // Create default collection, permissions are not set: const rftCollection = await helper.rft.mintCollection(alice); const ftCollection = await helper.ft.mintCollection(alice); @@ -220,15 +227,15 @@ const nestedToken2 = await collectionForNesting.mintToken(alice); await expect(nestedToken2.nest(alice, rftToken)).to.be.rejectedWith('refungible.RefungibleDisallowsNesting'); await expect(ftCollection.transfer(alice, {Ethereum: helper.ethAddress.fromTokenId(ftCollection.collectionId, 0)})).to.be.rejectedWith('fungible.FungibleDisallowsNesting'); - await expect(nativeFtCollection.transfer(alice, {Ethereum: helper.ethAddress.fromTokenId(nativeFtCollection.collectionId, 0)})).to.be.rejectedWith('common.UnsupportedOperation'); + await expect(nativeFtCollection.transfer(alice, {Ethereum: helper.ethAddress.fromTokenId(nativeFtCollection.collectionId, 0)})).to.be.not.rejected; }); - itSub('Cannot nest in restricted collection if collection is not in the list', async ({helper}) => { + itSub('Cannot nest in restricted collection if collection is not in the list (except native fungible collection)', async ({helper}) => { const {collectionId: allowedCollectionId} = await helper.nft.mintCollection(alice); const notAllowedCollectionNFT = await helper.nft.mintCollection(alice); const notAllowedCollectionRFT = await helper.rft.mintCollection(alice); const notAllowedCollectionFT = await helper.ft.mintCollection(alice); - const notAllowedCollectionNativeFT = helper.ft.getCollectionObject(0); + const allowedCollectionNativeFT = helper.ft.getCollectionObject(0); // Collection restricted to allowedCollectionId const restrictedCollectionA = await helper.nft.mintCollection(alice, {permissions: @@ -253,8 +260,8 @@ await expect(notAllowedCollectionRFT.mintToken(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenA.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); await expect(notAllowedCollectionFT.mint(alice, 100n, targetTokenB.nestingAccount())).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); - await expect(notAllowedCollectionNativeFT.transfer(alice, targetTokenA.nestingAccount(), 100n)).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); - await expect(notAllowedCollectionNativeFT.transfer(alice, targetTokenB.nestingAccount(), 100n)).to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); + await expect(allowedCollectionNativeFT.transfer(alice, targetTokenA.nestingAccount(), 100n)).to.be.not.rejected; + await expect(allowedCollectionNativeFT.transfer(alice, targetTokenB.nestingAccount(), 100n)).to.be.not.rejected; }); itSub('Cannot create nesting chains greater than 5', async ({helper}) => { -- gitstuff