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.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import config from '../config';19import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, requirePalletsOrSkip, Pallets} from '../util';20import {Event} from '../util/playgrounds/unique.dev';21import {nToBigInt} from '@polkadot/util';22import {hexToString} from '@polkadot/util';23import {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';242526const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;27const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;28const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;29const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;30const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;3132let balanceUniqueTokenInit: bigint;33let balanceUniqueTokenMiddle: bigint;34let balanceUniqueTokenFinal: bigint;35let unqFees: bigint;3637const testHelper = new XcmTestHelper('unique');3839async function genericSendUnqTo(40 networkName: keyof typeof NETWORKS,41 randomAccount: IKeyringPair,42 randomAccountOnTargetChain = randomAccount,43) {44 const networkUrl = mapToChainUrl(networkName);45 const targetPlayground = getDevPlayground(networkName);46 await usingPlaygrounds(async (helper) => {47 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);48 const destination = {49 V2: {50 parents: 1,51 interior: {52 X1: {53 Parachain: mapToChainId(networkName),54 },55 },56 },57 };5859 const beneficiary = {60 V2: {61 parents: 0,62 interior: {63 X1: (64 networkName == 'moonbeam' ?65 {66 AccountKey20: {67 network: 'Any',68 key: randomAccountOnTargetChain.address,69 },70 }71 :72 {73 AccountId32: {74 network: 'Any',75 id: randomAccountOnTargetChain.addressRaw,76 },77 }78 ),79 },80 },81 };8283 const assets = {84 V2: [85 {86 id: {87 Concrete: {88 parents: 0,89 interior: 'Here',90 },91 },92 fun: {93 Fungible: TRANSFER_AMOUNT,94 },95 },96 ],97 };98 const feeAssetItem = 0;99100 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');101 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);102 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);103104 unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;105 console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(unqFees));106 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;107108 await targetPlayground(networkUrl, async (helper) => {109 /*110 Since only the parachain part of the Polkadex111 infrastructure is launched (without their112 solochain validators), processing incoming113 assets will lead to an error.114 This error indicates that the Polkadex chain115 received a message from the Unique network,116 since the hash is being checked to ensure117 it matches what was sent.118 */119 if(networkName == 'polkadex') {120 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);121 } else {122 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash);123 }124 });125126 });127}128129async function genericSendUnqBack(130 networkName: keyof typeof NETWORKS,131 sudoer: IKeyringPair,132 randomAccountOnUnq: IKeyringPair,133) {134 const networkUrl = mapToChainUrl(networkName);135136 const targetPlayground = getDevPlayground(networkName);137 await usingPlaygrounds(async (helper) => {138139 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(140 randomAccountOnUnq.addressRaw,141 uniqueAssetId,142 SENDBACK_AMOUNT,143 );144145 let xcmProgramSent: any;146147148 await targetPlayground(networkUrl, async (helper) => {149 if('getSudo' in helper) {150 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, xcmProgram);151 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);152 } else if('fastDemocracy' in helper) {153 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, xcmProgram]);154 // Needed to bypass the call filter.155 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);156 await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);157 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);158 }159 });160161 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);162163 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);164165 expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees - STAYED_ON_TARGET_CHAIN);166167 });168}169170async function genericSendOnlyOwnedBalance(171 networkName: keyof typeof NETWORKS,172 sudoer: IKeyringPair,173) {174 const networkUrl = mapToChainUrl(networkName);175 const targetPlayground = getDevPlayground(networkName);176177 const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);178179 await usingPlaygrounds(async (helper) => {180 const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));181 await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);182 const moreThanTargetChainHas = 2n * targetChainBalance;183184 const targetAccount = helper.arrange.createEmptyAccount();185186 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(187 targetAccount.addressRaw,188 {189 Concrete: {190 parents: 0,191 interior: 'Here',192 },193 },194 moreThanTargetChainHas,195 );196197 let maliciousXcmProgramSent: any;198199200 await targetPlayground(networkUrl, async (helper) => {201 if('getSudo' in helper) {202 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgram);203 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);204 } else if('fastDemocracy' in helper) {205 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);206 // Needed to bypass the call filter.207 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);208 await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);209 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);210 }211 });212213 await expectFailedToTransact(helper, maliciousXcmProgramSent);214215 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);216 expect(targetAccountBalance).to.be.equal(0n);217 });218}219220async function genericReserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) {221 const networkUrl = mapToChainUrl(netwokrName);222 const targetPlayground = getDevPlayground(netwokrName);223224 await usingPlaygrounds(async (helper) => {225 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);226 const targetAccount = helper.arrange.createEmptyAccount();227228 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(229 targetAccount.addressRaw,230 uniqueAssetId,231 testAmount,232 );233234 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(235 targetAccount.addressRaw,236 {237 Concrete: {238 parents: 0,239 interior: 'Here',240 },241 },242 testAmount,243 );244245 let maliciousXcmProgramFullIdSent: any;246 let maliciousXcmProgramHereIdSent: any;247 const maxWaitBlocks = 3;248249 // Try to trick Unique using full UNQ identification250 await targetPlayground(networkUrl, async (helper) => {251 if('getSudo' in helper) {252 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramFullId);253 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);254 }255 // Moonbeam case256 else if('fastDemocracy' in helper) {257 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);258 // Needed to bypass the call filter.259 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);260 await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using path asset identification`,batchCall);261262 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);263 }264 });265266267 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);268269 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);270 expect(accountBalance).to.be.equal(0n);271272 // Try to trick Unique using shortened UNQ identification273 await targetPlayground(networkUrl, async (helper) => {274 if('getSudo' in helper) {275 await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramHereId);276 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);277 }278 else if('fastDemocracy' in helper) {279 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);280 // Needed to bypass the call filter.281 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);282 await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall);283284 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);285 }286 });287288 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);289290 accountBalance = await helper.balance.getSubstrate(targetAccount.address);291 expect(accountBalance).to.be.equal(0n);292 });293}294295async function genericRejectNativeTokensFrom(networkName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {296 const networkUrl = mapToChainUrl(networkName);297 const targetPlayground = getDevPlayground(networkName);298 let messageSent: any;299300 await usingPlaygrounds(async (helper) => {301 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(302 helper.arrange.createEmptyAccount().addressRaw,303 {304 Concrete: {305 parents: 1,306 interior: {307 X1: {308 Parachain: mapToChainId(networkName),309 },310 },311 },312 },313 TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,314 );315 await targetPlayground(networkUrl, async (helper) => {316 if('getSudo' in helper) {317 await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueVersionedMultilocation, maliciousXcmProgramFullId);318 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);319 } else if('fastDemocracy' in helper) {320 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);321 // Needed to bypass the call filter.322 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);323 await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall);324325 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);326 }327 });328 await expectFailedToTransact(helper, messageSent);329 });330}331332333describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => {334 let alice: IKeyringPair;335 let randomAccount: IKeyringPair;336337 before(async () => {338 await usingPlaygrounds(async (helper, privateKey) => {339 alice = await privateKey('//Alice');340 console.log(config.acalaUrl);341 randomAccount = helper.arrange.createEmptyAccount();342343 // Set the default version to wrap the first message to other chains.344 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);345 });346347 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {348 const destination = {349 V2: {350 parents: 1,351 interior: {352 X1: {353 Parachain: UNIQUE_CHAIN,354 },355 },356 },357 };358359 const metadata = {360 name: 'Unique Network',361 symbol: 'UNQ',362 decimals: 18,363 minimalBalance: 1250_000_000_000_000_000n,364 };365 const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>366 hexToString(v.toJSON()['symbol'])) as string[];367368 if(!assets.includes('UNQ')) {369 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);370 } else {371 console.log('UNQ token already registered on Acala assetRegistry pallet');372 }373 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);374 });375376 await usingPlaygrounds(async (helper) => {377 await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);378 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);379 });380 });381382 itSub('Should connect and send UNQ to Acala', async () => {383 await genericSendUnqTo('acala', randomAccount);384 });385386 itSub('Should connect to Acala and send UNQ back', async () => {387 await genericSendUnqBack('acala', alice, randomAccount);388 });389390 itSub('Acala can send only up to its balance', async () => {391 await genericSendOnlyOwnedBalance('acala', alice);392 });393394 itSub('Should not accept reserve transfer of UNQ from Acala', async () => {395 await genericReserveTransferUNQfrom('acala', alice);396 });397});398399describeXCM('[XCMLL] Integration test: Exchanging tokens with Polkadex', () => {400 let alice: IKeyringPair;401 let randomAccount: IKeyringPair;402403 before(async () => {404 await usingPlaygrounds(async (helper, privateKey) => {405 alice = await privateKey('//Alice');406 randomAccount = helper.arrange.createEmptyAccount();407408 // Set the default version to wrap the first message to other chains.409 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);410 });411412 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {413 const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))414 .toJSON() as [])415 .map(nToBigInt).length != 0;416 /*417 Check whether the Unique token has been added418 to the whitelist, since an error will occur419 if it is added again. Needed for debugging420 when this test is run multiple times.421 */422 if(isWhitelisted) {423 console.log('UNQ token is already whitelisted on Polkadex');424 } else {425 await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);426 }427428 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);429 });430431 await usingPlaygrounds(async (helper) => {432 await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);433 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);434 });435 });436437 itSub('Should connect and send UNQ to Polkadex', async () => {438 await testHelper.sendUnqTo('polkadex', randomAccount);439 });440441442 itSub('Should connect to Polkadex and send UNQ back', async () => {443 await testHelper.sendUnqBack('polkadex', alice, randomAccount);444 });445446 itSub('Polkadex can send only up to its balance', async () => {447 await testHelper.sendOnlyOwnedBalance('polkadex', alice);448 });449450 itSub('Should not accept reserve transfer of UNQ from Polkadex', async () => {451 await testHelper.reserveTransferUNQfrom('polkadex', alice);452 });453});454455// These tests are relevant only when456// the the corresponding foreign assets are not registered457describeXCM('[XCMLL] Integration test: Unique rejects non-native tokens', () => {458 let alice: IKeyringPair;459460 before(async () => {461 await usingPlaygrounds(async (helper, privateKey) => {462 alice = await privateKey('//Alice');463464 // Set the default version to wrap the first message to other chains.465 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);466 });467 });468469 itSub('Unique rejects ACA tokens from Acala', async () => {470 await genericRejectNativeTokensFrom('acala', alice);471 });472473 itSub('Unique rejects GLMR tokens from Moonbeam', async () => {474 await genericRejectNativeTokensFrom('moonbeam', alice);475 });476477 itSub('Unique rejects ASTR tokens from Astar', async () => {478 await genericRejectNativeTokensFrom('astar', alice);479 });480481 itSub('Unique rejects PDX tokens from Polkadex', async () => {482 await genericRejectNativeTokensFrom('polkadex', alice);483 });484});485486describeXCM('[XCMLL] Integration test: Exchanging UNQ with Moonbeam', () => {487 // Unique constants488 let alice: IKeyringPair;489 let uniqueAssetLocation;490491 let randomAccountUnique: IKeyringPair;492 let randomAccountMoonbeam: IKeyringPair;493494 // Moonbeam constants495 let assetId: string;496497 const uniqueAssetMetadata = {498 name: 'xcUnique',499 symbol: 'xcUNQ',500 decimals: 18,501 isFrozen: false,502 minimalBalance: 1n,503 };504505506 before(async () => {507 await usingPlaygrounds(async (helper, privateKey) => {508 alice = await privateKey('//Alice');509 [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);510511512 // Set the default version to wrap the first message to other chains.513 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);514 });515516 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {517 const alithAccount = helper.account.alithAccount();518 const baltatharAccount = helper.account.baltatharAccount();519 const dorothyAccount = helper.account.dorothyAccount();520521 randomAccountMoonbeam = helper.account.create();522523 // >>> Sponsoring Dorothy >>>524 console.log('Sponsoring Dorothy.......');525 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);526 console.log('Sponsoring Dorothy.......DONE');527 // <<< Sponsoring Dorothy <<<528 uniqueAssetLocation = {529 XCM: {530 parents: 1,531 interior: {X1: {Parachain: UNIQUE_CHAIN}},532 },533 };534 const existentialDeposit = 1n;535 const isSufficient = true;536 const unitsPerSecond = 1n;537 const numAssetsWeightHint = 0;538539 if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {540 console.log('Unique asset already registered on Moonbeam');541 } else {542 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({543 location: uniqueAssetLocation,544 metadata: uniqueAssetMetadata,545 existentialDeposit,546 isSufficient,547 unitsPerSecond,548 numAssetsWeightHint,549 });550551 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);552553 await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);554 }555556 // >>> Acquire Unique AssetId Info on Moonbeam >>>557 console.log('Acquire Unique AssetId Info on Moonbeam.......');558559 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();560561 console.log('UNQ asset ID is %s', assetId);562 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');563564 // >>> Sponsoring random Account >>>565 console.log('Sponsoring random Account.......');566 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);567 console.log('Sponsoring random Account.......DONE');568 // <<< Sponsoring random Account <<<569 });570571 await usingPlaygrounds(async (helper) => {572 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, SENDER_BUDGET);573 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);574 });575 });576577 itSub('Should connect and send UNQ to Moonbeam', async () => {578 await testHelper.sendUnqTo('moonbeam', randomAccountUnique, randomAccountMoonbeam);579 });580581 itSub('Should connect to Moonbeam and send UNQ back', async () => {582 await testHelper.sendUnqBack('moonbeam', alice, randomAccountUnique);583 });584585 itSub('Moonbeam can send only up to its balance', async () => {586 await testHelper.sendOnlyOwnedBalance('moonbeam', alice);587 });588589 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async () => {590 await testHelper.reserveTransferUNQfrom('moonbeam', alice);591 });592});593594describeXCM('[XCMLL] Integration test: Exchanging tokens with Astar', () => {595 let alice: IKeyringPair;596 let randomAccount: IKeyringPair;597598 const UNQ_ASSET_ID_ON_ASTAR = 1;599 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;600601 // Unique -> Astar602 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.603 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?604605 before(async () => {606 await usingPlaygrounds(async (helper, privateKey) => {607 alice = await privateKey('//Alice');608 randomAccount = helper.arrange.createEmptyAccount();609 await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);610 console.log('randomAccount', randomAccount.address);611612 // Set the default version to wrap the first message to other chains.613 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);614 });615616 await usingAstarPlaygrounds(astarUrl, async (helper) => {617 if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {618 console.log('1. Create foreign asset and metadata');619 // TODO update metadata with values from production620 await helper.assets.create(621 alice,622 UNQ_ASSET_ID_ON_ASTAR,623 alice.address,624 UNQ_MINIMAL_BALANCE_ON_ASTAR,625 );626627 await helper.assets.setMetadata(628 alice,629 UNQ_ASSET_ID_ON_ASTAR,630 'Cross chain UNQ',631 'xcUNQ',632 Number(UNQ_DECIMALS),633 );634635 console.log('2. Register asset location on Astar');636 const assetLocation = {637 V2: {638 parents: 1,639 interior: {640 X1: {641 Parachain: UNIQUE_CHAIN,642 },643 },644 },645 };646647 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);648649 console.log('3. Set UNQ payment for XCM execution on Astar');650 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);651 } else {652 console.log('UNQ is already registered on Astar');653 }654 console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');655 await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);656 });657 });658659 itSub('Should connect and send UNQ to Astar', async () => {660 await genericSendUnqTo('astar', randomAccount);661 });662663 itSub('Should connect to Astar and send UNQ back', async () => {664 await genericSendUnqBack('astar', alice, randomAccount);665 });666667 itSub('Astar can send only up to its balance', async () => {668 await genericSendOnlyOwnedBalance('astar', alice);669 });670671 itSub('Should not accept reserve transfer of UNQ from Astar', async () => {672 await genericReserveTransferUNQfrom('astar', alice);673 });674});675676describeXCM('[XCMLL] Integration test: The relay can do some root ops', () => {677 let sudoer: IKeyringPair;678679 before(async function () {680 await usingRelayPlaygrounds(relayUrl, async (_, privateKey) => {681 sudoer = await privateKey('//Alice');682 });683 });684685 // At the moment there is no reliable way686 // to establish the correspondence between the `ExecutedDownward` event687 // and the relay's sent message due to `SetTopic` instruction688 // containing an unpredictable topic silently added by the relay on the router level.689 // This changes the message hash on arrival to our chain.690 //691 // See:692 // * The relay's router: https://github.com/paritytech/polkadot-sdk/blob/f60318f68687e601c47de5ad5ca88e2c3f8139a7/polkadot/runtime/westend/src/xcm_config.rs#L83693 // * The `WithUniqueTopic` helper: https://github.com/paritytech/polkadot-sdk/blob/945ebbbcf66646be13d5b1d1bc26c8b0d3296d9e/polkadot/xcm/xcm-builder/src/routing.rs#L36694 //695 // Because of this, we insert time gaps between tests so696 // different `ExecutedDownward` events won't interfere with each other.697 afterEach(async () => {698 await usingPlaygrounds(async (helper) => {699 await helper.wait.newBlocks(3);700 });701 });702703 itSub('The relay can set storage', async () => {704 await testHelper.relayIsPermittedToSetStorage(sudoer, 'plain');705 });706707 itSub('The relay can batch set storage', async () => {708 await testHelper.relayIsPermittedToSetStorage(sudoer, 'batch');709 });710711 itSub('The relay can batchAll set storage', async () => {712 await testHelper.relayIsPermittedToSetStorage(sudoer, 'batchAll');713 });714715 itSub('The relay can forceBatch set storage', async () => {716 await testHelper.relayIsPermittedToSetStorage(sudoer, 'forceBatch');717 });718719 itSub('[negative] The relay cannot set balance', async () => {720 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'plain');721 });722723 itSub('[negative] The relay cannot set balance via batch', async () => {724 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batch');725 });726727 itSub('[negative] The relay cannot set balance via batchAll', async () => {728 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batchAll');729 });730731 itSub('[negative] The relay cannot set balance via forceBatch', async () => {732 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'forceBatch');733 });734735 itSub('[negative] The relay cannot set balance via dispatchAs', async () => {736 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'dispatchAs');737 });738});1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import config from '../config';19import {itSub, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds} from '../util';20import {nToBigInt} from '@polkadot/util';21import {hexToString} from '@polkadot/util';22import {ASTAR_DECIMALS, SAFE_XCM_VERSION, SENDER_BUDGET, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl, moonbeamUrl, polkadexUrl, relayUrl, uniqueAssetId} from './xcm.types';2324const testHelper = new XcmTestHelper('unique');2526272829describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => {30 let alice: IKeyringPair;31 let randomAccount: IKeyringPair;3233 before(async () => {34 await usingPlaygrounds(async (helper, privateKey) => {35 alice = await privateKey('//Alice');36 console.log(config.acalaUrl);37 randomAccount = helper.arrange.createEmptyAccount();3839 // Set the default version to wrap the first message to other chains.40 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);41 });4243 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {44 const destination = {45 V2: {46 parents: 1,47 interior: {48 X1: {49 Parachain: UNIQUE_CHAIN,50 },51 },52 },53 };5455 const metadata = {56 name: 'Unique Network',57 symbol: 'UNQ',58 decimals: 18,59 minimalBalance: 1250_000_000_000_000_000n,60 };61 const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>62 hexToString(v.toJSON()['symbol'])) as string[];6364 if(!assets.includes('UNQ')) {65 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);66 } else {67 console.log('UNQ token already registered on Acala assetRegistry pallet');68 }69 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);70 });7172 await usingPlaygrounds(async (helper) => {73 await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);74 });75 });7677 itSub('Should connect and send UNQ to Acala', async () => {78 await testHelper.sendUnqTo('acala', randomAccount);79 });8081 itSub('Should connect to Acala and send UNQ back', async () => {82 await testHelper.sendUnqBack('acala', alice, randomAccount);83 });8485 itSub('Acala can send only up to its balance', async () => {86 await testHelper.sendOnlyOwnedBalance('acala', alice);87 });8889 itSub('Should not accept reserve transfer of UNQ from Acala', async () => {90 await testHelper.reserveTransferUNQfrom('acala', alice);91 });92});9394describeXCM('[XCMLL] Integration test: Exchanging tokens with Polkadex', () => {95 let alice: IKeyringPair;96 let randomAccount: IKeyringPair;9798 before(async () => {99 await usingPlaygrounds(async (helper, privateKey) => {100 alice = await privateKey('//Alice');101 randomAccount = helper.arrange.createEmptyAccount();102103 // Set the default version to wrap the first message to other chains.104 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);105 });106107 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {108 const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))109 .toJSON() as [])110 .map(nToBigInt).length != 0;111 /*112 Check whether the Unique token has been added113 to the whitelist, since an error will occur114 if it is added again. Needed for debugging115 when this test is run multiple times.116 */117 if(isWhitelisted) {118 console.log('UNQ token is already whitelisted on Polkadex');119 } else {120 await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);121 }122123 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);124 });125126 await usingPlaygrounds(async (helper) => {127 await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);128 });129 });130131 itSub('Should connect and send UNQ to Polkadex', async () => {132 await testHelper.sendUnqTo('polkadex', randomAccount);133 });134135136 itSub('Should connect to Polkadex and send UNQ back', async () => {137 await testHelper.sendUnqBack('polkadex', alice, randomAccount);138 });139140 itSub('Polkadex can send only up to its balance', async () => {141 await testHelper.sendOnlyOwnedBalance('polkadex', alice);142 });143144 itSub('Should not accept reserve transfer of UNQ from Polkadex', async () => {145 await testHelper.reserveTransferUNQfrom('polkadex', alice);146 });147});148149// These tests are relevant only when150// the the corresponding foreign assets are not registered151describeXCM('[XCMLL] Integration test: Unique rejects non-native tokens', () => {152 let alice: IKeyringPair;153154 before(async () => {155 await usingPlaygrounds(async (helper, privateKey) => {156 alice = await privateKey('//Alice');157158 // Set the default version to wrap the first message to other chains.159 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);160 });161 });162163 itSub('Unique rejects ACA tokens from Acala', async () => {164 await testHelper.rejectNativeTokensFrom('acala', alice);165 });166167 itSub('Unique rejects GLMR tokens from Moonbeam', async () => {168 await testHelper.rejectNativeTokensFrom('moonbeam', alice);169 });170171 itSub('Unique rejects ASTR tokens from Astar', async () => {172 await testHelper.rejectNativeTokensFrom('astar', alice);173 });174175 itSub('Unique rejects PDX tokens from Polkadex', async () => {176 await testHelper.rejectNativeTokensFrom('polkadex', alice);177 });178});179180describeXCM('[XCMLL] Integration test: Exchanging UNQ with Moonbeam', () => {181 // Unique constants182 let alice: IKeyringPair;183 let uniqueAssetLocation;184185 let randomAccountUnique: IKeyringPair;186 let randomAccountMoonbeam: IKeyringPair;187188 // Moonbeam constants189 let assetId: string;190191 const uniqueAssetMetadata = {192 name: 'xcUnique',193 symbol: 'xcUNQ',194 decimals: 18,195 isFrozen: false,196 minimalBalance: 1n,197 };198199200 before(async () => {201 await usingPlaygrounds(async (helper, privateKey) => {202 alice = await privateKey('//Alice');203 [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);204205206 // Set the default version to wrap the first message to other chains.207 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);208 });209210 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {211 const alithAccount = helper.account.alithAccount();212 const baltatharAccount = helper.account.baltatharAccount();213 const dorothyAccount = helper.account.dorothyAccount();214215 randomAccountMoonbeam = helper.account.create();216217 // >>> Sponsoring Dorothy >>>218 console.log('Sponsoring Dorothy.......');219 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);220 console.log('Sponsoring Dorothy.......DONE');221 // <<< Sponsoring Dorothy <<<222 uniqueAssetLocation = {223 XCM: {224 parents: 1,225 interior: {X1: {Parachain: UNIQUE_CHAIN}},226 },227 };228 const existentialDeposit = 1n;229 const isSufficient = true;230 const unitsPerSecond = 1n;231 const numAssetsWeightHint = 0;232233 if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {234 console.log('Unique asset already registered on Moonbeam');235 } else {236 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({237 location: uniqueAssetLocation,238 metadata: uniqueAssetMetadata,239 existentialDeposit,240 isSufficient,241 unitsPerSecond,242 numAssetsWeightHint,243 });244245 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);246247 await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);248 }249250 // >>> Acquire Unique AssetId Info on Moonbeam >>>251 console.log('Acquire Unique AssetId Info on Moonbeam.......');252253 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();254255 console.log('UNQ asset ID is %s', assetId);256 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');257258 // >>> Sponsoring random Account >>>259 console.log('Sponsoring random Account.......');260 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);261 console.log('Sponsoring random Account.......DONE');262 // <<< Sponsoring random Account <<<263 });264265 await usingPlaygrounds(async (helper) => {266 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, SENDER_BUDGET);267 });268 });269270 itSub('Should connect and send UNQ to Moonbeam', async () => {271 await testHelper.sendUnqTo('moonbeam', randomAccountUnique, randomAccountMoonbeam);272 });273274 itSub('Should connect to Moonbeam and send UNQ back', async () => {275 await testHelper.sendUnqBack('moonbeam', alice, randomAccountUnique);276 });277278 itSub('Moonbeam can send only up to its balance', async () => {279 await testHelper.sendOnlyOwnedBalance('moonbeam', alice);280 });281282 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async () => {283 await testHelper.reserveTransferUNQfrom('moonbeam', alice);284 });285});286287describeXCM('[XCMLL] Integration test: Exchanging tokens with Astar', () => {288 let alice: IKeyringPair;289 let randomAccount: IKeyringPair;290291 const UNQ_ASSET_ID_ON_ASTAR = 1;292 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;293294 // Unique -> Astar295 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.296 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?297298 before(async () => {299 await usingPlaygrounds(async (helper, privateKey) => {300 alice = await privateKey('//Alice');301 randomAccount = helper.arrange.createEmptyAccount();302 await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);303 console.log('randomAccount', randomAccount.address);304305 // Set the default version to wrap the first message to other chains.306 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);307 });308309 await usingAstarPlaygrounds(astarUrl, async (helper) => {310 if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {311 console.log('1. Create foreign asset and metadata');312 // TODO update metadata with values from production313 await helper.assets.create(314 alice,315 UNQ_ASSET_ID_ON_ASTAR,316 alice.address,317 UNQ_MINIMAL_BALANCE_ON_ASTAR,318 );319320 await helper.assets.setMetadata(321 alice,322 UNQ_ASSET_ID_ON_ASTAR,323 'Cross chain UNQ',324 'xcUNQ',325 Number(UNQ_DECIMALS),326 );327328 console.log('2. Register asset location on Astar');329 const assetLocation = {330 V2: {331 parents: 1,332 interior: {333 X1: {334 Parachain: UNIQUE_CHAIN,335 },336 },337 },338 };339340 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);341342 console.log('3. Set UNQ payment for XCM execution on Astar');343 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);344 } else {345 console.log('UNQ is already registered on Astar');346 }347 console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');348 await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);349 });350 });351352 itSub('Should connect and send UNQ to Astar', async () => {353 await testHelper.sendUnqTo('astar', randomAccount);354 });355356 itSub('Should connect to Astar and send UNQ back', async () => {357 await testHelper.sendUnqBack('astar', alice, randomAccount);358 });359360 itSub('Astar can send only up to its balance', async () => {361 await testHelper.sendOnlyOwnedBalance('astar', alice);362 });363364 itSub('Should not accept reserve transfer of UNQ from Astar', async () => {365 await testHelper.reserveTransferUNQfrom('astar', alice);366 });367});368369describeXCM('[XCMLL] Integration test: The relay can do some root ops', () => {370 let sudoer: IKeyringPair;371372 before(async function () {373 await usingRelayPlaygrounds(relayUrl, async (_, privateKey) => {374 sudoer = await privateKey('//Alice');375 });376 });377378 // At the moment there is no reliable way379 // to establish the correspondence between the `ExecutedDownward` event380 // and the relay's sent message due to `SetTopic` instruction381 // containing an unpredictable topic silently added by the relay on the router level.382 // This changes the message hash on arrival to our chain.383 //384 // See:385 // * The relay's router: https://github.com/paritytech/polkadot-sdk/blob/f60318f68687e601c47de5ad5ca88e2c3f8139a7/polkadot/runtime/westend/src/xcm_config.rs#L83386 // * The `WithUniqueTopic` helper: https://github.com/paritytech/polkadot-sdk/blob/945ebbbcf66646be13d5b1d1bc26c8b0d3296d9e/polkadot/xcm/xcm-builder/src/routing.rs#L36387 //388 // Because of this, we insert time gaps between tests so389 // different `ExecutedDownward` events won't interfere with each other.390 afterEach(async () => {391 await usingPlaygrounds(async (helper) => {392 await helper.wait.newBlocks(3);393 });394 });395396 itSub('The relay can set storage', async () => {397 await testHelper.relayIsPermittedToSetStorage(sudoer, 'plain');398 });399400 itSub('The relay can batch set storage', async () => {401 await testHelper.relayIsPermittedToSetStorage(sudoer, 'batch');402 });403404 itSub('The relay can batchAll set storage', async () => {405 await testHelper.relayIsPermittedToSetStorage(sudoer, 'batchAll');406 });407408 itSub('The relay can forceBatch set storage', async () => {409 await testHelper.relayIsPermittedToSetStorage(sudoer, 'forceBatch');410 });411412 itSub('[negative] The relay cannot set balance', async () => {413 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'plain');414 });415416 itSub('[negative] The relay cannot set balance via batch', async () => {417 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batch');418 });419420 itSub('[negative] The relay cannot set balance via batchAll', async () => {421 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batchAll');422 });423424 itSub('[negative] The relay cannot set balance via forceBatch', async () => {425 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'forceBatch');426 });427428 itSub('[negative] The relay cannot set balance via dispatchAs', async () => {429 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'dispatchAs');430 });431});tests/src/xcm/xcm.types.tsdiffbeforeafterboth--- a/tests/src/xcm/xcm.types.ts
+++ b/tests/src/xcm/xcm.types.ts
@@ -3,7 +3,6 @@
import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, usingShidenPlaygrounds} from '../util';
import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
import config from '../config';
-import {blake2AsHex} from '@polkadot/util-crypto';
export const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);
export const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);
@@ -136,10 +135,10 @@
}
export 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;
+export const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;
+export const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;
+export const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;
+export const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;
export class XcmTestHelper {
private _balanceUniqueTokenInit: bigint = 0n;
@@ -163,6 +162,7 @@
return UNIQUE_CHAIN;
}
}
+
private _isAddress20FormatFor(network: NetworkNames) {
switch (network) {
case 'moonbeam':
@@ -173,6 +173,19 @@
}
}
+ private _runtimeVersionedMultilocation() {
+ return {
+ V3: {
+ parents: 1,
+ interior: {
+ X1: {
+ Parachain: this._getNativeId(),
+ },
+ },
+ },
+ };
+ }
+
uniqueChainMultilocationForRelay() {
return {
V3: {
@@ -250,8 +263,8 @@
this._balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);
this._unqFees = this._balanceUniqueTokenInit - this._balanceUniqueTokenMiddle - TRANSFER_AMOUNT;
- console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(this._unqFees));
- expect(this._unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;
+ console.log('[%s -> %s] transaction fees: %s', this._nativeRuntime, networkName, helper.util.bigIntToDecimals(this._unqFees));
+ expect(this._unqFees > 0n, 'Negative fees, looks like nothing was transferred').to.be.true;
await targetPlayground(networkUrl, async (helper) => {
/*
@@ -302,10 +315,10 @@
await targetPlayground(networkUrl, async (helper) => {
if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, xcmProgram);
+ await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), 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]);
+ const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), 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);
@@ -354,10 +367,10 @@
await targetPlayground(networkUrl, async (helper) => {
if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgram);
+ await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), 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]);
+ const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), 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);
@@ -413,12 +426,12 @@
// 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);
+ await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), 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]);
+ const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), 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);
@@ -436,11 +449,11 @@
// 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);
+ await helper.getSudo().xcm.send(sudoer, this._runtimeVersionedMultilocation(), 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]);
+ const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), 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);
@@ -478,10 +491,10 @@
);
await targetPlayground(networkUrl, async (helper) => {
if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueVersionedMultilocation, maliciousXcmProgramFullId);
+ await helper.getSudo().xcm.send(sudoerOnTargetChain, this._runtimeVersionedMultilocation(), 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]);
+ const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [this._runtimeVersionedMultilocation(), 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);
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);
});