--- a/tests/src/util/helpers.ts +++ b/tests/src/util/helpers.ts @@ -104,14 +104,9 @@ } export function bigIntToDecimals(number: bigint, decimals = 18): string { - let numberStr = number.toString(); - console.log('[0] str = ', numberStr); - - // Get rid of `n` at the end - numberStr = numberStr.substring(0, numberStr.length - 1); - console.log('[1] str = ', numberStr); - + const numberStr = number.toString(); const dotPos = numberStr.length - decimals; + if (dotPos <= 0) { return '0.' + numberStr; } else { @@ -1794,19 +1789,31 @@ ): Promise { const promise = new Promise(async (resolve) => { - const unsubscribe = await api.query.system.events(eventRecords => { + const unsubscribe = await api.rpc.chain.subscribeNewHeads(async header => { + const blockNumber = header.number.toHuman(); + const blockHash = header.hash; + const eventIdStr = `${eventSection}.${eventMethod}`; + const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`; + + console.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`); + + const apiAt = await api.at(blockHash); + const eventRecords = await apiAt.query.system.events(); + const neededEvent = eventRecords.find(r => { return r.event.section == eventSection && r.event.method == eventMethod; }); if (neededEvent) { + console.log(`Event \`${eventIdStr}\` is found`); + unsubscribe(); resolve(neededEvent); - } - - if (maxBlocksToWait > 0) { + } else if (maxBlocksToWait > 0) { maxBlocksToWait--; } else { + console.log(`Event \`${eventIdStr}\` is NOT found`); + unsubscribe(); resolve(null); } --- a/tests/src/xcm/xcmQuartz.test.ts +++ b/tests/src/xcm/xcmQuartz.test.ts @@ -26,6 +26,7 @@ import {blake2AsHex} from '@polkadot/util-crypto'; import waitNewBlocks from '../substrate/wait-new-blocks'; import getBalance from '../substrate/get-balance'; +import {XcmV2TraitsOutcome, XcmV2TraitsError} from '../interfaces'; chai.use(chaiAsPromised); const expect = chai.expect; @@ -34,6 +35,7 @@ const KARURA_CHAIN = 2000; const MOONRIVER_CHAIN = 2023; +const RELAY_PORT = 9844; const KARURA_PORT = 9946; const MOONRIVER_PORT = 9947; @@ -55,6 +57,10 @@ return parachainApiOptions(MOONRIVER_PORT); } +function relayOptions(): ApiOptions { + return parachainApiOptions(RELAY_PORT); +} + describe_xcm('Integration test: Exchanging tokens with Karura', () => { let alice: IKeyringPair; let randomAccount: IKeyringPair; @@ -200,8 +206,8 @@ const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle; const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit; - console.log(' - [Quartz -> Karura] transaction fees on Karura: %s KAR', + console.log( + '[Quartz -> Karura] transaction fees on Karura: %s KAR', bigIntToDecimals(karFees, KARURA_DECIMALS), ); console.log('[Quartz -> Karura] income %s QTZ', bigIntToDecimals(qtzIncomeTransfer)); @@ -281,10 +287,100 @@ expect(qtzFees == 0n).to.be.true; }); }); +}); + +// These tests are relevant only when the foreign asset pallet is disabled +describe('Integration test: Quartz rejects non-native tokens', () => { + let alice: IKeyringPair; + + before(async () => { + await usingApi(async (api, privateKeyWrapper) => { + alice = privateKeyWrapper('//Alice'); + }); + }); + + it('Quartz rejects tokens from the Relay', async () => { + await usingApi(async (api) => { + const destination = { + V1: { + parents: 0, + interior: {X1: { + Parachain: QUARTZ_CHAIN, + }, + }, + }}; + + const beneficiary = { + V1: { + parents: 0, + interior: {X1: { + AccountId32: { + network: 'Any', + id: alice.addressRaw, + }, + }}, + }, + }; + + const assets = { + V1: [ + { + id: { + Concrete: { + parents: 0, + interior: 'Here', + }, + }, + fun: { + Fungible: 50_000_000_000_000_000n, + }, + }, + ], + }; + + const feeAssetItem = 0; + + const weightLimit = { + Limited: 5_000_000_000, + }; + + const tx = api.tx.xcmPallet.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit); + const events = await submitTransactionAsync(alice, tx); + const result = getGenericResult(events); + expect(result.success).to.be.true; + }, relayOptions()); + + await usingApi(async api => { + const maxWaitBlocks = 3; + const dmpQueueExecutedDownward = await waitEvent( + api, + maxWaitBlocks, + 'dmpQueue', + 'ExecutedDownward', + ); + + expect( + dmpQueueExecutedDownward != null, + '[Relay] dmpQueue.ExecutedDownward event is expected', + ).to.be.true; + + const event = dmpQueueExecutedDownward!.event; + const outcome = event.data[1] as XcmV2TraitsOutcome; + + expect( + outcome.isIncomplete, + '[Relay] The outcome of the XCM should be `Incomplete`', + ).to.be.true; + + const incomplete = outcome.asIncomplete; + expect( + incomplete[1].toString() == 'AssetNotFound', + '[Relay] The XCM error should be `AssetNotFound`', + ).to.be.true; + }); + }); it('Quartz rejects KAR tokens from Karura', async () => { - // This test is relevant only when the foreign asset pallet is disabled - await usingApi(async (api) => { const destination = { V1: { @@ -295,7 +391,7 @@ { AccountId32: { network: 'Any', - id: randomAccount.addressRaw, + id: alice.addressRaw, }, }, ], @@ -321,7 +417,15 @@ expect( xcmpQueueFailEvent != null, - 'Only native token is supported when the Foreign-Assets pallet is not connected', + '[Karura] xcmpQueue.FailEvent event is expected', + ).to.be.true; + + const event = xcmpQueueFailEvent!.event; + const outcome = event.data[1] as XcmV2TraitsError; + + expect( + outcome.isUntrustedReserveLocation, + '[Karura] The XCM error should be `UntrustedReserveLocation`', ).to.be.true; }); }); --- a/tests/src/xcm/xcmUnique.test.ts +++ b/tests/src/xcm/xcmUnique.test.ts @@ -26,6 +26,7 @@ import {blake2AsHex} from '@polkadot/util-crypto'; import waitNewBlocks from '../substrate/wait-new-blocks'; import getBalance from '../substrate/get-balance'; +import {XcmV2TraitsError, XcmV2TraitsOutcome} from '../interfaces'; chai.use(chaiAsPromised); const expect = chai.expect; @@ -34,6 +35,7 @@ const ACALA_CHAIN = 2000; const MOONBEAM_CHAIN = 2004; +const RELAY_PORT = 9844; const ACALA_PORT = 9946; const MOONBEAM_PORT = 9947; @@ -55,6 +57,10 @@ return parachainApiOptions(MOONBEAM_PORT); } +function relayOptions(): ApiOptions { + return parachainApiOptions(RELAY_PORT); +} + describe_xcm('Integration test: Exchanging tokens with Acala', () => { let alice: IKeyringPair; let randomAccount: IKeyringPair; @@ -281,10 +287,100 @@ expect(unqFees == 0n).to.be.true; }); }); +}); + +// These tests are relevant only when the foreign asset pallet is disabled +describe('Integration test: Unique rejects non-native tokens', () => { + let alice: IKeyringPair; + + before(async () => { + await usingApi(async (api, privateKeyWrapper) => { + alice = privateKeyWrapper('//Alice'); + }); + }); + + it('Unique rejects tokens from the Relay', async () => { + await usingApi(async (api) => { + const destination = { + V1: { + parents: 0, + interior: {X1: { + Parachain: UNIQUE_CHAIN, + }, + }, + }}; + + const beneficiary = { + V1: { + parents: 0, + interior: {X1: { + AccountId32: { + network: 'Any', + id: alice.addressRaw, + }, + }}, + }, + }; + + const assets = { + V1: [ + { + id: { + Concrete: { + parents: 0, + interior: 'Here', + }, + }, + fun: { + Fungible: 50_000_000_000_000_000n, + }, + }, + ], + }; + + const feeAssetItem = 0; + + const weightLimit = { + Limited: 5_000_000_000, + }; + + const tx = api.tx.xcmPallet.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit); + const events = await submitTransactionAsync(alice, tx); + const result = getGenericResult(events); + expect(result.success).to.be.true; + }, relayOptions()); + + await usingApi(async api => { + const maxWaitBlocks = 3; + const dmpQueueExecutedDownward = await waitEvent( + api, + maxWaitBlocks, + 'dmpQueue', + 'ExecutedDownward', + ); + + expect( + dmpQueueExecutedDownward != null, + '[Relay] dmpQueue.ExecutedDownward event is expected', + ).to.be.true; + + const event = dmpQueueExecutedDownward!.event; + const outcome = event.data[1] as XcmV2TraitsOutcome; + + expect( + outcome.isIncomplete, + '[Relay] The outcome of the XCM should be `Incomplete`', + ).to.be.true; + + const incomplete = outcome.asIncomplete; + expect( + incomplete[1].toString() == 'AssetNotFound', + '[Relay] The XCM error should be `AssetNotFound`', + ).to.be.true; + }); + }); it('Unique rejects ACA tokens from Acala', async () => { - // This test is relevant only when the foreign asset pallet is disabled - await usingApi(async (api) => { const destination = { V1: { @@ -295,7 +391,7 @@ { AccountId32: { network: 'Any', - id: randomAccount.addressRaw, + id: alice.addressRaw, }, }, ], @@ -321,7 +417,15 @@ expect( xcmpQueueFailEvent != null, - 'Only native token is supported when the Foreign-Assets pallet is not connected', + '[Acala] xcmpQueue.FailEvent event is expected', + ).to.be.true; + + const event = xcmpQueueFailEvent!.event; + const outcome = event.data[1] as XcmV2TraitsError; + + expect( + outcome.isUntrustedReserveLocation, + '[Acala] The XCM error should be `UntrustedReserveLocation`', ).to.be.true; }); });