From 7b07931fc69adc9d36a065d45533d5700e6131b9 Mon Sep 17 00:00:00 2001 From: Daniel Shiposha Date: Fri, 02 Sep 2022 10:50:46 +0000 Subject: [PATCH] test(xcm): unique <-> moonbeam --- --- a/tests/src/xcm/xcmUnique.test.ts +++ b/tests/src/xcm/xcmUnique.test.ts @@ -17,11 +17,13 @@ import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; -import {WsProvider} from '@polkadot/api'; +import {WsProvider, Keyring} from '@polkadot/api'; import {ApiOptions} from '@polkadot/api/types'; import {IKeyringPair} from '@polkadot/types/types'; import usingApi, {submitTransactionAsync} from '../substrate/substrate-api'; import {getGenericResult, generateKeyringPair} from '../util/helpers'; +import {MultiLocation} from '@polkadot/types/interfaces'; +import {blake2AsHex} from '@polkadot/util-crypto'; import waitNewBlocks from '../substrate/wait-new-blocks'; import getBalance from '../substrate/get-balance'; @@ -30,23 +32,39 @@ const UNIQUE_CHAIN = 2037; const ACALA_CHAIN = 2000; +const MOONBEAM_CHAIN = 2004; -const ACALA_PORT = '9946'; +const ACALA_PORT = 9946; +const MOONBEAM_PORT = 9947; const TRANSFER_AMOUNT = 2000000000000000000000000n; +function parachainApiOptions(port: number): ApiOptions { + return { + provider: new WsProvider('ws://127.0.0.1:' + port.toString()), + }; +} + +function acalaOptions(): ApiOptions { + return parachainApiOptions(ACALA_PORT); +} + +function moonbeamOptions(): ApiOptions { + return parachainApiOptions(MOONBEAM_PORT); +} + describe('Integration test: Exchanging UNQ with Acala', () => { let alice: IKeyringPair; let randomAccount: IKeyringPair; - let balanceUniqueTokenBefore: bigint; - let balanceUniqueTokenAfter: bigint; + let balanceUniqueTokenInit: bigint; + let balanceUniqueTokenMiddle: bigint; let balanceUniqueTokenFinal: bigint; - let balanceAcalaTokenBefore: bigint; - let balanceAcalaTokenAfter: bigint; + let balanceAcalaTokenInit: bigint; + let balanceAcalaTokenMiddle: bigint; let balanceAcalaTokenFinal: bigint; - let balanceUniqueForeignTokenAfter: bigint; - let balanceUniqueForeignTokenBefore: bigint; + let balanceUniqueForeignTokenInit: bigint; + let balanceUniqueForeignTokenMiddle: bigint; let balanceUniqueForeignTokenFinal: bigint; before(async () => { @@ -54,11 +72,7 @@ alice = privateKeyWrapper('//Alice'); randomAccount = generateKeyringPair(); }); - - const acalaApiOptions: ApiOptions = { - provider: new WsProvider('ws://127.0.0.1:' + ACALA_PORT), - }; - + // Acala side await usingApi( async (api) => { @@ -91,13 +105,13 @@ const result1 = getGenericResult(events1); expect(result1.success).to.be.true; - [balanceAcalaTokenBefore] = await getBalance(api, [randomAccount.address]); + [balanceAcalaTokenInit] = await getBalance(api, [randomAccount.address]); { const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any; - balanceUniqueForeignTokenBefore = BigInt(free); + balanceUniqueForeignTokenInit = BigInt(free); } }, - acalaApiOptions, + acalaOptions(), ); // Unique side @@ -107,7 +121,7 @@ const result0 = getGenericResult(events0); expect(result0.success).to.be.true; - [balanceUniqueTokenBefore] = await getBalance(api, [randomAccount.address]); + [balanceUniqueTokenInit] = await getBalance(api, [randomAccount.address]); }); }); @@ -165,9 +179,9 @@ const result = getGenericResult(events); expect(result.success).to.be.true; - [balanceUniqueTokenAfter] = await getBalance(api, [randomAccount.address]); + [balanceUniqueTokenMiddle] = await getBalance(api, [randomAccount.address]); - const unqFees = balanceUniqueTokenBefore - balanceUniqueTokenAfter; + const unqFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle + TRANSFER_AMOUNT; console.log('[Unique -> Acala] transaction fees on Unique: %s UNQ', unqFees); expect(unqFees > 0n).to.be.true; }); @@ -175,24 +189,21 @@ // Acala side await usingApi( async (api) => { - // todo do something about instant sealing, where there might not be any new blocks await waitNewBlocks(api, 3); const {free} = (await api.query.tokens.accounts(randomAccount.addressRaw, {ForeignAsset: 0})).toJSON() as any; - balanceUniqueForeignTokenAfter = BigInt(free); + balanceUniqueForeignTokenMiddle = BigInt(free); - [balanceAcalaTokenAfter] = await getBalance(api, [randomAccount.address]); + [balanceAcalaTokenMiddle] = await getBalance(api, [randomAccount.address]); - const acaFees = balanceAcalaTokenBefore - balanceAcalaTokenAfter; - const unqDiffAfterIncomeTransfer = balanceUniqueForeignTokenAfter - balanceUniqueForeignTokenBefore; + const acaFees = balanceAcalaTokenInit - balanceAcalaTokenMiddle; + const unqIncomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenInit; - const unqFees = unqDiffAfterIncomeTransfer - TRANSFER_AMOUNT; - console.log('[Unique -> Acala] transaction fees on Acala: %s ACA', acaFees); - console.log('[Unique -> Acala] transaction fees on Acala: %s UNQ', unqFees); + console.log('[Unique -> Acala] income %s UNQ', unqIncomeTransfer); expect(acaFees == 0n).to.be.true; - expect(unqFees == 0n).to.be.true; + expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true; }, - {provider: new WsProvider('ws://127.0.0.1:' + ACALA_PORT)}, + acalaOptions(), ); }); @@ -221,11 +232,10 @@ const id = { ForeignAsset: 0, }; - - const amount = TRANSFER_AMOUNT; + const destWeight = 50000000; - const tx = api.tx.xTokens.transfer(id, amount, destination, destWeight); + const tx = api.tx.xTokens.transfer(id, TRANSFER_AMOUNT, destination, destWeight); const events = await submitTransactionAsync(randomAccount, tx); const result = getGenericResult(events); expect(result.success).to.be.true; @@ -236,28 +246,27 @@ balanceUniqueForeignTokenFinal = BigInt(free); } - const acaFees = balanceAcalaTokenAfter - balanceAcalaTokenFinal; - const unqDiffAfterOutcomeTransfer = balanceUniqueForeignTokenAfter - TRANSFER_AMOUNT; - - const unqFees = unqDiffAfterOutcomeTransfer - balanceUniqueForeignTokenFinal; + const acaFees = balanceAcalaTokenMiddle - balanceAcalaTokenFinal; + const unqOutcomeTransfer = balanceUniqueForeignTokenMiddle - balanceUniqueForeignTokenFinal; console.log('[Acala -> Unique] transaction fees on Acala: %s ACA', acaFees); - console.log('[Acala -> Unique] transaction fees on Acala: %s UNQ', unqFees); + console.log('[Acala -> Unique] outcome %s UNQ', unqOutcomeTransfer); expect(acaFees > 0).to.be.true; - expect(unqFees == 0n).to.be.true; + expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true; }, - {provider: new WsProvider('ws://127.0.0.1:' + ACALA_PORT)}, + acalaOptions(), ); // Unique side await usingApi(async (api) => { - // todo do something about instant sealing, where there might not be any new blocks await waitNewBlocks(api, 3); [balanceUniqueTokenFinal] = await getBalance(api, [randomAccount.address]); - const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenAfter; + const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle; expect(actuallyDelivered > 0).to.be.true; + + console.log('[Acala -> Unique] actually delivered %s UNQ', actuallyDelivered); const unqFees = TRANSFER_AMOUNT - actuallyDelivered; console.log('[Acala -> Unique] transaction fees on Unique: %s UNQ', unqFees); @@ -265,3 +274,365 @@ }); }); }); + +describe('Integration test: Exchanging UNQ with Moonbeam', () => { + + // Unique constants + let uniqueAlice: IKeyringPair; + let uniqueAssetLocation; + + let randomAccountUnique: IKeyringPair; + let randomAccountMoonbeam: IKeyringPair; + + // Moonbeam constants + let assetId: string; + + const moonbeamKeyring = new Keyring({type: 'ethereum'}); + const alithPrivateKey = '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133'; + const baltatharPrivateKey = '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b'; + const dorothyPrivateKey = '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68'; + + const alithAccount = moonbeamKeyring.addFromUri(alithPrivateKey, undefined, 'ethereum'); + const baltatharAccount = moonbeamKeyring.addFromUri(baltatharPrivateKey, undefined, 'ethereum'); + const dorothyAccount = moonbeamKeyring.addFromUri(dorothyPrivateKey, undefined, 'ethereum'); + + const councilVotingThreshold = 2; + const technicalCommitteeThreshold = 2; + const votingPeriod = 3; + const delayPeriod = 0; + + const uniqueAssetMetadata = { + name: 'xcUnique', + symbol: 'xcUNQ', + decimals: 18, + isFrozen: false, + minimalBalance: 1, + }; + + let balanceUniqueTokenInit: bigint; + let balanceUniqueTokenMiddle: bigint; + let balanceUniqueTokenFinal: bigint; + let balanceForeignUnqTokenInit: bigint; + let balanceForeignUnqTokenMiddle: bigint; + let balanceForeignUnqTokenFinal: bigint; + let balanceGlmrTokenInit: bigint; + let balanceGlmrTokenMiddle: bigint; + let balanceGlmrTokenFinal: bigint; + + before(async () => { + await usingApi(async (api, privateKeyWrapper) => { + uniqueAlice = privateKeyWrapper('//Alice'); + randomAccountUnique = generateKeyringPair(); + randomAccountMoonbeam = generateKeyringPair('ethereum'); + + balanceForeignUnqTokenInit = 0n; + }); + + await usingApi( + async (api) => { + + // >>> Sponsoring Dorothy >>> + console.log('Sponsoring Dorothy.......'); + const tx0 = api.tx.balances.transfer(dorothyAccount.address, 11_000_000_000_000_000_000n); + const events0 = await submitTransactionAsync(alithAccount, tx0); + const result0 = getGenericResult(events0); + expect(result0.success).to.be.true; + console.log('Sponsoring Dorothy.......DONE'); + // <<< Sponsoring Dorothy <<< + + const sourceLocation: MultiLocation = api.createType( + 'MultiLocation', + { + parents: 1, + interior: {X1: {Parachain: UNIQUE_CHAIN}}, + }, + ); + + uniqueAssetLocation = {XCM: sourceLocation}; + const existentialDeposit = 1; + const isSufficient = true; + const unitsPerSecond = '1'; + const numAssetsWeightHint = 0; + + const registerTx = api.tx.assetManager.registerForeignAsset( + uniqueAssetLocation, + uniqueAssetMetadata, + existentialDeposit, + isSufficient, + ); + console.log('Encoded proposal for registerAsset is %s', registerTx.method.toHex() || ''); + + const setUnitsTx = api.tx.assetManager.setAssetUnitsPerSecond( + uniqueAssetLocation, + unitsPerSecond, + numAssetsWeightHint, + ); + console.log('Encoded proposal for setAssetUnitsPerSecond is %s', setUnitsTx.method.toHex() || ''); + + const batchCall = api.tx.utility.batchAll([registerTx, setUnitsTx]); + console.log('Encoded proposal for batchCall is %s', batchCall.method.toHex() || ''); + + // >>> Note motion preimage >>> + console.log('Note motion preimage.......'); + const encodedProposal = batchCall?.method.toHex() || ''; + const proposalHash = blake2AsHex(encodedProposal); + console.log('Encoded proposal for batch utility after schedule is %s', encodedProposal); + console.log('Encoded proposal hash for batch utility after schedule is %s', proposalHash); + console.log('Encoded length %d', encodedProposal.length); + + const tx1 = api.tx.democracy.notePreimage(encodedProposal); + const events1 = await submitTransactionAsync(baltatharAccount, tx1); + const result1 = getGenericResult(events1); + expect(result1.success).to.be.true; + console.log('Note motion preimage.......DONE'); + // <<< Note motion preimage <<< + + // >>> Propose external motion through council >>> + console.log('Propose external motion through council.......'); + const externalMotion = api.tx.democracy.externalProposeMajority(proposalHash); + const tx2 = api.tx.councilCollective.propose( + councilVotingThreshold, + externalMotion, + externalMotion.encodedLength, + ); + const events2 = await submitTransactionAsync(baltatharAccount, tx2); + const result2 = getGenericResult(events2); + expect(result2.success).to.be.true; + + const encodedMotion = externalMotion?.method.toHex() || ''; + const motionHash = blake2AsHex(encodedMotion); + console.log('Motion hash is %s', motionHash); + + const tx3 = api.tx.councilCollective.vote(motionHash, 0, true); + { + const events3 = await submitTransactionAsync(dorothyAccount, tx3); + const result3 = getGenericResult(events3); + expect(result3.success).to.be.true; + } + { + const events3 = await submitTransactionAsync(baltatharAccount, tx3); + const result3 = getGenericResult(events3); + expect(result3.success).to.be.true; + } + + const tx4 = api.tx.councilCollective.close(motionHash, 0, 1_000_000_000, externalMotion.encodedLength); + const events4 = await submitTransactionAsync(dorothyAccount, tx4); + const result4 = getGenericResult(events4); + expect(result4.success).to.be.true; + console.log('Propose external motion through council.......DONE'); + // <<< Propose external motion through council <<< + + // >>> Fast track proposal through technical committee >>> + console.log('Fast track proposal through technical committee.......'); + const fastTrack = api.tx.democracy.fastTrack(proposalHash, votingPeriod, delayPeriod); + const tx5 = api.tx.techCommitteeCollective.propose( + technicalCommitteeThreshold, + fastTrack, + fastTrack.encodedLength, + ); + const events5 = await submitTransactionAsync(alithAccount, tx5); + const result5 = getGenericResult(events5); + expect(result5.success).to.be.true; + + const encodedFastTrack = fastTrack?.method.toHex() || ''; + const fastTrackHash = blake2AsHex(encodedFastTrack); + console.log('FastTrack hash is %s', fastTrackHash); + + const proposalIdx = Number(await api.query.techCommitteeCollective.proposalCount()) - 1; + const tx6 = api.tx.techCommitteeCollective.vote(fastTrackHash, proposalIdx, true); + { + const events6 = await submitTransactionAsync(baltatharAccount, tx6); + const result6 = getGenericResult(events6); + expect(result6.success).to.be.true; + } + { + const events6 = await submitTransactionAsync(alithAccount, tx6); + const result6 = getGenericResult(events6); + expect(result6.success).to.be.true; + } + + const tx7 = api.tx.techCommitteeCollective + .close(fastTrackHash, proposalIdx, 1_000_000_000, fastTrack.encodedLength); + const events7 = await submitTransactionAsync(baltatharAccount, tx7); + const result7 = getGenericResult(events7); + expect(result7.success).to.be.true; + console.log('Fast track proposal through technical committee.......DONE'); + // <<< Fast track proposal through technical committee <<< + + // >>> Referendum voting >>> + console.log('Referendum voting.......'); + const tx8 = api.tx.democracy.vote( + 0, + {Standard: {balance: 10_000_000_000_000_000_000n, vote: {aye: true, conviction: 1}}}, + ); + const events8 = await submitTransactionAsync(dorothyAccount, tx8); + const result8 = getGenericResult(events8); + expect(result8.success).to.be.true; + console.log('Referendum voting.......DONE'); + // <<< Referendum voting <<< + + // >>> Acquire Unique AssetId Info on Moonbeam >>> + console.log('Acquire Unique AssetId Info on Moonbeam.......'); + + // Wait for the democracy execute + await waitNewBlocks(api, 5); + + assetId = (await api.query.assetManager.assetTypeId({ + XCM: sourceLocation, + })).toString(); + + console.log('UNQ asset ID is %s', assetId); + console.log('Acquire Unique AssetId Info on Moonbeam.......DONE'); + // >>> Acquire Unique AssetId Info on Moonbeam >>> + + // >>> Sponsoring random Account >>> + console.log('Sponsoring random Account.......'); + const tx10 = api.tx.balances.transfer(randomAccountMoonbeam.address, 11_000_000_000_000_000_000n); + const events10 = await submitTransactionAsync(baltatharAccount, tx10); + const result10 = getGenericResult(events10); + expect(result10.success).to.be.true; + console.log('Sponsoring random Account.......DONE'); + // <<< Sponsoring random Account <<< + + [balanceGlmrTokenInit] = await getBalance(api, [randomAccountMoonbeam.address]); + }, + moonbeamOptions(), + ); + + await usingApi(async (api) => { + const tx0 = api.tx.balances.transfer(randomAccountUnique.address, 10n * TRANSFER_AMOUNT); + const events0 = await submitTransactionAsync(uniqueAlice, tx0); + const result0 = getGenericResult(events0); + expect(result0.success).to.be.true; + + [balanceUniqueTokenInit] = await getBalance(api, [randomAccountUnique.address]); + }); + }); + + it('Should connect and send UNQ to Moonbeam', async () => { + await usingApi(async (api) => { + const currencyId = { + NativeAssetId: 'Here', + }; + const dest = { + V1: { + parents: 1, + interior: { + X2: [ + {Parachain: MOONBEAM_CHAIN}, + {AccountKey20: {network: 'Any', key: randomAccountMoonbeam.address}}, + ], + }, + }, + }; + const amount = TRANSFER_AMOUNT; + const destWeight = 850000000; + + const tx = api.tx.xTokens.transfer(currencyId, amount, dest, destWeight); + const events = await submitTransactionAsync(randomAccountUnique, tx); + const result = getGenericResult(events); + expect(result.success).to.be.true; + + [balanceUniqueTokenMiddle] = await getBalance(api, [randomAccountUnique.address]); + expect(balanceUniqueTokenMiddle < balanceUniqueTokenInit).to.be.true; + + const transactionFees = balanceUniqueTokenInit - balanceUniqueTokenMiddle + TRANSFER_AMOUNT; + console.log('[Unique -> Moonbeam] transaction fees on Unique: %s UNQ', transactionFees); + expect(transactionFees > 0).to.be.true; + }); + + await usingApi( + async (api) => { + await waitNewBlocks(api, 3); + + [balanceGlmrTokenMiddle] = await getBalance(api, [randomAccountMoonbeam.address]); + + const glmrFees = balanceGlmrTokenInit - balanceGlmrTokenMiddle; + console.log('[Unique -> Moonbeam] transaction fees on Moonbeam: %s GLMR', glmrFees); + expect(glmrFees == 0n).to.be.true; + + const unqRandomAccountAsset = ( + await api.query.assets.account(assetId, randomAccountMoonbeam.address) + ).toJSON()! as any; + + balanceForeignUnqTokenMiddle = BigInt(unqRandomAccountAsset['balance']); + const unqIncomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenInit; + console.log('[Unique -> Moonbeam] income %s UNQ', unqIncomeTransfer); + expect(unqIncomeTransfer == TRANSFER_AMOUNT).to.be.true; + }, + moonbeamOptions(), + ); + }); + + it('Should connect to Moonbeam and send UNQ back', async () => { + await usingApi( + async (api) => { + const asset = { + V1: { + id: { + Concrete: { + parents: 1, + interior: { + X1: {Parachain: UNIQUE_CHAIN}, + }, + }, + }, + fun: { + Fungible: TRANSFER_AMOUNT, + }, + }, + }; + const destination = { + V1: { + parents: 1, + interior: { + X2: [ + {Parachain: UNIQUE_CHAIN}, + {AccountId32: {network: 'Any', id: randomAccountUnique.addressRaw}}, + ], + }, + }, + }; + const destWeight = 50000000; + + const tx = api.tx.xTokens.transferMultiasset(asset, destination, destWeight); + const events = await submitTransactionAsync(randomAccountMoonbeam, tx); + const result = getGenericResult(events); + expect(result.success).to.be.true; + + [balanceGlmrTokenFinal] = await getBalance(api, [randomAccountMoonbeam.address]); + + const glmrFees = balanceGlmrTokenMiddle - balanceGlmrTokenFinal; + console.log('[Moonbeam -> Unique] transaction fees on Moonbeam: %s GLMR', glmrFees); + expect(glmrFees > 0).to.be.true; + + const unqRandomAccountAsset = ( + await api.query.assets.account(assetId, randomAccountMoonbeam.address) + ).toJSON()! as any; + + expect(unqRandomAccountAsset).to.be.null; + + balanceForeignUnqTokenFinal = 0n; + + const unqOutcomeTransfer = balanceForeignUnqTokenMiddle - balanceForeignUnqTokenFinal; + console.log('[Unique -> Moonbeam] outcome %s UNQ', unqOutcomeTransfer); + expect(unqOutcomeTransfer == TRANSFER_AMOUNT).to.be.true; + }, + moonbeamOptions(), + ); + + await usingApi(async (api) => { + await waitNewBlocks(api, 3); + + [balanceUniqueTokenFinal] = await getBalance(api, [randomAccountUnique.address]); + const actuallyDelivered = balanceUniqueTokenFinal - balanceUniqueTokenMiddle; + expect(actuallyDelivered > 0).to.be.true; + + console.log('[Moonbeam -> Unique] actually delivered %s UNQ', actuallyDelivered); + + const unqFees = TRANSFER_AMOUNT - actuallyDelivered; + console.log('[Moonbeam -> Unique] transaction fees on Unique: %s UNQ', unqFees); + expect(unqFees == 0n).to.be.true; + }); + }); +}); -- gitstuff