Skip to content

Commit

Permalink
polish
Browse files Browse the repository at this point in the history
  • Loading branch information
shunjizhan committed Mar 27, 2024
1 parent 6bad44f commit c053c8c
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 57 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -426,10 +426,10 @@ data: {
=> route id
{
data: 'homa-0'
data: 'homa-1711514333845'
}
GET /routeStatus?routeId=homa-0
GET /routeStatus?routeId=homa-1711514333845
=> route status
{ data: { status: 0 } } // waiting for token
{ data: { status: 1 } } // token arrived, routing
Expand Down
3 changes: 2 additions & 1 deletion scripts/e2e-prod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ const routeWormhole = async (chainId: ROUTER_CHAIN_ID) => {
const routerAddr = res.data.data.routerAddr;
console.log({ routerAddr }); // 0x0FF0e74513fE82A0c4830309811f1aC1e5d06055 / 0xAAbc44730778B9Dc76fA0B1E65eBeF28D8B7B086

const provider = new AcalaJsonRpcProvider(chainId === CHAIN_ID_KARURA ? ETH_RPC.KARURA : ETH_RPC.ACALA);
const ethRpcUrl = chainId === CHAIN_ID_KARURA ? ETH_RPC.KARURA : ETH_RPC.ACALA;
const provider = new AcalaJsonRpcProvider(ethRpcUrl);
const wallet = new Wallet(key, provider);

const token = chainId === CHAIN_ID_KARURA
Expand Down
14 changes: 7 additions & 7 deletions src/__tests__/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { JsonRpcProvider } from '@ethersproject/providers';
import { ONE_ACA, almostEq, toHuman } from '@acala-network/asset-router/dist/utils';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { encodeAddress } from '@polkadot/util-crypto';
import { formatEther, parseEther, parseUnits } from 'ethers/lib/utils';
import { parseEther, parseUnits } from 'ethers/lib/utils';
import assert from 'assert';

import {
Expand Down Expand Up @@ -469,12 +469,12 @@ describe.skip('/routeHoma', () => {

// user should receive LDOT
const routingFee = await fee.getFee(DOT);
const exchangeRate = parseEther((1 / Number(formatEther(await homa.getExchangeRate()))).toString()); // 10{18} DOT => ? LDOT
const expectedLdot = parsedStakeAmount.sub(routingFee).mul(exchangeRate).div(ONE_ACA);
const exchangeRate = await homa.getExchangeRate(); // 10{18} LDOT => ? DOT
const expectedLdot = parsedStakeAmount.sub(routingFee).mul(ONE_ACA).div(exchangeRate);
const ldotReceived = bal1.userBalLdot.sub(bal0.userBalLdot);

expect(almostEq(expectedLdot, ldotReceived)).to.be.true;
// expect(bal0.userBalDot.sub(bal1.userBalDot)).to.eq(parsedStakeAmount); // TODO: why this has a super slight off?
expect(bal0.userBalDot.sub(bal1.userBalDot).toBigInt()).to.eq(parsedStakeAmount.toBigInt());

// relayer should receive DOT fee
expect(bal1.relayerBalDot.sub(bal0.relayerBalDot).toBigInt()).to.eq(routingFee.toBigInt());
Expand Down Expand Up @@ -543,12 +543,12 @@ describe.skip('/routeHoma', () => {

// user should receive LDOT
const routingFee = await fee.getFee(DOT);
const exchangeRate = parseEther((1 / Number(formatEther(await homa.getExchangeRate()))).toString()); // 10{18} DOT => ? LDOT
const expectedLdot = parsedStakeAmount.sub(routingFee).mul(exchangeRate).div(ONE_ACA);
const exchangeRate = await homa.getExchangeRate(); // 10{18} LDOT => ? DOT
const expectedLdot = parsedStakeAmount.sub(routingFee).mul(ONE_ACA).div(exchangeRate);
const ldotReceived = bal1.userBalLdot.sub(bal0.userBalLdot);

expect(almostEq(expectedLdot, ldotReceived)).to.be.true;
// expect(bal0.userBalDot.sub(bal1.userBalDot)).to.eq(parsedStakeAmount); // TODO: why this has a super slight off?
expect(bal0.userBalDot.sub(bal1.userBalDot).toBigInt()).to.eq(parsedStakeAmount.toBigInt());

// relayer should receive DOT fee
expect(bal1.relayerBalDot.sub(bal0.relayerBalDot).toBigInt()).to.eq(routingFee.toBigInt());
Expand Down
44 changes: 26 additions & 18 deletions src/api/homa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
getChainConfig,
getMainnetChainId,
routeStatusParams,
runWithRetry,
toAddr32,
} from '../utils';

Expand Down Expand Up @@ -66,10 +67,10 @@ export enum RouteStatus {

interface RouteInfo {
status: RouteStatus;
[key: string]: any;
txHash?: string;
err?: any;
}

let routeReqId = 0;
const routeTracker: Record<number, RouteInfo> = {};
export const routeHomaAuto = async (params: RouteParamsHoma) => {
const { chain, destAddr } = params;
Expand All @@ -79,15 +80,15 @@ export const routeHomaAuto = async (params: RouteParamsHoma) => {
throw new RouteError(msg, params);
}

const reqId = `homa-${routeReqId++}`;
routeTracker[reqId] = { status: RouteStatus.Waiting };
const reqId = `homa-${Date.now()}`;
const tracker = routeTracker[reqId] = { status: RouteStatus.Waiting } as RouteInfo;

const { homaFactory, feeAddr, routeToken, wallet } = await prepareRouteHoma(chain);
const dotOrKsm = ERC20__factory.connect(routeToken, wallet);

const waitForToken = new Promise<void>((resolve, reject) => {
const id = setInterval(async () => {
const bal = await dotOrKsm.balanceOf(routerAddr!); // TODO: probably should add retry here to make sure this won't throw
const bal = await runWithRetry(() => dotOrKsm.balanceOf(routerAddr!));
if (bal.gt(0)) {
clearInterval(id);
resolve();
Expand All @@ -102,30 +103,37 @@ export const routeHomaAuto = async (params: RouteParamsHoma) => {
});

waitForToken.then(async () => {
routeTracker[reqId] = { status: RouteStatus.Routing };
tracker.status = RouteStatus.Routing;

let tx: ContractTransaction;
try {
tx = await homaFactory.deployHomaRouterAndRoute(feeAddr, toAddr32(destAddr), routeToken);
tx = await runWithRetry(
() => homaFactory.deployHomaRouterAndRoute(feeAddr, toAddr32(destAddr), routeToken),
{ retry: 3, interval: 20 }
);
} catch (err) {
routeTracker[reqId] = { status: RouteStatus.Failed, err: err.message };
tracker.status = RouteStatus.Failed;
tracker.err = err.message;
return;
}
const txhash = tx.hash;

routeTracker[reqId] = { status: RouteStatus.Confirming, txhash };
const receipt = await tx.wait();
tracker.txHash = tx.hash;
tracker.status = RouteStatus.Confirming;

routeTracker[reqId] = receipt.status === 0
? { status: RouteStatus.Failed, txhash }
: { status: RouteStatus.Complete, txhash };
const receipt = await runWithRetry(() => tx.wait(), { retry: 3, interval: 10 });
tracker.status = receipt.status === 0
? RouteStatus.Failed
: RouteStatus.Complete;
}).catch(err => {
routeTracker[reqId] = err === 'timeout'
? { status: RouteStatus.Timeout }
: { status: RouteStatus.Failed, err: err.message };
if (err === 'timeout') {
tracker.status = RouteStatus.Timeout;
} else {
tracker.status = RouteStatus.Failed;
tracker.err = err;
}
});

// clear after 7 days to avoid memory blow
// clear record after 7 days to avoid memory blow
setTimeout(() => delete routeTracker[reqId], 7 * 24 * 60 * 60 * 1000);

return reqId;
Expand Down
2 changes: 1 addition & 1 deletion src/utils/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ export class RouteError extends RelayerError {
super(message, params);
this.name = 'RouteError';
}
};
};
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './validate';
export * from './wormhole';
export * from './address';
export * from './error';
export * from './token';
2 changes: 1 addition & 1 deletion src/utils/relay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import {
import { ChainConfig } from './configureEnv';
import { RELAYER_SUPPORTED_ADDRESSES_AND_THRESHOLDS } from '../consts';
import { RelayAndRouteParams } from './validate';
import { RelayError } from './error';
import { VaaInfo, parseVaaPayload } from './wormhole';
import { logger } from './logger';
import { RelayError } from './error';

interface ShouldRelayResult {
shouldRelay: boolean;
Expand Down
29 changes: 29 additions & 0 deletions src/utils/token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { BigNumber, Signer } from 'ethers';
import { ERC20__factory } from '@acala-network/asset-router/dist/typechain-types';
import { Provider } from '@ethersproject/abstract-provider';
import { formatUnits, parseUnits } from 'ethers/lib/utils';

export const parseAmount = async (
tokenAddr: string,
amount: string,
signerOrProvider: Signer | Provider,
): Promise<BigNumber> => {
const token = ERC20__factory.connect(tokenAddr, signerOrProvider);
const decimals = await token.decimals();

return parseUnits(amount, decimals);
};

export const getTokenBalance = async (
tokenAddr: string,
signer: Signer,
): Promise<string> => {
const token = ERC20__factory.connect(tokenAddr, signer);

const [bal, decimals] = await Promise.all([
token.balanceOf(await signer.getAddress()),
token.decimals(),
]);

return formatUnits(bal, decimals);
};
50 changes: 23 additions & 27 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { ApiPromise, Keyring, WsProvider } from '@polkadot/api';
import { BigNumber, Contract, PopulatedTransaction, Signer, Wallet } from 'ethers';
import { BigNumber, PopulatedTransaction, Wallet } from 'ethers';
import { CHAIN_ID_ACALA, CHAIN_ID_AVAX, CHAIN_ID_KARURA, CONTRACTS, hexToUint8Array } from '@certusone/wormhole-sdk';
import { DispatchError } from '@polkadot/types/interfaces';
import { ISubmittableResult } from '@polkadot/types/types';
import { JsonRpcProvider } from '@ethersproject/providers';
import { SubmittableExtrinsic } from '@polkadot/api/types';
import { SubstrateSigner } from '@acala-network/bodhi';
import { cryptoWaitReady } from '@polkadot/util-crypto';
import { decodeEthGas } from '@acala-network/eth-providers';
import { decodeEthGas, sleep } from '@acala-network/eth-providers';
import { options } from '@acala-network/api';
import { formatUnits, parseUnits } from 'ethers/lib/utils';

import { bridgeToken, getSignedVAAFromSequence } from './wormhole';
import { RelayerError } from './error';
import { bridgeToken, getSignedVAAFromSequence } from './wormhole';
import { parseAmount } from './token';
import { logger } from './logger';

export type ROUTER_CHAIN_ID = typeof CHAIN_ID_KARURA | typeof CHAIN_ID_ACALA;

Expand All @@ -31,17 +32,6 @@ export const getApi = async (privateKey: string, nodeUrl: string) => {
return { substrateAddr, api };
};

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

return parseUnits(amount, decimals);
};

export const transferFromAvax = async (
amount: string,
sourceAsset: string,
Expand Down Expand Up @@ -188,15 +178,21 @@ export const getEthExtrinsic = async (
);
};

export const getTokenBalance = 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);
};
// TODO: ideally this only retries on certain errors, such as network issues
export const runWithRetry = async <T>(
fn: () => Promise<T>,
{ retry = 10, interval = 5 } = {},
): Promise<T> => {
let error: any;
for (let i = 0; i < retry; i++) {
try {
return await fn();
} catch (err) {
error = err;
logger.info(`retrying ${fn.name} in ${interval}s [${i + 1}/${retry}]`);
await sleep(interval);
}
}

throw error;
};

0 comments on commit c053c8c

Please sign in to comment.