difftreelog
Merge pull request #747 from UniqueNetwork/tests/refungible
in: master
Transfer tests
14 files changed
pallets/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());
pallets/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())
}
}
pallets/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.
pallets/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());
}
pallets/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);
tests/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);
@@ -155,4 +180,48 @@
await expect(collection.burnTokens(alice, 11n)).to.be.rejectedWith('common.TokenValueTooLow');
expect(await collection.getBalance({Substrate: alice.address})).to.eq(10n);
});
+
+ itSub('Zero burn NFT', async ({helper}) => {
+ 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.executeExtrinsic(alice, 'api.tx.unique.burnItem', [collection.collectionId, tokenAlice.tokenId, 0]);
+ // 2. Zero burn of non-owned tokens not allowed:
+ 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.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(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 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.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.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.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;
+ // 4.2 bob is still the owner:
+ expect(await approvedNft.getOwner()).to.deep.eq({Substrate: bob.address});
+ expect(await notApprovedNft.getOwner()).to.deep.eq({Substrate: bob.address});
+ // 4.3 Alice can burn approved nft:
+ await approvedNft.burnFrom(alice, {Substrate: bob.address});
+ expect(await approvedNft.doesExist()).to.be.false;
+ });
});
tests/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);
tests/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', () => {
tests/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);
tests/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);
tests/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);
+ });
+});
tests/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);
+ });
+});
tests/src/transfer.test.tsdiffbeforeafterboth1// 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 });124125 itSub('[nft] Transfer with not existed collection_id', async ({helper}) => {126 const collectionId = (1 << 32) - 1;127 await expect(helper.nft.transferToken(alice, collectionId, 1, {Substrate: bob.address}))128 .to.be.rejectedWith(/common\.CollectionNotFound/);129 });130131 itSub('[fungible] Transfer with not existed collection_id', async ({helper}) => {132 const collectionId = (1 << 32) - 1;133 await expect(helper.ft.transfer(alice, collectionId, {Substrate: bob.address}))134 .to.be.rejectedWith(/common\.CollectionNotFound/);135 });136137 itSub.ifWithPallets('[refungible] Transfer with not existed collection_id', [Pallets.ReFungible], async ({helper}) => {138 const collectionId = (1 << 32) - 1;139 await expect(helper.rft.transferToken(alice, collectionId, 1, {Substrate: bob.address}))140 .to.be.rejectedWith(/common\.CollectionNotFound/);141 });142143 itSub('[nft] Transfer with deleted collection_id', async ({helper}) => {144 const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-1-NFT', description: '', tokenPrefix: 'T'});145 const nft = await collection.mintToken(alice);146147 await nft.burn(alice);148 await collection.burn(alice);149150 await expect(nft.transfer(alice, {Substrate: bob.address}))151 .to.be.rejectedWith(/common\.CollectionNotFound/);152 });153154 itSub('[fungible] Transfer with deleted collection_id', async ({helper}) => {155 const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-1-FT', description: '', tokenPrefix: 'T'});156 await collection.mint(alice, 10n);157158 await collection.burnTokens(alice, 10n);159 await collection.burn(alice);160161 await expect(collection.transfer(alice, {Substrate: bob.address}))162 .to.be.rejectedWith(/common\.CollectionNotFound/);163 });164 165 itSub.ifWithPallets('[refungible] Transfer with deleted collection_id', [Pallets.ReFungible], async ({helper}) => {166 const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-1-RFT', description: '', tokenPrefix: 'T'});167 const rft = await collection.mintToken(alice, 10n);168169 await rft.burn(alice, 10n);170 await collection.burn(alice);171172 await expect(rft.transfer(alice, {Substrate: bob.address}))173 .to.be.rejectedWith(/common\.CollectionNotFound/);174 });175176 itSub('[nft] Transfer with not existed item_id', async ({helper}) => {177 const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-2-NFT', description: '', tokenPrefix: 'T'});178 await expect(collection.transferToken(alice, 1, {Substrate: bob.address}))179 .to.be.rejectedWith(/common\.TokenNotFound/);180 });181182 itSub('[fungible] Transfer with not existed item_id', async ({helper}) => {183 const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-2-FT', description: '', tokenPrefix: 'T'});184 await expect(collection.transfer(alice, {Substrate: bob.address}))185 .to.be.rejectedWith(/common\.TokenValueTooLow/);186 });187188 itSub.ifWithPallets('[refungible] Transfer with not existed item_id', [Pallets.ReFungible], async ({helper}) => {189 const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-2-RFT', description: '', tokenPrefix: 'T'});190 await expect(collection.transferToken(alice, 1, {Substrate: bob.address}))191 .to.be.rejectedWith(/common\.TokenValueTooLow/);192 });193194 itSub('[nft] Transfer with deleted item_id', async ({helper}) => {195 const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-3-NFT', description: '', tokenPrefix: 'T'});196 const nft = await collection.mintToken(alice);197198 await nft.burn(alice);199200 await expect(nft.transfer(alice, {Substrate: bob.address}))201 .to.be.rejectedWith(/common\.TokenNotFound/);202 });203204 itSub('[fungible] Transfer with deleted item_id', async ({helper}) => {205 const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-3-FT', description: '', tokenPrefix: 'T'});206 await collection.mint(alice, 10n);207208 await collection.burnTokens(alice, 10n);209210 await expect(collection.transfer(alice, {Substrate: bob.address}))211 .to.be.rejectedWith(/common\.TokenValueTooLow/);212 });213214 itSub.ifWithPallets('[refungible] Transfer with deleted item_id', [Pallets.ReFungible], async ({helper}) => {215 const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-Neg-3-RFT', description: '', tokenPrefix: 'T'});216 const rft = await collection.mintToken(alice, 10n);217218 await rft.burn(alice, 10n);219220 await expect(rft.transfer(alice, {Substrate: bob.address}))221 .to.be.rejectedWith(/common\.TokenValueTooLow/);222 });223224 itSub('[nft] Transfer with recipient that is not owner', async ({helper}) => {225 const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-4-NFT', description: '', tokenPrefix: 'T'});226 const nft = await collection.mintToken(alice);227228 await expect(nft.transfer(bob, {Substrate: bob.address}))229 .to.be.rejectedWith(/common\.NoPermission/);230 expect(await nft.getOwner()).to.be.deep.equal({Substrate: alice.address});231 });232233 itSub('[fungible] Transfer with recipient that is not owner', async ({helper}) => {234 const collection = await helper.ft.mintCollection(alice, {name: 'Transfer-Neg-4-FT', description: '', tokenPrefix: 'T'});235 await collection.mint(alice, 10n);236237 await expect(collection.transfer(bob, {Substrate: bob.address}, 9n))238 .to.be.rejectedWith(/common\.TokenValueTooLow/);239 expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(0n);240 expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(10n);241 });242243 itSub.ifWithPallets('[refungible] Transfer with recipient that is not owner', [Pallets.ReFungible], async ({helper}) => {244 const collection = await helper.rft.mintCollection(alice, {name: 'Transfer-1-RFT', description: '', tokenPrefix: 'T'});245 const rft = await collection.mintToken(alice, 10n);246247 await expect(rft.transfer(bob, {Substrate: bob.address}, 9n))248 .to.be.rejectedWith(/common\.TokenValueTooLow/);249 expect(await rft.getBalance({Substrate: bob.address})).to.be.equal(0n);250 expect(await rft.getBalance({Substrate: alice.address})).to.be.equal(10n);251 });252});253254describe('Transfers to self (potentially over substrate-evm boundary)', () => {255 let donor: IKeyringPair;256257 before(async function() {258 await usingEthPlaygrounds(async (_, privateKey) => {259 donor = await privateKey({filename: __filename});260 });261 });262 263 itEth('Transfers to self. In case of same frontend', async ({helper}) => {264 const [owner] = await helper.arrange.createAccounts([10n], donor);265 const collection = await helper.ft.mintCollection(owner, {});266 await collection.mint(owner, 100n);267268 const ownerProxy = helper.address.substrateToEth(owner.address);269270 // transfer to own proxy271 await collection.transfer(owner, {Ethereum: ownerProxy}, 10n);272 expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(90n);273 expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(10n);274275 // transfer-from own proxy to own proxy again276 await collection.transferFrom(owner, {Ethereum: ownerProxy}, {Ethereum: ownerProxy}, 5n);277 expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(90n);278 expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(10n);279 });280281 itEth('Transfers to self. In case of substrate-evm boundary', async ({helper}) => {282 const [owner] = await helper.arrange.createAccounts([10n], donor);283 const collection = await helper.ft.mintCollection(owner, {});284 await collection.mint(owner, 100n);285286 const ownerProxy = helper.address.substrateToEth(owner.address);287288 // transfer to own proxy289 await collection.transfer(owner, {Ethereum: ownerProxy}, 10n);290 expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(90n);291 expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(10n);292293 // transfer-from own proxy to self294 await collection.transferFrom(owner, {Ethereum: ownerProxy}, {Substrate: owner.address}, 5n);295 expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(95n);296 expect(await collection.getBalance({Ethereum: ownerProxy})).to.be.equal(5n);297 });298299 itEth('Transfers to self. In case of inside substrate-evm', async ({helper}) => {300 const [owner] = await helper.arrange.createAccounts([10n], donor);301 const collection = await helper.ft.mintCollection(owner, {});302 await collection.mint(owner, 100n);303304 // transfer to self again305 await collection.transfer(owner, {Substrate: owner.address}, 10n);306 expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(100n);307308 // transfer-from self to self again309 await collection.transferFrom(owner, {Substrate: owner.address}, {Substrate: owner.address}, 5n);310 expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(100n);311 });312313 itEth('Transfers to self. In case of inside substrate-evm when not enought "Fungibles"', async ({helper}) => {314 const [owner] = await helper.arrange.createAccounts([10n], donor);315 const collection = await helper.ft.mintCollection(owner, {});316 await collection.mint(owner, 10n);317318 // transfer to self again319 await expect(collection.transfer(owner, {Substrate: owner.address}, 11n))320 .to.be.rejectedWith(/common\.TokenValueTooLow/);321322 // transfer-from self to self again323 await expect(collection.transferFrom(owner, {Substrate: owner.address}, {Substrate: owner.address}, 12n))324 .to.be.rejectedWith(/common\.TokenValueTooLow/);325 expect(await collection.getBalance({Substrate: owner.address})).to.be.equal(10n);326 });327});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});tests/src/transferFrom.test.tsdiffbeforeafterboth--- a/tests/src/transferFrom.test.ts
+++ b/tests/src/transferFrom.test.ts
@@ -349,4 +349,27 @@
{Substrate: charlie.address},
)).to.be.rejectedWith(/common\.ApprovedValueTooLow/);
});
+
+ itSub('zero transfer NFT', async ({helper}) => {
+ 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.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.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.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;
+ // 4.2 bob is still the owner:
+ expect(await approvedNft.getOwner()).to.deep.eq({Substrate: bob.address});
+ expect(await notApprovedNft.getOwner()).to.deep.eq({Substrate: bob.address});
+ // 4.3 Alice can transfer approved nft:
+ await approvedNft.transferFrom(alice, {Substrate: bob.address}, {Substrate: alice.address});
+ expect(await approvedNft.getOwner()).to.deep.eq({Substrate: alice.address});
+ });
});