Skip to content

Commit

Permalink
refactor(getPools): use multicall3 (#164)
Browse files Browse the repository at this point in the history
  • Loading branch information
fadeev authored Aug 6, 2024
1 parent 2bd1297 commit f0bf8b1
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 31 deletions.
134 changes: 103 additions & 31 deletions packages/client/src/getPools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ import SystemContract from "@zetachain/protocol-contracts/abi/zevm/SystemContrac
import { ethers } from "ethers";

import { ZetaChainClient } from "./client";
import MULTICALL3_ABI from "./multicall3.json";

type Pair = {
key: string;
tokenA: string;
tokenB: string;
};

export const getPools = async function (this: ZetaChainClient) {
const rpc = this.getEndpoint("evm", `zeta_${this.network}`);
Expand Down Expand Up @@ -38,11 +45,11 @@ export const getPools = async function (this: ZetaChainClient) {
);
tokenAddresses.push(zetaTokenAddress);

const uniquePairs = tokenAddresses.reduce(
(pairs: any, tokenA: string, i: any) => {
tokenAddresses.slice(i + 1).forEach((tokenB: any) => {
const uniquePairs: Pair[] = tokenAddresses.reduce(
(pairs: Pair[], tokenA: string, i: number) => {
tokenAddresses.slice(i + 1).forEach((tokenB: string) => {
const pairKey = [tokenA, tokenB].sort().join("-");
if (!pairs.some((p: any) => p.key === pairKey)) {
if (!pairs.some((p: Pair) => p.key === pairKey)) {
pairs.push({ key: pairKey, tokenA, tokenB });
}
});
Expand All @@ -51,38 +58,103 @@ export const getPools = async function (this: ZetaChainClient) {
[]
);

const poolPromises = uniquePairs.map(async ({ tokenA, tokenB }: any) => {
const pair = await systemContract.uniswapv2PairFor(
const multicallAddress = "0xca11bde05977b3631167028862be2a173976ca11";
const multicallContract = new ethers.Contract(
multicallAddress,
MULTICALL3_ABI,
provider
);

const calls = uniquePairs.map(({ tokenA, tokenB }) => ({
callData: systemContract.interface.encodeFunctionData("uniswapv2PairFor", [
uniswapV2FactoryAddress,
tokenA,
tokenB
);
tokenB,
]),
target: systemContractAddress,
}));

const { returnData } = await multicallContract.callStatic.aggregate(calls);

const validPairs = returnData
.map((data: any, index: number) => {
try {
const pair = systemContract.interface.decodeFunctionResult(
"uniswapv2PairFor",
data
)[0];
return pair !== ethers.constants.AddressZero ? pair : null;
} catch {
return null;
}
})
.filter((pair: string | null) => pair !== null);

const pairCalls = validPairs
.map((pair: string) => [
{
callData: new ethers.utils.Interface(
UniswapV2Pair.abi
).encodeFunctionData("token0"),
target: pair,
},
{
callData: new ethers.utils.Interface(
UniswapV2Pair.abi
).encodeFunctionData("token1"),
target: pair,
},
{
callData: new ethers.utils.Interface(
UniswapV2Pair.abi
).encodeFunctionData("getReserves"),
target: pair,
},
])
.flat();

if (pair === ethers.constants.AddressZero) return null;
const pairReturnData = await multicallContract.callStatic.aggregate(
pairCalls
);

const pools = [];
const uniswapInterface = new ethers.utils.Interface(UniswapV2Pair.abi);

for (let i = 0; i < pairReturnData.returnData.length; i += 3) {
const pairIndex = Math.floor(i / 3);
const pair = validPairs[pairIndex];

if (
!pairReturnData.returnData[i] ||
!pairReturnData.returnData[i + 1] ||
!pairReturnData.returnData[i + 2]
) {
console.warn(`Missing data for pair ${pair} at index ${i}`);
continue;
}

const token0Data = pairReturnData.returnData[i];
const token1Data = pairReturnData.returnData[i + 1];
const reservesData = pairReturnData.returnData[i + 2];

// Check if data can be decoded
let token0, token1, reserves;
try {
const pairContract = new ethers.Contract(
pair,
UniswapV2Pair.abi,
provider
token0 = uniswapInterface.decodeFunctionResult("token0", token0Data)[0];
token1 = uniswapInterface.decodeFunctionResult("token1", token1Data)[0];
reserves = uniswapInterface.decodeFunctionResult(
"getReserves",
reservesData
);
const [token0, token1] = await Promise.all([
pairContract.token0(),
pairContract.token1(),
]);
const reserves = await pairContract.getReserves();

return {
pair,
t0: { address: token0, reserve: reserves[0] },
t1: { address: token1, reserve: reserves[1] },
};
} catch (error) {
return null;
} catch {
continue;
}
});

let pools = (await Promise.all(poolPromises)).filter((pool) => pool !== null);
pools.push({
pair,
t0: { address: token0, reserve: reserves._reserve0 },
t1: { address: token1, reserve: reserves._reserve1 },
});
}

const zrc20Details = foreignCoins.reduce((acc: any, coin: any) => {
acc[coin.zrc20_contract_address.toLowerCase()] = {
Expand All @@ -92,7 +164,7 @@ export const getPools = async function (this: ZetaChainClient) {
return acc;
}, {});

pools = pools.map((t: any) => {
const formattedPools = pools.map((t: any) => {
const zeta = { decimals: 18, symbol: "WZETA" };
const t0 = t.t0.address.toLowerCase();
const t1 = t.t1.address.toLowerCase();
Expand All @@ -111,5 +183,5 @@ export const getPools = async function (this: ZetaChainClient) {
};
});

return pools;
return formattedPools;
};
38 changes: 38 additions & 0 deletions packages/client/src/multicall3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "target",
"type": "address"
},
{
"internalType": "bytes",
"name": "callData",
"type": "bytes"
}
],
"internalType": "struct IMulticall3.Call[]",
"name": "calls",
"type": "tuple[]"
}
],
"name": "aggregate",
"outputs": [
{
"internalType": "uint256",
"name": "blockNumber",
"type": "uint256"
},
{
"internalType": "bytes[]",
"name": "returnData",
"type": "bytes[]"
}
],
"stateMutability": "view",
"type": "function"
}
]

0 comments on commit f0bf8b1

Please sign in to comment.