Skip to content

Commit

Permalink
auto route polish (#62)
Browse files Browse the repository at this point in the history
* auto route polish

* polish

* update doc

* update

* polish
  • Loading branch information
shunjizhan authored Apr 2, 2024
1 parent 05cb5c0 commit ccd1354
Show file tree
Hide file tree
Showing 6 changed files with 336 additions and 241 deletions.
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -431,12 +431,20 @@ data: {
GET /routeStatus?routeId=homa-1711514333845
=> route status
{ data: { status: 0 } } // waiting for token
{ data: { status: 1 } } // token arrived, routing
{ data: { status: 2, txHash: '0x12345 } } // routing tx submitted, waiting for confirmation
{ data: { status: 3, txHash: '0x12345 } } // routing completed
{ data: { status: -1 } } // routing timeout out (usually becuase no token arrive in 3 min)
{ data: { status: -2, error: 'xxx' } } // routing failed
[{ data: { status: 0 } }] // waiting for token
[{ data: { status: 1 } }] // token arrived, routing
[{ data: { status: 2, txHash: '0x12345' } }] // routing tx submitted, waiting for confirmation
[{ data: { status: 3, txHash: '0x12345' } }] // routing completed
[{ data: { status: -1 } }] // routing timeout out (usually becuase no token arrive in 3 min)
[{ data: { status: -2, error: 'xxx' } }] // routing failed
GET /routeStatus?destAddr=0x0085560b24769dAC4ed057F1B2ae40746AA9aAb6
=> all route status for this address
[
{ data: { status: 3, txHash: '0x11111' } },
{ data: { status: 2, txHash: '0x22222' } },
{ data: { status: 1 } },
]
/* ---------- when error ---------- */
// similar to /routeXcm
Expand Down
224 changes: 0 additions & 224 deletions src/__tests__/route.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
import { ADDRESSES } from '@acala-network/asset-router/dist/consts';
import { AcalaJsonRpcProvider, sleep } from '@acala-network/eth-providers';
import { ApiPromise, WsProvider } from '@polkadot/api';
import { CHAIN_ID_AVAX, CHAIN_ID_KARURA, CONTRACTS, hexToUint8Array, parseSequenceFromLogEth, redeemOnEth } from '@certusone/wormhole-sdk';
import { ContractReceipt, Wallet } from 'ethers';
import { DOT, LDOT } from '@acala-network/contracts/utils/AcalaTokens';
import { ERC20__factory } from '@certusone/wormhole-sdk/lib/cjs/ethers-contracts';
import { EVMAccounts__factory, IHoma__factory } from '@acala-network/contracts/typechain';
import { EVM_ACCOUNTS, HOMA } from '@acala-network/contracts/utils/Predeploy';
import { FeeRegistry__factory } from '@acala-network/asset-router/dist/typechain-types';
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 { parseEther, parseUnits } from 'ethers/lib/utils';
import assert from 'assert';

import {
BASILISK_TESTNET_NODE_URL,
Expand All @@ -23,7 +14,6 @@ import {
TEST_KEY,
} from './testConsts';
import { ETH_RPC, FUJI_TOKEN, GOERLI_USDC, PARA_ID } from '../consts';
import { RouteStatus } from '../api';
import {
encodeXcmDest,
expectError,
Expand All @@ -32,12 +22,8 @@ import {
mockXcmToRouter,
relayAndRoute,
relayAndRouteBatch,
routeHoma,
routeHomaAuto,
routeStatus,
routeWormhole,
routeXcm,
shouldRouteHoma,
shouldRouteWormhole,
shouldRouteXcm,
transferFromFujiToKaruraTestnet,
Expand All @@ -60,10 +46,6 @@ const dest = encodeXcmDest({
const providerKarura = new AcalaJsonRpcProvider(ETH_RPC.KARURA_TESTNET);
const relayerKarura = new Wallet(TEST_KEY.RELAYER, providerKarura);

const providerAcalaFork = new AcalaJsonRpcProvider(ETH_RPC.LOCAL);
const relayerAcalaFork = new Wallet(TEST_KEY.RELAYER, providerAcalaFork);
const userAcalaFork = new Wallet(TEST_KEY.USER, providerAcalaFork);

describe('/routeXcm', () => {
const api = new ApiPromise({ provider: new WsProvider(BASILISK_TESTNET_NODE_URL) });

Expand Down Expand Up @@ -374,209 +356,3 @@ describe('/routeWormhole', () => {

// describe.skip('when should not route', () => {})
});

describe.skip('/routeHoma', () => {
const DOT_DECIMALS = 10;
const dot = ERC20__factory.connect(DOT, providerAcalaFork);
const ldot = ERC20__factory.connect(LDOT, providerAcalaFork);
const homa = IHoma__factory.connect(HOMA, providerAcalaFork);
const evmAccounts = EVMAccounts__factory.connect(EVM_ACCOUNTS, providerAcalaFork);
const fee = FeeRegistry__factory.connect(ADDRESSES.ACALA.feeAddr, providerAcalaFork);
const stakeAmount = 6;
const parsedStakeAmount = parseUnits(String(stakeAmount), DOT_DECIMALS);
let routerAddr: string;

const fetchTokenBalances = async () => {
if (!routerAddr) throw new Error('routerAddr not set');

const [
userBalDot,
relayerBalDot,
routerBalDot,
userBalLdot,
relayerBalLdot,
routerBalLdot,
] = await Promise.all([
dot.balanceOf(TEST_ADDR_USER),
dot.balanceOf(TEST_ADDR_RELAYER),
dot.balanceOf(routerAddr),
ldot.balanceOf(TEST_ADDR_USER),
ldot.balanceOf(TEST_ADDR_RELAYER),
ldot.balanceOf(routerAddr),
]);

console.log({
userBalDot: toHuman(userBalDot, DOT_DECIMALS),
relayerBalDot: toHuman(relayerBalDot, DOT_DECIMALS),
routerBalDot: toHuman(routerBalDot, DOT_DECIMALS),
userBalLdot: toHuman(userBalLdot, DOT_DECIMALS),
relayerBalLdot: toHuman(relayerBalLdot, DOT_DECIMALS),
routerBalLdot: toHuman(routerBalLdot, DOT_DECIMALS),
});

return {
userBalDot,
relayerBalDot,
routerBalDot,
userBalLdot,
relayerBalLdot,
routerBalLdot,
};
};

const testHomaRouter = async (destAddr: string) => {
const relayerBal = await relayerAcalaFork.getBalance();
assert(relayerBal.gt(parseEther('10')), `relayer doesn't have enough balance to relay! ${relayerAcalaFork.address}`);

const routeArgs = {
destAddr,
chain: 'acala',
};
const res = await shouldRouteHoma(routeArgs);
({ routerAddr } = res.data);

// make sure user has enough DOT to transfer to router
const bal = await fetchTokenBalances();
if (bal.userBalDot.lt(parsedStakeAmount)) {
if (bal.relayerBalDot.lt(parsedStakeAmount)) {
throw new Error('both relayer and user do not have enough DOT to transfer to router!');
}

console.log('refilling dot for user ...');
await (await dot.connect(relayerAcalaFork).transfer(TEST_ADDR_USER, parsedStakeAmount)).wait();
}

const bal0 = await fetchTokenBalances();

console.log('xcming to router ...');
await mockXcmToRouter(routerAddr, userAcalaFork, DOT, stakeAmount);

console.log('routing ...');
const routeRes = await routeHoma({
...routeArgs,
token: DOT,
});
const txHash = routeRes.data;
console.log(`route finished! txHash: ${txHash}`);

const bal1 = await fetchTokenBalances();

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

// user should receive LDOT
const routingFee = await fee.getFee(DOT);
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).toBigInt()).to.eq(parsedStakeAmount.toBigInt());

// relayer should receive DOT fee
expect(bal1.relayerBalDot.sub(bal0.relayerBalDot).toBigInt()).to.eq(routingFee.toBigInt());
};

const testAutoHomaRouter = async (destAddr: string) => {
const relayerBal = await relayerAcalaFork.getBalance();
assert(relayerBal.gt(parseEther('10')), `relayer doesn't have enough balance to relay! ${relayerAcalaFork.address}`);

const routeArgs = {
destAddr,
chain: 'acala',
};
const res = await shouldRouteHoma(routeArgs);
({ routerAddr } = res.data);

// make sure user has enough DOT to transfer to router
const bal = await fetchTokenBalances();
if (bal.userBalDot.lt(parsedStakeAmount)) {
if (bal.relayerBalDot.lt(parsedStakeAmount)) {
throw new Error('both relayer and user do not have enough DOT to transfer to router!');
}

console.log('refilling dot for user ...');
await (await dot.connect(relayerAcalaFork).transfer(TEST_ADDR_USER, parsedStakeAmount)).wait();
}

const bal0 = await fetchTokenBalances();

console.log('sending auto routing request ...');
const routeRes = await routeHomaAuto({
...routeArgs,
token: DOT,
});
const reqId = routeRes.data;
console.log(`auto route submitted! reqId: ${reqId}`);

const waitForRoute = new Promise<void>((resolve, reject) => {
const pollRouteStatus = setInterval(async () => {
const res = await routeStatus({ id: reqId });
// console.log(`current status: ${res.data.status}`);

if (res.data.status === RouteStatus.Complete) {
resolve();
clearInterval(pollRouteStatus);
}
}, 1000);

setTimeout(reject, 100 * 1000);
});

console.log('xcming to router ...');
await mockXcmToRouter(routerAddr, userAcalaFork, DOT, stakeAmount);

console.log('waiting for auto routing ...');
await waitForRoute;

console.log('route complete!');
const bal1 = await fetchTokenBalances();

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

// user should receive LDOT
const routingFee = await fee.getFee(DOT);
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).toBigInt()).to.eq(parsedStakeAmount.toBigInt());

// relayer should receive DOT fee
expect(bal1.relayerBalDot.sub(bal0.relayerBalDot).toBigInt()).to.eq(routingFee.toBigInt());
};

it('route to evm address', async () => {
await testHomaRouter(TEST_ADDR_USER);
});

it('route to substrate address', async () => {
const ACALA_SS58_PREFIX = 10;
const userAccountId = await evmAccounts.getAccountId(TEST_ADDR_USER);
const userSubstrateAddr = encodeAddress(userAccountId, ACALA_SS58_PREFIX);

await testHomaRouter(userSubstrateAddr);
});

it('auto route to evm address', async () => {
await testAutoHomaRouter(TEST_ADDR_USER);
});

it('auto route to substrate address', async () => {
const ACALA_SS58_PREFIX = 10;
const userAccountId = await evmAccounts.getAccountId(TEST_ADDR_USER);
const userSubstrateAddr = encodeAddress(userAccountId, ACALA_SS58_PREFIX);

await testAutoHomaRouter(userSubstrateAddr);
});
});


Loading

0 comments on commit ccd1354

Please sign in to comment.