difftreelog
test(xcm qtz) added `XcmTestHelper`
in: master
5 files changed
tests/package.jsondiffbeforeafterboth--- a/tests/package.json
+++ b/tests/package.json
@@ -116,6 +116,8 @@
"testXcmUnique": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmUnique.test.ts",
"testFullXcmUnique": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/*Unique.test.ts",
"testXcmQuartz": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmQuartz.test.ts",
+ "testLowLevelXcmQuartz": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/lowLevelXcmQuartz.test.ts",
+ "testFullXcmQuartz": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/*Quartz.test.ts",
"testXcmOpal": "RUN_XCM_TESTS=1 yarn _test ./**/xcm/xcmOpal.test.ts",
"testXcmTransferAcala": "yarn _test ./**/xcm/xcmTransferAcala.test.ts acalaId=2000 uniqueId=5000",
"testXcmTransferStatemine": "yarn _test ./**/xcm/xcmTransferStatemine.test.ts statemineId=1000 uniqueId=5000",
tests/src/xcm/lowLevelXcmQuartz.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 {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingMoonriverPlaygrounds, usingShidenPlaygrounds} from '../util';19import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';20import {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';212223const testHelper = new XcmTestHelper('quartz');2425describeXCM('[XCMLL] Integration test: Exchanging tokens with Karura', () => {26 let alice: IKeyringPair;27 let randomAccount: IKeyringPair;2829 let balanceQuartzTokenInit: bigint;30 let balanceQuartzTokenMiddle: bigint;31 let balanceQuartzTokenFinal: bigint;32 let balanceKaruraTokenInit: bigint;33 let balanceKaruraTokenMiddle: bigint;34 let balanceKaruraTokenFinal: bigint;35 let balanceQuartzForeignTokenInit: bigint;36 let balanceQuartzForeignTokenMiddle: bigint;37 let balanceQuartzForeignTokenFinal: bigint;3839 // computed by a test transfer from prod Quartz to prod Karura.40 // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e941 // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)42 const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;43 const karuraEps = 8n * 10n ** 16n;4445 let karuraBackwardTransferAmount: bigint;4647 before(async () => {48 await usingPlaygrounds(async (helper, privateKey) => {49 alice = await privateKey('//Alice');50 [randomAccount] = await helper.arrange.createAccounts([0n], alice);5152 // Set the default version to wrap the first message to other chains.53 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);54 });5556 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {57 const destination = {58 V2: {59 parents: 1,60 interior: {61 X1: {62 Parachain: QUARTZ_CHAIN,63 },64 },65 },66 };6768 const metadata = {69 name: 'Quartz',70 symbol: 'QTZ',71 decimals: 18,72 minimalBalance: 1000000000000000000n,73 };7475 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);76 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);77 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);78 balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});79 });8081 await usingPlaygrounds(async (helper) => {82 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);83 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);84 });85 });8687 itSub('Should connect and send QTZ to Karura', async () => {88 await testHelper.sendUnqTo('karura', randomAccount);89 });9091 itSub('Should connect to Karura and send QTZ back', async () => {92 await testHelper.sendUnqBack('karura', alice, randomAccount);93 });9495 itSub('Karura can send only up to its balance', async () => {96 await testHelper.sendOnlyOwnedBalance('karura', alice);97 });98});99// These tests are relevant only when100// the the corresponding foreign assets are not registered101describeXCM('[XCMLL] Integration test: Quartz rejects non-native tokens', () => {102 let alice: IKeyringPair;103 let alith: IKeyringPair;104105 const testAmount = 100_000_000_000n;106 let quartzParachainJunction;107 let quartzAccountJunction;108109 let quartzParachainMultilocation: any;110 let quartzAccountMultilocation: any;111 let quartzCombinedMultilocation: any;112113 let messageSent: any;114115 const maxWaitBlocks = 3;116117 before(async () => {118 await usingPlaygrounds(async (helper, privateKey) => {119 alice = await privateKey('//Alice');120121 quartzParachainJunction = {Parachain: QUARTZ_CHAIN};122 quartzAccountJunction = {123 AccountId32: {124 network: 'Any',125 id: alice.addressRaw,126 },127 };128129 quartzParachainMultilocation = {130 V2: {131 parents: 1,132 interior: {133 X1: quartzParachainJunction,134 },135 },136 };137138 quartzAccountMultilocation = {139 V2: {140 parents: 0,141 interior: {142 X1: quartzAccountJunction,143 },144 },145 };146147 quartzCombinedMultilocation = {148 V2: {149 parents: 1,150 interior: {151 X2: [quartzParachainJunction, quartzAccountJunction],152 },153 },154 };155156 // Set the default version to wrap the first message to other chains.157 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);158 });159160 // eslint-disable-next-line require-await161 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {162 alith = helper.account.alithAccount();163 });164 });165166 itSub('Quartz rejects KAR tokens from Karura', async () => {167 await testHelper.rejectNativeTokensFrom('karura', alice);168 });169170 itSub('Quartz rejects MOVR tokens from Moonriver', async () => {171 await testHelper.rejectNativeTokensFrom('moonriver', alice);172 });173174 itSub('Quartz rejects SDN tokens from Shiden', async () => {175 await testHelper.rejectNativeTokensFrom('shiden', alice);176 });177});178179describeXCM('[XCMLL] Integration test: Exchanging QTZ with Moonriver', () => {180 // Quartz constants181 let alice: IKeyringPair;182 let quartzAssetLocation;183184 let randomAccountQuartz: IKeyringPair;185 let randomAccountMoonriver: IKeyringPair;186187 // Moonriver constants188 let assetId: string;189190 const quartzAssetMetadata = {191 name: 'xcQuartz',192 symbol: 'xcQTZ',193 decimals: 18,194 isFrozen: false,195 minimalBalance: 1n,196 };197198 let balanceQuartzTokenInit: bigint;199 let balanceQuartzTokenMiddle: bigint;200 let balanceQuartzTokenFinal: bigint;201 let balanceForeignQtzTokenInit: bigint;202 let balanceForeignQtzTokenMiddle: bigint;203 let balanceForeignQtzTokenFinal: bigint;204 let balanceMovrTokenInit: bigint;205 let balanceMovrTokenMiddle: bigint;206 let balanceMovrTokenFinal: bigint;207208 before(async () => {209 await usingPlaygrounds(async (helper, privateKey) => {210 alice = await privateKey('//Alice');211 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);212213 balanceForeignQtzTokenInit = 0n;214215 // Set the default version to wrap the first message to other chains.216 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);217 });218219 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {220 const alithAccount = helper.account.alithAccount();221 const baltatharAccount = helper.account.baltatharAccount();222 const dorothyAccount = helper.account.dorothyAccount();223224 randomAccountMoonriver = helper.account.create();225226 // >>> Sponsoring Dorothy >>>227 console.log('Sponsoring Dorothy.......');228 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);229 console.log('Sponsoring Dorothy.......DONE');230 // <<< Sponsoring Dorothy <<<231232 quartzAssetLocation = {233 XCM: {234 parents: 1,235 interior: {X1: {Parachain: QUARTZ_CHAIN}},236 },237 };238 const existentialDeposit = 1n;239 const isSufficient = true;240 const unitsPerSecond = 1n;241 const numAssetsWeightHint = 0;242243 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({244 location: quartzAssetLocation,245 metadata: quartzAssetMetadata,246 existentialDeposit,247 isSufficient,248 unitsPerSecond,249 numAssetsWeightHint,250 });251252 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);253254 await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);255256 // >>> Acquire Quartz AssetId Info on Moonriver >>>257 console.log('Acquire Quartz AssetId Info on Moonriver.......');258259 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();260261 console.log('QTZ asset ID is %s', assetId);262 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');263 // >>> Acquire Quartz AssetId Info on Moonriver >>>264265 // >>> Sponsoring random Account >>>266 console.log('Sponsoring random Account.......');267 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);268 console.log('Sponsoring random Account.......DONE');269 // <<< Sponsoring random Account <<<270271 balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);272 });273274 await usingPlaygrounds(async (helper) => {275 await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);276 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);277 });278 });279280 itSub('Should connect and send QTZ to Moonriver', async () => {281 await testHelper.sendUnqTo('moonriver', randomAccountQuartz, randomAccountMoonriver);282 });283284 itSub('Should connect to Moonriver and send QTZ back', async () => {285 await testHelper.sendUnqBack('moonriver', alice, randomAccountQuartz);286 });287288 itSub('Moonriver can send only up to its balance', async () => {289 await testHelper.sendOnlyOwnedBalance('moonriver', alice);290 });291292 itSub('Should not accept reserve transfer of QTZ from Moonriver', async () => {293 await testHelper.reserveTransferUNQfrom('moonriver', alice);294 });295});296297describeXCM('[XCMLL] Integration test: Exchanging tokens with Shiden', () => {298 let alice: IKeyringPair;299 let sender: IKeyringPair;300301 const QTZ_ASSET_ID_ON_SHIDEN = 1;302 const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;303304 // Quartz -> Shiden305 const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden306 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?307 const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ308 const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens309310 // Shiden -> Quartz311 const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ312 const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 4.999_999_999_088_000_000n QTZ313314 let balanceAfterQuartzToShidenXCM: bigint;315316 before(async () => {317 await usingPlaygrounds(async (helper, privateKey) => {318 alice = await privateKey('//Alice');319 [sender] = await helper.arrange.createAccounts([100n], alice);320 console.log('sender', sender.address);321322 // Set the default version to wrap the first message to other chains.323 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);324 });325326 await usingShidenPlaygrounds(shidenUrl, async (helper) => {327 console.log('1. Create foreign asset and metadata');328 // TODO update metadata with values from production329 await helper.assets.create(330 alice,331 QTZ_ASSET_ID_ON_SHIDEN,332 alice.address,333 QTZ_MINIMAL_BALANCE_ON_SHIDEN,334 );335336 await helper.assets.setMetadata(337 alice,338 QTZ_ASSET_ID_ON_SHIDEN,339 'Cross chain QTZ',340 'xcQTZ',341 Number(QTZ_DECIMALS),342 );343344 console.log('2. Register asset location on Shiden');345 const assetLocation = {346 V2: {347 parents: 1,348 interior: {349 X1: {350 Parachain: QUARTZ_CHAIN,351 },352 },353 },354 };355356 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);357358 console.log('3. Set QTZ payment for XCM execution on Shiden');359 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);360361 console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');362 await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);363 });364 });365366 itSub('Should connect and send QTZ to Shiden', async () => {367 await testHelper.sendUnqTo('shiden', sender);368 });369370 itSub('Should connect to Shiden and send QTZ back', async () => {371 await testHelper.sendUnqBack('shiden', alice, sender);372 });373374 itSub('Shiden can send only up to its balance', async () => {375 await testHelper.sendOnlyOwnedBalance('shiden', alice);376 });377378 itSub('Should not accept reserve transfer of QTZ from Shiden', async () => {379 await testHelper.reserveTransferUNQfrom('shiden', alice);380 });381});tests/src/xcm/lowLevelXcmUnique.test.tsdiffbeforeafterboth--- a/tests/src/xcm/lowLevelXcmUnique.test.ts
+++ b/tests/src/xcm/lowLevelXcmUnique.test.ts
@@ -20,7 +20,7 @@
import {Event} from '../util/playgrounds/unique.dev';
import {nToBigInt} from '@polkadot/util';
import {hexToString} from '@polkadot/util';
-import {ASTAR_DECIMALS, NETWORKS, SAFE_XCM_VERSION, UNIQUE_CHAIN, UNQ_DECIMALS, acalaUrl, astarUrl, expectFailedToTransact, expectUntrustedReserveLocationFail, getDevPlayground, mapToChainId, mapToChainUrl, maxWaitBlocks, moonbeamUrl, polkadexUrl, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types';
+import {ASTAR_DECIMALS, NETWORKS, SAFE_XCM_VERSION, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl, expectFailedToTransact, expectUntrustedReserveLocationFail, getDevPlayground, mapToChainId, mapToChainUrl, maxWaitBlocks, moonbeamUrl, polkadexUrl, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types';
const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;
@@ -34,6 +34,7 @@
let balanceUniqueTokenFinal: bigint;
let unqFees: bigint;
+const testHelper = new XcmTestHelper('unique');
async function genericSendUnqTo(
networkName: keyof typeof NETWORKS,
@@ -434,20 +435,20 @@
});
itSub('Should connect and send UNQ to Polkadex', async () => {
- await genericSendUnqTo('polkadex', randomAccount);
+ await testHelper.sendUnqTo('polkadex', randomAccount);
});
itSub('Should connect to Polkadex and send UNQ back', async () => {
- await genericSendUnqBack('polkadex', alice, randomAccount);
+ await testHelper.sendUnqBack('polkadex', alice, randomAccount);
});
itSub('Polkadex can send only up to its balance', async () => {
- await genericSendOnlyOwnedBalance('polkadex', alice);
+ await testHelper.sendOnlyOwnedBalance('polkadex', alice);
});
itSub('Should not accept reserve transfer of UNQ from Polkadex', async () => {
- await genericReserveTransferUNQfrom('polkadex', alice);
+ await testHelper.reserveTransferUNQfrom('polkadex', alice);
});
});
@@ -574,19 +575,19 @@
});
itSub('Should connect and send UNQ to Moonbeam', async () => {
- await genericSendUnqTo('moonbeam', randomAccountUnique, randomAccountMoonbeam);
+ await testHelper.sendUnqTo('moonbeam', randomAccountUnique, randomAccountMoonbeam);
});
itSub('Should connect to Moonbeam and send UNQ back', async () => {
- await genericSendUnqBack('moonbeam', alice, randomAccountUnique);
+ await testHelper.sendUnqBack('moonbeam', alice, randomAccountUnique);
});
itSub('Moonbeam can send only up to its balance', async () => {
- await genericSendOnlyOwnedBalance('moonbeam', alice);
+ await testHelper.sendOnlyOwnedBalance('moonbeam', alice);
});
itSub('Should not accept reserve transfer of UNQ from Moonbeam', async () => {
- await genericReserveTransferUNQfrom('moonbeam', alice);
+ await testHelper.reserveTransferUNQfrom('moonbeam', alice);
});
});
tests/src/xcm/xcm.types.tsdiffbeforeafterboth--- a/tests/src/xcm/xcm.types.ts
+++ b/tests/src/xcm/xcm.types.ts
@@ -1,4 +1,5 @@
-import {usingAcalaPlaygrounds, usingAstarPlaygrounds, usingMoonbeamPlaygrounds, usingPolkadexPlaygrounds} from '../util';
+import {IKeyringPair} from '@polkadot/types/types';
+import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingShidenPlaygrounds} from '../util';
import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
import config from '../config';
@@ -9,19 +10,39 @@
export const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);
export const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);
+export const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095);
+export const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000);
+export const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000);
+export const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023);
+export const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007);
+
+export const relayUrl = config.relayUrl;
+export const statemintUrl = config.statemintUrl;
+export const statemineUrl = config.statemineUrl;
+
export const acalaUrl = config.acalaUrl;
export const moonbeamUrl = config.moonbeamUrl;
export const astarUrl = config.astarUrl;
export const polkadexUrl = config.polkadexUrl;
+export const karuraUrl = config.karuraUrl;
+export const moonriverUrl = config.moonriverUrl;
+export const shidenUrl = config.shidenUrl;
+
export const SAFE_XCM_VERSION = 3;
-export const maxWaitBlocks = 6;
+export const RELAY_DECIMALS = 12;
+export const STATEMINE_DECIMALS = 12;
+export const KARURA_DECIMALS = 12;
+export const SHIDEN_DECIMALS = 18n;
+export const QTZ_DECIMALS = 18n;
export const ASTAR_DECIMALS = 18n;
export const UNQ_DECIMALS = 18n;
+export const maxWaitBlocks = 6;
+
export const uniqueMultilocation = {
parents: 1,
interior: {
@@ -52,9 +73,15 @@
astar: usingAstarPlaygrounds,
polkadex: usingPolkadexPlaygrounds,
moonbeam: usingMoonbeamPlaygrounds,
+ moonriver: usingMoonriverPlaygrounds,
+ karura: usingKaruraPlaygrounds,
+ shiden: usingShidenPlaygrounds,
} as const;
+type NetworkNames = keyof typeof NETWORKS;
-export function mapToChainId(networkName: keyof typeof NETWORKS) {
+type NativeRuntime = 'opal' | 'quartz' | 'unique';
+
+export function mapToChainId(networkName: keyof typeof NETWORKS): number {
switch (networkName) {
case 'acala':
return ACALA_CHAIN;
@@ -64,10 +91,16 @@
return MOONBEAM_CHAIN;
case 'polkadex':
return POLKADEX_CHAIN;
+ case 'moonriver':
+ return MOONRIVER_CHAIN;
+ case 'karura':
+ return KARURA_CHAIN;
+ case 'shiden':
+ return SHIDEN_CHAIN;
}
}
-export function mapToChainUrl(networkName: keyof typeof NETWORKS): string {
+export function mapToChainUrl(networkName: NetworkNames): string {
switch (networkName) {
case 'acala':
return acalaUrl;
@@ -77,9 +110,364 @@
return moonbeamUrl;
case 'polkadex':
return polkadexUrl;
+ case 'moonriver':
+ return moonriverUrl;
+ case 'karura':
+ return karuraUrl;
+ case 'shiden':
+ return shidenUrl;
}
}
-export function getDevPlayground<T extends keyof typeof NETWORKS>(name: T) {
+export function getDevPlayground(name: NetworkNames) {
return NETWORKS[name];
+}
+
+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 class XcmTestHelper {
+ private _balanceUniqueTokenInit: bigint = 0n;
+ private _balanceUniqueTokenMiddle: bigint = 0n;
+ private _balanceUniqueTokenFinal: bigint = 0n;
+ private _unqFees: bigint = 0n;
+ private _nativeRuntime: NativeRuntime;
+
+ constructor(runtime: NativeRuntime) {
+ this._nativeRuntime = runtime;
+ }
+
+ private _getNativeId() {
+ switch (this._nativeRuntime) {
+ case 'opal':
+ // To-Do
+ return 10;
+ case 'quartz':
+ return QUARTZ_CHAIN;
+ case 'unique':
+ return UNIQUE_CHAIN;
+ }
+ }
+ private _isAddress20FormatFor(network: NetworkNames) {
+ switch (network) {
+ case 'moonbeam':
+ case 'moonriver':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ async sendUnqTo(
+ networkName: keyof typeof NETWORKS,
+ randomAccount: IKeyringPair,
+ randomAccountOnTargetChain = randomAccount,
+ ) {
+ const networkUrl = mapToChainUrl(networkName);
+ const targetPlayground = getDevPlayground(networkName);
+ await usingPlaygrounds(async (helper) => {
+ this._balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
+ const destination = {
+ V2: {
+ parents: 1,
+ interior: {
+ X1: {
+ Parachain: mapToChainId(networkName),
+ },
+ },
+ },
+ };
+
+ const beneficiary = {
+ V2: {
+ parents: 0,
+ interior: {
+ X1: (
+ this._isAddress20FormatFor(networkName) ?
+ {
+ 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);
+ 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;
+
+ 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 sendUnqBack(
+ 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,
+ {
+ Concrete: {
+ parents: 1,
+ interior: {
+ X1: {Parachain: this._getNativeId()},
+ },
+ },
+ },
+ 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);
+
+ this._balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);
+
+ expect(this._balanceUniqueTokenFinal).to.be.equal(this._balanceUniqueTokenInit - this._unqFees - STAYED_ON_TARGET_CHAIN);
+
+ });
+ }
+
+ async sendOnlyOwnedBalance(
+ 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 reserveTransferUNQfrom(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,
+ {
+ Concrete: {
+ parents: 1,
+ interior: {
+ X1: {
+ Parachain: this._getNativeId(),
+ },
+ },
+ },
+ },
+ 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 rejectNativeTokensFrom(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);
+ });
+ }
+
}
\ No newline at end of file
tests/src/xcm/xcmQuartz.test.tsdiffbeforeafterboth--- a/tests/src/xcm/xcmQuartz.test.ts
+++ b/tests/src/xcm/xcmQuartz.test.ts
@@ -15,29 +15,13 @@
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
import {IKeyringPair} from '@polkadot/types/types';
-import config from '../config';
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';
+
-const QUARTZ_CHAIN = +(process.env.RELAY_QUARTZ_ID || 2095);
-const STATEMINE_CHAIN = +(process.env.RELAY_STATEMINE_ID || 1000);
-const KARURA_CHAIN = +(process.env.RELAY_KARURA_ID || 2000);
-const MOONRIVER_CHAIN = +(process.env.RELAY_MOONRIVER_ID || 2023);
-const SHIDEN_CHAIN = +(process.env.RELAY_SHIDEN_ID || 2007);
const STATEMINE_PALLET_INSTANCE = 50;
-
-const relayUrl = config.relayUrl;
-const statemineUrl = config.statemineUrl;
-const karuraUrl = config.karuraUrl;
-const moonriverUrl = config.moonriverUrl;
-const shidenUrl = config.shidenUrl;
-
-const RELAY_DECIMALS = 12;
-const STATEMINE_DECIMALS = 12;
-const KARURA_DECIMALS = 12;
-const SHIDEN_DECIMALS = 18n;
-const QTZ_DECIMALS = 18n;
const TRANSFER_AMOUNT = 2000000000000000000000000n;