git.delta.rocks / unique-network / refs/commits / 15c7e8f27646

difftreelog

Merge branch 'develop' into tests/eth-helpers

Max Andreev2022-12-07parents: #571c89e #c8ad2c2.patch.diff
in: master

14 files changed

modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -379,7 +379,7 @@
 		let balance_from = <Balance<T>>::get((collection.id, from))
 			.checked_sub(amount)
 			.ok_or(<CommonError<T>>::TokenValueTooLow)?;
-		let balance_to = if from != to {
+		let balance_to = if from != to && amount != 0 {
 			Some(
 				<Balance<T>>::get((collection.id, to))
 					.checked_add(amount)
@@ -391,16 +391,17 @@
 
 		// =========
 
-		<PalletStructure<T>>::nest_if_sent_to_token(
-			from.clone(),
-			to,
-			collection.id,
-			TokenId::default(),
-			nesting_budget,
-		)?;
-
 		if let Some(balance_to) = balance_to {
-			// from != to
+			// from != to && amount != 0
+
+			<PalletStructure<T>>::nest_if_sent_to_token(
+				from.clone(),
+				to,
+				collection.id,
+				TokenId::default(),
+				nesting_budget,
+			)?;
+
 			if balance_from == 0 {
 				<Balance<T>>::remove((collection.id, from));
 				<PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -291,6 +291,7 @@
 				<CommonWeights<T>>::burn_item(),
 			)
 		} else {
+			<Pallet<T>>::check_token_immediate_ownership(self, token, &sender)?;
 			Ok(().into())
 		}
 	}
@@ -320,6 +321,7 @@
 				<CommonWeights<T>>::transfer(),
 			)
 		} else {
+			<Pallet<T>>::check_token_immediate_ownership(self, token, &from)?;
 			Ok(().into())
 		}
 	}
@@ -360,6 +362,8 @@
 				<CommonWeights<T>>::transfer_from(),
 			)
 		} else {
+			<Pallet<T>>::check_allowed(self, &sender, &from, token, nesting_budget)?;
+
 			Ok(().into())
 		}
 	}
@@ -380,6 +384,8 @@
 				<CommonWeights<T>>::burn_from(),
 			)
 		} else {
+			<Pallet<T>>::check_allowed(self, &sender, &from, token, nesting_budget)?;
+
 			Ok(().into())
 		}
 	}
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -814,6 +814,20 @@
 		<PalletCommon<T>>::set_property_permission(collection, sender, permission)
 	}
 
+	pub fn check_token_immediate_ownership(
+		collection: &NonfungibleHandle<T>,
+		token: TokenId,
+		possible_owner: &T::CrossAccountId,
+	) -> DispatchResult {
+		let token_data =
+			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;
+		ensure!(
+			&token_data.owner == possible_owner,
+			<CommonError<T>>::NoPermission
+		);
+		Ok(())
+	}
+
 	/// Transfer NFT token from one account to another.
 	///
 	/// `from` account stops being the owner and `to` account becomes the owner of the token.
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -34,6 +34,7 @@
 	CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,
 	erc::{CommonEvmHandler, CollectionCall, static_property::key},
 	eth::EthCrossAccount,
+	Error as CommonError,
 };
 use pallet_evm::{account::CrossAccountId, PrecompileHandle};
 use pallet_evm_coder_substrate::{call, dispatch_to_evm};
@@ -508,6 +509,13 @@
 ) -> Result<()> {
 	collection.consume_store_reads(1)?;
 	let total_supply = <TotalSupply<T>>::get((collection.id, token));
+
+	if owner_balance == 0 {
+		return Err(dispatch_to_evm::<T>(
+			<CommonError<T>>::MustBeTokenOwner.into(),
+		));
+	}
+
 	if total_supply != owner_balance {
 		return Err("token has multiple owners".into());
 	}
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -452,6 +452,10 @@
 		token: TokenId,
 		amount: u128,
 	) -> DispatchResult {
+		if <Balance<T>>::get((collection.id, token, owner)) == 0 {
+			return Err(<CommonError<T>>::TokenValueTooLow.into());
+		}
+
 		let total_supply = <TotalSupply<T>>::get((collection.id, token))
 			.checked_sub(amount)
 			.ok_or(<CommonError<T>>::TokenValueTooLow)?;
@@ -739,12 +743,17 @@
 		<PalletCommon<T>>::ensure_correct_receiver(to)?;
 
 		let initial_balance_from = <Balance<T>>::get((collection.id, token, from));
+
+		if initial_balance_from == 0 {
+			return Err(<CommonError<T>>::TokenValueTooLow.into());
+		}
+
 		let updated_balance_from = initial_balance_from
 			.checked_sub(amount)
 			.ok_or(<CommonError<T>>::TokenValueTooLow)?;
 		let mut create_target = false;
 		let from_to_differ = from != to;
-		let updated_balance_to = if from != to {
+		let updated_balance_to = if from != to && amount != 0 {
 			let old_balance = <Balance<T>>::get((collection.id, token, to));
 			if old_balance == 0 {
 				create_target = true;
@@ -786,16 +795,17 @@
 
 		// =========
 
-		<PalletStructure<T>>::nest_if_sent_to_token(
-			from.clone(),
-			to,
-			collection.id,
-			token,
-			nesting_budget,
-		)?;
+		if let Some(updated_balance_to) = updated_balance_to {
+			// from != to && amount != 0
+
+			<PalletStructure<T>>::nest_if_sent_to_token(
+				from.clone(),
+				to,
+				collection.id,
+				token,
+				nesting_budget,
+			)?;
 
-		if let Some(updated_balance_to) = updated_balance_to {
-			// from != to
 			if updated_balance_from == 0 {
 				<Balance<T>>::remove((collection.id, token, from));
 				<PalletStructure<T>>::unnest_if_nested(from, collection.id, token);
modifiedtests/src/burnItem.test.tsdiffbeforeafterboth
--- a/tests/src/burnItem.test.ts
+++ b/tests/src/burnItem.test.ts
@@ -140,6 +140,31 @@
     await expect(token.burn(bob)).to.be.rejectedWith('common.NoPermission');
   });
 
+  itSub.ifWithPallets('RFT: cannot burn non-owned token pieces', [Pallets.ReFungible], async ({helper}) => {
+    const collection = await helper.rft.mintCollection(alice);
+    const aliceToken = await collection.mintToken(alice, 10n, {Substrate: alice.address});
+    const bobToken = await collection.mintToken(alice, 10n, {Substrate: bob.address});
+
+    // 1. Cannot burn non-owned token:
+    await expect(bobToken.burn(alice, 0n)).to.be.rejectedWith('common.TokenValueTooLow');
+    await expect(bobToken.burn(alice, 5n)).to.be.rejectedWith('common.TokenValueTooLow');
+    // 2. Cannot burn non-existing token:
+    await expect(helper.rft.burnToken(alice, 99999, 10)).to.be.rejectedWith('common.CollectionNotFound');
+    await expect(helper.rft.burnToken(alice, collection.collectionId, 99999)).to.be.rejectedWith('common.TokenValueTooLow');
+    // 3. Can burn zero amount of owned tokens (EIP-20)
+    await aliceToken.burn(alice, 0n);
+
+    // 4. Storage is not corrupted:
+    expect(await aliceToken.getTop10Owners()).to.deep.eq([{Substrate: alice.address}]);
+    expect(await bobToken.getTop10Owners()).to.deep.eq([{Substrate: bob.address}]);
+
+    // 4.1 Tokens can be transfered:
+    await aliceToken.transfer(alice, {Substrate: bob.address}, 10n);
+    await bobToken.transfer(bob, {Substrate: alice.address}, 10n);
+    expect(await aliceToken.getTop10Owners()).to.deep.eq([{Substrate: bob.address}]);
+    expect(await bobToken.getTop10Owners()).to.deep.eq([{Substrate: alice.address}]);
+  });
+
   itSub('Transfer a burned token', async ({helper}) => {
     const collection = await helper.nft.mintCollection(alice);
     const token = await collection.mintToken(alice);
@@ -157,40 +182,38 @@
   });
 
   itSub('Zero burn NFT', async ({helper}) => {
-    const api = helper.getApi();
     const collection = await helper.nft.mintCollection(alice, {name: 'Coll', description: 'Desc', tokenPrefix: 'T'});
     const tokenAlice = await collection.mintToken(alice, {Substrate: alice.address});
     const tokenBob = await collection.mintToken(alice, {Substrate: bob.address});
     
     // 1. Zero burn of own tokens allowed:
-    await helper.signTransaction(alice, api.tx.unique.burnItem(collection.collectionId, tokenAlice.tokenId, 0));
+    await helper.executeExtrinsic(alice, 'api.tx.unique.burnItem', [collection.collectionId, tokenAlice.tokenId, 0]);
     // 2. Zero burn of non-owned tokens not allowed:
-    await expect(helper.signTransaction(alice, api.tx.unique.burnItem(collection.collectionId, tokenBob.tokenId, 0))).to.be.rejectedWith('common.NoPermission');
+    await expect(helper.executeExtrinsic(alice, 'api.tx.unique.burnItem', [collection.collectionId, tokenBob.tokenId, 0])).to.be.rejectedWith('common.NoPermission');
     // 3. Zero burn of non-existing tokens not allowed:
-    await expect(helper.signTransaction(alice, api.tx.unique.burnItem(collection.collectionId, 9999, 0))).to.be.rejectedWith('common.TokenNotFound');
+    await expect(helper.executeExtrinsic(alice, 'api.tx.unique.burnItem', [collection.collectionId, 9999, 0])).to.be.rejectedWith('common.TokenNotFound');
     expect(await tokenAlice.doesExist()).to.be.true;
     expect(await tokenAlice.getOwner()).to.deep.eq({Substrate: alice.address});
     expect(await tokenBob.getOwner()).to.deep.eq({Substrate: bob.address});
     // 4. Storage is not corrupted:
     await tokenAlice.transfer(alice, {Substrate: bob.address});
-    await tokenBob.transfer(alice, {Substrate: alice.address});
+    await tokenBob.transfer(bob, {Substrate: alice.address});
     expect(await tokenAlice.getOwner()).to.deep.eq({Substrate: bob.address});
     expect(await tokenBob.getOwner()).to.deep.eq({Substrate: alice.address});
   });
 
   itSub('zero burnFrom NFT', async ({helper}) => {
-    const api = helper.getApi();
     const collection = await helper.nft.mintCollection(alice, {name: 'Zero', description: 'Zero transfer', tokenPrefix: 'TF'});
     const notApprovedNft = await collection.mintToken(alice, {Substrate: bob.address});
     const approvedNft = await collection.mintToken(alice, {Substrate: bob.address});
     await approvedNft.approve(bob, {Substrate: alice.address});
 
     // 1. Zero burnFrom of non-existing tokens not allowed:
-    await expect(helper.signTransaction(alice, api.tx.unique.burnFrom(collection.collectionId, {Substrate: bob.address}, 9999, 0))).to.be.rejectedWith('common.ApprovedValueTooLow');
+    await expect(helper.executeExtrinsic(alice, 'api.tx.unique.burnFrom', [collection.collectionId, {Substrate: bob.address}, 9999, 0])).to.be.rejectedWith('common.ApprovedValueTooLow');
     // 2. Zero burnFrom of not approved tokens not allowed:
-    await expect(helper.signTransaction(alice, api.tx.unique.burnFrom(collection.collectionId, {Substrate: bob.address}, notApprovedNft.tokenId, 0))).to.be.rejectedWith('common.NoPermission');
+    await expect(helper.executeExtrinsic(alice, 'api.tx.unique.burnFrom', [collection.collectionId, {Substrate: bob.address}, notApprovedNft.tokenId, 0])).to.be.rejectedWith('common.ApprovedValueTooLow');
     // 3. Zero burnFrom of approved tokens allowed:
-    await helper.signTransaction(alice, api.tx.unique.burnFrom(collection.collectionId, {Substrate: bob.address}, approvedNft.tokenId, 0));
+    await helper.executeExtrinsic(alice, 'api.tx.unique.burnFrom', [collection.collectionId, {Substrate: bob.address}, approvedNft.tokenId, 0]);
 
     // 4.1 approvedNft still approved:
     expect(await approvedNft.isApproved({Substrate: alice.address})).to.be.true;
modifiedtests/src/eth/fungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/fungible.test.ts
+++ b/tests/src/eth/fungible.test.ts
@@ -277,7 +277,7 @@
     }
   });
 
-  itEth('Cannot transferCross() more than have', async ({helper}) => {
+  ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} incorrect amount`, async ({helper}) => {
     const sender = await helper.eth.createAccountWithBalance(donor);
     const receiverEth = await helper.eth.createAccountWithBalance(donor);
     const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);
@@ -289,8 +289,13 @@
     const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'ft', sender);
 
-    await expect(collectionEvm.methods.transferCross(receiverCrossEth, BALANCE_TO_TRANSFER).send({from: sender})).to.be.rejected;
-  });
+    // 1. Cannot transfer more than have
+    const receiver = testCase === 'transfer' ? receiverEth : receiverCrossEth;
+    await expect(collectionEvm.methods[testCase](receiver, BALANCE_TO_TRANSFER).send({from: sender})).to.be.rejected;
+    // 2. Zero transfer allowed (EIP-20):
+    await collectionEvm.methods[testCase](receiver, 0n).send({from: sender});
+  }));
+  
   
   itEth('Can perform transfer()', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
modifiedtests/src/eth/nonFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -517,6 +517,26 @@
       expect(receiverBalance).to.contain(tokenId);
     }
   });
+
+  ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {
+    const sender = await helper.eth.createAccountWithBalance(donor);
+    const tokenOwner = await helper.eth.createAccountWithBalance(donor);
+    const receiverSub = minter;
+    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);
+
+    const collection = await helper.nft.mintCollection(minter, {});
+    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', sender);
+
+    await collection.mintToken(minter, {Ethereum: sender});
+    const nonSendersToken = await collection.mintToken(minter, {Ethereum: tokenOwner});
+
+    // Cannot transferCross someone else's token:
+    const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;
+    await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;
+    // Cannot transfer token if it does not exist:
+    await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;
+  }));
 });
 
 describe('NFT: Fees', () => {
modifiedtests/src/eth/reFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -413,9 +413,10 @@
     }
   });
 
-  itEth.skip('Cannot transferCross with invalid params', async ({helper}) => {
+  ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {
     const sender = await helper.eth.createAccountWithBalance(donor);
     const tokenOwner = await helper.eth.createAccountWithBalance(donor);
+    const receiverSub = minter;
     const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);
 
     const collection = await helper.rft.mintCollection(minter, {});
@@ -423,12 +424,14 @@
     const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', sender);
 
     await collection.mintToken(minter, 50n, {Ethereum: sender});
-    const notSendersToken = await collection.mintToken(minter, 50n, {Ethereum: tokenOwner});
+    const nonSendersToken = await collection.mintToken(minter, 50n, {Ethereum: tokenOwner});
+
     // Cannot transferCross someone else's token:
-    await expect(collectionEvm.methods.transferCross(receiverCrossSub, notSendersToken.tokenId).send({from: sender})).to.be.rejected;
-    // FIXME: (transaction successful): Cannot transfer token if it does not exist:
-    await expect(collectionEvm.methods.transferCross(receiverCrossSub, 999999).send({from: sender})).to.be.rejected;
-  });
+    const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;
+    await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;
+    // Cannot transfer token if it does not exist:
+    await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;
+  }));
 
   itEth('transfer event on transfer from partial ownership to full ownership', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
modifiedtests/src/eth/reFungibleToken.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungibleToken.test.ts
+++ b/tests/src/eth/reFungibleToken.test.ts
@@ -227,6 +227,46 @@
     }
   });
 
+  [
+    'transfer',
+    // 'transferCross', // TODO
+  ].map(testCase => 
+    itEth(`Cannot ${testCase}() non-owned token`, async ({helper}) => {
+      const owner = await helper.eth.createAccountWithBalance(donor);
+      const receiver = await helper.eth.createAccountWithBalance(donor);
+      const collection = await helper.rft.mintCollection(alice);
+      const rftOwner = await collection.mintToken(alice, 10n, {Ethereum: owner});
+      const rftReceiver = await collection.mintToken(alice, 10n, {Ethereum: receiver});
+      const tokenIdNonExist = 9999999;
+  
+      const tokenAddress1 = helper.ethAddress.fromTokenId(collection.collectionId, rftOwner.tokenId);
+      const tokenAddress2 = helper.ethAddress.fromTokenId(collection.collectionId, rftReceiver.tokenId);
+      const tokenAddressNonExist = helper.ethAddress.fromTokenId(collection.collectionId, tokenIdNonExist);
+      const tokenEvmOwner = helper.ethNativeContract.rftToken(tokenAddress1, owner);
+      const tokenEvmReceiver = helper.ethNativeContract.rftToken(tokenAddress2, owner);
+      const tokenEvmNonExist = helper.ethNativeContract.rftToken(tokenAddressNonExist, owner);
+      
+      // 1. Can transfer zero amount (EIP-20):
+      await tokenEvmOwner.methods[testCase](receiver, 0).send({from: owner});
+      // 2. Cannot transfer non-owned token:
+      await expect(tokenEvmReceiver.methods[testCase](owner, 0).send({from: owner})).to.be.rejected;
+      await expect(tokenEvmReceiver.methods[testCase](owner, 5).send({from: owner})).to.be.rejected;
+      // 3. Cannot transfer non-existing token:
+      await expect(tokenEvmNonExist.methods[testCase](owner, 0).send({from: owner})).to.be.rejected;
+      await expect(tokenEvmNonExist.methods[testCase](owner, 5).send({from: owner})).to.be.rejected;
+
+      // 4. Storage is not corrupted:
+      expect(await rftOwner.getTop10Owners()).to.deep.eq([{Ethereum: owner.toLowerCase()}]);
+      expect(await rftReceiver.getTop10Owners()).to.deep.eq([{Ethereum: receiver.toLowerCase()}]);
+      expect(await helper.rft.getTokenTop10Owners(collection.collectionId, tokenIdNonExist)).to.deep.eq([]); // TODO
+
+      // 4.1 Tokens can be transferred:
+      await tokenEvmOwner.methods[testCase](receiver, 10).send({from: owner});
+      await tokenEvmReceiver.methods[testCase](owner, 10).send({from: receiver});
+      expect(await rftOwner.getTop10Owners()).to.deep.eq([{Ethereum: receiver.toLowerCase()}]);
+      expect(await rftReceiver.getTop10Owners()).to.deep.eq([{Ethereum: owner.toLowerCase()}]);
+    }));
+
   itEth('Can perform repartition()', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
     const receiver = await helper.eth.createAccountWithBalance(donor);
modifiedtests/src/fungible.test.tsdiffbeforeafterboth
--- a/tests/src/fungible.test.ts
+++ b/tests/src/fungible.test.ts
@@ -15,7 +15,7 @@
 // along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
 
 import {IKeyringPair} from '@polkadot/types/types';
-import {itSub, usingPlaygrounds, expect} from './util';
+import {itSub, usingPlaygrounds, expect, requirePalletsOrSkip, Pallets} from './util';
 
 const U128_MAX = (1n << 128n) - 1n;
 
@@ -145,3 +145,42 @@
     expect(await collection.getBalance(ethAcc)).to.be.equal(10n);
   });
 });
+
+describe('Fungible negative tests', () => {
+  let donor: IKeyringPair;
+  let alice: IKeyringPair;
+  let bob: IKeyringPair;
+  let charlie: IKeyringPair;
+
+  before(async function() {
+    await usingPlaygrounds(async (helper, privateKey) => {
+      requirePalletsOrSkip(this, helper, [Pallets.Fungible]);
+
+      donor = await privateKey({filename: __filename});
+      [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
+    });
+  });
+
+  itSub('Cannot transfer incorrect amount of tokens', async ({helper}) => {
+    const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+    const nonExistingCollection = helper.ft.getCollectionObject(99999);
+    await collection.mint(alice, 10n, {Substrate: bob.address});
+
+    // 1. Alice cannot transfer more than 0 tokens if balance low:
+    await expect(collection.transfer(alice, {Substrate: charlie.address}, 1n)).to.be.rejectedWith('common.TokenValueTooLow');
+    await expect(collection.transfer(alice, {Substrate: charlie.address}, 100n)).to.be.rejectedWith('common.TokenValueTooLow');
+    
+    // 2. Alice cannot transfer non-existing token:
+    await expect(nonExistingCollection.transfer(alice, {Substrate: charlie.address}, 0n)).to.be.rejectedWith('common.CollectionNotFound');
+    await expect(nonExistingCollection.transfer(alice, {Substrate: charlie.address}, 1n)).to.be.rejectedWith('common.CollectionNotFound');
+    
+    // 3. Zero transfer allowed (EIP-20):
+    await collection.transfer(bob, {Substrate: charlie.address}, 0n);
+    // 3.1 even if the balance = 0
+    await collection.transfer(alice, {Substrate: charlie.address}, 0n);
+
+    expect(await collection.getBalance({Substrate: alice.address})).to.eq(0n);
+    expect(await collection.getBalance({Substrate: bob.address})).to.eq(10n);
+    expect(await collection.getBalance({Substrate: charlie.address})).to.eq(0n);
+  });
+});
modifiedtests/src/refungible.test.tsdiffbeforeafterboth
--- a/tests/src/refungible.test.ts
+++ b/tests/src/refungible.test.ts
@@ -255,3 +255,43 @@
   });
 });
 
+describe('Refungible negative tests', () => {
+  let donor: IKeyringPair;
+  let alice: IKeyringPair;
+  let bob: IKeyringPair;
+  let charlie: IKeyringPair;
+
+  before(async function() {
+    await usingPlaygrounds(async (helper, privateKey) => {
+      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);
+
+      donor = await privateKey({filename: __filename});
+      [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);
+    });
+  });
+
+  itSub('Cannot transfer incorrect amount of token pieces', async ({helper}) => {
+    const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});
+    const tokenAlice = await collection.mintToken(alice, 10n, {Substrate: alice.address});
+    const tokenBob = await collection.mintToken(alice, 10n, {Substrate: bob.address});
+
+    // 1. Alice cannot transfer Bob's token:
+    await expect(tokenBob.transfer(alice, {Substrate: charlie.address}, 0n)).to.be.rejectedWith('common.TokenValueTooLow');
+    await expect(tokenBob.transfer(alice, {Substrate: charlie.address}, 1n)).to.be.rejectedWith('common.TokenValueTooLow');
+    await expect(tokenBob.transfer(alice, {Substrate: charlie.address}, 10n)).to.be.rejectedWith('common.TokenValueTooLow');
+    await expect(tokenBob.transfer(alice, {Substrate: charlie.address}, 100n)).to.be.rejectedWith('common.TokenValueTooLow');
+    
+    // 2. Alice cannot transfer non-existing token:
+    await expect(collection.transferToken(alice, 100, {Substrate: charlie.address}, 0n)).to.be.rejectedWith('common.TokenValueTooLow');
+    await expect(collection.transferToken(alice, 100, {Substrate: charlie.address}, 1n)).to.be.rejectedWith('common.TokenValueTooLow');
+
+    // 3. Zero transfer allowed (EIP-20):
+    await tokenAlice.transfer(alice, {Substrate: charlie.address}, 0n);
+
+    expect(await tokenAlice.getTop10Owners()).to.deep.eq([{Substrate: alice.address}]);
+    expect(await tokenBob.getTop10Owners()).to.deep.eq([{Substrate: bob.address}]);
+    expect(await tokenAlice.getBalance({Substrate: alice.address})).to.eq(10n);
+    expect(await tokenBob.getBalance({Substrate: bob.address})).to.eq(10n);
+    expect(await tokenBob.getBalance({Substrate: charlie.address})).to.eq(0n);
+  });
+});
modifiedtests/src/transfer.test.tsdiffbeforeafterboth
before · tests/src/transfer.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import {itEth, usingEthPlaygrounds} from './eth/util';19import {itSub, Pallets, usingPlaygrounds, expect} from './util';2021describe('Integration Test Transfer(recipient, collection_id, item_id, value)', () => {22  let donor: IKeyringPair;23  let alice: IKeyringPair;24  let bob: IKeyringPair;2526  before(async () => {27    await usingPlaygrounds(async (helper, privateKey) => {28      donor = await privateKey({filename: __filename});29      [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);30    });31  });32  33  itSub('Balance transfers and check balance', async ({helper}) => {34    const alicesBalanceBefore = await helper.balance.getSubstrate(alice.address);35    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);3637    expect(await helper.balance.transferToSubstrate(alice, bob.address, 1n)).to.be.true;3839    const alicesBalanceAfter = await helper.balance.getSubstrate(alice.address);40    const bobsBalanceAfter = await helper.balance.getSubstrate(bob.address);4142    expect(alicesBalanceAfter < alicesBalanceBefore).to.be.true;43    expect(bobsBalanceAfter > bobsBalanceBefore).to.be.true;44  });4546  itSub('Inability to pay fees error message is correct', async ({helper}) => {47    const [zero] = await helper.arrange.createAccounts([0n], donor);4849    // console.error = () => {};50    // The following operation throws an error into the console and the logs. Pay it no heed as long as the test succeeds.51    await expect(helper.balance.transferToSubstrate(zero, donor.address, 1n))52      .to.be.rejectedWith('Inability to pay some fees , e.g. account balance too low');53  });5455  itSub('[nft] User can transfer owned token', async ({helper}) => {56    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-1-NFT', description: '', tokenPrefix: 'T'});57    const nft = await collection.mintToken(alice);5859    await nft.transfer(alice, {Substrate: bob.address});60    expect(await nft.getOwner()).to.be.deep.equal({Substrate: bob.address});61  });6263  itSub('[fungible] User can transfer owned token', async ({helper}) => {64    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-1-FT', description: '', tokenPrefix: 'T'});65    await collection.mint(alice, 10n);6667    await collection.transfer(alice, {Substrate: bob.address}, 9n);68    expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(9n);69    expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(1n);70  });7172  itSub.ifWithPallets('[refungible] User can transfer owned token', [Pallets.ReFungible], async ({helper}) => {73    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-1-RFT', description: '', tokenPrefix: 'T'});74    const rft = await collection.mintToken(alice, 10n);7576    await rft.transfer(alice, {Substrate: bob.address}, 9n);77    expect(await rft.getBalance({Substrate: bob.address})).to.be.equal(9n);78    expect(await rft.getBalance({Substrate: alice.address})).to.be.equal(1n);79  });8081  itSub('[nft] Collection admin can transfer owned token', async ({helper}) => {82    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-2-NFT', description: '', tokenPrefix: 'T'});83    await collection.addAdmin(alice, {Substrate: bob.address});8485    const nft = await collection.mintToken(bob, {Substrate: bob.address});86    await nft.transfer(bob, {Substrate: alice.address});8788    expect(await nft.getOwner()).to.be.deep.equal({Substrate: alice.address});89  });9091  itSub('[fungible] Collection admin can transfer owned token', async ({helper}) => {92    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-2-FT', description: '', tokenPrefix: 'T'});93    await collection.addAdmin(alice, {Substrate: bob.address});9495    await collection.mint(bob, 10n, {Substrate: bob.address});96    await collection.transfer(bob, {Substrate: alice.address}, 1n);9798    expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(9n);99    expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(1n);100  });101102  itSub.ifWithPallets('[refungible] Collection admin can transfer owned token', [Pallets.ReFungible], async ({helper}) => {103    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-2-RFT', description: '', tokenPrefix: 'T'});104    await collection.addAdmin(alice, {Substrate: bob.address});105106    const rft = await collection.mintToken(bob, 10n, {Substrate: bob.address});107    await rft.transfer(bob, {Substrate: alice.address}, 1n);108109    expect(await rft.getBalance({Substrate: bob.address})).to.be.equal(9n);110    expect(await rft.getBalance({Substrate: alice.address})).to.be.equal(1n);111  });112});113114describe('Negative Integration Test Transfer(recipient, collection_id, item_id, value)', () => {115  let alice: IKeyringPair;116  let bob: IKeyringPair;117118  before(async () => {119    await usingPlaygrounds(async (helper, privateKey) => {120      const donor = await privateKey({filename: __filename});121      [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);122    });123  });124125126  itSub('[nft] Transfer with not existed collection_id', async ({helper}) => {127    const collectionId = (1 << 32) - 1;128    await expect(helper.nft.transferToken(alice, collectionId, 1, {Substrate: bob.address}))129      .to.be.rejectedWith(/common\.CollectionNotFound/);130  });131132  itSub('[fungible] Transfer with not existed collection_id', async ({helper}) => {133    const collectionId = (1 << 32) - 1;134    await expect(helper.ft.transfer(alice, collectionId, {Substrate: bob.address}))135      .to.be.rejectedWith(/common\.CollectionNotFound/);136  });137138  itSub.ifWithPallets('[refungible] Transfer with not existed collection_id', [Pallets.ReFungible], async ({helper}) => {139    const collectionId = (1 << 32) - 1;140    await expect(helper.rft.transferToken(alice, collectionId, 1, {Substrate: bob.address}))141      .to.be.rejectedWith(/common\.CollectionNotFound/);142  });143144  itSub('[nft] Transfer with deleted collection_id', async ({helper}) => {145    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-1-NFT', description: '', tokenPrefix: 'T'});146    const nft = await collection.mintToken(alice);147148    await nft.burn(alice);149    await collection.burn(alice);150151    await expect(nft.transfer(alice, {Substrate: bob.address}))152      .to.be.rejectedWith(/common\.CollectionNotFound/);153  });154155  itSub('[fungible] Transfer with deleted collection_id', async ({helper}) => {156    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-1-FT', description: '', tokenPrefix: 'T'});157    await collection.mint(alice, 10n);158159    await collection.burnTokens(alice, 10n);160    await collection.burn(alice);161162    await expect(collection.transfer(alice, {Substrate: bob.address}))163      .to.be.rejectedWith(/common\.CollectionNotFound/);164  });165  166  itSub.ifWithPallets('[refungible] Transfer with deleted collection_id', [Pallets.ReFungible], async ({helper}) => {167    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-1-RFT', description: '', tokenPrefix: 'T'});168    const rft = await collection.mintToken(alice, 10n);169170    await rft.burn(alice, 10n);171    await collection.burn(alice);172173    await expect(rft.transfer(alice, {Substrate: bob.address}))174      .to.be.rejectedWith(/common\.CollectionNotFound/);175  });176177  itSub('[nft] Transfer with not existed item_id', async ({helper}) => {178    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-2-NFT', description: '', tokenPrefix: 'T'});179    await expect(collection.transferToken(alice, 1, {Substrate: bob.address}))180      .to.be.rejectedWith(/common\.TokenNotFound/);181  });182183  itSub('[fungible] Transfer with not existed item_id', async ({helper}) => {184    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-2-FT', description: '', tokenPrefix: 'T'});185    await expect(collection.transfer(alice, {Substrate: bob.address}))186      .to.be.rejectedWith(/common\.TokenValueTooLow/);187  });188189  itSub.ifWithPallets('[refungible] Transfer with not existed item_id', [Pallets.ReFungible], async ({helper}) => {190    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-2-RFT', description: '', tokenPrefix: 'T'});191    await expect(collection.transferToken(alice, 1, {Substrate: bob.address}))192      .to.be.rejectedWith(/common\.TokenValueTooLow/);193  });194195  itSub('Zero transfer NFT', async ({helper}) => {196    const api = helper.getApi();197    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-3-NFT', description: '', tokenPrefix: 'T'});198    const tokenAlice = await collection.mintToken(alice, {Substrate: alice.address});199    const tokenBob = await collection.mintToken(alice, {Substrate: bob.address});200    // 1. Zero transfer of own tokens allowed:201    await helper.signTransaction(alice, api.tx.unique.transfer({Substrate: bob.address}, collection.collectionId, tokenAlice.tokenId, 0));202    // 2. Zero transfer of non-owned tokens not allowed:203    await expect(helper.signTransaction(alice, api.tx.unique.transfer({Substrate: alice.address}, collection.collectionId, tokenBob.tokenId, 0))).to.be.rejectedWith('common.NoPermission');204    // 3. Zero transfer of non-existing tokens not allowed:205    await expect(helper.signTransaction(alice, api.tx.unique.transfer({Substrate: alice.address}, collection.collectionId, 10, 0))).to.be.rejectedWith('common.TokenNotFound');206    expect(await tokenAlice.getOwner()).to.deep.eq({Substrate: alice.address});207    expect(await tokenBob.getOwner()).to.deep.eq({Substrate: bob.address});208    // 4. Storage is not corrupted:209    await tokenAlice.transfer(alice, {Substrate: bob.address});210    await tokenBob.transfer(alice, {Substrate: alice.address});211    expect(await tokenAlice.getOwner()).to.deep.eq({Substrate: bob.address});212    expect(await tokenBob.getOwner()).to.deep.eq({Substrate: alice.address});213  });214215  itSub('[nft] Transfer with deleted item_id', async ({helper}) => {216    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-3-NFT', description: '', tokenPrefix: 'T'});217    const nft = await collection.mintToken(alice);218219    await nft.burn(alice);220221    await expect(nft.transfer(alice, {Substrate: bob.address}))222      .to.be.rejectedWith(/common\.TokenNotFound/);223  });224225  itSub('[fungible] Transfer with deleted item_id', async ({helper}) => {226    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-3-FT', description: '', tokenPrefix: 'T'});227    await collection.mint(alice, 10n);228229    await collection.burnTokens(alice, 10n);230231    await expect(collection.transfer(alice, {Substrate: bob.address}))232      .to.be.rejectedWith(/common\.TokenValueTooLow/);233  });234235  itSub.ifWithPallets('[refungible] Transfer with deleted item_id', [Pallets.ReFungible], async ({helper}) => {236    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-3-RFT', description: '', tokenPrefix: 'T'});237    const rft = await collection.mintToken(alice, 10n);238239    await rft.burn(alice, 10n);240241    await expect(rft.transfer(alice, {Substrate: bob.address}))242      .to.be.rejectedWith(/common\.TokenValueTooLow/);243  });244245  itSub('[nft] Transfer with recipient that is not owner', async ({helper}) => {246    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-4-NFT', description: '', tokenPrefix: 'T'});247    const nft = await collection.mintToken(alice);248249    await expect(nft.transfer(bob, {Substrate: bob.address}))250      .to.be.rejectedWith(/common\.NoPermission/);251    expect(await nft.getOwner()).to.be.deep.equal({Substrate: alice.address});252  });253254  itSub('[fungible] Transfer with recipient that is not owner', async ({helper}) => {255    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-4-FT', description: '', tokenPrefix: 'T'});256    await collection.mint(alice, 10n);257258    await expect(collection.transfer(bob, {Substrate: bob.address}, 9n))259      .to.be.rejectedWith(/common\.TokenValueTooLow/);260    expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(0n);261    expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(10n);262  });263264  itSub.ifWithPallets('[refungible] Transfer with recipient that is not owner', [Pallets.ReFungible], async ({helper}) => {265    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-1-RFT', description: '', tokenPrefix: 'T'});266    const rft = await collection.mintToken(alice, 10n);267268    await expect(rft.transfer(bob, {Substrate: bob.address}, 9n))269      .to.be.rejectedWith(/common\.TokenValueTooLow/);270    expect(await rft.getBalance({Substrate: bob.address})).to.be.equal(0n);271    expect(await rft.getBalance({Substrate: alice.address})).to.be.equal(10n);272  });273});274275describe('Transfers to self (potentially over substrate-evm boundary)', () => {276  let donor: IKeyringPair;277278  before(async function() {279    await usingEthPlaygrounds(async (_, privateKey) => {280      donor = await privateKey({filename: __filename});281    });282  });283  284  itEth('Transfers to self. In case of same frontend', async ({helper}) => {285    const [owner] = await helper.arrange.createAccounts([10n], donor);286    const collection = await helper.ft.mintCollection(owner, {});287    await collection.mint(owner, 100n);288289    const ownerProxy = helper.address.substrateToEth(owner.address);290291    // transfer to own proxy292    await collection.transfer(owner, {Ethereum: ownerProxy}, 10n);293    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(90n);294    expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(10n);295296    // transfer-from own proxy to own proxy again297    await collection.transferFrom(owner, {Ethereum: ownerProxy}, {Ethereum: ownerProxy}, 5n);298    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(90n);299    expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(10n);300  });301302  itEth('Transfers to self. In case of substrate-evm boundary', async ({helper}) => {303    const [owner] = await helper.arrange.createAccounts([10n], donor);304    const collection = await helper.ft.mintCollection(owner, {});305    await collection.mint(owner, 100n);306307    const ownerProxy = helper.address.substrateToEth(owner.address);308309    // transfer to own proxy310    await collection.transfer(owner, {Ethereum: ownerProxy}, 10n);311    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(90n);312    expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(10n);313314    // transfer-from own proxy to self315    await collection.transferFrom(owner, {Ethereum: ownerProxy}, {Substrate: owner.address}, 5n);316    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(95n);317    expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(5n);318  });319320  itEth('Transfers to self. In case of inside substrate-evm', async ({helper}) => {321    const [owner] = await helper.arrange.createAccounts([10n], donor);322    const collection = await helper.ft.mintCollection(owner, {});323    await collection.mint(owner, 100n);324325    // transfer to self again326    await collection.transfer(owner, {Substrate: owner.address}, 10n);327    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(100n);328329    // transfer-from self to self again330    await collection.transferFrom(owner, {Substrate: owner.address}, {Substrate: owner.address}, 5n);331    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(100n);332  });333334  itEth('Transfers to self. In case of inside substrate-evm when not enought "Fungibles"', async ({helper}) => {335    const [owner] = await helper.arrange.createAccounts([10n], donor);336    const collection = await helper.ft.mintCollection(owner, {});337    await collection.mint(owner, 10n);338339    // transfer to self again340    await expect(collection.transfer(owner, {Substrate: owner.address}, 11n))341      .to.be.rejectedWith(/common\.TokenValueTooLow/);342343    // transfer-from self to self again344    await expect(collection.transferFrom(owner, {Substrate: owner.address}, {Substrate: owner.address}, 12n))345      .to.be.rejectedWith(/common\.TokenValueTooLow/);346    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(10n);347  });348});
after · tests/src/transfer.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import {itEth, usingEthPlaygrounds} from './eth/util';19import {itSub, Pallets, usingPlaygrounds, expect} from './util';2021describe('Integration Test Transfer(recipient, collection_id, item_id, value)', () => {22  let donor: IKeyringPair;23  let alice: IKeyringPair;24  let bob: IKeyringPair;2526  before(async () => {27    await usingPlaygrounds(async (helper, privateKey) => {28      donor = await privateKey({filename: __filename});29      [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);30    });31  });32  33  itSub('Balance transfers and check balance', async ({helper}) => {34    const alicesBalanceBefore = await helper.balance.getSubstrate(alice.address);35    const bobsBalanceBefore = await helper.balance.getSubstrate(bob.address);3637    expect(await helper.balance.transferToSubstrate(alice, bob.address, 1n)).to.be.true;3839    const alicesBalanceAfter = await helper.balance.getSubstrate(alice.address);40    const bobsBalanceAfter = await helper.balance.getSubstrate(bob.address);4142    expect(alicesBalanceAfter < alicesBalanceBefore).to.be.true;43    expect(bobsBalanceAfter > bobsBalanceBefore).to.be.true;44  });4546  itSub('Inability to pay fees error message is correct', async ({helper}) => {47    const [zero] = await helper.arrange.createAccounts([0n], donor);4849    // console.error = () => {};50    // The following operation throws an error into the console and the logs. Pay it no heed as long as the test succeeds.51    await expect(helper.balance.transferToSubstrate(zero, donor.address, 1n))52      .to.be.rejectedWith('Inability to pay some fees , e.g. account balance too low');53  });5455  itSub('[nft] User can transfer owned token', async ({helper}) => {56    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-1-NFT', description: '', tokenPrefix: 'T'});57    const nft = await collection.mintToken(alice);5859    await nft.transfer(alice, {Substrate: bob.address});60    expect(await nft.getOwner()).to.be.deep.equal({Substrate: bob.address});61  });6263  itSub('[fungible] User can transfer owned token', async ({helper}) => {64    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-1-FT', description: '', tokenPrefix: 'T'});65    await collection.mint(alice, 10n);6667    await collection.transfer(alice, {Substrate: bob.address}, 9n);68    expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(9n);69    expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(1n);70  });7172  itSub.ifWithPallets('[refungible] User can transfer owned token', [Pallets.ReFungible], async ({helper}) => {73    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-1-RFT', description: '', tokenPrefix: 'T'});74    const rft = await collection.mintToken(alice, 10n);7576    await rft.transfer(alice, {Substrate: bob.address}, 9n);77    expect(await rft.getBalance({Substrate: bob.address})).to.be.equal(9n);78    expect(await rft.getBalance({Substrate: alice.address})).to.be.equal(1n);79  });8081  itSub('[nft] Collection admin can transfer owned token', async ({helper}) => {82    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-2-NFT', description: '', tokenPrefix: 'T'});83    await collection.addAdmin(alice, {Substrate: bob.address});8485    const nft = await collection.mintToken(bob, {Substrate: bob.address});86    await nft.transfer(bob, {Substrate: alice.address});8788    expect(await nft.getOwner()).to.be.deep.equal({Substrate: alice.address});89  });9091  itSub('[fungible] Collection admin can transfer owned token', async ({helper}) => {92    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-2-FT', description: '', tokenPrefix: 'T'});93    await collection.addAdmin(alice, {Substrate: bob.address});9495    await collection.mint(bob, 10n, {Substrate: bob.address});96    await collection.transfer(bob, {Substrate: alice.address}, 1n);9798    expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(9n);99    expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(1n);100  });101102  itSub.ifWithPallets('[refungible] Collection admin can transfer owned token', [Pallets.ReFungible], async ({helper}) => {103    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-2-RFT', description: '', tokenPrefix: 'T'});104    await collection.addAdmin(alice, {Substrate: bob.address});105106    const rft = await collection.mintToken(bob, 10n, {Substrate: bob.address});107    await rft.transfer(bob, {Substrate: alice.address}, 1n);108109    expect(await rft.getBalance({Substrate: bob.address})).to.be.equal(9n);110    expect(await rft.getBalance({Substrate: alice.address})).to.be.equal(1n);111  });112});113114describe('Negative Integration Test Transfer(recipient, collection_id, item_id, value)', () => {115  let alice: IKeyringPair;116  let bob: IKeyringPair;117118  before(async () => {119    await usingPlaygrounds(async (helper, privateKey) => {120      const donor = await privateKey({filename: __filename});121      [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);122    });123  });124125126  itSub('[nft] Transfer with not existed collection_id', async ({helper}) => {127    const collectionId = (1 << 32) - 1;128    await expect(helper.nft.transferToken(alice, collectionId, 1, {Substrate: bob.address}))129      .to.be.rejectedWith(/common\.CollectionNotFound/);130  });131132  itSub('[fungible] Transfer with not existed collection_id', async ({helper}) => {133    const collectionId = (1 << 32) - 1;134    await expect(helper.ft.transfer(alice, collectionId, {Substrate: bob.address}))135      .to.be.rejectedWith(/common\.CollectionNotFound/);136  });137138  itSub.ifWithPallets('[refungible] Transfer with not existed collection_id', [Pallets.ReFungible], async ({helper}) => {139    const collectionId = (1 << 32) - 1;140    await expect(helper.rft.transferToken(alice, collectionId, 1, {Substrate: bob.address}))141      .to.be.rejectedWith(/common\.CollectionNotFound/);142  });143144  itSub('[nft] Transfer with deleted collection_id', async ({helper}) => {145    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-1-NFT', description: '', tokenPrefix: 'T'});146    const nft = await collection.mintToken(alice);147148    await nft.burn(alice);149    await collection.burn(alice);150151    await expect(nft.transfer(alice, {Substrate: bob.address}))152      .to.be.rejectedWith(/common\.CollectionNotFound/);153  });154155  itSub('[fungible] Transfer with deleted collection_id', async ({helper}) => {156    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-1-FT', description: '', tokenPrefix: 'T'});157    await collection.mint(alice, 10n);158159    await collection.burnTokens(alice, 10n);160    await collection.burn(alice);161162    await expect(collection.transfer(alice, {Substrate: bob.address}))163      .to.be.rejectedWith(/common\.CollectionNotFound/);164  });165  166  itSub.ifWithPallets('[refungible] Transfer with deleted collection_id', [Pallets.ReFungible], async ({helper}) => {167    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-1-RFT', description: '', tokenPrefix: 'T'});168    const rft = await collection.mintToken(alice, 10n);169170    await rft.burn(alice, 10n);171    await collection.burn(alice);172173    await expect(rft.transfer(alice, {Substrate: bob.address}))174      .to.be.rejectedWith(/common\.CollectionNotFound/);175  });176177  itSub('[nft] Transfer with not existed item_id', async ({helper}) => {178    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-2-NFT', description: '', tokenPrefix: 'T'});179    await expect(collection.transferToken(alice, 1, {Substrate: bob.address}))180      .to.be.rejectedWith(/common\.TokenNotFound/);181  });182183  itSub('[fungible] Transfer with not existed item_id', async ({helper}) => {184    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-2-FT', description: '', tokenPrefix: 'T'});185    await expect(collection.transfer(alice, {Substrate: bob.address}))186      .to.be.rejectedWith(/common\.TokenValueTooLow/);187  });188189  itSub.ifWithPallets('[refungible] Transfer with not existed item_id', [Pallets.ReFungible], async ({helper}) => {190    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-2-RFT', description: '', tokenPrefix: 'T'});191    await expect(collection.transferToken(alice, 1, {Substrate: bob.address}))192      .to.be.rejectedWith(/common\.TokenValueTooLow/);193  });194195  itSub('Zero transfer NFT', async ({helper}) => {196    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-3-NFT', description: '', tokenPrefix: 'T'});197    const tokenAlice = await collection.mintToken(alice, {Substrate: alice.address});198    const tokenBob = await collection.mintToken(alice, {Substrate: bob.address});199    // 1. Zero transfer of own tokens allowed:200    await helper.executeExtrinsic(alice, 'api.tx.unique.transfer', [{Substrate: bob.address}, collection.collectionId, tokenAlice.tokenId, 0]);201    // 2. Zero transfer of non-owned tokens not allowed:202    await expect(helper.executeExtrinsic(alice, 'api.tx.unique.transfer', [{Substrate: alice.address}, collection.collectionId, tokenBob.tokenId, 0])).to.be.rejectedWith('common.NoPermission');203    // 3. Zero transfer of non-existing tokens not allowed:204    await expect(helper.executeExtrinsic(alice, 'api.tx.unique.transfer', [{Substrate: alice.address}, collection.collectionId, 10, 0])).to.be.rejectedWith('common.TokenNotFound');205    expect(await tokenAlice.getOwner()).to.deep.eq({Substrate: alice.address});206    expect(await tokenBob.getOwner()).to.deep.eq({Substrate: bob.address});207    // 4. Storage is not corrupted:208    await tokenAlice.transfer(alice, {Substrate: bob.address});209    await tokenBob.transfer(bob, {Substrate: alice.address});210    expect(await tokenAlice.getOwner()).to.deep.eq({Substrate: bob.address});211    expect(await tokenBob.getOwner()).to.deep.eq({Substrate: alice.address});212  });213214  itSub('[nft] Transfer with deleted item_id', async ({helper}) => {215    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-3-NFT', description: '', tokenPrefix: 'T'});216    const nft = await collection.mintToken(alice);217218    await nft.burn(alice);219220    await expect(nft.transfer(alice, {Substrate: bob.address}))221      .to.be.rejectedWith(/common\.TokenNotFound/);222  });223224  itSub('[fungible] Transfer with deleted item_id', async ({helper}) => {225    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-3-FT', description: '', tokenPrefix: 'T'});226    await collection.mint(alice, 10n);227228    await collection.burnTokens(alice, 10n);229230    await expect(collection.transfer(alice, {Substrate: bob.address}))231      .to.be.rejectedWith(/common\.TokenValueTooLow/);232  });233234  itSub.ifWithPallets('[refungible] Transfer with deleted item_id', [Pallets.ReFungible], async ({helper}) => {235    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-3-RFT', description: '', tokenPrefix: 'T'});236    const rft = await collection.mintToken(alice, 10n);237238    await rft.burn(alice, 10n);239240    await expect(rft.transfer(alice, {Substrate: bob.address}))241      .to.be.rejectedWith(/common\.TokenValueTooLow/);242  });243244  itSub('[nft] Transfer with recipient that is not owner', async ({helper}) => {245    const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-4-NFT', description: '', tokenPrefix: 'T'});246    const nft = await collection.mintToken(alice);247248    await expect(nft.transfer(bob, {Substrate: bob.address}))249      .to.be.rejectedWith(/common\.NoPermission/);250    expect(await nft.getOwner()).to.be.deep.equal({Substrate: alice.address});251  });252253  itSub('[fungible] Transfer with recipient that is not owner', async ({helper}) => {254    const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-4-FT', description: '', tokenPrefix: 'T'});255    await collection.mint(alice, 10n);256257    await expect(collection.transfer(bob, {Substrate: bob.address}, 9n))258      .to.be.rejectedWith(/common\.TokenValueTooLow/);259    expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(0n);260    expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(10n);261  });262263  itSub.ifWithPallets('[refungible] Transfer with recipient that is not owner', [Pallets.ReFungible], async ({helper}) => {264    const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-1-RFT', description: '', tokenPrefix: 'T'});265    const rft = await collection.mintToken(alice, 10n);266267    await expect(rft.transfer(bob, {Substrate: bob.address}, 9n))268      .to.be.rejectedWith(/common\.TokenValueTooLow/);269    expect(await rft.getBalance({Substrate: bob.address})).to.be.equal(0n);270    expect(await rft.getBalance({Substrate: alice.address})).to.be.equal(10n);271  });272});273274describe('Transfers to self (potentially over substrate-evm boundary)', () => {275  let donor: IKeyringPair;276277  before(async function() {278    await usingEthPlaygrounds(async (_, privateKey) => {279      donor = await privateKey({filename: __filename});280    });281  });282  283  itEth('Transfers to self. In case of same frontend', async ({helper}) => {284    const [owner] = await helper.arrange.createAccounts([10n], donor);285    const collection = await helper.ft.mintCollection(owner, {});286    await collection.mint(owner, 100n);287288    const ownerProxy = helper.address.substrateToEth(owner.address);289290    // transfer to own proxy291    await collection.transfer(owner, {Ethereum: ownerProxy}, 10n);292    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(90n);293    expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(10n);294295    // transfer-from own proxy to own proxy again296    await collection.transferFrom(owner, {Ethereum: ownerProxy}, {Ethereum: ownerProxy}, 5n);297    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(90n);298    expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(10n);299  });300301  itEth('Transfers to self. In case of substrate-evm boundary', async ({helper}) => {302    const [owner] = await helper.arrange.createAccounts([10n], donor);303    const collection = await helper.ft.mintCollection(owner, {});304    await collection.mint(owner, 100n);305306    const ownerProxy = helper.address.substrateToEth(owner.address);307308    // transfer to own proxy309    await collection.transfer(owner, {Ethereum: ownerProxy}, 10n);310    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(90n);311    expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(10n);312313    // transfer-from own proxy to self314    await collection.transferFrom(owner, {Ethereum: ownerProxy}, {Substrate: owner.address}, 5n);315    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(95n);316    expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(5n);317  });318319  itEth('Transfers to self. In case of inside substrate-evm', async ({helper}) => {320    const [owner] = await helper.arrange.createAccounts([10n], donor);321    const collection = await helper.ft.mintCollection(owner, {});322    await collection.mint(owner, 100n);323324    // transfer to self again325    await collection.transfer(owner, {Substrate: owner.address}, 10n);326    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(100n);327328    // transfer-from self to self again329    await collection.transferFrom(owner, {Substrate: owner.address}, {Substrate: owner.address}, 5n);330    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(100n);331  });332333  itEth('Transfers to self. In case of inside substrate-evm when not enought "Fungibles"', async ({helper}) => {334    const [owner] = await helper.arrange.createAccounts([10n], donor);335    const collection = await helper.ft.mintCollection(owner, {});336    await collection.mint(owner, 10n);337338    // transfer to self again339    await expect(collection.transfer(owner, {Substrate: owner.address}, 11n))340      .to.be.rejectedWith(/common\.TokenValueTooLow/);341342    // transfer-from self to self again343    await expect(collection.transferFrom(owner, {Substrate: owner.address}, {Substrate: owner.address}, 12n))344      .to.be.rejectedWith(/common\.TokenValueTooLow/);345    expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(10n);346  });347});
modifiedtests/src/transferFrom.test.tsdiffbeforeafterboth
--- a/tests/src/transferFrom.test.ts
+++ b/tests/src/transferFrom.test.ts
@@ -351,18 +351,17 @@
   });
 
   itSub('zero transfer NFT', async ({helper}) => {
-    const api = helper.getApi();
     const collection = await helper.nft.mintCollection(alice, {name: 'Zero', description: 'Zero transfer', tokenPrefix: 'TF'});
     const notApprovedNft = await collection.mintToken(alice, {Substrate: bob.address});
     const approvedNft = await collection.mintToken(alice, {Substrate: bob.address});
     await approvedNft.approve(bob, {Substrate: alice.address});
 
     // 1. Cannot zero transferFrom (non-existing token)
-    await expect(helper.signTransaction(alice, api.tx.unique.transferFrom({Substrate: bob.address}, {Substrate: alice.address}, collection.collectionId, 9999, 0))).to.be.rejectedWith('common.ApprovedValueTooLow');
+    await expect(helper.executeExtrinsic(alice, 'api.tx.unique.transferFrom', [{Substrate: bob.address}, {Substrate: alice.address}, collection.collectionId, 9999, 0])).to.be.rejectedWith('common.ApprovedValueTooLow');
     // 2. Cannot zero transferFrom (not approved token)
-    await expect(helper.signTransaction(alice, api.tx.unique.transferFrom({Substrate: bob.address}, {Substrate: alice.address}, collection.collectionId, notApprovedNft.tokenId, 0))).to.be.rejectedWith('common.NoPermission');
+    await expect(helper.executeExtrinsic(alice, 'api.tx.unique.transferFrom', [{Substrate: bob.address}, {Substrate: alice.address}, collection.collectionId, notApprovedNft.tokenId, 0])).to.be.rejectedWith('common.ApprovedValueTooLow');
     // 3. Can zero transferFrom (approved token):
-    await helper.signTransaction(alice, api.tx.unique.transferFrom({Substrate: bob.address}, {Substrate: alice.address}, collection.collectionId, approvedNft.tokenId, 0));
+    await helper.executeExtrinsic(alice, 'api.tx.unique.transferFrom', [{Substrate: bob.address}, {Substrate: alice.address}, collection.collectionId, approvedNft.tokenId, 0]);
 
     // 4.1 approvedNft still approved:
     expect(await approvedNft.isApproved({Substrate: alice.address})).to.be.true;