Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow HydraDX atomic swap XCMs #1335

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
47 changes: 46 additions & 1 deletion integration-tests/moonwall.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,51 @@
"endpoints": ["ws://127.0.0.1:8002"]
}
]
}
},
{
"name": "chopsticks_zeitgeist_hydradx_atomic_swap",
"testFileDir": ["tests/zeitgeist-hydradx-atomic-swap-chopsticks"],
"runScripts": ["build-node.sh"],
"foundation": {
"type": "chopsticks",
"rtUpgradePath": "../target/release/wbuild/zeitgeist-runtime/zeitgeist_runtime.compact.compressed.wasm",
"launchSpec": [
{
"name": "ZeitgeistDB",
"type": "parachain",
"configPath": "./configs/zeitgeist.yml"
},
{
"name": "HydraDXDB",
"type": "parachain",
"configPath": "./configs/hydradx.yml"
},
{
"name": "PolkadotDB",
"type": "relaychain",
"configPath": "polkadot"
}
]
},
"envVars": ["LOG_LEVEL=debug", "VERBOSE_LOG"],
"buildBlockMode": "manual",
"connections": [
{
"name": "ZeitgeistPara",
"type": "polkadotJs",
"endpoints": ["ws://127.0.0.1:8000"]
},
{
"name": "HydraDXPara",
"type": "polkadotJs",
"endpoints": ["ws://127.0.0.1:8001"]
},
{
"name": "PolkadotRelay",
"type": "polkadotJs",
"endpoints": ["ws://127.0.0.1:8002"]
}
]
},
]
}
246 changes: 246 additions & 0 deletions integration-tests/tests/common-tests.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
// Copyright (C) Moondance Labs Ltd.
// Copyright 2024 Forecasting Technologies LTD.
//
// This file is part of Zeitgeist.
//
// Zeitgeist is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation, either version 3 of the License, or (at
// your option) any later version.
//
// Zeitgeist is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Zeitgeist. If not, see <https://www.gnu.org/licenses/>.

import { expect, ChopsticksContext } from "@moonwall/cli";
import { generateKeyringPair } from "@moonwall/util";
Expand Down Expand Up @@ -205,3 +221,233 @@ export async function canSendXcmTransfer(
"Unexpected xcm transfer balance diff"
).toBe(amount - xcmFee);
}

// tests this https://github.com/galacticcouncil/HydraDX-node/blob/7402f8ef84bb4a2ba71d54eb19f2e36742b8f655/integration-tests/src/exchange_asset.rs#L530-L597
export async function canExecuteAtomicSwap(
context: ChopsticksContext,
log: Debugger,
senderProviderName: string,
senderParaApi: ApiPromise,
senderParaId: number,
hydradxParaApi: ApiPromise,
hydradxParaId: number
) {
const keyring = new Keyring({ type: "sr25519" });
const alice = keyring.addFromUri("//Alice", { name: "Alice default" });

const senderBalanceBefore = (
(await senderParaApi.query.system.account(
alice.address
)) as unknown as AccountInfo
).data.free.toBigInt();

const ztg = { Ztg: null };
const aliceAccountId = senderParaApi
.createType("AccountId32", alice.address)
.toHex();

// TODO: register HDX token on Zeitgeist chain first in order to swap ZTG for HDX on HydraDX chain

const dest = {
parents: 1,
interior: {
X1: { Parachain: hydradxParaId },
},
};

const destination = {
V3: dest,
};

// taken from here https://github.com/galacticcouncil/HydraDX-node/blob/e3821e078bdb72a0416f8aebca21ba4a7a599f64/runtime/hydradx/src/xcm.rs#L312-L315
const localHDX = {
parents: 0,
interior: {
X1: { GeneralIndex: 0 },
},
};

// TODO: mint HDX token on HydraDX for the swap executor to pay for the XCM execution
const buyExecution = {
BuyExecution: {
fees: {
id: {
Concrete: localHDX,
},
fun: {
// 100 HDX (12 decimals base)
Fungible: 100_000_000_000_000n,
},
},
weightLimit: {
Unlimited: null,
},
},
};

const ztgOnHydraDX = {
parents: 1,
interior: {
X2: [
{ Parachain: senderParaId },
{
GeneralKey: {
length: 2,
data: "0x0001000000000000000000000000000000000000000000000000000000000000",
},
},
],
},
};

const exchangeAsset = {
ExchangeAsset: {
give: {
Definite: [
{
id: {
Concrete: ztgOnHydraDX,
},
fun: {
// 100 ZTG (10 decimals base)
Fungible: 1_000_000_000_000n,
},
},
],
},
want: {
id: {
Concrete: localHDX,
},
fun: {
// 50 HDX (12 decimals base)
Fungible: 50_000_000_000_000n,
},
},
// Reference: https://github.com/paritytech/polkadot-sdk/blob/289f5bbf7a45dc0380904a435464b15ec711ed03/polkadot/xcm/src/v3/mod.rs#L722-L724
// give as little ZTG as possible to receive at least 50 HDX
maximal: false,
},
};

const depositAsset = {
DepositAsset: {
assets: { Wild: { AllCounted: 2 } },
beneficiary: {
parents: 0,
interior: {
X1: { AccountId32: { id: aliceAccountId, network: null } },
},
},
},
};

// executed on HydraDX
const hydradxXcm = {
V3: [buyExecution, exchangeAsset, depositAsset],
};

const setFeesMode = {
SetFeesMode: {
jitWithdraw: true,
},
};

const localZTG = {
parents: 0,
interior: {
X1: {
GeneralKey: {
length: 2,
data: "0x0001000000000000000000000000000000000000000000000000000000000000",
},
},
},
};

// 100 ZTG (10 decimals base)
const ztgAmount = 1_000_000_000_000n;

const assets = [
{
id: {
Concrete: localZTG,
},
fun: {
Fungible: ztgAmount,
},
},
];

const transferReserveAsset = {
TransferReserveAsset: {
assets: assets,
dest: dest,
xcm: hydradxXcm,
},
};

// Reference: https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fzeitgeist-rpc.dwellir.com#/extrinsics/decode/0x7a0003010100c91f03082b0105040000010602000100000000000000000000000000000000000000000000000000000000000000070010a5d4e8010100c91f0c130000010500000b00407a10f35a000f000400010200b1200602000100000000000000000000000000000000000000000000000000000000000000070010a5d4e8040000010500000b00203d88792d000d01020800010100d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
const xcmMessage = {
V3: [setFeesMode, transferReserveAsset],
};

const xcmAtomicSwap = senderParaApi.tx.polkadotXcm.send(
destination,
xcmMessage
);

const { partialFee, weight } = await xcmAtomicSwap.paymentInfo(alice.address);
const transferFee: bigint = partialFee.toBigInt();

await xcmAtomicSwap.signAndSend(alice, { nonce: -1 });

await context.createBlock({
providerName: senderProviderName,
count: 1,
allowFailures: false,
});

const senderBalanceAfter = (
(await senderParaApi.query.system.account(
alice.address
)) as unknown as AccountInfo
).data.free.toBigInt();
expect(
senderBalanceBefore - senderBalanceAfter,
"Unexpected balance diff"
).toBe(ztgAmount + transferFee);

// RpcError: 1: Block 0x... not found, if using this `await context.createBlock({ providerName: "ReceiverPara", count: 1 });`
// Reported Bug here https://github.com/Moonsong-Labs/moonwall/issues/343

// use a workaround for creating a block
const newBlockPromise = new Promise((resolve, reject) => {
// ws://127.0.0.1:8001 represents the receiver chain endpoint
const ws = new WebSocket("ws://127.0.0.1:8001");

ws.on("open", function open() {
const message = {
jsonrpc: "2.0",
id: 1,
method: "dev_newBlock",
params: [{ count: 1 }],
};

ws.send(JSON.stringify(message));
});

ws.on("message", async function message(data) {
const dataObj = JSON.parse(data.toString());
log("Received message:", dataObj);
resolve(dataObj.result);
});

ws.on("error", function error(error) {
log("Error:", error.toString());
reject(error);
});
});

await newBlockPromise;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (C) Moondance Labs Ltd.
// Copyright 2024 Forecasting Technologies LTD.
//
// This file is part of Zeitgeist.
//
// Zeitgeist is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation, either version 3 of the License, or (at
// your option) any later version.
//
// Zeitgeist is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Zeitgeist. If not, see <https://www.gnu.org/licenses/>.

import { beforeAll, describeSuite, expect } from "@moonwall/cli";
import { KeyringPair } from "@moonwall/util";
import { ApiPromise, Keyring } from "@polkadot/api";
import { canExecuteAtomicSwap } from "tests/common-tests";
import { RuntimeVersion } from "@polkadot/types/interfaces";

const HYDRADX_PARA_ID = 2034;
describeSuite({
id: "CZH",
title: "Chopsticks Zeitgeist HydraDX Atomic Swap Tests",
foundationMethods: "chopsticks",
testCases: function ({ it, context, log }) {
let zeitgeistParaApi: ApiPromise;
let relayApi: ApiPromise;
let hydradxParaApi: ApiPromise;
let alice: KeyringPair;

beforeAll(async () => {
const keyring = new Keyring({ type: "sr25519" });
alice = keyring.addFromUri("//Alice", { name: "Alice default" });
zeitgeistParaApi = context.polkadotJs("ZeitgeistPara");
relayApi = context.polkadotJs("PolkadotRelay");
hydradxParaApi = context.polkadotJs("HydraDXPara");

const paraZeitgeistNetwork = (
zeitgeistParaApi.consts.system.version as unknown as RuntimeVersion
).specName.toString();
expect(paraZeitgeistNetwork, "Para API incorrect").to.contain(
"zeitgeist"
);

const relayNetwork = (
relayApi.consts.system.version as unknown as RuntimeVersion
).specName.toString();
expect(relayNetwork, "Relay API incorrect").to.contain("polkadot");

const paraHydraDXNetwork = (
hydradxParaApi.consts.system.version as unknown as RuntimeVersion
).specName.toString();
expect(paraHydraDXNetwork, "Para API incorrect").to.contain("hydradx");
}, 120000);

it({
id: "T1",
timeout: 60000,
title: "Can execute atomic swap on HydraDX",
test: async () => {
await canExecuteAtomicSwap(
context,
log,
"ZeitgeistPara",
zeitgeistParaApi,
hydradxParaApi,
HYDRADX_PARA_ID
);
},
});
},
});
3 changes: 3 additions & 0 deletions runtime/battery-station/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ use sp_runtime::{
use nimbus_primitives::CanAuthor;
use sp_version::RuntimeVersion;

#[cfg(feature = "parachain")]
use crate::xcm_config::hydra_atomic_swap::AllowHydraDxAtomicSwap;

#[cfg(test)]
pub mod integration_tests;
#[cfg(feature = "parachain")]
Expand Down
Loading