difftreelog
Merge pull request #747 from UniqueNetwork/tests/refungible
in: master
Transfer tests
14 files changed
pallets/fungible/src/lib.rsdiffbeforeafterboth379 let balance_from = <Balance<T>>::get((collection.id, from))379 let balance_from = <Balance<T>>::get((collection.id, from))380 .checked_sub(amount)380 .checked_sub(amount)381 .ok_or(<CommonError<T>>::TokenValueTooLow)?;381 .ok_or(<CommonError<T>>::TokenValueTooLow)?;382 let balance_to = if from != to {382 let balance_to = if from != to && amount != 0 {383 Some(383 Some(384 <Balance<T>>::get((collection.id, to))384 <Balance<T>>::get((collection.id, to))385 .checked_add(amount)385 .checked_add(amount)391391392 // =========392 // =========393394 if let Some(balance_to) = balance_to {395 // from != to && amount != 0393396394 <PalletStructure<T>>::nest_if_sent_to_token(397 <PalletStructure<T>>::nest_if_sent_to_token(395 from.clone(),398 from.clone(),399 nesting_budget,402 nesting_budget,400 )?;403 )?;401404402 if let Some(balance_to) = balance_to {403 // from != to404 if balance_from == 0 {405 if balance_from == 0 {405 <Balance<T>>::remove((collection.id, from));406 <Balance<T>>::remove((collection.id, from));406 <PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());407 <PalletStructure<T>>::unnest_if_nested(from, collection.id, TokenId::default());407 } else {408 } else {408 <Balance<T>>::insert((collection.id, from), balance_from);409 <Balance<T>>::insert((collection.id, from), balance_from);409 }410 }410 <Balance<T>>::insert((collection.id, to), balance_to);411 <Balance<T>>::insert((collection.id, to), balance_to);411 }412 }412413413 <PalletEvm<T>>::deposit_log(414 <PalletEvm<T>>::deposit_log(414 ERC20Events::Transfer {415 ERC20Events::Transfer {pallets/nonfungible/src/common.rsdiffbeforeafterboth291 <CommonWeights<T>>::burn_item(),291 <CommonWeights<T>>::burn_item(),292 )292 )293 } else {293 } else {294 <Pallet<T>>::check_token_immediate_ownership(self, token, &sender)?;294 Ok(().into())295 Ok(().into())295 }296 }296 }297 }320 <CommonWeights<T>>::transfer(),321 <CommonWeights<T>>::transfer(),321 )322 )322 } else {323 } else {324 <Pallet<T>>::check_token_immediate_ownership(self, token, &from)?;323 Ok(().into())325 Ok(().into())324 }326 }325 }327 }360 <CommonWeights<T>>::transfer_from(),362 <CommonWeights<T>>::transfer_from(),361 )363 )362 } else {364 } else {365 <Pallet<T>>::check_allowed(self, &sender, &from, token, nesting_budget)?;366363 Ok(().into())367 Ok(().into())364 }368 }380 <CommonWeights<T>>::burn_from(),384 <CommonWeights<T>>::burn_from(),381 )385 )382 } else {386 } else {387 <Pallet<T>>::check_allowed(self, &sender, &from, token, nesting_budget)?;388383 Ok(().into())389 Ok(().into())384 }390 }pallets/nonfungible/src/lib.rsdiffbeforeafterboth814 <PalletCommon<T>>::set_property_permission(collection, sender, permission)814 <PalletCommon<T>>::set_property_permission(collection, sender, permission)815 }815 }816817 pub fn check_token_immediate_ownership(818 collection: &NonfungibleHandle<T>,819 token: TokenId,820 possible_owner: &T::CrossAccountId,821 ) -> DispatchResult {822 let token_data =823 <TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;824 ensure!(825 &token_data.owner == possible_owner,826 <CommonError<T>>::NoPermission827 );828 Ok(())829 }816830817 /// Transfer NFT token from one account to another.831 /// Transfer NFT token from one account to another.818 ///832 ///pallets/refungible/src/erc.rsdiffbeforeafterboth34 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,34 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,35 erc::{CommonEvmHandler, CollectionCall, static_property::key},35 erc::{CommonEvmHandler, CollectionCall, static_property::key},36 eth::EthCrossAccount,36 eth::EthCrossAccount,37 Error as CommonError,37};38};38use pallet_evm::{account::CrossAccountId, PrecompileHandle};39use pallet_evm::{account::CrossAccountId, PrecompileHandle};39use pallet_evm_coder_substrate::{call, dispatch_to_evm};40use pallet_evm_coder_substrate::{call, dispatch_to_evm};509 collection.consume_store_reads(1)?;510 collection.consume_store_reads(1)?;510 let total_supply = <TotalSupply<T>>::get((collection.id, token));511 let total_supply = <TotalSupply<T>>::get((collection.id, token));512513 if owner_balance == 0 {514 return Err(dispatch_to_evm::<T>(515 <CommonError<T>>::MustBeTokenOwner.into(),516 ));517 }518511 if total_supply != owner_balance {519 if total_supply != owner_balance {512 return Err("token has multiple owners".into());520 return Err("token has multiple owners".into());pallets/refungible/src/lib.rsdiffbeforeafterboth452 token: TokenId,452 token: TokenId,453 amount: u128,453 amount: u128,454 ) -> DispatchResult {454 ) -> DispatchResult {455 if <Balance<T>>::get((collection.id, token, owner)) == 0 {456 return Err(<CommonError<T>>::TokenValueTooLow.into());457 }458455 let total_supply = <TotalSupply<T>>::get((collection.id, token))459 let total_supply = <TotalSupply<T>>::get((collection.id, token))456 .checked_sub(amount)460 .checked_sub(amount)740744741 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));745 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));746747 if initial_balance_from == 0 {748 return Err(<CommonError<T>>::TokenValueTooLow.into());749 }750742 let updated_balance_from = initial_balance_from751 let updated_balance_from = initial_balance_from743 .checked_sub(amount)752 .checked_sub(amount)744 .ok_or(<CommonError<T>>::TokenValueTooLow)?;753 .ok_or(<CommonError<T>>::TokenValueTooLow)?;745 let mut create_target = false;754 let mut create_target = false;746 let from_to_differ = from != to;755 let from_to_differ = from != to;747 let updated_balance_to = if from != to {756 let updated_balance_to = if from != to && amount != 0 {748 let old_balance = <Balance<T>>::get((collection.id, token, to));757 let old_balance = <Balance<T>>::get((collection.id, token, to));749 if old_balance == 0 {758 if old_balance == 0 {750 create_target = true;759 create_target = true;786795787 // =========796 // =========797798 if let Some(updated_balance_to) = updated_balance_to {799 // from != to && amount != 0788800789 <PalletStructure<T>>::nest_if_sent_to_token(801 <PalletStructure<T>>::nest_if_sent_to_token(790 from.clone(),802 from.clone(),794 nesting_budget,806 nesting_budget,795 )?;807 )?;796808797 if let Some(updated_balance_to) = updated_balance_to {798 // from != to799 if updated_balance_from == 0 {809 if updated_balance_from == 0 {800 <Balance<T>>::remove((collection.id, token, from));810 <Balance<T>>::remove((collection.id, token, from));801 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);811 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);811 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);821 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);812 <Owned<T>>::insert((collection.id, to, token), true);822 <Owned<T>>::insert((collection.id, to, token), true);813 }823 }814 }824 }815825816 <PalletEvm<T>>::deposit_log(826 <PalletEvm<T>>::deposit_log(817 ERC20Events::Transfer {827 ERC20Events::Transfer {tests/src/burnItem.test.tsdiffbeforeafterboth140 await expect(token.burn(bob)).to.be.rejectedWith('common.NoPermission');140 await expect(token.burn(bob)).to.be.rejectedWith('common.NoPermission');141 });141 });142143 itSub.ifWithPallets('RFT: cannot burn non-owned token pieces', [Pallets.ReFungible], async ({helper}) => {144 const collection = await helper.rft.mintCollection(alice);145 const aliceToken = await collection.mintToken(alice, 10n, {Substrate: alice.address});146 const bobToken = await collection.mintToken(alice, 10n, {Substrate: bob.address});147148 // 1. Cannot burn non-owned token:149 await expect(bobToken.burn(alice, 0n)).to.be.rejectedWith('common.TokenValueTooLow');150 await expect(bobToken.burn(alice, 5n)).to.be.rejectedWith('common.TokenValueTooLow');151 // 2. Cannot burn non-existing token:152 await expect(helper.rft.burnToken(alice, 99999, 10)).to.be.rejectedWith('common.CollectionNotFound');153 await expect(helper.rft.burnToken(alice, collection.collectionId, 99999)).to.be.rejectedWith('common.TokenValueTooLow');154 // 3. Can burn zero amount of owned tokens (EIP-20)155 await aliceToken.burn(alice, 0n);156157 // 4. Storage is not corrupted:158 expect(await aliceToken.getTop10Owners()).to.deep.eq([{Substrate: alice.address}]);159 expect(await bobToken.getTop10Owners()).to.deep.eq([{Substrate: bob.address}]);160161 // 4.1 Tokens can be transfered:162 await aliceToken.transfer(alice, {Substrate: bob.address}, 10n);163 await bobToken.transfer(bob, {Substrate: alice.address}, 10n);164 expect(await aliceToken.getTop10Owners()).to.deep.eq([{Substrate: bob.address}]);165 expect(await bobToken.getTop10Owners()).to.deep.eq([{Substrate: alice.address}]);166 });142167143 itSub('Transfer a burned token', async ({helper}) => {168 itSub('Transfer a burned token', async ({helper}) => {144 const collection = await helper.nft.mintCollection(alice);169 const collection = await helper.nft.mintCollection(alice);156 expect(await collection.getBalance({Substrate: alice.address})).to.eq(10n);181 expect(await collection.getBalance({Substrate: alice.address})).to.eq(10n);157 });182 });183184 itSub('Zero burn NFT', async ({helper}) => {185 const collection = await helper.nft.mintCollection(alice, {name: 'Coll', description: 'Desc', tokenPrefix: 'T'});186 const tokenAlice = await collection.mintToken(alice, {Substrate: alice.address});187 const tokenBob = await collection.mintToken(alice, {Substrate: bob.address});188 189 // 1. Zero burn of own tokens allowed:190 await helper.executeExtrinsic(alice, 'api.tx.unique.burnItem', [collection.collectionId, tokenAlice.tokenId, 0]);191 // 2. Zero burn of non-owned tokens not allowed:192 await expect(helper.executeExtrinsic(alice, 'api.tx.unique.burnItem', [collection.collectionId, tokenBob.tokenId, 0])).to.be.rejectedWith('common.NoPermission');193 // 3. Zero burn of non-existing tokens not allowed:194 await expect(helper.executeExtrinsic(alice, 'api.tx.unique.burnItem', [collection.collectionId, 9999, 0])).to.be.rejectedWith('common.TokenNotFound');195 expect(await tokenAlice.doesExist()).to.be.true;196 expect(await tokenAlice.getOwner()).to.deep.eq({Substrate: alice.address});197 expect(await tokenBob.getOwner()).to.deep.eq({Substrate: bob.address});198 // 4. Storage is not corrupted:199 await tokenAlice.transfer(alice, {Substrate: bob.address});200 await tokenBob.transfer(bob, {Substrate: alice.address});201 expect(await tokenAlice.getOwner()).to.deep.eq({Substrate: bob.address});202 expect(await tokenBob.getOwner()).to.deep.eq({Substrate: alice.address});203 });204205 itSub('zero burnFrom NFT', async ({helper}) => {206 const collection = await helper.nft.mintCollection(alice, {name: 'Zero', description: 'Zero transfer', tokenPrefix: 'TF'});207 const notApprovedNft = await collection.mintToken(alice, {Substrate: bob.address});208 const approvedNft = await collection.mintToken(alice, {Substrate: bob.address});209 await approvedNft.approve(bob, {Substrate: alice.address});210211 // 1. Zero burnFrom of non-existing tokens not allowed:212 await expect(helper.executeExtrinsic(alice, 'api.tx.unique.burnFrom', [collection.collectionId, {Substrate: bob.address}, 9999, 0])).to.be.rejectedWith('common.ApprovedValueTooLow');213 // 2. Zero burnFrom of not approved tokens not allowed:214 await expect(helper.executeExtrinsic(alice, 'api.tx.unique.burnFrom', [collection.collectionId, {Substrate: bob.address}, notApprovedNft.tokenId, 0])).to.be.rejectedWith('common.ApprovedValueTooLow');215 // 3. Zero burnFrom of approved tokens allowed:216 await helper.executeExtrinsic(alice, 'api.tx.unique.burnFrom', [collection.collectionId, {Substrate: bob.address}, approvedNft.tokenId, 0]);217218 // 4.1 approvedNft still approved:219 expect(await approvedNft.isApproved({Substrate: alice.address})).to.be.true;220 // 4.2 bob is still the owner:221 expect(await approvedNft.getOwner()).to.deep.eq({Substrate: bob.address});222 expect(await notApprovedNft.getOwner()).to.deep.eq({Substrate: bob.address});223 // 4.3 Alice can burn approved nft:224 await approvedNft.burnFrom(alice, {Substrate: bob.address});225 expect(await approvedNft.doesExist()).to.be.false;226 });158});227});159228tests/src/eth/fungible.test.tsdiffbeforeafterboth277 }277 }278 });278 });279279280 itEth('Cannot transferCross() more than have', async ({helper}) => {280 ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} incorrect amount`, async ({helper}) => {281 const sender = await helper.eth.createAccountWithBalance(donor);281 const sender = await helper.eth.createAccountWithBalance(donor);282 const receiverEth = await helper.eth.createAccountWithBalance(donor);282 const receiverEth = await helper.eth.createAccountWithBalance(donor);283 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);283 const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);289 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);289 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);290 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'ft', sender);290 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'ft', sender);291291292 // 1. Cannot transfer more than have293 const receiver = testCase === 'transfer' ? receiverEth : receiverCrossEth;292 await expect(collectionEvm.methods.transferCross(receiverCrossEth, BALANCE_TO_TRANSFER).send({from: sender})).to.be.rejected;294 await expect(collectionEvm.methods[testCase](receiver, BALANCE_TO_TRANSFER).send({from: sender})).to.be.rejected;295 // 2. Zero transfer allowed (EIP-20):296 await collectionEvm.methods[testCase](receiver, 0n).send({from: sender});293 });297 }));294 298 299 295 itEth('Can perform transfer()', async ({helper}) => {300 itEth('Can perform transfer()', async ({helper}) => {tests/src/eth/nonFungible.test.tsdiffbeforeafterboth518 }518 }519 });519 });520521 ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {522 const sender = await helper.eth.createAccountWithBalance(donor);523 const tokenOwner = await helper.eth.createAccountWithBalance(donor);524 const receiverSub = minter;525 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);526527 const collection = await helper.nft.mintCollection(minter, {});528 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);529 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', sender);530531 await collection.mintToken(minter, {Ethereum: sender});532 const nonSendersToken = await collection.mintToken(minter, {Ethereum: tokenOwner});533534 // Cannot transferCross someone else's token:535 const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;536 await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;537 // Cannot transfer token if it does not exist:538 await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;539 }));520});540});521541522describe('NFT: Fees', () => {542describe('NFT: Fees', () => {tests/src/eth/reFungible.test.tsdiffbeforeafterboth413 }413 }414 });414 });415415416 itEth.skip('Cannot transferCross with invalid params', async ({helper}) => {416 ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {417 const sender = await helper.eth.createAccountWithBalance(donor);417 const sender = await helper.eth.createAccountWithBalance(donor);418 const tokenOwner = await helper.eth.createAccountWithBalance(donor);418 const tokenOwner = await helper.eth.createAccountWithBalance(donor);419 const receiverSub = minter;419 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);420 const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);420421421 const collection = await helper.rft.mintCollection(minter, {});422 const collection = await helper.rft.mintCollection(minter, {});422 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);423 const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);423 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', sender);424 const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', sender);424425425 await collection.mintToken(minter, 50n, {Ethereum: sender});426 await collection.mintToken(minter, 50n, {Ethereum: sender});426 const notSendersToken = await collection.mintToken(minter, 50n, {Ethereum: tokenOwner});427 const nonSendersToken = await collection.mintToken(minter, 50n, {Ethereum: tokenOwner});428427 // Cannot transferCross someone else's token:429 // Cannot transferCross someone else's token:430 const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;428 await expect(collectionEvm.methods.transferCross(receiverCrossSub, notSendersToken.tokenId).send({from: sender})).to.be.rejected;431 await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;429 // FIXME: (transaction successful): Cannot transfer token if it does not exist:432 // Cannot transfer token if it does not exist:430 await expect(collectionEvm.methods.transferCross(receiverCrossSub, 999999).send({from: sender})).to.be.rejected;433 await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;431 });434 }));432435433 itEth('transfer event on transfer from partial ownership to full ownership', async ({helper}) => {436 itEth('transfer event on transfer from partial ownership to full ownership', async ({helper}) => {434 const caller = await helper.eth.createAccountWithBalance(donor);437 const caller = await helper.eth.createAccountWithBalance(donor);tests/src/eth/reFungibleToken.test.tsdiffbeforeafterboth227 }227 }228 });228 });229230 [231 'transfer',232 // 'transferCross', // TODO233 ].map(testCase => 234 itEth(`Cannot ${testCase}() non-owned token`, async ({helper}) => {235 const owner = await helper.eth.createAccountWithBalance(donor);236 const receiver = await helper.eth.createAccountWithBalance(donor);237 const collection = await helper.rft.mintCollection(alice);238 const rftOwner = await collection.mintToken(alice, 10n, {Ethereum: owner});239 const rftReceiver = await collection.mintToken(alice, 10n, {Ethereum: receiver});240 const tokenIdNonExist = 9999999;241 242 const tokenAddress1 = helper.ethAddress.fromTokenId(collection.collectionId, rftOwner.tokenId);243 const tokenAddress2 = helper.ethAddress.fromTokenId(collection.collectionId, rftReceiver.tokenId);244 const tokenAddressNonExist = helper.ethAddress.fromTokenId(collection.collectionId, tokenIdNonExist);245 const tokenEvmOwner = helper.ethNativeContract.rftToken(tokenAddress1, owner);246 const tokenEvmReceiver = helper.ethNativeContract.rftToken(tokenAddress2, owner);247 const tokenEvmNonExist = helper.ethNativeContract.rftToken(tokenAddressNonExist, owner);248 249 // 1. Can transfer zero amount (EIP-20):250 await tokenEvmOwner.methods[testCase](receiver, 0).send({from: owner});251 // 2. Cannot transfer non-owned token:252 await expect(tokenEvmReceiver.methods[testCase](owner, 0).send({from: owner})).to.be.rejected;253 await expect(tokenEvmReceiver.methods[testCase](owner, 5).send({from: owner})).to.be.rejected;254 // 3. Cannot transfer non-existing token:255 await expect(tokenEvmNonExist.methods[testCase](owner, 0).send({from: owner})).to.be.rejected;256 await expect(tokenEvmNonExist.methods[testCase](owner, 5).send({from: owner})).to.be.rejected;257258 // 4. Storage is not corrupted:259 expect(await rftOwner.getTop10Owners()).to.deep.eq([{Ethereum: owner.toLowerCase()}]);260 expect(await rftReceiver.getTop10Owners()).to.deep.eq([{Ethereum: receiver.toLowerCase()}]);261 expect(await helper.rft.getTokenTop10Owners(collection.collectionId, tokenIdNonExist)).to.deep.eq([]); // TODO262263 // 4.1 Tokens can be transferred:264 await tokenEvmOwner.methods[testCase](receiver, 10).send({from: owner});265 await tokenEvmReceiver.methods[testCase](owner, 10).send({from: receiver});266 expect(await rftOwner.getTop10Owners()).to.deep.eq([{Ethereum: receiver.toLowerCase()}]);267 expect(await rftReceiver.getTop10Owners()).to.deep.eq([{Ethereum: owner.toLowerCase()}]);268 }));229269230 itEth('Can perform repartition()', async ({helper}) => {270 itEth('Can perform repartition()', async ({helper}) => {231 const owner = await helper.eth.createAccountWithBalance(donor);271 const owner = await helper.eth.createAccountWithBalance(donor);tests/src/fungible.test.tsdiffbeforeafterboth15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.161617import {IKeyringPair} from '@polkadot/types/types';17import {IKeyringPair} from '@polkadot/types/types';18import {itSub, usingPlaygrounds, expect} from './util';18import {itSub, usingPlaygrounds, expect, requirePalletsOrSkip, Pallets} from './util';191920const U128_MAX = (1n << 128n) - 1n;20const U128_MAX = (1n << 128n) - 1n;2121146 });146 });147});147});148149describe('Fungible negative tests', () => {150 let donor: IKeyringPair;151 let alice: IKeyringPair;152 let bob: IKeyringPair;153 let charlie: IKeyringPair;154155 before(async function() {156 await usingPlaygrounds(async (helper, privateKey) => {157 requirePalletsOrSkip(this, helper, [Pallets.Fungible]);158159 donor = await privateKey({filename: __filename});160 [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);161 });162 });163164 itSub('Cannot transfer incorrect amount of tokens', async ({helper}) => {165 const collection = await helper.ft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});166 const nonExistingCollection = helper.ft.getCollectionObject(99999);167 await collection.mint(alice, 10n, {Substrate: bob.address});168169 // 1. Alice cannot transfer more than 0 tokens if balance low:170 await expect(collection.transfer(alice, {Substrate: charlie.address}, 1n)).to.be.rejectedWith('common.TokenValueTooLow');171 await expect(collection.transfer(alice, {Substrate: charlie.address}, 100n)).to.be.rejectedWith('common.TokenValueTooLow');172 173 // 2. Alice cannot transfer non-existing token:174 await expect(nonExistingCollection.transfer(alice, {Substrate: charlie.address}, 0n)).to.be.rejectedWith('common.CollectionNotFound');175 await expect(nonExistingCollection.transfer(alice, {Substrate: charlie.address}, 1n)).to.be.rejectedWith('common.CollectionNotFound');176 177 // 3. Zero transfer allowed (EIP-20):178 await collection.transfer(bob, {Substrate: charlie.address}, 0n);179 // 3.1 even if the balance = 0180 await collection.transfer(alice, {Substrate: charlie.address}, 0n);181182 expect(await collection.getBalance({Substrate: alice.address})).to.eq(0n);183 expect(await collection.getBalance({Substrate: bob.address})).to.eq(10n);184 expect(await collection.getBalance({Substrate: charlie.address})).to.eq(0n);185 });186});148187tests/src/refungible.test.tsdiffbeforeafterboth255 });255 });256});256});257258describe('Refungible negative tests', () => {259 let donor: IKeyringPair;260 let alice: IKeyringPair;261 let bob: IKeyringPair;262 let charlie: IKeyringPair;263264 before(async function() {265 await usingPlaygrounds(async (helper, privateKey) => {266 requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);267268 donor = await privateKey({filename: __filename});269 [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);270 });271 });272273 itSub('Cannot transfer incorrect amount of token pieces', async ({helper}) => {274 const collection = await helper.rft.mintCollection(alice, {name: 'test', description: 'test', tokenPrefix: 'test'});275 const tokenAlice = await collection.mintToken(alice, 10n, {Substrate: alice.address});276 const tokenBob = await collection.mintToken(alice, 10n, {Substrate: bob.address});277278 // 1. Alice cannot transfer Bob's token:279 await expect(tokenBob.transfer(alice, {Substrate: charlie.address}, 0n)).to.be.rejectedWith('common.TokenValueTooLow');280 await expect(tokenBob.transfer(alice, {Substrate: charlie.address}, 1n)).to.be.rejectedWith('common.TokenValueTooLow');281 await expect(tokenBob.transfer(alice, {Substrate: charlie.address}, 10n)).to.be.rejectedWith('common.TokenValueTooLow');282 await expect(tokenBob.transfer(alice, {Substrate: charlie.address}, 100n)).to.be.rejectedWith('common.TokenValueTooLow');283 284 // 2. Alice cannot transfer non-existing token:285 await expect(collection.transferToken(alice, 100, {Substrate: charlie.address}, 0n)).to.be.rejectedWith('common.TokenValueTooLow');286 await expect(collection.transferToken(alice, 100, {Substrate: charlie.address}, 1n)).to.be.rejectedWith('common.TokenValueTooLow');287288 // 3. Zero transfer allowed (EIP-20):289 await tokenAlice.transfer(alice, {Substrate: charlie.address}, 0n);290291 expect(await tokenAlice.getTop10Owners()).to.deep.eq([{Substrate: alice.address}]);292 expect(await tokenBob.getTop10Owners()).to.deep.eq([{Substrate: bob.address}]);293 expect(await tokenAlice.getBalance({Substrate: alice.address})).to.eq(10n);294 expect(await tokenBob.getBalance({Substrate: bob.address})).to.eq(10n);295 expect(await tokenBob.getBalance({Substrate: charlie.address})).to.eq(0n);296 });297});257298258tests/src/transfer.test.tsdiffbeforeafterboth191 .to.be.rejectedWith(/common\.TokenValueTooLow/);192 .to.be.rejectedWith(/common\.TokenValueTooLow/);192 });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 });193213194 itSub('[nft] Transfer with deleted item_id', async ({helper}) => {214 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'});215 const collection = await helper.nft.mintCollection(alice, {name: 'Transfer-Neg-3-NFT', description: '', tokenPrefix: 'T'});tests/src/transferFrom.test.tsdiffbeforeafterboth350 )).to.be.rejectedWith(/common\.ApprovedValueTooLow/);350 )).to.be.rejectedWith(/common\.ApprovedValueTooLow/);351 });351 });352353 itSub('zero transfer NFT', async ({helper}) => {354 const collection = await helper.nft.mintCollection(alice, {name: 'Zero', description: 'Zero transfer', tokenPrefix: 'TF'});355 const notApprovedNft = await collection.mintToken(alice, {Substrate: bob.address});356 const approvedNft = await collection.mintToken(alice, {Substrate: bob.address});357 await approvedNft.approve(bob, {Substrate: alice.address});358359 // 1. Cannot zero transferFrom (non-existing token)360 await expect(helper.executeExtrinsic(alice, 'api.tx.unique.transferFrom', [{Substrate: bob.address}, {Substrate: alice.address}, collection.collectionId, 9999, 0])).to.be.rejectedWith('common.ApprovedValueTooLow');361 // 2. Cannot zero transferFrom (not approved token)362 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');363 // 3. Can zero transferFrom (approved token):364 await helper.executeExtrinsic(alice, 'api.tx.unique.transferFrom', [{Substrate: bob.address}, {Substrate: alice.address}, collection.collectionId, approvedNft.tokenId, 0]);365366 // 4.1 approvedNft still approved:367 expect(await approvedNft.isApproved({Substrate: alice.address})).to.be.true;368 // 4.2 bob is still the owner:369 expect(await approvedNft.getOwner()).to.deep.eq({Substrate: bob.address});370 expect(await notApprovedNft.getOwner()).to.deep.eq({Substrate: bob.address});371 // 4.3 Alice can transfer approved nft:372 await approvedNft.transferFrom(alice, {Substrate: bob.address}, {Substrate: alice.address});373 expect(await approvedNft.getOwner()).to.deep.eq({Substrate: alice.address});374 });352});375});353376