difftreelog
test relay does root ops
in: master
3 files changed
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});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, 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});tests/src/xcm/lowLevelXcmUnique.test.tsdiffbeforeafterboth--- a/tests/src/xcm/lowLevelXcmUnique.test.ts
+++ b/tests/src/xcm/lowLevelXcmUnique.test.ts
@@ -16,11 +16,11 @@
import {IKeyringPair} from '@polkadot/types/types';
import config from '../config';
-import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util';
+import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, requirePalletsOrSkip, Pallets} from '../util';
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, XcmTestHelper, 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, relayUrl, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types';
const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;
@@ -672,3 +672,67 @@
await genericReserveTransferUNQfrom('astar', alice);
});
});
+
+describeXCM('[XCMLL] Integration test: The relay can do some root ops', () => {
+ let sudoer: IKeyringPair;
+
+ before(async function () {
+ await usingRelayPlaygrounds(relayUrl, async (_, privateKey) => {
+ sudoer = await privateKey('//Alice');
+ });
+ });
+
+ // At the moment there is no reliable way
+ // to establish the correspondence between the `ExecutedDownward` event
+ // and the relay's sent message due to `SetTopic` instruction
+ // containing an unpredictable topic silently added by the relay on the router level.
+ // This changes the message hash on arrival to our chain.
+ //
+ // See:
+ // * The relay's router: https://github.com/paritytech/polkadot-sdk/blob/f60318f68687e601c47de5ad5ca88e2c3f8139a7/polkadot/runtime/westend/src/xcm_config.rs#L83
+ // * The `WithUniqueTopic` helper: https://github.com/paritytech/polkadot-sdk/blob/945ebbbcf66646be13d5b1d1bc26c8b0d3296d9e/polkadot/xcm/xcm-builder/src/routing.rs#L36
+ //
+ // Because of this, we insert time gaps between tests so
+ // different `ExecutedDownward` events won't interfere with each other.
+ afterEach(async () => {
+ await usingPlaygrounds(async (helper) => {
+ await helper.wait.newBlocks(3);
+ });
+ });
+
+ itSub('The relay can set storage', async () => {
+ await testHelper.relayIsPermittedToSetStorage(sudoer, 'plain');
+ });
+
+ itSub('The relay can batch set storage', async () => {
+ await testHelper.relayIsPermittedToSetStorage(sudoer, 'batch');
+ });
+
+ itSub('The relay can batchAll set storage', async () => {
+ await testHelper.relayIsPermittedToSetStorage(sudoer, 'batchAll');
+ });
+
+ itSub('The relay can forceBatch set storage', async () => {
+ await testHelper.relayIsPermittedToSetStorage(sudoer, 'forceBatch');
+ });
+
+ itSub('[negative] The relay cannot set balance', async () => {
+ await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'plain');
+ });
+
+ itSub('[negative] The relay cannot set balance via batch', async () => {
+ await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batch');
+ });
+
+ itSub('[negative] The relay cannot set balance via batchAll', async () => {
+ await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'batchAll');
+ });
+
+ itSub('[negative] The relay cannot set balance via forceBatch', async () => {
+ await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'forceBatch');
+ });
+
+ itSub('[negative] The relay cannot set balance via dispatchAs', async () => {
+ await testHelper.relayIsNotPermittedToSetBalance(sudoer, 'dispatchAs');
+ });
+});
tests/src/xcm/xcm.types.tsdiffbeforeafterboth--- a/tests/src/xcm/xcm.types.ts
+++ b/tests/src/xcm/xcm.types.ts
@@ -1,7 +1,9 @@
import {IKeyringPair} from '@polkadot/types/types';
-import {expect, usingAcalaPlaygrounds, usingAstarPlaygrounds, usingKaruraPlaygrounds, usingMoonbeamPlaygrounds, usingMoonriverPlaygrounds, usingPlaygrounds, usingPolkadexPlaygrounds, usingShidenPlaygrounds} from '../util';
+import {hexToString} from '@polkadot/util';
+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);
@@ -68,6 +70,16 @@
&& event.outcome.isUntrustedReserveLocation);
};
+export const expectDownwardXcmNoPermission = async (helper: DevUniqueHelper) => {
+ // The correct messageHash for downward messages can't be reliably obtained
+ await helper.wait.expectEvent(maxWaitBlocks, Event.DmpQueue.ExecutedDownward, event => event.outcome.asIncomplete[1].isNoPermission);
+};
+
+export const expectDownwardXcmComplete = async (helper: DevUniqueHelper) => {
+ // The correct messageHash for downward messages can't be reliably obtained
+ await helper.wait.expectEvent(maxWaitBlocks, Event.DmpQueue.ExecutedDownward, event => event.outcome.isComplete);
+};
+
export const NETWORKS = {
acala: usingAcalaPlaygrounds,
astar: usingAstarPlaygrounds,
@@ -161,6 +173,17 @@
}
}
+ uniqueChainMultilocationForRelay() {
+ return {
+ V3: {
+ parents: 0,
+ interior: {
+ X1: {Parachain: this._getNativeId()},
+ },
+ },
+ };
+ }
+
async sendUnqTo(
networkName: keyof typeof NETWORKS,
randomAccount: IKeyringPair,
@@ -470,4 +493,116 @@
});
}
-}
\ No newline at end of file
+ private async _relayXcmTransactSetStorage(variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch') {
+ // eslint-disable-next-line require-await
+ return await usingPlaygrounds(async (helper) => {
+ const relayForceKV = () => {
+ const random = Math.random();
+ const key = `relay-forced-key (instance: ${random})`;
+ const val = `relay-forced-value (instance: ${random})`;
+ const call = helper.constructApiCall('api.tx.system.setStorage', [[[key, val]]]).method.toHex();
+
+ return {
+ call,
+ key,
+ val,
+ };
+ };
+
+ if(variant == 'plain') {
+ const kv = relayForceKV();
+ return {
+ program: helper.arrange.makeTransactProgram({
+ weightMultiplier: 1,
+ call: kv.call,
+ }),
+ kvs: [kv],
+ };
+ } else {
+ const kv0 = relayForceKV();
+ const kv1 = relayForceKV();
+
+ const batchCall = helper.constructApiCall(`api.tx.utility.${variant}`, [[kv0.call, kv1.call]]).method.toHex();
+ return {
+ program: helper.arrange.makeTransactProgram({
+ weightMultiplier: 2,
+ call: batchCall,
+ }),
+ kvs: [kv0, kv1],
+ };
+ }
+ });
+ }
+
+ async relayIsPermittedToSetStorage(relaySudoer: IKeyringPair, variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch') {
+ const {program, kvs} = await this._relayXcmTransactSetStorage(variant);
+
+ await usingRelayPlaygrounds(relayUrl, async (helper) => {
+ await helper.getSudo().executeExtrinsic(relaySudoer, 'api.tx.xcmPallet.send', [
+ this.uniqueChainMultilocationForRelay(),
+ program,
+ ]);
+ });
+
+ await usingPlaygrounds(async (helper) => {
+ await expectDownwardXcmComplete(helper);
+
+ for(const kv of kvs) {
+ const forcedValue = await helper.callRpc('api.rpc.state.getStorage', [kv.key]);
+ expect(hexToString(forcedValue.toHex())).to.be.equal(kv.val);
+ }
+ });
+ }
+
+ private async _relayXcmTransactSetBalance(variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch' | 'dispatchAs') {
+ // eslint-disable-next-line require-await
+ return await usingPlaygrounds(async (helper) => {
+ const emptyAccount = helper.arrange.createEmptyAccount().address;
+
+ const forceSetBalanceCall = helper.constructApiCall('api.tx.balances.forceSetBalance', [emptyAccount, 10_000n]).method.toHex();
+
+ let call;
+
+ if(variant == 'plain') {
+ call = forceSetBalanceCall;
+
+ } else if(variant == 'dispatchAs') {
+ call = helper.constructApiCall('api.tx.utility.dispatchAs', [
+ {
+ system: 'Root',
+ },
+ forceSetBalanceCall,
+ ]).method.toHex();
+ } else {
+ call = helper.constructApiCall(`api.tx.utility.${variant}`, [[forceSetBalanceCall]]).method.toHex();
+ }
+
+ return {
+ program: helper.arrange.makeTransactProgram({
+ weightMultiplier: 1,
+ call,
+ }),
+ emptyAccount,
+ };
+ });
+ }
+
+ async relayIsNotPermittedToSetBalance(
+ relaySudoer: IKeyringPair,
+ variant: 'plain' | 'batch' | 'batchAll' | 'forceBatch' | 'dispatchAs',
+ ) {
+ const {program, emptyAccount} = await this._relayXcmTransactSetBalance(variant);
+
+ await usingRelayPlaygrounds(relayUrl, async (helper) => {
+ await helper.getSudo().executeExtrinsic(relaySudoer, 'api.tx.xcmPallet.send', [
+ this.uniqueChainMultilocationForRelay(),
+ program,
+ ]);
+ });
+
+ await usingPlaygrounds(async (helper) => {
+ await expectDownwardXcmNoPermission(helper);
+ expect(await helper.balance.getSubstrate(emptyAccount)).to.be.equal(0n);
+ });
+ }
+}