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

difftreelog

source

tests/src/xcm/xcmUnique.test.ts48.1 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 {blake2AsHex} from '@polkadot/util-crypto';19import config from '../config';20import {XcmV2TraitsError} from '../interfaces';21import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds} from '../util';22import {DevUniqueHelper} from '../util/playgrounds/unique.dev';2324const UNIQUE_CHAIN = 2037;25const STATEMINT_CHAIN = 1000;26const ACALA_CHAIN = 2000;27const MOONBEAM_CHAIN = 2004;28const ASTAR_CHAIN = 2006;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;3738const RELAY_DECIMALS = 12;39const STATEMINT_DECIMALS = 12;40const ACALA_DECIMALS = 12;41const ASTAR_DECIMALS = 18n;42const UNQ_DECIMALS = 18n;4344const TRANSFER_AMOUNT = 2000000000000000000000000n;4546const FUNDING_AMOUNT = 3_500_000_0000_000_000n;4748const TRANSFER_AMOUNT_RELAY = 50_000_000_000_000_000n;4950const USDT_ASSET_ID = 100;51const USDT_ASSET_METADATA_DECIMALS = 18;52const USDT_ASSET_METADATA_NAME = 'USDT';53const USDT_ASSET_METADATA_DESCRIPTION = 'USDT';54const USDT_ASSET_METADATA_MINIMAL_BALANCE = 1n;55const USDT_ASSET_AMOUNT = 10_000_000_000_000_000_000_000_000n;5657const SAFE_XCM_VERSION = 2;5859describeXCM('[XCM] Integration test: Exchanging USDT with Statemint', () => {60  let alice: IKeyringPair;61  let bob: IKeyringPair;6263  let balanceStmnBefore: bigint;64  let balanceStmnAfter: bigint;6566  let balanceUniqueBefore: bigint;67  let balanceUniqueAfter: bigint;68  let balanceUniqueFinal: bigint;6970  let balanceBobBefore: bigint;71  let balanceBobAfter: bigint;72  let balanceBobFinal: bigint;7374  let balanceBobRelayTokenBefore: bigint;75  let balanceBobRelayTokenAfter: bigint;767778  before(async () => {79    await usingPlaygrounds(async (helper, privateKey) => {80      alice = await privateKey('//Alice');81      bob = await privateKey('//Bob'); // sovereign account on Statemint funds donor8283      // Set the default version to wrap the first message to other chains.84      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);85    });8687    await usingRelayPlaygrounds(relayUrl, async (helper) => {88      // Fund accounts on Statemint89      const relayXcmVersion = 2;90      await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, alice.addressRaw, FUNDING_AMOUNT, relayXcmVersion);91      await helper.xcm.teleportNativeAsset(alice, STATEMINT_CHAIN, bob.addressRaw, FUNDING_AMOUNT, relayXcmVersion);92    });9394    await usingStatemintPlaygrounds(statemintUrl, async (helper) => {95      const sovereignFundingAmount = 3_500_000_000n;9697      await helper.assets.create(98        alice,99        USDT_ASSET_ID,100        alice.address,101        USDT_ASSET_METADATA_MINIMAL_BALANCE,102      );103      await helper.assets.setMetadata(104        alice,105        USDT_ASSET_ID,106        USDT_ASSET_METADATA_NAME,107        USDT_ASSET_METADATA_DESCRIPTION,108        USDT_ASSET_METADATA_DECIMALS,109      );110      await helper.assets.mint(111        alice,112        USDT_ASSET_ID,113        alice.address,114        USDT_ASSET_AMOUNT,115      );116117      // funding parachain sovereing account on Statemint.118      // The sovereign account should be created before any action119      // (the assets pallet on Statemint check if the sovereign account exists)120      const parachainSovereingAccount = helper.address.paraSiblingSovereignAccount(UNIQUE_CHAIN);121      await helper.balance.transferToSubstrate(bob, parachainSovereingAccount, sovereignFundingAmount);122    });123124125    await usingPlaygrounds(async (helper) => {126      const location = {127        V2: {128          parents: 1,129          interior: {X3: [130            {131              Parachain: STATEMINT_CHAIN,132            },133            {134              PalletInstance: STATEMINT_PALLET_INSTANCE,135            },136            {137              GeneralIndex: USDT_ASSET_ID,138            },139          ]},140        },141      };142143      const metadata =144      {145        name: USDT_ASSET_ID,146        symbol: USDT_ASSET_METADATA_NAME,147        decimals: USDT_ASSET_METADATA_DECIMALS,148        minimalBalance: USDT_ASSET_METADATA_MINIMAL_BALANCE,149      };150      await helper.getSudo().foreignAssets.register(alice, alice.address, location, metadata);151      balanceUniqueBefore = await helper.balance.getSubstrate(alice.address);152    });153154155    // Providing the relay currency to the unique sender account156    // (fee for USDT XCM are paid in relay tokens)157    await usingRelayPlaygrounds(relayUrl, async (helper) => {158      const destination = {159        V1: {160          parents: 0,161          interior: {X1: {162            Parachain: UNIQUE_CHAIN,163          },164          },165        }};166167      const beneficiary = {168        V1: {169          parents: 0,170          interior: {X1: {171            AccountId32: {172              network: 'Any',173              id: alice.addressRaw,174            },175          }},176        },177      };178179      const assets = {180        V1: [181          {182            id: {183              Concrete: {184                parents: 0,185                interior: 'Here',186              },187            },188            fun: {189              Fungible: TRANSFER_AMOUNT_RELAY,190            },191          },192        ],193      };194195      const feeAssetItem = 0;196197      await helper.xcm.limitedReserveTransferAssets(alice, destination, beneficiary, assets, feeAssetItem, 'Unlimited');198    });199200  });201202  itSub('Should connect and send USDT from Statemint to Unique', async ({helper}) => {203    await usingStatemintPlaygrounds(statemintUrl, async (helper) => {204      const dest = {205        V1: {206          parents: 1,207          interior: {X1: {208            Parachain: UNIQUE_CHAIN,209          },210          },211        }};212213      const beneficiary = {214        V1: {215          parents: 0,216          interior: {X1: {217            AccountId32: {218              network: 'Any',219              id: alice.addressRaw,220            },221          }},222        },223      };224225      const assets = {226        V1: [227          {228            id: {229              Concrete: {230                parents: 0,231                interior: {232                  X2: [233                    {234                      PalletInstance: STATEMINT_PALLET_INSTANCE,235                    },236                    {237                      GeneralIndex: USDT_ASSET_ID,238                    },239                  ]},240              },241            },242            fun: {243              Fungible: TRANSFER_AMOUNT,244            },245          },246        ],247      };248249      const feeAssetItem = 0;250251      balanceStmnBefore = await helper.balance.getSubstrate(alice.address);252      await helper.xcm.limitedReserveTransferAssets(alice, dest, beneficiary, assets, feeAssetItem, 'Unlimited');253254      balanceStmnAfter = await helper.balance.getSubstrate(alice.address);255256      // common good parachain take commission in it native token257      console.log(258        '[Statemint -> Unique] transaction fees on Statemint: %s WND',259        helper.util.bigIntToDecimals(balanceStmnBefore - balanceStmnAfter, STATEMINT_DECIMALS),260      );261      expect(balanceStmnBefore > balanceStmnAfter).to.be.true;262263    });264265266    // ensure that asset has been delivered267    await helper.wait.newBlocks(3);268269    // expext collection id will be with id 1270    const free = await helper.ft.getBalance(1, {Substrate: alice.address});271272    balanceUniqueAfter = await helper.balance.getSubstrate(alice.address);273274    console.log(275      '[Statemint -> Unique] transaction fees on Unique: %s USDT',276      helper.util.bigIntToDecimals(TRANSFER_AMOUNT - free, USDT_ASSET_METADATA_DECIMALS),277    );278    console.log(279      '[Statemint -> Unique] transaction fees on Unique: %s UNQ',280      helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueBefore),281    );282    // commission has not paid in USDT token283    expect(free).to.be.equal(TRANSFER_AMOUNT);284    // ... and parachain native token285    expect(balanceUniqueAfter == balanceUniqueBefore).to.be.true;286  });287288  itSub('Should connect and send USDT from Unique to Statemint back', async ({helper}) => {289    const destination = {290      V2: {291        parents: 1,292        interior: {X2: [293          {294            Parachain: STATEMINT_CHAIN,295          },296          {297            AccountId32: {298              network: 'Any',299              id: alice.addressRaw,300            },301          },302        ]},303      },304    };305306    const relayFee = 400_000_000_000_000n;307    const currencies: [any, bigint][] = [308      [309        {310          ForeignAssetId: 0,311        },312        TRANSFER_AMOUNT,313      ],314      [315        {316          NativeAssetId: 'Parent',317        },318        relayFee,319      ],320    ];321322    const feeItem = 1;323324    await helper.xTokens.transferMulticurrencies(alice, currencies, feeItem, destination, 'Unlimited');325326    // the commission has been paid in parachain native token327    balanceUniqueFinal = await helper.balance.getSubstrate(alice.address);328    console.log('[Unique -> Statemint] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(balanceUniqueAfter - balanceUniqueFinal));329    expect(balanceUniqueAfter > balanceUniqueFinal).to.be.true;330331    await usingStatemintPlaygrounds(statemintUrl, async (helper) => {332      await helper.wait.newBlocks(3);333334      // The USDT token never paid fees. Its amount not changed from begin value.335      // Also check that xcm transfer has been succeeded336      expect((await helper.assets.account(USDT_ASSET_ID, alice.address))! == USDT_ASSET_AMOUNT).to.be.true;337    });338  });339340  itSub('Should connect and send Relay token to Unique', async ({helper}) => {341    balanceBobBefore = await helper.balance.getSubstrate(bob.address);342    balanceBobRelayTokenBefore = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});343344    await usingRelayPlaygrounds(relayUrl, async (helper) => {345      const destination = {346        V1: {347          parents: 0,348          interior: {X1: {349            Parachain: UNIQUE_CHAIN,350          },351          },352        }};353354      const beneficiary = {355        V1: {356          parents: 0,357          interior: {X1: {358            AccountId32: {359              network: 'Any',360              id: bob.addressRaw,361            },362          }},363        },364      };365366      const assets = {367        V1: [368          {369            id: {370              Concrete: {371                parents: 0,372                interior: 'Here',373              },374            },375            fun: {376              Fungible: TRANSFER_AMOUNT_RELAY,377            },378          },379        ],380      };381382      const feeAssetItem = 0;383384      await helper.xcm.limitedReserveTransferAssets(bob, destination, beneficiary, assets, feeAssetItem, 'Unlimited');385    });386387    await helper.wait.newBlocks(3);388389    balanceBobAfter = await helper.balance.getSubstrate(bob.address);390    balanceBobRelayTokenAfter = await helper.tokens.accounts(bob.address, {NativeAssetId: 'Parent'});391392    const wndFeeOnUnique = balanceBobRelayTokenAfter - TRANSFER_AMOUNT_RELAY - balanceBobRelayTokenBefore;393    const wndDiffOnUnique = balanceBobRelayTokenAfter - balanceBobRelayTokenBefore;394    console.log(395      '[Relay (Westend) -> Unique] transaction fees: %s UNQ',396      helper.util.bigIntToDecimals(balanceBobAfter - balanceBobBefore),397    );398    console.log(399      '[Relay (Westend) -> Unique] transaction fees: %s WND',400      helper.util.bigIntToDecimals(wndFeeOnUnique, STATEMINT_DECIMALS),401    );402    console.log('[Relay (Westend) -> Unique] actually delivered: %s WND', wndDiffOnUnique);403    expect(wndFeeOnUnique == 0n, 'No incoming WND fees should be taken').to.be.true;404    expect(balanceBobBefore == balanceBobAfter, 'No incoming UNQ fees should be taken').to.be.true;405  });406407  itSub('Should connect and send Relay token back', async ({helper}) => {408    let relayTokenBalanceBefore: bigint;409    let relayTokenBalanceAfter: bigint;410    await usingRelayPlaygrounds(relayUrl, async (helper) => {411      relayTokenBalanceBefore = await helper.balance.getSubstrate(bob.address);412    });413414    const destination = {415      V2: {416        parents: 1,417        interior: {418          X1:{419            AccountId32: {420              network: 'Any',421              id: bob.addressRaw,422            },423          },424        },425      },426    };427428    const currencies: any = [429      [430        {431          NativeAssetId: 'Parent',432        },433        TRANSFER_AMOUNT_RELAY,434      ],435    ];436437    const feeItem = 0;438439    await helper.xTokens.transferMulticurrencies(bob, currencies, feeItem, destination, 'Unlimited');440441    balanceBobFinal = await helper.balance.getSubstrate(bob.address);442    console.log('[Unique -> Relay (Westend)] transaction fees: %s UNQ',  helper.util.bigIntToDecimals(balanceBobAfter - balanceBobFinal));443444    await usingRelayPlaygrounds(relayUrl, async (helper) => {445      await helper.wait.newBlocks(10);446      relayTokenBalanceAfter = await helper.balance.getSubstrate(bob.address);447448      const diff = relayTokenBalanceAfter - relayTokenBalanceBefore;449      console.log('[Unique -> Relay (Westend)] actually delivered: %s WND', helper.util.bigIntToDecimals(diff, RELAY_DECIMALS));450      expect(diff > 0, 'Relay tokens was not delivered back').to.be.true;451    });452  });453});454455describeXCM('[XCM] Integration test: Exchanging tokens with Acala', () => {456  let alice: IKeyringPair;457  let randomAccount: IKeyringPair;458459  let balanceUniqueTokenInit: bigint;460  let balanceUniqueTokenMiddle: bigint;461  let balanceUniqueTokenFinal: bigint;462  let balanceAcalaTokenInit: bigint;463  let balanceAcalaTokenMiddle: bigint;464  let balanceAcalaTokenFinal: bigint;465  let balanceUniqueForeignTokenInit: bigint;466  let balanceUniqueForeignTokenMiddle: bigint;467  let balanceUniqueForeignTokenFinal: bigint;468469  // computed by a test transfer from prod Unique to prod Acala.470  // 2 UNQ sent https://unique.subscan.io/xcm_message/polkadot-bad0b68847e2398af25d482e9ee6f9c1f9ec2a48471  // 1.898970000000000000 UNQ received (you can check Acala's chain state in the corresponding block)472  const expectedAcalaIncomeFee = 2000000000000000000n - 1898970000000000000n;473474  const ACALA_BACKWARD_TRANSFER_AMOUNT = TRANSFER_AMOUNT - expectedAcalaIncomeFee;475476  before(async () => {477    await usingPlaygrounds(async (helper, privateKey) => {478      alice = await privateKey('//Alice');479      [randomAccount] = await helper.arrange.createAccounts([0n], alice);480481      // Set the default version to wrap the first message to other chains.482      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);483    });484485    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {486      const destination = {487        V1: {488          parents: 1,489          interior: {490            X1: {491              Parachain: UNIQUE_CHAIN,492            },493          },494        },495      };496497      const metadata = {498        name: 'Unique Network',499        symbol: 'UNQ',500        decimals: 18,501        minimalBalance: 1250000000000000000n,502      };503504      await helper.getSudo().assetRegistry.registerForeignAsset(alice, destination, metadata);505      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10000000000000n);506      balanceAcalaTokenInit = await helper.balance.getSubstrate(randomAccount.address);507      balanceUniqueForeignTokenInit = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});508    });509510    await usingPlaygrounds(async (helper) => {511      await helper.balance.transferToSubstrate(alice, randomAccount.address, 10n * TRANSFER_AMOUNT);512      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccount.address);513    });514  });515516  itSub('Should connect and send UNQ to Acala', async ({helper}) => {517518    const destination = {519      V2: {520        parents: 1,521        interior: {522          X1: {523            Parachain: ACALA_CHAIN,524          },525        },526      },527    };528529    const beneficiary = {530      V2: {531        parents: 0,532        interior: {533          X1: {534            AccountId32: {535              network: 'Any',536              id: randomAccount.addressRaw,537            },538          },539        },540      },541    };542543    const assets = {544      V2: [545        {546          id: {547            Concrete: {548              parents: 0,549              interior: 'Here',550            },551          },552          fun: {553            Fungible: TRANSFER_AMOUNT,554          },555        },556      ],557    };558559    const feeAssetItem = 0;560561    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');562    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);563564    const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;565    console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));566    expect(unqFees > 0n, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;567568    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {569      await helper.wait.newBlocks(3);570571      balanceUniqueForeignTokenMiddle = await helper.tokens.accounts(randomAccount.address, {ForeignAsset: 0});572      balanceAcalaTokenMiddle = await helper.balance.getSubstrate(randomAccount.address);573574      const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle;575      const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit;576      const acaUnqFees = TRANSFER_AMOUNT - unqIncomeTransfer;577578      console.log(579        '[Unique -> Acala] transaction fees on Acala: %s ACA',580        helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),581      );582      console.log(583        '[Unique -> Acala] transaction fees on Acala: %s UNQ',584        helper.util.bigIntToDecimals(acaUnqFees),585      );586      console.log('[Unique -> Acala] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));587      expect(acaFees == 0n).to.be.true;588      expect(589        acaUnqFees == expectedAcalaIncomeFee,590        'Acala took different income fee, check the Acala foreign asset config',591      ).to.be.true;592    });593  });594595  itSub('Should connect to Acala and send UNQ back', async ({helper}) => {596    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {597      const destination = {598        V1: {599          parents: 1,600          interior: {601            X2: [602              {Parachain: UNIQUE_CHAIN},603              {604                AccountId32: {605                  network: 'Any',606                  id: randomAccount.addressRaw,607                },608              },609            ],610          },611        },612      };613614      const id = {615        ForeignAsset: 0,616      };617618      await helper.xTokens.transfer(randomAccount, id, ACALA_BACKWARD_TRANSFER_AMOUNT, destination, 'Unlimited');619      balanceAcalaTokenFinal = await helper.balance.getSubstrate(randomAccount.address);620      balanceUniqueForeignTokenFinal = await helper.tokens.accounts(randomAccount.address, id);621622      const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal;623      const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal;624625      console.log(626        '[Acala -> Unique] transaction fees on Acala: %s ACA',627        helper.util.bigIntToDecimals(acaFees, ACALA_DECIMALS),628      );629      console.log('[Acala -> Unique] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));630631      expect(acaFees > 0, 'Negative fees ACA, looks like nothing was transferred').to.be.true;632      expect(unqOutcomeTransfer == ACALA_BACKWARD_TRANSFER_AMOUNT).to.be.true;633    });634635    await helper.wait.newBlocks(3);636637    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccount.address);638    const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;639    expect(actuallyDelivered > 0).to.be.true;640641    console.log('[Acala -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));642643    const unqFees = ACALA_BACKWARD_TRANSFER_AMOUNT - actuallyDelivered;644    console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));645    expect(unqFees == 0n).to.be.true;646  });647648  itSub('Acala can send only up to its balance', async ({helper}) => {649    // set Acala's sovereign account's balance650    const acalaBalance = 10000n * (10n ** UNQ_DECIMALS);651    const acalaSovereignAccount = helper.address.paraSiblingSovereignAccount(ACALA_CHAIN);652    await helper.getSudo().balance.setBalanceSubstrate(alice, acalaSovereignAccount, acalaBalance);653654    const moreThanAcalaHas = acalaBalance * 2n;655656    let targetAccountBalance = 0n;657    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);658659    const uniqueMultilocation = {660      V1: {661        parents: 1,662        interior: {663          X1: {Parachain: UNIQUE_CHAIN},664        },665      },666    };667668    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(targetAccount.addressRaw, moreThanAcalaHas);669670    // Try to trick Unique671    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {672      await helper.getSudo().executeExtrinsic(673        alice,674        'api.tx.polkadotXcm.send',675        [676          uniqueMultilocation,677          maliciousXcmProgram,678        ],679        true,680      );681    });682683    const maxWaitBlocks = 3;684685    const xcmpQueueFailEvent = await helper.wait.eventOutcome<XcmV2TraitsError>(686      maxWaitBlocks,687      'xcmpQueue',688      'Fail',689    );690691    expect(692      xcmpQueueFailEvent != null,693      `'xcmpQueue.FailEvent' event is expected`,694    ).to.be.true;695696    expect(697      xcmpQueueFailEvent!.isFailedToTransactAsset,698      `The XCM error should be 'FailedToTransactAsset'`,699    ).to.be.true;700701    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);702    expect(targetAccountBalance).to.be.equal(0n);703704    // But Acala still can send the correct amount705    const validTransferAmount = acalaBalance / 2n;706    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(targetAccount.addressRaw, validTransferAmount);707708    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {709      await helper.getSudo().executeExtrinsic(710        alice,711        'api.tx.polkadotXcm.send',712        [713          uniqueMultilocation,714          validXcmProgram,715        ],716        true,717      );718    });719720    await helper.wait.newBlocks(maxWaitBlocks);721722    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);723    expect(targetAccountBalance).to.be.equal(validTransferAmount);724  });725});726727// These tests are relevant only when728// the the corresponding foreign assets are not registered729describeXCM('[XCM] Integration test: Unique rejects non-native tokens', () => {730  let alice: IKeyringPair;731  let alith: IKeyringPair;732733  const testAmount = 100_000_000_000n;734  let uniqueParachainJunction;735  let uniqueAccountJunction;736737  let uniqueParachainMultilocation: any;738  let uniqueAccountMultilocation: any;739  let uniqueCombinedMultilocation: any;740741  before(async () => {742    await usingPlaygrounds(async (helper, privateKey) => {743      alice = await privateKey('//Alice');744745      uniqueParachainJunction = {Parachain: UNIQUE_CHAIN};746      uniqueAccountJunction = {747        AccountId32: {748          network: 'Any',749          id: alice.addressRaw,750        },751      };752753      uniqueParachainMultilocation = {754        V1: {755          parents: 1,756          interior: {757            X1: uniqueParachainJunction,758          },759        },760      };761762      uniqueAccountMultilocation = {763        V1: {764          parents: 0,765          interior: {766            X1: uniqueAccountJunction,767          },768        },769      };770771      uniqueCombinedMultilocation = {772        V1: {773          parents: 1,774          interior: {775            X2: [uniqueParachainJunction, uniqueAccountJunction],776          },777        },778      };779780      // Set the default version to wrap the first message to other chains.781      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);782    });783784    // eslint-disable-next-line require-await785    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {786      alith = helper.account.alithAccount();787    });788  });789790  const expectFailedToTransact = async (network: string, helper: DevUniqueHelper) => {791    const maxWaitBlocks = 3;792793    const xcmpQueueFailEvent = await helper.wait.eventOutcome<XcmV2TraitsError>(794      maxWaitBlocks,795      'xcmpQueue',796      'Fail',797    );798799    expect(800      xcmpQueueFailEvent != null,801      `[reject ${network} tokens] 'xcmpQueue.FailEvent' event is expected`,802    ).to.be.true;803804    expect(805      xcmpQueueFailEvent!.isFailedToTransactAsset,806      `[reject ${network} tokens] The XCM error should be 'FailedToTransactAsset'`,807    ).to.be.true;808  };809810  itSub('Unique rejects ACA tokens from Acala', async ({helper}) => {811    await usingAcalaPlaygrounds(acalaUrl, async (helper) => {812      const id = {813        Token: 'ACA',814      };815      const destination = uniqueCombinedMultilocation;816      await helper.xTokens.transfer(alice, id, testAmount, destination, 'Unlimited');817    });818819    await expectFailedToTransact('ACA', helper);820  });821822  itSub('Unique rejects GLMR tokens from Moonbeam', async ({helper}) => {823    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {824      const id = 'SelfReserve';825      const destination = uniqueCombinedMultilocation;826      await helper.xTokens.transfer(alith, id, testAmount, destination, 'Unlimited');827    });828829    await expectFailedToTransact('GLMR', helper);830  });831832  itSub('Unique rejects ASTR tokens from Astar', async ({helper}) => {833    await usingAstarPlaygrounds(astarUrl, async (helper) => {834      const destinationParachain = uniqueParachainMultilocation;835      const beneficiary = uniqueAccountMultilocation;836      const assets = {837        V1: [{838          id: {839            Concrete: {840              parents: 0,841              interior: 'Here',842            },843          },844          fun: {845            Fungible: testAmount,846          },847        }],848      };849      const feeAssetItem = 0;850851      await helper.executeExtrinsic(alice, 'api.tx.polkadotXcm.reserveWithdrawAssets', [852        destinationParachain,853        beneficiary,854        assets,855        feeAssetItem,856      ]);857    });858859    await expectFailedToTransact('ASTR', helper);860  });861});862863describeXCM('[XCM] Integration test: Exchanging UNQ with Moonbeam', () => {864  // Unique constants865  let uniqueDonor: IKeyringPair;866  let uniqueAssetLocation;867868  let randomAccountUnique: IKeyringPair;869  let randomAccountMoonbeam: IKeyringPair;870871  // Moonbeam constants872  let assetId: string;873874  const councilVotingThreshold = 2;875  const technicalCommitteeThreshold = 2;876  const votingPeriod = 3;877  const delayPeriod = 0;878879  const uniqueAssetMetadata = {880    name: 'xcUnique',881    symbol: 'xcUNQ',882    decimals: 18,883    isFrozen: false,884    minimalBalance: 1n,885  };886887  let balanceUniqueTokenInit: bigint;888  let balanceUniqueTokenMiddle: bigint;889  let balanceUniqueTokenFinal: bigint;890  let balanceForeignUnqTokenInit: bigint;891  let balanceForeignUnqTokenMiddle: bigint;892  let balanceForeignUnqTokenFinal: bigint;893  let balanceGlmrTokenInit: bigint;894  let balanceGlmrTokenMiddle: bigint;895  let balanceGlmrTokenFinal: bigint;896897  before(async () => {898    await usingPlaygrounds(async (helper, privateKey) => {899      uniqueDonor = await privateKey('//Alice');900      [randomAccountUnique] = await helper.arrange.createAccounts([0n], uniqueDonor);901902      balanceForeignUnqTokenInit = 0n;903904      // Set the default version to wrap the first message to other chains.905      const alice = uniqueDonor;906      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);907    });908909    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {910      const alithAccount = helper.account.alithAccount();911      const baltatharAccount = helper.account.baltatharAccount();912      const dorothyAccount = helper.account.dorothyAccount();913914      randomAccountMoonbeam = helper.account.create();915916      // >>> Sponsoring Dorothy >>>917      console.log('Sponsoring Dorothy.......');918      await helper.balance.transferToEthereum(alithAccount, dorothyAccount.address, 11_000_000_000_000_000_000n);919      console.log('Sponsoring Dorothy.......DONE');920      // <<< Sponsoring Dorothy <<<921922      uniqueAssetLocation = {923        XCM: {924          parents: 1,925          interior: {X1: {Parachain: UNIQUE_CHAIN}},926        },927      };928      const existentialDeposit = 1n;929      const isSufficient = true;930      const unitsPerSecond = 1n;931      const numAssetsWeightHint = 0;932933      const encodedProposal = helper.assetManager.makeRegisterForeignAssetProposal({934        location: uniqueAssetLocation,935        metadata: uniqueAssetMetadata,936        existentialDeposit,937        isSufficient,938        unitsPerSecond,939        numAssetsWeightHint,940      });941      const proposalHash = blake2AsHex(encodedProposal);942943      console.log('Encoded proposal for registerForeignAsset & setAssetUnitsPerSecond is %s', encodedProposal);944      console.log('Encoded length %d', encodedProposal.length);945      console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash);946947      // >>> Note motion preimage >>>948      console.log('Note motion preimage.......');949      await helper.democracy.notePreimage(baltatharAccount, encodedProposal);950      console.log('Note motion preimage.......DONE');951      // <<< Note motion preimage <<<952953      // >>> Propose external motion through council >>>954      console.log('Propose external motion through council.......');955      const externalMotion = helper.democracy.externalProposeMajority({Legacy: proposalHash});956      const encodedMotion = externalMotion?.method.toHex() || '';957      const motionHash = blake2AsHex(encodedMotion);958      console.log('Motion hash is %s', motionHash);959960      await helper.collective.council.propose(baltatharAccount, councilVotingThreshold, externalMotion, externalMotion.encodedLength);961962      const councilProposalIdx = await helper.collective.council.proposalCount() - 1;963      await helper.collective.council.vote(dorothyAccount, motionHash, councilProposalIdx, true);964      await helper.collective.council.vote(baltatharAccount, motionHash, councilProposalIdx, true);965966      await helper.collective.council.close(967        dorothyAccount,968        motionHash,969        councilProposalIdx,970        {971          refTime: 1_000_000_000,972          proofSize: 1_000_000,973        },974        externalMotion.encodedLength,975      );976      console.log('Propose external motion through council.......DONE');977      // <<< Propose external motion through council <<<978979      // >>> Fast track proposal through technical committee >>>980      console.log('Fast track proposal through technical committee.......');981      const fastTrack = helper.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod);982      const encodedFastTrack = fastTrack?.method.toHex() || '';983      const fastTrackHash = blake2AsHex(encodedFastTrack);984      console.log('FastTrack hash is %s', fastTrackHash);985986      await helper.collective.techCommittee.propose(alithAccount, technicalCommitteeThreshold, fastTrack, fastTrack.encodedLength);987988      const techProposalIdx = await helper.collective.techCommittee.proposalCount() - 1;989      await helper.collective.techCommittee.vote(baltatharAccount, fastTrackHash, techProposalIdx, true);990      await helper.collective.techCommittee.vote(alithAccount, fastTrackHash, techProposalIdx, true);991992      await helper.collective.techCommittee.close(993        baltatharAccount,994        fastTrackHash,995        techProposalIdx,996        {997          refTime: 1_000_000_000,998          proofSize: 1_000_000,999        },1000        fastTrack.encodedLength,1001      );1002      console.log('Fast track proposal through technical committee.......DONE');1003      // <<< Fast track proposal through technical committee <<<10041005      // >>> Referendum voting >>>1006      console.log('Referendum voting.......');1007      await helper.democracy.referendumVote(dorothyAccount, 0, {1008        balance: 10_000_000_000_000_000_000n,1009        vote: {aye: true, conviction: 1},1010      });1011      console.log('Referendum voting.......DONE');1012      // <<< Referendum voting <<<10131014      // >>> Acquire Unique AssetId Info on Moonbeam >>>1015      console.log('Acquire Unique AssetId Info on Moonbeam.......');10161017      // Wait for the democracy execute1018      await helper.wait.newBlocks(5);10191020      assetId = (await helper.assetManager.assetTypeId(uniqueAssetLocation)).toString();10211022      console.log('UNQ asset ID is %s', assetId);1023      console.log('Acquire Unique AssetId Info on Moonbeam.......DONE');1024      // >>> Acquire Unique AssetId Info on Moonbeam >>>10251026      // >>> Sponsoring random Account >>>1027      console.log('Sponsoring random Account.......');1028      await helper.balance.transferToEthereum(baltatharAccount, randomAccountMoonbeam.address, 11_000_000_000_000_000_000n);1029      console.log('Sponsoring random Account.......DONE');1030      // <<< Sponsoring random Account <<<10311032      balanceGlmrTokenInit = await helper.balance.getEthereum(randomAccountMoonbeam.address);1033    });10341035    await usingPlaygrounds(async (helper) => {1036      await helper.balance.transferToSubstrate(uniqueDonor, randomAccountUnique.address, 10n * TRANSFER_AMOUNT);1037      balanceUniqueTokenInit = await helper.balance.getSubstrate(randomAccountUnique.address);1038    });1039  });10401041  itSub('Should connect and send UNQ to Moonbeam', async ({helper}) => {1042    const currencyId = {1043      NativeAssetId: 'Here',1044    };1045    const dest = {1046      V2: {1047        parents: 1,1048        interior: {1049          X2: [1050            {Parachain: MOONBEAM_CHAIN},1051            {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}},1052          ],1053        },1054      },1055    };1056    const amount = TRANSFER_AMOUNT;10571058    await helper.xTokens.transfer(randomAccountUnique, currencyId, amount, dest, 'Unlimited');10591060    balanceUniqueTokenMiddle = await helper.balance.getSubstrate(randomAccountUnique.address);1061    expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true;10621063    const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle - TRANSFER_AMOUNT;1064    console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(transactionFees));1065    expect(transactionFees > 0, 'Negative fees UNQ, looks like nothing was transferred').to.be.true;10661067    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1068      await helper.wait.newBlocks(3);10691070      balanceGlmrTokenMiddle = await helper.balance.getEthereum(randomAccountMoonbeam.address);10711072      const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle;1073      console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1074      expect(glmrFees == 0n).to.be.true;10751076      balanceForeignUnqTokenMiddle = (await helper.assets.account(assetId, randomAccountMoonbeam.address))!;10771078      const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit;1079      console.log('[Unique -> Moonbeam] income %s UNQ', helper.util.bigIntToDecimals(unqIncomeTransfer));1080      expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true;1081    });1082  });10831084  itSub('Should connect to Moonbeam and send UNQ back', async ({helper}) => {1085    await usingMoonbeamPlaygrounds(moonbeamUrl, async (helper) => {1086      const asset = {1087        V1: {1088          id: {1089            Concrete: {1090              parents: 1,1091              interior: {1092                X1: {Parachain: UNIQUE_CHAIN},1093              },1094            },1095          },1096          fun: {1097            Fungible: TRANSFER_AMOUNT,1098          },1099        },1100      };1101      const destination = {1102        V1: {1103          parents: 1,1104          interior: {1105            X2: [1106              {Parachain: UNIQUE_CHAIN},1107              {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}},1108            ],1109          },1110        },1111      };11121113      await helper.xTokens.transferMultiasset(randomAccountMoonbeam, asset, destination, 'Unlimited');11141115      balanceGlmrTokenFinal = await helper.balance.getEthereum(randomAccountMoonbeam.address);11161117      const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal;1118      console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', helper.util.bigIntToDecimals(glmrFees));1119      expect(glmrFees > 0, 'Negative fees GLMR, looks like nothing was transferred').to.be.true;11201121      const unqRandomAccountAsset = await helper.assets.account(assetId, randomAccountMoonbeam.address);11221123      expect(unqRandomAccountAsset).to.be.null;11241125      balanceForeignUnqTokenFinal = 0n;11261127      const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal;1128      console.log('[Unique -> Moonbeam] outcome %s UNQ', helper.util.bigIntToDecimals(unqOutcomeTransfer));1129      expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true;1130    });11311132    await helper.wait.newBlocks(3);11331134    balanceUniqueTokenFinal = await helper.balance.getSubstrate(randomAccountUnique.address);1135    const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle;1136    expect(actuallyDelivered > 0).to.be.true;11371138    console.log('[Moonbeam -> Unique] actually delivered %s UNQ', helper.util.bigIntToDecimals(actuallyDelivered));11391140    const unqFees = TRANSFER_AMOUNT - actuallyDelivered;1141    console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', helper.util.bigIntToDecimals(unqFees));1142    expect(unqFees == 0n).to.be.true;1143  });11441145  itSub.skip('Moonbeam can send only up to its balance', async ({helper}) => {1146    throw Error("Not yet implemented");1147  });1148});11491150describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => {1151  let alice: IKeyringPair;1152  let randomAccount: IKeyringPair;11531154  const UNQ_ASSET_ID_ON_SHIDEN = 1;1155  const UNQ_MINIMAL_BALANCE_ON_SHIDEN = 1n;11561157  // Unique -> Astar1158  const astarInitialBalance = 1n * (10n ** ASTAR_DECIMALS); // 1 ASTR, existential deposit required to actually create the account on Shiden.1159  const unitsPerSecond = 228_000_000_000n; // This is Phala's value. What will be ours?1160  const unqToAstarTransferred = 10n * (10n ** UNQ_DECIMALS); // 10 UNQ1161  const unqToAstarArrived = 9_999_999_999_088_000_000n; // 9.999 ... UNQ, Shiden takes a commision in foreign tokens11621163  // Astar -> Unique1164  const unqFromAstarTransfered = 5n * (10n ** UNQ_DECIMALS); // 5 UNQ1165  const unqOnAstarLeft = unqToAstarArrived - unqFromAstarTransfered; // 4.999_999_999_088_000_000n UNQ11661167  let balanceAfterUniqueToAstarXCM: bigint;11681169  before(async () => {1170    await usingPlaygrounds(async (helper, privateKey) => {1171      alice = await privateKey('//Alice');1172      [randomAccount] = await helper.arrange.createAccounts([100n], alice);1173      console.log('randomAccount', randomAccount.address);11741175      // Set the default version to wrap the first message to other chains.1176      await helper.getSudo().xcm.setSafeXcmVersion(alice, SAFE_XCM_VERSION);1177    });11781179    await usingAstarPlaygrounds(astarUrl, async (helper) => {1180      console.log('1. Create foreign asset and metadata');1181      // TODO update metadata with values from production1182      await helper.assets.create(1183        alice,1184        UNQ_ASSET_ID_ON_SHIDEN,1185        alice.address,1186        UNQ_MINIMAL_BALANCE_ON_SHIDEN,1187      );11881189      await helper.assets.setMetadata(1190        alice,1191        UNQ_ASSET_ID_ON_SHIDEN,1192        'Cross chain UNQ',1193        'xcUNQ',1194        Number(UNQ_DECIMALS),1195      );11961197      console.log('2. Register asset location on Astar');1198      const assetLocation = {1199        V1: {1200          parents: 1,1201          interior: {1202            X1: {1203              Parachain: UNIQUE_CHAIN,1204            },1205          },1206        },1207      };12081209      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, UNQ_ASSET_ID_ON_SHIDEN]);12101211      console.log('3. Set UNQ payment for XCM execution on Astar');1212      await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]);12131214      console.log('4. Transfer 1 ASTR to recipient to create the account (needed due to existential balance)');1215      await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance);1216    });1217  });12181219  itSub('Should connect and send UNQ to Astar', async ({helper}) => {1220    const destination = {1221      V2: {1222        parents: 1,1223        interior: {1224          X1: {1225            Parachain: ASTAR_CHAIN,1226          },1227        },1228      },1229    };12301231    const beneficiary = {1232      V2: {1233        parents: 0,1234        interior: {1235          X1: {1236            AccountId32: {1237              network: 'Any',1238              id: randomAccount.addressRaw,1239            },1240          },1241        },1242      },1243    };12441245    const assets = {1246      V2: [1247        {1248          id: {1249            Concrete: {1250              parents: 0,1251              interior: 'Here',1252            },1253          },1254          fun: {1255            Fungible: unqToAstarTransferred,1256          },1257        },1258      ],1259    };12601261    // Initial balance is 100 UNQ1262    const balanceBefore = await helper.balance.getSubstrate(randomAccount.address);1263    console.log(`Initial balance is: ${balanceBefore}`);12641265    const feeAssetItem = 0;1266    await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');12671268    // Balance after reserve transfer is less than 901269    balanceAfterUniqueToAstarXCM = await helper.balance.getSubstrate(randomAccount.address);1270    console.log(`UNQ Balance on Unique after XCM is: ${balanceAfterUniqueToAstarXCM}`);1271    console.log(`Unique's UNQ commission is: ${balanceBefore - balanceAfterUniqueToAstarXCM}`);1272    expect(balanceBefore - balanceAfterUniqueToAstarXCM > 0).to.be.true;12731274    await usingAstarPlaygrounds(astarUrl, async (helper) => {1275      await helper.wait.newBlocks(3);1276      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_SHIDEN, randomAccount.address);1277      const astarBalance = await helper.balance.getSubstrate(randomAccount.address);12781279      console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);1280      console.log(`Astar's UNQ commission is: ${unqToAstarTransferred - xcUNQbalance!}`);12811282      expect(xcUNQbalance).to.eq(unqToAstarArrived);1283      // Astar balance does not changed1284      expect(astarBalance).to.eq(astarInitialBalance);1285    });1286  });12871288  itSub('Should connect to Astar and send UNQ back', async ({helper}) => {1289    await usingAstarPlaygrounds(astarUrl, async (helper) => {1290      const destination = {1291        V1: {1292          parents: 1,1293          interior: {1294            X1: {1295              Parachain: UNIQUE_CHAIN,1296            },1297          },1298        },1299      };13001301      const beneficiary = {1302        V1: {1303          parents: 0,1304          interior: {1305            X1: {1306              AccountId32: {1307                network: 'Any',1308                id: randomAccount.addressRaw,1309              },1310            },1311          },1312        },1313      };13141315      const assets = {1316        V1: [1317          {1318            id: {1319              Concrete: {1320                parents: 1,1321                interior: {1322                  X1: {1323                    Parachain: UNIQUE_CHAIN,1324                  },1325                },1326              },1327            },1328            fun: {1329              Fungible: unqFromAstarTransfered,1330            },1331          },1332        ],1333      };13341335      // Initial balance is 1 ASTR1336      const balanceASTRbefore = await helper.balance.getSubstrate(randomAccount.address);1337      console.log(`ASTR balance is: ${balanceASTRbefore}, it does not changed`);1338      expect(balanceASTRbefore).to.eq(astarInitialBalance);13391340      const feeAssetItem = 0;1341      // this is non-standard polkadotXcm extension for Astar only. It calls InitiateReserveWithdraw1342      await helper.executeExtrinsic(randomAccount, 'api.tx.polkadotXcm.reserveWithdrawAssets', [destination, beneficiary, assets, feeAssetItem]);13431344      const xcUNQbalance = await helper.assets.account(UNQ_ASSET_ID_ON_SHIDEN, randomAccount.address);1345      const balanceAstar = await helper.balance.getSubstrate(randomAccount.address);1346      console.log(`xcUNQ balance on Astar after XCM is: ${xcUNQbalance}`);13471348      // Assert: xcUNQ balance correctly decreased1349      expect(xcUNQbalance).to.eq(unqOnAstarLeft);1350      // Assert: ASTR balance is 0.996...1351      expect(balanceAstar / (10n ** (ASTAR_DECIMALS - 3n))).to.eq(996n);1352    });13531354    await helper.wait.newBlocks(3);1355    const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address);1356    console.log(`UNQ Balance on Unique after XCM is: ${balanceUNQ}`);1357    expect(balanceUNQ).to.eq(balanceAfterUniqueToAstarXCM + unqFromAstarTransfered);1358  });13591360  itSub.skip('Should not accept limitedReserveTransfer of UNQ from ASTAR', async ({helper}) => {1361    await usingAstarPlaygrounds(astarUrl, async (helper) => {1362      const destination = {1363        V1: {1364          parents: 1,1365          interior: {1366            X1: {1367              Parachain: UNIQUE_CHAIN,1368            },1369          },1370        },1371      };13721373      const beneficiary = {1374        V1: {1375          parents: 0,1376          interior: {1377            X1: {1378              AccountId32: {1379                network: 'Any',1380                id: randomAccount.addressRaw,1381              },1382            },1383          },1384        },1385      };13861387      const assets = {1388        V1: [1389          {1390            id: {1391              Concrete: {1392                parents: 1,1393                interior: {1394                  X1: {1395                    Parachain: UNIQUE_CHAIN,1396                  },1397                },1398              },1399            },1400            fun: {1401              Fungible: unqFromAstarTransfered,1402            },1403          },1404        ],1405      };14061407      // Initial balance is 1 ASTAR1408      expect(await helper.balance.getSubstrate(randomAccount.address)).to.eq(astarInitialBalance);14091410      const feeAssetItem = 0;1411      // TODO: expect rejected:1412      await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited');1413    });1414  });14151416  itSub('Astar can send only up to its balance', async ({helper}) => {1417    // set Astar's sovereign account's balance1418    const astarBalance = 10000n * (10n ** UNQ_DECIMALS);1419    const astarSovereignAccount = helper.address.paraSiblingSovereignAccount(ASTAR_CHAIN);1420    await helper.getSudo().balance.setBalanceSubstrate(alice, astarSovereignAccount, astarBalance);14211422    const moreThanShidenHas = astarBalance * 2n;14231424    let targetAccountBalance = 0n;1425    const [targetAccount] = await helper.arrange.createAccounts([targetAccountBalance], alice);14261427    const uniqueMultilocation = {1428      V1: {1429        parents: 1,1430        interior: {1431          X1: {Parachain: UNIQUE_CHAIN},1432        },1433      },1434    };14351436    const maliciousXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(targetAccount.addressRaw, moreThanShidenHas);14371438    // Try to trick Unique1439    await usingAstarPlaygrounds(astarUrl, async (helper) => {1440      await helper.getSudo().executeExtrinsic(1441        alice,1442        'api.tx.polkadotXcm.send',1443        [1444          uniqueMultilocation,1445          maliciousXcmProgram,1446        ],1447        true,1448      );1449    });14501451    const maxWaitBlocks = 3;14521453    const xcmpQueueFailEvent = await helper.wait.eventOutcome<XcmV2TraitsError>(1454      maxWaitBlocks,1455      'xcmpQueue',1456      'Fail',1457    );14581459    expect(1460      xcmpQueueFailEvent != null,1461      `'xcmpQueue.FailEvent' event is expected`,1462    ).to.be.true;14631464    expect(1465      xcmpQueueFailEvent!.isFailedToTransactAsset,1466      `The XCM error should be 'FailedToTransactAsset'`,1467    ).to.be.true;14681469    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1470    expect(targetAccountBalance).to.be.equal(0n);14711472    // But Astar still can send the correct amount1473    const validTransferAmount = astarBalance / 2n;1474    const validXcmProgram = helper.arrange.makeXcmProgramWithdrawDeposit(targetAccount.addressRaw, validTransferAmount);14751476    await usingAstarPlaygrounds(astarUrl, async (helper) => {1477      await helper.getSudo().executeExtrinsic(1478        alice,1479        'api.tx.polkadotXcm.send',1480        [1481          uniqueMultilocation,1482          validXcmProgram,1483        ],1484        true,1485      );1486    });14871488    await helper.wait.newBlocks(maxWaitBlocks);14891490    targetAccountBalance = await helper.balance.getSubstrate(targetAccount.address);1491    expect(targetAccountBalance).to.be.equal(validTransferAmount);1492  });1493});