--- a/crates/struct-versioning/src/lib.rs +++ b/crates/struct-versioning/src/lib.rs @@ -199,7 +199,7 @@ let mut out = Vec::new(); for version in attr.first_version..=attr.current_version { let name = if version == attr.current_version { - input.ident.clone() + input.ident.clone() } else { format_ident!("{}Version{}", &input.ident, version) }; --- a/tests/src/nesting/unnest.test.ts +++ b/tests/src/nesting/unnest.test.ts @@ -4,14 +4,13 @@ import usingApi, {executeTransaction} from '../substrate/substrate-api'; import { createCollectionExpectSuccess, - createItemExpectFailure, + createItemExpectFailure, createItemExpectSuccess, - getTokenOwner, - getTopmostTokenOwner, - normalizeAccountId, - setCollectionLimitsExpectSuccess, - transferExpectFailure, - transferExpectSuccess, + getTokenOwner, + getTopmostTokenOwner, + normalizeAccountId, + setCollectionLimitsExpectSuccess, + transferExpectSuccess, } from '../util/helpers'; import {IKeyringPair} from '@polkadot/types/types'; @@ -36,8 +35,8 @@ // Unnest await expect(executeTransaction( - api, - alice, + api, + alice, api.tx.unique.transferFrom(normalizeAccountId(targetAddress), normalizeAccountId(alice), collection, nestedToken, 1), )).to.not.be.rejected; expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address}); @@ -45,8 +44,8 @@ // Nest and burn await transferExpectSuccess(collection, nestedToken, alice, targetAddress); await expect(executeTransaction( - api, - alice, + api, + alice, api.tx.unique.burnFrom(collection, normalizeAccountId(alice.address), nestedToken, 1), )).to.not.be.rejected; await expect(getTokenOwner(api, collection, nestedToken)).to.be.rejected; // 'owner == null' @@ -61,7 +60,7 @@ alice = privateKey('//Alice'); bob = privateKey('//Bob'); }); - + it('Disallows a non-owner to unnest/burn a token', async () => { await usingApi(async api => { const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); @@ -74,8 +73,8 @@ // Try to unnest await expect(executeTransaction( - api, - bob, + api, + bob, api.tx.unique.transferFrom(normalizeAccountId(targetAddress), normalizeAccountId(bob), collection, nestedToken, 1), )).to.be.rejectedWith(/^common\.ApprovedValueTooLow$/); //await transferFromExpectSuccess(collection, nestedToken, bob, targetAddress, {Substrate: bob.address}); @@ -83,14 +82,14 @@ // Try to burn await expect(executeTransaction( - api, - bob, + api, + bob, api.tx.unique.burnFrom(collection, normalizeAccountId(bob.address), nestedToken, 1), )).to.not.be.rejectedWith(/^common\.ApprovedValueTooLow$/); expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()}); }); }); - + it('Disallows excessive token nesting', async () => { await usingApi(async api => { const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); @@ -108,23 +107,15 @@ }); // todo another test for creating excessive depth matryoshka with Ethereum, move this one to nest ^ - - // Recursive nesting - it('Prevents Ouroboros-nested operations', async () => { - await usingApi(async api => { - const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'}); - const targetToken = await createItemExpectSuccess(alice, collection, 'NFT'); - // Create a nested token ouroboros - const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)}); - await transferExpectSuccess(collection, targetToken, alice, {Ethereum: tokenIdToAddress(collection, nestedToken)}); - - expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()}); + // Recursive nesting + it('Prevents ouroboros creation', async () => { + const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); + await setCollectionLimitsExpectSuccess(alice, collection, {nestingRule: 'Owner'}); + const targetToken = await createItemExpectSuccess(alice, collection, 'NFT'); - // Make sure the ouroboros is detected - await expect(getTopmostTokenOwner(api, collection, nestedToken)).to.be.rejected; // With(/^common\.DepthLimit$/); - // todo transferFrom, must exit with Ouroboros error - }); + // Create a nested token ouroboros + const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)}); + expect(transferExpectSuccess(collection, targetToken, alice, {Ethereum: tokenIdToAddress(collection, nestedToken)})).to.be.rejectedWith(/^structure\.OuroborosDetected$/); }); -}); \ No newline at end of file +}); --- a/tests/src/pallet-presence.test.ts +++ b/tests/src/pallet-presence.test.ts @@ -30,6 +30,7 @@ 'timestamp', 'transactionpayment', 'treasury', + 'structure', 'system', 'vesting', 'parachainsystem', --- a/tests/src/util/helpers.ts +++ b/tests/src/util/helpers.ts @@ -17,15 +17,15 @@ import '../interfaces/augment-api-rpc'; import '../interfaces/augment-api-query'; import {ApiPromise, Keyring} from '@polkadot/api'; -import type {AccountId, EventRecord} from '@polkadot/types/interfaces'; -import {IKeyringPair} from '@polkadot/types/types'; +import type {AccountId, EventRecord, Event} from '@polkadot/types/interfaces'; +import {AnyTuple, IEvent, IKeyringPair} from '@polkadot/types/types'; import {evmToAddress} from '@polkadot/util-crypto'; import BN from 'bn.js'; import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; import {alicesPublicKey} from '../accounts'; import privateKey from '../substrate/privateKey'; -import {default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync} from '../substrate/substrate-api'; +import {default as usingApi, executeTransaction, submitTransactionAsync, submitTransactionExpectFailAsync} from '../substrate/substrate-api'; import {hexToStr, strToUTF16, utf16ToStr} from './util'; import {UpDataStructsRpcCollection} from '@polkadot/types/lookup'; @@ -105,7 +105,6 @@ } interface TransferResult { - success: boolean; collectionId: number; itemId: number; sender?: CrossAccountId; @@ -168,6 +167,12 @@ return result; } +export function getEvent(events: EventRecord[], check: (event: IEvent) => event is T): T | undefined { + const event = events.find(r => check(r.event)); + if (!event) return; + return event.event as T; +} + export function getGenericResult(events: EventRecord[]): GenericResult { const result: GenericResult = { success: false, @@ -225,27 +230,20 @@ return result; } -export function getTransferResult(events: EventRecord[]): TransferResult { - const result: TransferResult = { - success: false, - collectionId: 0, - itemId: 0, - value: 0n, - }; - - events.forEach(({event: {data, method, section}}) => { - if (method === 'ExtrinsicSuccess') { - result.success = true; - } else if (section === 'common' && method === 'Transfer') { - result.collectionId = +data[0].toString(); - result.itemId = +data[1].toString(); - result.sender = normalizeAccountId(data[2].toJSON() as any); - result.recipient = normalizeAccountId(data[3].toJSON() as any); - result.value = BigInt(data[4].toString()); +export function getTransferResult(api: ApiPromise, events: EventRecord[]): TransferResult { + for (const {event} of events) { + if (api.events.common.Transfer.is(event)) { + const [collection, token, sender, recipient, value] = event.data; + return { + collectionId: collection.toNumber(), + itemId: token.toNumber(), + sender: normalizeAccountId(sender.toJSON() as any), + recipient: normalizeAccountId(recipient.toJSON() as any), + value: value.toBigInt(), + }; } - }); - - return result; + } + throw new Error('no transfer event'); } interface Nft { @@ -300,9 +298,9 @@ } const tx = api.tx.unique.createCollectionEx({ - name: strToUTF16(name), - description: strToUTF16(description), - tokenPrefix: strToUTF16(tokenPrefix), + name: strToUTF16(name), + description: strToUTF16(description), + tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any, schemaVersion: schemaVersion, }); @@ -912,15 +910,15 @@ balanceBefore = await getBalance(api, collectionId, to, tokenId); } const transferTx = api.tx.unique.transfer(to, collectionId, tokenId, value); - const events = await submitTransactionAsync(sender, transferTx); - const result = getTransferResult(events); - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.true; + const events = await executeTransaction(api, sender, transferTx); + + const result = getTransferResult(api, events); expect(result.collectionId).to.be.equal(collectionId); expect(result.itemId).to.be.equal(tokenId); expect(result.sender).to.be.deep.equal(normalizeAccountId(sender.address)); expect(result.recipient).to.be.deep.equal(to); expect(result.value).to.be.equal(BigInt(value)); + if (type === 'NFT') { expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(to); }