diff --git a/Cargo.lock b/Cargo.lock index 59fb3433..7bcefb50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1242,7 +1242,7 @@ dependencies = [ "nom", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.60", ] [[package]] diff --git a/e2e_tests/common.ts b/e2e_tests/common.ts index 4a8c0a87..5c86271a 100644 --- a/e2e_tests/common.ts +++ b/e2e_tests/common.ts @@ -2,56 +2,64 @@ import { ApiPromise, Keyring } from "@polkadot/api"; import { SubmittableExtrinsic, SignerOptions } from "@polkadot/api/types"; import { KeyringPair } from "@polkadot/keyring/types"; -const RELAY_ASSET_ID = 1; +const RELAY_ASSET_ID = 1; -async function submitExtrinsic(signer: KeyringPair, call: SubmittableExtrinsic<"promise">, options: Partial): Promise { - return new Promise(async (resolve, reject) => { - const unsub = await call.signAndSend(signer, options, (result) => { - console.log(`Current status is ${result.status}`); - if (result.status.isInBlock) { - console.log( - `Transaction included at blockHash ${result.status.asInBlock}` - ); - } else if (result.status.isFinalized) { - console.log( - `Transaction finalized at blockHash ${result.status.asFinalized}` - ); - unsub(); - return resolve(); - } else if (result.isError) { - console.log(`Transaction error`); - unsub(); - return reject(); - } - }); - }); +async function submitExtrinsic( + signer: KeyringPair, + call: SubmittableExtrinsic<"promise">, + options: Partial +): Promise { + return new Promise(async (resolve, reject) => { + const unsub = await call.signAndSend(signer, options, (result) => { + console.log(`Current status is ${result.status}`); + if (result.status.isInBlock) { + console.log(`Transaction included at blockHash ${result.status.asInBlock}`); + } else if (result.status.isFinalized) { + console.log(`Transaction finalized at blockHash ${result.status.asFinalized}`); + unsub(); + return resolve(); + } else if (result.isError) { + console.log(`Transaction error`); + unsub(); + return reject(); + } + }); + }); } -async function setupRelayAsset(api: ApiPromise, signer: KeyringPair) { - const assetMetadata = { - decimals: 12, - name: "ROC", - symbol: "ROC", - existentialDeposit: 10n ** 3n, - location: null, - additional: null, - }; +async function setupRelayAsset( + api: ApiPromise, + signer: KeyringPair, + initialBalance: bigint = 10n ** 12n +) { + const assetMetadata = { + decimals: 12, + name: "ROC", + symbol: "ROC", + existentialDeposit: 10n ** 3n, + location: null, + additional: null, + }; - const assetSetupCalls = [ - api.tx.assetRegistry.registerAsset(assetMetadata, RELAY_ASSET_ID), - api.tx.assetRate.create(RELAY_ASSET_ID, 1_000_000_000_000_000_000n), // 1 on 1 - api.tx.tokens.setBalance( - signer.address, - RELAY_ASSET_ID, - 10n ** 12n, - 0, - ), - ]; + const assetSetupCalls = [ + api.tx.assetRegistry.registerAsset(assetMetadata, RELAY_ASSET_ID), + api.tx.assetRate.create(RELAY_ASSET_ID, 1_000_000_000_000_000_000n), // 1 on 1 + ]; - const batchCall = api.tx.utility.batch(assetSetupCalls); - const sudoCall = api.tx.sudo.sudo(batchCall); + if (initialBalance > BigInt(0)) { + assetSetupCalls.push( + api.tx.tokens.setBalance(signer.address, RELAY_ASSET_ID, initialBalance, 0) + ); + } - await submitExtrinsic(signer, sudoCall, {}); + const batchCall = api.tx.utility.batch(assetSetupCalls); + const sudoCall = api.tx.sudo.sudo(batchCall); + + await submitExtrinsic(signer, sudoCall, {}); +} + +async function sleep(milliseconds: number) { + return new Promise((resolve) => setTimeout(resolve, milliseconds)); } async function transferRelayAssetToRegionX(amount: string, relayApi: ApiPromise, signer: KeyringPair) { diff --git a/e2e_tests/fee-payment/custom.ts b/e2e_tests/fee-payment/custom.ts index 78c59e13..3883fdd2 100644 --- a/e2e_tests/fee-payment/custom.ts +++ b/e2e_tests/fee-payment/custom.ts @@ -26,45 +26,11 @@ async function run(nodeName: string, networkInfo: any, _jsArgs: any) { const setXcmVersion = rococoApi.tx.xcmPallet.forceDefaultXcmVersion([3]); await submitExtrinsic(alice, rococoApi.tx.sudo.sudo(setXcmVersion), {}); - await setupRelayAsset(regionXApi, alice); + await setupRelayAsset(regionXApi, alice, 10n**12n); const receiverKeypair = new Keyring(); receiverKeypair.addFromAddress(alice.address); - const feeAssetItem = 0; - const weightLimit = "Unlimited"; - const reserveTransfer = rococoApi.tx.xcmPallet.limitedReserveTransferAssets( - { V3: { parents: 0, interior: { X1: { Parachain: 2000 } } } }, //dest - { - V3: { - parents: 0, - interior: { - X1: { - AccountId32: { - chain: "Any", - id: receiverKeypair.pairs[0].publicKey, - }, - }, - }, - }, - }, //beneficiary - { - V3: [ - { - id: { - Concrete: { parents: 0, interior: "Here" }, - }, - fun: { - Fungible: 10n ** 9n, - }, - }, - ], - }, //asset - feeAssetItem, - weightLimit - ); - await submitExtrinsic(alice, reserveTransfer, {}); - // Try to pay for fees with relay chain asset. const remarkCall = regionXApi.tx.system.remark("0x44"); await submitExtrinsic(alice, remarkCall, { assetId: RELAY_ASSET_ID }); diff --git a/e2e_tests/xc-transfer/asset-transfer.ts b/e2e_tests/xc-transfer/asset-transfer.ts new file mode 100644 index 00000000..fba88b78 --- /dev/null +++ b/e2e_tests/xc-transfer/asset-transfer.ts @@ -0,0 +1,126 @@ +import { ApiPromise, WsProvider, Keyring } from "@polkadot/api"; +import { RELAY_ASSET_ID, setupRelayAsset, sleep, submitExtrinsic } from "../common"; + +import assert from "node:assert"; + +const TOLERANCE = 10n**10n; + +async function run(nodeName: string, networkInfo: any, _jsArgs: any) { + const { wsUri: regionXUri } = networkInfo.nodesByName[nodeName]; + const { wsUri: rococoUri } = networkInfo.nodesByName["rococo-validator01"]; + + const rococoApi = await ApiPromise.create({ provider: new WsProvider(rococoUri) }); + const regionXApi = await ApiPromise.create({ provider: new WsProvider(regionXUri) }); + + // account to submit tx + const keyring = new Keyring({ type: "sr25519" }); + const alice = keyring.addFromUri("//Alice"); + + const setXcmVersion = rococoApi.tx.xcmPallet.forceDefaultXcmVersion([3]); + await submitExtrinsic(alice, rococoApi.tx.sudo.sudo(setXcmVersion), {}); + + await setupRelayAsset(regionXApi, alice); + + const receiverKeypair = new Keyring(); + receiverKeypair.addFromAddress(alice.address); + + const assertRegionXBalance = async (address: string, balance: bigint) => { + const { free } = ( + await regionXApi.query.tokens.accounts(address, RELAY_ASSET_ID) + ).toHuman() as any; + + console.log(`RegionX: ${free}`); + assert(balance - BigInt(free.toString().replace(/,/g, "")) < TOLERANCE); + }; + + const assertRococoBalance = async (address: string, balance: bigint) => { + const { + data: { free }, + } = (await rococoApi.query.system.account(address)).toHuman() as any; + + console.log(`Rococo: ${free}`); + assert(balance - BigInt(free.toString().replace(/,/g, "")) < TOLERANCE); + }; + + await assertRegionXBalance(alice.address, 10n ** 12n); + await assertRococoBalance(alice.address, 10n ** 18n); + + const feeAssetItem = 0; + const weightLimit = "Unlimited"; + const rococoReserveTransfer = rococoApi.tx.xcmPallet.limitedReserveTransferAssets( + { V3: { parents: 0, interior: { X1: { Parachain: 2000 } } } }, //dest + { + V3: { + parents: 0, + interior: { + X1: { + AccountId32: { + chain: "Any", + id: receiverKeypair.pairs[0].publicKey, + }, + }, + }, + }, + }, //beneficiary + { + V3: [ + { + id: { + Concrete: { parents: 0, interior: "Here" }, + }, + fun: { + Fungible: 3n * 10n ** 12n, + }, + }, + ], + }, //asset + feeAssetItem, + weightLimit + ); + await submitExtrinsic(alice, rococoReserveTransfer, {}); + + await sleep(5 * 1000); + + await assertRegionXBalance(alice.address, 4n * 10n ** 12n); + await assertRococoBalance(alice.address, 10n ** 18n - 3n * 10n ** 12n); + + const regionXReserveTransfer = regionXApi.tx.polkadotXcm.limitedReserveTransferAssets( + { V3: { parents: 1, interior: "Here" } }, //dest + { + V3: { + parents: 0, + interior: { + X1: { + AccountId32: { + chain: "Any", + id: receiverKeypair.pairs[0].publicKey, + }, + }, + }, + }, + }, //beneficiary + { + V3: [ + { + id: { + Concrete: { parents: 1, interior: "Here" }, + }, + fun: { + Fungible: 10n ** 12n, + }, + }, + ], + }, //asset + feeAssetItem, + weightLimit + ); + + await submitExtrinsic(alice, regionXReserveTransfer, {}); + + await sleep(5 * 1000); + + await assertRegionXBalance(alice.address, 4n * 10n ** 12n); + await assertRococoBalance(alice.address, 10n ** 18n - 3n * 10n ** 12n); +} + +export { run }; diff --git a/zombienet_tests/0006-xc-transfer.toml b/zombienet_tests/0006-xc-transfer.toml new file mode 100644 index 00000000..d53556a4 --- /dev/null +++ b/zombienet_tests/0006-xc-transfer.toml @@ -0,0 +1,25 @@ +[settings] +timeout = 1000 + +[relaychain] +chain = "rococo-local" +command = "polkadot" + + [[relaychain.nodes]] + name = "rococo-validator01" + args = [ "--log=xcm=trace" ] + validator = true + + [[relaychain.nodes]] + name = "rococo-validator02" + args = [ "--log=xcm=trace" ] + validator = true + +[[parachains]] +id = 2000 +addToGenesis = false + + [parachains.collator] + name = "regionx-collator01" + command = "regionx-node" + args = [ "--log=xcm=trace" ] diff --git a/zombienet_tests/0006-xc-transfer.zndsl b/zombienet_tests/0006-xc-transfer.zndsl new file mode 100644 index 00000000..55ba38de --- /dev/null +++ b/zombienet_tests/0006-xc-transfer.zndsl @@ -0,0 +1,11 @@ +Description: Cross chain transfer between RegionX and the relay chain +Network: ./0006-xc-transfer.toml +Creds: config + +rococo-validator01: is up +rococo-validator02: is up + +rococo-validator01: parachain 2000 is registered within 225 seconds + +regionx-collator01: js-script ../e2e_tests/build/xc-transfer/asset-transfer.js return is 0 within 400 seconds +