difftreelog
Merge pull request #410 from UniqueNetwork/fix/admin-transfer
in: master
Fix/admin transfer
7 files changed
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -379,11 +379,7 @@
) -> DispatchResult {
let token_data =
<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;
- ensure!(
- &token_data.owner == sender
- || (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(sender)),
- <CommonError<T>>::NoPermission
- );
+ ensure!(&token_data.owner == sender, <CommonError<T>>::NoPermission);
if collection.permissions.access() == AccessMode::AllowList {
collection.check_allowlist(sender)?;
@@ -665,12 +661,7 @@
let token_data =
<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;
- // TODO: require sender to be token, owner, require admins to go through transfer_from
- ensure!(
- &token_data.owner == from
- || (collection.limits.owner_can_transfer() && collection.is_owner_or_admin(from)),
- <CommonError<T>>::NoPermission
- );
+ ensure!(&token_data.owner == from, <CommonError<T>>::NoPermission);
if collection.permissions.access() == AccessMode::AllowList {
collection.check_allowlist(from)?;
@@ -976,8 +967,12 @@
// `from`, `to` checked in [`transfer`]
collection.check_allowlist(spender)?;
}
+
+ if collection.limits.owner_can_transfer() && collection.is_owner_or_admin(spender) {
+ return Ok(());
+ }
+
if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {
- // TODO: should collection owner be allowed to perform this transfer?
ensure!(
<PalletStructure<T>>::check_indirectly_owned(
spender.clone(),
tests/package.jsondiffbeforeafterboth--- a/tests/package.json
+++ b/tests/package.json
@@ -60,6 +60,7 @@
"testAddToContractAllowList": "mocha --timeout 9999999 -r ts-node/register ./**/addToContractAllowList.test.ts",
"testTransfer": "mocha --timeout 9999999 -r ts-node/register ./**/transfer.test.ts",
"testBurnItem": "mocha --timeout 9999999 -r ts-node/register ./**/burnItem.test.ts",
+ "testAdminTransferAndBurn": "mocha --timeout 9999999 -r ts-node/register ./**/adminTransferAndBurn.test.ts",
"testSetMintPermission": "mocha --timeout 9999999 -r ts-node/register ./**/setMintPermission.test.ts",
"testCreditFeesToTreasury": "mocha --timeout 9999999 -r ts-node/register ./**/creditFeesToTreasury.test.ts",
"testContractSponsoring": "mocha --timeout 9999999 -r ts-node/register ./**/contractSponsoring.test.ts",
tests/src/adminTransferAndBurn.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/adminTransferAndBurn.test.ts
@@ -0,0 +1,72 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {IKeyringPair} from '@polkadot/types/types';
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import {default as usingApi} from './substrate/substrate-api';
+import {
+ createCollectionExpectSuccess,
+ createItemExpectSuccess,
+ transferExpectFailure,
+ transferFromExpectSuccess,
+ burnItemExpectFailure,
+ burnFromExpectSuccess,
+ setCollectionLimitsExpectSuccess,
+} from './util/helpers';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+describe('Integration Test: ownerCanTransfer allows admins to use only transferFrom/burnFrom:', () => {
+ let alice: IKeyringPair;
+ let bob: IKeyringPair;
+ let charlie: IKeyringPair;
+
+ before(async () => {
+ await usingApi(async (api, privateKeyWrapper) => {
+ alice = privateKeyWrapper('//Alice');
+ bob = privateKeyWrapper('//Bob');
+ charlie = privateKeyWrapper('//Charlie');
+ });
+ });
+
+ it('admin transfers other user\'s token', async () => {
+ await usingApi(async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionLimitsExpectSuccess(alice, collectionId, {ownerCanTransfer: true});
+
+ const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', {Substrate: bob.address});
+
+ await transferExpectFailure(collectionId, tokenId, alice, charlie);
+
+ await transferFromExpectSuccess(collectionId, tokenId, alice, bob, charlie, 1);
+ });
+ });
+
+ it('admin burns other user\'s token', async () => {
+ await usingApi(async () => {
+ const collectionId = await createCollectionExpectSuccess();
+ await setCollectionLimitsExpectSuccess(alice, collectionId, {ownerCanTransfer: true});
+
+ const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', {Substrate: bob.address});
+
+ await burnItemExpectFailure(alice, collectionId, tokenId);
+
+ await burnFromExpectSuccess(alice, bob, collectionId, tokenId);
+ });
+ });
+});
tests/src/burnItem.test.tsdiffbeforeafterboth--- a/tests/src/burnItem.test.ts
+++ b/tests/src/burnItem.test.ts
@@ -155,7 +155,7 @@
await addCollectionAdminExpectSuccess(alice, collectionId, bob.address);
await usingApi(async (api) => {
- const tx = api.tx.unique.burnItem(collectionId, tokenId, 1);
+ const tx = api.tx.unique.burnFrom(collectionId, {Substrate: alice.address}, tokenId, 1);
const events = await submitTransactionAsync(bob, tx);
const result = getGenericResult(events);
tests/src/nesting/graphs.test.tsdiffbeforeafterboth--- a/tests/src/nesting/graphs.test.ts
+++ b/tests/src/nesting/graphs.test.ts
@@ -36,7 +36,7 @@
await usingApi(async (api, privateKeyWrapper) => {
const alice = privateKeyWrapper('//Alice');
const collection = await buildComplexObjectGraph(api, alice);
- await setCollectionLimitsExpectSuccess(alice, collection, {ownerCanTransfer: true});
+ const tokenTwoParent = tokenIdToCross(collection, 1);
// to self
await expect(
@@ -49,7 +49,7 @@
'second transaction',
).to.be.rejectedWith(/structure\.OuroborosDetected/);
await expect(
- executeTransaction(api, alice, api.tx.unique.transfer(tokenIdToCross(collection, 8), collection, 2, 1)),
+ executeTransaction(api, alice, api.tx.unique.transferFrom(tokenTwoParent, tokenIdToCross(collection, 8), collection, 2, 1)),
'third transaction',
).to.be.rejectedWith(/structure\.OuroborosDetected/);
});
tests/src/nesting/nest.test.tsdiffbeforeafterboth1import {expect} from 'chai';2import {tokenIdToAddress} from '../eth/util/helpers';3import usingApi, {executeTransaction} from '../substrate/substrate-api';4import {5 addCollectionAdminExpectSuccess,6 addToAllowListExpectSuccess,7 createCollectionExpectSuccess,8 createItemExpectSuccess,9 enableAllowListExpectSuccess,10 enablePublicMintingExpectSuccess,11 getTokenChildren,12 getTokenOwner,13 getTopmostTokenOwner,14 normalizeAccountId,15 setCollectionPermissionsExpectSuccess,16 transferExpectFailure,17 transferExpectSuccess,18 transferFromExpectSuccess,19 setCollectionLimitsExpectSuccess,20} from '../util/helpers';21import {IKeyringPair} from '@polkadot/types/types';2223let alice: IKeyringPair;24let bob: IKeyringPair;25let charlie: IKeyringPair;2627describe('Integration Test: Composite nesting tests', () => {28 before(async () => {29 await usingApi(async (_, privateKeyWrapper) => {30 alice = privateKeyWrapper('//Alice');31 bob = privateKeyWrapper('//Bob');32 });33 });3435 it('Performs the full suite: bundles a token, transfers, and unnests', async () => {36 await usingApi(async api => {37 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});38 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});39 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');4041 // Create a nested token42 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});43 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});44 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});4546 // Create a token to be nested47 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');4849 // Nest50 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});51 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});52 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});5354 // Move bundle to different user55 await transferExpectSuccess(collection, targetToken, alice, {Substrate: bob.address});56 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: bob.address});57 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});5859 // Unnest60 await transferFromExpectSuccess(collection, newToken, bob, {Ethereum: tokenIdToAddress(collection, targetToken)}, {Substrate: bob.address});61 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});62 });63 });6465 it('Transfers an already bundled token', async () => {66 await usingApi(async api => {67 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});68 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});6970 const tokenA = await createItemExpectSuccess(alice, collection, 'NFT');71 const tokenB = await createItemExpectSuccess(alice, collection, 'NFT');7273 // Create a nested token74 const tokenC = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, tokenA)});75 expect(await getTopmostTokenOwner(api, collection, tokenC)).to.be.deep.equal({Substrate: alice.address});76 expect(await getTokenOwner(api, collection, tokenC)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, tokenA).toLowerCase()});7778 // Transfer the nested token to another token79 await expect(executeTransaction(80 api,81 alice,82 api.tx.unique.transferFrom(83 normalizeAccountId({Ethereum: tokenIdToAddress(collection, tokenA)}),84 normalizeAccountId({Ethereum: tokenIdToAddress(collection, tokenB)}),85 collection,86 tokenC,87 1,88 ),89 )).to.not.be.rejected;90 expect(await getTopmostTokenOwner(api, collection, tokenC)).to.be.deep.equal({Substrate: alice.address});91 expect(await getTokenOwner(api, collection, tokenC)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, tokenB).toLowerCase()});92 });93 });9495 it('Checks token children', async () => {96 await usingApi(async api => {97 const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}});98 await setCollectionLimitsExpectSuccess(alice, collectionA, {ownerCanTransfer: true});99 await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: {tokenOwner: true}});100 const collectionB = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});101102 const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT');103 const targetAddress = {Ethereum: tokenIdToAddress(collectionA, targetToken)};104 let children = await getTokenChildren(api, collectionA, targetToken);105 expect(children.length).to.be.equal(0, 'Children length check at creation');106107 // Create a nested NFT token108 const tokenA = await createItemExpectSuccess(alice, collectionA, 'NFT', targetAddress);109 children = await getTokenChildren(api, collectionA, targetToken);110 expect(children.length).to.be.equal(1, 'Children length check at nesting #1');111 expect(children).to.have.deep.members([112 {token: tokenA, collection: collectionA},113 ], 'Children contents check at nesting #1');114115 // Create then nest116 const tokenB = await createItemExpectSuccess(alice, collectionA, 'NFT');117 await transferExpectSuccess(collectionA, tokenB, alice, targetAddress);118 children = await getTokenChildren(api, collectionA, targetToken);119 expect(children.length).to.be.equal(2, 'Children length check at nesting #2');120 expect(children).to.have.deep.members([121 {token: tokenA, collection: collectionA},122 {token: tokenB, collection: collectionA},123 ], 'Children contents check at nesting #2');124125 // Move token B to a different user outside the nesting tree126 await transferExpectSuccess(collectionA, tokenB, alice, bob);127 children = await getTokenChildren(api, collectionA, targetToken);128 expect(children.length).to.be.equal(1, 'Children length check at unnesting');129 expect(children).to.be.have.deep.members([130 {token: tokenA, collection: collectionA},131 ], 'Children contents check at unnesting');132133 // Create a fungible token in another collection and then nest134 const tokenC = await createItemExpectSuccess(alice, collectionB, 'Fungible');135 await transferExpectSuccess(collectionB, tokenC, alice, targetAddress, 1, 'Fungible');136 children = await getTokenChildren(api, collectionA, targetToken);137 expect(children.length).to.be.equal(2, 'Children length check at nesting #3 (from another collection)');138 expect(children).to.be.have.deep.members([139 {token: tokenA, collection: collectionA},140 {token: tokenC, collection: collectionB},141 ], 'Children contents check at nesting #3 (from another collection)');142143 // Move the fungible token inside token A deeper in the nesting tree144 await transferFromExpectSuccess(collectionB, tokenC, alice, targetAddress, {Ethereum: tokenIdToAddress(collectionA, tokenA)}, 1, 'Fungible');145 children = await getTokenChildren(api, collectionA, targetToken);146 expect(children.length).to.be.equal(1, 'Children length check at deeper nesting');147 expect(children).to.be.have.deep.members([148 {token: tokenA, collection: collectionA},149 ], 'Children contents check at deeper nesting');150 });151 });152});153154describe('Integration Test: Various token type nesting', async () => {155 before(async () => {156 await usingApi(async (_, privateKeyWrapper) => {157 alice = privateKeyWrapper('//Alice');158 bob = privateKeyWrapper('//Bob');159 charlie = privateKeyWrapper('//Charlie');160 });161 });162163 it('Admin (NFT): allows an Admin to nest a token', async () => {164 await usingApi(async api => {165 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});166 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {collectionAdmin: true}});167 await addCollectionAdminExpectSuccess(alice, collection, bob.address);168 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT', charlie.address);169170 // Create a nested token171 const nestedToken = await createItemExpectSuccess(bob, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});172 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: charlie.address});173 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});174175 // Create a token to be nested and nest176 const newToken = await createItemExpectSuccess(bob, collection, 'NFT');177 await transferExpectSuccess(collection, newToken, bob, {Ethereum: tokenIdToAddress(collection, targetToken)});178 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: charlie.address});179 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});180 });181 });182183 it('Admin (NFT): Admin and Token Owner can operate together', async () => {184 await usingApi(async api => {185 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});186 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, collectionAdmin: true}});187 await addCollectionAdminExpectSuccess(alice, collection, bob.address);188 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT', charlie.address);189190 // Create a nested token by an administrator191 const nestedToken = await createItemExpectSuccess(bob, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});192 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: charlie.address});193 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});194195 // Create a token and allow the owner to nest too196 const newToken = await createItemExpectSuccess(alice, collection, 'NFT', charlie.address);197 await transferExpectSuccess(collection, newToken, charlie, {Ethereum: tokenIdToAddress(collection, nestedToken)});198 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: charlie.address});199 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, nestedToken).toLowerCase()});200 });201 });202203 it('Admin (NFT): allows an Admin to nest a token (Restricted nesting)', async () => {204 await usingApi(async api => {205 const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}});206 await addCollectionAdminExpectSuccess(alice, collectionA, bob.address);207 const collectionB = await createCollectionExpectSuccess({mode: {type: 'NFT'}});208 await addCollectionAdminExpectSuccess(alice, collectionB, bob.address);209 await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: {collectionAdmin: true, restricted:[collectionA, collectionB]}});210 const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT', charlie.address);211212 // Create a nested token213 const nestedToken = await createItemExpectSuccess(bob, collectionB, 'NFT', {Ethereum: tokenIdToAddress(collectionA, targetToken)});214 expect(await getTopmostTokenOwner(api, collectionB, nestedToken)).to.be.deep.equal({Substrate: charlie.address});215 expect(await getTokenOwner(api, collectionB, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collectionA, targetToken).toLowerCase()});216217 // Create a token to be nested and nest218 const newToken = await createItemExpectSuccess(bob, collectionB, 'NFT');219 await transferExpectSuccess(collectionB, newToken, bob, {Ethereum: tokenIdToAddress(collectionA, targetToken)});220 expect(await getTopmostTokenOwner(api, collectionB, newToken)).to.be.deep.equal({Substrate: charlie.address});221 expect(await getTokenOwner(api, collectionB, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collectionA, targetToken).toLowerCase()});222 });223 });224225 // ---------- Non-Fungible ----------226227 it('NFT: allows an Owner to nest/unnest their token', async () => {228 await usingApi(async api => {229 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});230 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});231 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');232233 // Create a nested token234 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});235 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});236 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});237238 // Create a token to be nested and nest239 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');240 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});241 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});242 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});243 });244 });245246 it('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async () => {247 await usingApi(async api => {248 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});249 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, restricted:[collection]}});250 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');251252 // Create a nested token253 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});254 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});255 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});256257 // Create a token to be nested and nest258 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');259 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});260 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});261 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});262 });263 });264265 // ---------- Fungible ----------266267 it('Fungible: allows an Owner to nest/unnest their token', async () => {268 await usingApi(async api => {269 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});270 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});271 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});272 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};273274 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});275276 // Create a nested token277 await expect(executeTransaction(api, alice, api.tx.unique.createItem(278 collectionFT,279 targetAddress,280 {Fungible: {Value: 10}},281 ))).to.not.be.rejected;282283 // Nest a new token284 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');285 await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');286 });287 });288289 it('Fungible: allows an Owner to nest/unnest their token (Restricted nesting)', async () => {290 await usingApi(async api => {291 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});292 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});293 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};294295 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});296297 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted: [collectionFT]}});298299 // Create a nested token300 await expect(executeTransaction(api, alice, api.tx.unique.createItem(301 collectionFT,302 targetAddress,303 {Fungible: {Value: 10}},304 ))).to.not.be.rejected;305306 // Nest a new token307 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');308 await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');309 });310 });311312 // ---------- Re-Fungible ----------313314 it('ReFungible: allows an Owner to nest/unnest their token', async () => {315 await usingApi(async api => {316 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});317 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});318 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});319 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};320321 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});322323 // Create a nested token324 await expect(executeTransaction(api, alice, api.tx.unique.createItem(325 collectionRFT,326 targetAddress,327 {ReFungible: {pieces: 100}},328 ))).to.not.be.rejected;329330 // Nest a new token331 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');332 await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');333 });334 });335336 it('ReFungible: allows an Owner to nest/unnest their token (Restricted nesting)', async () => {337 await usingApi(async api => {338 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});339 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});340 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};341342 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});343344 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[collectionRFT]}});345346 // Create a nested token347 await expect(executeTransaction(api, alice, api.tx.unique.createItem(348 collectionRFT,349 targetAddress,350 {ReFungible: {pieces: 100}},351 ))).to.not.be.rejected;352353 // Nest a new token354 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');355 await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');356 });357 });358});359360describe('Negative Test: Nesting', async() => {361 before(async () => {362 await usingApi(async (_, privateKeyWrapper) => {363 alice = privateKeyWrapper('//Alice');364 bob = privateKeyWrapper('//Bob');365 });366 });367368 it('Disallows excessive token nesting', async () => {369 await usingApi(async api => {370 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});371 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});372 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');373374 const maxNestingLevel = 5;375 let prevToken = targetToken;376377 // Create a nested-token matryoshka378 for (let i = 0; i < maxNestingLevel; i++) {379 const nestedToken = await createItemExpectSuccess(380 alice,381 collection,382 'NFT',383 {Ethereum: tokenIdToAddress(collection, prevToken)},384 );385386 prevToken = nestedToken;387 }388389 // The nesting depth is limited by `maxNestingLevel`390 await expect(executeTransaction(api, alice, api.tx.unique.createItem(391 collection,392 {Ethereum: tokenIdToAddress(collection, prevToken)},393 {nft: {}} as any,394 )), 'while creating nested token').to.be.rejectedWith(/^structure\.DepthLimit$/);395396 expect(await getTopmostTokenOwner(api, collection, prevToken)).to.be.deep.equal({Substrate: alice.address});397 });398 });399400 // ---------- Admin ------------401402 it('Admin (NFT): disallows an Admin to operate nesting when only TokenOwner is allowed', async () => {403 await usingApi(async api => {404 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});405 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});406 await addCollectionAdminExpectSuccess(alice, collection, bob.address);407 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');408409 // Try to create a nested token as collection admin when it's disallowed410 await expect(executeTransaction(api, bob, api.tx.unique.createItem(411 collection,412 {Ethereum: tokenIdToAddress(collection, targetToken)},413 {nft: {}} as any,414 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);415416 // Try to create and nest a token in the wrong collection417 const newToken = await createItemExpectSuccess(bob, collection, 'NFT');418 await expect(executeTransaction(419 api, 420 bob, 421 api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1),422 ), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);423 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});424 });425 });426427 it('Admin (NFT): disallows a Token Owner to operate nesting when only Admin is allowed', async () => {428 await usingApi(async api => {429 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});430 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {collectionAdmin: true}});431 await addToAllowListExpectSuccess(alice, collection, bob.address);432 await enableAllowListExpectSuccess(alice, collection);433 await enablePublicMintingExpectSuccess(alice, collection);434 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');435436 // Try to create a nested token as collection admin when it's disallowed437 await expect(executeTransaction(api, bob, api.tx.unique.createItem(438 collection,439 {Ethereum: tokenIdToAddress(collection, targetToken)},440 {nft: {}} as any,441 )), 'while creating nested token').to.be.rejectedWith(/common\.AddressNotInAllowlist/); 442443 // Try to create and nest a token in the wrong collection444 const newToken = await createItemExpectSuccess(bob, collection, 'NFT');445 await expect(executeTransaction(446 api, 447 bob, 448 api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1),449 ), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);450 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});451 });452 });453454 it('Admin (NFT): disallows an Admin to nest and unnest someone else\'s token', async () => {455 await usingApi(async api => {456 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});457 await setCollectionLimitsExpectSuccess(alice, collection, {ownerCanTransfer: true});458 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {collectionAdmin: true}});459460 await addToAllowListExpectSuccess(alice, collection, bob.address);461 await enableAllowListExpectSuccess(alice, collection);462 await enablePublicMintingExpectSuccess(alice, collection);463464 // Create a token to attempt to be nested into465 const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');466 const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()};467468 // Try to nest somebody else's token469 const newToken = await createItemExpectSuccess(bob, collection, 'NFT');470 await expect(executeTransaction(471 api, 472 alice, 473 api.tx.unique.transfer(targetAddress, collection, newToken, 1),474 ), 'while nesting another\'s token token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);475 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});476477 // Nest a token as admin and try to unnest it, now belonging to someone else478 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', targetAddress);479 await expect(executeTransaction(480 api, 481 alice, 482 api.tx.unique.transferFrom(targetAddress, normalizeAccountId(alice), collection, nestedToken, 1),483 ), 'while unnesting another\'s token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);484 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal(targetAddress);485 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: bob.address});486 });487 });488489 it('Admin (NFT): disallows an Admin to nest a token from an unlisted collection (Restricted nesting)', async () => {490 await usingApi(async api => {491 const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}});492 const collectionB = await createCollectionExpectSuccess({mode: {type: 'NFT'}});493 await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: {collectionAdmin: true, restricted:[collectionA]}});494495 // Create a token to attempt to be nested into496 const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT');497498 // Try to create and nest a token in the wrong collection499 const newToken = await createItemExpectSuccess(alice, collectionB, 'NFT');500 await expect(executeTransaction(501 api, 502 alice, 503 api.tx.unique.transfer({Ethereum: tokenIdToAddress(collectionA, targetToken)}, collectionB, newToken, 1),504 ), 'while nesting a foreign token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);505 expect(await getTokenOwner(api, collectionB, newToken)).to.be.deep.equal({Substrate: alice.address});506 });507 });508509 // ---------- Non-Fungible ----------510511 it('NFT: disallows to nest token if nesting is disabled', async () => {512 await usingApi(async api => {513 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});514 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {}});515 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');516517 // Try to create a nested token518 await expect(executeTransaction(api, alice, api.tx.unique.createItem(519 collection,520 {Ethereum: tokenIdToAddress(collection, targetToken)},521 {nft: {}} as any,522 )), 'while creating nested token').to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/);523524 // Create a token to be nested525 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');526 // Try to nest527 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);528 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});529 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});530 });531 });532533 it('NFT: disallows a non-Owner to nest someone else\'s token', async () => {534 await usingApi(async api => {535 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});536 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});537538 await addToAllowListExpectSuccess(alice, collection, bob.address);539 await enableAllowListExpectSuccess(alice, collection);540 await enablePublicMintingExpectSuccess(alice, collection);541542 // Create a token to attempt to be nested into543 const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');544545 // Try to create a nested token in the wrong collection546 await expect(executeTransaction(api, alice, api.tx.unique.createItem(547 collection,548 {Ethereum: tokenIdToAddress(collection, targetToken)},549 {nft: {}} as any,550 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);551552 // Try to create and nest a token in the wrong collection553 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');554 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);555 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});556 });557 });558559 it('NFT: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => {560 await usingApi(async api => {561 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});562 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, restricted:[collection]}});563564 await addToAllowListExpectSuccess(alice, collection, bob.address);565 await enableAllowListExpectSuccess(alice, collection);566 await enablePublicMintingExpectSuccess(alice, collection);567568 // Create a token to attempt to be nested into569 const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');570571 // Try to create a nested token in the wrong collection572 await expect(executeTransaction(api, alice, api.tx.unique.createItem(573 collection,574 {Ethereum: tokenIdToAddress(collection, targetToken)},575 {nft: {}} as any,576 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);577578 // Try to create and nest a token in the wrong collection579 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');580 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);581 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});582 });583 });584585 it('NFT: disallows to nest token in an unlisted collection', async () => {586 await usingApi(async api => {587 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});588 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, restricted:[]}});589590 // Create a token to attempt to be nested into591 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');592593 // Try to create a nested token in the wrong collection594 await expect(executeTransaction(api, alice, api.tx.unique.createItem(595 collection,596 {Ethereum: tokenIdToAddress(collection, targetToken)},597 {nft: {}} as any,598 )), 'while creating nested token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);599600 // Try to create and nest a token in the wrong collection601 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');602 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);603 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});604 });605 });606607 // ---------- Fungible ----------608609 it('Fungible: disallows to nest token if nesting is disabled', async () => {610 await usingApi(async api => {611 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});612 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {}});613 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');614 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};615616 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});617618 // Try to create a nested token619 await expect(executeTransaction(api, alice, api.tx.unique.createItem(620 collectionFT,621 targetAddress,622 {Fungible: {Value: 10}},623 )), 'while creating nested token').to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/);624625 // Create a token to be nested626 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');627 // Try to nest628 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);629630 // Create another token to be nested631 const newToken2 = await createItemExpectSuccess(alice, collectionFT, 'Fungible');632 // Try to nest inside a fungible token633 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collectionFT, newToken)}, collectionFT, newToken2, 1)), 'while nesting new token inside fungible').to.be.rejectedWith(/fungible\.FungibleDisallowsNesting/);634 });635 });636637 it('Fungible: disallows a non-Owner to nest someone else\'s token', async () => {638 await usingApi(async api => {639 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});640 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});641642 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);643 await enableAllowListExpectSuccess(alice, collectionNFT);644 await enablePublicMintingExpectSuccess(alice, collectionNFT);645646 // Create a token to attempt to be nested into647 const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');648 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};649650 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});651652 // Try to create a nested token in the wrong collection653 await expect(executeTransaction(api, alice, api.tx.unique.createItem(654 collectionFT,655 targetAddress,656 {Fungible: {Value: 10}},657 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);658659 // Try to create and nest a token in the wrong collection660 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');661 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);662 });663 });664665 it('Fungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => {666 await usingApi(async api => {667 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});668 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);669 await enableAllowListExpectSuccess(alice, collectionNFT);670 await enablePublicMintingExpectSuccess(alice, collectionNFT);671672 // Create a token to attempt to be nested into673 const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');674 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};675676 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});677 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[collectionFT]}});678679 // Try to create a nested token in the wrong collection680 await expect(executeTransaction(api, alice, api.tx.unique.createItem(681 collectionFT,682 targetAddress,683 {Fungible: {Value: 10}},684 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);685686 // Try to create and nest a token in the wrong collection687 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');688 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);689 });690 });691692 it('Fungible: disallows to nest token in an unlisted collection', async () => {693 await usingApi(async api => {694 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});695 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[]}});696697 // Create a token to attempt to be nested into698 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');699 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};700701 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});702703 // Try to create a nested token in the wrong collection704 await expect(executeTransaction(api, alice, api.tx.unique.createItem(705 collectionFT,706 targetAddress,707 {Fungible: {Value: 10}},708 )), 'while creating a nested token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);709710 // Try to create and nest a token in the wrong collection711 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');712 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);713 });714 });715716 // ---------- Re-Fungible ----------717718 it('ReFungible: disallows to nest token if nesting is disabled', async () => {719 await usingApi(async api => {720 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});721 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {}});722 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');723 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};724725 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});726727 // Create a nested token728 await expect(executeTransaction(api, alice, api.tx.unique.createItem(729 collectionRFT,730 targetAddress,731 {ReFungible: {pieces: 100}},732 )), 'while creating a nested token').to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/);733734 // Create a token to be nested735 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');736 // Try to nest737 await transferExpectFailure(collectionRFT, newToken, alice, targetAddress, 100);738 // Try to nest739 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);740741 // Create another token to be nested742 const newToken2 = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');743 // Try to nest inside a fungible token744 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collectionRFT, newToken)}, collectionRFT, newToken2, 1)), 'while nesting new token inside refungible').to.be.rejectedWith(/refungible\.RefungibleDisallowsNesting/);745 });746 });747748 it('ReFungible: disallows a non-Owner to nest someone else\'s token', async () => {749 await usingApi(async api => {750 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});751 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});752753 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);754 await enableAllowListExpectSuccess(alice, collectionNFT);755 await enablePublicMintingExpectSuccess(alice, collectionNFT);756757 // Create a token to attempt to be nested into758 const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');759 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};760761 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});762763 // Try to create a nested token in the wrong collection764 await expect(executeTransaction(api, alice, api.tx.unique.createItem(765 collectionRFT,766 targetAddress,767 {ReFungible: {pieces: 100}},768 )), 'while creating a nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);769770 // Try to create and nest a token in the wrong collection771 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');772 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);773 });774 });775776 it('ReFungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => {777 await usingApi(async api => {778 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});779 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);780 await enableAllowListExpectSuccess(alice, collectionNFT);781 await enablePublicMintingExpectSuccess(alice, collectionNFT);782783 // Create a token to attempt to be nested into784 const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');785 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};786787 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});788 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[collectionRFT]}});789790 // Try to create a nested token in the wrong collection791 await expect(executeTransaction(api, alice, api.tx.unique.createItem(792 collectionRFT,793 targetAddress,794 {ReFungible: {pieces: 100}},795 )), 'while creating a nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);796797 // Try to create and nest a token in the wrong collection798 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');799 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);800 });801 });802803 it('ReFungible: disallows to nest token to an unlisted collection', async () => {804 await usingApi(async api => {805 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});806 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[]}});807808 // Create a token to attempt to be nested into809 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');810 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};811812 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});813814 // Try to create a nested token in the wrong collection815 await expect(executeTransaction(api, alice, api.tx.unique.createItem(816 collectionRFT,817 targetAddress,818 {ReFungible: {pieces: 100}},819 )), 'while creating a nested token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);820821 // Try to create and nest a token in the wrong collection822 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');823 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);824 });825 });826});1import {expect} from 'chai';2import {tokenIdToAddress} from '../eth/util/helpers';3import usingApi, {executeTransaction} from '../substrate/substrate-api';4import {5 addCollectionAdminExpectSuccess,6 addToAllowListExpectSuccess,7 createCollectionExpectSuccess,8 createItemExpectSuccess,9 enableAllowListExpectSuccess,10 enablePublicMintingExpectSuccess,11 getTokenChildren,12 getTokenOwner,13 getTopmostTokenOwner,14 normalizeAccountId,15 setCollectionPermissionsExpectSuccess,16 transferExpectFailure,17 transferExpectSuccess,18 transferFromExpectSuccess,19 setCollectionLimitsExpectSuccess,20} from '../util/helpers';21import {IKeyringPair} from '@polkadot/types/types';2223let alice: IKeyringPair;24let bob: IKeyringPair;25let charlie: IKeyringPair;2627describe('Integration Test: Composite nesting tests', () => {28 before(async () => {29 await usingApi(async (_, privateKeyWrapper) => {30 alice = privateKeyWrapper('//Alice');31 bob = privateKeyWrapper('//Bob');32 });33 });3435 it('Performs the full suite: bundles a token, transfers, and unnests', async () => {36 await usingApi(async api => {37 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});38 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});39 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');4041 // Create a nested token42 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});43 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});44 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});4546 // Create a token to be nested47 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');4849 // Nest50 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});51 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});52 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});5354 // Move bundle to different user55 await transferExpectSuccess(collection, targetToken, alice, {Substrate: bob.address});56 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: bob.address});57 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});5859 // Unnest60 await transferFromExpectSuccess(collection, newToken, bob, {Ethereum: tokenIdToAddress(collection, targetToken)}, {Substrate: bob.address});61 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});62 });63 });6465 it('Transfers an already bundled token', async () => {66 await usingApi(async api => {67 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});68 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});6970 const tokenA = await createItemExpectSuccess(alice, collection, 'NFT');71 const tokenB = await createItemExpectSuccess(alice, collection, 'NFT');7273 // Create a nested token74 const tokenC = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, tokenA)});75 expect(await getTopmostTokenOwner(api, collection, tokenC)).to.be.deep.equal({Substrate: alice.address});76 expect(await getTokenOwner(api, collection, tokenC)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, tokenA).toLowerCase()});7778 // Transfer the nested token to another token79 await expect(executeTransaction(80 api,81 alice,82 api.tx.unique.transferFrom(83 normalizeAccountId({Ethereum: tokenIdToAddress(collection, tokenA)}),84 normalizeAccountId({Ethereum: tokenIdToAddress(collection, tokenB)}),85 collection,86 tokenC,87 1,88 ),89 )).to.not.be.rejected;90 expect(await getTopmostTokenOwner(api, collection, tokenC)).to.be.deep.equal({Substrate: alice.address});91 expect(await getTokenOwner(api, collection, tokenC)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, tokenB).toLowerCase()});92 });93 });9495 it('Checks token children', async () => {96 await usingApi(async api => {97 const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}});98 await setCollectionLimitsExpectSuccess(alice, collectionA, {ownerCanTransfer: true});99 await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: {tokenOwner: true}});100 const collectionB = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});101102 const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT');103 const targetAddress = {Ethereum: tokenIdToAddress(collectionA, targetToken)};104 let children = await getTokenChildren(api, collectionA, targetToken);105 expect(children.length).to.be.equal(0, 'Children length check at creation');106107 // Create a nested NFT token108 const tokenA = await createItemExpectSuccess(alice, collectionA, 'NFT', targetAddress);109 children = await getTokenChildren(api, collectionA, targetToken);110 expect(children.length).to.be.equal(1, 'Children length check at nesting #1');111 expect(children).to.have.deep.members([112 {token: tokenA, collection: collectionA},113 ], 'Children contents check at nesting #1');114115 // Create then nest116 const tokenB = await createItemExpectSuccess(alice, collectionA, 'NFT');117 await transferExpectSuccess(collectionA, tokenB, alice, targetAddress);118 children = await getTokenChildren(api, collectionA, targetToken);119 expect(children.length).to.be.equal(2, 'Children length check at nesting #2');120 expect(children).to.have.deep.members([121 {token: tokenA, collection: collectionA},122 {token: tokenB, collection: collectionA},123 ], 'Children contents check at nesting #2');124125 // Move token B to a different user outside the nesting tree126 await transferFromExpectSuccess(collectionA, tokenB, alice, targetAddress, bob);127 children = await getTokenChildren(api, collectionA, targetToken);128 expect(children.length).to.be.equal(1, 'Children length check at unnesting');129 expect(children).to.be.have.deep.members([130 {token: tokenA, collection: collectionA},131 ], 'Children contents check at unnesting');132133 // Create a fungible token in another collection and then nest134 const tokenC = await createItemExpectSuccess(alice, collectionB, 'Fungible');135 await transferExpectSuccess(collectionB, tokenC, alice, targetAddress, 1, 'Fungible');136 children = await getTokenChildren(api, collectionA, targetToken);137 expect(children.length).to.be.equal(2, 'Children length check at nesting #3 (from another collection)');138 expect(children).to.be.have.deep.members([139 {token: tokenA, collection: collectionA},140 {token: tokenC, collection: collectionB},141 ], 'Children contents check at nesting #3 (from another collection)');142143 // Move the fungible token inside token A deeper in the nesting tree144 await transferFromExpectSuccess(collectionB, tokenC, alice, targetAddress, {Ethereum: tokenIdToAddress(collectionA, tokenA)}, 1, 'Fungible');145 children = await getTokenChildren(api, collectionA, targetToken);146 expect(children.length).to.be.equal(1, 'Children length check at deeper nesting');147 expect(children).to.be.have.deep.members([148 {token: tokenA, collection: collectionA},149 ], 'Children contents check at deeper nesting');150 });151 });152});153154describe('Integration Test: Various token type nesting', async () => {155 before(async () => {156 await usingApi(async (_, privateKeyWrapper) => {157 alice = privateKeyWrapper('//Alice');158 bob = privateKeyWrapper('//Bob');159 charlie = privateKeyWrapper('//Charlie');160 });161 });162163 it('Admin (NFT): allows an Admin to nest a token', async () => {164 await usingApi(async api => {165 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});166 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {collectionAdmin: true}});167 await addCollectionAdminExpectSuccess(alice, collection, bob.address);168 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT', charlie.address);169170 // Create a nested token171 const nestedToken = await createItemExpectSuccess(bob, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});172 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: charlie.address});173 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});174175 // Create a token to be nested and nest176 const newToken = await createItemExpectSuccess(bob, collection, 'NFT');177 await transferExpectSuccess(collection, newToken, bob, {Ethereum: tokenIdToAddress(collection, targetToken)});178 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: charlie.address});179 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});180 });181 });182183 it('Admin (NFT): Admin and Token Owner can operate together', async () => {184 await usingApi(async api => {185 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});186 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, collectionAdmin: true}});187 await addCollectionAdminExpectSuccess(alice, collection, bob.address);188 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT', charlie.address);189190 // Create a nested token by an administrator191 const nestedToken = await createItemExpectSuccess(bob, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});192 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: charlie.address});193 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});194195 // Create a token and allow the owner to nest too196 const newToken = await createItemExpectSuccess(alice, collection, 'NFT', charlie.address);197 await transferExpectSuccess(collection, newToken, charlie, {Ethereum: tokenIdToAddress(collection, nestedToken)});198 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: charlie.address});199 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, nestedToken).toLowerCase()});200 });201 });202203 it('Admin (NFT): allows an Admin to nest a token (Restricted nesting)', async () => {204 await usingApi(async api => {205 const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}});206 await addCollectionAdminExpectSuccess(alice, collectionA, bob.address);207 const collectionB = await createCollectionExpectSuccess({mode: {type: 'NFT'}});208 await addCollectionAdminExpectSuccess(alice, collectionB, bob.address);209 await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: {collectionAdmin: true, restricted:[collectionA, collectionB]}});210 const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT', charlie.address);211212 // Create a nested token213 const nestedToken = await createItemExpectSuccess(bob, collectionB, 'NFT', {Ethereum: tokenIdToAddress(collectionA, targetToken)});214 expect(await getTopmostTokenOwner(api, collectionB, nestedToken)).to.be.deep.equal({Substrate: charlie.address});215 expect(await getTokenOwner(api, collectionB, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collectionA, targetToken).toLowerCase()});216217 // Create a token to be nested and nest218 const newToken = await createItemExpectSuccess(bob, collectionB, 'NFT');219 await transferExpectSuccess(collectionB, newToken, bob, {Ethereum: tokenIdToAddress(collectionA, targetToken)});220 expect(await getTopmostTokenOwner(api, collectionB, newToken)).to.be.deep.equal({Substrate: charlie.address});221 expect(await getTokenOwner(api, collectionB, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collectionA, targetToken).toLowerCase()});222 });223 });224225 // ---------- Non-Fungible ----------226227 it('NFT: allows an Owner to nest/unnest their token', async () => {228 await usingApi(async api => {229 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});230 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});231 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');232233 // Create a nested token234 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});235 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});236 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});237238 // Create a token to be nested and nest239 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');240 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});241 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});242 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});243 });244 });245246 it('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async () => {247 await usingApi(async api => {248 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});249 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, restricted:[collection]}});250 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');251252 // Create a nested token253 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)});254 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address});255 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});256257 // Create a token to be nested and nest258 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');259 await transferExpectSuccess(collection, newToken, alice, {Ethereum: tokenIdToAddress(collection, targetToken)});260 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});261 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()});262 });263 });264265 // ---------- Fungible ----------266267 it('Fungible: allows an Owner to nest/unnest their token', async () => {268 await usingApi(async api => {269 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});270 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});271 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});272 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};273274 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});275276 // Create a nested token277 await expect(executeTransaction(api, alice, api.tx.unique.createItem(278 collectionFT,279 targetAddress,280 {Fungible: {Value: 10}},281 ))).to.not.be.rejected;282283 // Nest a new token284 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');285 await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');286 });287 });288289 it('Fungible: allows an Owner to nest/unnest their token (Restricted nesting)', async () => {290 await usingApi(async api => {291 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});292 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});293 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};294295 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});296297 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted: [collectionFT]}});298299 // Create a nested token300 await expect(executeTransaction(api, alice, api.tx.unique.createItem(301 collectionFT,302 targetAddress,303 {Fungible: {Value: 10}},304 ))).to.not.be.rejected;305306 // Nest a new token307 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');308 await transferExpectSuccess(collectionFT, newToken, alice, targetAddress, 1, 'Fungible');309 });310 });311312 // ---------- Re-Fungible ----------313314 it('ReFungible: allows an Owner to nest/unnest their token', async () => {315 await usingApi(async api => {316 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});317 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});318 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});319 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};320321 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});322323 // Create a nested token324 await expect(executeTransaction(api, alice, api.tx.unique.createItem(325 collectionRFT,326 targetAddress,327 {ReFungible: {pieces: 100}},328 ))).to.not.be.rejected;329330 // Nest a new token331 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');332 await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');333 });334 });335336 it('ReFungible: allows an Owner to nest/unnest their token (Restricted nesting)', async () => {337 await usingApi(async api => {338 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});339 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT', {Substrate: alice.address});340 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};341342 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});343344 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[collectionRFT]}});345346 // Create a nested token347 await expect(executeTransaction(api, alice, api.tx.unique.createItem(348 collectionRFT,349 targetAddress,350 {ReFungible: {pieces: 100}},351 ))).to.not.be.rejected;352353 // Nest a new token354 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');355 await transferExpectSuccess(collectionRFT, newToken, alice, targetAddress, 100, 'ReFungible');356 });357 });358});359360describe('Negative Test: Nesting', async() => {361 before(async () => {362 await usingApi(async (_, privateKeyWrapper) => {363 alice = privateKeyWrapper('//Alice');364 bob = privateKeyWrapper('//Bob');365 });366 });367368 it('Disallows excessive token nesting', async () => {369 await usingApi(async api => {370 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});371 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});372 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');373374 const maxNestingLevel = 5;375 let prevToken = targetToken;376377 // Create a nested-token matryoshka378 for (let i = 0; i < maxNestingLevel; i++) {379 const nestedToken = await createItemExpectSuccess(380 alice,381 collection,382 'NFT',383 {Ethereum: tokenIdToAddress(collection, prevToken)},384 );385386 prevToken = nestedToken;387 }388389 // The nesting depth is limited by `maxNestingLevel`390 await expect(executeTransaction(api, alice, api.tx.unique.createItem(391 collection,392 {Ethereum: tokenIdToAddress(collection, prevToken)},393 {nft: {}} as any,394 )), 'while creating nested token').to.be.rejectedWith(/^structure\.DepthLimit$/);395396 expect(await getTopmostTokenOwner(api, collection, prevToken)).to.be.deep.equal({Substrate: alice.address});397 });398 });399400 // ---------- Admin ------------401402 it('Admin (NFT): disallows an Admin to operate nesting when only TokenOwner is allowed', async () => {403 await usingApi(async api => {404 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});405 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});406 await addCollectionAdminExpectSuccess(alice, collection, bob.address);407 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');408409 // Try to create a nested token as collection admin when it's disallowed410 await expect(executeTransaction(api, bob, api.tx.unique.createItem(411 collection,412 {Ethereum: tokenIdToAddress(collection, targetToken)},413 {nft: {}} as any,414 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);415416 // Try to create and nest a token in the wrong collection417 const newToken = await createItemExpectSuccess(bob, collection, 'NFT');418 await expect(executeTransaction(419 api, 420 bob, 421 api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1),422 ), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);423 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});424 });425 });426427 it('Admin (NFT): disallows a Token Owner to operate nesting when only Admin is allowed', async () => {428 await usingApi(async api => {429 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});430 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {collectionAdmin: true}});431 await addToAllowListExpectSuccess(alice, collection, bob.address);432 await enableAllowListExpectSuccess(alice, collection);433 await enablePublicMintingExpectSuccess(alice, collection);434 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');435436 // Try to create a nested token as collection admin when it's disallowed437 await expect(executeTransaction(api, bob, api.tx.unique.createItem(438 collection,439 {Ethereum: tokenIdToAddress(collection, targetToken)},440 {nft: {}} as any,441 )), 'while creating nested token').to.be.rejectedWith(/common\.AddressNotInAllowlist/); 442443 // Try to create and nest a token in the wrong collection444 const newToken = await createItemExpectSuccess(bob, collection, 'NFT');445 await expect(executeTransaction(446 api, 447 bob, 448 api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1),449 ), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);450 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});451 });452 });453454 it('Admin (NFT): disallows an Admin to nest and unnest someone else\'s token', async () => {455 await usingApi(async api => {456 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});457 await setCollectionLimitsExpectSuccess(alice, collection, {ownerCanTransfer: true});458 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {collectionAdmin: true}});459460 await addToAllowListExpectSuccess(alice, collection, bob.address);461 await enableAllowListExpectSuccess(alice, collection);462 await enablePublicMintingExpectSuccess(alice, collection);463464 // Create a token to attempt to be nested into465 const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');466 const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()};467468 // Try to nest somebody else's token469 const newToken = await createItemExpectSuccess(bob, collection, 'NFT');470 await expect(executeTransaction(471 api, 472 alice, 473 api.tx.unique.transferFrom(targetAddress, {Substrate: bob.address}, collection, newToken, 1),474 ), 'while nesting another\'s token token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);475 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address});476477 // Nest a token as admin and try to unnest it, now belonging to someone else478 const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', targetAddress);479 await expect(executeTransaction(480 api, 481 alice, 482 api.tx.unique.transferFrom(targetAddress, normalizeAccountId(alice), collection, nestedToken, 1),483 ), 'while unnesting another\'s token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);484 expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal(targetAddress);485 expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: bob.address});486 });487 });488489 it('Admin (NFT): disallows an Admin to nest a token from an unlisted collection (Restricted nesting)', async () => {490 await usingApi(async api => {491 const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}});492 const collectionB = await createCollectionExpectSuccess({mode: {type: 'NFT'}});493 await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: {collectionAdmin: true, restricted:[collectionA]}});494495 // Create a token to attempt to be nested into496 const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT');497498 // Try to create and nest a token in the wrong collection499 const newToken = await createItemExpectSuccess(alice, collectionB, 'NFT');500 await expect(executeTransaction(501 api, 502 alice, 503 api.tx.unique.transfer({Ethereum: tokenIdToAddress(collectionA, targetToken)}, collectionB, newToken, 1),504 ), 'while nesting a foreign token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);505 expect(await getTokenOwner(api, collectionB, newToken)).to.be.deep.equal({Substrate: alice.address});506 });507 });508509 // ---------- Non-Fungible ----------510511 it('NFT: disallows to nest token if nesting is disabled', async () => {512 await usingApi(async api => {513 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});514 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {}});515 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');516517 // Try to create a nested token518 await expect(executeTransaction(api, alice, api.tx.unique.createItem(519 collection,520 {Ethereum: tokenIdToAddress(collection, targetToken)},521 {nft: {}} as any,522 )), 'while creating nested token').to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/);523524 // Create a token to be nested525 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');526 // Try to nest527 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);528 expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});529 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});530 });531 });532533 it('NFT: disallows a non-Owner to nest someone else\'s token', async () => {534 await usingApi(async api => {535 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});536 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}});537538 await addToAllowListExpectSuccess(alice, collection, bob.address);539 await enableAllowListExpectSuccess(alice, collection);540 await enablePublicMintingExpectSuccess(alice, collection);541542 // Create a token to attempt to be nested into543 const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');544545 // Try to create a nested token in the wrong collection546 await expect(executeTransaction(api, alice, api.tx.unique.createItem(547 collection,548 {Ethereum: tokenIdToAddress(collection, targetToken)},549 {nft: {}} as any,550 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);551552 // Try to create and nest a token in the wrong collection553 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');554 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);555 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});556 });557 });558559 it('NFT: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => {560 await usingApi(async api => {561 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});562 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, restricted:[collection]}});563564 await addToAllowListExpectSuccess(alice, collection, bob.address);565 await enableAllowListExpectSuccess(alice, collection);566 await enablePublicMintingExpectSuccess(alice, collection);567568 // Create a token to attempt to be nested into569 const targetToken = await createItemExpectSuccess(bob, collection, 'NFT');570571 // Try to create a nested token in the wrong collection572 await expect(executeTransaction(api, alice, api.tx.unique.createItem(573 collection,574 {Ethereum: tokenIdToAddress(collection, targetToken)},575 {nft: {}} as any,576 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);577578 // Try to create and nest a token in the wrong collection579 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');580 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/);581 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});582 });583 });584585 it('NFT: disallows to nest token in an unlisted collection', async () => {586 await usingApi(async api => {587 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});588 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true, restricted:[]}});589590 // Create a token to attempt to be nested into591 const targetToken = await createItemExpectSuccess(alice, collection, 'NFT');592593 // Try to create a nested token in the wrong collection594 await expect(executeTransaction(api, alice, api.tx.unique.createItem(595 collection,596 {Ethereum: tokenIdToAddress(collection, targetToken)},597 {nft: {}} as any,598 )), 'while creating nested token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);599600 // Try to create and nest a token in the wrong collection601 const newToken = await createItemExpectSuccess(alice, collection, 'NFT');602 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);603 expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address});604 });605 });606607 // ---------- Fungible ----------608609 it('Fungible: disallows to nest token if nesting is disabled', async () => {610 await usingApi(async api => {611 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});612 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {}});613 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');614 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};615616 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});617618 // Try to create a nested token619 await expect(executeTransaction(api, alice, api.tx.unique.createItem(620 collectionFT,621 targetAddress,622 {Fungible: {Value: 10}},623 )), 'while creating nested token').to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/);624625 // Create a token to be nested626 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');627 // Try to nest628 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);629630 // Create another token to be nested631 const newToken2 = await createItemExpectSuccess(alice, collectionFT, 'Fungible');632 // Try to nest inside a fungible token633 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collectionFT, newToken)}, collectionFT, newToken2, 1)), 'while nesting new token inside fungible').to.be.rejectedWith(/fungible\.FungibleDisallowsNesting/);634 });635 });636637 it('Fungible: disallows a non-Owner to nest someone else\'s token', async () => {638 await usingApi(async api => {639 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});640 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});641642 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);643 await enableAllowListExpectSuccess(alice, collectionNFT);644 await enablePublicMintingExpectSuccess(alice, collectionNFT);645646 // Create a token to attempt to be nested into647 const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');648 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};649650 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});651652 // Try to create a nested token in the wrong collection653 await expect(executeTransaction(api, alice, api.tx.unique.createItem(654 collectionFT,655 targetAddress,656 {Fungible: {Value: 10}},657 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);658659 // Try to create and nest a token in the wrong collection660 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');661 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);662 });663 });664665 it('Fungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => {666 await usingApi(async api => {667 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});668 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);669 await enableAllowListExpectSuccess(alice, collectionNFT);670 await enablePublicMintingExpectSuccess(alice, collectionNFT);671672 // Create a token to attempt to be nested into673 const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');674 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};675676 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});677 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[collectionFT]}});678679 // Try to create a nested token in the wrong collection680 await expect(executeTransaction(api, alice, api.tx.unique.createItem(681 collectionFT,682 targetAddress,683 {Fungible: {Value: 10}},684 )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);685686 // Try to create and nest a token in the wrong collection687 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');688 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);689 });690 });691692 it('Fungible: disallows to nest token in an unlisted collection', async () => {693 await usingApi(async api => {694 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});695 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[]}});696697 // Create a token to attempt to be nested into698 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');699 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};700701 const collectionFT = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});702703 // Try to create a nested token in the wrong collection704 await expect(executeTransaction(api, alice, api.tx.unique.createItem(705 collectionFT,706 targetAddress,707 {Fungible: {Value: 10}},708 )), 'while creating a nested token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);709710 // Try to create and nest a token in the wrong collection711 const newToken = await createItemExpectSuccess(alice, collectionFT, 'Fungible');712 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);713 });714 });715716 // ---------- Re-Fungible ----------717718 it('ReFungible: disallows to nest token if nesting is disabled', async () => {719 await usingApi(async api => {720 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});721 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {}});722 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');723 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};724725 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});726727 // Create a nested token728 await expect(executeTransaction(api, alice, api.tx.unique.createItem(729 collectionRFT,730 targetAddress,731 {ReFungible: {pieces: 100}},732 )), 'while creating a nested token').to.be.rejectedWith(/^common\.UserIsNotAllowedToNest$/);733734 // Create a token to be nested735 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');736 // Try to nest737 await transferExpectFailure(collectionRFT, newToken, alice, targetAddress, 100);738 // Try to nest739 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);740741 // Create another token to be nested742 const newToken2 = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');743 // Try to nest inside a fungible token744 await expect(executeTransaction(api, alice, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collectionRFT, newToken)}, collectionRFT, newToken2, 1)), 'while nesting new token inside refungible').to.be.rejectedWith(/refungible\.RefungibleDisallowsNesting/);745 });746 });747748 it('ReFungible: disallows a non-Owner to nest someone else\'s token', async () => {749 await usingApi(async api => {750 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});751 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true}});752753 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);754 await enableAllowListExpectSuccess(alice, collectionNFT);755 await enablePublicMintingExpectSuccess(alice, collectionNFT);756757 // Create a token to attempt to be nested into758 const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');759 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};760761 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});762763 // Try to create a nested token in the wrong collection764 await expect(executeTransaction(api, alice, api.tx.unique.createItem(765 collectionRFT,766 targetAddress,767 {ReFungible: {pieces: 100}},768 )), 'while creating a nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);769770 // Try to create and nest a token in the wrong collection771 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');772 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);773 });774 });775776 it('ReFungible: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async () => {777 await usingApi(async api => {778 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});779 await addToAllowListExpectSuccess(alice, collectionNFT, bob.address);780 await enableAllowListExpectSuccess(alice, collectionNFT);781 await enablePublicMintingExpectSuccess(alice, collectionNFT);782783 // Create a token to attempt to be nested into784 const targetToken = await createItemExpectSuccess(bob, collectionNFT, 'NFT');785 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};786787 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});788 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[collectionRFT]}});789790 // Try to create a nested token in the wrong collection791 await expect(executeTransaction(api, alice, api.tx.unique.createItem(792 collectionRFT,793 targetAddress,794 {ReFungible: {pieces: 100}},795 )), 'while creating a nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);796797 // Try to create and nest a token in the wrong collection798 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');799 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/);800 });801 });802803 it('ReFungible: disallows to nest token to an unlisted collection', async () => {804 await usingApi(async api => {805 const collectionNFT = await createCollectionExpectSuccess({mode: {type: 'NFT'}});806 await setCollectionPermissionsExpectSuccess(alice, collectionNFT, {nesting: {tokenOwner: true, restricted:[]}});807808 // Create a token to attempt to be nested into809 const targetToken = await createItemExpectSuccess(alice, collectionNFT, 'NFT');810 const targetAddress = {Ethereum: tokenIdToAddress(collectionNFT, targetToken)};811812 const collectionRFT = await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});813814 // Try to create a nested token in the wrong collection815 await expect(executeTransaction(api, alice, api.tx.unique.createItem(816 collectionRFT,817 targetAddress,818 {ReFungible: {pieces: 100}},819 )), 'while creating a nested token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);820821 // Try to create and nest a token in the wrong collection822 const newToken = await createItemExpectSuccess(alice, collectionRFT, 'ReFungible');823 await expect(executeTransaction(api, alice, api.tx.unique.transfer(targetAddress, collectionRFT, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/);824 });825 });826});tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -830,6 +830,25 @@
});
}
+export async function burnItemExpectFailure(sender: IKeyringPair, collectionId: number, tokenId: number, value: number | bigint = 1) {
+ await usingApi(async (api) => {
+ const tx = api.tx.unique.burnItem(collectionId, tokenId, value);
+
+ const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;
+ const result = getCreateCollectionResult(events);
+ // tslint:disable-next-line:no-unused-expression
+ expect(result.success).to.be.false;
+ });
+}
+
+export async function burnFromExpectSuccess(sender: IKeyringPair, from: IKeyringPair | CrossAccountId, collectionId: number, tokenId: number, value: number | bigint = 1) {
+ await usingApi(async (api) => {
+ const tx = api.tx.unique.burnFrom(collectionId, normalizeAccountId(from), tokenId, value);
+ const events = await submitTransactionAsync(sender, tx);
+ return getGenericResult(events).success;
+ });
+}
+
export async function
approve(
api: ApiPromise,