git.delta.rocks / unique-network / refs/commits / fa6d62bd864f

difftreelog

fix(unq xcm tests) restore state for the Acala

PraetorP2023-09-25parent: #d91b2e0.patch.diff
in: master

1 file changed

modifiedtests/src/xcm/xcmUnique.test.tsdiffbeforeafterboth
after · tests/src/xcm/xcmUnique.test.ts
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 {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);13131314        // >>> Acquire Unique AssetId Info on Moonbeam >>>1315        console.log('Acquire Unique AssetId Info on Moonbeam.......');13161317        assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();13181319        console.log('UNQ asset ID is %s', assetId);1320        console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1321      }1322      // >>> Acquire Unique AssetId Info on Moonbeam >>>13231324      // >>> Sponsoring random Account >>>1325      console.log('Sponsoring random Account.......');1326      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1327      console.log('Sponsoring random Account.......DONE');1328      // <<< Sponsoring random Account <<<13291330      balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1331    });13321333    await usingPlaygrounds(async (helper) => {1334      await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1335      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1336    });1337  });13381339  itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1340    const currencyId = {1341      NativeAssetId: 'Here',1342    };1343    const dest = {1344      V2: {1345        parents: 1,1346        interior: {1347          X2: [1348            {Parachain: MOONBEAM_CHAIN},1349            {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1350          ],1351        },1352      },1353    };1354    const amount = TRANSFER_AMOUNT;13551356    await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');13571358    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1359    expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;13601361    const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1362    console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1363    expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;13641365    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1366      await helper.wait.newBlocks(3);1367      balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);13681369      const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1370      console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1371      expect(glmrFees == 0n).to.be.true;13721373      balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;13741375      const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1376      console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1377      expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1378    });1379  });13801381  itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1382    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1383      const asset = {1384        V2: {1385          id: {1386            Concrete: {1387              parents: 1,1388              interior: {1389                X1: {Parachain: UNIQUE_CHAIN},1390              },1391            },1392          },1393          fun: {1394            Fungible: TRANSFER_AMOUNT,1395          },1396        },1397      };1398      const destination = {1399        V2: {1400          parents: 1,1401          interior: {1402            X2: [1403              {Parachain: UNIQUE_CHAIN},1404              {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1405            ],1406          },1407        },1408      };14091410      await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');14111412      balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);14131414      const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1415      console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1416      expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;14171418      const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);14191420      expect(unqRandomAccountAsset).to.be.null;14211422      balanceForeignUnqTokenFinal = 0n;14231424      const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1425      console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1426      expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1427    });14281429    await helper.wait.newBlocks(3);14301431    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1432    const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1433    expect(actuallyDelivered > 0).to.be.true;14341435    console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));14361437    const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1438    console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1439    expect(unqFees == 0n).to.be.true;1440  });14411442  itSub('Moonbeam can send only up to its balance', async ({helper}) => {1443    // set Moonbeam's sovereign account's balance1444    const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1445    const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1446    await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);14471448    const moreThanMoonbeamHas = moonbeamBalance * 2n;14491450    let targetAccountBalance = 0n;1451    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);14521453    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1454      targetAccount.addressRaw,1455      {1456        Concrete: {1457          parents: 0,1458          interior: 'Here',1459        },1460      },1461      moreThanMoonbeamHas,1462    );14631464    let maliciousXcmProgramSent: any;1465    const maxWaitBlocks = 3;14661467    // Try to trick Unique1468    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1469      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgram]);14701471      // Needed to bypass the call filter.1472      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1473      await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);14741475      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1476    });14771478    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1479        && event.outcome.isFailedToTransactAsset);14801481    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1482    expect(targetAccountBalance).to.be.equal(0n);14831484    // But Moonbeam still can send the correct amount1485    const validTransferAmount = moonbeamBalance / 2n;1486    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1487      targetAccount.addressRaw,1488      {1489        Concrete: {1490          parents: 0,1491          interior: 'Here',1492        },1493      },1494      validTransferAmount,1495    );14961497    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1498      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, validXcmProgram]);14991500      // Needed to bypass the call filter.1501      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1502      await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1503    });15041505    await helper.wait.newBlocks(maxWaitBlocks);15061507    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1508    expect(targetAccountBalance).to.be.equal(validTransferAmount);1509  });15101511  itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1512    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1513    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15141515    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1516      targetAccount.addressRaw,1517      {1518        Concrete: {1519          parents: 0,1520          interior: {1521            X1: {1522              Parachain: UNIQUE_CHAIN,1523            },1524          },1525        },1526      },1527      testAmount,1528    );15291530    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1531      targetAccount.addressRaw,1532      {1533        Concrete: {1534          parents: 0,1535          interior: 'Here',1536        },1537      },1538      testAmount,1539    );15401541    let maliciousXcmProgramFullIdSent: any;1542    let maliciousXcmProgramHereIdSent: any;1543    const maxWaitBlocks = 3;15441545    // Try to trick Unique using full UNQ identification1546    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1547      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);15481549      // Needed to bypass the call filter.1550      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1551      await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);15521553      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1554    });15551556    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1557        && event.outcome.isUntrustedReserveLocation);15581559    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1560    expect(accountBalance).to.be.equal(0n);15611562    // Try to trick Unique using shortened UNQ identification1563    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1564      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramHereId]);15651566      // Needed to bypass the call filter.1567      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1568      await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);15691570      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1571    });15721573    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1574        && event.outcome.isUntrustedReserveLocation);15751576    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1577    expect(accountBalance).to.be.equal(0n);1578  });1579});15801581describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1582  let alice: IKeyringPair;1583  let randomAccount: IKeyringPair;15841585  const UNQ_ASSET_ID_ON_ASTAR = 1;1586  const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;15871588  // Unique -> Astar1589  const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.1590  const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1591  const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1592  const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Astar takes a commision in foreign tokens15931594  // Astar -> Unique1595  const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ1596  const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; // 4.999_999_999_088_000_000n UNQ15971598  let balanceAfterUniqueToAstarXCM: bigint;15991600  before(async () => {1601    await usingPlaygrounds(async (helper, privateKey) => {1602      alice = await privateKey('//Alice');1603      [randomAccount] = await helper.arrange.createAccounts([100n], alice);1604      console.log('randomAccount', randomAccount.address);16051606      // Set the default version to wrap the first message to other chains.1607      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1608    });16091610    await usingAstarPlaygrounds(astarUrl, async (helper) => {1611      if(!(await helper.callRpc('api.query.assets.asset', [UNQ_ASSET_ID_ON_ASTAR])).toJSON()) {1612        console.log('1. Create foreign asset and metadata');1613        // TODO update metadata with values from production1614        await helper.assets.create(1615          alice,1616          UNQ_ASSET_ID_ON_ASTAR,1617          alice.address,1618          UNQ_MINIMAL_BALANCE_ON_ASTAR,1619        );16201621        await helper.assets.setMetadata(1622          alice,1623          UNQ_ASSET_ID_ON_ASTAR,1624          'Cross chain UNQ',1625          'xcUNQ',1626          Number(UNQ_DECIMALS),1627        );16281629        console.log('2. Register asset location on Astar');1630        const assetLocation = {1631          V2: {1632            parents: 1,1633            interior: {1634              X1: {1635                Parachain: UNIQUE_CHAIN,1636              },1637            },1638          },1639        };16401641        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);16421643        console.log('3. Set UNQ payment for XCM execution on Astar');1644        await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);1645      }1646      console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1647      await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1648    });1649  });16501651  itSub('Should connect and send UNQ to Astar', async ({helper}) => {1652    const destination = {1653      V2: {1654        parents: 1,1655        interior: {1656          X1: {1657            Parachain: ASTAR_CHAIN,1658          },1659        },1660      },1661    };16621663    const beneficiary = {1664      V2: {1665        parents: 0,1666        interior: {1667          X1: {1668            AccountId32: {1669              network: 'Any',1670              id: randomAccount.addressRaw,1671            },1672          },1673        },1674      },1675    };16761677    const assets = {1678      V2: [1679        {1680          id: {1681            Concrete: {1682              parents: 0,1683              interior: 'Here',1684            },1685          },1686          fun: {1687            Fungible: unqToAstarTransferred,1688          },1689        },1690      ],1691    };16921693    // Initial balance is 100 UNQ1694    const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1695    console.log(`Initial balance is: ${balanceBefore}`);16961697    const feeAssetItem = 0;1698    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');16991700    // Balance after reserve transfer is less than 901701    balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1702    console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1703    console.log(`Unique's UNQ commission is: ${balanceBefore - balanceAfterUniqueToAstarXCM}`);1704    expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;17051706    await usingAstarPlaygrounds(astarUrl, async (helper) => {1707      await helper.wait.newBlocks(3);1708      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1709      const astarBalance = await helper.balance.getSubstrate(randomAccount.address);17101711      console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1712      console.log(`Astar's UNQ commission is: ${unqToAstarTransferred - xcUNQbalance!}`);17131714      expect(xcUNQbalance).to.eq(unqToAstarArrived);1715      // Astar balance does not changed1716      expect(astarBalance).to.eq(astarInitialBalance);1717    });1718  });17191720  itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1721    await usingAstarPlaygrounds(astarUrl, async (helper) => {1722      const destination = {1723        V2: {1724          parents: 1,1725          interior: {1726            X1: {1727              Parachain: UNIQUE_CHAIN,1728            },1729          },1730        },1731      };17321733      const beneficiary = {1734        V2: {1735          parents: 0,1736          interior: {1737            X1: {1738              AccountId32: {1739                network: 'Any',1740                id: randomAccount.addressRaw,1741              },1742            },1743          },1744        },1745      };17461747      const assets = {1748        V2: [1749          {1750            id: {1751              Concrete: {1752                parents: 1,1753                interior: {1754                  X1: {1755                    Parachain: UNIQUE_CHAIN,1756                  },1757                },1758              },1759            },1760            fun: {1761              Fungible: unqFromAstarTransfered,1762            },1763          },1764        ],1765      };17661767      // Initial balance is 1 ASTR1768      const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1769      console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1770      expect(balanceASTRbefore).to.eq(astarInitialBalance);17711772      const feeAssetItem = 0;1773      // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1774      await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);17751776      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1777      const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1778      console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);17791780      // Assert: xcUNQ balance correctly decreased1781      expect(xcUNQbalance).to.eq(unqOnAstarLeft);1782      // Assert: ASTR balance is 0.996...1783      expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1784    });17851786    await helper.wait.newBlocks(3);1787    const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1788    console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1789    expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1790  });17911792  itSub('Astar can send only up to its balance', async ({helper}) => {1793    // set Astar's sovereign account's balance1794    const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1795    const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1796    await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);17971798    const moreThanAstarHas = astarBalance * 2n;17991800    let targetAccountBalance = 0n;1801    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);18021803    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1804      targetAccount.addressRaw,1805      {1806        Concrete: {1807          parents: 0,1808          interior: 'Here',1809        },1810      },1811      moreThanAstarHas,1812    );18131814    let maliciousXcmProgramSent: any;1815    const maxWaitBlocks = 3;18161817    // Try to trick Unique1818    await usingAstarPlaygrounds(astarUrl, async (helper) => {1819      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);18201821      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1822    });18231824    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramSent.messageHash1825        && event.outcome.isFailedToTransactAsset);18261827    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1828    expect(targetAccountBalance).to.be.equal(0n);18291830    // But Astar still can send the correct amount1831    const validTransferAmount = astarBalance / 2n;1832    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1833      targetAccount.addressRaw,1834      {1835        Concrete: {1836          parents: 0,1837          interior: 'Here',1838        },1839      },1840      validTransferAmount,1841    );18421843    await usingAstarPlaygrounds(astarUrl, async (helper) => {1844      await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);1845    });18461847    await helper.wait.newBlocks(maxWaitBlocks);18481849    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1850    expect(targetAccountBalance).to.be.equal(validTransferAmount);1851  });18521853  itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1854    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1855    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);18561857    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1858      targetAccount.addressRaw,1859      {1860        Concrete: {1861          parents: 1,1862          interior: {1863            X1: {1864              Parachain: UNIQUE_CHAIN,1865            },1866          },1867        },1868      },1869      testAmount,1870    );18711872    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1873      targetAccount.addressRaw,1874      {1875        Concrete: {1876          parents: 0,1877          interior: 'Here',1878        },1879      },1880      testAmount,1881    );18821883    let maliciousXcmProgramFullIdSent: any;1884    let maliciousXcmProgramHereIdSent: any;1885    const maxWaitBlocks = 3;18861887    // Try to trick Unique using full UNQ identification1888    await usingAstarPlaygrounds(astarUrl, async (helper) => {1889      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);18901891      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1892    });18931894    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramFullIdSent.messageHash1895        && event.outcome.isUntrustedReserveLocation);18961897    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1898    expect(accountBalance).to.be.equal(0n);18991900    // Try to trick Unique using shortened UNQ identification1901    await usingAstarPlaygrounds(astarUrl, async (helper) => {1902      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);19031904      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1905    });19061907    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => event.messageHash == maliciousXcmProgramHereIdSent.messageHash1908        && event.outcome.isUntrustedReserveLocation);19091910    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1911    expect(accountBalance).to.be.equal(0n);1912  });1913});