Skip to content

Commit abc540d

Browse files
Ad96elntn-x2weichweich
authoredApr 22, 2024··
chore: integration tests chopsticks (#614)
## fixes [KILTprotocol/ticket#3098](KILTprotocol/ticket#3098) and fixes KILTprotocol/ticket#3239 and fixes KILTprotocol/ticket#3239. This PR introduces e2e tests based on chopsticks. Currently, only the `limitedReserAssetsTransfer` between KILT and HydraDx are tested. Due to the XCM configuration change, the XCM pallet is benchmarked. Otherwise, the extrinsic would exceed the block limits. --------- Co-authored-by: Antonio Antonino <antonio@kilt.io> Co-authored-by: Albrecht <albrecht@kilt.io>
1 parent e49a7c5 commit abc540d

24 files changed

+6213
-125
lines changed
 

‎.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@
2020
**/node_modules
2121

2222
runtimes/spiritnet/src/xcm_tests/e2e/out
23+
24+
*.db.sqlite*

‎.gitlab-ci.yml

+16
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,22 @@ test-features:
3030
script:
3131
- cargo test --all --all-features --all-targets --locked
3232

33+
integration-tests:
34+
timeout: 30 minutes
35+
image: paritytech/ci-unified:bullseye-1.70.0
36+
stage: test
37+
variables:
38+
CI: "true"
39+
script:
40+
- cd ./integration-tests/chopsticks
41+
- curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
42+
- export NVM_DIR="$HOME/.nvm" && . "$NVM_DIR/nvm.sh" --no-use
43+
- eval "[ -f .nvmrc ] && nvm install" && nvm use
44+
- cargo build -p spiritnet-runtime
45+
- yarn --immutable
46+
- yarn lint
47+
- yarn test:CI
48+
3349
# TODO: The try-runtime-cli executable could be built as part of the Docker image directly, saving some time.
3450
test-try-runtime:
3551
parallel:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
HYDRADX_WS=
2+
HYDRADX_PORT=
3+
POLKADOT_WS=
4+
POLKADOT_PORT=
5+
SPIRITNET_WS=
6+
SPIRITNET_PORT=
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"env": {
3+
"node": true,
4+
"es2021": true
5+
},
6+
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
7+
"parser": "@typescript-eslint/parser",
8+
"parserOptions": {
9+
"ecmaVersion": "latest",
10+
"sourceType": "module",
11+
"project": "./tsconfig.json"
12+
},
13+
"plugins": ["@typescript-eslint", "prettier"],
14+
"rules": {
15+
"quotes": ["warn", "single"]
16+
}
17+
}

‎integration-tests/chopsticks/.nvmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v20.11.0
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ "singleQuote": true, "trailingComma": "es5", "semi": false }
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "@kiltprotocol/e2e-tests",
3+
"version": "0.0.1",
4+
"description": "chopsticks integration tests",
5+
"private": "true",
6+
"type": "module",
7+
"repository": "git@github.com:KILTprotocol/kilt-node.git",
8+
"author": "[\"KILT <info@kilt.io>\"]",
9+
"license": "MIT",
10+
"devDependencies": {
11+
"@acala-network/chopsticks": "0.10.0",
12+
"@acala-network/chopsticks-testing": "0.10.1",
13+
"@polkadot/api": "^10.11.2",
14+
"@types/node": "^20.11.30",
15+
"@typescript-eslint/eslint-plugin": "^7.7.0",
16+
"@typescript-eslint/parser": "^7.7.0",
17+
"eslint": "^8.0.1",
18+
"eslint-config-airbnb": "^19.0.4",
19+
"eslint-config-prettier": "^9.1.0",
20+
"eslint-config-standard-with-typescript": "^43.0.1",
21+
"eslint-plugin-import": "^2.25.2",
22+
"eslint-plugin-n": "^15.0.0 || ^16.0.0 ",
23+
"eslint-plugin-prettier": "^5.1.3",
24+
"eslint-plugin-promise": "^6.0.0",
25+
"prettier": "^3.2.5",
26+
"ts-node": "^10.9.2",
27+
"tsx": "^4.7.1",
28+
"typescript": "*",
29+
"vitest": "^1.4.0",
30+
"eslint-plugin-jsx-a11y": "^6.8.0"
31+
},
32+
"scripts": {
33+
"lint": "eslint src && prettier --check src",
34+
"lint:fix": "eslint --fix src && prettier --write src",
35+
"clean": "rm -rf ./db && cargo build -p spiritnet-runtime",
36+
"test": "LOG_LEVEL=error vitest",
37+
"test:CI": "vitest --bail 0 --no-file-parallelism"
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { setupContext, SetupOption } from '@acala-network/chopsticks-testing'
2+
import type { Config } from './types.js'
3+
import * as SpiritnetConfig from './spiritnet.js'
4+
import { initialBalanceHDX, initialBalanceKILT, toNumber } from '../utils.js'
5+
6+
/// Options used to create the HydraDx context
7+
export const options: SetupOption = {
8+
endpoint: process.env.HYDRADX_WS || ['wss://hydradx-rpc.dwellir.com', 'wss://rpc.hydradx.cloud'],
9+
db: './db/hydradx.db.sqlite',
10+
port: toNumber(process.env.HYDRADX_PORT) || 9001,
11+
}
12+
13+
export const kiltTokenId = 60
14+
15+
/// Sets the [TechnicalCommittee] and [Council] governance to the given accounts
16+
export function setGovernance(addr: string[]) {
17+
return {
18+
TechnicalCommittee: { Members: addr },
19+
Council: { Members: addr },
20+
}
21+
}
22+
23+
/// Assigns the native tokens to an accounts
24+
export function assignNativeTokensToAccounts(addr: string[], balance: bigint = initialBalanceHDX) {
25+
return {
26+
System: {
27+
Account: addr.map((address) => [[address], { providers: 1, data: { free: balance } }]),
28+
},
29+
}
30+
}
31+
32+
/// Assigns KILT tokens to an accounts
33+
export function assignKiltTokensToAccounts(addr: string[], balance: bigint = initialBalanceKILT) {
34+
return {
35+
Tokens: {
36+
Accounts: addr.map((address) => [[address, kiltTokenId], { free: balance }]),
37+
},
38+
}
39+
}
40+
41+
/// Register KILT into HydraDX and allow KILT as payment
42+
export function registerKilt() {
43+
return {
44+
assetRegistry: {
45+
assetLocations: [[[kiltTokenId], { parents: 1, interior: { X1: { Parachain: SpiritnetConfig.paraId } } }]],
46+
assetIds: [[['KILT'], kiltTokenId]],
47+
locationAssets: [[[{ parents: 1, interior: { X1: { Parachain: SpiritnetConfig.paraId } } }], kiltTokenId]],
48+
assets: [
49+
[
50+
[kiltTokenId],
51+
{
52+
name: 'KILT',
53+
assetType: 'Token',
54+
existentialDeposit: 500,
55+
symbol: 'KILT',
56+
decimals: 18,
57+
xcmRateLimit: null,
58+
isSufficient: true,
59+
},
60+
],
61+
],
62+
},
63+
multiTransactionPayment: {
64+
acceptedCurrencies: [[[kiltTokenId], 100_000]],
65+
},
66+
}
67+
}
68+
69+
/// HydraDX ParaId
70+
export const paraId = 2034
71+
72+
/// OmniPool account
73+
export const omnipoolAccount = '7L53bUTBbfuj14UpdCNPwmgzzHSsrsTWBHX5pys32mVWM3C1'
74+
75+
export async function getContext(): Promise<Config> {
76+
return setupContext(options)
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { setupContext, SetupOption } from '@acala-network/chopsticks-testing'
2+
import type { Config } from './types.js'
3+
import { initialBalanceDOT, toNumber } from '../utils.js'
4+
5+
/// Options used to create the HydraDx context
6+
export const options: SetupOption = {
7+
endpoint: process.env.POLKADOT_WS || [
8+
'wss://rpc.polkadot.io',
9+
'wss://polkadot-rpc.dwellir.com',
10+
'wss://rpc.ibp.network/polkadot',
11+
],
12+
db: './db/polkadot.db.sqlite',
13+
port: toNumber(process.env.POLKADOT_PORT) || 9000,
14+
}
15+
16+
/// Assigns the native tokens to an accounts
17+
export function assignNativeTokensToAccounts(addr: string[], balance: bigint = initialBalanceDOT) {
18+
return {
19+
System: {
20+
Account: addr.map((address) => [[address], { providers: 1, data: { free: balance } }]),
21+
},
22+
}
23+
}
24+
25+
export function removeDisputesAndMessageQueues() {
26+
return {
27+
ParasDisputes: {
28+
// those can makes block building super slow
29+
$removePrefix: ['disputes'],
30+
},
31+
Dmp: {
32+
// clear existing dmp to avoid impact test result
33+
$removePrefix: ['downwardMessageQueues'],
34+
},
35+
}
36+
}
37+
38+
export async function getContext(): Promise<Config> {
39+
return setupContext(options)
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { setupContext, SetupOption } from '@acala-network/chopsticks-testing'
2+
import type { Config } from './types.js'
3+
import { initialBalanceKILT, toNumber } from '../utils.js'
4+
5+
/// Options used to create the Spiritnet context
6+
const options: SetupOption = {
7+
endpoint: process.env.SPIRITNET_WS || 'wss://kilt-rpc.dwellir.com',
8+
db: './db/spiritnet.db.sqlite',
9+
port: toNumber(process.env.SPIRITNET_PORT) || 9002,
10+
wasmOverride: '../../target/debug/wbuild/spiritnet-runtime/spiritnet_runtime.wasm',
11+
// Whether to allow WASM unresolved imports when using a WASM to build the parachain. This Flag is needed otherwise, the runtime can not be built from the WASM. Chopsticks throws an error when it encounters an unresolved import.
12+
allowUnresolvedImports: true,
13+
}
14+
15+
/// Assigns the native tokens to an accounts
16+
export function assignNativeTokensToAccounts(addr: string[], balance: bigint = initialBalanceKILT) {
17+
return {
18+
System: {
19+
Account: addr.map((address) => [[address], { providers: 1, data: { free: balance } }]),
20+
},
21+
}
22+
}
23+
24+
/// Sets the [technicalCommittee] and [council] governance to the given accounts
25+
export function setGovernance(addr: string[]) {
26+
return {
27+
technicalCommittee: { Members: addr },
28+
council: { Members: addr },
29+
}
30+
}
31+
32+
/// Sets the [safeXcmVersion] to the given version
33+
export function setSafeXcmVersion(version: number) {
34+
return {
35+
polkadotXcm: {
36+
safeXcmVersion: version,
37+
},
38+
}
39+
}
40+
41+
/// Spiritnet ParaId
42+
export const paraId = 2086
43+
44+
/// The sovereign account of HydraDx in Spiritnet
45+
export const hydraDxSovereignAccount = '4qXPdpioJ6D8cgdeYXaukV2Y2oAQUHaX1VnGhdbSRqJn2CBt'
46+
47+
/// Returns the Spiritnet context for the given options
48+
export async function getContext(): Promise<Config> {
49+
return setupContext(options)
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import type { setupContext } from '@acala-network/chopsticks-testing'
2+
3+
export type Config = Awaited<ReturnType<typeof setupContext>>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
export function getSiblingLocation(paraId: number) {
2+
return {
3+
parents: 1,
4+
interior: {
5+
X1: { Parachain: paraId },
6+
},
7+
}
8+
}
9+
10+
export function getParentLocation() {
11+
return {
12+
parents: 1,
13+
interior: 'Here',
14+
}
15+
}
16+
17+
export function getAccountLocationV2(addr: string) {
18+
return {
19+
V2: {
20+
parents: 0,
21+
interior: {
22+
X1: {
23+
AccountId32: {
24+
network: 'Any',
25+
id: addr,
26+
},
27+
},
28+
},
29+
},
30+
}
31+
}
32+
33+
export function getAccountLocationV3(addr: string) {
34+
return {
35+
V3: {
36+
parents: 0,
37+
interior: {
38+
X1: {
39+
AccountId32: {
40+
id: addr,
41+
},
42+
},
43+
},
44+
},
45+
}
46+
}
47+
48+
export function getNativeAssetIdLocation(amount: bigint) {
49+
return {
50+
id: { Concrete: { parents: 0, interior: 'Here' } },
51+
fun: { Fungible: amount },
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { beforeAll, afterAll } from 'vitest'
2+
import { connectParachains, connectVertical } from '@acala-network/chopsticks'
3+
import { setTimeout } from 'timers/promises'
4+
5+
import * as SpiritnetConfig from '../network/spiritnet.js'
6+
import * as PolkadotConfig from '../network/polkadot.js'
7+
import * as HydraDxConfig from '../network/hydraDx.js'
8+
import type { Config } from '../network/types.js'
9+
import { setStorage } from './utils.js'
10+
11+
export let spiritnetContext: Config
12+
export let hydradxContext: Config
13+
export let polkadotContext: Config
14+
15+
beforeAll(async () => {
16+
spiritnetContext = await SpiritnetConfig.getContext()
17+
hydradxContext = await HydraDxConfig.getContext()
18+
polkadotContext = await PolkadotConfig.getContext()
19+
20+
// Setup network
21+
await connectVertical(polkadotContext.chain, spiritnetContext.chain)
22+
await connectVertical(polkadotContext.chain, hydradxContext.chain)
23+
await connectParachains([spiritnetContext.chain, hydradxContext.chain])
24+
25+
const newBlockConfig = { count: 2 }
26+
// fixes api runtime disconnect warning
27+
await setTimeout(50)
28+
// Perform runtime upgrade and establish xcm connections.
29+
await Promise.all([
30+
polkadotContext.dev.newBlock(newBlockConfig),
31+
spiritnetContext.dev.newBlock(newBlockConfig),
32+
hydradxContext.dev.newBlock(newBlockConfig),
33+
])
34+
35+
console.info('Runtime Upgrade completed')
36+
37+
// set SafeXcmVersion to 3
38+
await setStorage(spiritnetContext, SpiritnetConfig.setSafeXcmVersion(3))
39+
40+
// register Kilt in HydraDX
41+
await setStorage(hydradxContext, HydraDxConfig.registerKilt())
42+
}, 300_000)
43+
44+
afterAll(async () => {
45+
// fixes api runtime disconnect warning
46+
await setTimeout(50)
47+
await Promise.all([spiritnetContext.teardown(), hydradxContext.teardown(), polkadotContext.teardown()])
48+
})
49+
50+
export async function getFreeBalanceSpiritnet(account: string): Promise<bigint> {
51+
const accountInfo = await spiritnetContext.api.query.system.account(account)
52+
return accountInfo.data.free.toBigInt()
53+
}
54+
55+
export async function getFreeBalanceHydraDxKilt(account: string): Promise<bigint> {
56+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
57+
const accountInfo: any = await hydradxContext.api.query.tokens.accounts(account, HydraDxConfig.kiltTokenId)
58+
return accountInfo.free.toBigInt()
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { ExpectStatic } from 'vitest'
2+
import { setTimeout } from 'timers/promises'
3+
import { u8aToHex } from '@polkadot/util'
4+
import { decodeAddress } from '@polkadot/util-crypto'
5+
6+
import { Config } from '../network/types.js'
7+
8+
/// Creates a new block for the given context
9+
export async function createBlock(context: Config) {
10+
// fixes api runtime disconnect warning
11+
await setTimeout(50)
12+
await context.dev.newBlock()
13+
}
14+
15+
/// sets the storage for the given context.
16+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
17+
export async function setStorage(context: Config, storage: { [key: string]: any }) {
18+
await context.dev.setStorage(storage)
19+
await createBlock(context)
20+
}
21+
22+
/// checks the balance of an account and expects it to be the given amount
23+
export async function checkBalance(
24+
getFreeBalanceFunction: (account: string) => Promise<bigint>,
25+
account: string,
26+
expect: ExpectStatic,
27+
expectedAmount = BigInt(0)
28+
) {
29+
const balance = await getFreeBalanceFunction(account)
30+
expect(balance).eq(BigInt(expectedAmount))
31+
}
32+
33+
export function hexAddress(addr: string) {
34+
return u8aToHex(decodeAddress(addr))
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`Limited Reserve V2 Transfers from Spiritnet Account Alice -> HydraDx > receiver events currencies 1`] = `
4+
[
5+
{
6+
"data": {
7+
"amount": 1000000000000000,
8+
"currencyId": 60,
9+
"who": "7L53bUTBbfuj14UpdCNPwmgzzHSsrsTWBHX5pys32mVWM3C1",
10+
},
11+
"method": "Deposited",
12+
"section": "currencies",
13+
},
14+
]
15+
`;
16+
17+
exports[`Limited Reserve V2 Transfers from Spiritnet Account Alice -> HydraDx > receiver events xcmpQueue 1`] = `
18+
[
19+
{
20+
"data": {
21+
"messageHash": "(hash)",
22+
"messageId": "(hash)",
23+
"weight": {
24+
"proofSize": 0,
25+
"refTime": 400000000,
26+
},
27+
},
28+
"method": "Success",
29+
"section": "xcmpQueue",
30+
},
31+
]
32+
`;
33+
34+
exports[`Limited Reserve V2 Transfers from Spiritnet Account Alice -> HydraDx > sender events Balances 1`] = `
35+
[
36+
{
37+
"data": {
38+
"amount": "(rounded 170000000000)",
39+
"who": "4seWojfEHrk5YKPahdErazQ3CWEHZYi6NV4gKz5AaejWbRPJ",
40+
},
41+
"method": "Withdraw",
42+
"section": "balances",
43+
},
44+
]
45+
`;
46+
47+
exports[`Limited Reserve V2 Transfers from Spiritnet Account Alice -> HydraDx > sender events xcm pallet 1`] = `
48+
[
49+
{
50+
"data": {
51+
"outcome": {
52+
"Complete": {
53+
"proofSize": 0,
54+
"refTime": 400000000,
55+
},
56+
},
57+
},
58+
"method": "Attempted",
59+
"section": "polkadotXcm",
60+
},
61+
]
62+
`;
63+
64+
exports[`Limited Reserve V2 Transfers from Spiritnet Account Alice -> HydraDx > sender events xcm queue pallet 1`] = `
65+
[
66+
{
67+
"data": {
68+
"messageHash": "(hash)",
69+
},
70+
"method": "XcmpMessageSent",
71+
"section": "xcmpQueue",
72+
},
73+
]
74+
`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`Limited Reserve V3 Transfers from Spiritnet Account Alice -> HydraDx > receiver events currencies 1`] = `
4+
[
5+
{
6+
"data": {
7+
"amount": 1000000000000000,
8+
"currencyId": 60,
9+
"who": "7L53bUTBbfuj14UpdCNPwmgzzHSsrsTWBHX5pys32mVWM3C1",
10+
},
11+
"method": "Deposited",
12+
"section": "currencies",
13+
},
14+
]
15+
`;
16+
17+
exports[`Limited Reserve V3 Transfers from Spiritnet Account Alice -> HydraDx > receiver events xcmpQueue 1`] = `
18+
[
19+
{
20+
"data": {
21+
"messageHash": "(hash)",
22+
"messageId": "(hash)",
23+
"weight": {
24+
"proofSize": 0,
25+
"refTime": 400000000,
26+
},
27+
},
28+
"method": "Success",
29+
"section": "xcmpQueue",
30+
},
31+
]
32+
`;
33+
34+
exports[`Limited Reserve V3 Transfers from Spiritnet Account Alice -> HydraDx > sender events Balances 1`] = `
35+
[
36+
{
37+
"data": {
38+
"amount": "(rounded 170000000000)",
39+
"who": "4seWojfEHrk5YKPahdErazQ3CWEHZYi6NV4gKz5AaejWbRPJ",
40+
},
41+
"method": "Withdraw",
42+
"section": "balances",
43+
},
44+
]
45+
`;
46+
47+
exports[`Limited Reserve V3 Transfers from Spiritnet Account Alice -> HydraDx > sender events xcm pallet 1`] = `
48+
[
49+
{
50+
"data": {
51+
"outcome": {
52+
"Complete": {
53+
"proofSize": 0,
54+
"refTime": 400000000,
55+
},
56+
},
57+
},
58+
"method": "Attempted",
59+
"section": "polkadotXcm",
60+
},
61+
]
62+
`;
63+
64+
exports[`Limited Reserve V3 Transfers from Spiritnet Account Alice -> HydraDx > sender events xcm queue pallet 1`] = `
65+
[
66+
{
67+
"data": {
68+
"messageHash": "(hash)",
69+
},
70+
"method": "XcmpMessageSent",
71+
"section": "xcmpQueue",
72+
},
73+
]
74+
`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`Limited Reserve Transfers from HydraDx Account Bob -> Spiritnet > receiver events Balances 1`] = `
4+
[
5+
{
6+
"data": {
7+
"amount": 1000000000000000,
8+
"who": "4qXPdpioJ6D8cgdeYXaukV2Y2oAQUHaX1VnGhdbSRqJn2CBt",
9+
},
10+
"method": "Withdraw",
11+
"section": "balances",
12+
},
13+
]
14+
`;
15+
16+
exports[`Limited Reserve Transfers from HydraDx Account Bob -> Spiritnet > receiver events Balances 2`] = `
17+
[
18+
{
19+
"data": {
20+
"account": "4seWojfEHrk5YKPahdErazQ3CWEHZYi6NV4gKz5AaejWbRPJ",
21+
"freeBalance": "(rounded 1000000000000000)",
22+
},
23+
"method": "Endowed",
24+
"section": "balances",
25+
},
26+
]
27+
`;
28+
29+
exports[`Limited Reserve Transfers from HydraDx Account Bob -> Spiritnet > receiver events xcmpQueue 1`] = `
30+
[
31+
{
32+
"data": {
33+
"messageHash": "(hash)",
34+
"messageId": "(hash)",
35+
"weight": {
36+
"proofSize": 0,
37+
"refTime": 800000000,
38+
},
39+
},
40+
"method": "Success",
41+
"section": "xcmpQueue",
42+
},
43+
]
44+
`;
45+
46+
exports[`Limited Reserve Transfers from HydraDx Account Bob -> Spiritnet > sender events currencies 1`] = `
47+
[
48+
{
49+
"data": {
50+
"amount": "(rounded 440000000000)",
51+
"currencyId": 0,
52+
"who": "7MZG43idRmdg8VSt5BS9mVJeBhhxxt5y55hCsMpoKp5xFQX2",
53+
},
54+
"method": "Withdrawn",
55+
"section": "currencies",
56+
},
57+
{
58+
"data": {
59+
"amount": 1000000000000000,
60+
"currencyId": 60,
61+
"who": "7MZG43idRmdg8VSt5BS9mVJeBhhxxt5y55hCsMpoKp5xFQX2",
62+
},
63+
"method": "Withdrawn",
64+
"section": "currencies",
65+
},
66+
]
67+
`;
68+
69+
exports[`Limited Reserve Transfers from HydraDx Account Bob -> Spiritnet > sender events currencies 2`] = `
70+
[
71+
{
72+
"data": {
73+
"assets": [
74+
{
75+
"fun": {
76+
"Fungible": 1000000000000000,
77+
},
78+
"id": {
79+
"Concrete": {
80+
"interior": {
81+
"X1": {
82+
"Parachain": "(rounded 2100)",
83+
},
84+
},
85+
"parents": 1,
86+
},
87+
},
88+
},
89+
],
90+
"dest": {
91+
"interior": {
92+
"X2": [
93+
{
94+
"Parachain": "(rounded 2100)",
95+
},
96+
{
97+
"AccountId32": {
98+
"id": "(hash)",
99+
"network": null,
100+
},
101+
},
102+
],
103+
},
104+
"parents": 1,
105+
},
106+
"fee": {
107+
"fun": {
108+
"Fungible": 1000000000000000,
109+
},
110+
"id": {
111+
"Concrete": {
112+
"interior": {
113+
"X1": {
114+
"Parachain": "(rounded 2100)",
115+
},
116+
},
117+
"parents": 1,
118+
},
119+
},
120+
},
121+
"sender": "7MZG43idRmdg8VSt5BS9mVJeBhhxxt5y55hCsMpoKp5xFQX2",
122+
},
123+
"method": "TransferredMultiAssets",
124+
"section": "xTokens",
125+
},
126+
]
127+
`;
128+
129+
exports[`Limited Reserve Transfers from HydraDx Account Bob -> Spiritnet > sender events xcm queue pallet 1`] = `
130+
[
131+
{
132+
"data": {
133+
"messageHash": "(hash)",
134+
},
135+
"method": "XcmpMessageSent",
136+
"section": "xcmpQueue",
137+
},
138+
]
139+
`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { test } from 'vitest'
2+
import { sendTransaction, withExpect } from '@acala-network/chopsticks-testing'
3+
4+
import * as SpiritnetConfig from '../../network/spiritnet.js'
5+
import * as HydraDxConfig from '../../network/hydraDx.js'
6+
import { KILT, keysAlice } from '../../utils.js'
7+
import { spiritnetContext, hydradxContext, getFreeBalanceSpiritnet, getFreeBalanceHydraDxKilt } from '../index.js'
8+
import { getAccountLocationV2, getNativeAssetIdLocation, getSiblingLocation } from '../../network/utils.js'
9+
import { checkBalance, createBlock, hexAddress, setStorage } from '../utils.js'
10+
11+
const KILT_ASSET_V2 = { V2: [getNativeAssetIdLocation(KILT)] }
12+
13+
test('Limited Reserve V2 Transfers from Spiritnet Account Alice -> HydraDx', async ({ expect }) => {
14+
const { checkEvents, checkSystemEvents } = withExpect(expect)
15+
16+
// Set storage
17+
await setStorage(spiritnetContext, SpiritnetConfig.assignNativeTokensToAccounts([keysAlice.address]))
18+
await setStorage(hydradxContext, HydraDxConfig.assignNativeTokensToAccounts([keysAlice.address]))
19+
20+
// check initial balance
21+
await checkBalance(getFreeBalanceSpiritnet, SpiritnetConfig.hydraDxSovereignAccount, expect)
22+
await checkBalance(getFreeBalanceHydraDxKilt, HydraDxConfig.omnipoolAccount, expect)
23+
24+
const omniPoolAddress = hexAddress(HydraDxConfig.omnipoolAccount)
25+
const hydraDxDestination = { V2: getSiblingLocation(HydraDxConfig.paraId) }
26+
const beneficiary = getAccountLocationV2(omniPoolAddress)
27+
28+
const signedTx = spiritnetContext.api.tx.polkadotXcm
29+
.limitedReserveTransferAssets(hydraDxDestination, beneficiary, KILT_ASSET_V2, 0, 'Unlimited')
30+
.signAsync(keysAlice)
31+
32+
const events = await sendTransaction(signedTx)
33+
34+
// Check sender state
35+
await createBlock(spiritnetContext)
36+
37+
// Check events sender
38+
checkEvents(events, 'xcmpQueue').toMatchSnapshot('sender events xcm queue pallet')
39+
checkEvents(events, 'polkadotXcm').toMatchSnapshot('sender events xcm pallet')
40+
checkEvents(events, { section: 'balances', method: 'Withdraw' }).toMatchSnapshot('sender events Balances')
41+
42+
// check balance
43+
await checkBalance(getFreeBalanceSpiritnet, SpiritnetConfig.hydraDxSovereignAccount, expect, KILT)
44+
45+
// Check receiver state
46+
await createBlock(hydradxContext)
47+
48+
// Check events receiver
49+
checkSystemEvents(hydradxContext, { section: 'currencies', method: 'Deposited' }).toMatchSnapshot(
50+
'receiver events currencies'
51+
)
52+
checkSystemEvents(hydradxContext, 'xcmpQueue').toMatchSnapshot('receiver events xcmpQueue')
53+
54+
// check balance
55+
await checkBalance(getFreeBalanceHydraDxKilt, HydraDxConfig.omnipoolAccount, expect, KILT)
56+
}, 20_000)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { test } from 'vitest'
2+
import { sendTransaction, withExpect } from '@acala-network/chopsticks-testing'
3+
4+
import * as SpiritnetConfig from '../../network/spiritnet.js'
5+
import * as HydraDxConfig from '../../network/hydraDx.js'
6+
import { KILT, keysAlice } from '../../utils.js'
7+
import { spiritnetContext, hydradxContext, getFreeBalanceSpiritnet, getFreeBalanceHydraDxKilt } from '../index.js'
8+
import { getAccountLocationV3, getNativeAssetIdLocation, getSiblingLocation } from '../../network/utils.js'
9+
import { checkBalance, createBlock, hexAddress, setStorage } from '../utils.js'
10+
11+
const KILT_ASSET_V3 = { V3: [getNativeAssetIdLocation(KILT)] }
12+
13+
test('Limited Reserve V3 Transfers from Spiritnet Account Alice -> HydraDx', async ({ expect }) => {
14+
const { checkEvents, checkSystemEvents } = withExpect(expect)
15+
16+
// set storage
17+
await setStorage(spiritnetContext, SpiritnetConfig.assignNativeTokensToAccounts([keysAlice.address]))
18+
await setStorage(hydradxContext, HydraDxConfig.assignNativeTokensToAccounts([keysAlice.address]))
19+
20+
// check initial balance
21+
await checkBalance(getFreeBalanceSpiritnet, SpiritnetConfig.hydraDxSovereignAccount, expect)
22+
await checkBalance(getFreeBalanceHydraDxKilt, HydraDxConfig.omnipoolAccount, expect)
23+
24+
const omniPoolAddress = hexAddress(HydraDxConfig.omnipoolAccount)
25+
const hydraDxDestination = { V3: getSiblingLocation(HydraDxConfig.paraId) }
26+
const beneficiary = getAccountLocationV3(omniPoolAddress)
27+
28+
const signedTx = spiritnetContext.api.tx.polkadotXcm
29+
.limitedReserveTransferAssets(hydraDxDestination, beneficiary, KILT_ASSET_V3, 0, 'Unlimited')
30+
.signAsync(keysAlice)
31+
32+
const events = await sendTransaction(signedTx)
33+
34+
// Check sender state
35+
await createBlock(spiritnetContext)
36+
37+
// Check events sender
38+
checkEvents(events, 'xcmpQueue').toMatchSnapshot('sender events xcm queue pallet')
39+
checkEvents(events, 'polkadotXcm').toMatchSnapshot('sender events xcm pallet')
40+
checkEvents(events, { section: 'balances', method: 'Withdraw' }).toMatchSnapshot('sender events Balances')
41+
42+
// check balance sender
43+
await checkBalance(getFreeBalanceSpiritnet, SpiritnetConfig.hydraDxSovereignAccount, expect, KILT)
44+
45+
// Check receiver state
46+
await createBlock(hydradxContext)
47+
48+
// Check events receiver
49+
checkSystemEvents(hydradxContext, { section: 'currencies', method: 'Deposited' }).toMatchSnapshot(
50+
'receiver events currencies'
51+
)
52+
checkSystemEvents(hydradxContext, 'xcmpQueue').toMatchSnapshot('receiver events xcmpQueue')
53+
54+
// check balance receiver
55+
await checkBalance(getFreeBalanceHydraDxKilt, HydraDxConfig.omnipoolAccount, expect, KILT)
56+
}, 20_000)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { test } from 'vitest'
2+
import { sendTransaction, withExpect } from '@acala-network/chopsticks-testing'
3+
4+
import * as HydraDxConfig from '../../network/hydraDx.js'
5+
import * as SpiritnetConfig from '../../network/spiritnet.js'
6+
import { KILT, initialBalanceKILT, keysAlice, keysBob } from '../../utils.js'
7+
import { getFreeBalanceHydraDxKilt, getFreeBalanceSpiritnet, hydradxContext, spiritnetContext } from '../index.js'
8+
import { checkBalance, createBlock, hexAddress, setStorage } from '../utils.js'
9+
10+
const aliceLocation = {
11+
V3: {
12+
parents: 1,
13+
interior: {
14+
X2: [
15+
{ Parachain: SpiritnetConfig.paraId },
16+
{
17+
AccountId32: {
18+
id: hexAddress(keysAlice.address),
19+
},
20+
},
21+
],
22+
},
23+
},
24+
}
25+
26+
test('Limited Reserve Transfers from HydraDx Account Bob -> Spiritnet', async ({ expect }) => {
27+
const { checkEvents, checkSystemEvents } = withExpect(expect)
28+
29+
const hydraDxConfig = {
30+
...HydraDxConfig.assignKiltTokensToAccounts([keysBob.address, HydraDxConfig.omnipoolAccount]),
31+
...HydraDxConfig.assignNativeTokensToAccounts([keysBob.address, HydraDxConfig.omnipoolAccount]),
32+
}
33+
34+
// Update storage
35+
await setStorage(
36+
spiritnetContext,
37+
SpiritnetConfig.assignNativeTokensToAccounts([SpiritnetConfig.hydraDxSovereignAccount])
38+
)
39+
await setStorage(hydradxContext, hydraDxConfig)
40+
41+
await createBlock(spiritnetContext)
42+
await createBlock(hydradxContext)
43+
44+
// check initial balance of alice
45+
await checkBalance(getFreeBalanceSpiritnet, keysAlice.address, expect)
46+
47+
const signedTx = hydradxContext.api.tx.xTokens
48+
.transfer(HydraDxConfig.kiltTokenId, KILT, aliceLocation, 'Unlimited')
49+
.signAsync(keysBob)
50+
51+
const events = await sendTransaction(signedTx)
52+
53+
// Check sender state
54+
await createBlock(hydradxContext)
55+
56+
// Check events sender
57+
checkEvents(events, 'xcmpQueue').toMatchSnapshot('sender events xcm queue pallet')
58+
checkEvents(events, { section: 'currencies', method: 'Withdrawn' }).toMatchSnapshot('sender events currencies')
59+
checkEvents(events, 'xTokens').toMatchSnapshot('sender events currencies')
60+
61+
// Check balance
62+
await checkBalance(getFreeBalanceHydraDxKilt, keysBob.address, expect, initialBalanceKILT - KILT)
63+
64+
// Check receiver state
65+
await createBlock(spiritnetContext)
66+
67+
// check events receiver
68+
checkSystemEvents(spiritnetContext, 'xcmpQueue').toMatchSnapshot('receiver events xcmpQueue')
69+
checkSystemEvents(spiritnetContext, { section: 'balances', method: 'Withdraw' }).toMatchSnapshot(
70+
'receiver events Balances'
71+
)
72+
checkSystemEvents(spiritnetContext, { section: 'balances', method: 'Endowed' }).toMatchSnapshot(
73+
'receiver events Balances'
74+
)
75+
76+
// Check balance receiver
77+
await checkBalance(
78+
getFreeBalanceSpiritnet,
79+
SpiritnetConfig.hydraDxSovereignAccount,
80+
expect,
81+
initialBalanceKILT - KILT
82+
)
83+
// Alice receives a bit less since the tx fees has to be paid.
84+
await checkBalance(getFreeBalanceSpiritnet, keysAlice.address, expect, BigInt('999999999971175'))
85+
}, 20_000)
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Keyring } from '@polkadot/keyring'
2+
3+
const keyring = new Keyring({ type: 'ed25519', ss58Format: 38 })
4+
5+
export const keysAlice = keyring.addFromUri('//alice', undefined, 'ed25519')
6+
export const keysBob = keyring.addFromUri('//bob', undefined, 'ed25519')
7+
export const keysCharlie = keyring.addFromUri('//charlie', undefined, 'ed25519')
8+
9+
export function toNumber(value: string | undefined): number | undefined {
10+
if (value === undefined) {
11+
return undefined
12+
}
13+
14+
return Number(value)
15+
}
16+
17+
export const KILT = BigInt(1e15)
18+
export const DOT = BigInt(1e10)
19+
export const HDX = BigInt(1e12)
20+
21+
export const initialBalanceKILT = BigInt(100) * KILT
22+
export const initialBalanceDOT = BigInt(100) * DOT
23+
export const initialBalanceHDX = BigInt(100) * HDX
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es2016",
4+
"lib": ["es2019"],
5+
"module": "NodeNext",
6+
"rootDir": "src",
7+
"esModuleInterop": true,
8+
"forceConsistentCasingInFileNames": true,
9+
"strict": true,
10+
"noImplicitAny": true,
11+
"skipLibCheck": true
12+
}
13+
}

‎integration-tests/chopsticks/yarn.lock

+5,139
Large diffs are not rendered by default.

‎runtimes/spiritnet/src/weights/pallet_xcm.rs

+155-125
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.