--- a/tests/src/config.ts +++ b/tests/src/config.ts @@ -24,6 +24,8 @@ karuraUrl: process.env.acalaUrl || 'ws://127.0.0.1:9946', moonbeamUrl: process.env.moonbeamUrl || 'ws://127.0.0.1:9947', moonriverUrl: process.env.moonbeamUrl || 'ws://127.0.0.1:9947', + astarUrl: process.env.astarUrl || 'ws://127.0.0.1:9949', + shidenUrl: process.env.shidenUrl || 'ws://127.0.0.1:9949', westmintUrl: process.env.westmintUrl || 'ws://127.0.0.1:9948', statemineUrl: process.env.statemineUrl || 'ws://127.0.0.1:9948', statemintUrl: process.env.statemintUrl || 'ws://127.0.0.1:9948', --- a/tests/src/util/index.ts +++ b/tests/src/util/index.ts @@ -11,7 +11,7 @@ import config from '../config'; import {ChainHelperBase} from './playgrounds/unique'; import {ILogger} from './playgrounds/types'; -import {DevUniqueHelper, SilentLogger, SilentConsole, DevMoonbeamHelper, DevMoonriverHelper, DevAcalaHelper, DevKaruraHelper, DevRelayHelper, DevWestmintHelper, DevStatemineHelper, DevStatemintHelper} from './playgrounds/unique.dev'; +import {DevUniqueHelper, SilentLogger, SilentConsole, DevMoonbeamHelper, DevMoonriverHelper, DevAcalaHelper, DevKaruraHelper, DevRelayHelper, DevWestmintHelper, DevStatemineHelper, DevStatemintHelper, DevAstarHelper} from './playgrounds/unique.dev'; import {dirname} from 'path'; import {fileURLToPath} from 'url'; @@ -101,6 +101,11 @@ return usingPlaygroundsGeneral(DevMoonriverHelper, url, code); }; +export const usingAstarPlaygrounds = (url: string, code: (helper: DevAstarHelper, privateKey: (seed: string) => Promise) => Promise) => { + return usingPlaygroundsGeneral(DevAstarHelper, url, code); +}; + + export const MINIMUM_DONOR_FUND = 100_000n; export const DONOR_FUNDING = 2_000_000n; --- a/tests/src/util/playgrounds/unique.dev.ts +++ b/tests/src/util/playgrounds/unique.dev.ts @@ -3,7 +3,7 @@ import {stringToU8a} from '@polkadot/util'; import {encodeAddress, mnemonicGenerate} from '@polkadot/util-crypto'; -import {UniqueHelper, MoonbeamHelper, ChainHelperBase, AcalaHelper, RelayHelper, WestmintHelper} from './unique'; +import {UniqueHelper, MoonbeamHelper, ChainHelperBase, AcalaHelper, RelayHelper, WestmintHelper, AstarHelper} from './unique'; import {ApiPromise, Keyring, WsProvider} from '@polkadot/api'; import * as defs from '../../interfaces/definitions'; import {IKeyringPair} from '@polkadot/types/types'; @@ -173,6 +173,17 @@ } } +export class DevAstarHelper extends AstarHelper { + wait: WaitGroup; + + constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { + options.helperBase = options.helperBase ?? DevAstarHelper; + + super(logger, options); + this.wait = new WaitGroup(this); + } +} + export class DevAcalaHelper extends AcalaHelper { wait: WaitGroup; --- a/tests/src/util/playgrounds/unique.ts +++ b/tests/src/util/playgrounds/unique.ts @@ -3243,6 +3243,26 @@ } } +export class AstarHelper extends XcmChainHelper { + balance: SubstrateBalanceGroup; + assets: AssetsGroup; + xcm: XcmGroup; + + constructor(logger?: ILogger, options: {[key: string]: any} = {}) { + super(logger, options.helperBase ?? AstarHelper); + + this.balance = new SubstrateBalanceGroup(this); + this.assets = new AssetsGroup(this); + this.xcm = new XcmGroup(this, 'polkadotXcm'); + } + + getSudo() { + // eslint-disable-next-line @typescript-eslint/naming-convention + const SudoHelperType = SudoHelper(this.helperBase); + return this.clone(SudoHelperType) as T; + } +} + export class AcalaHelper extends XcmChainHelper { balance: SubstrateBalanceGroup; assetRegistry: AcalaAssetRegistryGroup; --- a/tests/src/xcm/xcmUnique.test.ts +++ b/tests/src/xcm/xcmUnique.test.ts @@ -18,12 +18,13 @@ import {blake2AsHex} from '@polkadot/util-crypto'; import config from '../config'; import {XcmV2TraitsError} from '../interfaces'; -import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds} from '../util'; +import {itSub, expect, describeXCM, usingPlaygrounds, usingAcalaPlaygrounds, usingRelayPlaygrounds, usingMoonbeamPlaygrounds, usingStatemintPlaygrounds, usingAstarPlaygrounds} from '../util'; const UNIQUE_CHAIN = 2037; const STATEMINT_CHAIN = 1000; const ACALA_CHAIN = 2000; const MOONBEAM_CHAIN = 2004; +const ASTAR_CHAIN = 2006; const STATEMINT_PALLET_INSTANCE = 50; @@ -31,6 +32,7 @@ const statemintUrl = config.statemintUrl; const acalaUrl = config.acalaUrl; const moonbeamUrl = config.moonbeamUrl; +const astarUrl = config.astarUrl; const RELAY_DECIMALS = 12; const STATEMINT_DECIMALS = 12; @@ -980,3 +982,189 @@ expect(unqFees == 0n).to.be.true; }); }); + +describeXCM('[XCM] Integration test: Exchanging tokens with Astar', () => { + let alice: IKeyringPair; + let randomAccount: IKeyringPair; + + const astarInitialBalance = 1n * (10n ** 18n); + const unqToAstarAmount = 10n * (10n ** 18n); + + before(async () => { + await usingPlaygrounds(async (helper, privateKey) => { + alice = await privateKey('//Alice'); + [randomAccount] = await helper.arrange.createAccounts([100n], alice); + console.log('randomAccount', randomAccount.address); + }); + + await usingAstarPlaygrounds(astarUrl, async (helper) => { + console.log('1. Create foreign asset and metadata'); + await helper.assets.create( + alice, + 1, + alice.address, + 1n, // TODO set correct minimal balance + ); + + await helper.assets.setMetadata( + alice, + 1, + 'Cross chain UNQ', + 'xcUNQ', + 18, + ); + + console.log('2. Register asset location'); + const assetLocation = { + V1: { + parents: 1, + interior: { + X1: { + Parachain: UNIQUE_CHAIN, + }, + }, + }, + }; + + await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.registerAssetLocation', [assetLocation, 1]); + + console.log('3. Set payment for computation'); + // TODO this is Phala's price, what price will be for Unique? + const unitsPerSecond = 228_000_000_000n; + await helper.getSudo().executeExtrinsic(alice, 'api.tx.xcAssetConfig.setAssetUnitsPerSecond', [assetLocation, unitsPerSecond]); + + console.log('4. Transfer 1 ASTAR for recepient'); + await helper.balance.transferToSubstrate(alice, randomAccount.address, astarInitialBalance); + }); + }); + + itSub.only('Should connect and send UNQ to Astar', async ({helper}) => { + const destination = { + V1: { + parents: 1, + interior: { + X1: { + Parachain: ASTAR_CHAIN, + }, + }, + }, + }; + + const beneficiary = { + V1: { + parents: 0, + interior: { + X1: { + AccountId32: { + network: 'Any', + id: randomAccount.addressRaw, + }, + }, + }, + }, + }; + + const assets = { + V1: [ + { + id: { + Concrete: { + parents: 0, + interior: 'Here', + }, + }, + fun: { + Fungible: unqToAstarAmount, + }, + }, + ], + }; + + // Initial balance is 100 UNQ + expect(await helper.balance.getSubstrate(randomAccount.address)).to.eq(100n * (10n ** 18n)); + + const feeAssetItem = 0; + await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); + + // Balance after reserve transfer is less than 90 + expect(await helper.balance.getSubstrate(randomAccount.address)).to.eq(89_941967662676666465n); + + await usingAstarPlaygrounds(astarUrl, async (helper) => { + await helper.wait.newBlocks(3); + const xcUNQbalance = await helper.assets.account(1, randomAccount.address); + const astarBalance = await helper.balance.getSubstrate(randomAccount.address); + + expect(xcUNQbalance).to.eq(9_999_999_999_088_000_000n); + // Astar balance does not changed + expect(astarBalance).to.eq(1_000_000_000_000_000_000n); + }); + }); + + itSub.only('Should connect to Astar and send UNQ back', async ({helper}) => { + await usingAstarPlaygrounds(astarUrl, async (helper) => { + const destination = { + V1: { + parents: 1, + interior: { + X1: { + Parachain: UNIQUE_CHAIN, + }, + }, + }, + }; + + const beneficiary = { + V1: { + parents: 0, + interior: { + X1: { + AccountId32: { + network: 'Any', + id: randomAccount.addressRaw, + }, + }, + }, + }, + }; + + const assets = { + V1: [ + { + id: { + Concrete: { + parents: 1, + interior: { + X1: { + Parachain: UNIQUE_CHAIN, + }, + }, + }, + }, + fun: { + Fungible: 5_000_000_000_000_000_000n, // TODO set another value + }, + }, + ], + }; + + // Initial balance is 1 ASTAR + expect(await helper.balance.getSubstrate(randomAccount.address)).to.eq(1_000_000_000_000_000_000n); + + const feeAssetItem = 0; + await helper.xcm.limitedReserveTransferAssets(randomAccount, destination, beneficiary, assets, feeAssetItem, 'Unlimited'); + + // Balance after reserve transfer is less than 1 ASTAR + const xcUNQbalance = await helper.assets.account(1, randomAccount.address); + const balanceAstar = await helper.balance.getSubstrate(randomAccount.address); + + // xcUNQ balance decreased + expect(xcUNQbalance).to.eq(4_999_999_999_088_000_000n); + // Astar balance is 0.997... + expect(balanceAstar / (10n ** 15n)).to.eq(997n); + }); + + await helper.wait.newBlocks(3); + const balanceUNQ = await helper.balance.getSubstrate(randomAccount.address); + expect(balanceUNQ).to.eq(89_941967662676666465n + 5_000_000_000_000_000_000n); + }); +});