Skip to content

Commit

Permalink
Merge pull request #31 from diwu1989/registryHelper
Browse files Browse the repository at this point in the history
Introduce registry helper contracts to speed up pair discovery & bulk pair refresh
  • Loading branch information
zviadm authored Jun 20, 2022
2 parents a880c62 + 113a5bb commit 3fa5228
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 15 deletions.
41 changes: 34 additions & 7 deletions contracts/interfaces/balancer/IBPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,38 @@
pragma solidity 0.6.8;

interface IBPool {
function swapExactAmountIn(address, uint, address, uint, uint) external returns (uint, uint);
function swapExactAmountOut(address, uint, address, uint, uint) external returns (uint, uint);
function calcInGivenOut(uint, uint, uint, uint, uint, uint) external pure returns (uint);
function calcOutGivenIn(uint, uint, uint, uint, uint, uint) external pure returns (uint);
function getDenormalizedWeight(address) external view returns (uint);
function getBalance(address) external view returns (uint);
function getNumTokens() external view returns (uint);
function getCurrentTokens() external view returns (address[] memory tokens);
function swapExactAmountIn(
address tokenIn,
uint tokenAmountIn,
address tokenOut,
uint minAmountOut,
uint maxPrice) external returns (uint tokenAmountOut, uint spotPriceAfter);
function swapExactAmountOut(
address tokenIn,
uint maxAmountIn,
address tokenOut,
uint tokenAmountOut,
uint maxPrice) external returns (uint tokenAmountIn, uint spotPriceAfter);
function calcInGivenOut(
uint tokenBalanceIn,
uint tokenWeightIn,
uint tokenBalanceOut,
uint tokenWeightOut,
uint tokenAmountOut,
uint swapFee) external pure returns (uint tokenAmountIn);
function calcOutGivenIn(
uint tokenBalanceIn,
uint tokenWeightIn,
uint tokenBalanceOut,
uint tokenWeightOut,
uint tokenAmountIn,
uint swapFee) external pure returns (uint tokenAmountOut);
function getNormalizedWeight(address token) external view returns (uint);
function getDenormalizedWeight(address token) external view returns (uint);
function getTotalDenormalizedWeight() external view returns (uint);
function isFinalized() external view returns (bool);
function getBalance(address token) external view returns (uint);
function getSwapFee() external view returns (uint);
}
}
93 changes: 93 additions & 0 deletions contracts/swappa/RegistryHelperBalancer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.6.8;
pragma experimental ABIEncoderV2;

import "../interfaces/balancer/IBPool.sol";
import "../interfaces/balancer/IBRegistry.sol";

contract RegistryHelperBalancer {

struct TokenState {
address token;
uint balance;
uint denormalizedWeight;
}

struct PoolInfo {
IBPool pool;
uint swapFee;
TokenState[] tokenStates;
}

function findPools(
IBRegistry registry,
address[] calldata fromTokens,
address[] calldata toTokens
) external view returns (PoolInfo[] memory result) {
require(fromTokens.length == toTokens.length,
"fromTokens and toTokens must be of equal length");

IBPool[] memory foundPools = new IBPool[](fromTokens.length * 5);
uint found = 0;

for (uint i = 0; i < fromTokens.length; i++) {
// only take up the best 5 pools for a particular pair
address[] memory pools =
registry.getBestPoolsWithLimit(fromTokens[i], toTokens[i], 5);
for (uint j = 0; j < pools.length; j++) {
IBPool pool = IBPool(pools[j]);
if (!pool.isFinalized()) {
continue;
}

bool addPool = true;
for (uint k = 0; k < found; k++) {
if (foundPools[k] == pool) {
// already seen this pool, skip
addPool = false;
break;
}
}
if (addPool) {
// add this newly found pool
foundPools[found++] = pool;
}
}
}

result = new PoolInfo[](found);
for (uint i = 0; i < found; i++) {
IBPool pool = foundPools[i];
result[i] = this.getPoolInfo(pool);
}
}

function refreshPools(
IBPool[] calldata pools
) external view returns (PoolInfo[] memory result) {
result = new PoolInfo[](pools.length);
for (uint i = 0; i < pools.length; i++) {
result[i] = this.getPoolInfo(pools[i]);
}
}

function getPoolInfo(IBPool pool) external view returns (PoolInfo memory result) {
address[] memory poolTokens = pool.getCurrentTokens();
TokenState[] memory tokenStates = new TokenState[](poolTokens.length);
// collect information about all of the tokens in the pool
for (uint j = 0; j < poolTokens.length; j++) {
address token = poolTokens[j];
tokenStates[j] = TokenState(
token,
pool.getBalance(token),
pool.getDenormalizedWeight(token)
);
}

result = PoolInfo(
pool,
pool.getSwapFee(),
tokenStates
);
}
}
57 changes: 57 additions & 0 deletions contracts/swappa/RegistryHelperUniswapV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.6.8;
pragma experimental ABIEncoderV2;

import "../interfaces/uniswap/IUniswapV2Pair.sol";
import "../interfaces/uniswap/IUniswapV2Factory.sol";

contract RegistryHelperUniswapV2 {

struct PairState {
uint reserve0;
uint reserve1;
}

struct PairInfo {
IUniswapV2Pair pair;
address token0;
address token1;
PairState state;
}

function findPairs(
IUniswapV2Factory factory,
uint offset,
uint limit
) external view returns (PairInfo[] memory result) {
uint allPairsUpTo = factory.allPairsLength();

if (allPairsUpTo > offset + limit) {
// limit the number of pairs returned
allPairsUpTo = offset + limit;
} else if (allPairsUpTo < offset) {
// there are no more pairs
return result;
}

// allocate a buffer array with the upper bound of the number of pairs returned
result = new PairInfo[](allPairsUpTo - offset);
for (uint i = offset; i < allPairsUpTo; i++) {
IUniswapV2Pair uniPair = IUniswapV2Pair(factory.allPairs(i));
address token0 = uniPair.token0();
address token1 = uniPair.token1();
(uint reserve0, uint reserve1, ) = uniPair.getReserves();
result[i - offset] = PairInfo(uniPair, token0, token1, PairState(reserve0, reserve1));
}
}

function refreshPairs(
IUniswapV2Pair[] calldata pairs
) external view returns (PairState[] memory result) {
result = new PairState[](pairs.length);
for (uint i = 0; i < pairs.length; i++) {
(uint reserve0, uint reserve1, ) = pairs[i].getReserves();
result[i] = PairState(reserve0, reserve1);
}
}
}
24 changes: 22 additions & 2 deletions src/registries/uniswapv2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import BigNumber from "bignumber.js"
import Web3 from "web3"
import { concurrentMap } from '@celo/utils/lib/async'

import { RegistryHelperUniswapV2, ABI as RegistryHelperUniswapV2ABI } from "../../types/web3-v1-contracts/RegistryHelperUniswapV2"
import { IUniswapV2Factory, ABI as FactoryABI } from "../../types/web3-v1-contracts/IUniswapV2Factory"
import { Address, Pair } from "../pair"
import { PairUniswapV2 } from "../pairs/uniswapv2"
Expand All @@ -10,24 +11,43 @@ import { initPairsAndFilterByWhitelist } from "../utils"

export class RegistryUniswapV2 extends Registry {
private factory: IUniswapV2Factory
private helper?: RegistryHelperUniswapV2

constructor(
name: string,
private web3: Web3,
factoryAddr: Address,
private factoryAddr: Address,
private opts?: {
fixedFee?: BigNumber,
registryHelperAddr?: Address
fetchUsingTokenList?: boolean,
},
) {
super(name)
this.factory = new web3.eth.Contract(FactoryABI, factoryAddr) as unknown as IUniswapV2Factory
if (opts?.registryHelperAddr) {
this.helper = new web3.eth.Contract(RegistryHelperUniswapV2ABI, opts.registryHelperAddr) as unknown as RegistryHelperUniswapV2
}
}

findPairs = async (tokenWhitelist: Address[]): Promise<Pair[]> => {
const chainId = await this.web3.eth.getChainId()
let pairsFetched
if (this.opts?.fetchUsingTokenList) {
if (this.helper) {
// registry helper contract is available for fast discovery of pairs
const nPairs = Number.parseInt(await this.factory.methods.allPairsLength().call())
const limit = 100
pairsFetched = []
for (let offset = 0; offset < nPairs; offset += limit) {
const result = await this.helper.methods.findPairs(this.factoryAddr, offset, limit).call()
for (let pairInfo of result) {
if (tokenWhitelist.indexOf(pairInfo.token0) === -1 && tokenWhitelist.indexOf(pairInfo.token1) === -1) {
continue
}
pairsFetched.push(new PairUniswapV2(chainId, this.web3, pairInfo.pair, this.opts?.fixedFee))
}
}
} else if (this.opts?.fetchUsingTokenList) {
const pairsToFetch: {tokenA: Address, tokenB: Address}[] = []
for (let i = 0; i < tokenWhitelist.length - 1; i += 1) {
for (let j = i + 1; j < tokenWhitelist.length; j += 1) {
Expand Down
22 changes: 16 additions & 6 deletions src/registry-cfg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,21 @@ import { RegistryMento } from "./registries/mento"
import { RegistryStatic } from "./registries/static"
import { RegistryUniswapV2 } from "./registries/uniswapv2"
import { RegistryBalancer } from "./registries/balancer"
import { address as registryHelperUniswapV2 } from "../tools/deployed/mainnet.RegistryHelperUniswapV2.addr.json"

export const mainnetRegistryMoola =
(kit: ContractKit) => new RegistryAave("moola", kit, "0x7AAaD5a5fa74Aec83b74C2a098FBC86E17Ce4aEA")
export const mainnetRegistryUbeswap =
(kit: ContractKit) => new RegistryUniswapV2("ubeswap", kit.web3 as unknown as Web3, "0x62d5b84bE28a183aBB507E125B384122D2C25fAE")
(kit: ContractKit) => new RegistryUniswapV2(
"ubeswap", kit.web3 as unknown as Web3,
"0x62d5b84bE28a183aBB507E125B384122D2C25fAE",
{ registryHelperAddr: registryHelperUniswapV2 })
export const mainnetRegistrySushiswap =
(kit: ContractKit) => new RegistryUniswapV2("sushiswap", kit.web3 as unknown as Web3, "0xc35DADB65012eC5796536bD9864eD8773aBc74C4")
(kit: ContractKit) => new RegistryUniswapV2(
"sushiswap",
kit.web3 as unknown as Web3,
"0xc35DADB65012eC5796536bD9864eD8773aBc74C4",
{ registryHelperAddr: registryHelperUniswapV2 })
export const mainnetRegistryMobius =
(kit: ContractKit) => {
const web3 = kit.web3 as unknown as Web3
Expand Down Expand Up @@ -62,15 +70,17 @@ export const mainnetRegistryMisc =
]))
}
export const mainnetRegistrySavingsCELO =
(kit: ContractKit) => new RegistryStatic("savingscelo", web3.eth.getChainId().then(chainId => [
(kit: ContractKit) => new RegistryStatic("savingscelo", kit.web3.eth.getChainId().then(chainId => [
new PairSavingsCELO(chainId, kit, SavingsCELOAddressMainnet),
]))
export const mainnetRegistryMoolaV2 =
(kit: ContractKit) => new RegistryAaveV2("moola-v2", kit.web3 as unknown as Web3, "0xD1088091A174d33412a968Fa34Cb67131188B332")
export const mainnetRegistryCeloDex =
(kit: ContractKit) => new RegistryUniswapV2("celodex", kit.web3 as unknown as Web3, "0x31bD38d982ccDf3C2D95aF45a3456d319f0Ee1b6", {
fixedFee: new BigNumber(0.997), // TODO(zviadm): Figure out actual fee for CeloDex pairs.
})
(kit: ContractKit) => new RegistryUniswapV2(
"celodex",
kit.web3 as unknown as Web3,
"0x31bD38d982ccDf3C2D95aF45a3456d319f0Ee1b6",
{ registryHelperAddr: registryHelperUniswapV2 })
export const mainnetRegistrySymmetric =
(kit: ContractKit) => new RegistryBalancer("symmetric", kit.web3 as unknown as Web3, "0x3E30b138ecc85cD89210e1A19a8603544A917372")

Expand Down
1 change: 1 addition & 0 deletions tools/deployed/mainnet.RegistryHelperUniswapV2.addr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"address":"0xe9579Fc54Be093972b44aA7E12b130a2D9B1b6af"}
9 changes: 9 additions & 0 deletions tools/deployer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import path from "path"
import { ContractKit, newKit } from "@celo/contractkit"

import SwappaRouterV1Json from "../build/contracts/SwappaRouterV1.json"
import RegistryHelperUniswapV2 from "../build/contracts/RegistryHelperUniswapV2.json"

import PairUniswapV2 from "../build/contracts/PairUniswapV2.json"
import PairMento from "../build/contracts/PairMento.json"
import PairAToken from "../build/contracts/PairAToken.json"
Expand Down Expand Up @@ -89,8 +91,15 @@ async function main() {
const kit = newKit(networkURL)
kit.defaultAccount = opts.from

// Main Router
await readAddressOrDeployContract(
kit, opts.network, "SwappaRouterV1", SwappaRouterV1Json.bytecode)

// Helper contracts
await readAddressOrDeployContract(
kit, opts.network, "RegistryHelperUniswapV2", RegistryHelperUniswapV2.bytecode)

// Pairs
await readAddressOrDeployContract(
kit, opts.network, "PairUniswapV2", PairUniswapV2.bytecode)
await readAddressOrDeployContract(
Expand Down

0 comments on commit 3fa5228

Please sign in to comment.