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

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
**/lib/*
**/build/*


1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const config = require('@substrate/dev/config/eslint');

module.exports = {
...config,
parser: "@typescript-eslint/parser",
parserOptions: {
project: '**/tsconfig.json',
},
Expand Down
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,7 @@ The below chart is focusing on what directions are supported for constructing as
| Parachain to Parachain | ✅ | ✅ | ✅ |
| Parachain to Relay | ✅ | ✅ | ✅ |
| Parachain to System | ✅ | ✅ | ✅ |

## 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.).
| Parachain to Ethereum | ❌ | ✅ | ✅ |

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

Expand Down
222 changes: 222 additions & 0 deletions e2e-tests/hydration.ethereum.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import { setupNetworks, testingPairs } from '@acala-network/chopsticks-testing';
import { NetworkContext } from '@acala-network/chopsticks-utils';
import { setTimeout } from 'timers/promises';
import { afterEach, beforeEach, expect, test } from 'vitest';

import { AssetTransferApi } from '../src/AssetTransferApi';

describe('Hydration <> Ethereum', () => {
const ethereumNetworkGlobalConsensusLocation = `{"parents":"2","interior":{"X1":{"GlobalConsensus":{"Ethereum":{"chainId":"1"}}}}}`;
let hydration: NetworkContext;
let polkadotAssetHub: NetworkContext;
let polkadotBridgeHub: NetworkContext;

const { alice, alith } = testingPairs();

beforeEach(async () => {
const { hydration1, polkadotBridgeHub1, polkadotAssetHub1 } = await setupNetworks({
hydration1: {
endpoint: 'wss://rpc.hydradx.cloud',
db: './db.sqlite',
port: 8010,
},
polkadotBridgeHub1: {
endpoint: 'wss://bridge-hub-polkadot-rpc.dwellir.com',
db: './db.sqlite',
port: 8011,
},
polkadotAssetHub1: {
endpoint: 'wss://asset-hub-polkadot-rpc.dwellir.com',
db: './db.sqlite',
port: 8012,
},
});

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

afterEach(async () => {
await hydration.teardown();
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,
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
Loading