difftreelog
feat(xcm tests) `XcmHelper` integraion for QTZ\UNQ
in: master
6 files changed
.baedeker/.gitignorediffbeforeafterboth--- a/.baedeker/.gitignore
+++ b/.baedeker/.gitignore
@@ -1,4 +1,5 @@
/.bdk-env
-/rewrites.jsonnet
+/rewrites*.jsonnet
/vendor
/baedeker-library
+!/rewrites.example.jsonnet
\ No newline at end of file
.github/workflows/xcm.ymldiffbeforeafterboth--- a/.github/workflows/xcm.yml
+++ b/.github/workflows/xcm.yml
@@ -38,7 +38,7 @@
with:
matrix: |
network {opal}, relay_branch {${{ env.UNIQUEWEST_MAINNET_BRANCH }}}, acala_version {${{ env.ACALA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONBEAM_BUILD_BRANCH }}}, cumulus_version {${{ env.WESTMINT_BUILD_BRANCH }}}, astar_version {${{ env.ASTAR_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testXcmOpal}, runtime_features {opal-runtime}
- network {quartz}, relay_branch {${{ env.KUSAMA_MAINNET_BRANCH }}}, acala_version {${{ env.KARURA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONRIVER_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINE_BUILD_BRANCH }}}, astar_version {${{ env.SHIDEN_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testXcmQuartz}, runtime_features {quartz-runtime}
+ network {quartz}, relay_branch {${{ env.KUSAMA_MAINNET_BRANCH }}}, acala_version {${{ env.KARURA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONRIVER_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINE_BUILD_BRANCH }}}, astar_version {${{ env.SHIDEN_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testFullXcmQuartz}, runtime_features {quartz-runtime}
network {unique}, relay_branch {${{ env.POLKADOT_MAINNET_BRANCH }}}, acala_version {${{ env.ACALA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONBEAM_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINT_BUILD_BRANCH }}}, astar_version {${{ env.ASTAR_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testFullXcmUnique}, runtime_features {unique-runtime}
xcm:
tests/src/xcm/lowLevelXcmQuartz.test.tsdiffbeforeafterboth--- a/tests/src/xcm/lowLevelXcmQuartz.test.ts
+++ b/tests/src/xcm/lowLevelXcmQuartz.test.ts
@@ -15,35 +15,16 @@
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
import {IKeyringPair} from '@polkadot/types/types';
-import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingMoonriverPlaygrounds, usingShidenPlaygrounds, usingRelayPlaygrounds} from '../util';
-import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
-import {STATEMINE_CHAIN, QUARTZ_CHAIN, KARURA_CHAIN, MOONRIVER_CHAIN, SHIDEN_CHAIN, STATEMINE_DECIMALS, KARURA_DECIMALS, QTZ_DECIMALS, RELAY_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, relayUrl, shidenUrl, statemineUrl, SAFE_XCM_VERSION, XcmTestHelper, TRANSFER_AMOUNT} from './xcm.types';
-
+import {itSub, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingMoonriverPlaygrounds, usingShidenPlaygrounds, usingRelayPlaygrounds} from '../util';
+import {QUARTZ_CHAIN, QTZ_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, shidenUrl, SAFE_XCM_VERSION, XcmTestHelper, TRANSFER_AMOUNT, SENDER_BUDGET, relayUrl} from './xcm.types';
+import {hexToString} from '@polkadot/util';
const testHelper = new XcmTestHelper('quartz');
describeXCM('[XCMLL] Integration test: Exchanging tokens with Karura', () => {
let alice: IKeyringPair;
let randomAccount: IKeyringPair;
-
- let balanceQuartzTokenInit: bigint;
- let balanceQuartzTokenMiddle: bigint;
- let balanceQuartzTokenFinal: bigint;
- let balanceKaruraTokenInit: bigint;
- let balanceKaruraTokenMiddle: bigint;
- let balanceKaruraTokenFinal: bigint;
- let balanceQuartzForeignTokenInit: bigint;
- let balanceQuartzForeignTokenMiddle: bigint;
- let balanceQuartzForeignTokenFinal: bigint;
- // computed by a test transfer from prod Quartz to prod Karura.
- // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9
- // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)
- const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;
- const karuraEps = 8n * 10n ** 16n;
-
- let karuraBackwardTransferAmount: bigint;
-
before(async () => {
await usingPlaygrounds(async (helper, privateKey) => {
alice = await privateKey('//Alice');
@@ -72,15 +53,19 @@
minimalBalance: 1000000000000000000n,
};
- await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);
+ const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v]: [any, any]) =>
+ hexToString(v.toJSON()['symbol'])) as string[];
+
+ if(!assets.includes('QTZ')) {
+ await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);
+ } else {
+ console.log('QTZ token already registered on Karura assetRegistry pallet');
+ }
await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);
- balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);
- balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});
});
await usingPlaygrounds(async (helper) => {
- await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);
- balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);
+ await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
});
});
@@ -100,67 +85,17 @@
// the the corresponding foreign assets are not registered
describeXCM('[XCMLL] Integration test: Quartz rejects non-native tokens', () => {
let alice: IKeyringPair;
- let alith: IKeyringPair;
- const testAmount = 100_000_000_000n;
- let quartzParachainJunction;
- let quartzAccountJunction;
- let quartzParachainMultilocation: any;
- let quartzAccountMultilocation: any;
- let quartzCombinedMultilocation: any;
-
- let messageSent: any;
-
- const maxWaitBlocks = 3;
-
before(async () => {
await usingPlaygrounds(async (helper, privateKey) => {
alice = await privateKey('//Alice');
- quartzParachainJunction = {Parachain: QUARTZ_CHAIN};
- quartzAccountJunction = {
- AccountId32: {
- network: 'Any',
- id: alice.addressRaw,
- },
- };
-
- quartzParachainMultilocation = {
- V2: {
- parents: 1,
- interior: {
- X1: quartzParachainJunction,
- },
- },
- };
- quartzAccountMultilocation = {
- V2: {
- parents: 0,
- interior: {
- X1: quartzAccountJunction,
- },
- },
- };
- quartzCombinedMultilocation = {
- V2: {
- parents: 1,
- interior: {
- X2: [quartzParachainJunction, quartzAccountJunction],
- },
- },
- };
-
// Set the default version to wrap the first message to other chains.
await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
});
-
- // eslint-disable-next-line require-await
- await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {
- alith = helper.account.alithAccount();
- });
});
itSub('Quartz rejects KAR tokens from Karura', async () => {
@@ -195,22 +130,12 @@
minimalBalance: 1n,
};
- let balanceQuartzTokenInit: bigint;
- let balanceQuartzTokenMiddle: bigint;
- let balanceQuartzTokenFinal: bigint;
- let balanceForeignQtzTokenInit: bigint;
- let balanceForeignQtzTokenMiddle: bigint;
- let balanceForeignQtzTokenFinal: bigint;
- let balanceMovrTokenInit: bigint;
- let balanceMovrTokenMiddle: bigint;
- let balanceMovrTokenFinal: bigint;
before(async () => {
await usingPlaygrounds(async (helper, privateKey) => {
alice = await privateKey('//Alice');
[randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);
- balanceForeignQtzTokenInit = 0n;
// Set the default version to wrap the first message to other chains.
await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
@@ -239,20 +164,22 @@
const isSufficient = true;
const unitsPerSecond = 1n;
const numAssetsWeightHint = 0;
+ if((await helper.assetManager.assetTypeId(quartzAssetLocation)).toJSON()) {
+ console.log('Quartz asset already registered on Moonriver');
+ } else {
+ const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
+ location: quartzAssetLocation,
+ metadata: quartzAssetMetadata,
+ existentialDeposit,
+ isSufficient,
+ unitsPerSecond,
+ numAssetsWeightHint,
+ });
- const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
- location: quartzAssetLocation,
- metadata: quartzAssetMetadata,
- existentialDeposit,
- isSufficient,
- unitsPerSecond,
- numAssetsWeightHint,
- });
-
- console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);
-
- await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);
+ console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);
+ await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);
+ }
// >>> Acquire Quartz AssetId Info on Moonriver >>>
console.log('Acquire Quartz AssetId Info on Moonriver.......');
@@ -267,13 +194,10 @@
await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);
console.log('Sponsoring random Account.......DONE');
// <<< Sponsoring random Account <<<
-
- balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);
});
await usingPlaygrounds(async (helper) => {
await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);
- balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);
});
});
@@ -296,7 +220,7 @@
describeXCM('[XCMLL] Integration test: Exchanging tokens with Shiden', () => {
let alice: IKeyringPair;
- let sender: IKeyringPair;
+ let randomAccount: IKeyringPair;
const QTZ_ASSET_ID_ON_SHIDEN = 1;
const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;
@@ -304,71 +228,69 @@
// Quartz -> Shiden
const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden
const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?
- const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ
- const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens
- // Shiden -> Quartz
- const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ
- const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 4.999_999_999_088_000_000n QTZ
- let balanceAfterQuartzToShidenXCM: bigint;
before(async () => {
await usingPlaygrounds(async (helper, privateKey) => {
alice = await privateKey('//Alice');
- [sender] = await helper.arrange.createAccounts([100n], alice);
- console.log('sender', sender.address);
+ randomAccount = helper.arrange.createEmptyAccount();
+ await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
+ console.log('sender: ', randomAccount.address);
// Set the default version to wrap the first message to other chains.
await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
});
await usingShidenPlaygrounds(shidenUrl, async (helper) => {
- console.log('1. Create foreign asset and metadata');
- // TODO update metadata with values from production
- await helper.assets.create(
- alice,
- QTZ_ASSET_ID_ON_SHIDEN,
- alice.address,
- QTZ_MINIMAL_BALANCE_ON_SHIDEN,
- );
+ if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) {
+ console.log('1. Create foreign asset and metadata');
+ // TODO update metadata with values from production
+ await helper.assets.create(
+ alice,
+ QTZ_ASSET_ID_ON_SHIDEN,
+ alice.address,
+ QTZ_MINIMAL_BALANCE_ON_SHIDEN,
+ );
- await helper.assets.setMetadata(
- alice,
- QTZ_ASSET_ID_ON_SHIDEN,
- 'Cross chain QTZ',
- 'xcQTZ',
- Number(QTZ_DECIMALS),
- );
+ await helper.assets.setMetadata(
+ alice,
+ QTZ_ASSET_ID_ON_SHIDEN,
+ 'Cross chain QTZ',
+ 'xcQTZ',
+ Number(QTZ_DECIMALS),
+ );
- console.log('2. Register asset location on Shiden');
- const assetLocation = {
- V2: {
- parents: 1,
- interior: {
- X1: {
- Parachain: QUARTZ_CHAIN,
+ console.log('2. Register asset location on Shiden');
+ const assetLocation = {
+ V2: {
+ parents: 1,
+ interior: {
+ X1: {
+ Parachain: QUARTZ_CHAIN,
+ },
},
},
- },
- };
-
- await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);
+ };
- console.log('3. Set QTZ payment for XCM execution on Shiden');
- await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);
+ await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);
+ console.log('3. Set QTZ payment for XCM execution on Shiden');
+ await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);
+ } else {
+ console.log('QTZ is already registered on Shiden');
+ }
console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');
- await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);
+ await helper.balance.transferToSubstrate(alice, randomAccount.address, shidenInitialBalance);
});
});
itSub('Should connect and send QTZ to Shiden', async () => {
- await testHelper.sendUnqTo('shiden', sender);
+ await testHelper.sendUnqTo('shiden', randomAccount);
});
itSub('Should connect to Shiden and send QTZ back', async () => {
- await testHelper.sendUnqBack('shiden', alice, sender);
+ await testHelper.sendUnqBack('shiden', alice, randomAccount);
});
itSub('Shiden can send only up to its balance', async () => {
tests/src/xcm/lowLevelXcmUnique.test.tsdiffbeforeafterboth--- a/tests/src/xcm/lowLevelXcmUnique.test.ts
+++ b/tests/src/xcm/lowLevelXcmUnique.test.ts
@@ -16,320 +16,16 @@
import {IKeyringPair} from '@polkadot/types/types';
import config from '../config';
-import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, requirePalletsOrSkip, Pallets} from '../util';
-import {Event} from '../util/playgrounds/unique.dev';
+import {itSub, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds} from '../util';
import {nToBigInt} from '@polkadot/util';
import {hexToString} from '@polkadot/util';
-import {ASTAR_DECIMALS, NETWORKS, SAFE_XCM_VERSION, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl, expectFailedToTransact, expectUntrustedReserveLocationFail, getDevPlayground, mapToChainId, mapToChainUrl, maxWaitBlocks, moonbeamUrl, polkadexUrl, relayUrl, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types';
-
-
-const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;
-const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;
-const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;
-const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;
-const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;
-
-let balanceUniqueTokenInit: bigint;
-let balanceUniqueTokenMiddle: bigint;
-let balanceUniqueTokenFinal: bigint;
-let unqFees: bigint;
+import {ASTAR_DECIMALS, SAFE_XCM_VERSION, SENDER_BUDGET, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl, moonbeamUrl, polkadexUrl, relayUrl, uniqueAssetId} from './xcm.types';
const testHelper = new XcmTestHelper('unique');
-
-async function genericSendUnqTo(
- networkName: keyof typeof NETWORKS,
- randomAccount: IKeyringPair,
- randomAccountOnTargetChain = randomAccount,
-) {
- const networkUrl = mapToChainUrl(networkName);
- const targetPlayground = getDevPlayground(networkName);
- await usingPlaygrounds(async (helper) => {
- balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
- const destination = {
- V2: {
- parents: 1,
- interior: {
- X1: {
- Parachain: mapToChainId(networkName),
- },
- },
- },
- };
-
- const beneficiary = {
- V2: {
- parents: 0,
- interior: {
- X1: (
- networkName == 'moonbeam' ?
- {
- AccountKey20: {
- network: 'Any',
- key: randomAccountOnTargetChain.address,
- },
- }
- :
- {
- AccountId32: {
- network: 'Any',
- id: randomAccountOnTargetChain.addressRaw,
- },
- }
- ),
- },
- },
- };
-
- const assets = {
- V2: [
- {
- id: {
- Concrete: {
- parents: 0,
- interior: 'Here',
- },
- },
- fun: {
- Fungible: TRANSFER_AMOUNT,
- },
- },
- ],
- };
- const feeAssetItem = 0;
-
- await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');
- const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);
-
- unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;
- console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(unqFees));
- expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;
-
- await targetPlayground(networkUrl, async (helper) => {
- /*
- Since only the parachain part of the Polkadex
- infrastructure is launched (without their
- solochain validators), processing incoming
- assets will lead to an error.
- This error indicates that the Polkadex chain
- received a message from the Unique network,
- since the hash is being checked to ensure
- it matches what was sent.
- */
- if(networkName == 'polkadex') {
- await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);
- } else {
- await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash);
- }
- });
-
- });
-}
-
-async function genericSendUnqBack(
- networkName: keyof typeof NETWORKS,
- sudoer: IKeyringPair,
- randomAccountOnUnq: IKeyringPair,
-) {
- const networkUrl = mapToChainUrl(networkName);
-
- const targetPlayground = getDevPlayground(networkName);
- await usingPlaygrounds(async (helper) => {
-
- const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
- randomAccountOnUnq.addressRaw,
- uniqueAssetId,
- SENDBACK_AMOUNT,
- );
-
- let xcmProgramSent: any;
-
- await targetPlayground(networkUrl, async (helper) => {
- if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, xcmProgram);
- xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- } else if('fastDemocracy' in helper) {
- const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, xcmProgram]);
- // Needed to bypass the call filter.
- const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
- await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);
- xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- }
- });
-
- await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);
-
- balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);
-
- expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees - STAYED_ON_TARGET_CHAIN);
-
- });
-}
-async function genericSendOnlyOwnedBalance(
- networkName: keyof typeof NETWORKS,
- sudoer: IKeyringPair,
-) {
- const networkUrl = mapToChainUrl(networkName);
- const targetPlayground = getDevPlayground(networkName);
- const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);
- await usingPlaygrounds(async (helper) => {
- const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));
- await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);
- const moreThanTargetChainHas = 2n * targetChainBalance;
-
- const targetAccount = helper.arrange.createEmptyAccount();
-
- const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
- targetAccount.addressRaw,
- {
- Concrete: {
- parents: 0,
- interior: 'Here',
- },
- },
- moreThanTargetChainHas,
- );
-
- let maliciousXcmProgramSent: any;
-
-
- await targetPlayground(networkUrl, async (helper) => {
- if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgram);
- maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- } else if('fastDemocracy' in helper) {
- const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);
- // Needed to bypass the call filter.
- const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
- await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);
- maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- }
- });
-
- await expectFailedToTransact(helper, maliciousXcmProgramSent);
-
- const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
- expect(targetAccountBalance).to.be.equal(0n);
- });
-}
-
-async function genericReserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) {
- const networkUrl = mapToChainUrl(netwokrName);
- const targetPlayground = getDevPlayground(netwokrName);
-
- await usingPlaygrounds(async (helper) => {
- const testAmount = 10_000n * (10n ** UNQ_DECIMALS);
- const targetAccount = helper.arrange.createEmptyAccount();
-
- const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
- targetAccount.addressRaw,
- uniqueAssetId,
- testAmount,
- );
-
- const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(
- targetAccount.addressRaw,
- {
- Concrete: {
- parents: 0,
- interior: 'Here',
- },
- },
- testAmount,
- );
-
- let maliciousXcmProgramFullIdSent: any;
- let maliciousXcmProgramHereIdSent: any;
- const maxWaitBlocks = 3;
-
- // Try to trick Unique using full UNQ identification
- await targetPlayground(networkUrl, async (helper) => {
- if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramFullId);
- maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- }
- // Moonbeam case
- else if('fastDemocracy' in helper) {
- const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);
- // Needed to bypass the call filter.
- const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
- await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using path asset identification`,batchCall);
-
- maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- }
- });
-
-
- await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);
-
- let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
- expect(accountBalance).to.be.equal(0n);
-
- // Try to trick Unique using shortened UNQ identification
- await targetPlayground(networkUrl, async (helper) => {
- if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramHereId);
- maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- }
- else if('fastDemocracy' in helper) {
- const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);
- // Needed to bypass the call filter.
- const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
- await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall);
-
- maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- }
- });
-
- await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);
-
- accountBalance = await helper.balance.getSubstrate(targetAccount.address);
- expect(accountBalance).to.be.equal(0n);
- });
-}
-
-async function genericRejectNativeTokensFrom(networkName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {
- const networkUrl = mapToChainUrl(networkName);
- const targetPlayground = getDevPlayground(networkName);
- let messageSent: any;
-
- await usingPlaygrounds(async (helper) => {
- const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
- helper.arrange.createEmptyAccount().addressRaw,
- {
- Concrete: {
- parents: 1,
- interior: {
- X1: {
- Parachain: mapToChainId(networkName),
- },
- },
- },
- },
- TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,
- );
- await targetPlayground(networkUrl, async (helper) => {
- if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueVersionedMultilocation, maliciousXcmProgramFullId);
- messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- } else if('fastDemocracy' in helper) {
- const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);
- // Needed to bypass the call filter.
- const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
- await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall);
-
- messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- }
- });
- await expectFailedToTransact(helper, messageSent);
- });
-}
-
-
describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => {
let alice: IKeyringPair;
let randomAccount: IKeyringPair;
@@ -375,24 +71,23 @@
await usingPlaygrounds(async (helper) => {
await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
- balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
});
});
itSub('Should connect and send UNQ to Acala', async () => {
- await genericSendUnqTo('acala', randomAccount);
+ await testHelper.sendUnqTo('acala', randomAccount);
});
itSub('Should connect to Acala and send UNQ back', async () => {
- await genericSendUnqBack('acala', alice, randomAccount);
+ await testHelper.sendUnqBack('acala', alice, randomAccount);
});
itSub('Acala can send only up to its balance', async () => {
- await genericSendOnlyOwnedBalance('acala', alice);
+ await testHelper.sendOnlyOwnedBalance('acala', alice);
});
itSub('Should not accept reserve transfer of UNQ from Acala', async () => {
- await genericReserveTransferUNQfrom('acala', alice);
+ await testHelper.reserveTransferUNQfrom('acala', alice);
});
});
@@ -430,7 +125,6 @@
await usingPlaygrounds(async (helper) => {
await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
- balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
});
});
@@ -467,19 +161,19 @@
});
itSub('Unique rejects ACA tokens from Acala', async () => {
- await genericRejectNativeTokensFrom('acala', alice);
+ await testHelper.rejectNativeTokensFrom('acala', alice);
});
itSub('Unique rejects GLMR tokens from Moonbeam', async () => {
- await genericRejectNativeTokensFrom('moonbeam', alice);
+ await testHelper.rejectNativeTokensFrom('moonbeam', alice);
});
itSub('Unique rejects ASTR tokens from Astar', async () => {
- await genericRejectNativeTokensFrom('astar', alice);
+ await testHelper.rejectNativeTokensFrom('astar', alice);
});
itSub('Unique rejects PDX tokens from Polkadex', async () => {
- await genericRejectNativeTokensFrom('polkadex', alice);
+ await testHelper.rejectNativeTokensFrom('polkadex', alice);
});
});
@@ -570,7 +264,6 @@
await usingPlaygrounds(async (helper) => {
await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, SENDER_BUDGET);
- balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);
});
});
@@ -657,19 +350,19 @@
});
itSub('Should connect and send UNQ to Astar', async () => {
- await genericSendUnqTo('astar', randomAccount);
+ await testHelper.sendUnqTo('astar', randomAccount);
});
itSub('Should connect to Astar and send UNQ back', async () => {
- await genericSendUnqBack('astar', alice, randomAccount);
+ await testHelper.sendUnqBack('astar', alice, randomAccount);
});
itSub('Astar can send only up to its balance', async () => {
- await genericSendOnlyOwnedBalance('astar', alice);
+ await testHelper.sendOnlyOwnedBalance('astar', alice);
});
itSub('Should not accept reserve transfer of UNQ from Astar', async () => {
- await genericReserveTransferUNQfrom('astar', alice);
+ await testHelper.reserveTransferUNQfrom('astar', alice);
});
});
tests/src/xcm/xcm.types.tsdiffbeforeafterboth1import {IKeyringPair} from '@polkadot/types/types';2import {hexToString} from '@polkadot/util';3import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, usingShidenPlaygrounds} from '../util';4import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';5import config from '../config';6import {blake2AsHex} from '@polkadot/util-crypto';78export const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);9export const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);10export const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000);11export const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);12export const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);13export const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);1415export const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095);16export const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000);17export const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000);18export const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023);19export const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007);2021export const relayUrl = config.relayUrl;22export const statemintUrl = config.statemintUrl;23export const statemineUrl = config.statemineUrl;2425export const acalaUrl = config.acalaUrl;26export const moonbeamUrl = config.moonbeamUrl;27export const astarUrl = config.astarUrl;28export const polkadexUrl = config.polkadexUrl;2930export const karuraUrl = config.karuraUrl;31export const moonriverUrl = config.moonriverUrl;32export const shidenUrl = config.shidenUrl;3334export const SAFE_XCM_VERSION = 3;353637export const RELAY_DECIMALS = 12;38export const STATEMINE_DECIMALS = 12;39export const KARURA_DECIMALS = 12;40export const SHIDEN_DECIMALS = 18n;41export const QTZ_DECIMALS = 18n;4243export const ASTAR_DECIMALS = 18n;44export const UNQ_DECIMALS = 18n;4546export const maxWaitBlocks = 6;4748export const uniqueMultilocation = {49 parents: 1,50 interior: {51 X1: {52 Parachain: UNIQUE_CHAIN,53 },54 },55};56export const uniqueVersionedMultilocation = {57 V3: uniqueMultilocation,58};5960export const uniqueAssetId = {61 Concrete: uniqueMultilocation,62};6364export const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {65 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash66 && event.outcome.isFailedToTransactAsset);67};68export const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {69 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash70 && event.outcome.isUntrustedReserveLocation);71};7273export const expectDownwardXcmNoPermission = async (helper: DevUniqueHelper) => {74 // The correct messageHash for downward messages can't be reliably obtained75 await helper.wait.expectEvent(maxWaitBlocks, Event.DmpQueue.ExecutedDownward, event => event.outcome.asIncomplete[1].isNoPermission);76};7778export const expectDownwardXcmComplete = async (helper: DevUniqueHelper) => {79 // The correct messageHash for downward messages can't be reliably obtained80 await helper.wait.expectEvent(maxWaitBlocks, Event.DmpQueue.ExecutedDownward, event => event.outcome.isComplete);81};8283export const NETWORKS = {84 acala: usingAcalaPlaygrounds,85 astar: usingAstarPlaygrounds,86 polkadex: usingPolkadexPlaygrounds,87 moonbeam: usingMoonbeamPlaygrounds,88 moonriver: usingMoonriverPlaygrounds,89 karura: usingKaruraPlaygrounds,90 shiden: usingShidenPlaygrounds,91} as const;92type NetworkNames = keyof typeof NETWORKS;9394type NativeRuntime = 'opal' | 'quartz' | 'unique';9596export function mapToChainId(networkName: keyof typeof NETWORKS): number {97 switch (networkName) {98 case 'acala':99 return ACALA_CHAIN;100 case 'astar':101 return ASTAR_CHAIN;102 case 'moonbeam':103 return MOONBEAM_CHAIN;104 case 'polkadex':105 return POLKADEX_CHAIN;106 case 'moonriver':107 return MOONRIVER_CHAIN;108 case 'karura':109 return KARURA_CHAIN;110 case 'shiden':111 return SHIDEN_CHAIN;112 }113}114115export function mapToChainUrl(networkName: NetworkNames): string {116 switch (networkName) {117 case 'acala':118 return acalaUrl;119 case 'astar':120 return astarUrl;121 case 'moonbeam':122 return moonbeamUrl;123 case 'polkadex':124 return polkadexUrl;125 case 'moonriver':126 return moonriverUrl;127 case 'karura':128 return karuraUrl;129 case 'shiden':130 return shidenUrl;131 }132}133134export function getDevPlayground(name: NetworkNames) {135 return NETWORKS[name];136}137138export const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;139const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;140const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;141const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;142const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;143144export class XcmTestHelper {145 private _balanceUniqueTokenInit: bigint = 0n;146 private _balanceUniqueTokenMiddle: bigint = 0n;147 private _balanceUniqueTokenFinal: bigint = 0n;148 private _unqFees: bigint = 0n;149 private _nativeRuntime: NativeRuntime;150151 constructor(runtime: NativeRuntime) {152 this._nativeRuntime = runtime;153 }154155 private _getNativeId() {156 switch (this._nativeRuntime) {157 case 'opal':158 // To-Do159 return 10;160 case 'quartz':161 return QUARTZ_CHAIN;162 case 'unique':163 return UNIQUE_CHAIN;164 }165 }166 private _isAddress20FormatFor(network: NetworkNames) {167 switch (network) {168 case 'moonbeam':169 case 'moonriver':170 return true;171 default:172 return false;173 }174 }175176 uniqueChainMultilocationForRelay() {177 return {178 V3: {179 parents: 0,180 interior: {181 X1: {Parachain: this._getNativeId()},182 },183 },184 };185 }186187 async sendUnqTo(188 networkName: keyof typeof NETWORKS,189 randomAccount: IKeyringPair,190 randomAccountOnTargetChain = randomAccount,191 ) {192 const networkUrl = mapToChainUrl(networkName);193 const targetPlayground = getDevPlayground(networkName);194 await usingPlaygrounds(async (helper) => {195 this._balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);196 const destination = {197 V2: {198 parents: 1,199 interior: {200 X1: {201 Parachain: mapToChainId(networkName),202 },203 },204 },205 };206207 const beneficiary = {208 V2: {209 parents: 0,210 interior: {211 X1: (212 this._isAddress20FormatFor(networkName) ?213 {214 AccountKey20: {215 network: 'Any',216 key: randomAccountOnTargetChain.address,217 },218 }219 :220 {221 AccountId32: {222 network: 'Any',223 id: randomAccountOnTargetChain.addressRaw,224 },225 }226 ),227 },228 },229 };230231 const assets = {232 V2: [233 {234 id: {235 Concrete: {236 parents: 0,237 interior: 'Here',238 },239 },240 fun: {241 Fungible: TRANSFER_AMOUNT,242 },243 },244 ],245 };246 const feeAssetItem = 0;247248 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');249 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);250 this._balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);251252 this._unqFees = this._balanceUniqueTokenInit - this._balanceUniqueTokenMiddle - TRANSFER_AMOUNT;253 console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(this._unqFees));254 expect(this._unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;255256 await targetPlayground(networkUrl, async (helper) => {257 /*258 Since only the parachain part of the Polkadex259 infrastructure is launched (without their260 solochain validators), processing incoming261 assets will lead to an error.262 This error indicates that the Polkadex chain263 received a message from the Unique network,264 since the hash is being checked to ensure265 it matches what was sent.266 */267 if(networkName == 'polkadex') {268 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);269 } else {270 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash);271 }272 });273274 });275 }276277 async sendUnqBack(278 networkName: keyof typeof NETWORKS,279 sudoer: IKeyringPair,280 randomAccountOnUnq: IKeyringPair,281 ) {282 const networkUrl = mapToChainUrl(networkName);283284 const targetPlayground = getDevPlayground(networkName);285 await usingPlaygrounds(async (helper) => {286287 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(288 randomAccountOnUnq.addressRaw,289 {290 Concrete: {291 parents: 1,292 interior: {293 X1: {Parachain: this._getNativeId()},294 },295 },296 },297 SENDBACK_AMOUNT,298 );299300 let xcmProgramSent: any;301302303 await targetPlayground(networkUrl, async (helper) => {304 if('getSudo' in helper) {305 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, xcmProgram);306 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);307 } else if('fastDemocracy' in helper) {308 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, xcmProgram]);309 // Needed to bypass the call filter.310 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);311 await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);312 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);313 }314 });315316 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);317318 this._balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);319320 expect(this._balanceUniqueTokenFinal).to.be.equal(this._balanceUniqueTokenInit - this._unqFees - STAYED_ON_TARGET_CHAIN);321322 });323 }324325 async sendOnlyOwnedBalance(326 networkName: keyof typeof NETWORKS,327 sudoer: IKeyringPair,328 ) {329 const networkUrl = mapToChainUrl(networkName);330 const targetPlayground = getDevPlayground(networkName);331332 const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);333334 await usingPlaygrounds(async (helper) => {335 const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));336 await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);337 const moreThanTargetChainHas = 2n * targetChainBalance;338339 const targetAccount = helper.arrange.createEmptyAccount();340341 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(342 targetAccount.addressRaw,343 {344 Concrete: {345 parents: 0,346 interior: 'Here',347 },348 },349 moreThanTargetChainHas,350 );351352 let maliciousXcmProgramSent: any;353354355 await targetPlayground(networkUrl, async (helper) => {356 if('getSudo' in helper) {357 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgram);358 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);359 } else if('fastDemocracy' in helper) {360 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);361 // Needed to bypass the call filter.362 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);363 await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);364 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);365 }366 });367368 await expectFailedToTransact(helper, maliciousXcmProgramSent);369370 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);371 expect(targetAccountBalance).to.be.equal(0n);372 });373 }374375 async reserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) {376 const networkUrl = mapToChainUrl(netwokrName);377 const targetPlayground = getDevPlayground(netwokrName);378379 await usingPlaygrounds(async (helper) => {380 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);381 const targetAccount = helper.arrange.createEmptyAccount();382383 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(384 targetAccount.addressRaw,385 {386 Concrete: {387 parents: 1,388 interior: {389 X1: {390 Parachain: this._getNativeId(),391 },392 },393 },394 },395 testAmount,396 );397398 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(399 targetAccount.addressRaw,400 {401 Concrete: {402 parents: 0,403 interior: 'Here',404 },405 },406 testAmount,407 );408409 let maliciousXcmProgramFullIdSent: any;410 let maliciousXcmProgramHereIdSent: any;411 const maxWaitBlocks = 3;412413 // Try to trick Unique using full UNQ identification414 await targetPlayground(networkUrl, async (helper) => {415 if('getSudo' in helper) {416 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramFullId);417 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);418 }419 // Moonbeam case420 else if('fastDemocracy' in helper) {421 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);422 // Needed to bypass the call filter.423 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);424 await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using path asset identification`,batchCall);425426 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);427 }428 });429430431 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);432433 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);434 expect(accountBalance).to.be.equal(0n);435436 // Try to trick Unique using shortened UNQ identification437 await targetPlayground(networkUrl, async (helper) => {438 if('getSudo' in helper) {439 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramHereId);440 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);441 }442 else if('fastDemocracy' in helper) {443 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);444 // Needed to bypass the call filter.445 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);446 await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall);447448 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);449 }450 });451452 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);453454 accountBalance = await helper.balance.getSubstrate(targetAccount.address);455 expect(accountBalance).to.be.equal(0n);456 });457 }458459 async rejectNativeTokensFrom(networkName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {460 const networkUrl = mapToChainUrl(networkName);461 const targetPlayground = getDevPlayground(networkName);462 let messageSent: any;463464 await usingPlaygrounds(async (helper) => {465 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(466 helper.arrange.createEmptyAccount().addressRaw,467 {468 Concrete: {469 parents: 1,470 interior: {471 X1: {472 Parachain: mapToChainId(networkName),473 },474 },475 },476 },477 TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,478 );479 await targetPlayground(networkUrl, async (helper) => {480 if('getSudo' in helper) {481 await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueVersionedMultilocation, maliciousXcmProgramFullId);482 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);483 } else if('fastDemocracy' in helper) {484 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);485 // Needed to bypass the call filter.486 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);487 await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall);488489 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);490 }491 });492 await expectFailedToTransact(helper, messageSent);493 });494 }495496 private async _relayXcmTransactSetStorage(variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch') {497 // eslint-disable-next-line require-await498 return await usingPlaygrounds(async (helper) => {499 const relayForceKV = () => {500 const random = Math.random();501 const key = `relay-forced-key (instance: ${random})`;502 const val = `relay-forced-value (instance: ${random})`;503 const call = helper.constructApiCall('api.tx.system.setStorage', [[[key, val]]]).method.toHex();504505 return {506 call,507 key,508 val,509 };510 };511512 if(variant == 'plain') {513 const kv = relayForceKV();514 return {515 program: helper.arrange.makeTransactProgram({516 weightMultiplier: 1,517 call: kv.call,518 }),519 kvs: [kv],520 };521 } else {522 const kv0 = relayForceKV();523 const kv1 = relayForceKV();524525 const batchCall = helper.constructApiCall(`api.tx.utility.${variant}`, [[kv0.call, kv1.call]]).method.toHex();526 return {527 program: helper.arrange.makeTransactProgram({528 weightMultiplier: 2,529 call: batchCall,530 }),531 kvs: [kv0, kv1],532 };533 }534 });535 }536537 async relayIsPermittedToSetStorage(relaySudoer: IKeyringPair, variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch') {538 const {program, kvs} = await this._relayXcmTransactSetStorage(variant);539540 await usingRelayPlaygrounds(relayUrl, async (helper) => {541 await helper.getSudo().executeExtrinsic(relaySudoer, 'api.tx.xcmPallet.send', [542 this.uniqueChainMultilocationForRelay(),543 program,544 ]);545 });546547 await usingPlaygrounds(async (helper) => {548 await expectDownwardXcmComplete(helper);549550 for(const kv of kvs) {551 const forcedValue = await helper.callRpc('api.rpc.state.getStorage', [kv.key]);552 expect(hexToString(forcedValue.toHex())).to.be.equal(kv.val);553 }554 });555 }556557 private async _relayXcmTransactSetBalance(variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch' | 'dispatchAs') {558 // eslint-disable-next-line require-await559 return await usingPlaygrounds(async (helper) => {560 const emptyAccount = helper.arrange.createEmptyAccount().address;561562 const forceSetBalanceCall = helper.constructApiCall('api.tx.balances.forceSetBalance', [emptyAccount, 10_000n]).method.toHex();563564 let call;565566 if(variant == 'plain') {567 call = forceSetBalanceCall;568569 } else if(variant == 'dispatchAs') {570 call = helper.constructApiCall('api.tx.utility.dispatchAs', [571 {572 system: 'Root',573 },574 forceSetBalanceCall,575 ]).method.toHex();576 } else {577 call = helper.constructApiCall(`api.tx.utility.${variant}`, [[forceSetBalanceCall]]).method.toHex();578 }579580 return {581 program: helper.arrange.makeTransactProgram({582 weightMultiplier: 1,583 call,584 }),585 emptyAccount,586 };587 });588 }589590 async relayIsNotPermittedToSetBalance(591 relaySudoer: IKeyringPair,592 variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch' | 'dispatchAs',593 ) {594 const {program, emptyAccount} = await this._relayXcmTransactSetBalance(variant);595596 await usingRelayPlaygrounds(relayUrl, async (helper) => {597 await helper.getSudo().executeExtrinsic(relaySudoer, 'api.tx.xcmPallet.send', [598 this.uniqueChainMultilocationForRelay(),599 program,600 ]);601 });602603 await usingPlaygrounds(async (helper) => {604 await expectDownwardXcmNoPermission(helper);605 expect(await helper.balance.getSubstrate(emptyAccount)).to.be.equal(0n);606 });607 }608}1import {IKeyringPair} from '@polkadot/types/types';2import {hexToString} from '@polkadot/util';3import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, usingShidenPlaygrounds} from '../util';4import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';5import config from '../config';67export const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);8export const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);9export const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000);10export const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);11export const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);12export const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);1314export const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095);15export const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000);16export const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000);17export const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023);18export const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007);1920export const relayUrl = config.relayUrl;21export const statemintUrl = config.statemintUrl;22export const statemineUrl = config.statemineUrl;2324export const acalaUrl = config.acalaUrl;25export const moonbeamUrl = config.moonbeamUrl;26export const astarUrl = config.astarUrl;27export const polkadexUrl = config.polkadexUrl;2829export const karuraUrl = config.karuraUrl;30export const moonriverUrl = config.moonriverUrl;31export const shidenUrl = config.shidenUrl;3233export const SAFE_XCM_VERSION = 3;343536export const RELAY_DECIMALS = 12;37export const STATEMINE_DECIMALS = 12;38export const KARURA_DECIMALS = 12;39export const SHIDEN_DECIMALS = 18n;40export const QTZ_DECIMALS = 18n;4142export const ASTAR_DECIMALS = 18n;43export const UNQ_DECIMALS = 18n;4445export const maxWaitBlocks = 6;4647export const uniqueMultilocation = {48 parents: 1,49 interior: {50 X1: {51 Parachain: UNIQUE_CHAIN,52 },53 },54};55export const uniqueVersionedMultilocation = {56 V3: uniqueMultilocation,57};5859export const uniqueAssetId = {60 Concrete: uniqueMultilocation,61};6263export const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {64 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash65 && event.outcome.isFailedToTransactAsset);66};67export const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {68 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash69 && event.outcome.isUntrustedReserveLocation);70};7172export const expectDownwardXcmNoPermission = async (helper: DevUniqueHelper) => {73 // The correct messageHash for downward messages can't be reliably obtained74 await helper.wait.expectEvent(maxWaitBlocks, Event.DmpQueue.ExecutedDownward, event => event.outcome.asIncomplete[1].isNoPermission);75};7677export const expectDownwardXcmComplete = async (helper: DevUniqueHelper) => {78 // The correct messageHash for downward messages can't be reliably obtained79 await helper.wait.expectEvent(maxWaitBlocks, Event.DmpQueue.ExecutedDownward, event => event.outcome.isComplete);80};8182export const NETWORKS = {83 acala: usingAcalaPlaygrounds,84 astar: usingAstarPlaygrounds,85 polkadex: usingPolkadexPlaygrounds,86 moonbeam: usingMoonbeamPlaygrounds,87 moonriver: usingMoonriverPlaygrounds,88 karura: usingKaruraPlaygrounds,89 shiden: usingShidenPlaygrounds,90} as const;91type NetworkNames = keyof typeof NETWORKS;9293type NativeRuntime = 'opal' | 'quartz' | 'unique';9495export function mapToChainId(networkName: keyof typeof NETWORKS): number {96 switch (networkName) {97 case 'acala':98 return ACALA_CHAIN;99 case 'astar':100 return ASTAR_CHAIN;101 case 'moonbeam':102 return MOONBEAM_CHAIN;103 case 'polkadex':104 return POLKADEX_CHAIN;105 case 'moonriver':106 return MOONRIVER_CHAIN;107 case 'karura':108 return KARURA_CHAIN;109 case 'shiden':110 return SHIDEN_CHAIN;111 }112}113114export function mapToChainUrl(networkName: NetworkNames): string {115 switch (networkName) {116 case 'acala':117 return acalaUrl;118 case 'astar':119 return astarUrl;120 case 'moonbeam':121 return moonbeamUrl;122 case 'polkadex':123 return polkadexUrl;124 case 'moonriver':125 return moonriverUrl;126 case 'karura':127 return karuraUrl;128 case 'shiden':129 return shidenUrl;130 }131}132133export function getDevPlayground(name: NetworkNames) {134 return NETWORKS[name];135}136137export const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;138export const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;139export const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;140export const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;141export const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;142143export class XcmTestHelper {144 private _balanceUniqueTokenInit: bigint = 0n;145 private _balanceUniqueTokenMiddle: bigint = 0n;146 private _balanceUniqueTokenFinal: bigint = 0n;147 private _unqFees: bigint = 0n;148 private _nativeRuntime: NativeRuntime;149150 constructor(runtime: NativeRuntime) {151 this._nativeRuntime = runtime;152 }153154 private _getNativeId() {155 switch (this._nativeRuntime) {156 case 'opal':157 // To-Do158 return 10;159 case 'quartz':160 return QUARTZ_CHAIN;161 case 'unique':162 return UNIQUE_CHAIN;163 }164 }165166 private _isAddress20FormatFor(network: NetworkNames) {167 switch (network) {168 case 'moonbeam':169 case 'moonriver':170 return true;171 default:172 return false;173 }174 }175176 private _runtimeVersionedMultilocation() {177 return {178 V3: {179 parents: 1,180 interior: {181 X1: {182 Parachain: this._getNativeId(),183 },184 },185 },186 };187 }188189 uniqueChainMultilocationForRelay() {190 return {191 V3: {192 parents: 0,193 interior: {194 X1: {Parachain: this._getNativeId()},195 },196 },197 };198 }199200 async sendUnqTo(201 networkName: keyof typeof NETWORKS,202 randomAccount: IKeyringPair,203 randomAccountOnTargetChain = randomAccount,204 ) {205 const networkUrl = mapToChainUrl(networkName);206 const targetPlayground = getDevPlayground(networkName);207 await usingPlaygrounds(async (helper) => {208 this._balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);209 const destination = {210 V2: {211 parents: 1,212 interior: {213 X1: {214 Parachain: mapToChainId(networkName),215 },216 },217 },218 };219220 const beneficiary = {221 V2: {222 parents: 0,223 interior: {224 X1: (225 this._isAddress20FormatFor(networkName) ?226 {227 AccountKey20: {228 network: 'Any',229 key: randomAccountOnTargetChain.address,230 },231 }232 :233 {234 AccountId32: {235 network: 'Any',236 id: randomAccountOnTargetChain.addressRaw,237 },238 }239 ),240 },241 },242 };243244 const assets = {245 V2: [246 {247 id: {248 Concrete: {249 parents: 0,250 interior: 'Here',251 },252 },253 fun: {254 Fungible: TRANSFER_AMOUNT,255 },256 },257 ],258 };259 const feeAssetItem = 0;260261 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');262 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);263 this._balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);264265 this._unqFees = this._balanceUniqueTokenInit - this._balanceUniqueTokenMiddle - TRANSFER_AMOUNT;266 console.log('[%s -> %s] transaction fees: %s', this._nativeRuntime, networkName, helper.util.bigIntToDecimals(this._unqFees));267 expect(this._unqFees > 0n, 'Negative fees, looks like nothing was transferred').to.be.true;268269 await targetPlayground(networkUrl, async (helper) => {270 /*271 Since only the parachain part of the Polkadex272 infrastructure is launched (without their273 solochain validators), processing incoming274 assets will lead to an error.275 This error indicates that the Polkadex chain276 received a message from the Unique network,277 since the hash is being checked to ensure278 it matches what was sent.279 */280 if(networkName == 'polkadex') {281 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);282 } else {283 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash);284 }285 });286287 });288 }289290 async sendUnqBack(291 networkName: keyof typeof NETWORKS,292 sudoer: IKeyringPair,293 randomAccountOnUnq: IKeyringPair,294 ) {295 const networkUrl = mapToChainUrl(networkName);296297 const targetPlayground = getDevPlayground(networkName);298 await usingPlaygrounds(async (helper) => {299300 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(301 randomAccountOnUnq.addressRaw,302 {303 Concrete: {304 parents: 1,305 interior: {306 X1: {Parachain: this._getNativeId()},307 },308 },309 },310 SENDBACK_AMOUNT,311 );312313 let xcmProgramSent: any;314315316 await targetPlayground(networkUrl, async (helper) => {317 if('getSudo' in helper) {318 await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), xcmProgram);319 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);320 } else if('fastDemocracy' in helper) {321 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), xcmProgram]);322 // Needed to bypass the call filter.323 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);324 await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);325 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);326 }327 });328329 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);330331 this._balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);332333 expect(this._balanceUniqueTokenFinal).to.be.equal(this._balanceUniqueTokenInit - this._unqFees - STAYED_ON_TARGET_CHAIN);334335 });336 }337338 async sendOnlyOwnedBalance(339 networkName: keyof typeof NETWORKS,340 sudoer: IKeyringPair,341 ) {342 const networkUrl = mapToChainUrl(networkName);343 const targetPlayground = getDevPlayground(networkName);344345 const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);346347 await usingPlaygrounds(async (helper) => {348 const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));349 await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);350 const moreThanTargetChainHas = 2n * targetChainBalance;351352 const targetAccount = helper.arrange.createEmptyAccount();353354 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(355 targetAccount.addressRaw,356 {357 Concrete: {358 parents: 0,359 interior: 'Here',360 },361 },362 moreThanTargetChainHas,363 );364365 let maliciousXcmProgramSent: any;366367368 await targetPlayground(networkUrl, async (helper) => {369 if('getSudo' in helper) {370 await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgram);371 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);372 } else if('fastDemocracy' in helper) {373 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgram]);374 // Needed to bypass the call filter.375 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);376 await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);377 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);378 }379 });380381 await expectFailedToTransact(helper, maliciousXcmProgramSent);382383 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);384 expect(targetAccountBalance).to.be.equal(0n);385 });386 }387388 async reserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) {389 const networkUrl = mapToChainUrl(netwokrName);390 const targetPlayground = getDevPlayground(netwokrName);391392 await usingPlaygrounds(async (helper) => {393 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);394 const targetAccount = helper.arrange.createEmptyAccount();395396 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(397 targetAccount.addressRaw,398 {399 Concrete: {400 parents: 1,401 interior: {402 X1: {403 Parachain: this._getNativeId(),404 },405 },406 },407 },408 testAmount,409 );410411 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(412 targetAccount.addressRaw,413 {414 Concrete: {415 parents: 0,416 interior: 'Here',417 },418 },419 testAmount,420 );421422 let maliciousXcmProgramFullIdSent: any;423 let maliciousXcmProgramHereIdSent: any;424 const maxWaitBlocks = 3;425426 // Try to trick Unique using full UNQ identification427 await targetPlayground(networkUrl, async (helper) => {428 if('getSudo' in helper) {429 await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId);430 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);431 }432 // Moonbeam case433 else if('fastDemocracy' in helper) {434 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId]);435 // Needed to bypass the call filter.436 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);437 await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using path asset identification`,batchCall);438439 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);440 }441 });442443444 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);445446 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);447 expect(accountBalance).to.be.equal(0n);448449 // Try to trick Unique using shortened UNQ identification450 await targetPlayground(networkUrl, async (helper) => {451 if('getSudo' in helper) {452 await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), maliciousXcmProgramHereId);453 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);454 }455 else if('fastDemocracy' in helper) {456 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramHereId]);457 // Needed to bypass the call filter.458 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);459 await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall);460461 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);462 }463 });464465 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);466467 accountBalance = await helper.balance.getSubstrate(targetAccount.address);468 expect(accountBalance).to.be.equal(0n);469 });470 }471472 async rejectNativeTokensFrom(networkName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {473 const networkUrl = mapToChainUrl(networkName);474 const targetPlayground = getDevPlayground(networkName);475 let messageSent: any;476477 await usingPlaygrounds(async (helper) => {478 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(479 helper.arrange.createEmptyAccount().addressRaw,480 {481 Concrete: {482 parents: 1,483 interior: {484 X1: {485 Parachain: mapToChainId(networkName),486 },487 },488 },489 },490 TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,491 );492 await targetPlayground(networkUrl, async (helper) => {493 if('getSudo' in helper) {494 await helper.getSudo().xcm.send(sudoerOnTargetChain, this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId);495 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);496 } else if('fastDemocracy' in helper) {497 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), maliciousXcmProgramFullId]);498 // Needed to bypass the call filter.499 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);500 await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall);501502 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);503 }504 });505 await expectFailedToTransact(helper, messageSent);506 });507 }508509 private async _relayXcmTransactSetStorage(variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch') {510 // eslint-disable-next-line require-await511 return await usingPlaygrounds(async (helper) => {512 const relayForceKV = () => {513 const random = Math.random();514 const key = `relay-forced-key (instance: ${random})`;515 const val = `relay-forced-value (instance: ${random})`;516 const call = helper.constructApiCall('api.tx.system.setStorage', [[[key, val]]]).method.toHex();517518 return {519 call,520 key,521 val,522 };523 };524525 if(variant == 'plain') {526 const kv = relayForceKV();527 return {528 program: helper.arrange.makeTransactProgram({529 weightMultiplier: 1,530 call: kv.call,531 }),532 kvs: [kv],533 };534 } else {535 const kv0 = relayForceKV();536 const kv1 = relayForceKV();537538 const batchCall = helper.constructApiCall(`api.tx.utility.${variant}`, [[kv0.call, kv1.call]]).method.toHex();539 return {540 program: helper.arrange.makeTransactProgram({541 weightMultiplier: 2,542 call: batchCall,543 }),544 kvs: [kv0, kv1],545 };546 }547 });548 }549550 async relayIsPermittedToSetStorage(relaySudoer: IKeyringPair, variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch') {551 const {program, kvs} = await this._relayXcmTransactSetStorage(variant);552553 await usingRelayPlaygrounds(relayUrl, async (helper) => {554 await helper.getSudo().executeExtrinsic(relaySudoer, 'api.tx.xcmPallet.send', [555 this.uniqueChainMultilocationForRelay(),556 program,557 ]);558 });559560 await usingPlaygrounds(async (helper) => {561 await expectDownwardXcmComplete(helper);562563 for(const kv of kvs) {564 const forcedValue = await helper.callRpc('api.rpc.state.getStorage', [kv.key]);565 expect(hexToString(forcedValue.toHex())).to.be.equal(kv.val);566 }567 });568 }569570 private async _relayXcmTransactSetBalance(variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch' | 'dispatchAs') {571 // eslint-disable-next-line require-await572 return await usingPlaygrounds(async (helper) => {573 const emptyAccount = helper.arrange.createEmptyAccount().address;574575 const forceSetBalanceCall = helper.constructApiCall('api.tx.balances.forceSetBalance', [emptyAccount, 10_000n]).method.toHex();576577 let call;578579 if(variant == 'plain') {580 call = forceSetBalanceCall;581582 } else if(variant == 'dispatchAs') {583 call = helper.constructApiCall('api.tx.utility.dispatchAs', [584 {585 system: 'Root',586 },587 forceSetBalanceCall,588 ]).method.toHex();589 } else {590 call = helper.constructApiCall(`api.tx.utility.${variant}`, [[forceSetBalanceCall]]).method.toHex();591 }592593 return {594 program: helper.arrange.makeTransactProgram({595 weightMultiplier: 1,596 call,597 }),598 emptyAccount,599 };600 });601 }602603 async relayIsNotPermittedToSetBalance(604 relaySudoer: IKeyringPair,605 variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch' | 'dispatchAs',606 ) {607 const {program, emptyAccount} = await this._relayXcmTransactSetBalance(variant);608609 await usingRelayPlaygrounds(relayUrl, async (helper) => {610 await helper.getSudo().executeExtrinsic(relaySudoer, 'api.tx.xcmPallet.send', [611 this.uniqueChainMultilocationForRelay(),612 program,613 ]);614 });615616 await usingPlaygrounds(async (helper) => {617 await expectDownwardXcmNoPermission(helper);618 expect(await helper.balance.getSubstrate(emptyAccount)).to.be.equal(0n);619 });620 }621}tests/src/xcm/xcmQuartz.test.tsdiffbeforeafterboth--- a/tests/src/xcm/xcmQuartz.test.ts
+++ b/tests/src/xcm/xcmQuartz.test.ts
@@ -18,6 +18,7 @@
import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, usingShidenPlaygrounds} from '../util';
import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
import {STATEMINE_CHAIN, QUARTZ_CHAIN, KARURA_CHAIN, MOONRIVER_CHAIN, SHIDEN_CHAIN, STATEMINE_DECIMALS, KARURA_DECIMALS, QTZ_DECIMALS, RELAY_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, relayUrl, shidenUrl, statemineUrl} from './xcm.types';
+import {hexToString} from '@polkadot/util';
@@ -483,7 +484,14 @@
minimalBalance: 1000000000000000000n,
};
- await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);
+ const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v]: [any, any]) =>
+ hexToString(v.toJSON()['symbol'])) as string[];
+
+ if(!assets.includes('QTZ')) {
+ await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);
+ } else {
+ console.log('QTZ token already registered on Karura assetRegistry pallet');
+ }
await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);
balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);
balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});
@@ -969,19 +977,22 @@
const unitsPerSecond = 1n;
const numAssetsWeightHint = 0;
- const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
- location: quartzAssetLocation,
- metadata: quartzAssetMetadata,
- existentialDeposit,
- isSufficient,
- unitsPerSecond,
- numAssetsWeightHint,
- });
-
- console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);
+ if((await helper.assetManager.assetTypeId(quartzAssetLocation)).toJSON()) {
+ console.log('Quartz asset already registered on Moonriver');
+ } else {
+ const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
+ location: quartzAssetLocation,
+ metadata: quartzAssetMetadata,
+ existentialDeposit,
+ isSufficient,
+ unitsPerSecond,
+ numAssetsWeightHint,
+ });
- await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);
+ console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);
+ await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);
+ }
// >>> Acquire Quartz AssetId Info on Moonriver >>>
console.log('Acquire Quartz AssetId Info on Moonriver.......');
@@ -1298,40 +1309,43 @@
});
await usingShidenPlaygrounds(shidenUrl, async (helper) => {
- console.log('1. Create foreign asset and metadata');
- // TODO update metadata with values from production
- await helper.assets.create(
- alice,
- QTZ_ASSET_ID_ON_SHIDEN,
- alice.address,
- QTZ_MINIMAL_BALANCE_ON_SHIDEN,
- );
+ if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) {
+ console.log('1. Create foreign asset and metadata');
+ // TODO update metadata with values from production
+ await helper.assets.create(
+ alice,
+ QTZ_ASSET_ID_ON_SHIDEN,
+ alice.address,
+ QTZ_MINIMAL_BALANCE_ON_SHIDEN,
+ );
- await helper.assets.setMetadata(
- alice,
- QTZ_ASSET_ID_ON_SHIDEN,
- 'Cross chain QTZ',
- 'xcQTZ',
- Number(QTZ_DECIMALS),
- );
+ await helper.assets.setMetadata(
+ alice,
+ QTZ_ASSET_ID_ON_SHIDEN,
+ 'Cross chain QTZ',
+ 'xcQTZ',
+ Number(QTZ_DECIMALS),
+ );
- console.log('2. Register asset location on Shiden');
- const assetLocation = {
- V2: {
- parents: 1,
- interior: {
- X1: {
- Parachain: QUARTZ_CHAIN,
+ console.log('2. Register asset location on Shiden');
+ const assetLocation = {
+ V2: {
+ parents: 1,
+ interior: {
+ X1: {
+ Parachain: QUARTZ_CHAIN,
+ },
},
},
- },
- };
+ };
- await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);
-
- console.log('3. Set QTZ payment for XCM execution on Shiden');
- await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);
+ await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);
+ console.log('3. Set QTZ payment for XCM execution on Shiden');
+ await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);
+ } else {
+ console.log('QTZ is already registered on Shiden');
+ }
console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');
await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);
});