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

feat: ParaToEthereum #487

Merged
merged 17 commits into from
Dec 19, 2024
Prev Previous commit
Next Next commit
add hydration to ethereum end to end tests for xcm v3 and v4
expanded existing e2e tests to cover xcm v3 and v4
updated integration tests
  • Loading branch information
marshacb committed Dec 17, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit 9dce245c15da5628b97c7255625670bea714eed9
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -42,10 +42,6 @@ The below chart is focusing on what directions are supported for constructing as
| Parachain to System | ✅ | ✅ | ✅ |
| Parachain to Ethereum | ❌ | ✅ | ✅ |

## Note on Parachain to Parachain Support

Parachain To Parachain support is currently limited to XCM V2, with the exception of Parachain primary asset tx construction (e.g. MOVR, SDN, etc.).

## Note: System refers to System Parachains like Asset Hub.

## Usage
303 changes: 190 additions & 113 deletions e2e-tests/hydration.ethereum.spec.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,24 @@
import { setupNetworks, testingPairs, withExpect } from '@acala-network/chopsticks-testing';
import { setupNetworks, testingPairs } from '@acala-network/chopsticks-testing';
import { NetworkContext } from '@acala-network/chopsticks-utils';
import { AccountData } from '@polkadot/types/interfaces';
import { setTimeout } from 'timers/promises';
import { afterEach, beforeEach, expect, test } from 'vitest';
import { PalletAssetsAssetAccount } from '@polkadot/types/lookup';
const { checkSystemEvents } = withExpect(expect);

import { AssetTransferApi } from '../src/AssetTransferApi';
import { Option } from '@polkadot/types-codec';
import { AugmentedEvents } from '@polkadot/api/types';

describe('Hydration <> Ethereum', () => {
const ethereumNetworkGlobalConsensusLocation = `{"parents":"2","interior":{"X1":{"GlobalConsensus":{"Ethereum":{"chainId":"1"}}}}}`;
const recipientAddress = '15McF4S5ZsoAJGzdXE3FwSFVjSPoz1Cd7Xj7VQZCb7HULcjx';
const snowbridgeWETHLocation = {
parents: 2,
interior: {
X2: [
{ GlobalConsensus: { Ethereum: { chainId: 1 } } },
{ AccountKey20: { network: null, key: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2' } },
],
},
};

let hydration: NetworkContext;
const ethereumNetworkGlobalConsensusLocation = `{"parents":"2","interior":{"X1":{"GlobalConsensus":{"Ethereum":{"chainId":"1"}}}}}`;
let hydration: NetworkContext;
let polkadotAssetHub: NetworkContext;
let polkadotBridgeHub: NetworkContext;
let polkadotBridgeHub: NetworkContext;

const { alice, alith } = testingPairs();

beforeEach(async () => {
beforeEach(async () => {
const { hydration1, polkadotBridgeHub1, polkadotAssetHub1 } = await setupNetworks({
hydration1: {
hydration1: {
endpoint: 'wss://rpc.hydradx.cloud',
db: './db.sqlite',
port: 8010,
},
polkadotBridgeHub1: {
endpoint: 'wss://bridge-hub-polkadot-rpc.dwellir.com',
@@ -46,100 +32,191 @@ describe('Hydration <> Ethereum', () => {
},
});

hydration = hydration1;
hydration = hydration1;
polkadotBridgeHub = polkadotBridgeHub1;
polkadotAssetHub = polkadotAssetHub1;
}, 200000);
}, 500000);

afterEach(async () => {
afterEach(async () => {
await hydration.teardown();
await polkadotAssetHub.teardown();
await polkadotBridgeHub.teardown();
}, 200000);

test('Transfer Snowbridge WETH from Hydration to Ethereum', async () => {
await hydration.dev.setStorage({
System: {
Account: [
[[alice.address], { providers: 1, data: { free: 10 * 1e12 } }], // HDX
],
},
Tokens: {
Accounts: [
[[alice.address, 0], { free: '50000000000000000000000000' }], // HDX
[[alice.address, 5], { free: '50000000000000000000000000' }], // DOT
[[alice.address, 1000189], { free: '500000000000000000000' }], // Snowbridge WETH
],
},
});

const assetTransferApi = new AssetTransferApi(hydration.api, 'hydradx', 4, {
registryType: 'NPM',
injectedRegistry: {
polkadot: {
2034: {
tokens: [],
assetsInfo: {},
foreignAssetsInfo: {},
poolPairsInfo: {},
specName: 'hydradx',
xcAssetsData: [
{
paraID: 0,
symbol: 'WETH.snow',
decimals: 18,
xcmV1MultiLocation:
'{"v1":{"parents":2,"interior":{"x2":[{"globalConsensus":{"ethereum":{"chainId":1}}},{"accountKey20":{"network":null,"key":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"}}]}}}',
asset: '1000189',
assetHubReserveLocation: '{"parents":"1","interior":{"X1":{"Parachain":"1000"}}}',
},
],
await polkadotAssetHub.teardown();
await polkadotBridgeHub.teardown();
}, 500000);

describe('XCM V3', () => {
const xcmVersion = 3;

test('Transfer Snowbridge WETH from Hydration to Ethereum', async () => {
await hydration.dev.setStorage({
System: {
Account: [
[[alice.address], { providers: 1, data: { free: 10 * 1e12 } }], // HDX
],
},
Tokens: {
Accounts: [
[[alice.address, 0], { free: '50000000000000000000000000' }], // HDX
[[alice.address, 5], { free: '50000000000000000000000000' }], // DOT
[[alice.address, 1000189], { free: '500000000000000000000' }], // Snowbridge WETH
],
},
});

const assetTransferApi = new AssetTransferApi(hydration.api, 'hydradx', xcmVersion, {
registryType: 'NPM',
injectedRegistry: {
polkadot: {
2034: {
tokens: [],
assetsInfo: {},
foreignAssetsInfo: {},
poolPairsInfo: {},
specName: 'hydradx',
xcAssetsData: [
{
paraID: 0,
symbol: 'WETH.snow',
decimals: 18,
xcmV1MultiLocation:
'{"v1":{"parents":2,"interior":{"x2":[{"globalConsensus":{"ethereum":{"chainId":1}}},{"accountKey20":{"network":null,"key":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"}}]}}}',
asset: '1000189',
assetHubReserveLocation: '{"parents":"1","interior":{"X1":{"Parachain":"1000"}}}',
},
],
},
},
},
},
});

const tx = await assetTransferApi.createTransferTransaction(
ethereumNetworkGlobalConsensusLocation,
alith.address,
['DOT', 'WETH.snow'],
['500000000000', '75000000000000'],
{
sendersAddr: alice.address,
format: 'payload',
xcmVersion: 4,
paysWithFeeDest: 'DOT',
},
);

console.log("payload", JSON.stringify(tx));

const extrinsic = assetTransferApi.api.registry.createType('Extrinsic', { method: tx.tx.method }, { version: 4 });

await hydration.api.tx(extrinsic).signAndSend(alice);
await hydration.dev.newBlock();
await polkadotAssetHub.dev.newBlock();

const assetHubEvents = await polkadotAssetHub.api.query.system.events();
for (const event of assetHubEvents) {
console.log("EVENT---", event.toHuman());
}
const foreignAssetsBurnedEvent = assetHubEvents[4];
expect(foreignAssetsBurnedEvent.phase.toString()).toEqual('Finalization');
expect(foreignAssetsBurnedEvent.event.section).toEqual('foreignAssets');
expect(foreignAssetsBurnedEvent.event.method).toEqual('Burned');

const xcmMessageSentEvent = assetHubEvents[6];
expect(xcmMessageSentEvent.phase.toString()).toEqual('Finalization');
expect(xcmMessageSentEvent.event.method).toEqual('XcmpMessageSent');
expect(xcmMessageSentEvent.event.section).toEqual('xcmpQueue');

await polkadotBridgeHub.dev.newBlock();

await checkSystemEvents(polkadotBridgeHub, 'ethereumOutboundQueue', 'balances')
const bridgeHubEvents = await polkadotBridgeHub.api.query.system.events();
for (const event of bridgeHubEvents) {
console.log("BridgeHub Events---", event.toHuman());
}
}, 100000);
});
});

const tx = await assetTransferApi.createTransferTransaction(
ethereumNetworkGlobalConsensusLocation,
alith.address,
['DOT', 'WETH.snow'],
['500000000000', '75000000000000'],
{
sendersAddr: alice.address,
format: 'payload',
xcmVersion,
paysWithFeeDest: 'DOT',
},
);

const extrinsic = assetTransferApi.api.registry.createType('Extrinsic', { method: tx.tx.method }, { version: 4 });

await hydration.api.tx(extrinsic).signAndSend(alice);
await hydration.dev.newBlock();
await polkadotAssetHub.dev.newBlock();

const assetHubEvents = await polkadotAssetHub.api.query.system.events();
const foreignAssetsBurnedEvent = assetHubEvents[4];
expect(foreignAssetsBurnedEvent.phase.toString()).toEqual('Finalization');
expect(foreignAssetsBurnedEvent.event.section).toEqual('foreignAssets');
expect(foreignAssetsBurnedEvent.event.method).toEqual('Burned');

const xcmMessageSentEvent = assetHubEvents[6];
expect(xcmMessageSentEvent.phase.toString()).toEqual('Finalization');
expect(xcmMessageSentEvent.event.method).toEqual('XcmpMessageSent');
expect(xcmMessageSentEvent.event.section).toEqual('xcmpQueue');

await setTimeout(5000);
await polkadotBridgeHub.dev.timeTravel(1);

const bridgeHubEvents = await polkadotBridgeHub.api.query.system.events();
const messageAcceptedEvent = bridgeHubEvents[bridgeHubEvents.length - 3];
expect(messageAcceptedEvent.event.section).toEqual('ethereumOutboundQueue');
expect(messageAcceptedEvent.event.method).toEqual('MessageAccepted');

const messageCommittedEvent = bridgeHubEvents[bridgeHubEvents.length - 1];
expect(messageCommittedEvent.event.section).toEqual('ethereumOutboundQueue');
expect(messageCommittedEvent.event.method).toEqual('MessagesCommitted');
}, 100000);
});
describe('XCM V4', () => {
const xcmVersion = 4;

test('Transfer Snowbridge WETH from Hydration to Ethereum', async () => {
await hydration.dev.setStorage({
System: {
Account: [
[[alice.address], { providers: 1, data: { free: 10 * 1e12 } }], // HDX
],
},
Tokens: {
Accounts: [
[[alice.address, 0], { free: '50000000000000000000000000' }], // HDX
[[alice.address, 5], { free: '50000000000000000000000000' }], // DOT
[[alice.address, 1000189], { free: '500000000000000000000' }], // Snowbridge WETH
],
},
});

const assetTransferApi = new AssetTransferApi(hydration.api, 'hydradx', xcmVersion, {
registryType: 'NPM',
injectedRegistry: {
polkadot: {
2034: {
tokens: [],
assetsInfo: {},
foreignAssetsInfo: {},
poolPairsInfo: {},
specName: 'hydradx',
xcAssetsData: [
{
paraID: 0,
symbol: 'WETH.snow',
decimals: 18,
xcmV1MultiLocation:
'{"v1":{"parents":2,"interior":{"x2":[{"globalConsensus":{"ethereum":{"chainId":1}}},{"accountKey20":{"network":null,"key":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"}}]}}}',
asset: '1000189',
assetHubReserveLocation: '{"parents":"1","interior":{"X1":{"Parachain":"1000"}}}',
},
],
},
},
},
});

const tx = await assetTransferApi.createTransferTransaction(
ethereumNetworkGlobalConsensusLocation,
alith.address,
['DOT', 'WETH.snow'],
['500000000000', '75000000000000'],
{
sendersAddr: alice.address,
format: 'payload',
xcmVersion,
paysWithFeeDest: 'DOT',
},
);

const extrinsic = assetTransferApi.api.registry.createType('Extrinsic', { method: tx.tx.method }, { version: 4 });

await hydration.api.tx(extrinsic).signAndSend(alice);
await hydration.dev.newBlock();
await polkadotAssetHub.dev.newBlock();

const assetHubEvents = await polkadotAssetHub.api.query.system.events();
const foreignAssetsBurnedEvent = assetHubEvents[4];
expect(foreignAssetsBurnedEvent.phase.toString()).toEqual('Finalization');
expect(foreignAssetsBurnedEvent.event.section).toEqual('foreignAssets');
expect(foreignAssetsBurnedEvent.event.method).toEqual('Burned');

const xcmMessageSentEvent = assetHubEvents[6];
expect(xcmMessageSentEvent.phase.toString()).toEqual('Finalization');
expect(xcmMessageSentEvent.event.method).toEqual('XcmpMessageSent');
expect(xcmMessageSentEvent.event.section).toEqual('xcmpQueue');

await setTimeout(5000);
await polkadotBridgeHub.dev.timeTravel(1);

const bridgeHubEvents = await polkadotBridgeHub.api.query.system.events();
const messageAcceptedEvent = bridgeHubEvents[bridgeHubEvents.length - 3];
expect(messageAcceptedEvent.event.section).toEqual('ethereumOutboundQueue');
expect(messageAcceptedEvent.event.method).toEqual('MessageAccepted');

const messageCommittedEvent = bridgeHubEvents[bridgeHubEvents.length - 1];
expect(messageCommittedEvent.event.section).toEqual('ethereumOutboundQueue');
expect(messageCommittedEvent.event.method).toEqual('MessagesCommitted');
}, 100000);
});
});
Loading