Skip to content

Commit

Permalink
batch relay and route
Browse files Browse the repository at this point in the history
  • Loading branch information
shunjizhan committed Aug 3, 2023
1 parent 3b267a2 commit 68c3242
Show file tree
Hide file tree
Showing 13 changed files with 353 additions and 47 deletions.
7 changes: 4 additions & 3 deletions .env.test
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
### testnet urls
# relayer addr: 0xe3234f433914d4cfcf846491ec5a7831ab9f0bb3
# relayer substrate addr: 5EkD8nyaXBmhhhuzipcQDWnLU2f1WwrqMob4xm8zZFTz7bZ3

# KARURA_ETH_RPC=http://localhost:8545
KARURA_ETH_RPC=https://eth-rpc-karura-testnet-dev.aca-dev.network
# KARURA_ETH_RPC=https://eth-rpc-karura-testnet.aca-staging.network
KARURA_ETH_RPC=https://eth-rpc-karura-testnet.aca-staging.network
KARURA_PRIVATE_KEY=efb03e3f4fd8b3d7f9b14de6c6fb95044e2321d6bcb9dfe287ba987920254044
KARURA_MNEMONIC='fox sight canyon orphan hotel grow hedgehog build bless august weather swarm'

ACALA_ETH_RPC=https://eth-rpc-acala-testnet.aca-staging.network
ACALA_PRIVATE_KEY=efb03e3f4fd8b3d7f9b14de6c6fb95044e2321d6bcb9dfe287ba987920254044
ACALA_MNEMONIC='fox sight canyon orphan hotel grow hedgehog build bless august weather swarm'

PORT=3111
TESTNET_MODE=1
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"dependencies": {
"@acala-network/api": "~5.1.0-0",
"@acala-network/asset-router": "~1.0.10",
"@acala-network/bodhi": "^2.7.5",
"@acala-network/eth-providers": "~2.6.8",
"@babel/runtime": "^7.21.5",
"@certusone/wormhole-sdk": "^0.9.12",
Expand Down
84 changes: 84 additions & 0 deletions src/__tests__/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
getBasiliskUsdcBalance,
mockXcmToRouter,
relayAndRoute,
relayAndRouteBatch,
routeWormhole,
routeXcm,
shouldRouteWormhole,
Expand Down Expand Up @@ -173,6 +174,89 @@ describe('/relayAndRoute', () => {
});
});

describe('/relayAndRouteBatch', () => {
const api = new ApiPromise({ provider: new WsProvider(BASILISK_TESTNET_NODE_URL) });
const usdc = ERC20__factory.connect(KARURA_USDC_ADDRESS, providerKarura);

beforeAll(async () => { await api.isReady; });
afterAll(async () => { await api.disconnect(); });

it('when should route', async () => {
const routeArgs = {
dest,
destParaId: PARA_ID.BASILISK,
originAddr: GOERLI_USDC,
};

const curBalUser = await getBasiliskUsdcBalance(api, destAddr);
const curBalRelayer = (await usdc.balanceOf(TEST_ADDR_RELAYER)).toBigInt();
console.log({ curBalUser, curBalRelayer });

const { routerAddr } = (await shouldRouteXcm(routeArgs)).data;
console.log({ routerAddr });

const signedVAA = await transferFromFujiToKaruraTestnet('0.001', FUJI_TOKEN.USDC, routerAddr);
console.log({ signedVAA });

const relayAndRouteArgs = {
...routeArgs,
signedVAA,
};

const res = await relayAndRouteBatch(relayAndRouteArgs);
console.log(`route finished! txHash: ${res.data}`);

console.log('waiting for token to arrive at basilisk ...');
await sleep(25000);

const afterBalUser = await getBasiliskUsdcBalance(api, destAddr);
const afterBalRelayer = (await usdc.balanceOf(TEST_ADDR_RELAYER)).toBigInt();
console.log({ afterBalUser, afterBalRelayer });

expect(afterBalRelayer - curBalRelayer).to.eq(200n);
expect(afterBalUser - curBalUser).to.eq(800n); // 1000 - 200
expect((await usdc.balanceOf(routerAddr)).toBigInt()).to.eq(0n);

// router should be destroyed
const routerCode = await providerKarura.getCode(routerAddr);
expect(routerCode).to.eq('0x');
});

it.only('when should not route', async () => {
const routeArgs = {
dest,
destParaId: PARA_ID.BASILISK,
originAddr: GOERLI_USDC,
};

try {
await relayAndRouteBatch({
...routeArgs,

// bridge 0.000001 USDC
signedVAA: '010000000001004ba23fa55bcb370773bdba954523ea305f96f814f51ce259fb327b57d985eec86a0f69bf46c4bb444d09b1d70e3b2aaa434639ec3ae93f5d0671b3e38055cf3501648726ae4d36000000040000000000000000000000009dcf9d205c9de35334d646bee44b2d2859712a0900000000000012580f01000000000000000000000000000000000000000000000000000000000000000100000000000000000000000007865c6e87b9f70255377e024ace6630c1eaa37f00020000000000000000000000008341cd8b7bd360461fe3ce01422fe3e24628262f000b0000000000000000000000000000000000000000000000000000000000000000',
});

expect.fail('relayAndRouteBatch did not throw when it should!');
} catch (err) {
expectError(err, 'token amount too small to relay', 500);
}

try {
await relayAndRouteBatch({
...routeArgs,

// bridge 10 TKN
signedVAA: '01000000000100689102e0be499c096acd1ac49a34216a32f8c19f1b053e0ff47e0a994ea302b50261b4c1feab4ae933fa8de83bd86efde12cc3b82da00b9b8ccc2d502e145ad2006487368e8f3a010000040000000000000000000000009dcf9d205c9de35334d646bee44b2d2859712a09000000000000125c0f01000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000009c8bcccdb17545658c6b84591567c6ed9b4d55bb000b0000000000000000000000008341cd8b7bd360461fe3ce01422fe3e24628262f000b0000000000000000000000000000000000000000000000000000000000000000',
});

expect.fail('relayAndRouteBatch did not throw when it should!');
} catch (err) {
expectError(err, 'unsupported token', 500);
}
});
});

describe('/routeWormhole', () => {
const usdcK = ERC20__factory.connect(KARURA_USDC_ADDRESS, providerKarura);
const usdcF = ERC20__factory.connect(FUJI_TOKEN.USDC, new JsonRpcProvider(ETH_RPC.FUJI));
Expand Down
4 changes: 4 additions & 0 deletions src/__tests__/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ export const relayAndRoute = process.env.COVERAGE
? _supertestPost(RELAYER_API.RELAY_AND_ROUTE)
: _axiosPost(RELAYER_URL.RELAY_AND_ROUTE);

export const relayAndRouteBatch = process.env.COVERAGE
? _supertestPost(RELAYER_API.RELAY_AND_ROUTE_BATCH)
: _axiosPost(RELAYER_URL.RELAY_AND_ROUTE_BATCH);

export const routeWormhole = process.env.COVERAGE
? _supertestPost(RELAYER_API.ROUTE_WORMHOLE)
: _axiosPost(RELAYER_URL.ROUTE_WORMHOLE);
Expand Down
86 changes: 66 additions & 20 deletions src/api/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ChainId, tryNativeToHexString } from '@certusone/wormhole-sdk';
import { Bridge__factory } from '@certusone/wormhole-sdk/lib/cjs/ethers-contracts';
import { ChainId, hexToUint8Array, tryNativeToHexString } from '@certusone/wormhole-sdk';
import { Factory__factory } from '@acala-network/asset-router/dist/typechain-types';
import { JsonRpcProvider } from '@ethersproject/providers';
import { Signer } from 'ethers';
import { WormholeInstructionsStruct, XcmInstructionsStruct } from '@acala-network/asset-router/dist/typechain-types/src/Factory';

Expand All @@ -10,32 +12,19 @@ import {
ZERO_ADDR,
} from '../consts';
import {
RelayAndRouteParams,
RouteParamsWormhole,
RouteParamsXcm,
checkShouldRelayBeforeRouting,
getApi,
getEthExtrinsic,
getRouterChainTokenAddr,
getSigner,
logger,
relayEVM,
sendExtrinsic,
} from '../utils';

interface RouteParamsBase {
originAddr: string; // origin token address
}

export interface RouteParamsWormhole extends RouteParamsBase {
targetChainId: string;
destAddr: string; // recepient address in hex
fromParaId: string; // from parachain id in number
}

export interface RouteParamsXcm extends RouteParamsBase {
destParaId: string; // TODO: maybe can decode from dest
dest: string; // xcm encoded dest in hex
}

export interface RelayAndRouteParams extends RouteParamsXcm {
signedVAA: string;
}

interface RouteProps {
routerAddr: string;
chainConfig: ChainConfig;
Expand Down Expand Up @@ -165,6 +154,63 @@ export const routeXcm = async (routeParamsXcm: RouteParamsXcm): Promise<string>
return receipt.transactionHash;
};

export const _getRelayTx = async (params: RelayAndRouteParams) => {
const routerChainId = DEST_PARA_ID_TO_ROUTER_WORMHOLE_CHAIN_ID[params.destParaId] as ChainId;
const { chainConfig, signer } = await _prepareRoute(routerChainId);
await checkShouldRelayBeforeRouting(params, chainConfig, signer);

const bridge = Bridge__factory.connect(chainConfig.tokenBridgeAddr, signer);
return await bridge.populateTransaction.completeTransfer(hexToUint8Array(params.signedVAA));
};

export const _getRouteTx = async (routeParamsXcm: RelayAndRouteParams) => {
const { chainConfig, signer } = await prepareRouteXcm(routeParamsXcm);

const xcmInstruction: XcmInstructionsStruct = {
dest: routeParamsXcm.dest,
weight: '0x00',
};
const factory = Factory__factory.connect(chainConfig.factoryAddr, signer);
const routerChainTokenAddr = await getRouterChainTokenAddr(routeParamsXcm.originAddr, chainConfig);

return await factory.connect(signer).populateTransaction.deployXcmRouterAndRoute(
chainConfig.feeAddr,
xcmInstruction,
routerChainTokenAddr,
);
};

export const relayAndRouteBatch = async (params: RelayAndRouteParams): Promise<string> => {
const [relayTx, routeTx] = await Promise.all([
_getRelayTx(params),
_getRouteTx(params),
]);

logger.debug({ relayTx, routeTx });

const routerChainId = DEST_PARA_ID_TO_ROUTER_WORMHOLE_CHAIN_ID[params.destParaId] as ChainId;
const { chainConfig } = await _prepareRoute(routerChainId);

const { addr, api } = await getApi(chainConfig);
const signer = await getSigner(chainConfig);
const provider = signer.provider! as JsonRpcProvider;

const relayExtrinsic = getEthExtrinsic(api, relayTx);
const routeExtrinsic = getEthExtrinsic(api, routeTx);

// await Promise.all([
// relayExtrinsic.signAsync(addr),
// routeExtrinsic.signAsync(addr),
// ]);

const batchTx = api.tx.utility.batchAll([relayExtrinsic, routeExtrinsic]);
await batchTx.signAsync(addr);

const txHash = await sendExtrinsic(batchTx, provider);

return txHash;
};

export const relayAndRoute = async (params: RelayAndRouteParams): Promise<[string, string]> => {
const routerChainId = DEST_PARA_ID_TO_ROUTER_WORMHOLE_CHAIN_ID[params.destParaId] as ChainId;
const { chainConfig, signer } = await _prepareRoute(routerChainId);
Expand Down
2 changes: 2 additions & 0 deletions src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export const RELAYER_API = {
SHOULD_ROUTE_WORMHOLE: '/shouldRouteWormhole',
ROUTE_WORMHOLE: '/routeWormhole',
RELAY_AND_ROUTE: '/relayAndRoute',
RELAY_AND_ROUTE_BATCH: '/relayAndRouteBatch',

NO_ROUTE: '/noRoute',
VERSION: '/version',
Expand All @@ -65,6 +66,7 @@ export const RELAYER_URL = {
SHOULD_ROUTE_WORMHOLE: `${RELAYER_BASE_URL}${RELAYER_API.SHOULD_ROUTE_WORMHOLE}`,
ROUTE_WORMHOLE: `${RELAYER_BASE_URL}${RELAYER_API.ROUTE_WORMHOLE}`,
RELAY_AND_ROUTE: `${RELAYER_BASE_URL}${RELAYER_API.RELAY_AND_ROUTE}`,
RELAY_AND_ROUTE_BATCH: `${RELAYER_BASE_URL}${RELAYER_API.RELAY_AND_ROUTE_BATCH}`,

NO_ROUTE: `${RELAYER_BASE_URL}${RELAYER_API.NO_ROUTE}`,
VERSION: `${RELAYER_BASE_URL}${RELAYER_API.VERSION}`,
Expand Down
6 changes: 6 additions & 0 deletions src/middlewares/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ export const errorHandler = (err: unknown, req: Request, res: Response, _next: N
error: err.message,
params: err.params,
});
} else if (err instanceof RelayerError) {
res.status(500).json({
msg: 'an error occurred!',
error: err.message,
params: err.params,
});
} else if (err instanceof Error) {
res.status(500).json({
msg: `internal server error: ${err.name}`,
Expand Down
17 changes: 11 additions & 6 deletions src/middlewares/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@ import { NextFunction, Request, Response } from 'express';
import { Schema } from 'yup';

import { NoRouteError } from './error';
import {
logger,
relayAndRouteSchema,
routeWormholeSchema,
routeXcmSchema,
} from '../utils';
import { logger } from '../utils';
import {
relayAndRoute,
relayAndRouteBatch,
routeWormhole,
routeXcm,
shouldRouteWormhole,
shouldRouteXcm,
} from '../api/route';
import {
relayAndRouteSchema,
routeWormholeSchema,
routeXcmSchema,
} from '../utils/validate';

interface RouterConfig {
schema: Schema;
Expand Down Expand Up @@ -50,6 +51,10 @@ const ROUTER_CONFIGS: {
schema: relayAndRouteSchema,
handler: relayAndRoute,
},
'/relayAndRouteBatch': {
schema: relayAndRouteSchema,
handler: relayAndRouteBatch,
},
},
};

Expand Down
15 changes: 13 additions & 2 deletions src/utils/configureEnv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ export type RelayerEnvironment = {
export type ChainConfig = {
chainId: ROUTER_CHAIN_ID;
ethRpc: string;
nodeUrl: string;
walletPrivateKey: string;
walletMnemonic: string;
tokenBridgeAddr: string;
feeAddr: string;
factoryAddr: string;
Expand All @@ -22,7 +24,7 @@ export type ChainConfig = {

const isTestnet = Boolean(Number(process.env.TESTNET_MODE ?? 1));

export function validateEnvironment(): RelayerEnvironment {
export function prepareEnvironment(): RelayerEnvironment {
const supportedChains: ChainConfig[] = [];
supportedChains.push(configKarura());
supportedChains.push(configAcala());
Expand All @@ -34,6 +36,8 @@ function configKarura(): ChainConfig {
const requiredEnvVars = [
'KARURA_ETH_RPC',
'KARURA_PRIVATE_KEY',
'KARURA_MNEMONIC',
'KARURA_NODE_URL',
];

for (const envVar of requiredEnvVars) {
Expand All @@ -50,7 +54,9 @@ function configKarura(): ChainConfig {
return {
chainId: CHAIN_ID_KARURA,
ethRpc: process.env.KARURA_ETH_RPC!,
nodeUrl: process.env.KARURA_NODE_URL!,
walletPrivateKey: process.env.KARURA_PRIVATE_KEY!,
walletMnemonic: process.env.KARURA_MNEMONIC!,
isTestnet,
...addresses,
};
Expand All @@ -60,6 +66,8 @@ function configAcala(): ChainConfig {
const requiredEnvVars = [
'ACALA_ETH_RPC',
'ACALA_PRIVATE_KEY',
'ACALA_MNEMONIC',
'ACALA_NODE_URL',
];

for (const envVar of requiredEnvVars) {
Expand All @@ -76,14 +84,17 @@ function configAcala(): ChainConfig {
return {
chainId: CHAIN_ID_ACALA,
ethRpc: process.env.ACALA_ETH_RPC!,
nodeUrl: process.env.ACALA_NODE_URL!,
walletPrivateKey: process.env.ACALA_PRIVATE_KEY!,
walletMnemonic: process.env.ACALA_MNEMONIC!,
isTestnet,
...addresses,
};
}

const env: RelayerEnvironment = validateEnvironment();
const env: RelayerEnvironment = prepareEnvironment();

// TODO: maybe export signer and api directly from here?
export const getChainConfig = (chainId: ChainId): ChainConfig | undefined => (
env.supportedChains.find((x) => x.chainId === chainId)
);
2 changes: 1 addition & 1 deletion src/utils/relay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {

import { ChainConfig } from './configureEnv';
import { RELAYER_SUPPORTED_ADDRESSES_AND_THRESHOLDS } from '../consts';
import { RelayAndRouteParams } from '../api/route';
import { RelayAndRouteParams } from './validate';
import { RelayError } from '../middlewares/error';
import { VaaInfo, parseVaaPayload } from './wormhole';
import { getSigner } from './utils';
Expand Down
Loading

0 comments on commit 68c3242

Please sign in to comment.