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.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, usingRelayPlaygrounds} 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});382383describeXCM('[XCMLL] Integration test: The relay can do some root ops', () => {384 let sudoer: IKeyringPair;385386 before(async function () {387 await usingRelayPlaygrounds(relayUrl, async (_, privateKey) => {388 sudoer = await privateKey('//Alice');389 });390 });391392 // At the moment there is no reliable way393 // to establish the correspondence between the `ExecutedDownward` event394 // and the relay's sent message due to `SetTopic` instruction395 // containing an unpredictable topic silently added by the relay on the router level.396 // This changes the message hash on arrival to our chain.397 //398 // See:399 // * The relay's router: https://github.com/paritytech/polkadot-sdk/blob/f60318f68687e601c47de5ad5ca88e2c3f8139a7/polkadot/runtime/westend/src/xcm_config.rs#L83400 // * The `WithUniqueTopic` helper: https://github.com/paritytech/polkadot-sdk/blob/945ebbbcf66646be13d5b1d1bc26c8b0d3296d9e/polkadot/xcm/xcm-builder/src/routing.rs#L36401 //402 // Because of this, we insert time gaps between tests so403 // different `ExecutedDownward` events won't interfere with each other.404 afterEach(async () => {405 await usingPlaygrounds(async (helper) => {406 await helper.wait.newBlocks(3);407 });408 });409410 itSub('The relay can set storage', async () => {411 await testHelper.relayIsPermittedToSetStorage(sudoer, 'plain');412 });413414 itSub('The relay can batch set storage', async () => {415 await testHelper.relayIsPermittedToSetStorage(sudoer, 'batch');416 });417418 itSub('The relay can batchAll set storage', async () => {419 await testHelper.relayIsPermittedToSetStorage(sudoer, 'batchAll');420 });421422 itSub('The relay can forceBatch set storage', async () => {423 await testHelper.relayIsPermittedToSetStorage(sudoer, 'forceBatch');424 });425426 itSub('[negative] The relay cannot set balance', async () => {427 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'plain');428 });429430 itSub('[negative] The relay cannot set balance via batch', async () => {431 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batch');432 });433434 itSub('[negative] The relay cannot set balance via batchAll', async () => {435 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batchAll');436 });437438 itSub('[negative] The relay cannot set balance via forceBatch', async () => {439 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'forceBatch');440 });441442 itSub('[negative] The relay cannot set balance via dispatchAs', async () => {443 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'dispatchAs');444 });445});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 {itSub, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingMoonriverPlaygrounds, usingShidenPlaygrounds, usingRelayPlaygrounds} from '../util';19import {QUARTZ_CHAIN, QTZ_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, shidenUrl, SAFE_XCM_VERSION, XcmTestHelper, TRANSFER_AMOUNT, SENDER_BUDGET, relayUrl} from './xcm.types';20import {hexToString} from '@polkadot/util';2122const testHelper = new XcmTestHelper('quartz');2324describeXCM('[XCMLL] Integration test: Exchanging tokens with Karura', () => {25 let alice: IKeyringPair;26 let randomAccount: IKeyringPair;2728 before(async () => {29 await usingPlaygrounds(async (helper, privateKey) => {30 alice = await privateKey('//Alice');31 [randomAccount] = await helper.arrange.createAccounts([0n], alice);3233 // Set the default version to wrap the first message to other chains.34 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);35 });3637 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {38 const destination = {39 V2: {40 parents: 1,41 interior: {42 X1: {43 Parachain: QUARTZ_CHAIN,44 },45 },46 },47 };4849 const metadata = {50 name: 'Quartz',51 symbol: 'QTZ',52 decimals: 18,53 minimalBalance: 1000000000000000000n,54 };5556 const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v]: [any, any]) =>57 hexToString(v.toJSON()['symbol'])) as string[];5859 if(!assets.includes('QTZ')) {60 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);61 } else {62 console.log('QTZ token already registered on Karura assetRegistry pallet');63 }64 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);65 });6667 await usingPlaygrounds(async (helper) => {68 await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);69 });70 });7172 itSub('Should connect and send QTZ to Karura', async () => {73 await testHelper.sendUnqTo('karura', randomAccount);74 });7576 itSub('Should connect to Karura and send QTZ back', async () => {77 await testHelper.sendUnqBack('karura', alice, randomAccount);78 });7980 itSub('Karura can send only up to its balance', async () => {81 await testHelper.sendOnlyOwnedBalance('karura', alice);82 });83});84// These tests are relevant only when85// the the corresponding foreign assets are not registered86describeXCM('[XCMLL] Integration test: Quartz rejects non-native tokens', () => {87 let alice: IKeyringPair;888990 before(async () => {91 await usingPlaygrounds(async (helper, privateKey) => {92 alice = await privateKey('//Alice');93949596 // Set the default version to wrap the first message to other chains.97 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);98 });99 });100101 itSub('Quartz rejects KAR tokens from Karura', async () => {102 await testHelper.rejectNativeTokensFrom('karura', alice);103 });104105 itSub('Quartz rejects MOVR tokens from Moonriver', async () => {106 await testHelper.rejectNativeTokensFrom('moonriver', alice);107 });108109 itSub('Quartz rejects SDN tokens from Shiden', async () => {110 await testHelper.rejectNativeTokensFrom('shiden', alice);111 });112});113114describeXCM('[XCMLL] Integration test: Exchanging QTZ with Moonriver', () => {115 // Quartz constants116 let alice: IKeyringPair;117 let quartzAssetLocation;118119 let randomAccountQuartz: IKeyringPair;120 let randomAccountMoonriver: IKeyringPair;121122 // Moonriver constants123 let assetId: string;124125 const quartzAssetMetadata = {126 name: 'xcQuartz',127 symbol: 'xcQTZ',128 decimals: 18,129 isFrozen: false,130 minimalBalance: 1n,131 };132133134 before(async () => {135 await usingPlaygrounds(async (helper, privateKey) => {136 alice = await privateKey('//Alice');137 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);138139140 // Set the default version to wrap the first message to other chains.141 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);142 });143144 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {145 const alithAccount = helper.account.alithAccount();146 const baltatharAccount = helper.account.baltatharAccount();147 const dorothyAccount = helper.account.dorothyAccount();148149 randomAccountMoonriver = helper.account.create();150151 // >>> Sponsoring Dorothy >>>152 console.log('Sponsoring Dorothy.......');153 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);154 console.log('Sponsoring Dorothy.......DONE');155 // <<< Sponsoring Dorothy <<<156157 quartzAssetLocation = {158 XCM: {159 parents: 1,160 interior: {X1: {Parachain: QUARTZ_CHAIN}},161 },162 };163 const existentialDeposit = 1n;164 const isSufficient = true;165 const unitsPerSecond = 1n;166 const numAssetsWeightHint = 0;167 if((await helper.assetManager.assetTypeId(quartzAssetLocation)).toJSON()) {168 console.log('Quartz asset already registered on Moonriver');169 } else {170 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({171 location: quartzAssetLocation,172 metadata: quartzAssetMetadata,173 existentialDeposit,174 isSufficient,175 unitsPerSecond,176 numAssetsWeightHint,177 });178179 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);180181 await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);182 }183 // >>> Acquire Quartz AssetId Info on Moonriver >>>184 console.log('Acquire Quartz AssetId Info on Moonriver.......');185186 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();187188 console.log('QTZ asset ID is %s', assetId);189 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');190 // >>> Acquire Quartz AssetId Info on Moonriver >>>191192 // >>> Sponsoring random Account >>>193 console.log('Sponsoring random Account.......');194 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);195 console.log('Sponsoring random Account.......DONE');196 // <<< Sponsoring random Account <<<197 });198199 await usingPlaygrounds(async (helper) => {200 await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);201 });202 });203204 itSub('Should connect and send QTZ to Moonriver', async () => {205 await testHelper.sendUnqTo('moonriver', randomAccountQuartz, randomAccountMoonriver);206 });207208 itSub('Should connect to Moonriver and send QTZ back', async () => {209 await testHelper.sendUnqBack('moonriver', alice, randomAccountQuartz);210 });211212 itSub('Moonriver can send only up to its balance', async () => {213 await testHelper.sendOnlyOwnedBalance('moonriver', alice);214 });215216 itSub('Should not accept reserve transfer of QTZ from Moonriver', async () => {217 await testHelper.reserveTransferUNQfrom('moonriver', alice);218 });219});220221describeXCM('[XCMLL] Integration test: Exchanging tokens with Shiden', () => {222 let alice: IKeyringPair;223 let randomAccount: IKeyringPair;224225 const QTZ_ASSET_ID_ON_SHIDEN = 1;226 const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;227228 // Quartz -> Shiden229 const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden230 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?231232233234 before(async () => {235 await usingPlaygrounds(async (helper, privateKey) => {236 alice = await privateKey('//Alice');237 randomAccount = helper.arrange.createEmptyAccount();238 await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);239 console.log('sender: ', randomAccount.address);240241 // Set the default version to wrap the first message to other chains.242 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);243 });244245 await usingShidenPlaygrounds(shidenUrl, async (helper) => {246 if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) {247 console.log('1. Create foreign asset and metadata');248 // TODO update metadata with values from production249 await helper.assets.create(250 alice,251 QTZ_ASSET_ID_ON_SHIDEN,252 alice.address,253 QTZ_MINIMAL_BALANCE_ON_SHIDEN,254 );255256 await helper.assets.setMetadata(257 alice,258 QTZ_ASSET_ID_ON_SHIDEN,259 'Cross chain QTZ',260 'xcQTZ',261 Number(QTZ_DECIMALS),262 );263264 console.log('2. Register asset location on Shiden');265 const assetLocation = {266 V2: {267 parents: 1,268 interior: {269 X1: {270 Parachain: QUARTZ_CHAIN,271 },272 },273 },274 };275276 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);277278 console.log('3. Set QTZ payment for XCM execution on Shiden');279 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);280 } else {281 console.log('QTZ is already registered on Shiden');282 }283 console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');284 await helper.balance.transferToSubstrate(alice, randomAccount.address, shidenInitialBalance);285 });286 });287288 itSub('Should connect and send QTZ to Shiden', async () => {289 await testHelper.sendUnqTo('shiden', randomAccount);290 });291292 itSub('Should connect to Shiden and send QTZ back', async () => {293 await testHelper.sendUnqBack('shiden', alice, randomAccount);294 });295296 itSub('Shiden can send only up to its balance', async () => {297 await testHelper.sendOnlyOwnedBalance('shiden', alice);298 });299300 itSub('Should not accept reserve transfer of QTZ from Shiden', async () => {301 await testHelper.reserveTransferUNQfrom('shiden', alice);302 });303});304305describeXCM('[XCMLL] Integration test: The relay can do some root ops', () => {306 let sudoer: IKeyringPair;307308 before(async function () {309 await usingRelayPlaygrounds(relayUrl, async (_, privateKey) => {310 sudoer = await privateKey('//Alice');311 });312 });313314 // At the moment there is no reliable way315 // to establish the correspondence between the `ExecutedDownward` event316 // and the relay's sent message due to `SetTopic` instruction317 // containing an unpredictable topic silently added by the relay on the router level.318 // This changes the message hash on arrival to our chain.319 //320 // See:321 // * The relay's router: https://github.com/paritytech/polkadot-sdk/blob/f60318f68687e601c47de5ad5ca88e2c3f8139a7/polkadot/runtime/westend/src/xcm_config.rs#L83322 // * The `WithUniqueTopic` helper: https://github.com/paritytech/polkadot-sdk/blob/945ebbbcf66646be13d5b1d1bc26c8b0d3296d9e/polkadot/xcm/xcm-builder/src/routing.rs#L36323 //324 // Because of this, we insert time gaps between tests so325 // different `ExecutedDownward` events won't interfere with each other.326 afterEach(async () => {327 await usingPlaygrounds(async (helper) => {328 await helper.wait.newBlocks(3);329 });330 });331332 itSub('The relay can set storage', async () => {333 await testHelper.relayIsPermittedToSetStorage(sudoer, 'plain');334 });335336 itSub('The relay can batch set storage', async () => {337 await testHelper.relayIsPermittedToSetStorage(sudoer, 'batch');338 });339340 itSub('The relay can batchAll set storage', async () => {341 await testHelper.relayIsPermittedToSetStorage(sudoer, 'batchAll');342 });343344 itSub('The relay can forceBatch set storage', async () => {345 await testHelper.relayIsPermittedToSetStorage(sudoer, 'forceBatch');346 });347348 itSub('[negative] The relay cannot set balance', async () => {349 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'plain');350 });351352 itSub('[negative] The relay cannot set balance via batch', async () => {353 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batch');354 });355356 itSub('[negative] The relay cannot set balance via batchAll', async () => {357 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batchAll');358 });359360 itSub('[negative] The relay cannot set balance via forceBatch', async () => {361 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'forceBatch');362 });363364 itSub('[negative] The relay cannot set balance via dispatchAs', async () => {365 await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'dispatchAs');366 });367});tests/src/xcm/lowLevelXcmUnique.test.tsdiffbeforeafterboth--- a/tests/src/xcm/lowLevelXcmUnique.test.ts
+++ b/tests/src/xcm/lowLevelXcmUnique.test.ts
@@ -16,320 +16,16 @@
import {IKeyringPair} from '@polkadot/types/types';
import config from '../config';
-import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, requirePalletsOrSkip, Pallets} from '../util';
-import {Event} from '../util/playgrounds/unique.dev';
+import {itSub, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds} from '../util';
import {nToBigInt} from '@polkadot/util';
import {hexToString} from '@polkadot/util';
-import {ASTAR_DECIMALS, NETWORKS, SAFE_XCM_VERSION, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl, expectFailedToTransact, expectUntrustedReserveLocationFail, getDevPlayground, mapToChainId, mapToChainUrl, maxWaitBlocks, moonbeamUrl, polkadexUrl, relayUrl, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types';
-
-
-const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;
-const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;
-const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;
-const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;
-const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;
-
-let balanceUniqueTokenInit: bigint;
-let balanceUniqueTokenMiddle: bigint;
-let balanceUniqueTokenFinal: bigint;
-let unqFees: bigint;
+import {ASTAR_DECIMALS, SAFE_XCM_VERSION, SENDER_BUDGET, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl, moonbeamUrl, polkadexUrl, relayUrl, uniqueAssetId} from './xcm.types';
const testHelper = new XcmTestHelper('unique');
-
-async function genericSendUnqTo(
- networkName: keyof typeof NETWORKS,
- randomAccount: IKeyringPair,
- randomAccountOnTargetChain = randomAccount,
-) {
- const networkUrl = mapToChainUrl(networkName);
- const targetPlayground = getDevPlayground(networkName);
- await usingPlaygrounds(async (helper) => {
- balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
- const destination = {
- V2: {
- parents: 1,
- interior: {
- X1: {
- Parachain: mapToChainId(networkName),
- },
- },
- },
- };
-
- const beneficiary = {
- V2: {
- parents: 0,
- interior: {
- X1: (
- networkName == 'moonbeam' ?
- {
- AccountKey20: {
- network: 'Any',
- key: randomAccountOnTargetChain.address,
- },
- }
- :
- {
- AccountId32: {
- network: 'Any',
- id: randomAccountOnTargetChain.addressRaw,
- },
- }
- ),
- },
- },
- };
-
- const assets = {
- V2: [
- {
- id: {
- Concrete: {
- parents: 0,
- interior: 'Here',
- },
- },
- fun: {
- Fungible: TRANSFER_AMOUNT,
- },
- },
- ],
- };
- const feeAssetItem = 0;
-
- await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');
- const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);
-
- unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;
- console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(unqFees));
- expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;
-
- await targetPlayground(networkUrl, async (helper) => {
- /*
- Since only the parachain part of the Polkadex
- infrastructure is launched (without their
- solochain validators), processing incoming
- assets will lead to an error.
- This error indicates that the Polkadex chain
- received a message from the Unique network,
- since the hash is being checked to ensure
- it matches what was sent.
- */
- if(networkName == 'polkadex') {
- await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);
- } else {
- await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash);
- }
- });
-
- });
-}
-
-async function genericSendUnqBack(
- networkName: keyof typeof NETWORKS,
- sudoer: IKeyringPair,
- randomAccountOnUnq: IKeyringPair,
-) {
- const networkUrl = mapToChainUrl(networkName);
-
- const targetPlayground = getDevPlayground(networkName);
- await usingPlaygrounds(async (helper) => {
-
- const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
- randomAccountOnUnq.addressRaw,
- uniqueAssetId,
- SENDBACK_AMOUNT,
- );
-
- let xcmProgramSent: any;
-
- await targetPlayground(networkUrl, async (helper) => {
- if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, xcmProgram);
- xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- } else if('fastDemocracy' in helper) {
- const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, xcmProgram]);
- // Needed to bypass the call filter.
- const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
- await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);
- xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- }
- });
-
- await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);
-
- balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);
-
- expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees - STAYED_ON_TARGET_CHAIN);
-
- });
-}
-async function genericSendOnlyOwnedBalance(
- networkName: keyof typeof NETWORKS,
- sudoer: IKeyringPair,
-) {
- const networkUrl = mapToChainUrl(networkName);
- const targetPlayground = getDevPlayground(networkName);
- const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);
- await usingPlaygrounds(async (helper) => {
- const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));
- await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);
- const moreThanTargetChainHas = 2n * targetChainBalance;
-
- const targetAccount = helper.arrange.createEmptyAccount();
-
- const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
- targetAccount.addressRaw,
- {
- Concrete: {
- parents: 0,
- interior: 'Here',
- },
- },
- moreThanTargetChainHas,
- );
-
- let maliciousXcmProgramSent: any;
-
-
- await targetPlayground(networkUrl, async (helper) => {
- if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgram);
- maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- } else if('fastDemocracy' in helper) {
- const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);
- // Needed to bypass the call filter.
- const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
- await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);
- maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- }
- });
-
- await expectFailedToTransact(helper, maliciousXcmProgramSent);
-
- const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
- expect(targetAccountBalance).to.be.equal(0n);
- });
-}
-
-async function genericReserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) {
- const networkUrl = mapToChainUrl(netwokrName);
- const targetPlayground = getDevPlayground(netwokrName);
-
- await usingPlaygrounds(async (helper) => {
- const testAmount = 10_000n * (10n ** UNQ_DECIMALS);
- const targetAccount = helper.arrange.createEmptyAccount();
-
- const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
- targetAccount.addressRaw,
- uniqueAssetId,
- testAmount,
- );
-
- const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(
- targetAccount.addressRaw,
- {
- Concrete: {
- parents: 0,
- interior: 'Here',
- },
- },
- testAmount,
- );
-
- let maliciousXcmProgramFullIdSent: any;
- let maliciousXcmProgramHereIdSent: any;
- const maxWaitBlocks = 3;
-
- // Try to trick Unique using full UNQ identification
- await targetPlayground(networkUrl, async (helper) => {
- if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramFullId);
- maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- }
- // Moonbeam case
- else if('fastDemocracy' in helper) {
- const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);
- // Needed to bypass the call filter.
- const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
- await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using path asset identification`,batchCall);
-
- maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- }
- });
-
-
- await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);
-
- let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
- expect(accountBalance).to.be.equal(0n);
-
- // Try to trick Unique using shortened UNQ identification
- await targetPlayground(networkUrl, async (helper) => {
- if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramHereId);
- maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- }
- else if('fastDemocracy' in helper) {
- const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);
- // Needed to bypass the call filter.
- const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
- await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall);
-
- maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- }
- });
-
- await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);
-
- accountBalance = await helper.balance.getSubstrate(targetAccount.address);
- expect(accountBalance).to.be.equal(0n);
- });
-}
-
-async function genericRejectNativeTokensFrom(networkName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {
- const networkUrl = mapToChainUrl(networkName);
- const targetPlayground = getDevPlayground(networkName);
- let messageSent: any;
-
- await usingPlaygrounds(async (helper) => {
- const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
- helper.arrange.createEmptyAccount().addressRaw,
- {
- Concrete: {
- parents: 1,
- interior: {
- X1: {
- Parachain: mapToChainId(networkName),
- },
- },
- },
- },
- TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,
- );
- await targetPlayground(networkUrl, async (helper) => {
- if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueVersionedMultilocation, maliciousXcmProgramFullId);
- messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- } else if('fastDemocracy' in helper) {
- const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);
- // Needed to bypass the call filter.
- const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
- await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall);
-
- messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- }
- });
- await expectFailedToTransact(helper, messageSent);
- });
-}
-
-
describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => {
let alice: IKeyringPair;
let randomAccount: IKeyringPair;
@@ -375,24 +71,23 @@
await usingPlaygrounds(async (helper) => {
await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
- balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
});
});
itSub('Should connect and send UNQ to Acala', async () => {
- await genericSendUnqTo('acala', randomAccount);
+ await testHelper.sendUnqTo('acala', randomAccount);
});
itSub('Should connect to Acala and send UNQ back', async () => {
- await genericSendUnqBack('acala', alice, randomAccount);
+ await testHelper.sendUnqBack('acala', alice, randomAccount);
});
itSub('Acala can send only up to its balance', async () => {
- await genericSendOnlyOwnedBalance('acala', alice);
+ await testHelper.sendOnlyOwnedBalance('acala', alice);
});
itSub('Should not accept reserve transfer of UNQ from Acala', async () => {
- await genericReserveTransferUNQfrom('acala', alice);
+ await testHelper.reserveTransferUNQfrom('acala', alice);
});
});
@@ -430,7 +125,6 @@
await usingPlaygrounds(async (helper) => {
await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
- balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
});
});
@@ -467,19 +161,19 @@
});
itSub('Unique rejects ACA tokens from Acala', async () => {
- await genericRejectNativeTokensFrom('acala', alice);
+ await testHelper.rejectNativeTokensFrom('acala', alice);
});
itSub('Unique rejects GLMR tokens from Moonbeam', async () => {
- await genericRejectNativeTokensFrom('moonbeam', alice);
+ await testHelper.rejectNativeTokensFrom('moonbeam', alice);
});
itSub('Unique rejects ASTR tokens from Astar', async () => {
- await genericRejectNativeTokensFrom('astar', alice);
+ await testHelper.rejectNativeTokensFrom('astar', alice);
});
itSub('Unique rejects PDX tokens from Polkadex', async () => {
- await genericRejectNativeTokensFrom('polkadex', alice);
+ await testHelper.rejectNativeTokensFrom('polkadex', alice);
});
});
@@ -570,7 +264,6 @@
await usingPlaygrounds(async (helper) => {
await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, SENDER_BUDGET);
- balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);
});
});
@@ -657,19 +350,19 @@
});
itSub('Should connect and send UNQ to Astar', async () => {
- await genericSendUnqTo('astar', randomAccount);
+ await testHelper.sendUnqTo('astar', randomAccount);
});
itSub('Should connect to Astar and send UNQ back', async () => {
- await genericSendUnqBack('astar', alice, randomAccount);
+ await testHelper.sendUnqBack('astar', alice, randomAccount);
});
itSub('Astar can send only up to its balance', async () => {
- await genericSendOnlyOwnedBalance('astar', alice);
+ await testHelper.sendOnlyOwnedBalance('astar', alice);
});
itSub('Should not accept reserve transfer of UNQ from Astar', async () => {
- await genericReserveTransferUNQfrom('astar', alice);
+ await testHelper.reserveTransferUNQfrom('astar', alice);
});
});
tests/src/xcm/xcm.types.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);
});