git.delta.rocks / unique-network / refs/commits / 748c609fe7ab

difftreelog

source

tests/src/xcm/xcmUnique.test.ts54.2 KiBsourcehistory
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} from '../util';20import {DevUniqueHelper, Event} from '../util/playgrounds/unique.dev';2122const UNIQUE_CHAIN = 2037;23const STATEMINT_CHAIN = 1000;24const ACALA_CHAIN = 2000;25const MOONBEAM_CHAIN = 2004;26const ASTAR_CHAIN = 2006;2728const STATEMINT_PALLET_INSTANCE = 50;2930const relayUrl = config.relayUrl;31const statemintUrl = config.statemintUrl;32const acalaUrl = config.acalaUrl;33const moonbeamUrl = config.moonbeamUrl;34const astarUrl = config.astarUrl;3536const RELAY_DECIMALS = 12;37const STATEMINT_DECIMALS = 12;38const ACALA_DECIMALS = 12;39const ASTAR_DECIMALS = 18n;40const UNQ_DECIMALS = 18n;4142const TRANSFER_AMOUNT = 2000000000000000000000000n;4344const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4546const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4748const USDT_ASSET_ID = 100;49const USDT_ASSET_METADATA_DECIMALS = 18;50const USDT_ASSET_METADATA_NAME = 'USDT';51const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';52const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;53const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5455const SAFE_XCM_VERSION = 2;5657describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {58  let alice: IKeyringPair;59  let bob: IKeyringPair;6061  let balanceStmnBefore: bigint;62  let balanceStmnAfter: bigint;6364  let balanceUniqueBefore: bigint;65  let balanceUniqueAfter: bigint;66  let balanceUniqueFinal: bigint;6768  let balanceBobBefore: bigint;69  let balanceBobAfter: bigint;70  let balanceBobFinal: bigint;7172  let balanceBobRelayTokenBefore: bigint;73  let balanceBobRelayTokenAfter: bigint;747576  before(async () => {77    await usingPlaygrounds(async (helper, privateKey) => {78      alice = await privateKey('//Alice');79      bob = await privateKey('//Bob'); // sovereign account on Statemint funds donor8081      // Set the default version to wrap the first message to other chains.82      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);83    });8485    await usingRelayPlaygrounds(relayUrl, async (helper) => {86      // Fund accounts on Statemint87      const relayXcmVersion = 2;88      await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT, relayXcmVersion);89      await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT, relayXcmVersion);90    });9192    await usingStatemintPlaygrounds(statemintUrl, async (helper) => {93      const sovereignFundingAmount = 3_500_000_000n;9495      await helper.assets.create(96        alice,97        USDT_ASSET_ID,98        alice.address,99        USDT_ASSET_METADATA_MINIMAL_BALANCE,100      );101      await helper.assets.setMetadata(102        alice,103        USDT_ASSET_ID,104        USDT_ASSET_METADATA_NAME,105        USDT_ASSET_METADATA_DESCRIPTION,106        USDT_ASSET_METADATA_DECIMALS,107      );108      await helper.assets.mint(109        alice,110        USDT_ASSET_ID,111        alice.address,112        USDT_ASSET_AMOUNT,113      );114115      // funding parachain sovereing account on Statemint.116      // The sovereign account should be created before any action117      // (the assets pallet on Statemint check if the sovereign account exists)118      const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);119      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);120    });121122123    await usingPlaygrounds(async (helper) => {124      const location = {125        V2: {126          parents: 1,127          interior: {X3: [128            {129              Parachain: STATEMINT_CHAIN,130            },131            {132              PalletInstance: STATEMINT_PALLET_INSTANCE,133            },134            {135              GeneralIndex: USDT_ASSET_ID,136            },137          ]},138        },139      };140141      const metadata =142      {143        name: USDT_ASSET_ID,144        symbol: USDT_ASSET_METADATA_NAME,145        decimals: USDT_ASSET_METADATA_DECIMALS,146        minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,147      };148      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);149      balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);150    });151152153    // Providing the relay currency to the unique sender account154    // (fee for USDT XCM are paid in relay tokens)155    await usingRelayPlaygrounds(relayUrl, async (helper) => {156      const destination = {157        V1: {158          parents: 0,159          interior: {X1: {160            Parachain: UNIQUE_CHAIN,161          },162          },163        }};164165      const beneficiary = {166        V1: {167          parents: 0,168          interior: {X1: {169            AccountId32: {170              network: 'Any',171              id: alice.addressRaw,172            },173          }},174        },175      };176177      const assets = {178        V1: [179          {180            id: {181              Concrete: {182                parents: 0,183                interior: 'Here',184              },185            },186            fun: {187              Fungible: TRANSFER_AMOUNT_RELAY,188            },189          },190        ],191      };192193      const feeAssetItem = 0;194195      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');196    });197198  });199200  itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {201    await usingStatemintPlaygrounds(statemintUrl, async (helper) => {202      const dest = {203        V1: {204          parents: 1,205          interior: {X1: {206            Parachain: UNIQUE_CHAIN,207          },208          },209        }};210211      const beneficiary = {212        V1: {213          parents: 0,214          interior: {X1: {215            AccountId32: {216              network: 'Any',217              id: alice.addressRaw,218            },219          }},220        },221      };222223      const assets = {224        V1: [225          {226            id: {227              Concrete: {228                parents: 0,229                interior: {230                  X2: [231                    {232                      PalletInstance: STATEMINT_PALLET_INSTANCE,233                    },234                    {235                      GeneralIndex: USDT_ASSET_ID,236                    },237                  ]},238              },239            },240            fun: {241              Fungible: TRANSFER_AMOUNT,242            },243          },244        ],245      };246247      const feeAssetItem = 0;248249      balanceStmnBefore = await helper.balance.getSubstrate(alice.address);250      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');251252      balanceStmnAfter = await helper.balance.getSubstrate(alice.address);253254      // common good parachain take commission in it native token255      console.log(256        '[Statemint -> Unique] transaction fees on Statemint: %s WND',257        helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_DECIMALS),258      );259      expect(balanceStmnBefore > balanceStmnAfter).to.be.true;260261    });262263264    // ensure that asset has been delivered265    await helper.wait.newBlocks(3);266267    // expext collection id will be with id 1268    const free = await helper.ft.getBalance(1, {Substrate: alice.address});269270    balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);271272    console.log(273      '[Statemint -> Unique] transaction fees on Unique: %s USDT',274      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),275    );276    console.log(277      '[Statemint -> Unique] transaction fees on Unique: %s UNQ',278      helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore),279    );280    // commission has not paid in USDT token281    expect(free).to.be.equal(TRANSFER_AMOUNT);282    // ... and parachain native token283    expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true;284  });285286  itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => {287    const destination = {288      V2: {289        parents: 1,290        interior: {X2: [291          {292            Parachain: STATEMINT_CHAIN,293          },294          {295            AccountId32: {296              network: 'Any',297              id: alice.addressRaw,298            },299          },300        ]},301      },302    };303304    const relayFee = 400_000_000_000_000n;305    const currencies: [any, bigint][] = [306      [307        {308          ForeignAssetId: 0,309        },310        TRANSFER_AMOUNT,311      ],312      [313        {314          NativeAssetId: 'Parent',315        },316        relayFee,317      ],318    ];319320    const feeItem = 1;321322    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');323324    // the commission has been paid in parachain native token325    balanceUniqueFinal = await helper.balance.getSubstrate(alice.address);326    console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueFinal));327    expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true;328329    await usingStatemintPlaygrounds(statemintUrl, async (helper) => {330      await helper.wait.newBlocks(3);331332      // The USDT token never paid fees. Its amount not changed from begin value.333      // Also check that xcm transfer has been succeeded334      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;335    });336  });337338  itSub('Should connect and send Relay token to Unique', async ({helper}) => {339    balanceBobBefore = await helper.balance.getSubstrate(bob.address);340    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});341342    await usingRelayPlaygrounds(relayUrl, async (helper) => {343      const destination = {344        V1: {345          parents: 0,346          interior: {X1: {347            Parachain: UNIQUE_CHAIN,348          },349          },350        }};351352      const beneficiary = {353        V1: {354          parents: 0,355          interior: {X1: {356            AccountId32: {357              network: 'Any',358              id: bob.addressRaw,359            },360          }},361        },362      };363364      const assets = {365        V1: [366          {367            id: {368              Concrete: {369                parents: 0,370                interior: 'Here',371              },372            },373            fun: {374              Fungible: TRANSFER_AMOUNT_RELAY,375            },376          },377        ],378      };379380      const feeAssetItem = 0;381382      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');383    });384385    await helper.wait.newBlocks(3);386387    balanceBobAfter = await helper.balance.getSubstrate(bob.address);388    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});389390    const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;391    const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;392    console.log(393      '[Relay (Westend) -> Unique] transaction fees: %s UNQ',394      helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),395    );396    console.log(397      '[Relay (Westend) -> Unique] transaction fees: %s WND',398      helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS),399    );400    console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique);401    expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true;402    expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ fees should be taken').to.be.true;403  });404405  itSub('Should connect and send Relay token back', async ({helper}) => {406    let relayTokenBalanceBefore: bigint;407    let relayTokenBalanceAfter: bigint;408    await usingRelayPlaygrounds(relayUrl, async (helper) => {409      relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);410    });411412    const destination = {413      V2: {414        parents: 1,415        interior: {416          X1:{417            AccountId32: {418              network: 'Any',419              id: bob.addressRaw,420            },421          },422        },423      },424    };425426    const currencies: any = [427      [428        {429          NativeAssetId: 'Parent',430        },431        TRANSFER_AMOUNT_RELAY,432      ],433    ];434435    const feeItem = 0;436437    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');438439    balanceBobFinal = await helper.balance.getSubstrate(bob.address);440    console.log('[Unique -> Relay (Westend)] transaction fees: %s UNQ',  helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));441442    await usingRelayPlaygrounds(relayUrl, async (helper) => {443      await helper.wait.newBlocks(10);444      relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);445446      const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;447      console.log('[Unique -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));448      expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;449    });450  });451});452453describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => {454  let alice: IKeyringPair;455  let randomAccount: IKeyringPair;456457  let balanceUniqueTokenInit: bigint;458  let balanceUniqueTokenMiddle: bigint;459  let balanceUniqueTokenFinal: bigint;460  let balanceAcalaTokenInit: bigint;461  let balanceAcalaTokenMiddle: bigint;462  let balanceAcalaTokenFinal: bigint;463  let balanceUniqueForeignTokenInit: bigint;464  let balanceUniqueForeignTokenMiddle: bigint;465  let balanceUniqueForeignTokenFinal: bigint;466467  // computed by a test transfer from prod Unique to prod Acala.468  // 2 UNQ sent https://unique.subscan.io/xcm_message/polkadot-bad0b68847e2398af25d482e9ee6f9c1f9ec2a48469  // 1.898970000000000000 UNQ received (you can check Acala's chain state in the corresponding block)470  const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;471472  const ACALA_BACKWARD_TRANSFER_AMOUNT = TRANSFER_AMOUNT - expectedAcalaIncomeFee;473474  before(async () => {475    await usingPlaygrounds(async (helper, privateKey) => {476      alice = await privateKey('//Alice');477      [randomAccount] = await helper.arrange.createAccounts([0n], alice);478479      // Set the default version to wrap the first message to other chains.480      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);481    });482483    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {484      const destination = {485        V1: {486          parents: 1,487          interior: {488            X1: {489              Parachain: UNIQUE_CHAIN,490            },491          },492        },493      };494495      const metadata = {496        name: 'Unique Network',497        symbol: 'UNQ',498        decimals: 18,499        minimalBalance: 1250000000000000000n,500      };501502      await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);503      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);504      balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);505      balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});506    });507508    await usingPlaygrounds(async (helper) => {509      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);510      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);511    });512  });513514  itSub('Should connect and send UNQ to Acala', async ({helper}) => {515516    const destination = {517      V2: {518        parents: 1,519        interior: {520          X1: {521            Parachain: ACALA_CHAIN,522          },523        },524      },525    };526527    const beneficiary = {528      V2: {529        parents: 0,530        interior: {531          X1: {532            AccountId32: {533              network: 'Any',534              id: randomAccount.addressRaw,535            },536          },537        },538      },539    };540541    const assets = {542      V2: [543        {544          id: {545            Concrete: {546              parents: 0,547              interior: 'Here',548            },549          },550          fun: {551            Fungible: TRANSFER_AMOUNT,552          },553        },554      ],555    };556557    const feeAssetItem = 0;558559    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');560    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);561562    const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;563    console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));564    expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;565566    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {567      await helper.wait.newBlocks(3);568569      balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});570      balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);571572      const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;573      const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;574      const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;575576      console.log(577        '[Unique -> Acala] transaction fees on Acala: %s ACA',578        helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),579      );580      console.log(581        '[Unique -> Acala] transaction fees on Acala: %s UNQ',582        helper.util.bigIntToDecimals(acaUnqFees),583      );584      console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));585      expect(acaFees == 0n).to.be.true;586      expect(587        acaUnqFees == expectedAcalaIncomeFee,588        'Acala took different income fee, check the Acala foreign asset config',589      ).to.be.true;590    });591  });592593  itSub('Should connect to Acala and send UNQ back', async ({helper}) => {594    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {595      const destination = {596        V1: {597          parents: 1,598          interior: {599            X2: [600              {Parachain: UNIQUE_CHAIN},601              {602                AccountId32: {603                  network: 'Any',604                  id: randomAccount.addressRaw,605                },606              },607            ],608          },609        },610      };611612      const id = {613        ForeignAsset: 0,614      };615616      await helper.xTokens.transfer(randomAccount, id, ACALA_BACKWARD_TRANSFER_AMOUNT, destination, 'Unlimited');617      balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);618      balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);619620      const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;621      const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;622623      console.log(624        '[Acala -> Unique] transaction fees on Acala: %s ACA',625        helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),626      );627      console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));628629      expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;630      expect(unqOutcomeTransfer == ACALA_BACKWARD_TRANSFER_AMOUNT).to.be.true;631    });632633    await helper.wait.newBlocks(3);634635    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);636    const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;637    expect(actuallyDelivered > 0).to.be.true;638639    console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));640641    const unqFees = ACALA_BACKWARD_TRANSFER_AMOUNT - actuallyDelivered;642    console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));643    expect(unqFees == 0n).to.be.true;644  });645646  itSub('Acala can send only up to its balance', async ({helper}) => {647    // set Acala's sovereign account's balance648    const acalaBalance = 10000n * (10n ** UNQ_DECIMALS);649    const acalaSovereignAccount = helper.address.paraSiblingSovereignAccount(ACALA_CHAIN);650    await helper.getSudo().balance.setBalanceSubstrate(alice, acalaSovereignAccount, acalaBalance);651652    const moreThanAcalaHas = acalaBalance * 2n;653654    let targetAccountBalance = 0n;655    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);656657    const uniqueMultilocation = {658      V1: {659        parents: 1,660        interior: {661          X1: {Parachain: UNIQUE_CHAIN},662        },663      },664    };665666    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(667      targetAccount.addressRaw,668      {669        Concrete: {670          parents: 0,671          interior: 'Here',672        },673      },674      moreThanAcalaHas,675    );676677    let maliciousXcmProgramSent: any;678    const maxWaitBlocks = 3;679680    // Try to trick Unique681    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {682      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);683684      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);685    });686687    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {688      return event.messageHash() == maliciousXcmProgramSent.messageHash()689        && event.outcome().isFailedToTransactAsset;690    });691692    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);693    expect(targetAccountBalance).to.be.equal(0n);694695    // But Acala still can send the correct amount696    const validTransferAmount = acalaBalance / 2n;697    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(698      targetAccount.addressRaw,699      {700        Concrete: {701          parents: 0,702          interior: 'Here',703        },704      },705      validTransferAmount,706    );707708    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {709      await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);710    });711712    await helper.wait.newBlocks(maxWaitBlocks);713714    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);715    expect(targetAccountBalance).to.be.equal(validTransferAmount);716  });717718  itSub('Should not accept reserve transfer of UNQ from Acala', async ({helper}) => {719    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);720    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);721722    const uniqueMultilocation = {723      V1: {724        parents: 1,725        interior: {726          X1: {727            Parachain: UNIQUE_CHAIN,728          },729        },730      },731    };732733    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(734      targetAccount.addressRaw,735      {736        Concrete: {737          parents: 1,738          interior: {739            X1: {740              Parachain: UNIQUE_CHAIN,741            },742          },743        },744      },745      testAmount,746    );747748    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(749      targetAccount.addressRaw,750      {751        Concrete: {752          parents: 0,753          interior: 'Here',754        },755      },756      testAmount,757    );758759    let maliciousXcmProgramFullIdSent: any;760    let maliciousXcmProgramHereIdSent: any;761    const maxWaitBlocks = 3;762763    // Try to trick Unique using full UNQ identification764    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {765      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);766767      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);768    });769770    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {771      return event.messageHash() == maliciousXcmProgramFullIdSent.messageHash()772        && event.outcome().isUntrustedReserveLocation;773    });774775    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);776    expect(accountBalance).to.be.equal(0n);777778    // Try to trick Unique using shortened UNQ identification779    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {780      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);781782      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);783    });784785    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {786      return event.messageHash() == maliciousXcmProgramHereIdSent.messageHash()787        && event.outcome().isUntrustedReserveLocation;788    });789790    accountBalance = await helper.balance.getSubstrate(targetAccount.address);791    expect(accountBalance).to.be.equal(0n);792  });793});794795// These tests are relevant only when796// the the corresponding foreign assets are not registered797describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {798  let alice: IKeyringPair;799  let alith: IKeyringPair;800801  const testAmount = 100_000_000_000n;802  let uniqueParachainJunction;803  let uniqueAccountJunction;804805  let uniqueParachainMultilocation: any;806  let uniqueAccountMultilocation: any;807  let uniqueCombinedMultilocation: any;808  let uniqueCombinedMultilocationAcala: any; // TODO remove when Acala goes V2809810  let messageSent: any;811812  const maxWaitBlocks = 3;813814  before(async () => {815    await usingPlaygrounds(async (helper, privateKey) => {816      alice = await privateKey('//Alice');817818      uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};819      uniqueAccountJunction = {820        AccountId32: {821          network: 'Any',822          id: alice.addressRaw,823        },824      };825826      uniqueParachainMultilocation = {827        V2: {828          parents: 1,829          interior: {830            X1: uniqueParachainJunction,831          },832        },833      };834835      uniqueAccountMultilocation = {836        V2: {837          parents: 0,838          interior: {839            X1: uniqueAccountJunction,840          },841        },842      };843844      uniqueCombinedMultilocation = {845        V2: {846          parents: 1,847          interior: {848            X2: [uniqueParachainJunction, uniqueAccountJunction],849          },850        },851      };852853      uniqueCombinedMultilocationAcala = {854        V1: {855          parents: 1,856          interior: {857            X2: [uniqueParachainJunction, uniqueAccountJunction],858          },859        },860      };861862      // Set the default version to wrap the first message to other chains.863      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);864    });865866    // eslint-disable-next-line require-await867    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {868      alith = helper.account.alithAccount();869    });870  });871872  const expectFailedToTransact = async (helper: DevUniqueHelper, messageSent: any) => {873    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {874      return event.messageHash() == messageSent.messageHash()875        && event.outcome().isFailedToTransactAsset;876    });877  };878879  itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {880    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {881      const id = {882        Token: 'ACA',883      };884      const destination = uniqueCombinedMultilocationAcala;885      await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');886887      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);888    });889890    await expectFailedToTransact(helper, messageSent);891  });892893  itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {894    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {895      const id = 'SelfReserve';896      const destination = uniqueCombinedMultilocation;897      await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');898899      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);900    });901902    await expectFailedToTransact(helper, messageSent);903  });904905  itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {906    await usingAstarPlaygrounds(astarUrl, async (helper) => {907      const destinationParachain = uniqueParachainMultilocation;908      const beneficiary = uniqueAccountMultilocation;909      const assets = {910        V2: [{911          id: {912            Concrete: {913              parents: 0,914              interior: 'Here',915            },916          },917          fun: {918            Fungible: testAmount,919          },920        }],921      };922      const feeAssetItem = 0;923924      await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [925        destinationParachain,926        beneficiary,927        assets,928        feeAssetItem,929      ]);930931      messageSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);932    });933934    await expectFailedToTransact(helper, messageSent);935  });936});937938describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {939  // Unique constants940  let alice: IKeyringPair;941  let uniqueAssetLocation;942943  let randomAccountUnique: IKeyringPair;944  let randomAccountMoonbeam: IKeyringPair;945946  // Moonbeam constants947  let assetId: string;948949  const uniqueAssetMetadata = {950    name: 'xcUnique',951    symbol: 'xcUNQ',952    decimals: 18,953    isFrozen: false,954    minimalBalance: 1n,955  };956957  let balanceUniqueTokenInit: bigint;958  let balanceUniqueTokenMiddle: bigint;959  let balanceUniqueTokenFinal: bigint;960  let balanceForeignUnqTokenInit: bigint;961  let balanceForeignUnqTokenMiddle: bigint;962  let balanceForeignUnqTokenFinal: bigint;963  let balanceGlmrTokenInit: bigint;964  let balanceGlmrTokenMiddle: bigint;965  let balanceGlmrTokenFinal: bigint;966967  before(async () => {968    await usingPlaygrounds(async (helper, privateKey) => {969      alice = await privateKey('//Alice');970      [randomAccountUnique] = await helper.arrange.createAccounts([0n], alice);971972      balanceForeignUnqTokenInit = 0n;973974      // Set the default version to wrap the first message to other chains.975      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);976    });977978    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {979      const alithAccount = helper.account.alithAccount();980      const baltatharAccount = helper.account.baltatharAccount();981      const dorothyAccount = helper.account.dorothyAccount();982983      randomAccountMoonbeam = helper.account.create();984985      // >>> Sponsoring Dorothy >>>986      console.log('Sponsoring Dorothy.......');987      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);988      console.log('Sponsoring Dorothy.......DONE');989      // <<< Sponsoring Dorothy <<<990991      uniqueAssetLocation = {992        XCM: {993          parents: 1,994          interior: {X1: {Parachain: UNIQUE_CHAIN}},995        },996      };997      const existentialDeposit = 1n;998      const isSufficient = true;999      const unitsPerSecond = 1n;1000      const numAssetsWeightHint = 0;10011002      const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({1003        location: uniqueAssetLocation,1004        metadata: uniqueAssetMetadata,1005        existentialDeposit,1006        isSufficient,1007        unitsPerSecond,1008        numAssetsWeightHint,1009      });10101011      console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);10121013      await helper.fastDemocracy.executeProposal('register UNQ foreign asset', encodedProposal);10141015      // >>> Acquire Unique AssetId Info on Moonbeam >>>1016      console.log('Acquire Unique AssetId Info on Moonbeam.......');10171018      assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();10191020      console.log('UNQ asset ID is %s', assetId);1021      console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1022      // >>> Acquire Unique AssetId Info on Moonbeam >>>10231024      // >>> Sponsoring random Account >>>1025      console.log('Sponsoring random Account.......');1026      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1027      console.log('Sponsoring random Account.......DONE');1028      // <<< Sponsoring random Account <<<10291030      balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1031    });10321033    await usingPlaygrounds(async (helper) => {1034      await helper.balance.transferToSubstrate(alice, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1035      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1036    });1037  });10381039  itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1040    const currencyId = {1041      NativeAssetId: 'Here',1042    };1043    const dest = {1044      V2: {1045        parents: 1,1046        interior: {1047          X2: [1048            {Parachain: MOONBEAM_CHAIN},1049            {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1050          ],1051        },1052      },1053    };1054    const amount = TRANSFER_AMOUNT;10551056    await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');10571058    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1059    expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;10601061    const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1062    console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1063    expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;10641065    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1066      await helper.wait.newBlocks(3);10671068      balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);10691070      const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1071      console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1072      expect(glmrFees == 0n).to.be.true;10731074      balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;10751076      const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1077      console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1078      expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1079    });1080  });10811082  itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1083    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1084      const asset = {1085        V2: {1086          id: {1087            Concrete: {1088              parents: 1,1089              interior: {1090                X1: {Parachain: UNIQUE_CHAIN},1091              },1092            },1093          },1094          fun: {1095            Fungible: TRANSFER_AMOUNT,1096          },1097        },1098      };1099      const destination = {1100        V2: {1101          parents: 1,1102          interior: {1103            X2: [1104              {Parachain: UNIQUE_CHAIN},1105              {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1106            ],1107          },1108        },1109      };11101111      await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');11121113      balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);11141115      const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1116      console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1117      expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;11181119      const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);11201121      expect(unqRandomAccountAsset).to.be.null;11221123      balanceForeignUnqTokenFinal = 0n;11241125      const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1126      console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1127      expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1128    });11291130    await helper.wait.newBlocks(3);11311132    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1133    const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1134    expect(actuallyDelivered > 0).to.be.true;11351136    console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));11371138    const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1139    console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1140    expect(unqFees == 0n).to.be.true;1141  });11421143  itSub('Moonbeam can send only up to its balance', async ({helper}) => {1144    // set Moonbeam's sovereign account's balance1145    const moonbeamBalance = 10000n * (10n ** UNQ_DECIMALS);1146    const moonbeamSovereignAccount = helper.address.paraSiblingSovereignAccount(MOONBEAM_CHAIN);1147    await helper.getSudo().balance.setBalanceSubstrate(alice, moonbeamSovereignAccount, moonbeamBalance);11481149    const moreThanMoonbeamHas = moonbeamBalance * 2n;11501151    let targetAccountBalance = 0n;1152    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);11531154    const uniqueMultilocation = {1155      V2: {1156        parents: 1,1157        interior: {1158          X1: {Parachain: UNIQUE_CHAIN},1159        },1160      },1161    };11621163    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1164      targetAccount.addressRaw,1165      {1166        Concrete: {1167          parents: 0,1168          interior: 'Here',1169        },1170      },1171      moreThanMoonbeamHas,1172    );11731174    let maliciousXcmProgramSent: any;1175    const maxWaitBlocks = 3;11761177    // Try to trick Unique1178    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1179      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgram]);11801181      // Needed to bypass the call filter.1182      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1183      await helper.fastDemocracy.executeProposal('try to spend more UNQ than Moonbeam has', batchCall);11841185      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1186    });11871188    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {1189      return event.messageHash() == maliciousXcmProgramSent.messageHash()1190        && event.outcome().isFailedToTransactAsset;1191    });11921193    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1194    expect(targetAccountBalance).to.be.equal(0n);11951196    // But Moonbeam still can send the correct amount1197    const validTransferAmount = moonbeamBalance / 2n;1198    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1199      targetAccount.addressRaw,1200      {1201        Concrete: {1202          parents: 0,1203          interior: 'Here',1204        },1205      },1206      validTransferAmount,1207    );12081209    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1210      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, validXcmProgram]);12111212      // Needed to bypass the call filter.1213      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1214      await helper.fastDemocracy.executeProposal('Spend the correct amount of UNQ', batchCall);1215    });12161217    await helper.wait.newBlocks(maxWaitBlocks);12181219    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1220    expect(targetAccountBalance).to.be.equal(validTransferAmount);1221  });12221223  itSub('Should not accept reserve transfer of UNQ from Moonbeam', async ({helper}) => {1224    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1225    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);12261227    const uniqueMultilocation = {1228      V2: {1229        parents: 1,1230        interior: {1231          X1: {1232            Parachain: UNIQUE_CHAIN,1233          },1234        },1235      },1236    };12371238    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1239      targetAccount.addressRaw,1240      {1241        Concrete: {1242          parents: 0,1243          interior: {1244            X1: {1245              Parachain: UNIQUE_CHAIN,1246            },1247          },1248        },1249      },1250      testAmount,1251    );12521253    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1254      targetAccount.addressRaw,1255      {1256        Concrete: {1257          parents: 0,1258          interior: 'Here',1259        },1260      },1261      testAmount,1262    );12631264    let maliciousXcmProgramFullIdSent: any;1265    let maliciousXcmProgramHereIdSent: any;1266    const maxWaitBlocks = 3;12671268    // Try to trick Unique using full UNQ identification1269    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1270      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramFullId]);12711272      // Needed to bypass the call filter.1273      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1274      await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using path asset identification', batchCall);12751276      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1277    });12781279    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {1280      return event.messageHash() == maliciousXcmProgramFullIdSent.messageHash()1281        && event.outcome().isUntrustedReserveLocation;1282    });12831284    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1285    expect(accountBalance).to.be.equal(0n);12861287    // Try to trick Unique using shortened UNQ identification1288    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1289      const xcmSend = helper.constructApiCall('api.tx.polkadotXcm.send', [uniqueMultilocation, maliciousXcmProgramHereId]);12901291      // Needed to bypass the call filter.1292      const batchCall = helper.encodeApiCall('api.tx.utility.batch', [[xcmSend]]);1293      await helper.fastDemocracy.executeProposal('try to act like a reserve location for UNQ using "here" asset identification', batchCall);12941295      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1296    });12971298    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {1299      return event.messageHash() == maliciousXcmProgramHereIdSent.messageHash()1300        && event.outcome().isUntrustedReserveLocation;1301    });13021303    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1304    expect(accountBalance).to.be.equal(0n);1305  });1306});13071308describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1309  let alice: IKeyringPair;1310  let randomAccount: IKeyringPair;13111312  const UNQ_ASSET_ID_ON_ASTAR = 1;1313  const UNQ_MINIMAL_BALANCE_ON_ASTAR = 1n;13141315  // Unique -> Astar1316  const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Astar.1317  const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1318  const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1319  const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Astar takes a commision in foreign tokens13201321  // Astar -> Unique1322  const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ1323  const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; // 4.999_999_999_088_000_000n UNQ13241325  let balanceAfterUniqueToAstarXCM: bigint;13261327  before(async () => {1328    await usingPlaygrounds(async (helper, privateKey) => {1329      alice = await privateKey('//Alice');1330      [randomAccount] = await helper.arrange.createAccounts([100n], alice);1331      console.log('randomAccount', randomAccount.address);13321333      // Set the default version to wrap the first message to other chains.1334      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1335    });13361337    await usingAstarPlaygrounds(astarUrl, async (helper) => {1338      console.log('1. Create foreign asset and metadata');1339      // TODO update metadata with values from production1340      await helper.assets.create(1341        alice,1342        UNQ_ASSET_ID_ON_ASTAR,1343        alice.address,1344        UNQ_MINIMAL_BALANCE_ON_ASTAR,1345      );13461347      await helper.assets.setMetadata(1348        alice,1349        UNQ_ASSET_ID_ON_ASTAR,1350        'Cross chain UNQ',1351        'xcUNQ',1352        Number(UNQ_DECIMALS),1353      );13541355      console.log('2. Register asset location on Astar');1356      const assetLocation = {1357        V2: {1358          parents: 1,1359          interior: {1360            X1: {1361              Parachain: UNIQUE_CHAIN,1362            },1363          },1364        },1365      };13661367      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_ASTAR]);13681369      console.log('3. Set UNQ payment for XCM execution on Astar');1370      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);13711372      console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1373      await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1374    });1375  });13761377  itSub('Should connect and send UNQ to Astar', async ({helper}) => {1378    const destination = {1379      V2: {1380        parents: 1,1381        interior: {1382          X1: {1383            Parachain: ASTAR_CHAIN,1384          },1385        },1386      },1387    };13881389    const beneficiary = {1390      V2: {1391        parents: 0,1392        interior: {1393          X1: {1394            AccountId32: {1395              network: 'Any',1396              id: randomAccount.addressRaw,1397            },1398          },1399        },1400      },1401    };14021403    const assets = {1404      V2: [1405        {1406          id: {1407            Concrete: {1408              parents: 0,1409              interior: 'Here',1410            },1411          },1412          fun: {1413            Fungible: unqToAstarTransferred,1414          },1415        },1416      ],1417    };14181419    // Initial balance is 100 UNQ1420    const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1421    console.log(`Initial balance is: ${balanceBefore}`);14221423    const feeAssetItem = 0;1424    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');14251426    // Balance after reserve transfer is less than 901427    balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1428    console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1429    console.log(`Unique's UNQ commission is: ${balanceBefore - balanceAfterUniqueToAstarXCM}`);1430    expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;14311432    await usingAstarPlaygrounds(astarUrl, async (helper) => {1433      await helper.wait.newBlocks(3);1434      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1435      const astarBalance = await helper.balance.getSubstrate(randomAccount.address);14361437      console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1438      console.log(`Astar's UNQ commission is: ${unqToAstarTransferred - xcUNQbalance!}`);14391440      expect(xcUNQbalance).to.eq(unqToAstarArrived);1441      // Astar balance does not changed1442      expect(astarBalance).to.eq(astarInitialBalance);1443    });1444  });14451446  itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1447    await usingAstarPlaygrounds(astarUrl, async (helper) => {1448      const destination = {1449        V2: {1450          parents: 1,1451          interior: {1452            X1: {1453              Parachain: UNIQUE_CHAIN,1454            },1455          },1456        },1457      };14581459      const beneficiary = {1460        V2: {1461          parents: 0,1462          interior: {1463            X1: {1464              AccountId32: {1465                network: 'Any',1466                id: randomAccount.addressRaw,1467              },1468            },1469          },1470        },1471      };14721473      const assets = {1474        V2: [1475          {1476            id: {1477              Concrete: {1478                parents: 1,1479                interior: {1480                  X1: {1481                    Parachain: UNIQUE_CHAIN,1482                  },1483                },1484              },1485            },1486            fun: {1487              Fungible: unqFromAstarTransfered,1488            },1489          },1490        ],1491      };14921493      // Initial balance is 1 ASTR1494      const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1495      console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1496      expect(balanceASTRbefore).to.eq(astarInitialBalance);14971498      const feeAssetItem = 0;1499      // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1500      await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);15011502      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_ASTAR, randomAccount.address);1503      const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1504      console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);15051506      // Assert: xcUNQ balance correctly decreased1507      expect(xcUNQbalance).to.eq(unqOnAstarLeft);1508      // Assert: ASTR balance is 0.996...1509      expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1510    });15111512    await helper.wait.newBlocks(3);1513    const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1514    console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1515    expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1516  });15171518  itSub('Astar can send only up to its balance', async ({helper}) => {1519    // set Astar's sovereign account's balance1520    const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1521    const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1522    await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);15231524    const moreThanAstarHas = astarBalance * 2n;15251526    let targetAccountBalance = 0n;1527    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);15281529    const uniqueMultilocation = {1530      V2: {1531        parents: 1,1532        interior: {1533          X1: {Parachain: UNIQUE_CHAIN},1534        },1535      },1536    };15371538    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1539      targetAccount.addressRaw,1540      {1541        Concrete: {1542          parents: 0,1543          interior: 'Here',1544        },1545      },1546      moreThanAstarHas,1547    );15481549    let maliciousXcmProgramSent: any;1550    const maxWaitBlocks = 3;15511552    // Try to trick Unique1553    await usingAstarPlaygrounds(astarUrl, async (helper) => {1554      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgram);15551556      maliciousXcmProgramSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1557    });15581559    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {1560      return event.messageHash() == maliciousXcmProgramSent.messageHash()1561        && event.outcome().isFailedToTransactAsset;1562    });15631564    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1565    expect(targetAccountBalance).to.be.equal(0n);15661567    // But Astar still can send the correct amount1568    const validTransferAmount = astarBalance / 2n;1569    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(1570      targetAccount.addressRaw,1571      {1572        Concrete: {1573          parents: 0,1574          interior: 'Here',1575        },1576      },1577      validTransferAmount,1578    );15791580    await usingAstarPlaygrounds(astarUrl, async (helper) => {1581      await helper.getSudo().xcm.send(alice, uniqueMultilocation, validXcmProgram);1582    });15831584    await helper.wait.newBlocks(maxWaitBlocks);15851586    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1587    expect(targetAccountBalance).to.be.equal(validTransferAmount);1588  });15891590  itSub('Should not accept reserve transfer of UNQ from Astar', async ({helper}) => {1591    const testAmount = 10_000n * (10n ** UNQ_DECIMALS);1592    const [targetAccount] = await helper.arrange.createAccounts([0n], alice);15931594    const uniqueMultilocation = {1595      V2: {1596        parents: 1,1597        interior: {1598          X1: {1599            Parachain: UNIQUE_CHAIN,1600          },1601        },1602      },1603    };16041605    const maliciousXcmProgramFullId = helper.arrange.makeXcmProgramReserveAssetDeposited(1606      targetAccount.addressRaw,1607      {1608        Concrete: {1609          parents: 1,1610          interior: {1611            X1: {1612              Parachain: UNIQUE_CHAIN,1613            },1614          },1615        },1616      },1617      testAmount,1618    );16191620    const maliciousXcmProgramHereId = helper.arrange.makeXcmProgramReserveAssetDeposited(1621      targetAccount.addressRaw,1622      {1623        Concrete: {1624          parents: 0,1625          interior: 'Here',1626        },1627      },1628      testAmount,1629    );16301631    let maliciousXcmProgramFullIdSent: any;1632    let maliciousXcmProgramHereIdSent: any;1633    const maxWaitBlocks = 3;16341635    // Try to trick Unique using full UNQ identification1636    await usingAstarPlaygrounds(astarUrl, async (helper) => {1637      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramFullId);16381639      maliciousXcmProgramFullIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1640    });16411642    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {1643      return event.messageHash() == maliciousXcmProgramFullIdSent.messageHash()1644        && event.outcome().isUntrustedReserveLocation;1645    });16461647    let accountBalance = await helper.balance.getSubstrate(targetAccount.address);1648    expect(accountBalance).to.be.equal(0n);16491650    // Try to trick Unique using shortened UNQ identification1651    await usingAstarPlaygrounds(astarUrl, async (helper) => {1652      await helper.getSudo().xcm.send(alice, uniqueMultilocation, maliciousXcmProgramHereId);16531654      maliciousXcmProgramHereIdSent = await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.XcmpMessageSent);1655    });16561657    await helper.wait.expectEvent(maxWaitBlocks, Event.XcmpQueue.Fail, event => {1658      return event.messageHash() == maliciousXcmProgramHereIdSent.messageHash()1659        && event.outcome().isUntrustedReserveLocation;1660    });16611662    accountBalance = await helper.balance.getSubstrate(targetAccount.address);1663    expect(accountBalance).to.be.equal(0n);1664  });1665});