Skip to content

Commit

Permalink
Merge pull request #29 from AcalaNetwork/relay-support-any-origin-chain
Browse files Browse the repository at this point in the history
support any origin chain
  • Loading branch information
shunjizhan committed Jun 20, 2023
2 parents b42e7c0 + 9b54406 commit d4d4b5b
Show file tree
Hide file tree
Showing 15 changed files with 129 additions and 74 deletions.
6 changes: 5 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ module.exports = {
functions: 'only-multiline',
}],
'@typescript-eslint/member-delimiter-style': 2,
'multiline-ternary': [2, 'always'],
'@typescript-eslint/no-unused-vars': [1, {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
}],

/* ---------- turn off ---------- */
'@typescript-eslint/no-extra-semi': 0,
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,6 @@ dist

# TernJS port file
.tern-port


src/scripts/.env
4 changes: 0 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,10 @@ services:
# - 3112:3112
environment:
- KARURA_ETH_RPC=https://eth-rpc-karura-testnet.aca-staging.network
- KARURA_NODE_URL=wss://karura-dev.aca-dev.network/rpc/ws
- KARURA_PRIVATE_KEY=efb03e3f4fd8b3d7f9b14de6c6fb95044e2321d6bcb9dfe287ba987920254044
- KARURA_TOKEN_BRIDGE_ADDRESS=0xd11De1f930eA1F7Dd0290Fe3a2e35b9C91AEFb37

- KARURA_ETH_RPC=https://eth-rpc-acala-testnet.aca-staging.network
- ACALA_NODE_URL=wss://acala-dev.aca-dev.network/rpc/ws
- ACALA_PRIVATE_KEY=efb03e3f4fd8b3d7f9b14de6c6fb95044e2321d6bcb9dfe287ba987920254044
- ACALA_TOKEN_BRIDGE_ADDRESS=0xebA00cbe08992EdD08ed7793E07ad6063c807004

- PORT=3111
- TESTNET_MODE=1
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
},
"dependencies": {
"@acala-network/api": "~5.1.0-0",
"@acala-network/asset-router": "~1.0.7",
"@acala-network/asset-router": "~1.0.8",
"@acala-network/eth-providers": "~2.6.8",
"@babel/runtime": "^7.21.5",
"@certusone/wormhole-sdk": "^0.9.12",
Expand Down
8 changes: 0 additions & 8 deletions src/__tests__/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,6 @@ export const TEST_USER_PRIVATE_KEY = '01392cd1a09fc0f4857742f0f0daa3ebd5a0f44a7d
export const TEST_USER_ADDR = new Wallet(TEST_USER_PRIVATE_KEY).address; // 0x0085560b24769dAC4ed057F1B2ae40746AA9aAb6
export const TEST_RELAYER_ADDR = '0xe3234f433914d4cfCF846491EC5a7831ab9f0bb3';

export const BSC_CORE_BRIDGE_ADDRESS = '0x68605AD7b15c732a30b1BbC62BE8F2A509D74b4D';
export const GOERLI_CORE_BRIDGE_ADDRESS = '0x706abc4E45D419950511e474C7B9Ed348A4a716c';
export const KARURA_CORE_BRIDGE_ADDRESS = '0xE4eacc10990ba3308DdCC72d985f2a27D20c7d03';

export const BSC_TOKEN_BRIDGE_ADDRESS = '0x9dcF9D205C9De35334D646BeE44b2D2859712A09';
export const GOERLI_TOKEN_BRIDGE_ADDRESS = '0xF890982f9310df57d00f659cf4fd87e65adEd8d7';
export const KARURA_TOKEN_BRIDGE_ADDRESS = '0xd11De1f930eA1F7Dd0290Fe3a2e35b9C91AEFb37';

export const BSC_USDT_ADDRESS = '0x337610d27c682e347c9cd60bd4b3b107c9d34ddd';
export const BSC_USDC_ADDRESS = '0x861B5C16A2EcED022241072A7beA9D530b99EB6f';
export const KARURA_USDC_ADDRESS = '0xE5BA1e8E6BBbdC8BbC72A58d68E74B13FcD6e4c7';
Expand Down
5 changes: 2 additions & 3 deletions src/__tests__/relay.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { CHAIN_ID_KARURA } from '@certusone/wormhole-sdk';
import { CHAIN_ID_KARURA, CONTRACTS } from '@certusone/wormhole-sdk';
import axios from 'axios';
import { expect } from 'chai';
import {
RELAY_URL,
KARURA_TOKEN_BRIDGE_ADDRESS,
BSC_USDT_ADDRESS,
NOT_SUPPORTED_ADDRESS,
TEST_USER_ADDR,
Expand All @@ -27,7 +26,7 @@ describe('/relay', () => {

expect(result.data).to.includes({
from: TEST_RELAYER_ADDR,
to: KARURA_TOKEN_BRIDGE_ADDRESS,
to: CONTRACTS.TESTNET.karura.token_bridge,
status: 1,
});
});
Expand Down
13 changes: 5 additions & 8 deletions src/__tests__/route.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AcalaJsonRpcProvider, sleep } from '@acala-network/eth-providers';
import { CHAIN_ID_BSC, CHAIN_ID_KARURA, hexToUint8Array, parseSequenceFromLogEth, redeemOnEth } from '@certusone/wormhole-sdk';
import { CHAIN_ID_BSC, CHAIN_ID_KARURA, CONTRACTS, hexToUint8Array, parseSequenceFromLogEth, redeemOnEth } from '@certusone/wormhole-sdk';
import axios, { AxiosError } from 'axios';
import { expect } from 'chai';
import { ContractReceipt, Wallet } from 'ethers';
Expand All @@ -20,10 +20,7 @@ import {
SHOULD_ROUTE_WORMHOLE_URL,
TEST_USER_ADDR,
ROUTE_WORMHOLE_URL,
KARURA_CORE_BRIDGE_ADDRESS,
KARURA_TOKEN_BRIDGE_ADDRESS,
ETH_RPC_BSC,
BSC_TOKEN_BRIDGE_ADDRESS,
} from './consts';
import { GOERLI_USDC, PARA_ID } from '../consts';
import { getSignedVAAFromSequence, transferFromBSCToKarura } from './utils';
Expand All @@ -33,7 +30,7 @@ const KARURA_ETH_RPC = 'https://eth-rpc-karura-testnet.aca-staging.network';
// 0xe3234f433914d4cfCF846491EC5a7831ab9f0bb3
const RELAYER_TEST_KEY = 'efb03e3f4fd8b3d7f9b14de6c6fb95044e2321d6bcb9dfe287ba987920254044';

const encodeXcmDest = (data: any) => {
const encodeXcmDest = (_data: any) => {
// TODO: use api to encode
return '0x03010200a9200100d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d';
};
Expand Down Expand Up @@ -281,20 +278,20 @@ describe('/routeWormhole', () => {

/* ---------- should be able to redeem from eth ---------- */
const depositReceipt = await providerKarura.getTransactionReceipt(txHash);
const sequence = parseSequenceFromLogEth(depositReceipt as ContractReceipt, KARURA_CORE_BRIDGE_ADDRESS);
const sequence = parseSequenceFromLogEth(depositReceipt as ContractReceipt, CONTRACTS.TESTNET.karura.core);
console.log('route to wormhole complete', { sequence }, 'waiting for VAA...');

const signedVAA = await getSignedVAAFromSequence(
sequence,
CHAIN_ID_KARURA,
KARURA_TOKEN_BRIDGE_ADDRESS,
CONTRACTS.TESTNET.karura.token_bridge,
);
console.log({ signedVAA });

const providerBSC = new JsonRpcProvider(ETH_RPC_BSC);
const relayerSignerBSC = new Wallet(RELAYER_TEST_KEY, providerBSC);
const receipt = await redeemOnEth(
BSC_TOKEN_BRIDGE_ADDRESS,
CONTRACTS.TESTNET.bsc.token_bridge,
relayerSignerBSC,
hexToUint8Array(signedVAA),
);
Expand Down
39 changes: 28 additions & 11 deletions src/__tests__/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,37 @@ import {
CHAIN_ID_BSC,
CHAIN_ID_KARURA,
ChainId,
CONTRACTS,
} from '@certusone/wormhole-sdk';
import getSignedVAAWithRetry from '@certusone/wormhole-sdk/lib/cjs/rpc/getSignedVAAWithRetry';
import { BigNumber, ethers } from 'ethers';
import { BigNumber, Contract, Signer, Wallet, ethers } from 'ethers';
import { NodeHttpTransport } from '@improbable-eng/grpc-web-node-http-transport';
import { bridgeToken } from '../utils';
import {
WORMHOLE_GUARDIAN_RPC,
ETH_RPC_BSC,
BSC_CORE_BRIDGE_ADDRESS,
BSC_TOKEN_BRIDGE_ADDRESS,
TEST_USER_PRIVATE_KEY,
} from './consts';
import { formatUnits, parseUnits } from '@ethersproject/units';

export const parseAmount = async (tokenAddr: string, amount: string, provider: any): Promise<BigNumber> => {
const erc20Contract = new ethers.Contract(tokenAddr, ['function decimals() view returns (uint8)'], provider);
const decimals = await erc20Contract.decimals();
const erc20 = new Contract(tokenAddr, ['function decimals() view returns (uint8)'], provider);
const decimals = await erc20.decimals();

return ethers.utils.parseUnits(amount, decimals);
return parseUnits(amount, decimals);
};

export const getErc20Balance = async (tokenAddr: string, signer: Signer): Promise<string> => {
const erc20 = new Contract(tokenAddr, [
'function decimals() view returns (uint8)',
'function balanceOf(address _owner) public view returns (uint256 balance)',
], signer);
const [bal, decimals] = await Promise.all([
erc20.balanceOf(await signer.getAddress()),
erc20.decimals(),
]);

return formatUnits(bal, decimals);
};

export const getSignedVAAFromSequence = async (
Expand All @@ -45,15 +58,19 @@ export const transferFromBSCToKarura = async (
amount: string,
sourceAsset: string,
recipientAddr: string,
wallet?: Wallet,
isMainnet = false,
): Promise<string> => {
const provider = new ethers.providers.JsonRpcProvider(ETH_RPC_BSC);
const signer = new ethers.Wallet(TEST_USER_PRIVATE_KEY, provider);
const signer = wallet ?? new ethers.Wallet(TEST_USER_PRIVATE_KEY, provider);

const parsedAmount = await parseAmount(sourceAsset, amount, provider);
const tokenBridgeAddr = CONTRACTS[isMainnet ? 'MAINNET' : 'TESTNET'].bsc.token_bridge;
const coreBridgeAddr = CONTRACTS[isMainnet ? 'MAINNET' : 'TESTNET'].bsc.core;
const parsedAmount = await parseAmount(sourceAsset, amount, signer);
const { sequence } = await bridgeToken(
signer,
BSC_TOKEN_BRIDGE_ADDRESS,
BSC_CORE_BRIDGE_ADDRESS,
tokenBridgeAddr,
coreBridgeAddr,
recipientAddr,
sourceAsset,
CHAIN_ID_KARURA,
Expand All @@ -64,6 +81,6 @@ export const transferFromBSCToKarura = async (
return getSignedVAAFromSequence(
sequence,
CHAIN_ID_BSC,
BSC_TOKEN_BRIDGE_ADDRESS,
tokenBridgeAddr,
);
};
1 change: 0 additions & 1 deletion src/configureEnv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export type ChainConfig = {
tokenBridgeAddr: string;
feeAddr: string;
factoryAddr: string;
xtokensAddr: string;
};

const isTestnet = Number(process.env.TESTNET_MODE ?? 1);
Expand Down
8 changes: 4 additions & 4 deletions src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,14 @@ const ROUTE_SUPPORTED_CHAINS_AND_ASSETS_DEV = {

const ROUTE_SUPPORTED_CHAINS_AND_ASSETS_PROD = {
[PARA_ID.BASILISK]: [
ROUTER_TOKEN_INFO.KARURA.usdc.srcAddr,
ROUTER_TOKEN_INFO.KARURA.weth.srcAddr,
ROUTER_TOKEN_INFO.KARURA.wbtc.srcAddr,
ROUTER_TOKEN_INFO.KARURA.usdc.originAddr,
ROUTER_TOKEN_INFO.KARURA.weth.originAddr,
ROUTER_TOKEN_INFO.KARURA.wbtc.originAddr,
],
[PARA_ID.CALAMARI]: [
],
[PARA_ID.SHADOW]: [
ROUTER_TOKEN_INFO.KARURA.csm.srcAddr,
ROUTER_TOKEN_INFO.KARURA.csm.originAddr,
],
} as const;

Expand Down
20 changes: 13 additions & 7 deletions src/middlewares/error.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
import { NextFunction, Request, Response } from 'express';
import { ValidationError } from 'yup';

export class RelayerError extends Error {
params: any;

constructor(message: string, params?: any) {
super(message);
this.name = 'RelayerError';
this.params = params;
}
}

export class NoRouteError extends Error {
constructor(message: string) {
super(message);
this.name = 'NoRouteError';
}
}

export class RelayError extends Error {
params: any;

export class RelayError extends RelayerError {
constructor(message: string, params?: any) {
super(message);
this.name = 'RelayError';
this.params = params;
super(message, params);
}
};

export const errorHandler = (err: unknown, req: Request, res: Response, next: NextFunction) => {
export const errorHandler = (err: unknown, req: Request, res: Response, _next: NextFunction) => {
if (err instanceof ValidationError) {
res.status(400).json({
msg: 'invalid request params!',
Expand Down
25 changes: 25 additions & 0 deletions src/scripts/bridge-to-router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Wallet } from 'ethers';
import dotenv from 'dotenv';
import path from 'path';
import { JsonRpcProvider } from '@ethersproject/providers';

import { getErc20Balance, transferFromBSCToKarura } from '../__tests__/utils';

dotenv.config({ path: path.join(__dirname, '.env') });
const key = process.env.KEY;
if (!key) throw new Error('KEY is not defined');

(async () => {
const USDC_BSC = '0xB04906e95AB5D797aDA81508115611fee694c2b3';
const routerAddr = '0x7745CAf117104FABCF5b0e4815184d7c3b2f99D9';
const ETH_RPC_BSC = 'https://endpoints.omniatech.io/v1/bsc/mainnet/public';

const provider = new JsonRpcProvider(ETH_RPC_BSC);
const wallet = new Wallet(key, provider);

const bal = await getErc20Balance(USDC_BSC, wallet);
console.log(`usdc balance: ${bal}`);

const signedVAA = await transferFromBSCToKarura('0.05', USDC_BSC, routerAddr, wallet, true);
console.log({ signedVAA });
})();
8 changes: 3 additions & 5 deletions src/scripts/manual-relay.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { CHAIN_ID_KARURA } from '@certusone/wormhole-sdk';
import { ChainID } from '@certusone/wormhole-sdk/lib/cjs/proto/publicrpc/v1/publicrpc';
import { CHAIN_ID_KARURA, ChainId } from '@certusone/wormhole-sdk';
import axios from 'axios';
import { ChainConfigInfo } from '../configureEnv';
import { relayEVM } from '../utils';

// const KARURA_ETH_RPC='https://eth-rpc-karura-testnet.aca-staging.network';
Expand All @@ -18,7 +16,7 @@ import { relayEVM } from '../utils';
// walletPrivateKey: '0xefb03e3f4fd8b3d7f9b14de6c6fb95044e2321d6bcb9dfe287ba987920254044',
// };

const manualRelayVAA = async (relayerUrl: string, targetChain: ChainID, signedVAA: string) => {
const manualRelayVAA = async (relayerUrl: string, targetChain: ChainId, signedVAA: string) => {
console.log(`relaying with ${relayerUrl}`);
try {
const res = await axios.post(`${relayerUrl}/relay`, {
Expand Down Expand Up @@ -51,4 +49,4 @@ const manualRelayVAA = async (relayerUrl: string, targetChain: ChainID, signedVA
const vaa = '010000000001000e728dc328a104bfd405af83768ffb09fc178c23850595fd62329f0ce6a8c5ff74f08ffb8a50119990302599722eb1dbb66a4c0f67aaf38c8518bf9b1a928b32006336aec84a8d000000040000000000000000000000009dcf9d205c9de35334d646bee44b2d2859712a0900000000000007260f010000000000000000000000000000000000000000000000000000000000989680000000000000000000000000337610d27c682e347c9cd60bd4b3b107c9d34ddd0004000000000000000000000000e3234f433914d4cfcf846491ec5a7831ab9f0bb3000b0000000000000000000000000000000000000000000000000000000000000000';

await manualRelayVAA(relayerUrl, CHAIN_ID_KARURA, vaa);
})();
})();
31 changes: 25 additions & 6 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@ import {
tryNativeToHexString,
transferFromEth,
parseSequenceFromLogEth,
CHAIN_ID_ETH,
tryHexToNativeString,
parseVaa,
CHAIN_ID_KARURA,
} from '@certusone/wormhole-sdk';
import { Bridge__factory } from '@certusone/wormhole-sdk/lib/cjs/ethers-contracts';
import { ERC20__factory, FeeRegistry__factory } from '@acala-network/asset-router/dist/typechain-types';
import { CHAIN, CHAIN_NAME_TO_WORMHOLE_CHAIN_ID, ROUTER_TOKEN_INFO } from '@acala-network/asset-router/dist/consts';
import { BigNumber, BigNumberish, ContractReceipt, ethers, Signer, Wallet } from 'ethers';
import { AcalaJsonRpcProvider } from '@acala-network/eth-providers';

import { ChainConfig } from './configureEnv';
import { RELAYER_SUPPORTED_ADDRESSES_AND_THRESHOLDS } from './consts';
import { logger } from './logger';
import { RelayAndRouteParams } from './route';
import { ERC20__factory, FeeRegistry__factory } from '@acala-network/asset-router/dist/typechain-types';
import { RelayError } from './middlewares/error';
import { RelayError, RelayerError } from './middlewares/error';

interface VaaInfo {
amount: bigint;
Expand Down Expand Up @@ -162,9 +164,26 @@ export const getRouterChainTokenAddr = async (originAddr: string, chainInfo: Cha
const signer = await getSigner(chainInfo);
const tokenBridge = Bridge__factory.connect(chainInfo.tokenBridgeAddr, signer);

const routerChain = chainInfo.chainId === CHAIN_ID_KARURA
? CHAIN.KARURA
: CHAIN.ACALA;

const originTokenInfo = Object.entries(ROUTER_TOKEN_INFO[routerChain])
.map(info => info[1])
.find(tokenInfo => tokenInfo.originAddr === originAddr);

if (!originTokenInfo) {
throw new RelayerError(
'cannot find originTokenInfo',
{ originAddr, chainId: chainInfo.chainId },
);
}

const originChainId = CHAIN_NAME_TO_WORMHOLE_CHAIN_ID[originTokenInfo.originChain];

return tokenBridge.wrappedAsset(
CHAIN_ID_ETH, // TODO: from other chains
Buffer.from(tryNativeToHexString(originAddr, CHAIN_ID_ETH), 'hex'),
originChainId,
Buffer.from(tryNativeToHexString(originAddr, originChainId), 'hex'),
);
};

Expand All @@ -186,7 +205,7 @@ export const bridgeToken = async (
}
const vaaCompatibleAddr = hexToUint8Array(hexString);

console.log('sending bridging tx...');
console.log(`sending bridging tx with wallet ${signer.address} and amount ${amount} ...`);
const receipt = await transferFromEth(
tokenBridgeAddr,
signer,
Expand Down
Loading

0 comments on commit d4d4b5b

Please sign in to comment.