Skip to content

Commit

Permalink
add route euphrate tests
Browse files Browse the repository at this point in the history
  • Loading branch information
shunjizhan committed Jan 11, 2024
1 parent f0b5ac2 commit 1c5665a
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 15 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,5 @@ dist
.tern-port


src/scripts/.env
**/.env
db.*
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
},
"dependencies": {
"@acala-network/api": "~6.0.4",
"@acala-network/asset-router": "1.0.13-1",
"@acala-network/asset-router": "1.0.13",
"@acala-network/bodhi": "~2.7.13",
"@acala-network/contracts": "^4.5.0",
"@acala-network/eth-providers": "~2.7.14",
Expand Down
159 changes: 159 additions & 0 deletions src/__tests__/routeEuphrates.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { ADDRESSES } from '@acala-network/asset-router/dist/consts';
import { AcalaJsonRpcProvider } from '@acala-network/eth-providers';
import { DOT, LCDOT_13 as LCDOT, LDOT } from '@acala-network/contracts/utils/AcalaTokens';
import { ERC20__factory } from '@certusone/wormhole-sdk/lib/cjs/ethers-contracts';
import { FeeRegistry__factory } from '@acala-network/asset-router/dist/typechain-types';
import { IHoma__factory } from '@acala-network/contracts/typechain';
import { ONE_ACA, almostEq, toHuman } from '@acala-network/asset-router/dist/utils';
import { Wallet } from 'ethers';
import { describe, expect, it } from 'vitest';
import { formatEther, parseEther, parseUnits } from 'ethers/lib/utils';

import { ETH_RPC, EUPHRATES_ADDR, EUPHRATES_POOLS } from '../consts';
import { HOMA } from '@acala-network/contracts/utils/Predeploy';
import {
TEST_ADDR_RELAYER,
TEST_ADDR_USER,
TEST_KEY,
} from './testConsts';
import {
routeEuphrates,
shouldRouteEuphrates,
transferToRouter,
} from './testUtils';

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

// [inToken, outToken]
const WTDOT = '0xe1bd4306a178f86a9214c39abcd53d021bedb0f9';
const EUPHRAETS_POOL_INFO = {
0: [LCDOT, LDOT],
1: [LCDOT, WTDOT],
2: [DOT, LDOT],
3: [DOT, WTDOT],
};

describe('/routeEuphrates', () => {
const DOT_DECIMALS = 10;
const homa = IHoma__factory.connect(HOMA, providerAcalaFork);
const fee = FeeRegistry__factory.connect(ADDRESSES.ACALA_TESTNET.feeAddr, providerAcalaFork);
const stakeAmount = 6;
const parsedStakeAmount = parseUnits(String(stakeAmount), DOT_DECIMALS);
let routerAddr: string;

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

const [inTokenAddr, outTokenAddr] = EUPHRAETS_POOL_INFO[pooId];
const inToken = ERC20__factory.connect(inTokenAddr, providerAcalaFork);
const outToken = ERC20__factory.connect(outTokenAddr, providerAcalaFork);

const [
userBalIn,
relayerBalIn,
routerBalIn,
userBalOut,
routerBalOut,
euphratesBalOut,
] = await Promise.all([
inToken.balanceOf(TEST_ADDR_USER),
inToken.balanceOf(TEST_ADDR_RELAYER),
inToken.balanceOf(routerAddr),
outToken.balanceOf(TEST_ADDR_USER),
outToken.balanceOf(routerAddr),
outToken.balanceOf(EUPHRATES_ADDR),
]);

console.log({
userBalIn: toHuman(userBalIn, DOT_DECIMALS),
relayerBalIn: toHuman(relayerBalIn, DOT_DECIMALS),
routerBalIn: toHuman(routerBalIn, DOT_DECIMALS),
userBalOut: toHuman(userBalOut, DOT_DECIMALS),
routerBalOut: toHuman(routerBalOut, DOT_DECIMALS),
euphratesBalOut: toHuman(euphratesBalOut, DOT_DECIMALS),
});

return {
userBalIn,
relayerBalIn,
routerBalIn,
userBalOut,
routerBalOut,
euphratesBalOut,
};
};

const testEuphratesRouter = async (poolId: string) => {
const [inTokenAddr] = EUPHRAETS_POOL_INFO[poolId];

const relayerBal = await relayerAcalaFork.getBalance();
expect(relayerBal.gt(parseEther('10'))).to.be.true;

const routeArgs = {
recipient: userAcalaFork.address,
poolId,
};
const res = await shouldRouteEuphrates(routeArgs);
({ routerAddr } = res.data);

// make sure user has enough DOT to transfer to router
const bal = await fetchTokenBalances(poolId);
if (bal.userBalIn.lt(parsedStakeAmount)) {
if (bal.relayerBalIn.lt(parsedStakeAmount)) {
throw new Error(`both relayer and user do not have enough ${inTokenAddr} to transfer to router!`);
}

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

const bal0 = await fetchTokenBalances(poolId);

console.log('transferring token to router ...');
await transferToRouter(routerAddr, userAcalaFork, inTokenAddr, stakeAmount);

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

const bal1 = await fetchTokenBalances(poolId);

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

// euphrate contract should receive Liquid Token
const routingFee = await fee.getFee(inTokenAddr);
const liquidTokenReceived = bal1.euphratesBalOut.sub(bal0.euphratesBalOut);

if (['0', '2'].includes(poolId)) {
// 10{18} DOT => ? LDOT
const exchangeRate = parseEther(
(1 / Number(formatEther(await homa.getExchangeRate()))).toString()
);
const liquidTokenExpected = parsedStakeAmount.sub(routingFee).mul(exchangeRate).div(ONE_ACA);
expect(almostEq(liquidTokenExpected, liquidTokenReceived)).to.be.true;
} else {
// calculating exact tdot is out of scope of this test
expect(liquidTokenReceived.toBigInt()).toBeGreaterThan(0);
}
expect(bal0.userBalIn.sub(bal1.userBalIn).toBigInt()).to.eq(parsedStakeAmount.toBigInt());

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

it('worked for all pools', async () => {
for (const poolId of EUPHRATES_POOLS) {
await testEuphratesRouter(poolId);
}
});
});
8 changes: 5 additions & 3 deletions src/__tests__/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const getBasiliskUsdcBalance = async (api: ApiPromise, addr: string) => {
return (balance as any).free.toBigInt();
};

export const mockXcmToRouter = async (
export const transferToRouter = async (
routerAddr: string,
signer: Wallet,
tokenAddr = KARURA_USDC_ADDRESS,
Expand All @@ -61,12 +61,14 @@ export const mockXcmToRouter = async (
if (routerBal.gt(0)) {
expect(routerBal.toBigInt()).to.eq(routeAmount.toBigInt());
} else {
if ((await token.balanceOf(signer.address)).lt(routeAmount)) {
throw new Error(`signer ${signer.address} has no enough token [${tokenAddr}] to transfer!`);
const signerTokenBal = await token.balanceOf(signer.address);
if (signerTokenBal.lt(routeAmount)) {
throw new Error(`signer ${signer.address} has no enough token [${tokenAddr}] to transfer! ${signerTokenBal.toBigInt()} < ${routeAmount.toBigInt()}`);
}
await (await token.transfer(routerAddr, routeAmount)).wait();
}
};
export const mockXcmToRouter = transferToRouter;

export const expectError = (err: any, msg: any, code: number) => {
if (axios.isAxiosError(err)) {
Expand Down
14 changes: 4 additions & 10 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,16 @@
"@acala-network/api-derive" "6.0.4"
"@acala-network/types" "6.0.4"

"@acala-network/[email protected]-1":
version "1.0.13-1"
resolved "https://registry.yarnpkg.com/@acala-network/asset-router/-/asset-router-1.0.13-1.tgz#a5da78315335ce24cb289019f9ba8ac6af28423e"
integrity sha512-zi2j3Nijn94DlgTu+NilHcb+6mWEIzInm+Gy66OmfQQAYbjG2X7gg45O5oyBPs6kClHrSf459zeAYMtobWb/Sg==
"@acala-network/[email protected]":
version "1.0.13"
resolved "https://registry.yarnpkg.com/@acala-network/asset-router/-/asset-router-1.0.13.tgz#0ad0e8ce8bd462a7291a6bf8f951bd24f83ea0e4"
integrity sha512-J0W5MSVKYIz/cgx9KVCqe9NLkg+FdRrAl6BqCZzZkj8JPcQhUlxgW7TP/s3yYr/5MXk3wDb69z/f2cBWGjhbxw==
dependencies:
"@acala-network/contracts" "^4.5.0"
"@acala-network/eth-providers" "^2.7.13"
"@certusone/wormhole-sdk" "^0.9.11"
"@ethersproject/abi" "^5.7.0"
"@ethersproject/providers" "^5.7.0"
"@openzeppelin/contracts" "^5.0.1"
"@polkadot/util-crypto" "^12.5.1"
ethers "^5.7.0"

Expand Down Expand Up @@ -1293,11 +1292,6 @@
resolved "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.18.1.tgz#8e47caf57a84b1dcc1722b2025693348cdf443b4"
integrity sha512-+NLGHr6VZwcgE/2lw8zDIufOCGnzsA5CbQIMleXZTrgkBd0TanCX+MiDYJ1TOS4KL/Tqk0nFRxawnaYr6pkZkA==

"@openzeppelin/contracts@^5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.1.tgz#93da90fc209a0a4ff09c1deb037fbb35e4020890"
integrity sha512-yQJaT5HDp9hYOOp4jTYxMsR02gdFZFXhewX5HW9Jo4fsqSVqqyIO/xTHdWDaKX5a3pv1txmf076Lziz+sO7L1w==

"@polka/url@^1.0.0-next.20":
version "1.0.0-next.21"
resolved "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1"
Expand Down

0 comments on commit 1c5665a

Please sign in to comment.