difftreelog
feat(xcm tests) `XcmHelper` integraion for QTZ\UNQ
in: master
6 files changed
.baedeker/.gitignorediffbeforeafterboth--- a/.baedeker/.gitignore
+++ b/.baedeker/.gitignore
@@ -1,4 +1,5 @@
/.bdk-env
-/rewrites.jsonnet
+/rewrites*.jsonnet
/vendor
/baedeker-library
+!/rewrites.example.jsonnet
\ No newline at end of file
.github/workflows/xcm.ymldiffbeforeafterboth--- a/.github/workflows/xcm.yml
+++ b/.github/workflows/xcm.yml
@@ -38,7 +38,7 @@
with:
matrix: |
network {opal}, relay_branch {${{ env.UNIQUEWEST_MAINNET_BRANCH }}}, acala_version {${{ env.ACALA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONBEAM_BUILD_BRANCH }}}, cumulus_version {${{ env.WESTMINT_BUILD_BRANCH }}}, astar_version {${{ env.ASTAR_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testXcmOpal}, runtime_features {opal-runtime}
- network {quartz}, relay_branch {${{ env.KUSAMA_MAINNET_BRANCH }}}, acala_version {${{ env.KARURA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONRIVER_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINE_BUILD_BRANCH }}}, astar_version {${{ env.SHIDEN_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testXcmQuartz}, runtime_features {quartz-runtime}
+ network {quartz}, relay_branch {${{ env.KUSAMA_MAINNET_BRANCH }}}, acala_version {${{ env.KARURA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONRIVER_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINE_BUILD_BRANCH }}}, astar_version {${{ env.SHIDEN_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testFullXcmQuartz}, runtime_features {quartz-runtime}
network {unique}, relay_branch {${{ env.POLKADOT_MAINNET_BRANCH }}}, acala_version {${{ env.ACALA_BUILD_BRANCH }}}, moonbeam_version {${{ env.MOONBEAM_BUILD_BRANCH }}}, cumulus_version {${{ env.STATEMINT_BUILD_BRANCH }}}, astar_version {${{ env.ASTAR_BUILD_BRANCH }}}, polkadex_version {${{ env.POLKADEX_BUILD_BRANCH }}}, runtest {testFullXcmUnique}, runtime_features {unique-runtime}
xcm:
tests/src/xcm/lowLevelXcmQuartz.test.tsdiffbeforeafterboth--- a/tests/src/xcm/lowLevelXcmQuartz.test.ts
+++ b/tests/src/xcm/lowLevelXcmQuartz.test.ts
@@ -15,35 +15,16 @@
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
import {IKeyringPair} from '@polkadot/types/types';
-import {itSub, expect, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingMoonriverPlaygrounds, usingShidenPlaygrounds, usingRelayPlaygrounds} from '../util';
-import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
-import {STATEMINE_CHAIN, QUARTZ_CHAIN, KARURA_CHAIN, MOONRIVER_CHAIN, SHIDEN_CHAIN, STATEMINE_DECIMALS, KARURA_DECIMALS, QTZ_DECIMALS, RELAY_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, relayUrl, shidenUrl, statemineUrl, SAFE_XCM_VERSION, XcmTestHelper, TRANSFER_AMOUNT} from './xcm.types';
-
+import {itSub, describeXCM, usingPlaygrounds, usingKaruraPlaygrounds, usingMoonriverPlaygrounds, usingShidenPlaygrounds, usingRelayPlaygrounds} from '../util';
+import {QUARTZ_CHAIN, QTZ_DECIMALS, SHIDEN_DECIMALS, karuraUrl, moonriverUrl, shidenUrl, SAFE_XCM_VERSION, XcmTestHelper, TRANSFER_AMOUNT, SENDER_BUDGET, relayUrl} from './xcm.types';
+import {hexToString} from '@polkadot/util';
const testHelper = new XcmTestHelper('quartz');
describeXCM('[XCMLL] Integration test: Exchanging tokens with Karura', () => {
let alice: IKeyringPair;
let randomAccount: IKeyringPair;
-
- let balanceQuartzTokenInit: bigint;
- let balanceQuartzTokenMiddle: bigint;
- let balanceQuartzTokenFinal: bigint;
- let balanceKaruraTokenInit: bigint;
- let balanceKaruraTokenMiddle: bigint;
- let balanceKaruraTokenFinal: bigint;
- let balanceQuartzForeignTokenInit: bigint;
- let balanceQuartzForeignTokenMiddle: bigint;
- let balanceQuartzForeignTokenFinal: bigint;
- // computed by a test transfer from prod Quartz to prod Karura.
- // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9
- // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)
- const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;
- const karuraEps = 8n * 10n ** 16n;
-
- let karuraBackwardTransferAmount: bigint;
-
before(async () => {
await usingPlaygrounds(async (helper, privateKey) => {
alice = await privateKey('//Alice');
@@ -72,15 +53,19 @@
minimalBalance: 1000000000000000000n,
};
- await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);
+ const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v]: [any, any]) =>
+ hexToString(v.toJSON()['symbol'])) as string[];
+
+ if(!assets.includes('QTZ')) {
+ await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);
+ } else {
+ console.log('QTZ token already registered on Karura assetRegistry pallet');
+ }
await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);
- balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);
- balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});
});
await usingPlaygrounds(async (helper) => {
- await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);
- balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);
+ await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
});
});
@@ -100,67 +85,17 @@
// the the corresponding foreign assets are not registered
describeXCM('[XCMLL] Integration test: Quartz rejects non-native tokens', () => {
let alice: IKeyringPair;
- let alith: IKeyringPair;
- const testAmount = 100_000_000_000n;
- let quartzParachainJunction;
- let quartzAccountJunction;
- let quartzParachainMultilocation: any;
- let quartzAccountMultilocation: any;
- let quartzCombinedMultilocation: any;
-
- let messageSent: any;
-
- const maxWaitBlocks = 3;
-
before(async () => {
await usingPlaygrounds(async (helper, privateKey) => {
alice = await privateKey('//Alice');
- quartzParachainJunction = {Parachain: QUARTZ_CHAIN};
- quartzAccountJunction = {
- AccountId32: {
- network: 'Any',
- id: alice.addressRaw,
- },
- };
-
- quartzParachainMultilocation = {
- V2: {
- parents: 1,
- interior: {
- X1: quartzParachainJunction,
- },
- },
- };
- quartzAccountMultilocation = {
- V2: {
- parents: 0,
- interior: {
- X1: quartzAccountJunction,
- },
- },
- };
- quartzCombinedMultilocation = {
- V2: {
- parents: 1,
- interior: {
- X2: [quartzParachainJunction, quartzAccountJunction],
- },
- },
- };
-
// Set the default version to wrap the first message to other chains.
await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
});
-
- // eslint-disable-next-line require-await
- await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {
- alith = helper.account.alithAccount();
- });
});
itSub('Quartz rejects KAR tokens from Karura', async () => {
@@ -195,22 +130,12 @@
minimalBalance: 1n,
};
- let balanceQuartzTokenInit: bigint;
- let balanceQuartzTokenMiddle: bigint;
- let balanceQuartzTokenFinal: bigint;
- let balanceForeignQtzTokenInit: bigint;
- let balanceForeignQtzTokenMiddle: bigint;
- let balanceForeignQtzTokenFinal: bigint;
- let balanceMovrTokenInit: bigint;
- let balanceMovrTokenMiddle: bigint;
- let balanceMovrTokenFinal: bigint;
before(async () => {
await usingPlaygrounds(async (helper, privateKey) => {
alice = await privateKey('//Alice');
[randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);
- balanceForeignQtzTokenInit = 0n;
// Set the default version to wrap the first message to other chains.
await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
@@ -239,20 +164,22 @@
const isSufficient = true;
const unitsPerSecond = 1n;
const numAssetsWeightHint = 0;
+ if((await helper.assetManager.assetTypeId(quartzAssetLocation)).toJSON()) {
+ console.log('Quartz asset already registered on Moonriver');
+ } else {
+ const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
+ location: quartzAssetLocation,
+ metadata: quartzAssetMetadata,
+ existentialDeposit,
+ isSufficient,
+ unitsPerSecond,
+ numAssetsWeightHint,
+ });
- const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
- location: quartzAssetLocation,
- metadata: quartzAssetMetadata,
- existentialDeposit,
- isSufficient,
- unitsPerSecond,
- numAssetsWeightHint,
- });
-
- console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);
-
- await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);
+ console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);
+ await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);
+ }
// >>> Acquire Quartz AssetId Info on Moonriver >>>
console.log('Acquire Quartz AssetId Info on Moonriver.......');
@@ -267,13 +194,10 @@
await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);
console.log('Sponsoring random Account.......DONE');
// <<< Sponsoring random Account <<<
-
- balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);
});
await usingPlaygrounds(async (helper) => {
await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);
- balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);
});
});
@@ -296,7 +220,7 @@
describeXCM('[XCMLL] Integration test: Exchanging tokens with Shiden', () => {
let alice: IKeyringPair;
- let sender: IKeyringPair;
+ let randomAccount: IKeyringPair;
const QTZ_ASSET_ID_ON_SHIDEN = 1;
const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;
@@ -304,71 +228,69 @@
// Quartz -> Shiden
const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden
const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?
- const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ
- const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens
- // Shiden -> Quartz
- const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ
- const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 4.999_999_999_088_000_000n QTZ
- let balanceAfterQuartzToShidenXCM: bigint;
before(async () => {
await usingPlaygrounds(async (helper, privateKey) => {
alice = await privateKey('//Alice');
- [sender] = await helper.arrange.createAccounts([100n], alice);
- console.log('sender', sender.address);
+ randomAccount = helper.arrange.createEmptyAccount();
+ await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
+ console.log('sender: ', randomAccount.address);
// Set the default version to wrap the first message to other chains.
await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);
});
await usingShidenPlaygrounds(shidenUrl, async (helper) => {
- console.log('1. Create foreign asset and metadata');
- // TODO update metadata with values from production
- await helper.assets.create(
- alice,
- QTZ_ASSET_ID_ON_SHIDEN,
- alice.address,
- QTZ_MINIMAL_BALANCE_ON_SHIDEN,
- );
+ if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) {
+ console.log('1. Create foreign asset and metadata');
+ // TODO update metadata with values from production
+ await helper.assets.create(
+ alice,
+ QTZ_ASSET_ID_ON_SHIDEN,
+ alice.address,
+ QTZ_MINIMAL_BALANCE_ON_SHIDEN,
+ );
- await helper.assets.setMetadata(
- alice,
- QTZ_ASSET_ID_ON_SHIDEN,
- 'Cross chain QTZ',
- 'xcQTZ',
- Number(QTZ_DECIMALS),
- );
+ await helper.assets.setMetadata(
+ alice,
+ QTZ_ASSET_ID_ON_SHIDEN,
+ 'Cross chain QTZ',
+ 'xcQTZ',
+ Number(QTZ_DECIMALS),
+ );
- console.log('2. Register asset location on Shiden');
- const assetLocation = {
- V2: {
- parents: 1,
- interior: {
- X1: {
- Parachain: QUARTZ_CHAIN,
+ console.log('2. Register asset location on Shiden');
+ const assetLocation = {
+ V2: {
+ parents: 1,
+ interior: {
+ X1: {
+ Parachain: QUARTZ_CHAIN,
+ },
},
},
- },
- };
-
- await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);
+ };
- console.log('3. Set QTZ payment for XCM execution on Shiden');
- await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);
+ await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);
+ console.log('3. Set QTZ payment for XCM execution on Shiden');
+ await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);
+ } else {
+ console.log('QTZ is already registered on Shiden');
+ }
console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');
- await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);
+ await helper.balance.transferToSubstrate(alice, randomAccount.address, shidenInitialBalance);
});
});
itSub('Should connect and send QTZ to Shiden', async () => {
- await testHelper.sendUnqTo('shiden', sender);
+ await testHelper.sendUnqTo('shiden', randomAccount);
});
itSub('Should connect to Shiden and send QTZ back', async () => {
- await testHelper.sendUnqBack('shiden', alice, sender);
+ await testHelper.sendUnqBack('shiden', alice, randomAccount);
});
itSub('Shiden can send only up to its balance', async () => {
tests/src/xcm/lowLevelXcmUnique.test.tsdiffbeforeafterboth--- a/tests/src/xcm/lowLevelXcmUnique.test.ts
+++ b/tests/src/xcm/lowLevelXcmUnique.test.ts
@@ -16,320 +16,16 @@
import {IKeyringPair} from '@polkadot/types/types';
import config from '../config';
-import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds, requirePalletsOrSkip, Pallets} from '../util';
-import {Event} from '../util/playgrounds/unique.dev';
+import {itSub, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds, usingRelayPlaygrounds} from '../util';
import {nToBigInt} from '@polkadot/util';
import {hexToString} from '@polkadot/util';
-import {ASTAR_DECIMALS, NETWORKS, SAFE_XCM_VERSION, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl, expectFailedToTransact, expectUntrustedReserveLocationFail, getDevPlayground, mapToChainId, mapToChainUrl, maxWaitBlocks, moonbeamUrl, polkadexUrl, relayUrl, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types';
-
-
-const TRANSFER_AMOUNT = 2000000_000_000_000_000_000_000n;
-const SENDER_BUDGET = 2n * TRANSFER_AMOUNT;
-const SENDBACK_AMOUNT = TRANSFER_AMOUNT / 2n;
-const STAYED_ON_TARGET_CHAIN = TRANSFER_AMOUNT - SENDBACK_AMOUNT;
-const TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT = 100_000_000_000n;
-
-let balanceUniqueTokenInit: bigint;
-let balanceUniqueTokenMiddle: bigint;
-let balanceUniqueTokenFinal: bigint;
-let unqFees: bigint;
+import {ASTAR_DECIMALS, SAFE_XCM_VERSION, SENDER_BUDGET, UNIQUE_CHAIN, UNQ_DECIMALS, XcmTestHelper, acalaUrl, astarUrl, moonbeamUrl, polkadexUrl, relayUrl, uniqueAssetId} from './xcm.types';
const testHelper = new XcmTestHelper('unique');
-
-async function genericSendUnqTo(
- networkName: keyof typeof NETWORKS,
- randomAccount: IKeyringPair,
- randomAccountOnTargetChain = randomAccount,
-) {
- const networkUrl = mapToChainUrl(networkName);
- const targetPlayground = getDevPlayground(networkName);
- await usingPlaygrounds(async (helper) => {
- balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
- const destination = {
- V2: {
- parents: 1,
- interior: {
- X1: {
- Parachain: mapToChainId(networkName),
- },
- },
- },
- };
-
- const beneficiary = {
- V2: {
- parents: 0,
- interior: {
- X1: (
- networkName == 'moonbeam' ?
- {
- AccountKey20: {
- network: 'Any',
- key: randomAccountOnTargetChain.address,
- },
- }
- :
- {
- AccountId32: {
- network: 'Any',
- id: randomAccountOnTargetChain.addressRaw,
- },
- }
- ),
- },
- },
- };
-
- const assets = {
- V2: [
- {
- id: {
- Concrete: {
- parents: 0,
- interior: 'Here',
- },
- },
- fun: {
- Fungible: TRANSFER_AMOUNT,
- },
- },
- ],
- };
- const feeAssetItem = 0;
-
- await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');
- const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);
-
- unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;
- console.log('[Unique -> %s] transaction fees on Unique: %s UNQ', networkName, helper.util.bigIntToDecimals(unqFees));
- expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;
-
- await targetPlayground(networkUrl, async (helper) => {
- /*
- Since only the parachain part of the Polkadex
- infrastructure is launched (without their
- solochain validators), processing incoming
- assets will lead to an error.
- This error indicates that the Polkadex chain
- received a message from the Unique network,
- since the hash is being checked to ensure
- it matches what was sent.
- */
- if(networkName == 'polkadex') {
- await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);
- } else {
- await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == messageSent.messageHash);
- }
- });
-
- });
-}
-
-async function genericSendUnqBack(
- networkName: keyof typeof NETWORKS,
- sudoer: IKeyringPair,
- randomAccountOnUnq: IKeyringPair,
-) {
- const networkUrl = mapToChainUrl(networkName);
-
- const targetPlayground = getDevPlayground(networkName);
- await usingPlaygrounds(async (helper) => {
-
- const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
- randomAccountOnUnq.addressRaw,
- uniqueAssetId,
- SENDBACK_AMOUNT,
- );
-
- let xcmProgramSent: any;
-
- await targetPlayground(networkUrl, async (helper) => {
- if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, xcmProgram);
- xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- } else if('fastDemocracy' in helper) {
- const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, xcmProgram]);
- // Needed to bypass the call filter.
- const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
- await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);
- xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- }
- });
-
- await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);
-
- balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountOnUnq.address);
-
- expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees - STAYED_ON_TARGET_CHAIN);
-
- });
-}
-async function genericSendOnlyOwnedBalance(
- networkName: keyof typeof NETWORKS,
- sudoer: IKeyringPair,
-) {
- const networkUrl = mapToChainUrl(networkName);
- const targetPlayground = getDevPlayground(networkName);
- const targetChainBalance = 10000n * (10n ** UNQ_DECIMALS);
- await usingPlaygrounds(async (helper) => {
- const targetChainSovereignAccount = helper.address.paraSiblingSovereignAccount(mapToChainId(networkName));
- await helper.getSudo().balance.setBalanceSubstrate(sudoer, targetChainSovereignAccount, targetChainBalance);
- const moreThanTargetChainHas = 2n * targetChainBalance;
-
- const targetAccount = helper.arrange.createEmptyAccount();
-
- const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(
- targetAccount.addressRaw,
- {
- Concrete: {
- parents: 0,
- interior: 'Here',
- },
- },
- moreThanTargetChainHas,
- );
-
- let maliciousXcmProgramSent: any;
-
-
- await targetPlayground(networkUrl, async (helper) => {
- if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgram);
- maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- } else if('fastDemocracy' in helper) {
- const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);
- // Needed to bypass the call filter.
- const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
- await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);
- maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- }
- });
-
- await expectFailedToTransact(helper, maliciousXcmProgramSent);
-
- const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);
- expect(targetAccountBalance).to.be.equal(0n);
- });
-}
-
-async function genericReserveTransferUNQfrom(netwokrName: keyof typeof NETWORKS, sudoer: IKeyringPair) {
- const networkUrl = mapToChainUrl(netwokrName);
- const targetPlayground = getDevPlayground(netwokrName);
-
- await usingPlaygrounds(async (helper) => {
- const testAmount = 10_000n * (10n ** UNQ_DECIMALS);
- const targetAccount = helper.arrange.createEmptyAccount();
-
- const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
- targetAccount.addressRaw,
- uniqueAssetId,
- testAmount,
- );
-
- const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(
- targetAccount.addressRaw,
- {
- Concrete: {
- parents: 0,
- interior: 'Here',
- },
- },
- testAmount,
- );
-
- let maliciousXcmProgramFullIdSent: any;
- let maliciousXcmProgramHereIdSent: any;
- const maxWaitBlocks = 3;
-
- // Try to trick Unique using full UNQ identification
- await targetPlayground(networkUrl, async (helper) => {
- if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramFullId);
- maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- }
- // Moonbeam case
- else if('fastDemocracy' in helper) {
- const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);
- // Needed to bypass the call filter.
- const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
- await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using path asset identification`,batchCall);
-
- maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- }
- });
-
-
- await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);
-
- let accountBalance = await helper.balance.getSubstrate(targetAccount.address);
- expect(accountBalance).to.be.equal(0n);
-
- // Try to trick Unique using shortened UNQ identification
- await targetPlayground(networkUrl, async (helper) => {
- if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoer, uniqueVersionedMultilocation, maliciousXcmProgramHereId);
- maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- }
- else if('fastDemocracy' in helper) {
- const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);
- // Needed to bypass the call filter.
- const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
- await helper.fastDemocracy.executeProposal(`${netwokrName} try to act like a reserve location for UNQ using "here" asset identification`, batchCall);
-
- maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- }
- });
-
- await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);
-
- accountBalance = await helper.balance.getSubstrate(targetAccount.address);
- expect(accountBalance).to.be.equal(0n);
- });
-}
-
-async function genericRejectNativeTokensFrom(networkName: keyof typeof NETWORKS, sudoerOnTargetChain: IKeyringPair) {
- const networkUrl = mapToChainUrl(networkName);
- const targetPlayground = getDevPlayground(networkName);
- let messageSent: any;
-
- await usingPlaygrounds(async (helper) => {
- const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(
- helper.arrange.createEmptyAccount().addressRaw,
- {
- Concrete: {
- parents: 1,
- interior: {
- X1: {
- Parachain: mapToChainId(networkName),
- },
- },
- },
- },
- TARGET_CHAIN_TOKEN_TRANSFER_AMOUNT,
- );
- await targetPlayground(networkUrl, async (helper) => {
- if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueVersionedMultilocation, maliciousXcmProgramFullId);
- messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- } else if('fastDemocracy' in helper) {
- const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);
- // Needed to bypass the call filter.
- const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);
- await helper.fastDemocracy.executeProposal(`${networkName} sending native tokens to the Unique via fast democracy`, batchCall);
-
- messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
- }
- });
- await expectFailedToTransact(helper, messageSent);
- });
-}
-
-
describeXCM('[XCMLL] Integration test: Exchanging tokens with Acala', () => {
let alice: IKeyringPair;
let randomAccount: IKeyringPair;
@@ -375,24 +71,23 @@
await usingPlaygrounds(async (helper) => {
await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
- balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
});
});
itSub('Should connect and send UNQ to Acala', async () => {
- await genericSendUnqTo('acala', randomAccount);
+ await testHelper.sendUnqTo('acala', randomAccount);
});
itSub('Should connect to Acala and send UNQ back', async () => {
- await genericSendUnqBack('acala', alice, randomAccount);
+ await testHelper.sendUnqBack('acala', alice, randomAccount);
});
itSub('Acala can send only up to its balance', async () => {
- await genericSendOnlyOwnedBalance('acala', alice);
+ await testHelper.sendOnlyOwnedBalance('acala', alice);
});
itSub('Should not accept reserve transfer of UNQ from Acala', async () => {
- await genericReserveTransferUNQfrom('acala', alice);
+ await testHelper.reserveTransferUNQfrom('acala', alice);
});
});
@@ -430,7 +125,6 @@
await usingPlaygrounds(async (helper) => {
await helper.balance.transferToSubstrate(alice, randomAccount.address, SENDER_BUDGET);
- balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);
});
});
@@ -467,19 +161,19 @@
});
itSub('Unique rejects ACA tokens from Acala', async () => {
- await genericRejectNativeTokensFrom('acala', alice);
+ await testHelper.rejectNativeTokensFrom('acala', alice);
});
itSub('Unique rejects GLMR tokens from Moonbeam', async () => {
- await genericRejectNativeTokensFrom('moonbeam', alice);
+ await testHelper.rejectNativeTokensFrom('moonbeam', alice);
});
itSub('Unique rejects ASTR tokens from Astar', async () => {
- await genericRejectNativeTokensFrom('astar', alice);
+ await testHelper.rejectNativeTokensFrom('astar', alice);
});
itSub('Unique rejects PDX tokens from Polkadex', async () => {
- await genericRejectNativeTokensFrom('polkadex', alice);
+ await testHelper.rejectNativeTokensFrom('polkadex', alice);
});
});
@@ -570,7 +264,6 @@
await usingPlaygrounds(async (helper) => {
await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, SENDER_BUDGET);
- balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);
});
});
@@ -657,19 +350,19 @@
});
itSub('Should connect and send UNQ to Astar', async () => {
- await genericSendUnqTo('astar', randomAccount);
+ await testHelper.sendUnqTo('astar', randomAccount);
});
itSub('Should connect to Astar and send UNQ back', async () => {
- await genericSendUnqBack('astar', alice, randomAccount);
+ await testHelper.sendUnqBack('astar', alice, randomAccount);
});
itSub('Astar can send only up to its balance', async () => {
- await genericSendOnlyOwnedBalance('astar', alice);
+ await testHelper.sendOnlyOwnedBalance('astar', alice);
});
itSub('Should not accept reserve transfer of UNQ from Astar', async () => {
- await genericReserveTransferUNQfrom('astar', alice);
+ await testHelper.reserveTransferUNQfrom('astar', alice);
});
});
tests/src/xcm/xcm.types.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.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, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, 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} from './xcm.types';21222324const STATEMINE_PALLET_INSTANCE = 50;2526const TRANSFER_AMOUNT = 2000000000000000000000000n;2728const FUNDING_AMOUNT = 3_500_000_0000_000_000n;2930const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;3132const USDT_ASSET_ID = 100;33const USDT_ASSET_METADATA_DECIMALS = 18;34const USDT_ASSET_METADATA_NAME = 'USDT';35const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';36const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;37const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;3839const SAFE_XCM_VERSION = 2;4041describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {42 let alice: IKeyringPair;43 let bob: IKeyringPair;4445 let balanceStmnBefore: bigint;46 let balanceStmnAfter: bigint;4748 let balanceQuartzBefore: bigint;49 let balanceQuartzAfter: bigint;50 let balanceQuartzFinal: bigint;5152 let balanceBobBefore: bigint;53 let balanceBobAfter: bigint;54 let balanceBobFinal: bigint;5556 let balanceBobRelayTokenBefore: bigint;57 let balanceBobRelayTokenAfter: bigint;585960 before(async () => {61 await usingPlaygrounds(async (helper, privateKey) => {62 alice = await privateKey('//Alice');63 bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor6465 // Set the default version to wrap the first message to other chains.66 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);67 });6869 await usingRelayPlaygrounds(relayUrl, async (helper) => {70 // Fund accounts on Statemine(t)71 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);72 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);73 });7475 await usingStateminePlaygrounds(statemineUrl, async (helper) => {76 const sovereignFundingAmount = 3_500_000_000n;7778 await helper.assets.create(79 alice,80 USDT_ASSET_ID,81 alice.address,82 USDT_ASSET_METADATA_MINIMAL_BALANCE,83 );84 await helper.assets.setMetadata(85 alice,86 USDT_ASSET_ID,87 USDT_ASSET_METADATA_NAME,88 USDT_ASSET_METADATA_DESCRIPTION,89 USDT_ASSET_METADATA_DECIMALS,90 );91 await helper.assets.mint(92 alice,93 USDT_ASSET_ID,94 alice.address,95 USDT_ASSET_AMOUNT,96 );9798 // funding parachain sovereing account on Statemine(t).99 // The sovereign account should be created before any action100 // (the assets pallet on Statemine(t) check if the sovereign account exists)101 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);102 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);103 });104105106 await usingPlaygrounds(async (helper) => {107 const location = {108 V2: {109 parents: 1,110 interior: {X3: [111 {112 Parachain: STATEMINE_CHAIN,113 },114 {115 PalletInstance: STATEMINE_PALLET_INSTANCE,116 },117 {118 GeneralIndex: USDT_ASSET_ID,119 },120 ]},121 },122 };123124 const metadata =125 {126 name: USDT_ASSET_ID,127 symbol: USDT_ASSET_METADATA_NAME,128 decimals: USDT_ASSET_METADATA_DECIMALS,129 minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,130 };131 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);132 balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);133 });134135136 // Providing the relay currency to the quartz sender account137 // (fee for USDT XCM are paid in relay tokens)138 await usingRelayPlaygrounds(relayUrl, async (helper) => {139 const destination = {140 V2: {141 parents: 0,142 interior: {X1: {143 Parachain: QUARTZ_CHAIN,144 },145 },146 }};147148 const beneficiary = {149 V2: {150 parents: 0,151 interior: {X1: {152 AccountId32: {153 network: 'Any',154 id: alice.addressRaw,155 },156 }},157 },158 };159160 const assets = {161 V2: [162 {163 id: {164 Concrete: {165 parents: 0,166 interior: 'Here',167 },168 },169 fun: {170 Fungible: TRANSFER_AMOUNT_RELAY,171 },172 },173 ],174 };175176 const feeAssetItem = 0;177178 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');179 });180181 });182183 itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {184 await usingStateminePlaygrounds(statemineUrl, async (helper) => {185 const dest = {186 V2: {187 parents: 1,188 interior: {X1: {189 Parachain: QUARTZ_CHAIN,190 },191 },192 }};193194 const beneficiary = {195 V2: {196 parents: 0,197 interior: {X1: {198 AccountId32: {199 network: 'Any',200 id: alice.addressRaw,201 },202 }},203 },204 };205206 const assets = {207 V2: [208 {209 id: {210 Concrete: {211 parents: 0,212 interior: {213 X2: [214 {215 PalletInstance: STATEMINE_PALLET_INSTANCE,216 },217 {218 GeneralIndex: USDT_ASSET_ID,219 },220 ]},221 },222 },223 fun: {224 Fungible: TRANSFER_AMOUNT,225 },226 },227 ],228 };229230 const feeAssetItem = 0;231232 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);233 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');234235 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);236237 // common good parachain take commission in it native token238 console.log(239 '[Statemine -> Quartz] transaction fees on Statemine: %s WND',240 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),241 );242 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;243244 });245246247 // ensure that asset has been delivered248 await helper.wait.newBlocks(3);249250 // expext collection id will be with id 1251 const free = await helper.ft.getBalance(1, {Substrate: alice.address});252253 balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);254255 console.log(256 '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',257 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),258 );259 console.log(260 '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',261 helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),262 );263 // commission has not paid in USDT token264 expect(free).to.be.equal(TRANSFER_AMOUNT);265 // ... and parachain native token266 expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;267 });268269 itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {270 const destination = {271 V2: {272 parents: 1,273 interior: {X2: [274 {275 Parachain: STATEMINE_CHAIN,276 },277 {278 AccountId32: {279 network: 'Any',280 id: alice.addressRaw,281 },282 },283 ]},284 },285 };286287 const relayFee = 400_000_000_000_000n;288 const currencies: [any, bigint][] = [289 [290 {291 ForeignAssetId: 0,292 },293 TRANSFER_AMOUNT,294 ],295 [296 {297 NativeAssetId: 'Parent',298 },299 relayFee,300 ],301 ];302303 const feeItem = 1;304305 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');306307 // the commission has been paid in parachain native token308 balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);309 console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzFinal));310 expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;311312 await usingStateminePlaygrounds(statemineUrl, async (helper) => {313 await helper.wait.newBlocks(3);314315 // The USDT token never paid fees. Its amount not changed from begin value.316 // Also check that xcm transfer has been succeeded317 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;318 });319 });320321 itSub('Should connect and send Relay token to Quartz', async ({helper}) => {322 balanceBobBefore = await helper.balance.getSubstrate(bob.address);323 balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});324325 await usingRelayPlaygrounds(relayUrl, async (helper) => {326 const destination = {327 V2: {328 parents: 0,329 interior: {X1: {330 Parachain: QUARTZ_CHAIN,331 },332 },333 }};334335 const beneficiary = {336 V2: {337 parents: 0,338 interior: {X1: {339 AccountId32: {340 network: 'Any',341 id: bob.addressRaw,342 },343 }},344 },345 };346347 const assets = {348 V2: [349 {350 id: {351 Concrete: {352 parents: 0,353 interior: 'Here',354 },355 },356 fun: {357 Fungible: TRANSFER_AMOUNT_RELAY,358 },359 },360 ],361 };362363 const feeAssetItem = 0;364365 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');366 });367368 await helper.wait.newBlocks(3);369370 balanceBobAfter = await helper.balance.getSubstrate(bob.address);371 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});372373 const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;374 const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;375 console.log(376 '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',377 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),378 );379 console.log(380 '[Relay (Westend) -> Quartz] transaction fees: %s WND',381 helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),382 );383 console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);384 expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;385 expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;386 });387388 itSub('Should connect and send Relay token back', async ({helper}) => {389 let relayTokenBalanceBefore: bigint;390 let relayTokenBalanceAfter: bigint;391 await usingRelayPlaygrounds(relayUrl, async (helper) => {392 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);393 });394395 const destination = {396 V2: {397 parents: 1,398 interior: {399 X1:{400 AccountId32: {401 network: 'Any',402 id: bob.addressRaw,403 },404 },405 },406 },407 };408409 const currencies: any = [410 [411 {412 NativeAssetId: 'Parent',413 },414 TRANSFER_AMOUNT_RELAY,415 ],416 ];417418 const feeItem = 0;419420 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');421422 balanceBobFinal = await helper.balance.getSubstrate(bob.address);423 console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));424425 await usingRelayPlaygrounds(relayUrl, async (helper) => {426 await helper.wait.newBlocks(10);427 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);428429 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;430 console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));431 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;432 });433 });434});435436describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {437 let alice: IKeyringPair;438 let randomAccount: IKeyringPair;439440 let balanceQuartzTokenInit: bigint;441 let balanceQuartzTokenMiddle: bigint;442 let balanceQuartzTokenFinal: bigint;443 let balanceKaruraTokenInit: bigint;444 let balanceKaruraTokenMiddle: bigint;445 let balanceKaruraTokenFinal: bigint;446 let balanceQuartzForeignTokenInit: bigint;447 let balanceQuartzForeignTokenMiddle: bigint;448 let balanceQuartzForeignTokenFinal: bigint;449450 // computed by a test transfer from prod Quartz to prod Karura.451 // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9452 // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)453 const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;454 const karuraEps = 8n * 10n ** 16n;455456 let karuraBackwardTransferAmount: bigint;457458 before(async () => {459 await usingPlaygrounds(async (helper, privateKey) => {460 alice = await privateKey('//Alice');461 [randomAccount] = await helper.arrange.createAccounts([0n], alice);462463 // Set the default version to wrap the first message to other chains.464 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);465 });466467 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {468 const destination = {469 V2: {470 parents: 1,471 interior: {472 X1: {473 Parachain: QUARTZ_CHAIN,474 },475 },476 },477 };478479 const metadata = {480 name: 'Quartz',481 symbol: 'QTZ',482 decimals: 18,483 minimalBalance: 1000000000000000000n,484 };485486 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);487 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);488 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);489 balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});490 });491492 await usingPlaygrounds(async (helper) => {493 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);494 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);495 });496 });497498 itSub('Should connect and send QTZ to Karura', async ({helper}) => {499 const destination = {500 V2: {501 parents: 1,502 interior: {503 X1: {504 Parachain: KARURA_CHAIN,505 },506 },507 },508 };509510 const beneficiary = {511 V2: {512 parents: 0,513 interior: {514 X1: {515 AccountId32: {516 network: 'Any',517 id: randomAccount.addressRaw,518 },519 },520 },521 },522 };523524 const assets = {525 V2: [526 {527 id: {528 Concrete: {529 parents: 0,530 interior: 'Here',531 },532 },533 fun: {534 Fungible: TRANSFER_AMOUNT,535 },536 },537 ],538 };539540 const feeAssetItem = 0;541542 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');543 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);544545 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;546 expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;547 console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));548549 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {550 await helper.wait.newBlocks(3);551552 balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});553 balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);554555 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;556 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;557 karuraBackwardTransferAmount = qtzIncomeTransfer;558559 const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer;560561 console.log(562 '[Quartz -> Karura] transaction fees on Karura: %s KAR',563 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),564 );565 console.log(566 '[Quartz -> Karura] transaction fees on Karura: %s QTZ',567 helper.util.bigIntToDecimals(karUnqFees),568 );569 console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));570 expect(karFees == 0n).to.be.true;571572 const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;573574 expect(575 bigintAbs(karUnqFees - expectedKaruraIncomeFee) < karuraEps,576 'Karura took different income fee, check the Karura foreign asset config',577 ).to.be.true;578 });579 });580581 itSub('Should connect to Karura and send QTZ back', async ({helper}) => {582 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {583 const destination = {584 V2: {585 parents: 1,586 interior: {587 X2: [588 {Parachain: QUARTZ_CHAIN},589 {590 AccountId32: {591 network: 'Any',592 id: randomAccount.addressRaw,593 },594 },595 ],596 },597 },598 };599600 const id = {601 ForeignAsset: 0,602 };603604 await helper.xTokens.transfer(randomAccount, id, karuraBackwardTransferAmount, destination, 'Unlimited');605 balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);606 balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);607608 const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;609 const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;610611 console.log(612 '[Karura -> Quartz] transaction fees on Karura: %s KAR',613 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),614 );615 console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));616617 expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;618 expect(qtzOutcomeTransfer == karuraBackwardTransferAmount).to.be.true;619 });620621 await helper.wait.newBlocks(3);622623 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);624 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;625 expect(actuallyDelivered > 0).to.be.true;626627 console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));628629 const qtzFees = karuraBackwardTransferAmount - actuallyDelivered;630 console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));631 expect(qtzFees == 0n).to.be.true;632 });633634 itSub('Karura can send only up to its balance', async ({helper}) => {635 // set Karura's sovereign account's balance636 const karuraBalance = 10000n * (10n ** QTZ_DECIMALS);637 const karuraSovereignAccount = helper.address.paraSiblingSovereignAccount(KARURA_CHAIN);638 await helper.getSudo().balance.setBalanceSubstrate(alice, karuraSovereignAccount, karuraBalance);639640 const moreThanKaruraHas = karuraBalance * 2n;641642 let targetAccountBalance = 0n;643 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);644645 const quartzMultilocation = {646 V2: {647 parents: 1,648 interior: {649 X1: {Parachain: QUARTZ_CHAIN},650 },651 },652 };653654 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(655 targetAccount.addressRaw,656 {657 Concrete: {658 parents: 0,659 interior: 'Here',660 },661 },662 moreThanKaruraHas,663 );664665 let maliciousXcmProgramSent: any;666 const maxWaitBlocks = 5;667668 // Try to trick Quartz669 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {670 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);671672 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);673 });674675 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash676 && event.outcome.isFailedToTransactAsset);677678 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);679 expect(targetAccountBalance).to.be.equal(0n);680681 // But Karura still can send the correct amount682 const validTransferAmount = karuraBalance / 2n;683 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(684 targetAccount.addressRaw,685 {686 Concrete: {687 parents: 0,688 interior: 'Here',689 },690 },691 validTransferAmount,692 );693694 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {695 await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);696 });697698 await helper.wait.newBlocks(maxWaitBlocks);699700 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);701 expect(targetAccountBalance).to.be.equal(validTransferAmount);702 });703704 itSub('Should not accept reserve transfer of QTZ from Karura', async ({helper}) => {705 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);706 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);707708 const quartzMultilocation = {709 V2: {710 parents: 1,711 interior: {712 X1: {713 Parachain: QUARTZ_CHAIN,714 },715 },716 },717 };718719 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(720 targetAccount.addressRaw,721 {722 Concrete: {723 parents: 1,724 interior: {725 X1: {726 Parachain: QUARTZ_CHAIN,727 },728 },729 },730 },731 testAmount,732 );733734 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(735 targetAccount.addressRaw,736 {737 Concrete: {738 parents: 0,739 interior: 'Here',740 },741 },742 testAmount,743 );744745 let maliciousXcmProgramFullIdSent: any;746 let maliciousXcmProgramHereIdSent: any;747 const maxWaitBlocks = 3;748749 // Try to trick Quartz using full QTZ identification750 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {751 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);752753 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);754 });755756 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash757 && event.outcome.isUntrustedReserveLocation);758759 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);760 expect(accountBalance).to.be.equal(0n);761762 // Try to trick Quartz using shortened QTZ identification763 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {764 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);765766 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);767 });768769 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash770 && event.outcome.isUntrustedReserveLocation);771772 accountBalance = await helper.balance.getSubstrate(targetAccount.address);773 expect(accountBalance).to.be.equal(0n);774 });775});776777// These tests are relevant only when778// the the corresponding foreign assets are not registered779describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {780 let alice: IKeyringPair;781 let alith: IKeyringPair;782783 const testAmount = 100_000_000_000n;784 let quartzParachainJunction;785 let quartzAccountJunction;786787 let quartzParachainMultilocation: any;788 let quartzAccountMultilocation: any;789 let quartzCombinedMultilocation: any;790791 let messageSent: any;792793 const maxWaitBlocks = 3;794795 before(async () => {796 await usingPlaygrounds(async (helper, privateKey) => {797 alice = await privateKey('//Alice');798799 quartzParachainJunction = {Parachain: QUARTZ_CHAIN};800 quartzAccountJunction = {801 AccountId32: {802 network: 'Any',803 id: alice.addressRaw,804 },805 };806807 quartzParachainMultilocation = {808 V2: {809 parents: 1,810 interior: {811 X1: quartzParachainJunction,812 },813 },814 };815816 quartzAccountMultilocation = {817 V2: {818 parents: 0,819 interior: {820 X1: quartzAccountJunction,821 },822 },823 };824825 quartzCombinedMultilocation = {826 V2: {827 parents: 1,828 interior: {829 X2: [quartzParachainJunction, quartzAccountJunction],830 },831 },832 };833834 // Set the default version to wrap the first message to other chains.835 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);836 });837838 // eslint-disable-next-line require-await839 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {840 alith = helper.account.alithAccount();841 });842 });843844 const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {845 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash846 && event.outcome.isFailedToTransactAsset);847 };848849 itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {850 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {851 const id = {852 Token: 'KAR',853 };854 const destination = quartzCombinedMultilocation;855 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');856857 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);858 });859860 await expectFailedToTransact(helper, messageSent);861 });862863 itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => {864 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {865 const id = 'SelfReserve';866 const destination = quartzCombinedMultilocation;867 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');868869 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);870 });871872 await expectFailedToTransact(helper, messageSent);873 });874875 itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => {876 await usingShidenPlaygrounds(shidenUrl, async (helper) => {877 const destinationParachain = quartzParachainMultilocation;878 const beneficiary = quartzAccountMultilocation;879 const assets = {880 V2: [{881 id: {882 Concrete: {883 parents: 0,884 interior: 'Here',885 },886 },887 fun: {888 Fungible: testAmount,889 },890 }],891 };892 const feeAssetItem = 0;893894 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [895 destinationParachain,896 beneficiary,897 assets,898 feeAssetItem,899 ]);900901 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);902 });903904 await expectFailedToTransact(helper, messageSent);905 });906});907908describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {909 // Quartz constants910 let alice: IKeyringPair;911 let quartzAssetLocation;912913 let randomAccountQuartz: IKeyringPair;914 let randomAccountMoonriver: IKeyringPair;915916 // Moonriver constants917 let assetId: string;918919 const quartzAssetMetadata = {920 name: 'xcQuartz',921 symbol: 'xcQTZ',922 decimals: 18,923 isFrozen: false,924 minimalBalance: 1n,925 };926927 let balanceQuartzTokenInit: bigint;928 let balanceQuartzTokenMiddle: bigint;929 let balanceQuartzTokenFinal: bigint;930 let balanceForeignQtzTokenInit: bigint;931 let balanceForeignQtzTokenMiddle: bigint;932 let balanceForeignQtzTokenFinal: bigint;933 let balanceMovrTokenInit: bigint;934 let balanceMovrTokenMiddle: bigint;935 let balanceMovrTokenFinal: bigint;936937 before(async () => {938 await usingPlaygrounds(async (helper, privateKey) => {939 alice = await privateKey('//Alice');940 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);941942 balanceForeignQtzTokenInit = 0n;943944 // Set the default version to wrap the first message to other chains.945 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);946 });947948 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {949 const alithAccount = helper.account.alithAccount();950 const baltatharAccount = helper.account.baltatharAccount();951 const dorothyAccount = helper.account.dorothyAccount();952953 randomAccountMoonriver = helper.account.create();954955 // >>> Sponsoring Dorothy >>>956 console.log('Sponsoring Dorothy.......');957 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);958 console.log('Sponsoring Dorothy.......DONE');959 // <<< Sponsoring Dorothy <<<960961 quartzAssetLocation = {962 XCM: {963 parents: 1,964 interior: {X1: {Parachain: QUARTZ_CHAIN}},965 },966 };967 const existentialDeposit = 1n;968 const isSufficient = true;969 const unitsPerSecond = 1n;970 const numAssetsWeightHint = 0;971972 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({973 location: quartzAssetLocation,974 metadata: quartzAssetMetadata,975 existentialDeposit,976 isSufficient,977 unitsPerSecond,978 numAssetsWeightHint,979 });980981 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);982983 await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);984985 // >>> Acquire Quartz AssetId Info on Moonriver >>>986 console.log('Acquire Quartz AssetId Info on Moonriver.......');987988 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();989990 console.log('QTZ asset ID is %s', assetId);991 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');992 // >>> Acquire Quartz AssetId Info on Moonriver >>>993994 // >>> Sponsoring random Account >>>995 console.log('Sponsoring random Account.......');996 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);997 console.log('Sponsoring random Account.......DONE');998 // <<< Sponsoring random Account <<<9991000 balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);1001 });10021003 await usingPlaygrounds(async (helper) => {1004 await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);1005 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);1006 });1007 });10081009 itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {1010 const currencyId = {1011 NativeAssetId: 'Here',1012 };1013 const dest = {1014 V2: {1015 parents: 1,1016 interior: {1017 X2: [1018 {Parachain: MOONRIVER_CHAIN},1019 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},1020 ],1021 },1022 },1023 };1024 const amount = TRANSFER_AMOUNT;10251026 await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');10271028 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);1029 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;10301031 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;1032 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));1033 expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;10341035 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1036 await helper.wait.newBlocks(3);10371038 balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);10391040 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;1041 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));1042 expect(movrFees == 0n).to.be.true;10431044 balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);1045 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;1046 console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));1047 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1048 });1049 });10501051 itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {1052 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1053 const asset = {1054 V2: {1055 id: {1056 Concrete: {1057 parents: 1,1058 interior: {1059 X1: {Parachain: QUARTZ_CHAIN},1060 },1061 },1062 },1063 fun: {1064 Fungible: TRANSFER_AMOUNT,1065 },1066 },1067 };1068 const destination = {1069 V2: {1070 parents: 1,1071 interior: {1072 X2: [1073 {Parachain: QUARTZ_CHAIN},1074 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},1075 ],1076 },1077 },1078 };10791080 await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');10811082 balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);10831084 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;1085 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));1086 expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;10871088 const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);10891090 expect(qtzRandomAccountAsset).to.be.null;10911092 balanceForeignQtzTokenFinal = 0n;10931094 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;1095 console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));1096 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1097 });10981099 await helper.wait.newBlocks(3);11001101 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);1102 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;1103 expect(actuallyDelivered > 0).to.be.true;11041105 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));11061107 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;1108 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));1109 expect(qtzFees == 0n).to.be.true;1110 });11111112 itSub('Moonriver can send only up to its balance', async ({helper}) => {1113 // set Moonriver's sovereign account's balance1114 const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS);1115 const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN);1116 await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance);11171118 const moreThanMoonriverHas = moonriverBalance * 2n;11191120 let targetAccountBalance = 0n;1121 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);11221123 const quartzMultilocation = {1124 V2: {1125 parents: 1,1126 interior: {1127 X1: {Parachain: QUARTZ_CHAIN},1128 },1129 },1130 };11311132 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1133 targetAccount.addressRaw,1134 {1135 Concrete: {1136 parents: 0,1137 interior: 'Here',1138 },1139 },1140 moreThanMoonriverHas,1141 );11421143 let maliciousXcmProgramSent: any;1144 const maxWaitBlocks = 3;11451146 // Try to trick Quartz1147 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1148 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);11491150 // Needed to bypass the call filter.1151 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1152 await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver has', batchCall);11531154 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1155 });11561157 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1158 && event.outcome.isFailedToTransactAsset);11591160 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1161 expect(targetAccountBalance).to.be.equal(0n);11621163 // But Moonriver still can send the correct amount1164 const validTransferAmount = moonriverBalance / 2n;1165 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1166 targetAccount.addressRaw,1167 {1168 Concrete: {1169 parents: 0,1170 interior: 'Here',1171 },1172 },1173 validTransferAmount,1174 );11751176 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1177 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]);11781179 // Needed to bypass the call filter.1180 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1181 await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall);1182 });11831184 await helper.wait.newBlocks(maxWaitBlocks);11851186 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1187 expect(targetAccountBalance).to.be.equal(validTransferAmount);1188 });11891190 itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {1191 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1192 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);11931194 const quartzMultilocation = {1195 V2: {1196 parents: 1,1197 interior: {1198 X1: {1199 Parachain: QUARTZ_CHAIN,1200 },1201 },1202 },1203 };12041205 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1206 targetAccount.addressRaw,1207 {1208 Concrete: {1209 parents: 0,1210 interior: {1211 X1: {1212 Parachain: QUARTZ_CHAIN,1213 },1214 },1215 },1216 },1217 testAmount,1218 );12191220 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1221 targetAccount.addressRaw,1222 {1223 Concrete: {1224 parents: 0,1225 interior: 'Here',1226 },1227 },1228 testAmount,1229 );12301231 let maliciousXcmProgramFullIdSent: any;1232 let maliciousXcmProgramHereIdSent: any;1233 const maxWaitBlocks = 3;12341235 // Try to trick Quartz using full QTZ identification1236 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1237 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);12381239 // Needed to bypass the call filter.1240 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1241 await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using path asset identification', batchCall);12421243 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1244 });12451246 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1247 && event.outcome.isUntrustedReserveLocation);12481249 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1250 expect(accountBalance).to.be.equal(0n);12511252 // Try to trick Quartz using shortened QTZ identification1253 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1254 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]);12551256 // Needed to bypass the call filter.1257 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1258 await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using "here" asset identification', batchCall);12591260 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1261 });12621263 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1264 && event.outcome.isUntrustedReserveLocation);12651266 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1267 expect(accountBalance).to.be.equal(0n);1268 });1269});12701271describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => {1272 let alice: IKeyringPair;1273 let sender: IKeyringPair;12741275 const QTZ_ASSET_ID_ON_SHIDEN = 1;1276 const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;12771278 // Quartz -> Shiden1279 const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden1280 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1281 const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ1282 const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens12831284 // Shiden -> Quartz1285 const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ1286 const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 4.999_999_999_088_000_000n QTZ12871288 let balanceAfterQuartzToShidenXCM: bigint;12891290 before(async () => {1291 await usingPlaygrounds(async (helper, privateKey) => {1292 alice = await privateKey('//Alice');1293 [sender] = await helper.arrange.createAccounts([100n], alice);1294 console.log('sender', sender.address);12951296 // Set the default version to wrap the first message to other chains.1297 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1298 });12991300 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1301 console.log('1. Create foreign asset and metadata');1302 // TODO update metadata with values from production1303 await helper.assets.create(1304 alice,1305 QTZ_ASSET_ID_ON_SHIDEN,1306 alice.address,1307 QTZ_MINIMAL_BALANCE_ON_SHIDEN,1308 );13091310 await helper.assets.setMetadata(1311 alice,1312 QTZ_ASSET_ID_ON_SHIDEN,1313 'Cross chain QTZ',1314 'xcQTZ',1315 Number(QTZ_DECIMALS),1316 );13171318 console.log('2. Register asset location on Shiden');1319 const assetLocation = {1320 V2: {1321 parents: 1,1322 interior: {1323 X1: {1324 Parachain: QUARTZ_CHAIN,1325 },1326 },1327 },1328 };13291330 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);13311332 console.log('3. Set QTZ payment for XCM execution on Shiden');1333 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);13341335 console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');1336 await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);1337 });1338 });13391340 itSub('Should connect and send QTZ to Shiden', async ({helper}) => {1341 const destination = {1342 V2: {1343 parents: 1,1344 interior: {1345 X1: {1346 Parachain: SHIDEN_CHAIN,1347 },1348 },1349 },1350 };13511352 const beneficiary = {1353 V2: {1354 parents: 0,1355 interior: {1356 X1: {1357 AccountId32: {1358 network: 'Any',1359 id: sender.addressRaw,1360 },1361 },1362 },1363 },1364 };13651366 const assets = {1367 V2: [1368 {1369 id: {1370 Concrete: {1371 parents: 0,1372 interior: 'Here',1373 },1374 },1375 fun: {1376 Fungible: qtzToShidenTransferred,1377 },1378 },1379 ],1380 };13811382 // Initial balance is 100 QTZ1383 const balanceBefore = await helper.balance.getSubstrate(sender.address);1384 console.log(`Initial balance is: ${balanceBefore}`);13851386 const feeAssetItem = 0;1387 await helper.xcm.limitedReserveTransferAssets(sender, destination, beneficiary, assets, feeAssetItem, 'Unlimited');13881389 // Balance after reserve transfer is less than 901390 balanceAfterQuartzToShidenXCM = await helper.balance.getSubstrate(sender.address);1391 console.log(`QTZ Balance on Quartz after XCM is: ${balanceAfterQuartzToShidenXCM}`);1392 console.log(`Quartz's QTZ commission is: ${balanceBefore-balanceAfterQuartzToShidenXCM}`);1393 expect(balanceBefore - balanceAfterQuartzToShidenXCM > 0).to.be.true;13941395 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1396 await helper.wait.newBlocks(3);1397 const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1398 const shidenBalance = await helper.balance.getSubstrate(sender.address);13991400 console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);1401 console.log(`Shiden's QTZ commission is: ${qtzToShidenTransferred-xcQTZbalance!}`);14021403 expect(xcQTZbalance).to.eq(qtzToShidenArrived);1404 // SHD balance does not changed:1405 expect(shidenBalance).to.eq(shidenInitialBalance);1406 });1407 });14081409 itSub('Should connect to Shiden and send QTZ back', async ({helper}) => {1410 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1411 const destination = {1412 V2: {1413 parents: 1,1414 interior: {1415 X1: {1416 Parachain: QUARTZ_CHAIN,1417 },1418 },1419 },1420 };14211422 const beneficiary = {1423 V2: {1424 parents: 0,1425 interior: {1426 X1: {1427 AccountId32: {1428 network: 'Any',1429 id: sender.addressRaw,1430 },1431 },1432 },1433 },1434 };14351436 const assets = {1437 V2: [1438 {1439 id: {1440 Concrete: {1441 parents: 1,1442 interior: {1443 X1: {1444 Parachain: QUARTZ_CHAIN,1445 },1446 },1447 },1448 },1449 fun: {1450 Fungible: qtzFromShidenTransfered,1451 },1452 },1453 ],1454 };14551456 // Initial balance is 1 SDN1457 const balanceSDNbefore = await helper.balance.getSubstrate(sender.address);1458 console.log(`SDN balance is: ${balanceSDNbefore}, it does not changed`);1459 expect(balanceSDNbefore).to.eq(shidenInitialBalance);14601461 const feeAssetItem = 0;1462 // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1463 await helper.executeExtrinsic(sender, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);14641465 // Balance after reserve transfer is less than 1 SDN1466 const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1467 const balanceSDN = await helper.balance.getSubstrate(sender.address);1468 console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);14691470 // Assert: xcQTZ balance correctly decreased1471 expect(xcQTZbalance).to.eq(qtzOnShidenLeft);1472 // Assert: SDN balance is 0.996...1473 expect(balanceSDN / (10n ** (SHIDEN_DECIMALS - 3n))).to.eq(996n);1474 });14751476 await helper.wait.newBlocks(3);1477 const balanceQTZ = await helper.balance.getSubstrate(sender.address);1478 console.log(`QTZ Balance on Quartz after XCM is: ${balanceQTZ}`);1479 expect(balanceQTZ).to.eq(balanceAfterQuartzToShidenXCM + qtzFromShidenTransfered);1480 });14811482 itSub('Shiden can send only up to its balance', async ({helper}) => {1483 // set Shiden's sovereign account's balance1484 const shidenBalance = 10000n * (10n ** QTZ_DECIMALS);1485 const shidenSovereignAccount = helper.address.paraSiblingSovereignAccount(SHIDEN_CHAIN);1486 await helper.getSudo().balance.setBalanceSubstrate(alice, shidenSovereignAccount, shidenBalance);14871488 const moreThanShidenHas = shidenBalance * 2n;14891490 let targetAccountBalance = 0n;1491 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);14921493 const quartzMultilocation = {1494 V2: {1495 parents: 1,1496 interior: {1497 X1: {Parachain: QUARTZ_CHAIN},1498 },1499 },1500 };15011502 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1503 targetAccount.addressRaw,1504 {1505 Concrete: {1506 parents: 0,1507 interior: 'Here',1508 },1509 },1510 moreThanShidenHas,1511 );15121513 let maliciousXcmProgramSent: any;1514 const maxWaitBlocks = 3;15151516 // Try to trick Quartz1517 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1518 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);15191520 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1521 });15221523 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1524 && event.outcome.isFailedToTransactAsset);15251526 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1527 expect(targetAccountBalance).to.be.equal(0n);15281529 // But Shiden still can send the correct amount1530 const validTransferAmount = shidenBalance / 2n;1531 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1532 targetAccount.addressRaw,1533 {1534 Concrete: {1535 parents: 0,1536 interior: 'Here',1537 },1538 },1539 validTransferAmount,1540 );15411542 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1543 await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);1544 });15451546 await helper.wait.newBlocks(maxWaitBlocks);15471548 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1549 expect(targetAccountBalance).to.be.equal(validTransferAmount);1550 });15511552 itSub('Should not accept reserve transfer of QTZ from Shiden', async ({helper}) => {1553 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1554 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15551556 const quartzMultilocation = {1557 V2: {1558 parents: 1,1559 interior: {1560 X1: {1561 Parachain: QUARTZ_CHAIN,1562 },1563 },1564 },1565 };15661567 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1568 targetAccount.addressRaw,1569 {1570 Concrete: {1571 parents: 1,1572 interior: {1573 X1: {1574 Parachain: QUARTZ_CHAIN,1575 },1576 },1577 },1578 },1579 testAmount,1580 );15811582 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1583 targetAccount.addressRaw,1584 {1585 Concrete: {1586 parents: 0,1587 interior: 'Here',1588 },1589 },1590 testAmount,1591 );15921593 let maliciousXcmProgramFullIdSent: any;1594 let maliciousXcmProgramHereIdSent: any;1595 const maxWaitBlocks = 3;15961597 // Try to trick Quartz using full QTZ identification1598 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1599 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);16001601 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1602 });16031604 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1605 && event.outcome.isUntrustedReserveLocation);16061607 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1608 expect(accountBalance).to.be.equal(0n);16091610 // Try to trick Quartz using shortened QTZ identification1611 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1612 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);16131614 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1615 });16161617 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1618 && event.outcome.isUntrustedReserveLocation);16191620 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1621 expect(accountBalance).to.be.equal(0n);1622 });1623});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, usingRelayPlaygrounds, usingMoonriverPlaygrounds, usingStateminePlaygrounds, 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} from './xcm.types';21import {hexToString} from '@polkadot/util';22232425const STATEMINE_PALLET_INSTANCE = 50;2627const TRANSFER_AMOUNT = 2000000000000000000000000n;2829const FUNDING_AMOUNT = 3_500_000_0000_000_000n;3031const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;3233const USDT_ASSET_ID = 100;34const USDT_ASSET_METADATA_DECIMALS = 18;35const USDT_ASSET_METADATA_NAME = 'USDT';36const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';37const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;38const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;3940const SAFE_XCM_VERSION = 2;4142describeXCM('[XCM] Integration test: Exchanging USDT with Statemine', () => {43 let alice: IKeyringPair;44 let bob: IKeyringPair;4546 let balanceStmnBefore: bigint;47 let balanceStmnAfter: bigint;4849 let balanceQuartzBefore: bigint;50 let balanceQuartzAfter: bigint;51 let balanceQuartzFinal: bigint;5253 let balanceBobBefore: bigint;54 let balanceBobAfter: bigint;55 let balanceBobFinal: bigint;5657 let balanceBobRelayTokenBefore: bigint;58 let balanceBobRelayTokenAfter: bigint;596061 before(async () => {62 await usingPlaygrounds(async (helper, privateKey) => {63 alice = await privateKey('//Alice');64 bob = await privateKey('//Bob'); // sovereign account on Statemine(t) funds donor6566 // Set the default version to wrap the first message to other chains.67 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);68 });6970 await usingRelayPlaygrounds(relayUrl, async (helper) => {71 // Fund accounts on Statemine(t)72 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, alice.addressRaw, FUNDING_AMOUNT);73 await helper.xcm.teleportNativeAsset(alice, STATEMINE_CHAIN, bob.addressRaw, FUNDING_AMOUNT);74 });7576 await usingStateminePlaygrounds(statemineUrl, async (helper) => {77 const sovereignFundingAmount = 3_500_000_000n;7879 await helper.assets.create(80 alice,81 USDT_ASSET_ID,82 alice.address,83 USDT_ASSET_METADATA_MINIMAL_BALANCE,84 );85 await helper.assets.setMetadata(86 alice,87 USDT_ASSET_ID,88 USDT_ASSET_METADATA_NAME,89 USDT_ASSET_METADATA_DESCRIPTION,90 USDT_ASSET_METADATA_DECIMALS,91 );92 await helper.assets.mint(93 alice,94 USDT_ASSET_ID,95 alice.address,96 USDT_ASSET_AMOUNT,97 );9899 // funding parachain sovereing account on Statemine(t).100 // The sovereign account should be created before any action101 // (the assets pallet on Statemine(t) check if the sovereign account exists)102 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(QUARTZ_CHAIN);103 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);104 });105106107 await usingPlaygrounds(async (helper) => {108 const location = {109 V2: {110 parents: 1,111 interior: {X3: [112 {113 Parachain: STATEMINE_CHAIN,114 },115 {116 PalletInstance: STATEMINE_PALLET_INSTANCE,117 },118 {119 GeneralIndex: USDT_ASSET_ID,120 },121 ]},122 },123 };124125 const metadata =126 {127 name: USDT_ASSET_ID,128 symbol: USDT_ASSET_METADATA_NAME,129 decimals: USDT_ASSET_METADATA_DECIMALS,130 minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,131 };132 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);133 balanceQuartzBefore = await helper.balance.getSubstrate(alice.address);134 });135136137 // Providing the relay currency to the quartz sender account138 // (fee for USDT XCM are paid in relay tokens)139 await usingRelayPlaygrounds(relayUrl, async (helper) => {140 const destination = {141 V2: {142 parents: 0,143 interior: {X1: {144 Parachain: QUARTZ_CHAIN,145 },146 },147 }};148149 const beneficiary = {150 V2: {151 parents: 0,152 interior: {X1: {153 AccountId32: {154 network: 'Any',155 id: alice.addressRaw,156 },157 }},158 },159 };160161 const assets = {162 V2: [163 {164 id: {165 Concrete: {166 parents: 0,167 interior: 'Here',168 },169 },170 fun: {171 Fungible: TRANSFER_AMOUNT_RELAY,172 },173 },174 ],175 };176177 const feeAssetItem = 0;178179 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');180 });181182 });183184 itSub('Should connect and send USDT from Statemine to Quartz', async ({helper}) => {185 await usingStateminePlaygrounds(statemineUrl, async (helper) => {186 const dest = {187 V2: {188 parents: 1,189 interior: {X1: {190 Parachain: QUARTZ_CHAIN,191 },192 },193 }};194195 const beneficiary = {196 V2: {197 parents: 0,198 interior: {X1: {199 AccountId32: {200 network: 'Any',201 id: alice.addressRaw,202 },203 }},204 },205 };206207 const assets = {208 V2: [209 {210 id: {211 Concrete: {212 parents: 0,213 interior: {214 X2: [215 {216 PalletInstance: STATEMINE_PALLET_INSTANCE,217 },218 {219 GeneralIndex: USDT_ASSET_ID,220 },221 ]},222 },223 },224 fun: {225 Fungible: TRANSFER_AMOUNT,226 },227 },228 ],229 };230231 const feeAssetItem = 0;232233 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);234 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');235236 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);237238 // common good parachain take commission in it native token239 console.log(240 '[Statemine -> Quartz] transaction fees on Statemine: %s WND',241 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINE_DECIMALS),242 );243 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;244245 });246247248 // ensure that asset has been delivered249 await helper.wait.newBlocks(3);250251 // expext collection id will be with id 1252 const free = await helper.ft.getBalance(1, {Substrate: alice.address});253254 balanceQuartzAfter = await helper.balance.getSubstrate(alice.address);255256 console.log(257 '[Statemine -> Quartz] transaction fees on Quartz: %s USDT',258 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),259 );260 console.log(261 '[Statemine -> Quartz] transaction fees on Quartz: %s QTZ',262 helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzBefore),263 );264 // commission has not paid in USDT token265 expect(free).to.be.equal(TRANSFER_AMOUNT);266 // ... and parachain native token267 expect(balanceQuartzAfter == balanceQuartzBefore).to.be.true;268 });269270 itSub('Should connect and send USDT from Quartz to Statemine back', async ({helper}) => {271 const destination = {272 V2: {273 parents: 1,274 interior: {X2: [275 {276 Parachain: STATEMINE_CHAIN,277 },278 {279 AccountId32: {280 network: 'Any',281 id: alice.addressRaw,282 },283 },284 ]},285 },286 };287288 const relayFee = 400_000_000_000_000n;289 const currencies: [any, bigint][] = [290 [291 {292 ForeignAssetId: 0,293 },294 TRANSFER_AMOUNT,295 ],296 [297 {298 NativeAssetId: 'Parent',299 },300 relayFee,301 ],302 ];303304 const feeItem = 1;305306 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');307308 // the commission has been paid in parachain native token309 balanceQuartzFinal = await helper.balance.getSubstrate(alice.address);310 console.log('[Quartz -> Statemine] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(balanceQuartzAfter - balanceQuartzFinal));311 expect(balanceQuartzAfter > balanceQuartzFinal).to.be.true;312313 await usingStateminePlaygrounds(statemineUrl, async (helper) => {314 await helper.wait.newBlocks(3);315316 // The USDT token never paid fees. Its amount not changed from begin value.317 // Also check that xcm transfer has been succeeded318 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;319 });320 });321322 itSub('Should connect and send Relay token to Quartz', async ({helper}) => {323 balanceBobBefore = await helper.balance.getSubstrate(bob.address);324 balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});325326 await usingRelayPlaygrounds(relayUrl, async (helper) => {327 const destination = {328 V2: {329 parents: 0,330 interior: {X1: {331 Parachain: QUARTZ_CHAIN,332 },333 },334 }};335336 const beneficiary = {337 V2: {338 parents: 0,339 interior: {X1: {340 AccountId32: {341 network: 'Any',342 id: bob.addressRaw,343 },344 }},345 },346 };347348 const assets = {349 V2: [350 {351 id: {352 Concrete: {353 parents: 0,354 interior: 'Here',355 },356 },357 fun: {358 Fungible: TRANSFER_AMOUNT_RELAY,359 },360 },361 ],362 };363364 const feeAssetItem = 0;365366 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');367 });368369 await helper.wait.newBlocks(3);370371 balanceBobAfter = await helper.balance.getSubstrate(bob.address);372 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});373374 const wndFeeOnQuartz = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;375 const wndDiffOnQuartz = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;376 console.log(377 '[Relay (Westend) -> Quartz] transaction fees: %s QTZ',378 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),379 );380 console.log(381 '[Relay (Westend) -> Quartz] transaction fees: %s WND',382 helper.util.bigIntToDecimals(wndFeeOnQuartz, STATEMINE_DECIMALS),383 );384 console.log('[Relay (Westend) -> Quartz] actually delivered: %s WND', wndDiffOnQuartz);385 expect(wndFeeOnQuartz == 0n, 'No incoming WND fees should be taken').to.be.true;386 expect(balanceBobBefore == balanceBobAfter, 'No incoming QTZ fees should be taken').to.be.true;387 });388389 itSub('Should connect and send Relay token back', async ({helper}) => {390 let relayTokenBalanceBefore: bigint;391 let relayTokenBalanceAfter: bigint;392 await usingRelayPlaygrounds(relayUrl, async (helper) => {393 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);394 });395396 const destination = {397 V2: {398 parents: 1,399 interior: {400 X1:{401 AccountId32: {402 network: 'Any',403 id: bob.addressRaw,404 },405 },406 },407 },408 };409410 const currencies: any = [411 [412 {413 NativeAssetId: 'Parent',414 },415 TRANSFER_AMOUNT_RELAY,416 ],417 ];418419 const feeItem = 0;420421 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');422423 balanceBobFinal = await helper.balance.getSubstrate(bob.address);424 console.log('[Quartz -> Relay (Westend)] transaction fees: %s QTZ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));425426 await usingRelayPlaygrounds(relayUrl, async (helper) => {427 await helper.wait.newBlocks(10);428 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);429430 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;431 console.log('[Quartz -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));432 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;433 });434 });435});436437describeXCM('[XCM] Integration test: Exchanging tokens with Karura', () => {438 let alice: IKeyringPair;439 let randomAccount: IKeyringPair;440441 let balanceQuartzTokenInit: bigint;442 let balanceQuartzTokenMiddle: bigint;443 let balanceQuartzTokenFinal: bigint;444 let balanceKaruraTokenInit: bigint;445 let balanceKaruraTokenMiddle: bigint;446 let balanceKaruraTokenFinal: bigint;447 let balanceQuartzForeignTokenInit: bigint;448 let balanceQuartzForeignTokenMiddle: bigint;449 let balanceQuartzForeignTokenFinal: bigint;450451 // computed by a test transfer from prod Quartz to prod Karura.452 // 2 QTZ sent https://quartz.subscan.io/xcm_message/kusama-f60d821b049f8835a3005ce7102285006f5b61e9453 // 1.919176000000000000 QTZ received (you can check Karura's chain state in the corresponding block)454 const expectedKaruraIncomeFee = 2000000000000000000n - 1919176000000000000n;455 const karuraEps = 8n * 10n ** 16n;456457 let karuraBackwardTransferAmount: bigint;458459 before(async () => {460 await usingPlaygrounds(async (helper, privateKey) => {461 alice = await privateKey('//Alice');462 [randomAccount] = await helper.arrange.createAccounts([0n], alice);463464 // Set the default version to wrap the first message to other chains.465 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);466 });467468 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {469 const destination = {470 V2: {471 parents: 1,472 interior: {473 X1: {474 Parachain: QUARTZ_CHAIN,475 },476 },477 },478 };479480 const metadata = {481 name: 'Quartz',482 symbol: 'QTZ',483 decimals: 18,484 minimalBalance: 1000000000000000000n,485 };486487 const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v]: [any, any]) =>488 hexToString(v.toJSON()['symbol'])) as string[];489490 if(!assets.includes('QTZ')) {491 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);492 } else {493 console.log('QTZ token already registered on Karura assetRegistry pallet');494 }495 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);496 balanceKaruraTokenInit = await helper.balance.getSubstrate(randomAccount.address);497 balanceQuartzForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});498 });499500 await usingPlaygrounds(async (helper) => {501 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);502 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccount.address);503 });504 });505506 itSub('Should connect and send QTZ to Karura', async ({helper}) => {507 const destination = {508 V2: {509 parents: 1,510 interior: {511 X1: {512 Parachain: KARURA_CHAIN,513 },514 },515 },516 };517518 const beneficiary = {519 V2: {520 parents: 0,521 interior: {522 X1: {523 AccountId32: {524 network: 'Any',525 id: randomAccount.addressRaw,526 },527 },528 },529 },530 };531532 const assets = {533 V2: [534 {535 id: {536 Concrete: {537 parents: 0,538 interior: 'Here',539 },540 },541 fun: {542 Fungible: TRANSFER_AMOUNT,543 },544 },545 ],546 };547548 const feeAssetItem = 0;549550 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');551 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);552553 const qtzFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;554 expect(qtzFees > 0n, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;555 console.log('[Quartz -> Karura] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));556557 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {558 await helper.wait.newBlocks(3);559560 balanceQuartzForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});561 balanceKaruraTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);562563 const karFees = balanceKaruraTokenInit - balanceKaruraTokenMiddle;564 const qtzIncomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenInit;565 karuraBackwardTransferAmount = qtzIncomeTransfer;566567 const karUnqFees = TRANSFER_AMOUNT - qtzIncomeTransfer;568569 console.log(570 '[Quartz -> Karura] transaction fees on Karura: %s KAR',571 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),572 );573 console.log(574 '[Quartz -> Karura] transaction fees on Karura: %s QTZ',575 helper.util.bigIntToDecimals(karUnqFees),576 );577 console.log('[Quartz -> Karura] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));578 expect(karFees == 0n).to.be.true;579580 const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;581582 expect(583 bigintAbs(karUnqFees - expectedKaruraIncomeFee) < karuraEps,584 'Karura took different income fee, check the Karura foreign asset config',585 ).to.be.true;586 });587 });588589 itSub('Should connect to Karura and send QTZ back', async ({helper}) => {590 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {591 const destination = {592 V2: {593 parents: 1,594 interior: {595 X2: [596 {Parachain: QUARTZ_CHAIN},597 {598 AccountId32: {599 network: 'Any',600 id: randomAccount.addressRaw,601 },602 },603 ],604 },605 },606 };607608 const id = {609 ForeignAsset: 0,610 };611612 await helper.xTokens.transfer(randomAccount, id, karuraBackwardTransferAmount, destination, 'Unlimited');613 balanceKaruraTokenFinal = await helper.balance.getSubstrate(randomAccount.address);614 balanceQuartzForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);615616 const karFees = balanceKaruraTokenMiddle - balanceKaruraTokenFinal;617 const qtzOutcomeTransfer = balanceQuartzForeignTokenMiddle - balanceQuartzForeignTokenFinal;618619 console.log(620 '[Karura -> Quartz] transaction fees on Karura: %s KAR',621 helper.util.bigIntToDecimals(karFees, KARURA_DECIMALS),622 );623 console.log('[Karura -> Quartz] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));624625 expect(karFees > 0, 'Negative fees KAR, looks like nothing was transferred').to.be.true;626 expect(qtzOutcomeTransfer == karuraBackwardTransferAmount).to.be.true;627 });628629 await helper.wait.newBlocks(3);630631 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccount.address);632 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;633 expect(actuallyDelivered > 0).to.be.true;634635 console.log('[Karura -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));636637 const qtzFees = karuraBackwardTransferAmount - actuallyDelivered;638 console.log('[Karura -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));639 expect(qtzFees == 0n).to.be.true;640 });641642 itSub('Karura can send only up to its balance', async ({helper}) => {643 // set Karura's sovereign account's balance644 const karuraBalance = 10000n * (10n ** QTZ_DECIMALS);645 const karuraSovereignAccount = helper.address.paraSiblingSovereignAccount(KARURA_CHAIN);646 await helper.getSudo().balance.setBalanceSubstrate(alice, karuraSovereignAccount, karuraBalance);647648 const moreThanKaruraHas = karuraBalance * 2n;649650 let targetAccountBalance = 0n;651 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);652653 const quartzMultilocation = {654 V2: {655 parents: 1,656 interior: {657 X1: {Parachain: QUARTZ_CHAIN},658 },659 },660 };661662 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(663 targetAccount.addressRaw,664 {665 Concrete: {666 parents: 0,667 interior: 'Here',668 },669 },670 moreThanKaruraHas,671 );672673 let maliciousXcmProgramSent: any;674 const maxWaitBlocks = 5;675676 // Try to trick Quartz677 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {678 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);679680 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);681 });682683 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash684 && event.outcome.isFailedToTransactAsset);685686 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);687 expect(targetAccountBalance).to.be.equal(0n);688689 // But Karura still can send the correct amount690 const validTransferAmount = karuraBalance / 2n;691 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(692 targetAccount.addressRaw,693 {694 Concrete: {695 parents: 0,696 interior: 'Here',697 },698 },699 validTransferAmount,700 );701702 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {703 await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);704 });705706 await helper.wait.newBlocks(maxWaitBlocks);707708 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);709 expect(targetAccountBalance).to.be.equal(validTransferAmount);710 });711712 itSub('Should not accept reserve transfer of QTZ from Karura', async ({helper}) => {713 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);714 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);715716 const quartzMultilocation = {717 V2: {718 parents: 1,719 interior: {720 X1: {721 Parachain: QUARTZ_CHAIN,722 },723 },724 },725 };726727 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(728 targetAccount.addressRaw,729 {730 Concrete: {731 parents: 1,732 interior: {733 X1: {734 Parachain: QUARTZ_CHAIN,735 },736 },737 },738 },739 testAmount,740 );741742 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(743 targetAccount.addressRaw,744 {745 Concrete: {746 parents: 0,747 interior: 'Here',748 },749 },750 testAmount,751 );752753 let maliciousXcmProgramFullIdSent: any;754 let maliciousXcmProgramHereIdSent: any;755 const maxWaitBlocks = 3;756757 // Try to trick Quartz using full QTZ identification758 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {759 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);760761 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);762 });763764 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash765 && event.outcome.isUntrustedReserveLocation);766767 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);768 expect(accountBalance).to.be.equal(0n);769770 // Try to trick Quartz using shortened QTZ identification771 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {772 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);773774 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);775 });776777 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash778 && event.outcome.isUntrustedReserveLocation);779780 accountBalance = await helper.balance.getSubstrate(targetAccount.address);781 expect(accountBalance).to.be.equal(0n);782 });783});784785// These tests are relevant only when786// the the corresponding foreign assets are not registered787describeXCM('[XCM] Integration test: Quartz rejects non-native tokens', () => {788 let alice: IKeyringPair;789 let alith: IKeyringPair;790791 const testAmount = 100_000_000_000n;792 let quartzParachainJunction;793 let quartzAccountJunction;794795 let quartzParachainMultilocation: any;796 let quartzAccountMultilocation: any;797 let quartzCombinedMultilocation: any;798799 let messageSent: any;800801 const maxWaitBlocks = 3;802803 before(async () => {804 await usingPlaygrounds(async (helper, privateKey) => {805 alice = await privateKey('//Alice');806807 quartzParachainJunction = {Parachain: QUARTZ_CHAIN};808 quartzAccountJunction = {809 AccountId32: {810 network: 'Any',811 id: alice.addressRaw,812 },813 };814815 quartzParachainMultilocation = {816 V2: {817 parents: 1,818 interior: {819 X1: quartzParachainJunction,820 },821 },822 };823824 quartzAccountMultilocation = {825 V2: {826 parents: 0,827 interior: {828 X1: quartzAccountJunction,829 },830 },831 };832833 quartzCombinedMultilocation = {834 V2: {835 parents: 1,836 interior: {837 X2: [quartzParachainJunction, quartzAccountJunction],838 },839 },840 };841842 // Set the default version to wrap the first message to other chains.843 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);844 });845846 // eslint-disable-next-line require-await847 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {848 alith = helper.account.alithAccount();849 });850 });851852 const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {853 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash854 && event.outcome.isFailedToTransactAsset);855 };856857 itSub('Quartz rejects KAR tokens from Karura', async ({helper}) => {858 await usingKaruraPlaygrounds(karuraUrl, async (helper) => {859 const id = {860 Token: 'KAR',861 };862 const destination = quartzCombinedMultilocation;863 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');864865 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);866 });867868 await expectFailedToTransact(helper, messageSent);869 });870871 itSub('Quartz rejects MOVR tokens from Moonriver', async ({helper}) => {872 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {873 const id = 'SelfReserve';874 const destination = quartzCombinedMultilocation;875 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');876877 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);878 });879880 await expectFailedToTransact(helper, messageSent);881 });882883 itSub('Quartz rejects SDN tokens from Shiden', async ({helper}) => {884 await usingShidenPlaygrounds(shidenUrl, async (helper) => {885 const destinationParachain = quartzParachainMultilocation;886 const beneficiary = quartzAccountMultilocation;887 const assets = {888 V2: [{889 id: {890 Concrete: {891 parents: 0,892 interior: 'Here',893 },894 },895 fun: {896 Fungible: testAmount,897 },898 }],899 };900 const feeAssetItem = 0;901902 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [903 destinationParachain,904 beneficiary,905 assets,906 feeAssetItem,907 ]);908909 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);910 });911912 await expectFailedToTransact(helper, messageSent);913 });914});915916describeXCM('[XCM] Integration test: Exchanging QTZ with Moonriver', () => {917 // Quartz constants918 let alice: IKeyringPair;919 let quartzAssetLocation;920921 let randomAccountQuartz: IKeyringPair;922 let randomAccountMoonriver: IKeyringPair;923924 // Moonriver constants925 let assetId: string;926927 const quartzAssetMetadata = {928 name: 'xcQuartz',929 symbol: 'xcQTZ',930 decimals: 18,931 isFrozen: false,932 minimalBalance: 1n,933 };934935 let balanceQuartzTokenInit: bigint;936 let balanceQuartzTokenMiddle: bigint;937 let balanceQuartzTokenFinal: bigint;938 let balanceForeignQtzTokenInit: bigint;939 let balanceForeignQtzTokenMiddle: bigint;940 let balanceForeignQtzTokenFinal: bigint;941 let balanceMovrTokenInit: bigint;942 let balanceMovrTokenMiddle: bigint;943 let balanceMovrTokenFinal: bigint;944945 before(async () => {946 await usingPlaygrounds(async (helper, privateKey) => {947 alice = await privateKey('//Alice');948 [randomAccountQuartz] = await helper.arrange.createAccounts([0n], alice);949950 balanceForeignQtzTokenInit = 0n;951952 // Set the default version to wrap the first message to other chains.953 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);954 });955956 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {957 const alithAccount = helper.account.alithAccount();958 const baltatharAccount = helper.account.baltatharAccount();959 const dorothyAccount = helper.account.dorothyAccount();960961 randomAccountMoonriver = helper.account.create();962963 // >>> Sponsoring Dorothy >>>964 console.log('Sponsoring Dorothy.......');965 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);966 console.log('Sponsoring Dorothy.......DONE');967 // <<< Sponsoring Dorothy <<<968969 quartzAssetLocation = {970 XCM: {971 parents: 1,972 interior: {X1: {Parachain: QUARTZ_CHAIN}},973 },974 };975 const existentialDeposit = 1n;976 const isSufficient = true;977 const unitsPerSecond = 1n;978 const numAssetsWeightHint = 0;979980 if((await helper.assetManager.assetTypeId(quartzAssetLocation)).toJSON()) {981 console.log('Quartz asset already registered on Moonriver');982 } else {983 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({984 location: quartzAssetLocation,985 metadata: quartzAssetMetadata,986 existentialDeposit,987 isSufficient,988 unitsPerSecond,989 numAssetsWeightHint,990 });991992 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);993994 await helper.fastDemocracy.executeProposal('register QTZ foreign asset', encodedProposal);995 }996 // >>> Acquire Quartz AssetId Info on Moonriver >>>997 console.log('Acquire Quartz AssetId Info on Moonriver.......');998999 assetId = (await helper.assetManager.assetTypeId(quartzAssetLocation)).toString();10001001 console.log('QTZ asset ID is %s', assetId);1002 console.log('Acquire Quartz AssetId Info on Moonriver.......DONE');1003 // >>> Acquire Quartz AssetId Info on Moonriver >>>10041005 // >>> Sponsoring random Account >>>1006 console.log('Sponsoring random Account.......');1007 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonriver.address, 11_000_000_000_000_000_000n);1008 console.log('Sponsoring random Account.......DONE');1009 // <<< Sponsoring random Account <<<10101011 balanceMovrTokenInit = await helper.balance.getEthereum(randomAccountMoonriver.address);1012 });10131014 await usingPlaygrounds(async (helper) => {1015 await helper.balance.transferToSubstrate(alice, randomAccountQuartz.address, 10n * TRANSFER_AMOUNT);1016 balanceQuartzTokenInit = await helper.balance.getSubstrate(randomAccountQuartz.address);1017 });1018 });10191020 itSub('Should connect and send QTZ to Moonriver', async ({helper}) => {1021 const currencyId = {1022 NativeAssetId: 'Here',1023 };1024 const dest = {1025 V2: {1026 parents: 1,1027 interior: {1028 X2: [1029 {Parachain: MOONRIVER_CHAIN},1030 {AccountKey20: {network: 'Any', key: randomAccountMoonriver.address}},1031 ],1032 },1033 },1034 };1035 const amount = TRANSFER_AMOUNT;10361037 await helper.xTokens.transfer(randomAccountQuartz, currencyId, amount, dest, 'Unlimited');10381039 balanceQuartzTokenMiddle = await helper.balance.getSubstrate(randomAccountQuartz.address);1040 expect(balanceQuartzTokenMiddle < balanceQuartzTokenInit).to.be.true;10411042 const transactionFees = balanceQuartzTokenInit - balanceQuartzTokenMiddle - TRANSFER_AMOUNT;1043 console.log('[Quartz -> Moonriver] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(transactionFees));1044 expect(transactionFees > 0, 'Negative fees QTZ, looks like nothing was transferred').to.be.true;10451046 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1047 await helper.wait.newBlocks(3);10481049 balanceMovrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonriver.address);10501051 const movrFees = balanceMovrTokenInit - balanceMovrTokenMiddle;1052 console.log('[Quartz -> Moonriver] transaction fees on Moonriver: %s MOVR',helper.util.bigIntToDecimals(movrFees));1053 expect(movrFees == 0n).to.be.true;10541055 balanceForeignQtzTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonriver.address))!; // BigInt(qtzRandomAccountAsset['balance']);1056 const qtzIncomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenInit;1057 console.log('[Quartz -> Moonriver] income %s QTZ', helper.util.bigIntToDecimals(qtzIncomeTransfer));1058 expect(qtzIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1059 });1060 });10611062 itSub('Should connect to Moonriver and send QTZ back', async ({helper}) => {1063 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1064 const asset = {1065 V2: {1066 id: {1067 Concrete: {1068 parents: 1,1069 interior: {1070 X1: {Parachain: QUARTZ_CHAIN},1071 },1072 },1073 },1074 fun: {1075 Fungible: TRANSFER_AMOUNT,1076 },1077 },1078 };1079 const destination = {1080 V2: {1081 parents: 1,1082 interior: {1083 X2: [1084 {Parachain: QUARTZ_CHAIN},1085 {AccountId32: {network: 'Any', id: randomAccountQuartz.addressRaw}},1086 ],1087 },1088 },1089 };10901091 await helper.xTokens.transferMultiasset(randomAccountMoonriver, asset, destination, 'Unlimited');10921093 balanceMovrTokenFinal = await helper.balance.getEthereum(randomAccountMoonriver.address);10941095 const movrFees = balanceMovrTokenMiddle - balanceMovrTokenFinal;1096 console.log('[Moonriver -> Quartz] transaction fees on Moonriver: %s MOVR', helper.util.bigIntToDecimals(movrFees));1097 expect(movrFees > 0, 'Negative fees MOVR, looks like nothing was transferred').to.be.true;10981099 const qtzRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonriver.address);11001101 expect(qtzRandomAccountAsset).to.be.null;11021103 balanceForeignQtzTokenFinal = 0n;11041105 const qtzOutcomeTransfer = balanceForeignQtzTokenMiddle - balanceForeignQtzTokenFinal;1106 console.log('[Quartz -> Moonriver] outcome %s QTZ', helper.util.bigIntToDecimals(qtzOutcomeTransfer));1107 expect(qtzOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1108 });11091110 await helper.wait.newBlocks(3);11111112 balanceQuartzTokenFinal = await helper.balance.getSubstrate(randomAccountQuartz.address);1113 const actuallyDelivered = balanceQuartzTokenFinal - balanceQuartzTokenMiddle;1114 expect(actuallyDelivered > 0).to.be.true;11151116 console.log('[Moonriver -> Quartz] actually delivered %s QTZ', helper.util.bigIntToDecimals(actuallyDelivered));11171118 const qtzFees = TRANSFER_AMOUNT - actuallyDelivered;1119 console.log('[Moonriver -> Quartz] transaction fees on Quartz: %s QTZ', helper.util.bigIntToDecimals(qtzFees));1120 expect(qtzFees == 0n).to.be.true;1121 });11221123 itSub('Moonriver can send only up to its balance', async ({helper}) => {1124 // set Moonriver's sovereign account's balance1125 const moonriverBalance = 10000n * (10n ** QTZ_DECIMALS);1126 const moonriverSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONRIVER_CHAIN);1127 await helper.getSudo().balance.setBalanceSubstrate(alice, moonriverSovereignAccount, moonriverBalance);11281129 const moreThanMoonriverHas = moonriverBalance * 2n;11301131 let targetAccountBalance = 0n;1132 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);11331134 const quartzMultilocation = {1135 V2: {1136 parents: 1,1137 interior: {1138 X1: {Parachain: QUARTZ_CHAIN},1139 },1140 },1141 };11421143 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1144 targetAccount.addressRaw,1145 {1146 Concrete: {1147 parents: 0,1148 interior: 'Here',1149 },1150 },1151 moreThanMoonriverHas,1152 );11531154 let maliciousXcmProgramSent: any;1155 const maxWaitBlocks = 3;11561157 // Try to trick Quartz1158 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1159 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgram]);11601161 // Needed to bypass the call filter.1162 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1163 await helper.fastDemocracy.executeProposal('try to spend more QTZ than Moonriver has', batchCall);11641165 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1166 });11671168 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1169 && event.outcome.isFailedToTransactAsset);11701171 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1172 expect(targetAccountBalance).to.be.equal(0n);11731174 // But Moonriver still can send the correct amount1175 const validTransferAmount = moonriverBalance / 2n;1176 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1177 targetAccount.addressRaw,1178 {1179 Concrete: {1180 parents: 0,1181 interior: 'Here',1182 },1183 },1184 validTransferAmount,1185 );11861187 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1188 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, validXcmProgram]);11891190 // Needed to bypass the call filter.1191 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1192 await helper.fastDemocracy.executeProposal('Spend the correct amount of QTZ', batchCall);1193 });11941195 await helper.wait.newBlocks(maxWaitBlocks);11961197 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1198 expect(targetAccountBalance).to.be.equal(validTransferAmount);1199 });12001201 itSub('Should not accept reserve transfer of QTZ from Moonriver', async ({helper}) => {1202 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1203 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);12041205 const quartzMultilocation = {1206 V2: {1207 parents: 1,1208 interior: {1209 X1: {1210 Parachain: QUARTZ_CHAIN,1211 },1212 },1213 },1214 };12151216 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1217 targetAccount.addressRaw,1218 {1219 Concrete: {1220 parents: 0,1221 interior: {1222 X1: {1223 Parachain: QUARTZ_CHAIN,1224 },1225 },1226 },1227 },1228 testAmount,1229 );12301231 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1232 targetAccount.addressRaw,1233 {1234 Concrete: {1235 parents: 0,1236 interior: 'Here',1237 },1238 },1239 testAmount,1240 );12411242 let maliciousXcmProgramFullIdSent: any;1243 let maliciousXcmProgramHereIdSent: any;1244 const maxWaitBlocks = 3;12451246 // Try to trick Quartz using full QTZ identification1247 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1248 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramFullId]);12491250 // Needed to bypass the call filter.1251 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1252 await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using path asset identification', batchCall);12531254 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1255 });12561257 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1258 && event.outcome.isUntrustedReserveLocation);12591260 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1261 expect(accountBalance).to.be.equal(0n);12621263 // Try to trick Quartz using shortened QTZ identification1264 await usingMoonriverPlaygrounds(moonriverUrl, async (helper) => {1265 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [quartzMultilocation, maliciousXcmProgramHereId]);12661267 // Needed to bypass the call filter.1268 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1269 await helper.fastDemocracy.executeProposal('try to act like a reserve location for QTZ using "here" asset identification', batchCall);12701271 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1272 });12731274 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1275 && event.outcome.isUntrustedReserveLocation);12761277 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1278 expect(accountBalance).to.be.equal(0n);1279 });1280});12811282describeXCM('[XCM] Integration test: Exchanging tokens with Shiden', () => {1283 let alice: IKeyringPair;1284 let sender: IKeyringPair;12851286 const QTZ_ASSET_ID_ON_SHIDEN = 1;1287 const QTZ_MINIMAL_BALANCE_ON_SHIDEN = 1n;12881289 // Quartz -> Shiden1290 const shidenInitialBalance = 1n * (10n ** SHIDEN_DECIMALS); // 1 SHD, existential deposit required to actually create the account on Shiden1291 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1292 const qtzToShidenTransferred = 10n * (10n ** QTZ_DECIMALS); // 10 QTZ1293 const qtzToShidenArrived = 9_999_999_999_088_000_000n; // 9.999 ... QTZ, Shiden takes a commision in foreign tokens12941295 // Shiden -> Quartz1296 const qtzFromShidenTransfered = 5n * (10n ** QTZ_DECIMALS); // 5 QTZ1297 const qtzOnShidenLeft = qtzToShidenArrived - qtzFromShidenTransfered; // 4.999_999_999_088_000_000n QTZ12981299 let balanceAfterQuartzToShidenXCM: bigint;13001301 before(async () => {1302 await usingPlaygrounds(async (helper, privateKey) => {1303 alice = await privateKey('//Alice');1304 [sender] = await helper.arrange.createAccounts([100n], alice);1305 console.log('sender', sender.address);13061307 // Set the default version to wrap the first message to other chains.1308 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1309 });13101311 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1312 if(!(await helper.callRpc('api.query.assets.asset', [QTZ_ASSET_ID_ON_SHIDEN])).toJSON()) {1313 console.log('1. Create foreign asset and metadata');1314 // TODO update metadata with values from production1315 await helper.assets.create(1316 alice,1317 QTZ_ASSET_ID_ON_SHIDEN,1318 alice.address,1319 QTZ_MINIMAL_BALANCE_ON_SHIDEN,1320 );13211322 await helper.assets.setMetadata(1323 alice,1324 QTZ_ASSET_ID_ON_SHIDEN,1325 'Cross chain QTZ',1326 'xcQTZ',1327 Number(QTZ_DECIMALS),1328 );13291330 console.log('2. Register asset location on Shiden');1331 const assetLocation = {1332 V2: {1333 parents: 1,1334 interior: {1335 X1: {1336 Parachain: QUARTZ_CHAIN,1337 },1338 },1339 },1340 };13411342 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, QTZ_ASSET_ID_ON_SHIDEN]);13431344 console.log('3. Set QTZ payment for XCM execution on Shiden');1345 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1346 } else {1347 console.log('QTZ is already registered on Shiden');1348 }1349 console.log('4. Transfer 1 SDN to recipient to create the account (needed due to existential balance)');1350 await helper.balance.transferToSubstrate(alice, sender.address, shidenInitialBalance);1351 });1352 });13531354 itSub('Should connect and send QTZ to Shiden', async ({helper}) => {1355 const destination = {1356 V2: {1357 parents: 1,1358 interior: {1359 X1: {1360 Parachain: SHIDEN_CHAIN,1361 },1362 },1363 },1364 };13651366 const beneficiary = {1367 V2: {1368 parents: 0,1369 interior: {1370 X1: {1371 AccountId32: {1372 network: 'Any',1373 id: sender.addressRaw,1374 },1375 },1376 },1377 },1378 };13791380 const assets = {1381 V2: [1382 {1383 id: {1384 Concrete: {1385 parents: 0,1386 interior: 'Here',1387 },1388 },1389 fun: {1390 Fungible: qtzToShidenTransferred,1391 },1392 },1393 ],1394 };13951396 // Initial balance is 100 QTZ1397 const balanceBefore = await helper.balance.getSubstrate(sender.address);1398 console.log(`Initial balance is: ${balanceBefore}`);13991400 const feeAssetItem = 0;1401 await helper.xcm.limitedReserveTransferAssets(sender, destination, beneficiary, assets, feeAssetItem, 'Unlimited');14021403 // Balance after reserve transfer is less than 901404 balanceAfterQuartzToShidenXCM = await helper.balance.getSubstrate(sender.address);1405 console.log(`QTZ Balance on Quartz after XCM is: ${balanceAfterQuartzToShidenXCM}`);1406 console.log(`Quartz's QTZ commission is: ${balanceBefore-balanceAfterQuartzToShidenXCM}`);1407 expect(balanceBefore - balanceAfterQuartzToShidenXCM > 0).to.be.true;14081409 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1410 await helper.wait.newBlocks(3);1411 const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1412 const shidenBalance = await helper.balance.getSubstrate(sender.address);14131414 console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);1415 console.log(`Shiden's QTZ commission is: ${qtzToShidenTransferred-xcQTZbalance!}`);14161417 expect(xcQTZbalance).to.eq(qtzToShidenArrived);1418 // SHD balance does not changed:1419 expect(shidenBalance).to.eq(shidenInitialBalance);1420 });1421 });14221423 itSub('Should connect to Shiden and send QTZ back', async ({helper}) => {1424 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1425 const destination = {1426 V2: {1427 parents: 1,1428 interior: {1429 X1: {1430 Parachain: QUARTZ_CHAIN,1431 },1432 },1433 },1434 };14351436 const beneficiary = {1437 V2: {1438 parents: 0,1439 interior: {1440 X1: {1441 AccountId32: {1442 network: 'Any',1443 id: sender.addressRaw,1444 },1445 },1446 },1447 },1448 };14491450 const assets = {1451 V2: [1452 {1453 id: {1454 Concrete: {1455 parents: 1,1456 interior: {1457 X1: {1458 Parachain: QUARTZ_CHAIN,1459 },1460 },1461 },1462 },1463 fun: {1464 Fungible: qtzFromShidenTransfered,1465 },1466 },1467 ],1468 };14691470 // Initial balance is 1 SDN1471 const balanceSDNbefore = await helper.balance.getSubstrate(sender.address);1472 console.log(`SDN balance is: ${balanceSDNbefore}, it does not changed`);1473 expect(balanceSDNbefore).to.eq(shidenInitialBalance);14741475 const feeAssetItem = 0;1476 // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1477 await helper.executeExtrinsic(sender, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);14781479 // Balance after reserve transfer is less than 1 SDN1480 const xcQTZbalance = await helper.assets.account(QTZ_ASSET_ID_ON_SHIDEN, sender.address);1481 const balanceSDN = await helper.balance.getSubstrate(sender.address);1482 console.log(`xcQTZ balance on Shiden after XCM is: ${xcQTZbalance}`);14831484 // Assert: xcQTZ balance correctly decreased1485 expect(xcQTZbalance).to.eq(qtzOnShidenLeft);1486 // Assert: SDN balance is 0.996...1487 expect(balanceSDN / (10n ** (SHIDEN_DECIMALS - 3n))).to.eq(996n);1488 });14891490 await helper.wait.newBlocks(3);1491 const balanceQTZ = await helper.balance.getSubstrate(sender.address);1492 console.log(`QTZ Balance on Quartz after XCM is: ${balanceQTZ}`);1493 expect(balanceQTZ).to.eq(balanceAfterQuartzToShidenXCM + qtzFromShidenTransfered);1494 });14951496 itSub('Shiden can send only up to its balance', async ({helper}) => {1497 // set Shiden's sovereign account's balance1498 const shidenBalance = 10000n * (10n ** QTZ_DECIMALS);1499 const shidenSovereignAccount = helper.address.paraSiblingSovereignAccount(SHIDEN_CHAIN);1500 await helper.getSudo().balance.setBalanceSubstrate(alice, shidenSovereignAccount, shidenBalance);15011502 const moreThanShidenHas = shidenBalance * 2n;15031504 let targetAccountBalance = 0n;1505 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);15061507 const quartzMultilocation = {1508 V2: {1509 parents: 1,1510 interior: {1511 X1: {Parachain: QUARTZ_CHAIN},1512 },1513 },1514 };15151516 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1517 targetAccount.addressRaw,1518 {1519 Concrete: {1520 parents: 0,1521 interior: 'Here',1522 },1523 },1524 moreThanShidenHas,1525 );15261527 let maliciousXcmProgramSent: any;1528 const maxWaitBlocks = 3;15291530 // Try to trick Quartz1531 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1532 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgram);15331534 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1535 });15361537 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1538 && event.outcome.isFailedToTransactAsset);15391540 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1541 expect(targetAccountBalance).to.be.equal(0n);15421543 // But Shiden still can send the correct amount1544 const validTransferAmount = shidenBalance / 2n;1545 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1546 targetAccount.addressRaw,1547 {1548 Concrete: {1549 parents: 0,1550 interior: 'Here',1551 },1552 },1553 validTransferAmount,1554 );15551556 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1557 await helper.getSudo().xcm.send(alice, quartzMultilocation, validXcmProgram);1558 });15591560 await helper.wait.newBlocks(maxWaitBlocks);15611562 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1563 expect(targetAccountBalance).to.be.equal(validTransferAmount);1564 });15651566 itSub('Should not accept reserve transfer of QTZ from Shiden', async ({helper}) => {1567 const testAmount = 10_000n * (10n ** QTZ_DECIMALS);1568 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15691570 const quartzMultilocation = {1571 V2: {1572 parents: 1,1573 interior: {1574 X1: {1575 Parachain: QUARTZ_CHAIN,1576 },1577 },1578 },1579 };15801581 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1582 targetAccount.addressRaw,1583 {1584 Concrete: {1585 parents: 1,1586 interior: {1587 X1: {1588 Parachain: QUARTZ_CHAIN,1589 },1590 },1591 },1592 },1593 testAmount,1594 );15951596 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1597 targetAccount.addressRaw,1598 {1599 Concrete: {1600 parents: 0,1601 interior: 'Here',1602 },1603 },1604 testAmount,1605 );16061607 let maliciousXcmProgramFullIdSent: any;1608 let maliciousXcmProgramHereIdSent: any;1609 const maxWaitBlocks = 3;16101611 // Try to trick Quartz using full QTZ identification1612 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1613 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramFullId);16141615 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1616 });16171618 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1619 && event.outcome.isUntrustedReserveLocation);16201621 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1622 expect(accountBalance).to.be.equal(0n);16231624 // Try to trick Quartz using shortened QTZ identification1625 await usingShidenPlaygrounds(shidenUrl, async (helper) => {1626 await helper.getSudo().xcm.send(alice, quartzMultilocation, maliciousXcmProgramHereId);16271628 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1629 });16301631 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1632 && event.outcome.isUntrustedReserveLocation);16331634 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1635 expect(accountBalance).to.be.equal(0n);1636 });1637});