difftreelog
refactor xcm Unq tests
in: master
3 files changed
tests/src/xcm/lowLevelXcmUnique.test.tsdiffbeforeafterboth--- a/tests/src/xcm/lowLevelXcmUnique.test.ts
+++ b/tests/src/xcm/lowLevelXcmUnique.test.ts
@@ -17,98 +17,24 @@
import {IKeyringPair} from '@polkadot/types/types';
import config from '../config';
import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingMoonbeamPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util';
-import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
+import {Event} from '../util/playgrounds/unique.dev';
import {nToBigInt} from '@polkadot/util';
import {hexToString} from '@polkadot/util';
-
-const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);
-const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000);
-const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);
-const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);
-const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);
-
-
+import {ASTAR_DECIMALS, NETWORKS, SAFE_XCM_VERSION, UNIQUE_CHAIN, UNQ_DECIMALS, acalaUrl, astarUrl, expectFailedToTransact, expectUntrustedReserveLocationFail, getDevPlayground, mapToChainId, mapToChainUrl, maxWaitBlocks, moonbeamUrl, polkadexUrl, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types';
-const acalaUrl = config.acalaUrl;
-const moonbeamUrl = config.moonbeamUrl;
-const astarUrl = config.astarUrl;
-const polkadexUrl = config.polkadexUrl;
-const ASTAR_DECIMALS = 18n;
-const UNQ_DECIMALS = 18n;
-
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;
-
-const SAFE_XCM_VERSION = 2;
-const maxWaitBlocks = 6;
-
-const uniqueMultilocation = {
- V2: {
- parents: 1,
- interior: {
- X1: {
- Parachain: UNIQUE_CHAIN,
- },
- },
- },
-};
let balanceUniqueTokenInit: bigint;
let balanceUniqueTokenMiddle: bigint;
let balanceUniqueTokenFinal: bigint;
let unqFees: bigint;
-
-const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {
- await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash
- && event.outcome.isFailedToTransactAsset);
-};
-const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {
- await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash
- && event.outcome.isUntrustedReserveLocation);
-};
-const NETWORKS = {
- acala: usingAcalaPlaygrounds,
- astar: usingAstarPlaygrounds,
- polkadex: usingPolkadexPlaygrounds,
- moonbeam: usingMoonbeamPlaygrounds,
-} as const;
-function mapToChainId(networkName: keyof typeof NETWORKS) {
- switch (networkName) {
- case 'acala':
- return ACALA_CHAIN;
- case 'astar':
- return ASTAR_CHAIN;
- case 'moonbeam':
- return MOONBEAM_CHAIN;
- case 'polkadex':
- return POLKADEX_CHAIN;
- }
-}
-
-function mapToChainUrl(networkName: keyof typeof NETWORKS): string {
- switch (networkName) {
- case 'acala':
- return acalaUrl;
- case 'astar':
- return astarUrl;
- case 'moonbeam':
- return moonbeamUrl;
- case 'polkadex':
- return polkadexUrl;
- }
-}
-
-function getDevPlayground<T extends keyof typeof NETWORKS>(name: T) {
- return NETWORKS[name];
-}
-
-
async function genericSendUnqTo(
networkName: keyof typeof NETWORKS,
randomAccount: IKeyringPair,
@@ -227,13 +153,13 @@
await targetPlayground(networkUrl, async (helper) => {
if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoer, uniqueMultilocation, xcmProgram);
+ 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', [uniqueMultilocation, xcmProgram]);
+ 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 MoonBeam -> Unique via XCM program', batchCall);
+ await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);
xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
}
});
@@ -279,13 +205,13 @@
await targetPlayground(networkUrl, async (helper) => {
if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoer, uniqueMultilocation, maliciousXcmProgram);
+ 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', [uniqueMultilocation, maliciousXcmProgram]);
+ 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 MoonBeam -> Unique via XCM program', batchCall);
+ await helper.fastDemocracy.executeProposal(`sending ${networkName} -> Unique via XCM program`, batchCall);
maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
}
});
@@ -338,16 +264,15 @@
// Try to trick Unique using full UNQ identification
await targetPlayground(networkUrl, async (helper) => {
if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoer, uniqueMultilocation, maliciousXcmProgramFullId);
+ 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', [uniqueMultilocation, maliciousXcmProgramFullId]);
-
+ 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('try to act like a reserve location for UNQ using path asset identification', batchCall);
+ 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);
}
@@ -362,15 +287,14 @@
// Try to trick Unique using shortened UNQ identification
await targetPlayground(networkUrl, async (helper) => {
if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoer, uniqueMultilocation, maliciousXcmProgramHereId);
+ 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', [uniqueMultilocation, maliciousXcmProgramHereId]);
-
+ 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('try to act like a reserve location for UNQ using "here" asset identification', batchCall);
+ 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);
}
@@ -405,19 +329,17 @@
);
await targetPlayground(networkUrl, async (helper) => {
if('getSudo' in helper) {
- await helper.getSudo().xcm.send(sudoerOnTargetChain, uniqueMultilocation, maliciousXcmProgramFullId);
+ 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', [uniqueMultilocation, maliciousXcmProgramFullId]);
-
+ 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('sending native tokens to the Unique via fast democracy', batchCall);
+ await helper.fastDemocracy.executeProposal(`${netwokrName} sending native tokens to the Unique via fast democracy`, batchCall);
messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);
}
});
-
await expectFailedToTransact(helper, messageSent);
});
}
@@ -492,17 +414,6 @@
describeXCM('[XCMLL] Integration test: Exchanging tokens with Polkadex', () => {
let alice: IKeyringPair;
let randomAccount: IKeyringPair;
-
- const uniqueAssetId = {
- Concrete: {
- parents: 1,
- interior: {
- X1: {
- Parachain: UNIQUE_CHAIN,
- },
- },
- },
- };
before(async () => {
await usingPlaygrounds(async (helper, privateKey) => {
@@ -523,7 +434,9 @@
if it is added again. Needed for debugging
when this test is run multiple times.
*/
- if(!isWhitelisted) {
+ if(isWhitelisted) {
+ console.log('UNQ token is already whitelisted on Polkadex');
+ } else {
await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);
}
@@ -639,7 +552,7 @@
const numAssetsWeightHint = 0;
if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {
- console.log('Unique asset already registered');
+ console.log('Unique asset already registered on Moonbeam');
} else {
const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({
location: uniqueAssetLocation,
@@ -750,6 +663,8 @@
console.log('3. Set UNQ payment for XCM execution on Astar');
await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);
+ } else {
+ console.log('UNQ is already registered on Astar');
}
console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');
await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);
tests/src/xcm/xcm.types.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/xcm/xcm.types.ts
@@ -0,0 +1,85 @@
+import {usingAcalaPlaygrounds, usingAstarPlaygrounds, usingMoonbeamPlaygrounds, usingPolkadexPlaygrounds} from '../util';
+import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';
+import config from '../config';
+
+export const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);
+export const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);
+export const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000);
+export const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);
+export const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);
+export const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);
+
+export const acalaUrl = config.acalaUrl;
+export const moonbeamUrl = config.moonbeamUrl;
+export const astarUrl = config.astarUrl;
+export const polkadexUrl = config.polkadexUrl;
+
+export const SAFE_XCM_VERSION = 3;
+
+export const maxWaitBlocks = 6;
+
+
+export const ASTAR_DECIMALS = 18n;
+export const UNQ_DECIMALS = 18n;
+
+export const uniqueMultilocation = {
+ parents: 1,
+ interior: {
+ X1: {
+ Parachain: UNIQUE_CHAIN,
+ },
+ },
+};
+export const uniqueVersionedMultilocation = {
+ V3: uniqueMultilocation,
+};
+
+export const uniqueAssetId = {
+ Concrete: uniqueMultilocation,
+};
+
+export const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {
+ await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash
+ && event.outcome.isFailedToTransactAsset);
+};
+export const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {
+ await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash
+ && event.outcome.isUntrustedReserveLocation);
+};
+
+export const NETWORKS = {
+ acala: usingAcalaPlaygrounds,
+ astar: usingAstarPlaygrounds,
+ polkadex: usingPolkadexPlaygrounds,
+ moonbeam: usingMoonbeamPlaygrounds,
+} as const;
+
+export function mapToChainId(networkName: keyof typeof NETWORKS) {
+ switch (networkName) {
+ case 'acala':
+ return ACALA_CHAIN;
+ case 'astar':
+ return ASTAR_CHAIN;
+ case 'moonbeam':
+ return MOONBEAM_CHAIN;
+ case 'polkadex':
+ return POLKADEX_CHAIN;
+ }
+}
+
+export function mapToChainUrl(networkName: keyof typeof NETWORKS): string {
+ switch (networkName) {
+ case 'acala':
+ return acalaUrl;
+ case 'astar':
+ return astarUrl;
+ case 'moonbeam':
+ return moonbeamUrl;
+ case 'polkadex':
+ return polkadexUrl;
+ }
+}
+
+export function getDevPlayground<T extends keyof typeof NETWORKS>(name: T) {
+ return NETWORKS[name];
+}
\ No newline at end of file
tests/src/xcm/xcmUnique.test.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import config from '../config';19import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util';20import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';21import {hexToString, nToBigInt} from '@polkadot/util';2223const UNIQUE_CHAIN = +(process.env.RELAY_UNIQUE_ID || 2037);24const STATEMINT_CHAIN = +(process.env.RELAY_STATEMINT_ID || 1000);25const ACALA_CHAIN = +(process.env.RELAY_ACALA_ID || 2000);26const MOONBEAM_CHAIN = +(process.env.RELAY_MOONBEAM_ID || 2004);27const ASTAR_CHAIN = +(process.env.RELAY_ASTAR_ID || 2006);28const POLKADEX_CHAIN = +(process.env.RELAY_POLKADEX_ID || 2040);2930const STATEMINT_PALLET_INSTANCE = 50;3132const relayUrl = config.relayUrl;33const statemintUrl = config.statemintUrl;34const acalaUrl = config.acalaUrl;35const moonbeamUrl = config.moonbeamUrl;36const astarUrl = config.astarUrl;37const polkadexUrl = config.polkadexUrl;3839const RELAY_DECIMALS = 12;40const STATEMINT_DECIMALS = 12;41const ACALA_DECIMALS = 12;42const ASTAR_DECIMALS = 18n;43const UNQ_DECIMALS = 18n;4445const TRANSFER_AMOUNT = 2000000000000000000000000n;4647const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4849const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;5051const USDT_ASSET_ID = 100;52const USDT_ASSET_METADATA_DECIMALS = 18;53const USDT_ASSET_METADATA_NAME = 'USDT';54const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';55const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;56const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5758const SAFE_XCM_VERSION = 2;59const maxWaitBlocks = 6;6061const uniqueMultilocation = {62 V2: {63 parents: 1,64 interior: {65 X1: {66 Parachain: UNIQUE_CHAIN,67 },68 },69 },70};7172const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {73 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash74 && event.outcome.isFailedToTransactAsset);75};76const expectUntrustedReserveLocationFail = async (helper: DevUniqueHelper, messageSent: any) => {77 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash78 && event.outcome.isUntrustedReserveLocation);79};80describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {81 let alice: IKeyringPair;82 let bob: IKeyringPair;8384 let balanceStmnBefore: bigint;85 let balanceStmnAfter: bigint;8687 let balanceUniqueBefore: bigint;88 let balanceUniqueAfter: bigint;89 let balanceUniqueFinal: bigint;9091 let balanceBobBefore: bigint;92 let balanceBobAfter: bigint;93 let balanceBobFinal: bigint;9495 let balanceBobRelayTokenBefore: bigint;96 let balanceBobRelayTokenAfter: bigint;979899 before(async () => {100 await usingPlaygrounds(async (helper, privateKey) => {101 alice = await privateKey('//Alice');102 bob = await privateKey('//Bob'); // sovereign account on Statemint funds donor103104 // Set the default version to wrap the first message to other chains.105 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);106 });107108 await usingRelayPlaygrounds(relayUrl, async (helper) => {109 // Fund accounts on Statemint110 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT);111 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT);112 });113114 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {115 const sovereignFundingAmount = 3_500_000_000n;116117 await helper.assets.create(118 alice,119 USDT_ASSET_ID,120 alice.address,121 USDT_ASSET_METADATA_MINIMAL_BALANCE,122 );123 await helper.assets.setMetadata(124 alice,125 USDT_ASSET_ID,126 USDT_ASSET_METADATA_NAME,127 USDT_ASSET_METADATA_DESCRIPTION,128 USDT_ASSET_METADATA_DECIMALS,129 );130 await helper.assets.mint(131 alice,132 USDT_ASSET_ID,133 alice.address,134 USDT_ASSET_AMOUNT,135 );136137 // funding parachain sovereing account on Statemint.138 // The sovereign account should be created before any action139 // (the assets pallet on Statemint check if the sovereign account exists)140 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);141 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);142 });143144145 await usingPlaygrounds(async (helper) => {146 const location = {147 V2: {148 parents: 1,149 interior: {X3: [150 {151 Parachain: STATEMINT_CHAIN,152 },153 {154 PalletInstance: STATEMINT_PALLET_INSTANCE,155 },156 {157 GeneralIndex: USDT_ASSET_ID,158 },159 ]},160 },161 };162163 const metadata =164 {165 name: USDT_ASSET_ID,166 symbol: USDT_ASSET_METADATA_NAME,167 decimals: USDT_ASSET_METADATA_DECIMALS,168 minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,169 };170 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);171 balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);172 });173174175 // Providing the relay currency to the unique sender account176 // (fee for USDT XCM are paid in relay tokens)177 await usingRelayPlaygrounds(relayUrl, async (helper) => {178 const destination = {179 V2: {180 parents: 0,181 interior: {X1: {182 Parachain: UNIQUE_CHAIN,183 },184 },185 }};186187 const beneficiary = {188 V2: {189 parents: 0,190 interior: {X1: {191 AccountId32: {192 network: 'Any',193 id: alice.addressRaw,194 },195 }},196 },197 };198199 const assets = {200 V2: [201 {202 id: {203 Concrete: {204 parents: 0,205 interior: 'Here',206 },207 },208 fun: {209 Fungible: TRANSFER_AMOUNT_RELAY,210 },211 },212 ],213 };214215 const feeAssetItem = 0;216217 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');218 });219220 });221222 itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {223 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {224 const dest = {225 V2: {226 parents: 1,227 interior: {X1: {228 Parachain: UNIQUE_CHAIN,229 },230 },231 }};232233 const beneficiary = {234 V2: {235 parents: 0,236 interior: {X1: {237 AccountId32: {238 network: 'Any',239 id: alice.addressRaw,240 },241 }},242 },243 };244245 const assets = {246 V2: [247 {248 id: {249 Concrete: {250 parents: 0,251 interior: {252 X2: [253 {254 PalletInstance: STATEMINT_PALLET_INSTANCE,255 },256 {257 GeneralIndex: USDT_ASSET_ID,258 },259 ]},260 },261 },262 fun: {263 Fungible: TRANSFER_AMOUNT,264 },265 },266 ],267 };268269 const feeAssetItem = 0;270271 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);272 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');273274 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);275276 // common good parachain take commission in it native token277 console.log(278 '[Statemint -> Unique] transaction fees on Statemint: %s WND',279 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_DECIMALS),280 );281 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;282283 });284285286 // ensure that asset has been delivered287 await helper.wait.newBlocks(3);288289 // expext collection id will be with id 1290 const free = await helper.ft.getBalance(1, {Substrate: alice.address});291292 balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);293294 console.log(295 '[Statemint -> Unique] transaction fees on Unique: %s USDT',296 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),297 );298 console.log(299 '[Statemint -> Unique] transaction fees on Unique: %s UNQ',300 helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore),301 );302 // commission has not paid in USDT token303 expect(free).to.be.equal(TRANSFER_AMOUNT);304 // ... and parachain native token305 expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true;306 });307308 itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => {309 const destination = {310 V2: {311 parents: 1,312 interior: {X2: [313 {314 Parachain: STATEMINT_CHAIN,315 },316 {317 AccountId32: {318 network: 'Any',319 id: alice.addressRaw,320 },321 },322 ]},323 },324 };325326 const relayFee = 400_000_000_000_000n;327 const currencies: [any, bigint][] = [328 [329 {330 ForeignAssetId: 0,331 },332 TRANSFER_AMOUNT,333 ],334 [335 {336 NativeAssetId: 'Parent',337 },338 relayFee,339 ],340 ];341342 const feeItem = 1;343344 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');345346 // the commission has been paid in parachain native token347 balanceUniqueFinal = await helper.balance.getSubstrate(alice.address);348 console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueFinal));349 expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true;350351 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {352 await helper.wait.newBlocks(3);353354 // The USDT token never paid fees. Its amount not changed from begin value.355 // Also check that xcm transfer has been succeeded356 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;357 });358 });359360 itSub('Should connect and send Relay token to Unique', async ({helper}) => {361 balanceBobBefore = await helper.balance.getSubstrate(bob.address);362 balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});363364 await usingRelayPlaygrounds(relayUrl, async (helper) => {365 const destination = {366 V2: {367 parents: 0,368 interior: {X1: {369 Parachain: UNIQUE_CHAIN,370 },371 },372 }};373374 const beneficiary = {375 V2: {376 parents: 0,377 interior: {X1: {378 AccountId32: {379 network: 'Any',380 id: bob.addressRaw,381 },382 }},383 },384 };385386 const assets = {387 V2: [388 {389 id: {390 Concrete: {391 parents: 0,392 interior: 'Here',393 },394 },395 fun: {396 Fungible: TRANSFER_AMOUNT_RELAY,397 },398 },399 ],400 };401402 const feeAssetItem = 0;403404 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');405 });406407 await helper.wait.newBlocks(3);408409 balanceBobAfter = await helper.balance.getSubstrate(bob.address);410 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});411412 const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;413 const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;414 console.log(415 '[Relay (Westend) -> Unique] transaction fees: %s UNQ',416 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),417 );418 console.log(419 '[Relay (Westend) -> Unique] transaction fees: %s WND',420 helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS),421 );422 console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique);423 expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true;424 expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ fees should be taken').to.be.true;425 });426427 itSub('Should connect and send Relay token back', async ({helper}) => {428 let relayTokenBalanceBefore: bigint;429 let relayTokenBalanceAfter: bigint;430 await usingRelayPlaygrounds(relayUrl, async (helper) => {431 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);432 });433434 const destination = {435 V2: {436 parents: 1,437 interior: {438 X1:{439 AccountId32: {440 network: 'Any',441 id: bob.addressRaw,442 },443 },444 },445 },446 };447448 const currencies: any = [449 [450 {451 NativeAssetId: 'Parent',452 },453 TRANSFER_AMOUNT_RELAY,454 ],455 ];456457 const feeItem = 0;458459 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');460461 balanceBobFinal = await helper.balance.getSubstrate(bob.address);462 console.log('[Unique -> Relay (Westend)] transaction fees: %s UNQ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));463464 await usingRelayPlaygrounds(relayUrl, async (helper) => {465 await helper.wait.newBlocks(10);466 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);467468 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;469 console.log('[Unique -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));470 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;471 });472 });473});474475describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => {476 let alice: IKeyringPair;477 let randomAccount: IKeyringPair;478479 let balanceUniqueTokenInit: bigint;480 let balanceUniqueTokenMiddle: bigint;481 let balanceUniqueTokenFinal: bigint;482 let balanceAcalaTokenInit: bigint;483 let balanceAcalaTokenMiddle: bigint;484 let balanceAcalaTokenFinal: bigint;485 let balanceUniqueForeignTokenInit: bigint;486 let balanceUniqueForeignTokenMiddle: bigint;487 let balanceUniqueForeignTokenFinal: bigint;488489 // computed by a test transfer from prod Unique to prod Acala.490 // 2 UNQ sent https://unique.subscan.io/xcm_message/polkadot-bad0b68847e2398af25d482e9ee6f9c1f9ec2a48491 // 1.898970000000000000 UNQ received (you can check Acala's chain state in the corresponding block)492 const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;493 const acalaEps = 8n * 10n ** 16n;494495 let acalaBackwardTransferAmount: bigint;496497 before(async () => {498 await usingPlaygrounds(async (helper, privateKey) => {499 alice = await privateKey('//Alice');500 [randomAccount] = await helper.arrange.createAccounts([0n], alice);501502 // Set the default version to wrap the first message to other chains.503 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);504 });505506 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {507 const destination = {508 V2: {509 parents: 1,510 interior: {511 X1: {512 Parachain: UNIQUE_CHAIN,513 },514 },515 },516 };517518 const metadata = {519 name: 'Unique Network',520 symbol: 'UNQ',521 decimals: 18,522 minimalBalance: 1250000000000000000n,523 };524 const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>525 hexToString(v.toJSON()['symbol'])) as string[];526527 if(!assets.includes('UNQ')) {528 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);529 } else {530 console.log('UNQ token already registered on Acala assetRegistry pallet');531 }532 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);533 balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);534 balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});535 });536537 await usingPlaygrounds(async (helper) => {538 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);539 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);540 });541 });542543 itSub('Should connect and send UNQ to Acala', async ({helper}) => {544545 const destination = {546 V2: {547 parents: 1,548 interior: {549 X1: {550 Parachain: ACALA_CHAIN,551 },552 },553 },554 };555556 const beneficiary = {557 V2: {558 parents: 0,559 interior: {560 X1: {561 AccountId32: {562 network: 'Any',563 id: randomAccount.addressRaw,564 },565 },566 },567 },568 };569570 const assets = {571 V2: [572 {573 id: {574 Concrete: {575 parents: 0,576 interior: 'Here',577 },578 },579 fun: {580 Fungible: TRANSFER_AMOUNT,581 },582 },583 ],584 };585586 const feeAssetItem = 0;587588 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');589 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);590591 const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;592 console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));593 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;594595 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {596 await helper.wait.newBlocks(3);597598 balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});599 balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);600601 const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;602 const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;603 acalaBackwardTransferAmount = unqIncomeTransfer;604605 const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;606607 console.log(608 '[Unique -> Acala] transaction fees on Acala: %s ACA',609 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),610 );611 console.log(612 '[Unique -> Acala] transaction fees on Acala: %s UNQ',613 helper.util.bigIntToDecimals(acaUnqFees),614 );615 console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));616 expect(acaFees == 0n).to.be.true;617618 const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;619620 expect(621 bigintAbs(acaUnqFees - expectedAcalaIncomeFee) < acalaEps,622 'Acala took different income fee, check the Acala foreign asset config',623 ).to.be.true;624 });625 });626627 itSub('Should connect to Acala and send UNQ back', async ({helper}) => {628 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {629 const destination = {630 V2: {631 parents: 1,632 interior: {633 X2: [634 {Parachain: UNIQUE_CHAIN},635 {636 AccountId32: {637 network: 'Any',638 id: randomAccount.addressRaw,639 },640 },641 ],642 },643 },644 };645646 const id = {647 ForeignAsset: 0,648 };649650 await helper.xTokens.transfer(randomAccount, id, acalaBackwardTransferAmount, destination, 'Unlimited');651 balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);652 balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);653654 const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;655 const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;656657 console.log(658 '[Acala -> Unique] transaction fees on Acala: %s ACA',659 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),660 );661 console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));662663 expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;664 expect(unqOutcomeTransfer == acalaBackwardTransferAmount).to.be.true;665 });666667 await helper.wait.newBlocks(3);668669 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);670 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;671 expect(actuallyDelivered > 0).to.be.true;672673 console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));674675 const unqFees = acalaBackwardTransferAmount - actuallyDelivered;676 console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));677 expect(unqFees == 0n).to.be.true;678 });679680 itSub('Acala can send only up to its balance', async ({helper}) => {681 // set Acala's sovereign account's balance682 const acalaBalance = 10000n * (10n ** UNQ_DECIMALS);683 const acalaSovereignAccount = helper.address.paraSiblingSovereignAccount(ACALA_CHAIN);684 await helper.getSudo().balance.setBalanceSubstrate(alice, acalaSovereignAccount, acalaBalance);685686 const moreThanAcalaHas = acalaBalance * 2n;687688 let targetAccountBalance = 0n;689 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);690691 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(692 targetAccount.addressRaw,693 {694 Concrete: {695 parents: 0,696 interior: 'Here',697 },698 },699 moreThanAcalaHas,700 );701702 let maliciousXcmProgramSent: any;703 const maxWaitBlocks = 3;704705 // Try to trick Unique706 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {707 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);708709 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);710 });711712 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash713 && event.outcome.isFailedToTransactAsset);714715 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);716 expect(targetAccountBalance).to.be.equal(0n);717718 // But Acala still can send the correct amount719 const validTransferAmount = acalaBalance / 2n;720 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(721 targetAccount.addressRaw,722 {723 Concrete: {724 parents: 0,725 interior: 'Here',726 },727 },728 validTransferAmount,729 );730731 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {732 await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);733 });734735 await helper.wait.newBlocks(maxWaitBlocks);736737 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);738 expect(targetAccountBalance).to.be.equal(validTransferAmount);739 });740741 itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {742 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);743 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);744745 const uniqueMultilocation = {746 V2: {747 parents: 1,748 interior: {749 X1: {750 Parachain: UNIQUE_CHAIN,751 },752 },753 },754 };755756 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(757 targetAccount.addressRaw,758 {759 Concrete: {760 parents: 1,761 interior: {762 X1: {763 Parachain: UNIQUE_CHAIN,764 },765 },766 },767 },768 testAmount,769 );770771 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(772 targetAccount.addressRaw,773 {774 Concrete: {775 parents: 0,776 interior: 'Here',777 },778 },779 testAmount,780 );781782 let maliciousXcmProgramFullIdSent: any;783 let maliciousXcmProgramHereIdSent: any;784 const maxWaitBlocks = 3;785786 // Try to trick Unique using full UNQ identification787 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {788 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);789790 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);791 });792793 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash794 && event.outcome.isUntrustedReserveLocation);795796 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);797 expect(accountBalance).to.be.equal(0n);798799 // Try to trick Unique using shortened UNQ identification800 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {801 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);802803 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);804 });805806 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash807 && event.outcome.isUntrustedReserveLocation);808809 accountBalance = await helper.balance.getSubstrate(targetAccount.address);810 expect(accountBalance).to.be.equal(0n);811 });812});813814describeXCM('[XCM] Integration test: Exchanging tokens with Polkadex', () => {815 let alice: IKeyringPair;816 let randomAccount: IKeyringPair;817 let unqFees: bigint;818 let balanceUniqueTokenInit: bigint;819 let balanceUniqueTokenMiddle: bigint;820 let balanceUniqueTokenFinal: bigint;821 const maxWaitBlocks = 6;822823 const uniqueAssetId = {824 Concrete: {825 parents: 1,826 interior: {827 X1: {828 Parachain: UNIQUE_CHAIN,829 },830 },831 },832 };833834 before(async () => {835 await usingPlaygrounds(async (helper, privateKey) => {836 alice = await privateKey('//Alice');837 [randomAccount] = await helper.arrange.createAccounts([0n], alice);838839 // Set the default version to wrap the first message to other chains.840 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);841 });842843 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {844 const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))845 .toJSON() as [])846 .map(nToBigInt).length != 0;847 /*848 Check whether the Unique token has been added849 to the whitelist, since an error will occur850 if it is added again. Needed for debugging851 when this test is run multiple times.852 */853 if(!isWhitelisted) {854 await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);855 }856857 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);858 });859860 await usingPlaygrounds(async (helper) => {861 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);862 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);863 });864 });865866 itSub('Should connect and send UNQ to Polkadex', async ({helper}) => {867868 const destination = {869 V2: {870 parents: 1,871 interior: {872 X1: {873 Parachain: POLKADEX_CHAIN,874 },875 },876 },877 };878879 const beneficiary = {880 V2: {881 parents: 0,882 interior: {883 X1: {884 AccountId32: {885 network: 'Any',886 id: randomAccount.addressRaw,887 },888 },889 },890 },891 };892893 const assets = {894 V2: [895 {896 id: {897 Concrete: {898 parents: 0,899 interior: 'Here',900 },901 },902 fun: {903 Fungible: TRANSFER_AMOUNT,904 },905 },906 ],907 };908909 const feeAssetItem = 0;910911 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');912 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);913 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);914915 unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;916 console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));917 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;918919 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {920 /*921 Since only the parachain part of the Polkadex922 infrastructure is launched (without their923 solochain validators), processing incoming924 assets will lead to an error.925 This error indicates that the Polkadex chain926 received a message from the Unique network,927 since the hash is being checked to ensure928 it matches what was sent.929 */930 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);931 });932 });933934935 itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {936937 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(938 randomAccount.addressRaw,939 {940 Concrete: {941 parents: 1,942 interior: {943 X1: {Parachain: UNIQUE_CHAIN},944 },945 },946 },947 TRANSFER_AMOUNT,948 );949950 let xcmProgramSent: any;951952953 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {954 await helper.getSudo().xcm.send(alice, uniqueMultilocation, xcmProgram);955956 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);957 });958959 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);960961 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);962963 expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees);964 });965966 itSub('Polkadex can send only up to its balance', async ({helper}) => {967 const polkadexBalance = 10000n * (10n ** UNQ_DECIMALS);968 const polkadexSovereignAccount = helper.address.paraSiblingSovereignAccount(POLKADEX_CHAIN);969 await helper.getSudo().balance.setBalanceSubstrate(alice, polkadexSovereignAccount, polkadexBalance);970 const moreThanPolkadexHas = 2n * polkadexBalance;971972 const targetAccount = helper.arrange.createEmptyAccount();973974 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(975 targetAccount.addressRaw,976 {977 Concrete: {978 parents: 0,979 interior: 'Here',980 },981 },982 moreThanPolkadexHas,983 );984985 let maliciousXcmProgramSent: any;986987988 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {989 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);990991 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);992 });993994 await expectFailedToTransact(helper, maliciousXcmProgramSent);995996 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);997 expect(targetAccountBalance).to.be.equal(0n);998 });9991000 itSub('Should not accept reserve transfer of UNQ from Polkadex', async ({helper}) => {1001 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1002 const targetAccount = helper.arrange.createEmptyAccount();10031004 const uniqueMultilocation = {1005 V2: {1006 parents: 1,1007 interior: {1008 X1: {1009 Parachain: UNIQUE_CHAIN,1010 },1011 },1012 },1013 };10141015 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1016 targetAccount.addressRaw,1017 {1018 Concrete: {1019 parents: 1,1020 interior: {1021 X1: {1022 Parachain: UNIQUE_CHAIN,1023 },1024 },1025 },1026 },1027 testAmount,1028 );10291030 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1031 targetAccount.addressRaw,1032 {1033 Concrete: {1034 parents: 0,1035 interior: 'Here',1036 },1037 },1038 testAmount,1039 );10401041 let maliciousXcmProgramFullIdSent: any;1042 let maliciousXcmProgramHereIdSent: any;1043 const maxWaitBlocks = 3;10441045 // Try to trick Unique using full UNQ identification1046 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1047 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);10481049 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1050 });10511052 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);10531054 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1055 expect(accountBalance).to.be.equal(0n);10561057 // Try to trick Unique using shortened UNQ identification1058 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1059 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);10601061 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1062 });10631064 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);10651066 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1067 expect(accountBalance).to.be.equal(0n);1068 });1069});10701071// These tests are relevant only when1072// the the corresponding foreign assets are not registered1073describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {1074 let alice: IKeyringPair;1075 let alith: IKeyringPair;10761077 const testAmount = 100_000_000_000n;1078 let uniqueParachainJunction;1079 let uniqueAccountJunction;10801081 let uniqueParachainMultilocation: any;1082 let uniqueAccountMultilocation: any;1083 let uniqueCombinedMultilocation: any;1084 let uniqueCombinedMultilocationAcala: any; // TODO remove when Acala goes V210851086 let messageSent: any;10871088 const maxWaitBlocks = 3;10891090 before(async () => {1091 await usingPlaygrounds(async (helper, privateKey) => {1092 alice = await privateKey('//Alice');10931094 uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};1095 uniqueAccountJunction = {1096 AccountId32: {1097 network: 'Any',1098 id: alice.addressRaw,1099 },1100 };11011102 uniqueParachainMultilocation = {1103 V2: {1104 parents: 1,1105 interior: {1106 X1: uniqueParachainJunction,1107 },1108 },1109 };11101111 uniqueAccountMultilocation = {1112 V2: {1113 parents: 0,1114 interior: {1115 X1: uniqueAccountJunction,1116 },1117 },1118 };11191120 uniqueCombinedMultilocation = {1121 V2: {1122 parents: 1,1123 interior: {1124 X2: [uniqueParachainJunction, uniqueAccountJunction],1125 },1126 },1127 };11281129 uniqueCombinedMultilocationAcala = {1130 V2: {1131 parents: 1,1132 interior: {1133 X2: [uniqueParachainJunction, uniqueAccountJunction],1134 },1135 },1136 };11371138 // Set the default version to wrap the first message to other chains.1139 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1140 });11411142 // eslint-disable-next-line require-await1143 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1144 alith = helper.account.alithAccount();1145 });1146 });1147114811491150 itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {1151 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {1152 const id = {1153 Token: 'ACA',1154 };1155 const destination = uniqueCombinedMultilocationAcala;1156 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');11571158 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1159 });11601161 await expectFailedToTransact(helper, messageSent);1162 });11631164 itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {1165 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1166 const id = 'SelfReserve';1167 const destination = uniqueCombinedMultilocation;1168 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');11691170 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1171 });11721173 await expectFailedToTransact(helper, messageSent);1174 });11751176 itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {1177 await usingAstarPlaygrounds(astarUrl, async (helper) => {1178 const destinationParachain = uniqueParachainMultilocation;1179 const beneficiary = uniqueAccountMultilocation;1180 const assets = {1181 V2: [{1182 id: {1183 Concrete: {1184 parents: 0,1185 interior: 'Here',1186 },1187 },1188 fun: {1189 Fungible: testAmount,1190 },1191 }],1192 };1193 const feeAssetItem = 0;11941195 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1196 destinationParachain,1197 beneficiary,1198 assets,1199 feeAssetItem,1200 ]);12011202 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1203 });12041205 await expectFailedToTransact(helper, messageSent);1206 });12071208 itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => {12091210 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1211 helper.arrange.createEmptyAccount().addressRaw,1212 {1213 Concrete: {1214 parents: 1,1215 interior: {1216 X1: {1217 Parachain: POLKADEX_CHAIN,1218 },1219 },1220 },1221 },1222 testAmount,1223 );12241225 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1226 await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId);1227 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1228 });12291230 await expectFailedToTransact(helper, messageSent);1231 });1232});12331234describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {1235 // Unique constants1236 let alice: IKeyringPair;1237 let uniqueAssetLocation;12381239 let randomAccountUnique: IKeyringPair;1240 let randomAccountMoonbeam: IKeyringPair;12411242 // Moonbeam constants1243 let assetId: string;12441245 const uniqueAssetMetadata = {1246 name: 'xcUnique',1247 symbol: 'xcUNQ',1248 decimals: 18,1249 isFrozen: false,1250 minimalBalance: 1n,1251 };12521253 let balanceUniqueTokenInit: bigint;1254 let balanceUniqueTokenMiddle: bigint;1255 let balanceUniqueTokenFinal: bigint;1256 let balanceForeignUnqTokenInit: bigint;1257 let balanceForeignUnqTokenMiddle: bigint;1258 let balanceForeignUnqTokenFinal: bigint;1259 let balanceGlmrTokenInit: bigint;1260 let balanceGlmrTokenMiddle: bigint;1261 let balanceGlmrTokenFinal: bigint;12621263 before(async () => {1264 await usingPlaygrounds(async (helper, privateKey) => {1265 alice = await privateKey('//Alice');1266 [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);12671268 balanceForeignUnqTokenInit = 0n;12691270 // Set the default version to wrap the first message to other chains.1271 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1272 });12731274 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1275 const alithAccount = helper.account.alithAccount();1276 const baltatharAccount = helper.account.baltatharAccount();1277 const dorothyAccount = helper.account.dorothyAccount();12781279 randomAccountMoonbeam = helper.account.create();12801281 // >>> Sponsoring Dorothy >>>1282 console.log('Sponsoring Dorothy.......');1283 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1284 console.log('Sponsoring Dorothy.......DONE');1285 // <<< Sponsoring Dorothy <<<12861287 uniqueAssetLocation = {1288 XCM: {1289 parents: 1,1290 interior: {X1: {Parachain: UNIQUE_CHAIN}},1291 },1292 };1293 const existentialDeposit = 1n;1294 const isSufficient = true;1295 const unitsPerSecond = 1n;1296 const numAssetsWeightHint = 0;12971298 if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {1299 console.log('Unique asset is already registered on MoonBeam');1300 } else {1301 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1302 location: uniqueAssetLocation,1303 metadata: uniqueAssetMetadata,1304 existentialDeposit,1305 isSufficient,1306 unitsPerSecond,1307 numAssetsWeightHint,1308 });13091310 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);13111312 await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);1313 }13141315 // >>> Acquire Unique AssetId Info on Moonbeam >>>1316 console.log('Acquire Unique AssetId Info on Moonbeam.......');13171318 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();1319 console.log('UNQ asset ID is %s', assetId);1320 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1321 // >>> Acquire Unique AssetId Info on Moonbeam >>>13221323 // >>> Sponsoring random Account >>>1324 console.log('Sponsoring random Account.......');1325 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1326 console.log('Sponsoring random Account.......DONE');1327 // <<< Sponsoring random Account <<<13281329 balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1330 });13311332 await usingPlaygrounds(async (helper) => {1333 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1334 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1335 });1336 });13371338 itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1339 const currencyId = {1340 NativeAssetId: 'Here',1341 };1342 const dest = {1343 V2: {1344 parents: 1,1345 interior: {1346 X2: [1347 {Parachain: MOONBEAM_CHAIN},1348 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1349 ],1350 },1351 },1352 };1353 const amount = TRANSFER_AMOUNT;13541355 await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');13561357 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1358 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;13591360 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1361 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1362 expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13631364 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1365 await helper.wait.newBlocks(3);1366 balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);13671368 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1369 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1370 expect(glmrFees == 0n).to.be.true;13711372 balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;13731374 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1375 console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1376 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1377 });1378 });13791380 itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1381 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1382 const asset = {1383 V2: {1384 id: {1385 Concrete: {1386 parents: 1,1387 interior: {1388 X1: {Parachain: UNIQUE_CHAIN},1389 },1390 },1391 },1392 fun: {1393 Fungible: TRANSFER_AMOUNT,1394 },1395 },1396 };1397 const destination = {1398 V2: {1399 parents: 1,1400 interior: {1401 X2: [1402 {Parachain: UNIQUE_CHAIN},1403 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1404 ],1405 },1406 },1407 };14081409 await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');14101411 balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);14121413 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1414 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1415 expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;14161417 const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);14181419 expect(unqRandomAccountAsset).to.be.null;14201421 balanceForeignUnqTokenFinal = 0n;14221423 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1424 console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1425 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1426 });14271428 await helper.wait.newBlocks(3);14291430 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1431 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1432 expect(actuallyDelivered > 0).to.be.true;14331434 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));14351436 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1437 console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1438 expect(unqFees == 0n).to.be.true;1439 });14401441 itSub('Moonbeam can send only up to its balance', async ({helper}) => {1442 // set Moonbeam's sovereign account's balance1443 const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1444 const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1445 await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);14461447 const moreThanMoonbeamHas = moonbeamBalance * 2n;14481449 let targetAccountBalance = 0n;1450 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);14511452 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1453 targetAccount.addressRaw,1454 {1455 Concrete: {1456 parents: 0,1457 interior: 'Here',1458 },1459 },1460 moreThanMoonbeamHas,1461 );14621463 let maliciousXcmProgramSent: any;1464 const maxWaitBlocks = 3;14651466 // Try to trick Unique1467 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1468 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgram]);14691470 // Needed to bypass the call filter.1471 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1472 await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14731474 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1475 });14761477 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1478 && event.outcome.isFailedToTransactAsset);14791480 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1481 expect(targetAccountBalance).to.be.equal(0n);14821483 // But Moonbeam still can send the correct amount1484 const validTransferAmount = moonbeamBalance / 2n;1485 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1486 targetAccount.addressRaw,1487 {1488 Concrete: {1489 parents: 0,1490 interior: 'Here',1491 },1492 },1493 validTransferAmount,1494 );14951496 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1497 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, validXcmProgram]);14981499 // Needed to bypass the call filter.1500 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1501 await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1502 });15031504 await helper.wait.newBlocks(maxWaitBlocks);15051506 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1507 expect(targetAccountBalance).to.be.equal(validTransferAmount);1508 });15091510 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1511 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1512 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15131514 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1515 targetAccount.addressRaw,1516 {1517 Concrete: {1518 parents: 0,1519 interior: {1520 X1: {1521 Parachain: UNIQUE_CHAIN,1522 },1523 },1524 },1525 },1526 testAmount,1527 );15281529 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1530 targetAccount.addressRaw,1531 {1532 Concrete: {1533 parents: 0,1534 interior: 'Here',1535 },1536 },1537 testAmount,1538 );15391540 let maliciousXcmProgramFullIdSent: any;1541 let maliciousXcmProgramHereIdSent: any;1542 const maxWaitBlocks = 3;15431544 // Try to trick Unique using full UNQ identification1545 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1546 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);15471548 // Needed to bypass the call filter.1549 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1550 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);15511552 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1553 });15541555 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1556 && event.outcome.isUntrustedReserveLocation);15571558 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1559 expect(accountBalance).to.be.equal(0n);15601561 // Try to trick Unique using shortened UNQ identification1562 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1563 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramHereId]);15641565 // Needed to bypass the call filter.1566 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1567 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);15681569 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1570 });15711572 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1573 && event.outcome.isUntrustedReserveLocation);15741575 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1576 expect(accountBalance).to.be.equal(0n);1577 });1578});15791580describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1581 let alice: IKeyringPair;1582 let randomAccount: IKeyringPair;15831584 const UNQ_ASSET_ID_ON_ASTAR = 1;1585 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;15861587 // Unique -> Astar1588 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.1589 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1590 const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1591 const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Astar takes a commision in foreign tokens15921593 // Astar -> Unique1594 const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ1595 const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; // 4.999_999_999_088_000_000n UNQ15961597 let balanceAfterUniqueToAstarXCM: bigint;15981599 before(async () => {1600 await usingPlaygrounds(async (helper, privateKey) => {1601 alice = await privateKey('//Alice');1602 [randomAccount] = await helper.arrange.createAccounts([100n], alice);1603 console.log('randomAccount', randomAccount.address);16041605 // Set the default version to wrap the first message to other chains.1606 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1607 });16081609 await usingAstarPlaygrounds(astarUrl, async (helper) => {1610 if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {1611 console.log('1. Create foreign asset and metadata');1612 // TODO update metadata with values from production1613 await helper.assets.create(1614 alice,1615 UNQ_ASSET_ID_ON_ASTAR,1616 alice.address,1617 UNQ_MINIMAL_BALANCE_ON_ASTAR,1618 );16191620 await helper.assets.setMetadata(1621 alice,1622 UNQ_ASSET_ID_ON_ASTAR,1623 'Cross chain UNQ',1624 'xcUNQ',1625 Number(UNQ_DECIMALS),1626 );16271628 console.log('2. Register asset location on Astar');1629 const assetLocation = {1630 V2: {1631 parents: 1,1632 interior: {1633 X1: {1634 Parachain: UNIQUE_CHAIN,1635 },1636 },1637 },1638 };16391640 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);16411642 console.log('3. Set UNQ payment for XCM execution on Astar');1643 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1644 }1645 console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1646 await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1647 });1648 });16491650 itSub('Should connect and send UNQ to Astar', async ({helper}) => {1651 const destination = {1652 V2: {1653 parents: 1,1654 interior: {1655 X1: {1656 Parachain: ASTAR_CHAIN,1657 },1658 },1659 },1660 };16611662 const beneficiary = {1663 V2: {1664 parents: 0,1665 interior: {1666 X1: {1667 AccountId32: {1668 network: 'Any',1669 id: randomAccount.addressRaw,1670 },1671 },1672 },1673 },1674 };16751676 const assets = {1677 V2: [1678 {1679 id: {1680 Concrete: {1681 parents: 0,1682 interior: 'Here',1683 },1684 },1685 fun: {1686 Fungible: unqToAstarTransferred,1687 },1688 },1689 ],1690 };16911692 // Initial balance is 100 UNQ1693 const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1694 console.log(`Initial balance is: ${balanceBefore}`);16951696 const feeAssetItem = 0;1697 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');16981699 // Balance after reserve transfer is less than 901700 balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1701 console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1702 console.log(`Unique's UNQ commission is: ${balanceBefore-balanceAfterUniqueToAstarXCM}`);1703 expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;17041705 await usingAstarPlaygrounds(astarUrl, async (helper) => {1706 await helper.wait.newBlocks(3);1707 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1708 const astarBalance = await helper.balance.getSubstrate(randomAccount.address);17091710 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1711 console.log(`Astar's UNQ commission is: ${unqToAstarTransferred-xcUNQbalance!}`);17121713 expect(xcUNQbalance).to.eq(unqToAstarArrived);1714 // Astar balance does not changed1715 expect(astarBalance).to.eq(astarInitialBalance);1716 });1717 });17181719 itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1720 await usingAstarPlaygrounds(astarUrl, async (helper) => {1721 const destination = {1722 V2: {1723 parents: 1,1724 interior: {1725 X1: {1726 Parachain: UNIQUE_CHAIN,1727 },1728 },1729 },1730 };17311732 const beneficiary = {1733 V2: {1734 parents: 0,1735 interior: {1736 X1: {1737 AccountId32: {1738 network: 'Any',1739 id: randomAccount.addressRaw,1740 },1741 },1742 },1743 },1744 };17451746 const assets = {1747 V2: [1748 {1749 id: {1750 Concrete: {1751 parents: 1,1752 interior: {1753 X1: {1754 Parachain: UNIQUE_CHAIN,1755 },1756 },1757 },1758 },1759 fun: {1760 Fungible: unqFromAstarTransfered,1761 },1762 },1763 ],1764 };17651766 // Initial balance is 1 ASTR1767 const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1768 console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1769 expect(balanceASTRbefore).to.eq(astarInitialBalance);17701771 const feeAssetItem = 0;1772 // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1773 await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);17741775 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1776 const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1777 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);17781779 // Assert: xcUNQ balance correctly decreased1780 expect(xcUNQbalance).to.eq(unqOnAstarLeft);1781 // Assert: ASTR balance is 0.996...1782 expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1783 });17841785 await helper.wait.newBlocks(3);1786 const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1787 console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1788 expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1789 });17901791 itSub('Astar can send only up to its balance', async ({helper}) => {1792 // set Astar's sovereign account's balance1793 const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1794 const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1795 await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);17961797 const moreThanAstarHas = astarBalance * 2n;17981799 let targetAccountBalance = 0n;1800 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);18011802 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1803 targetAccount.addressRaw,1804 {1805 Concrete: {1806 parents: 0,1807 interior: 'Here',1808 },1809 },1810 moreThanAstarHas,1811 );18121813 let maliciousXcmProgramSent: any;1814 const maxWaitBlocks = 3;18151816 // Try to trick Unique1817 await usingAstarPlaygrounds(astarUrl, async (helper) => {1818 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);18191820 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1821 });18221823 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1824 && event.outcome.isFailedToTransactAsset);18251826 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1827 expect(targetAccountBalance).to.be.equal(0n);18281829 // But Astar still can send the correct amount1830 const validTransferAmount = astarBalance / 2n;1831 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1832 targetAccount.addressRaw,1833 {1834 Concrete: {1835 parents: 0,1836 interior: 'Here',1837 },1838 },1839 validTransferAmount,1840 );18411842 await usingAstarPlaygrounds(astarUrl, async (helper) => {1843 await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);1844 });18451846 await helper.wait.newBlocks(maxWaitBlocks);18471848 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1849 expect(targetAccountBalance).to.be.equal(validTransferAmount);1850 });18511852 itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1853 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1854 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);18551856 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1857 targetAccount.addressRaw,1858 {1859 Concrete: {1860 parents: 1,1861 interior: {1862 X1: {1863 Parachain: UNIQUE_CHAIN,1864 },1865 },1866 },1867 },1868 testAmount,1869 );18701871 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1872 targetAccount.addressRaw,1873 {1874 Concrete: {1875 parents: 0,1876 interior: 'Here',1877 },1878 },1879 testAmount,1880 );18811882 let maliciousXcmProgramFullIdSent: any;1883 let maliciousXcmProgramHereIdSent: any;1884 const maxWaitBlocks = 3;18851886 // Try to trick Unique using full UNQ identification1887 await usingAstarPlaygrounds(astarUrl, async (helper) => {1888 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);18891890 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1891 });18921893 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1894 && event.outcome.isUntrustedReserveLocation);18951896 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1897 expect(accountBalance).to.be.equal(0n);18981899 // Try to trick Unique using shortened UNQ identification1900 await usingAstarPlaygrounds(astarUrl, async (helper) => {1901 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);19021903 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1904 });19051906 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1907 && event.outcome.isUntrustedReserveLocation);19081909 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1910 expect(accountBalance).to.be.equal(0n);1911 });1912});1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {IKeyringPair} from '@polkadot/types/types';18import config from '../config';19import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds, usingPolkadexPlaygrounds} from '../util';20import {Event} from '../util/playgrounds/unique.dev';21import {hexToString, nToBigInt} from '@polkadot/util';22import {ACALA_CHAIN, ASTAR_CHAIN, MOONBEAM_CHAIN, POLKADEX_CHAIN, SAFE_XCM_VERSION, STATEMINT_CHAIN, UNIQUE_CHAIN, expectFailedToTransact, expectUntrustedReserveLocationFail, uniqueAssetId, uniqueVersionedMultilocation} from './xcm.types';232425const STATEMINT_PALLET_INSTANCE = 50;2627const relayUrl = config.relayUrl;28const statemintUrl = config.statemintUrl;29const acalaUrl = config.acalaUrl;30const moonbeamUrl = config.moonbeamUrl;31const astarUrl = config.astarUrl;32const polkadexUrl = config.polkadexUrl;3334const RELAY_DECIMALS = 12;35const STATEMINT_DECIMALS = 12;36const ACALA_DECIMALS = 12;37const ASTAR_DECIMALS = 18n;38const UNQ_DECIMALS = 18n;3940const TRANSFER_AMOUNT = 2000000000000000000000000n;4142const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4344const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4546const USDT_ASSET_ID = 100;47const USDT_ASSET_METADATA_DECIMALS = 18;48const USDT_ASSET_METADATA_NAME = 'USDT';49const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';50const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;51const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5253describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {54 let alice: IKeyringPair;55 let bob: IKeyringPair;5657 let balanceStmnBefore: bigint;58 let balanceStmnAfter: bigint;5960 let balanceUniqueBefore: bigint;61 let balanceUniqueAfter: bigint;62 let balanceUniqueFinal: bigint;6364 let balanceBobBefore: bigint;65 let balanceBobAfter: bigint;66 let balanceBobFinal: bigint;6768 let balanceBobRelayTokenBefore: bigint;69 let balanceBobRelayTokenAfter: bigint;707172 before(async () => {73 await usingPlaygrounds(async (helper, privateKey) => {74 alice = await privateKey('//Alice');75 bob = await privateKey('//Bob'); // sovereign account on Statemint funds donor7677 // Set the default version to wrap the first message to other chains.78 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);79 });8081 await usingRelayPlaygrounds(relayUrl, async (helper) => {82 // Fund accounts on Statemint83 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT);84 await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT);85 });8687 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {88 const sovereignFundingAmount = 3_500_000_000n;8990 await helper.assets.create(91 alice,92 USDT_ASSET_ID,93 alice.address,94 USDT_ASSET_METADATA_MINIMAL_BALANCE,95 );96 await helper.assets.setMetadata(97 alice,98 USDT_ASSET_ID,99 USDT_ASSET_METADATA_NAME,100 USDT_ASSET_METADATA_DESCRIPTION,101 USDT_ASSET_METADATA_DECIMALS,102 );103 await helper.assets.mint(104 alice,105 USDT_ASSET_ID,106 alice.address,107 USDT_ASSET_AMOUNT,108 );109110 // funding parachain sovereing account on Statemint.111 // The sovereign account should be created before any action112 // (the assets pallet on Statemint check if the sovereign account exists)113 const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);114 await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);115 });116117118 await usingPlaygrounds(async (helper) => {119 const location = {120 V2: {121 parents: 1,122 interior: {X3: [123 {124 Parachain: STATEMINT_CHAIN,125 },126 {127 PalletInstance: STATEMINT_PALLET_INSTANCE,128 },129 {130 GeneralIndex: USDT_ASSET_ID,131 },132 ]},133 },134 };135136 const metadata =137 {138 name: USDT_ASSET_ID,139 symbol: USDT_ASSET_METADATA_NAME,140 decimals: USDT_ASSET_METADATA_DECIMALS,141 minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,142 };143 await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);144 balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);145 });146147148 // Providing the relay currency to the unique sender account149 // (fee for USDT XCM are paid in relay tokens)150 await usingRelayPlaygrounds(relayUrl, async (helper) => {151 const destination = {152 V2: {153 parents: 0,154 interior: {X1: {155 Parachain: UNIQUE_CHAIN,156 },157 },158 }};159160 const beneficiary = {161 V2: {162 parents: 0,163 interior: {X1: {164 AccountId32: {165 network: 'Any',166 id: alice.addressRaw,167 },168 }},169 },170 };171172 const assets = {173 V2: [174 {175 id: {176 Concrete: {177 parents: 0,178 interior: 'Here',179 },180 },181 fun: {182 Fungible: TRANSFER_AMOUNT_RELAY,183 },184 },185 ],186 };187188 const feeAssetItem = 0;189190 await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');191 });192193 });194195 itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {196 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {197 const dest = {198 V2: {199 parents: 1,200 interior: {X1: {201 Parachain: UNIQUE_CHAIN,202 },203 },204 }};205206 const beneficiary = {207 V2: {208 parents: 0,209 interior: {X1: {210 AccountId32: {211 network: 'Any',212 id: alice.addressRaw,213 },214 }},215 },216 };217218 const assets = {219 V2: [220 {221 id: {222 Concrete: {223 parents: 0,224 interior: {225 X2: [226 {227 PalletInstance: STATEMINT_PALLET_INSTANCE,228 },229 {230 GeneralIndex: USDT_ASSET_ID,231 },232 ]},233 },234 },235 fun: {236 Fungible: TRANSFER_AMOUNT,237 },238 },239 ],240 };241242 const feeAssetItem = 0;243244 balanceStmnBefore = await helper.balance.getSubstrate(alice.address);245 await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');246247 balanceStmnAfter = await helper.balance.getSubstrate(alice.address);248249 // common good parachain take commission in it native token250 console.log(251 '[Statemint -> Unique] transaction fees on Statemint: %s WND',252 helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_DECIMALS),253 );254 expect(balanceStmnBefore > balanceStmnAfter).to.be.true;255256 });257258259 // ensure that asset has been delivered260 await helper.wait.newBlocks(3);261262 // expext collection id will be with id 1263 const free = await helper.ft.getBalance(1, {Substrate: alice.address});264265 balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);266267 console.log(268 '[Statemint -> Unique] transaction fees on Unique: %s USDT',269 helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),270 );271 console.log(272 '[Statemint -> Unique] transaction fees on Unique: %s UNQ',273 helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore),274 );275 // commission has not paid in USDT token276 expect(free).to.be.equal(TRANSFER_AMOUNT);277 // ... and parachain native token278 expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true;279 });280281 itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => {282 const destination = {283 V2: {284 parents: 1,285 interior: {X2: [286 {287 Parachain: STATEMINT_CHAIN,288 },289 {290 AccountId32: {291 network: 'Any',292 id: alice.addressRaw,293 },294 },295 ]},296 },297 };298299 const relayFee = 400_000_000_000_000n;300 const currencies: [any, bigint][] = [301 [302 {303 ForeignAssetId: 0,304 },305 TRANSFER_AMOUNT,306 ],307 [308 {309 NativeAssetId: 'Parent',310 },311 relayFee,312 ],313 ];314315 const feeItem = 1;316317 await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');318319 // the commission has been paid in parachain native token320 balanceUniqueFinal = await helper.balance.getSubstrate(alice.address);321 console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueFinal));322 expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true;323324 await usingStatemintPlaygrounds(statemintUrl, async (helper) => {325 await helper.wait.newBlocks(3);326327 // The USDT token never paid fees. Its amount not changed from begin value.328 // Also check that xcm transfer has been succeeded329 expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;330 });331 });332333 itSub('Should connect and send Relay token to Unique', async ({helper}) => {334 balanceBobBefore = await helper.balance.getSubstrate(bob.address);335 balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});336337 await usingRelayPlaygrounds(relayUrl, async (helper) => {338 const destination = {339 V2: {340 parents: 0,341 interior: {X1: {342 Parachain: UNIQUE_CHAIN,343 },344 },345 }};346347 const beneficiary = {348 V2: {349 parents: 0,350 interior: {X1: {351 AccountId32: {352 network: 'Any',353 id: bob.addressRaw,354 },355 }},356 },357 };358359 const assets = {360 V2: [361 {362 id: {363 Concrete: {364 parents: 0,365 interior: 'Here',366 },367 },368 fun: {369 Fungible: TRANSFER_AMOUNT_RELAY,370 },371 },372 ],373 };374375 const feeAssetItem = 0;376377 await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');378 });379380 await helper.wait.newBlocks(3);381382 balanceBobAfter = await helper.balance.getSubstrate(bob.address);383 balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});384385 const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;386 const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;387 console.log(388 '[Relay (Westend) -> Unique] transaction fees: %s UNQ',389 helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),390 );391 console.log(392 '[Relay (Westend) -> Unique] transaction fees: %s WND',393 helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS),394 );395 console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique);396 expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true;397 expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ fees should be taken').to.be.true;398 });399400 itSub('Should connect and send Relay token back', async ({helper}) => {401 let relayTokenBalanceBefore: bigint;402 let relayTokenBalanceAfter: bigint;403 await usingRelayPlaygrounds(relayUrl, async (helper) => {404 relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);405 });406407 const destination = {408 V2: {409 parents: 1,410 interior: {411 X1:{412 AccountId32: {413 network: 'Any',414 id: bob.addressRaw,415 },416 },417 },418 },419 };420421 const currencies: any = [422 [423 {424 NativeAssetId: 'Parent',425 },426 TRANSFER_AMOUNT_RELAY,427 ],428 ];429430 const feeItem = 0;431432 await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');433434 balanceBobFinal = await helper.balance.getSubstrate(bob.address);435 console.log('[Unique -> Relay (Westend)] transaction fees: %s UNQ', helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));436437 await usingRelayPlaygrounds(relayUrl, async (helper) => {438 await helper.wait.newBlocks(10);439 relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);440441 const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;442 console.log('[Unique -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));443 expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;444 });445 });446});447448describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => {449 let alice: IKeyringPair;450 let randomAccount: IKeyringPair;451452 let balanceUniqueTokenInit: bigint;453 let balanceUniqueTokenMiddle: bigint;454 let balanceUniqueTokenFinal: bigint;455 let balanceAcalaTokenInit: bigint;456 let balanceAcalaTokenMiddle: bigint;457 let balanceAcalaTokenFinal: bigint;458 let balanceUniqueForeignTokenInit: bigint;459 let balanceUniqueForeignTokenMiddle: bigint;460 let balanceUniqueForeignTokenFinal: bigint;461462 // computed by a test transfer from prod Unique to prod Acala.463 // 2 UNQ sent https://unique.subscan.io/xcm_message/polkadot-bad0b68847e2398af25d482e9ee6f9c1f9ec2a48464 // 1.898970000000000000 UNQ received (you can check Acala's chain state in the corresponding block)465 const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;466 const acalaEps = 8n * 10n ** 16n;467468 let acalaBackwardTransferAmount: bigint;469470 before(async () => {471 await usingPlaygrounds(async (helper, privateKey) => {472 alice = await privateKey('//Alice');473 [randomAccount] = await helper.arrange.createAccounts([0n], alice);474475 // Set the default version to wrap the first message to other chains.476 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);477 });478479 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {480 const destination = {481 V2: {482 parents: 1,483 interior: {484 X1: {485 Parachain: UNIQUE_CHAIN,486 },487 },488 },489 };490491 const metadata = {492 name: 'Unique Network',493 symbol: 'UNQ',494 decimals: 18,495 minimalBalance: 1250000000000000000n,496 };497 const assets = (await (helper.callRpc('api.query.assetRegistry.assetMetadatas.entries'))).map(([_k, v] : [any, any]) =>498 hexToString(v.toJSON()['symbol'])) as string[];499500 if(!assets.includes('UNQ')) {501 await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);502 } else {503 console.log('UNQ token already registered on Acala assetRegistry pallet');504 }505 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);506 balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);507 balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});508 });509510 await usingPlaygrounds(async (helper) => {511 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);512 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);513 });514 });515516 itSub('Should connect and send UNQ to Acala', async ({helper}) => {517518 const destination = {519 V2: {520 parents: 1,521 interior: {522 X1: {523 Parachain: ACALA_CHAIN,524 },525 },526 },527 };528529 const beneficiary = {530 V2: {531 parents: 0,532 interior: {533 X1: {534 AccountId32: {535 network: 'Any',536 id: randomAccount.addressRaw,537 },538 },539 },540 },541 };542543 const assets = {544 V2: [545 {546 id: {547 Concrete: {548 parents: 0,549 interior: 'Here',550 },551 },552 fun: {553 Fungible: TRANSFER_AMOUNT,554 },555 },556 ],557 };558559 const feeAssetItem = 0;560561 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');562 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);563564 const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;565 console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));566 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;567568 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {569 await helper.wait.newBlocks(3);570571 balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});572 balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);573574 const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;575 const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;576 acalaBackwardTransferAmount = unqIncomeTransfer;577578 const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;579580 console.log(581 '[Unique -> Acala] transaction fees on Acala: %s ACA',582 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),583 );584 console.log(585 '[Unique -> Acala] transaction fees on Acala: %s UNQ',586 helper.util.bigIntToDecimals(acaUnqFees),587 );588 console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));589 expect(acaFees == 0n).to.be.true;590591 const bigintAbs = (n: bigint) => (n < 0n) ? -n : n;592593 expect(594 bigintAbs(acaUnqFees - expectedAcalaIncomeFee) < acalaEps,595 'Acala took different income fee, check the Acala foreign asset config',596 ).to.be.true;597 });598 });599600 itSub('Should connect to Acala and send UNQ back', async ({helper}) => {601 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {602 const destination = {603 V2: {604 parents: 1,605 interior: {606 X2: [607 {Parachain: UNIQUE_CHAIN},608 {609 AccountId32: {610 network: 'Any',611 id: randomAccount.addressRaw,612 },613 },614 ],615 },616 },617 };618619 const id = {620 ForeignAsset: 0,621 };622623 await helper.xTokens.transfer(randomAccount, id, acalaBackwardTransferAmount, destination, 'Unlimited');624 balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);625 balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);626627 const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;628 const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;629630 console.log(631 '[Acala -> Unique] transaction fees on Acala: %s ACA',632 helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),633 );634 console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));635636 expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;637 expect(unqOutcomeTransfer == acalaBackwardTransferAmount).to.be.true;638 });639640 await helper.wait.newBlocks(3);641642 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);643 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;644 expect(actuallyDelivered > 0).to.be.true;645646 console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));647648 const unqFees = acalaBackwardTransferAmount - actuallyDelivered;649 console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));650 expect(unqFees == 0n).to.be.true;651 });652653 itSub('Acala can send only up to its balance', async ({helper}) => {654 // set Acala's sovereign account's balance655 const acalaBalance = 10000n * (10n ** UNQ_DECIMALS);656 const acalaSovereignAccount = helper.address.paraSiblingSovereignAccount(ACALA_CHAIN);657 await helper.getSudo().balance.setBalanceSubstrate(alice, acalaSovereignAccount, acalaBalance);658659 const moreThanAcalaHas = acalaBalance * 2n;660661 let targetAccountBalance = 0n;662 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);663664 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(665 targetAccount.addressRaw,666 {667 Concrete: {668 parents: 0,669 interior: 'Here',670 },671 },672 moreThanAcalaHas,673 );674675 let maliciousXcmProgramSent: any;676 const maxWaitBlocks = 3;677678 // Try to trick Unique679 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {680 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);681682 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);683 });684685 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash686 && event.outcome.isFailedToTransactAsset);687688 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);689 expect(targetAccountBalance).to.be.equal(0n);690691 // But Acala still can send the correct amount692 const validTransferAmount = acalaBalance / 2n;693 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(694 targetAccount.addressRaw,695 {696 Concrete: {697 parents: 0,698 interior: 'Here',699 },700 },701 validTransferAmount,702 );703704 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {705 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);706 });707708 await helper.wait.newBlocks(maxWaitBlocks);709710 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);711 expect(targetAccountBalance).to.be.equal(validTransferAmount);712 });713714 itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {715 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);716 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);717718 const uniqueMultilocation = {719 V2: {720 parents: 1,721 interior: {722 X1: {723 Parachain: UNIQUE_CHAIN,724 },725 },726 },727 };728729 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(730 targetAccount.addressRaw,731 {732 Concrete: {733 parents: 1,734 interior: {735 X1: {736 Parachain: UNIQUE_CHAIN,737 },738 },739 },740 },741 testAmount,742 );743744 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(745 targetAccount.addressRaw,746 {747 Concrete: {748 parents: 0,749 interior: 'Here',750 },751 },752 testAmount,753 );754755 let maliciousXcmProgramFullIdSent: any;756 let maliciousXcmProgramHereIdSent: any;757 const maxWaitBlocks = 3;758759 // Try to trick Unique using full UNQ identification760 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {761 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);762763 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);764 });765766 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash767 && event.outcome.isUntrustedReserveLocation);768769 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);770 expect(accountBalance).to.be.equal(0n);771772 // Try to trick Unique using shortened UNQ identification773 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {774 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);775776 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);777 });778779 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash780 && event.outcome.isUntrustedReserveLocation);781782 accountBalance = await helper.balance.getSubstrate(targetAccount.address);783 expect(accountBalance).to.be.equal(0n);784 });785});786787describeXCM('[XCM] Integration test: Exchanging tokens with Polkadex', () => {788 let alice: IKeyringPair;789 let randomAccount: IKeyringPair;790 let unqFees: bigint;791 let balanceUniqueTokenInit: bigint;792 let balanceUniqueTokenMiddle: bigint;793 let balanceUniqueTokenFinal: bigint;794 const maxWaitBlocks = 6;795796 before(async () => {797 await usingPlaygrounds(async (helper, privateKey) => {798 alice = await privateKey('//Alice');799 [randomAccount] = await helper.arrange.createAccounts([0n], alice);800801 // Set the default version to wrap the first message to other chains.802 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);803 });804805 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {806 const isWhitelisted = ((await helper.callRpc('api.query.xcmHelper.whitelistedTokens', []))807 .toJSON() as [])808 .map(nToBigInt).length != 0;809 /*810 Check whether the Unique token has been added811 to the whitelist, since an error will occur812 if it is added again. Needed for debugging813 when this test is run multiple times.814 */815 if(isWhitelisted) {816 console.log('UNQ token is already whitelisted on Polkadex');817 } else {818 await helper.getSudo().xcmHelper.whitelistToken(alice, uniqueAssetId);819 }820821 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);822 });823824 await usingPlaygrounds(async (helper) => {825 await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);826 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);827 });828 });829830 itSub('Should connect and send UNQ to Polkadex', async ({helper}) => {831832 const destination = {833 V2: {834 parents: 1,835 interior: {836 X1: {837 Parachain: POLKADEX_CHAIN,838 },839 },840 },841 };842843 const beneficiary = {844 V2: {845 parents: 0,846 interior: {847 X1: {848 AccountId32: {849 network: 'Any',850 id: randomAccount.addressRaw,851 },852 },853 },854 },855 };856857 const assets = {858 V2: [859 {860 id: {861 Concrete: {862 parents: 0,863 interior: 'Here',864 },865 },866 fun: {867 Fungible: TRANSFER_AMOUNT,868 },869 },870 ],871 };872873 const feeAssetItem = 0;874875 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');876 const messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);877 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);878879 unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;880 console.log('[Unique -> Polkadex] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));881 expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;882883 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {884 /*885 Since only the parachain part of the Polkadex886 infrastructure is launched (without their887 solochain validators), processing incoming888 assets will lead to an error.889 This error indicates that the Polkadex chain890 received a message from the Unique network,891 since the hash is being checked to ensure892 it matches what was sent.893 */894 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == messageSent.messageHash);895 });896 });897898899 itSub('Should connect to Polkadex and send UNQ back', async ({helper}) => {900901 const xcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(902 randomAccount.addressRaw,903 {904 Concrete: {905 parents: 1,906 interior: {907 X1: {Parachain: UNIQUE_CHAIN},908 },909 },910 },911 TRANSFER_AMOUNT,912 );913914 let xcmProgramSent: any;915916917 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {918 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, xcmProgram);919920 xcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);921 });922923 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Success, event => event.messageHash == xcmProgramSent.messageHash);924925 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);926927 expect(balanceUniqueTokenFinal).to.be.equal(balanceUniqueTokenInit - unqFees);928 });929930 itSub('Polkadex can send only up to its balance', async ({helper}) => {931 const polkadexBalance = 10000n * (10n ** UNQ_DECIMALS);932 const polkadexSovereignAccount = helper.address.paraSiblingSovereignAccount(POLKADEX_CHAIN);933 await helper.getSudo().balance.setBalanceSubstrate(alice, polkadexSovereignAccount, polkadexBalance);934 const moreThanPolkadexHas = 2n * polkadexBalance;935936 const targetAccount = helper.arrange.createEmptyAccount();937938 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(939 targetAccount.addressRaw,940 {941 Concrete: {942 parents: 0,943 interior: 'Here',944 },945 },946 moreThanPolkadexHas,947 );948949 let maliciousXcmProgramSent: any;950951952 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {953 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);954955 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);956 });957958 await expectFailedToTransact(helper, maliciousXcmProgramSent);959960 const targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);961 expect(targetAccountBalance).to.be.equal(0n);962 });963964 itSub('Should not accept reserve transfer of UNQ from Polkadex', async ({helper}) => {965 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);966 const targetAccount = helper.arrange.createEmptyAccount();967968 const uniqueMultilocation = {969 V2: {970 parents: 1,971 interior: {972 X1: {973 Parachain: UNIQUE_CHAIN,974 },975 },976 },977 };978979 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(980 targetAccount.addressRaw,981 {982 Concrete: {983 parents: 1,984 interior: {985 X1: {986 Parachain: UNIQUE_CHAIN,987 },988 },989 },990 },991 testAmount,992 );993994 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(995 targetAccount.addressRaw,996 {997 Concrete: {998 parents: 0,999 interior: 'Here',1000 },1001 },1002 testAmount,1003 );10041005 let maliciousXcmProgramFullIdSent: any;1006 let maliciousXcmProgramHereIdSent: any;1007 const maxWaitBlocks = 3;10081009 // Try to trick Unique using full UNQ identification1010 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1011 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);10121013 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1014 });10151016 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramFullIdSent);10171018 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1019 expect(accountBalance).to.be.equal(0n);10201021 // Try to trick Unique using shortened UNQ identification1022 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1023 await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);10241025 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1026 });10271028 await expectUntrustedReserveLocationFail(helper, maliciousXcmProgramHereIdSent);10291030 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1031 expect(accountBalance).to.be.equal(0n);1032 });1033});10341035// These tests are relevant only when1036// the the corresponding foreign assets are not registered1037describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {1038 let alice: IKeyringPair;1039 let alith: IKeyringPair;10401041 const testAmount = 100_000_000_000n;1042 let uniqueParachainJunction;1043 let uniqueAccountJunction;10441045 let uniqueParachainMultilocation: any;1046 let uniqueAccountMultilocation: any;1047 let uniqueCombinedMultilocation: any;1048 let uniqueCombinedMultilocationAcala: any; // TODO remove when Acala goes V210491050 let messageSent: any;10511052 const maxWaitBlocks = 3;10531054 before(async () => {1055 await usingPlaygrounds(async (helper, privateKey) => {1056 alice = await privateKey('//Alice');10571058 uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};1059 uniqueAccountJunction = {1060 AccountId32: {1061 network: 'Any',1062 id: alice.addressRaw,1063 },1064 };10651066 uniqueParachainMultilocation = {1067 V2: {1068 parents: 1,1069 interior: {1070 X1: uniqueParachainJunction,1071 },1072 },1073 };10741075 uniqueAccountMultilocation = {1076 V2: {1077 parents: 0,1078 interior: {1079 X1: uniqueAccountJunction,1080 },1081 },1082 };10831084 uniqueCombinedMultilocation = {1085 V2: {1086 parents: 1,1087 interior: {1088 X2: [uniqueParachainJunction, uniqueAccountJunction],1089 },1090 },1091 };10921093 uniqueCombinedMultilocationAcala = {1094 V2: {1095 parents: 1,1096 interior: {1097 X2: [uniqueParachainJunction, uniqueAccountJunction],1098 },1099 },1100 };11011102 // Set the default version to wrap the first message to other chains.1103 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1104 });11051106 // eslint-disable-next-line require-await1107 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1108 alith = helper.account.alithAccount();1109 });1110 });1111111211131114 itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {1115 await usingAcalaPlaygrounds(acalaUrl, async (helper) => {1116 const id = {1117 Token: 'ACA',1118 };1119 const destination = uniqueCombinedMultilocationAcala;1120 await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');11211122 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1123 });11241125 await expectFailedToTransact(helper, messageSent);1126 });11271128 itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {1129 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1130 const id = 'SelfReserve';1131 const destination = uniqueCombinedMultilocation;1132 await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');11331134 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1135 });11361137 await expectFailedToTransact(helper, messageSent);1138 });11391140 itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {1141 await usingAstarPlaygrounds(astarUrl, async (helper) => {1142 const destinationParachain = uniqueParachainMultilocation;1143 const beneficiary = uniqueAccountMultilocation;1144 const assets = {1145 V2: [{1146 id: {1147 Concrete: {1148 parents: 0,1149 interior: 'Here',1150 },1151 },1152 fun: {1153 Fungible: testAmount,1154 },1155 }],1156 };1157 const feeAssetItem = 0;11581159 await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [1160 destinationParachain,1161 beneficiary,1162 assets,1163 feeAssetItem,1164 ]);11651166 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1167 });11681169 await expectFailedToTransact(helper, messageSent);1170 });11711172 itSub('Unique rejects PDX tokens from Polkadex', async ({helper}) => {11731174 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1175 helper.arrange.createEmptyAccount().addressRaw,1176 {1177 Concrete: {1178 parents: 1,1179 interior: {1180 X1: {1181 Parachain: POLKADEX_CHAIN,1182 },1183 },1184 },1185 },1186 testAmount,1187 );11881189 await usingPolkadexPlaygrounds(polkadexUrl, async (helper) => {1190 await helper.getSudo().xcm.send(alice, uniqueParachainMultilocation, maliciousXcmProgramFullId);1191 messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1192 });11931194 await expectFailedToTransact(helper, messageSent);1195 });1196});11971198describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {1199 // Unique constants1200 let alice: IKeyringPair;1201 let uniqueAssetLocation;12021203 let randomAccountUnique: IKeyringPair;1204 let randomAccountMoonbeam: IKeyringPair;12051206 // Moonbeam constants1207 let assetId: string;12081209 const uniqueAssetMetadata = {1210 name: 'xcUnique',1211 symbol: 'xcUNQ',1212 decimals: 18,1213 isFrozen: false,1214 minimalBalance: 1n,1215 };12161217 let balanceUniqueTokenInit: bigint;1218 let balanceUniqueTokenMiddle: bigint;1219 let balanceUniqueTokenFinal: bigint;1220 let balanceForeignUnqTokenInit: bigint;1221 let balanceForeignUnqTokenMiddle: bigint;1222 let balanceForeignUnqTokenFinal: bigint;1223 let balanceGlmrTokenInit: bigint;1224 let balanceGlmrTokenMiddle: bigint;1225 let balanceGlmrTokenFinal: bigint;12261227 before(async () => {1228 await usingPlaygrounds(async (helper, privateKey) => {1229 alice = await privateKey('//Alice');1230 [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);12311232 balanceForeignUnqTokenInit = 0n;12331234 // Set the default version to wrap the first message to other chains.1235 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1236 });12371238 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1239 const alithAccount = helper.account.alithAccount();1240 const baltatharAccount = helper.account.baltatharAccount();1241 const dorothyAccount = helper.account.dorothyAccount();12421243 randomAccountMoonbeam = helper.account.create();12441245 // >>> Sponsoring Dorothy >>>1246 console.log('Sponsoring Dorothy.......');1247 await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);1248 console.log('Sponsoring Dorothy.......DONE');1249 // <<< Sponsoring Dorothy <<<12501251 uniqueAssetLocation = {1252 XCM: {1253 parents: 1,1254 interior: {X1: {Parachain: UNIQUE_CHAIN}},1255 },1256 };1257 const existentialDeposit = 1n;1258 const isSufficient = true;1259 const unitsPerSecond = 1n;1260 const numAssetsWeightHint = 0;12611262 if((await helper.assetManager.assetTypeId(uniqueAssetLocation)).toJSON()) {1263 console.log('Unique asset is already registered on MoonBeam');1264 } else {1265 const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1266 location: uniqueAssetLocation,1267 metadata: uniqueAssetMetadata,1268 existentialDeposit,1269 isSufficient,1270 unitsPerSecond,1271 numAssetsWeightHint,1272 });12731274 console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);12751276 await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);1277 }12781279 // >>> Acquire Unique AssetId Info on Moonbeam >>>1280 console.log('Acquire Unique AssetId Info on Moonbeam.......');12811282 assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();1283 console.log('UNQ asset ID is %s', assetId);1284 console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1285 // >>> Acquire Unique AssetId Info on Moonbeam >>>12861287 // >>> Sponsoring random Account >>>1288 console.log('Sponsoring random Account.......');1289 await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1290 console.log('Sponsoring random Account.......DONE');1291 // <<< Sponsoring random Account <<<12921293 balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1294 });12951296 await usingPlaygrounds(async (helper) => {1297 await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1298 balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1299 });1300 });13011302 itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1303 const currencyId = {1304 NativeAssetId: 'Here',1305 };1306 const dest = {1307 V2: {1308 parents: 1,1309 interior: {1310 X2: [1311 {Parachain: MOONBEAM_CHAIN},1312 {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1313 ],1314 },1315 },1316 };1317 const amount = TRANSFER_AMOUNT;13181319 await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');13201321 balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1322 expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;13231324 const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1325 console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1326 expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13271328 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1329 await helper.wait.newBlocks(3);1330 balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);13311332 const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1333 console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1334 expect(glmrFees == 0n).to.be.true;13351336 balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;13371338 const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1339 console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1340 expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1341 });1342 });13431344 itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1345 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1346 const asset = {1347 V2: {1348 id: {1349 Concrete: {1350 parents: 1,1351 interior: {1352 X1: {Parachain: UNIQUE_CHAIN},1353 },1354 },1355 },1356 fun: {1357 Fungible: TRANSFER_AMOUNT,1358 },1359 },1360 };1361 const destination = {1362 V2: {1363 parents: 1,1364 interior: {1365 X2: [1366 {Parachain: UNIQUE_CHAIN},1367 {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1368 ],1369 },1370 },1371 };13721373 await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');13741375 balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);13761377 const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1378 console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1379 expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;13801381 const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);13821383 expect(unqRandomAccountAsset).to.be.null;13841385 balanceForeignUnqTokenFinal = 0n;13861387 const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1388 console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1389 expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1390 });13911392 await helper.wait.newBlocks(3);13931394 balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1395 const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1396 expect(actuallyDelivered > 0).to.be.true;13971398 console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));13991400 const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1401 console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1402 expect(unqFees == 0n).to.be.true;1403 });14041405 itSub('Moonbeam can send only up to its balance', async ({helper}) => {1406 // set Moonbeam's sovereign account's balance1407 const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1408 const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1409 await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);14101411 const moreThanMoonbeamHas = moonbeamBalance * 2n;14121413 let targetAccountBalance = 0n;1414 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);14151416 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1417 targetAccount.addressRaw,1418 {1419 Concrete: {1420 parents: 0,1421 interior: 'Here',1422 },1423 },1424 moreThanMoonbeamHas,1425 );14261427 let maliciousXcmProgramSent: any;1428 const maxWaitBlocks = 3;14291430 // Try to trick Unique1431 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1432 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgram]);14331434 // Needed to bypass the call filter.1435 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1436 await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14371438 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1439 });14401441 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1442 && event.outcome.isFailedToTransactAsset);14431444 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1445 expect(targetAccountBalance).to.be.equal(0n);14461447 // But Moonbeam still can send the correct amount1448 const validTransferAmount = moonbeamBalance / 2n;1449 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1450 targetAccount.addressRaw,1451 {1452 Concrete: {1453 parents: 0,1454 interior: 'Here',1455 },1456 },1457 validTransferAmount,1458 );14591460 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1461 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, validXcmProgram]);14621463 // Needed to bypass the call filter.1464 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1465 await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1466 });14671468 await helper.wait.newBlocks(maxWaitBlocks);14691470 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1471 expect(targetAccountBalance).to.be.equal(validTransferAmount);1472 });14731474 itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1475 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1476 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);14771478 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1479 targetAccount.addressRaw,1480 {1481 Concrete: {1482 parents: 0,1483 interior: {1484 X1: {1485 Parachain: UNIQUE_CHAIN,1486 },1487 },1488 },1489 },1490 testAmount,1491 );14921493 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1494 targetAccount.addressRaw,1495 {1496 Concrete: {1497 parents: 0,1498 interior: 'Here',1499 },1500 },1501 testAmount,1502 );15031504 let maliciousXcmProgramFullIdSent: any;1505 let maliciousXcmProgramHereIdSent: any;1506 const maxWaitBlocks = 3;15071508 // Try to trick Unique using full UNQ identification1509 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1510 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramFullId]);15111512 // Needed to bypass the call filter.1513 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1514 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);15151516 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1517 });15181519 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1520 && event.outcome.isUntrustedReserveLocation);15211522 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1523 expect(accountBalance).to.be.equal(0n);15241525 // Try to trick Unique using shortened UNQ identification1526 await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1527 const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueVersionedMultilocation, maliciousXcmProgramHereId]);15281529 // Needed to bypass the call filter.1530 const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1531 await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);15321533 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1534 });15351536 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1537 && event.outcome.isUntrustedReserveLocation);15381539 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1540 expect(accountBalance).to.be.equal(0n);1541 });1542});15431544describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1545 let alice: IKeyringPair;1546 let randomAccount: IKeyringPair;15471548 const UNQ_ASSET_ID_ON_ASTAR = 1;1549 const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;15501551 // Unique -> Astar1552 const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.1553 const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1554 const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1555 const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Astar takes a commision in foreign tokens15561557 // Astar -> Unique1558 const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ1559 const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; // 4.999_999_999_088_000_000n UNQ15601561 let balanceAfterUniqueToAstarXCM: bigint;15621563 before(async () => {1564 await usingPlaygrounds(async (helper, privateKey) => {1565 alice = await privateKey('//Alice');1566 [randomAccount] = await helper.arrange.createAccounts([100n], alice);1567 console.log('randomAccount', randomAccount.address);15681569 // Set the default version to wrap the first message to other chains.1570 await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1571 });15721573 await usingAstarPlaygrounds(astarUrl, async (helper) => {1574 if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {1575 console.log('1. Create foreign asset and metadata');1576 // TODO update metadata with values from production1577 await helper.assets.create(1578 alice,1579 UNQ_ASSET_ID_ON_ASTAR,1580 alice.address,1581 UNQ_MINIMAL_BALANCE_ON_ASTAR,1582 );15831584 await helper.assets.setMetadata(1585 alice,1586 UNQ_ASSET_ID_ON_ASTAR,1587 'Cross chain UNQ',1588 'xcUNQ',1589 Number(UNQ_DECIMALS),1590 );15911592 console.log('2. Register asset location on Astar');1593 const assetLocation = {1594 V2: {1595 parents: 1,1596 interior: {1597 X1: {1598 Parachain: UNIQUE_CHAIN,1599 },1600 },1601 },1602 };16031604 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);16051606 console.log('3. Set UNQ payment for XCM execution on Astar');1607 await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1608 } else {1609 console.log('UNQ is already registered on Astar');1610 }1611 console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1612 await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1613 });1614 });16151616 itSub('Should connect and send UNQ to Astar', async ({helper}) => {1617 const destination = {1618 V2: {1619 parents: 1,1620 interior: {1621 X1: {1622 Parachain: ASTAR_CHAIN,1623 },1624 },1625 },1626 };16271628 const beneficiary = {1629 V2: {1630 parents: 0,1631 interior: {1632 X1: {1633 AccountId32: {1634 network: 'Any',1635 id: randomAccount.addressRaw,1636 },1637 },1638 },1639 },1640 };16411642 const assets = {1643 V2: [1644 {1645 id: {1646 Concrete: {1647 parents: 0,1648 interior: 'Here',1649 },1650 },1651 fun: {1652 Fungible: unqToAstarTransferred,1653 },1654 },1655 ],1656 };16571658 // Initial balance is 100 UNQ1659 const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1660 console.log(`Initial balance is: ${balanceBefore}`);16611662 const feeAssetItem = 0;1663 await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');16641665 // Balance after reserve transfer is less than 901666 balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1667 console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1668 console.log(`Unique's UNQ commission is: ${balanceBefore-balanceAfterUniqueToAstarXCM}`);1669 expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;16701671 await usingAstarPlaygrounds(astarUrl, async (helper) => {1672 await helper.wait.newBlocks(3);1673 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1674 const astarBalance = await helper.balance.getSubstrate(randomAccount.address);16751676 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1677 console.log(`Astar's UNQ commission is: ${unqToAstarTransferred-xcUNQbalance!}`);16781679 expect(xcUNQbalance).to.eq(unqToAstarArrived);1680 // Astar balance does not changed1681 expect(astarBalance).to.eq(astarInitialBalance);1682 });1683 });16841685 itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1686 await usingAstarPlaygrounds(astarUrl, async (helper) => {1687 const destination = {1688 V2: {1689 parents: 1,1690 interior: {1691 X1: {1692 Parachain: UNIQUE_CHAIN,1693 },1694 },1695 },1696 };16971698 const beneficiary = {1699 V2: {1700 parents: 0,1701 interior: {1702 X1: {1703 AccountId32: {1704 network: 'Any',1705 id: randomAccount.addressRaw,1706 },1707 },1708 },1709 },1710 };17111712 const assets = {1713 V2: [1714 {1715 id: {1716 Concrete: {1717 parents: 1,1718 interior: {1719 X1: {1720 Parachain: UNIQUE_CHAIN,1721 },1722 },1723 },1724 },1725 fun: {1726 Fungible: unqFromAstarTransfered,1727 },1728 },1729 ],1730 };17311732 // Initial balance is 1 ASTR1733 const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1734 console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1735 expect(balanceASTRbefore).to.eq(astarInitialBalance);17361737 const feeAssetItem = 0;1738 // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1739 await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);17401741 const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1742 const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1743 console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);17441745 // Assert: xcUNQ balance correctly decreased1746 expect(xcUNQbalance).to.eq(unqOnAstarLeft);1747 // Assert: ASTR balance is 0.996...1748 expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1749 });17501751 await helper.wait.newBlocks(3);1752 const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1753 console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1754 expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1755 });17561757 itSub('Astar can send only up to its balance', async ({helper}) => {1758 // set Astar's sovereign account's balance1759 const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1760 const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1761 await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);17621763 const moreThanAstarHas = astarBalance * 2n;17641765 let targetAccountBalance = 0n;1766 const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);17671768 const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1769 targetAccount.addressRaw,1770 {1771 Concrete: {1772 parents: 0,1773 interior: 'Here',1774 },1775 },1776 moreThanAstarHas,1777 );17781779 let maliciousXcmProgramSent: any;1780 const maxWaitBlocks = 3;17811782 // Try to trick Unique1783 await usingAstarPlaygrounds(astarUrl, async (helper) => {1784 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgram);17851786 maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1787 });17881789 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1790 && event.outcome.isFailedToTransactAsset);17911792 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1793 expect(targetAccountBalance).to.be.equal(0n);17941795 // But Astar still can send the correct amount1796 const validTransferAmount = astarBalance / 2n;1797 const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1798 targetAccount.addressRaw,1799 {1800 Concrete: {1801 parents: 0,1802 interior: 'Here',1803 },1804 },1805 validTransferAmount,1806 );18071808 await usingAstarPlaygrounds(astarUrl, async (helper) => {1809 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, validXcmProgram);1810 });18111812 await helper.wait.newBlocks(maxWaitBlocks);18131814 targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1815 expect(targetAccountBalance).to.be.equal(validTransferAmount);1816 });18171818 itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1819 const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1820 const [targetAccount] = await helper.arrange.createAccounts([0n], alice);18211822 const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1823 targetAccount.addressRaw,1824 {1825 Concrete: {1826 parents: 1,1827 interior: {1828 X1: {1829 Parachain: UNIQUE_CHAIN,1830 },1831 },1832 },1833 },1834 testAmount,1835 );18361837 const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1838 targetAccount.addressRaw,1839 {1840 Concrete: {1841 parents: 0,1842 interior: 'Here',1843 },1844 },1845 testAmount,1846 );18471848 let maliciousXcmProgramFullIdSent: any;1849 let maliciousXcmProgramHereIdSent: any;1850 const maxWaitBlocks = 3;18511852 // Try to trick Unique using full UNQ identification1853 await usingAstarPlaygrounds(astarUrl, async (helper) => {1854 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramFullId);18551856 maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1857 });18581859 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1860 && event.outcome.isUntrustedReserveLocation);18611862 let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1863 expect(accountBalance).to.be.equal(0n);18641865 // Try to trick Unique using shortened UNQ identification1866 await usingAstarPlaygrounds(astarUrl, async (helper) => {1867 await helper.getSudo().xcm.send(alice, uniqueVersionedMultilocation, maliciousXcmProgramHereId);18681869 maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1870 });18711872 await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1873 && event.outcome.isUntrustedReserveLocation);18741875 accountBalance = await helper.balance.getSubstrate(targetAccount.address);1876 expect(accountBalance).to.be.equal(0n);1877 });1878});