git.delta.rocks / unique-network / refs/commits / 012bbc1934d8

difftreelog

fix native ft nesting

Trubnikov Sergey2023-05-17parent: #53c316b.patch.diff
in: master

6 files changed

modifiedCargo.lockdiffbeforeafterboth
--- 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",
modifiedpallets/balances-adapter/src/lib.rsdiffbeforeafterboth
--- 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,
 				)?;
-
-				<PalletStructure<T>>::nest_if_sent_to_token(
-					from.clone(),
-					to,
-					NATIVE_FUNGIBLE_COLLECTION_ID,
-					TokenId::default(),
-					nesting_budget,
-				)?;
-
-				let balance_from: u128 =
-					<T as Config>::Currency::free_balance(from.as_sub()).into();
-				if balance_from == 0 {
-					<PalletStructure<T>>::unnest_if_nested(
-						from,
-						NATIVE_FUNGIBLE_COLLECTION_ID,
-						TokenId::default(),
-					);
-				}
 			};
 
 			Ok(PostDispatchInfo {
modifiedpallets/nonfungible/Cargo.tomldiffbeforeafterboth
--- 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 }
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- 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)) {
-		<TokenChildren<T>>::insert((under.0, under.1, (to_nest.0, to_nest.1)), true);
+		if to_nest.0 != pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+			<TokenChildren<T>>::insert((under.0, under.1, to_nest), true);
+		}
 	}
 
 	fn unnest(under: (CollectionId, TokenId), to_unnest: (CollectionId, TokenId)) {
-		<TokenChildren<T>>::remove((under.0, under.1, to_unnest));
+		if to_unnest.0 != pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {
+			<TokenChildren<T>>::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 {
-		<TokenChildren<T>>::iter_prefix((collection_id, token_id))
-			.next()
-			.is_some()
+		let address = T::CrossTokenAddressMapping::token_to_address(collection_id, token_id);
+		let balance = <pallet_balances::Pallet<T>>::free_balance(address.as_sub());
+
+		balance > T::Balance::default()
+			|| <TokenChildren<T>>::iter_prefix((collection_id, token_id))
+				.next()
+				.is_some()
 	}
 
 	pub fn token_children_ids(collection_id: CollectionId, token_id: TokenId) -> Vec<TokenChild> {
-		<TokenChildren<T>>::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 = <pallet_balances::Pallet<T>>::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(
+			<TokenChildren<T>>::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.
modifiedtests/src/eth/nesting/nest.test.tsdiffbeforeafterboth
after · tests/src/eth/nesting/nest.test.ts
1import {IKeyringPair} from '@polkadot/types/types';2import {Contract} from 'web3-eth-contract';34import {itEth, EthUniqueHelper, usingEthPlaygrounds, expect} from '../util';56const createNestingCollection = async (7  helper: EthUniqueHelper,8  owner: string,9): Promise<{ collectionId: number, collectionAddress: string, contract: Contract }> => {10  const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');1112  const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner);13  await contract.methods.setCollectionNesting(true).send({from: owner});1415  return {collectionId, collectionAddress, contract};16};171819describe('EVM nesting tests group', () => {20  let donor: IKeyringPair;2122  before(async function() {23    await usingEthPlaygrounds(async (_, privateKey) => {24      donor = await privateKey({url: import.meta.url});25    });26  });2728  describe('Integration Test: EVM Nesting', () => {29    itEth('NFT: allows an Owner to nest/unnest their token', async ({helper}) => {30      const owner = await helper.eth.createAccountWithBalance(donor);31      const {collectionId, contract} = await createNestingCollection(helper, owner);3233      // Create a token to be nested to34      const mintingTargetNFTTokenIdResult = await contract.methods.mint(owner).send({from: owner});35      const targetNFTTokenId = mintingTargetNFTTokenIdResult.events.Transfer.returnValues.tokenId;36      const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetNFTTokenId);3738      // Create a nested token39      const mintingFirstTokenIdResult = await contract.methods.mint(targetNftTokenAddress).send({from: owner});40      const firstTokenId = mintingFirstTokenIdResult.events.Transfer.returnValues.tokenId;41      expect(await contract.methods.ownerOf(firstTokenId).call()).to.be.equal(targetNftTokenAddress);4243      // Create a token to be nested and nest44      const mintingSecondTokenIdResult = await contract.methods.mint(owner).send({from: owner});45      const secondTokenId = mintingSecondTokenIdResult.events.Transfer.returnValues.tokenId;4647      await contract.methods.transfer(targetNftTokenAddress, secondTokenId).send({from: owner});48      expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(targetNftTokenAddress);4950      // Unnest token back51      await contract.methods.transferFrom(targetNftTokenAddress, owner, secondTokenId).send({from: owner});52      expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(owner);53    });5455    itEth('NFT: collectionNestingRestrictedCollectionIds() & collectionNestingPermissions', async ({helper}) => {56      const owner = await helper.eth.createAccountWithBalance(donor);57      const {collectionId: unnestedCollsectionId, collectionAddress: unnsetedCollectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');58      const unnestedContract = await helper.ethNativeContract.collection(unnsetedCollectionAddress, 'nft', owner);59      expect(await unnestedContract.methods.collectionNestingRestrictedCollectionIds().call({from: owner})).to.be.like([false, []]);6061      const {contract} = await createNestingCollection(helper, owner);62      expect(await contract.methods.collectionNestingRestrictedCollectionIds().call({from: owner})).to.be.like([true, []]);63      await contract.methods.setCollectionNesting(true, [unnsetedCollectionAddress]).send({from: owner});64      expect(await contract.methods.collectionNestingRestrictedCollectionIds().call({from: owner})).to.be.like([true, [unnestedCollsectionId.toString()]]);65      expect(await contract.methods.collectionNestingPermissions().call({from: owner})).to.be.like([['1', false], ['0', true]]);66      await contract.methods.setCollectionNesting(false).send({from: owner});67      expect(await contract.methods.collectionNestingPermissions().call({from: owner})).to.be.like([['1', false], ['0', false]]);68    });6970    itEth('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async ({helper}) => {71      const owner = await helper.eth.createAccountWithBalance(donor);7273      const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(helper, owner);74      const {collectionAddress: collectionAddressB, contract: contractB} = await createNestingCollection(helper, owner);75      await contractA.methods.setCollectionNesting(true, [collectionAddressA, collectionAddressB]).send({from: owner});7677      // Create a token to nest into78      const mintingtargetNftTokenIdResult = await contractA.methods.mint(owner).send({from: owner});79      const targetNftTokenId = mintingtargetNftTokenIdResult.events.Transfer.returnValues.tokenId;80      const nftTokenAddressA1 = helper.ethAddress.fromTokenId(collectionIdA, targetNftTokenId);8182      // Create a token for nesting in the same collection as the target83      const mintingTokenIdAResult = await contractA.methods.mint(owner).send({from: owner});84      const nftTokenIdA = mintingTokenIdAResult.events.Transfer.returnValues.tokenId;8586      // Create a token for nesting in a different collection87      const mintingTokenIdBResult = await contractB.methods.mint(owner).send({from: owner});88      const nftTokenIdB = mintingTokenIdBResult.events.Transfer.returnValues.tokenId;8990      // Nest91      await contractA.methods.transfer(nftTokenAddressA1, nftTokenIdA).send({from: owner});92      expect(await contractA.methods.ownerOf(nftTokenIdA).call()).to.be.equal(nftTokenAddressA1);9394      await contractB.methods.transfer(nftTokenAddressA1, nftTokenIdB).send({from: owner});95      expect(await contractB.methods.ownerOf(nftTokenIdB).call()).to.be.equal(nftTokenAddressA1);96    });97  });9899  describe('Negative Test: EVM Nesting', () => {100    itEth('NFT: disallows to nest token if nesting is disabled', async ({helper}) => {101      const owner = await helper.eth.createAccountWithBalance(donor);102103      const {collectionId, contract} = await createNestingCollection(helper, owner);104      await contract.methods.setCollectionNesting(false).send({from: owner});105106      // Create a token to nest into107      const mintingTargetTokenIdResult = await contract.methods.mint(owner).send({from: owner});108      const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;109      const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetTokenId);110111      // Create a token to nest112      const mintingNftTokenIdResult = await contract.methods.mint(owner).send({from: owner});113      const nftTokenId = mintingNftTokenIdResult.events.Transfer.returnValues.tokenId;114115      // Try to nest116      await expect(contract.methods117        .transfer(targetNftTokenAddress, nftTokenId)118        .call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest');119    });120121    itEth('NFT: disallows a non-Owner to nest someone else\'s token', async ({helper}) => {122      const owner = await helper.eth.createAccountWithBalance(donor);123      const malignant = await helper.eth.createAccountWithBalance(donor);124125      const {collectionId, contract} = await createNestingCollection(helper, owner);126127      // Mint a token128      const mintingTargetTokenIdResult = await contract.methods.mint(owner).send({from: owner});129      const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;130      const targetTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetTokenId);131132      // Mint a token belonging to a different account133      const mintingTokenIdResult = await contract.methods.mint(malignant).send({from: owner});134      const tokenId = mintingTokenIdResult.events.Transfer.returnValues.tokenId;135136      // Try to nest one token in another as a non-owner account137      await expect(contract.methods138        .transfer(targetTokenAddress, tokenId)139        .call({from: malignant})).to.be.rejectedWith('UserIsNotAllowedToNest');140    });141142    itEth('NFT: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async ({helper}) => {143      const owner = await helper.eth.createAccountWithBalance(donor);144      const malignant = await helper.eth.createAccountWithBalance(donor);145146      const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(helper, owner);147      const {collectionAddress: collectionAddressB, contract: contractB} = await createNestingCollection(helper, owner);148149      await contractA.methods.setCollectionNesting(true, [collectionAddressA, collectionAddressB]).send({from: owner});150151      // Create a token in one collection152      const mintingTokenIdAResult = await contractA.methods.mint(owner).send({from: owner});153      const nftTokenIdA = mintingTokenIdAResult.events.Transfer.returnValues.tokenId;154      const nftTokenAddressA = helper.ethAddress.fromTokenId(collectionIdA, nftTokenIdA);155156      // Create a token in another collection157      const mintingTokenIdBResult = await contractB.methods.mint(malignant).send({from: owner});158      const nftTokenIdB = mintingTokenIdBResult.events.Transfer.returnValues.tokenId;159160      // Try to drag someone else's token into the other collection and nest161      await expect(contractB.methods162        .transfer(nftTokenAddressA, nftTokenIdB)163        .call({from: malignant})).to.be.rejectedWith('UserIsNotAllowedToNest');164    });165166    itEth('NFT: disallows to nest token in an unlisted collection', async ({helper}) => {167      const owner = await helper.eth.createAccountWithBalance(donor);168169      const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(helper, owner);170      const {contract: contractB} = await createNestingCollection(helper, owner);171172      await contractA.methods.setCollectionNesting(true, [collectionAddressA]).send({from: owner});173174      // Create a token in one collection175      const mintingTokenIdAResult = await contractA.methods.mint(owner).send({from: owner});176      const nftTokenIdA = mintingTokenIdAResult.events.Transfer.returnValues.tokenId;177      const nftTokenAddressA = helper.ethAddress.fromTokenId(collectionIdA, nftTokenIdA);178179      // Create a token in another collection180      const mintingTokenIdBResult = await contractB.methods.mint(owner).send({from: owner});181      const nftTokenIdB = mintingTokenIdBResult.events.Transfer.returnValues.tokenId;182183184      // Try to nest into a token in the other collection, disallowed in the first185      await expect(contractB.methods186        .transfer(nftTokenAddressA, nftTokenIdB)187        .call()).to.be.rejectedWith('SourceCollectionIsNotAllowedToNest');188    });189  });190191  describe('Fungible', () => {192    async function createFungibleCollection(helper: EthUniqueHelper, owner: string, mode: 'ft' | 'native ft') {193      if (mode === 'ft') {194        const {collectionAddress} = await helper.eth.createFungibleCollection(owner, '', 18, '', '');195        const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);196        await contract.methods.mint(owner, 100n).send({from: owner});197        return {collectionAddress, contract};198      }199200      // native ft201      const collectionAddress = helper.ethAddress.fromCollectionId(0);202      const contract = await helper.ethNativeContract.collection(collectionAddress, 'ft', owner);203      return {collectionAddress, contract};204    }205206    [207      {mode: 'ft' as const},208      {mode: 'native ft' as const},209    ].map(testCase => {210      itEth(`Allow nest [${testCase.mode}]`, async ({helper}) => {211        const owner = await helper.eth.createAccountWithBalance(donor);212        const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner);213        const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode);214215        const mintingTargetTokenIdResult = await targetContract.methods.mint(owner).send({from: owner});216        const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;217        const targetTokenAddress = helper.ethAddress.fromTokenId(targetCollectionId, targetTokenId);218219        await ftContract.methods.transfer(targetTokenAddress, 10n).send({from: owner});220        expect(await ftContract.methods.balanceOf(targetTokenAddress).call({from: owner})).to.be.equal('10');221      });222    });223224    [225      {mode: 'ft' as const},226      {mode: 'native ft' as const},227    ].map(testCase => {228      itEth(`Allow partial/full unnest [${testCase.mode}]`, async ({helper}) => {229        const owner = await helper.eth.createAccountWithBalance(donor);230        const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner);231        const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode);232233        const mintingTargetTokenIdResult = await targetContract.methods.mint(owner).send({from: owner});234        const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;235        const targetTokenAddress = helper.ethAddress.fromTokenId(targetCollectionId, targetTokenId);236237        await ftContract.methods.transfer(targetTokenAddress, 10n).send({from: owner});238239        await ftContract.methods.transferFrom(targetTokenAddress, owner, 5n).send({from: owner});240        expect(await ftContract.methods.balanceOf(targetTokenAddress).call({from: owner})).to.be.equal('5');241242        await ftContract.methods.transferFrom(targetTokenAddress, owner, 5n).send({from: owner});243        expect(await ftContract.methods.balanceOf(targetTokenAddress).call({from: owner})).to.be.equal('0');244      });245    });246247    [248      {mode: 'ft' as const},249      {mode: 'native ft' as const},250    ].map(testCase => {251      itEth(`Disallow nest into collection without nesting permission [${testCase.mode}] (except for native fungible collection)`, async ({helper}) => {252        const owner = await helper.eth.createAccountWithBalance(donor);253        const {collectionId: targetCollectionId, contract: targetContract} = await createNestingCollection(helper, owner);254        await targetContract.methods.setCollectionNesting(false).send({from: owner});255256        const {contract: ftContract} = await createFungibleCollection(helper, owner, testCase.mode);257258        const mintingTargetTokenIdResult = await targetContract.methods.mint(owner).send({from: owner});259        const targetTokenId = mintingTargetTokenIdResult.events.Transfer.returnValues.tokenId;260        const targetTokenAddress = helper.ethAddress.fromTokenId(targetCollectionId, targetTokenId);261262        if (testCase.mode === 'ft') {263          await expect(ftContract.methods.transfer(targetTokenAddress, 10n).call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest');264        } else {265          await expect(ftContract.methods.transfer(targetTokenAddress, 10n).call({from: owner})).to.be.not.rejected;266        }267      });268    });269  });270});
modifiedtests/src/sub/nesting/nesting.negative.test.tsdiffbeforeafterboth
--- 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}) => {