Compare commits

...

8 Commits

Author SHA1 Message Date
Jacob Evans
754e6ebd47 map automatically 2022-02-25 20:42:26 +10:00
Jacob Evans
4f391c6ab1 lint 2022-02-24 18:47:05 +10:00
Jacob Evans
fc298b3c44 Iterate through pools 2022-02-24 18:04:28 +10:00
Jacob Evans
74c255661e join both pools 2022-02-24 18:04:28 +10:00
Jacob Evans
06bbb5bea6 rename and prevent a failure from killing everything 2022-02-24 18:04:27 +10:00
Jacob Evans
8b9e92cc22 skip no balance pools, diff the CurveInfos 2022-02-24 18:04:27 +10:00
Jacob Evans
70522e463d Pull out symbols for readability 2022-02-24 18:04:27 +10:00
Jacob Evans
5dbba26be8 chore: Read Curve factory pools 2022-02-24 18:04:27 +10:00
8 changed files with 399 additions and 2 deletions

View File

@@ -0,0 +1,251 @@
// SPDX-License-Identifier: Apache-2.0
/*
Copyright 2022 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
pragma solidity ^0.6;
pragma experimental ABIEncoderV2;
interface IERC20Reader {
function symbol() external view returns (string memory);
function balanceOf(address owner)
external
view
returns (uint256);
}
interface ICurveFactoryPool {
function coins(uint256) external view returns (address);
}
interface ICurvePoolFactory {
function pool_count() external view returns (uint256);
function pool_list(uint256) external view returns (address);
function get_coins() external view returns (address[2] memory);
}
interface ICurveMetaPoolFactory {
function pool_count() external view returns (uint256);
function pool_list(uint256) external view returns (address);
function get_coins() external view returns (address[4] memory);
function get_underlying_coins(address) external view returns (address[8] memory);
function is_meta(address) external view returns (bool);
}
interface ICurvePoolRegistry {
function pool_count() external view returns (uint256);
function pool_list(uint256) external view returns (address);
function get_coins(address) external view returns (address[8] memory);
function get_underlying_coins(address) external view returns (address[8] memory);
function is_meta(address) external view returns (bool);
function get_pool_name(address) external view returns (string memory);
}
contract CurvePoolFactoryReader {
struct CurveFactoryPoolInfo {
address[] coins;
string[] symbols;
address pool;
bool hasBalance;
bytes32 codeHash;
}
struct CurveRegistryPoolInfo {
address[] coins;
address[] underlyingCoins;
string[] symbols;
string[] underlyingSymbols;
address pool;
bool hasBalance;
bytes32 codeHash;
bool isMeta;
}
function getFactoryPools(ICurvePoolFactory factory)
external
view
returns (CurveFactoryPoolInfo[] memory poolInfos)
{
uint256 poolCount = factory.pool_count();
poolInfos = new CurveFactoryPoolInfo[](poolCount);
for (uint256 i = 0; i < poolCount; i++) {
ICurveFactoryPool pool = ICurveFactoryPool(factory.pool_list(i));
try
CurvePoolFactoryReader(address(this)).getFactoryPoolInfo
(pool)
returns (CurveFactoryPoolInfo memory poolInfo)
{
poolInfos[i] = poolInfo;
} catch { }
// Set regardless, failed pools will have an address but no data
poolInfos[i].pool = address(pool);
}
}
function getRegistryPools(ICurvePoolRegistry registry)
external
view
returns (CurveRegistryPoolInfo[] memory poolInfos)
{
uint256 poolCount = registry.pool_count();
poolInfos = new CurveRegistryPoolInfo[](poolCount);
for (uint256 i = 0; i < poolCount; i++) {
ICurveFactoryPool pool = ICurveFactoryPool(registry.pool_list(i));
try
CurvePoolFactoryReader(address(this)).getRegistryPoolInfo
(registry, pool)
returns (CurveRegistryPoolInfo memory poolInfo)
{
poolInfos[i] = poolInfo;
} catch { }
// Set regardless, failed pools will have an address but no data
poolInfos[i].pool = address(pool);
}
}
function getRegistryPoolInfo(ICurvePoolRegistry registry, ICurveFactoryPool pool)
external
view
returns (CurveRegistryPoolInfo memory poolInfo)
{
// CurveFactoryPoolInfo memory factoryPoolInfo = this.getFactoryPoolInfo(pool);
poolInfo.pool = address(pool);
poolInfo.codeHash = _codeHash(address(pool));
// poolInfo.hasBalance = factoryPoolInfo.hasBalance;
poolInfo.isMeta = registry.is_meta(address(pool));
// Pulled iteratively in getCryptoFactoryPoolInfo
// poolInfo.coins = factoryPoolInfo.coins;
// Convert from address[8]
poolInfo.coins = _trimCoins(registry.get_coins(address(pool)));
poolInfo.symbols = _coinSymbols(poolInfo.coins);
poolInfo.underlyingCoins = _trimCoins(registry.get_underlying_coins(address(pool)));
poolInfo.underlyingSymbols = _coinSymbols(poolInfo.underlyingCoins);
}
function getFactoryPoolInfo(ICurveFactoryPool pool)
external
view
returns (CurveFactoryPoolInfo memory poolInfo)
{
poolInfo.pool = address(pool);
// Fetch all of the coins in the pool, no consistent way
// to determine how many there are, so we'll just try
uint256 count = 0;
address lastAddress = pool.coins(count);
do {
poolInfo.coins = _append(poolInfo.coins, lastAddress);
count++;
try
pool.coins(count)
returns (address nextAddress)
{
lastAddress = nextAddress;
} catch {
break;
}
} while (lastAddress != address(0));
// Fetch all of the symbols in the pool, some may fail
poolInfo.symbols = _coinSymbols(poolInfo.coins);
// Also fetch the accumulated balances, so we can reject anything that's
// basically uninitialized (balance 0)
uint256 totalBalance = 0;
for (uint256 i = 0; i < poolInfo.coins.length; i++) {
// 0x0000 or 0xeeeee
if (_codeHash(poolInfo.coins[i]) == bytes32(0)) {
continue;
}
IERC20Reader coin = IERC20Reader(poolInfo.coins[i]);
try
coin.balanceOf(address(pool))
returns (uint256 balance)
{
totalBalance += balance;
} catch { }
}
// If theres some tokens in there
poolInfo.hasBalance = totalBalance > 0;
poolInfo.codeHash = _codeHash(address(pool));
}
function _coinSymbols(address[] memory coins)
internal
view
returns (string[] memory symbols)
{
symbols = new string[](coins.length);
for (uint256 i = 0; i < coins.length; i++) {
(bool didSucceed, bytes memory result) = coins[i].staticcall(
abi.encodeWithSelector(
IERC20Reader(coins[i]).symbol.selector
)
);
if (didSucceed && result.length > 0) {
symbols[i] = abi.decode(result, (string));
}
}
}
function _append(address[] memory addressArray, address addr)
internal
view
returns (address[] memory newArray)
{
newArray = new address[](addressArray.length + 1);
for (uint256 i = 0; i < addressArray.length; i++) {
newArray[i] = addressArray[i];
}
newArray[newArray.length-1] = addr;
}
function _trimCoins(address[8] memory coins)
internal
view
returns (address[] memory)
{
address[] memory trimmed;
for (uint256 i = 0; i < coins.length; i++) {
// Sometimes first item is address(0), ETH?
if (coins[i] != address(0)) {
trimmed = _append(trimmed, coins[i]);
}
}
return trimmed;
}
function _codeHash(address addr)
internal
view
returns (bytes32)
{
bytes32 codeHash;
assembly { codeHash := extcodehash(addr) }
return codeHash;
}
}

View File

@@ -37,9 +37,9 @@
"sampler-size": "jq .compilerOutput.evm.deployedBytecode.object -- test/generated-artifacts/ERC20BridgeSampler.json | echo $(( $(wc -c) / 2 - 1 ))"
},
"config": {
"publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,FakeTaker",
"publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,FakeTaker,CurvePoolFactoryReader",
"abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.",
"abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BalancerV2Sampler|BancorSampler|CompoundSampler|CurveSampler|DODOSampler|DODOV2Sampler|DummyLiquidityProvider|ERC20BridgeSampler|FakeTaker|IBalancer|IBancor|ICurve|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|ISmoothy|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|KyberSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SmoothySampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler).json",
"abis": "./test/generated-artifacts/@(ApproximateBuys|BalanceChecker|BalancerSampler|BalancerV2Sampler|BancorSampler|CompoundSampler|CurvePoolFactoryReader|CurveSampler|DODOSampler|DODOV2Sampler|DummyLiquidityProvider|ERC20BridgeSampler|FakeTaker|IBalancer|IBancor|ICurve|IKyberNetwork|IMStable|IMooniswap|IMultiBridge|IShell|ISmoothy|IUniswapExchangeQuotes|IUniswapV2Router01|KyberDmmSampler|KyberSampler|LidoSampler|LiquidityProviderSampler|MStableSampler|MakerPSMSampler|MooniswapSampler|MultiBridgeSampler|NativeOrderSampler|SamplerUtils|ShellSampler|SmoothySampler|TestERC20BridgeSampler|TestNativeOrderSampler|TwoHopSampler|UniswapSampler|UniswapV2Sampler|UniswapV3Sampler|UtilitySampler).json",
"postpublish": {
"assets": []
}

View File

@@ -6,10 +6,12 @@
import { ContractArtifact } from 'ethereum-types';
import * as BalanceChecker from '../generated-artifacts/BalanceChecker.json';
import * as CurvePoolFactoryReader from '../generated-artifacts/CurvePoolFactoryReader.json';
import * as ERC20BridgeSampler from '../generated-artifacts/ERC20BridgeSampler.json';
import * as FakeTaker from '../generated-artifacts/FakeTaker.json';
export const artifacts = {
ERC20BridgeSampler: ERC20BridgeSampler as ContractArtifact,
BalanceChecker: BalanceChecker as ContractArtifact,
FakeTaker: FakeTaker as ContractArtifact,
CurvePoolFactoryReader: CurvePoolFactoryReader as ContractArtifact,
};

View File

@@ -0,0 +1,138 @@
import { RPCSubprovider, Web3ProviderEngine } from '@0x/subproviders';
import { providerUtils as ZeroExProviderUtils } from '@0x/utils';
import { BlockParamLiteral } from '@0x/web3-wrapper';
import _ = require('lodash');
import { inspect } from 'util';
import { artifacts } from '../artifacts';
import { CURVE_MAINNET_INFOS } from '../utils/market_operation_utils/constants';
import { CurveFunctionSelectors, CurveInfo } from '../utils/market_operation_utils/types';
import { CurvePoolFactoryReaderContract } from '../wrappers';
const PROVIDER = new Web3ProviderEngine();
PROVIDER.addProvider(new RPCSubprovider(process.env.ETHEREUM_RPC_URL!));
ZeroExProviderUtils.startProviderEngine(PROVIDER);
const QUERY_ADDRESS = '0x5555555555555555555555555555555555555555';
const overrides = {
[QUERY_ADDRESS]: { code: _.get(artifacts.CurvePoolFactoryReader, 'compilerOutput.evm.deployedBytecode.object') },
};
const FACTORY_READER = new CurvePoolFactoryReaderContract(QUERY_ADDRESS, PROVIDER);
interface PoolBase {
coins: string[];
symbols: string[];
pool: string;
hasBalance: boolean;
codeHash: string;
}
interface RegistryPool extends PoolBase {
underlyingCoins: string[];
underlyingSymbols: string[];
isMeta: boolean;
}
const getCurveFactoryPools = async (address: string): Promise<Array<PoolBase>> => {
const pools = await FACTORY_READER.getFactoryPools(address).callAsync({ overrides }, BlockParamLiteral.Latest);
return pools;
};
const getCurveRegistryPools = async (address: string): Promise<Array<RegistryPool>> => {
const pools = await FACTORY_READER.getRegistryPools(address).callAsync({ overrides }, BlockParamLiteral.Latest);
return pools;
};
// Provides the address to the registry
// https://curve.readthedocs.io/registry-address-provider.html
const REGISTRY_ADDRESS_PROVIDER = '0x0000000022D53366457F9d5E68Ec105046FC4383';
// get_registry on the registryAddressProvider or also the 0th address in get_address
// 0: The main registry contract. Used to locate pools and query information about them.
const CURVE_REGISTRY = '0x90e00ace148ca3b23ac1bc8c240c2a7dd9c2d7f5'; // 42 pools
// 1: Aggregate getter methods for querying large data sets about a single pool. Designed for off-chain use.
// 2: Generalized swap contract. Used for finding rates and performing exchanges.
// 3: The metapool factory.
const CURVE_META_FACTORY = '0xb9fc157394af804a3578134a6585c0dc9cc990d4';
// 4: The fee distributor. Used to distribute collected fees to veCRV holders.
// 5: ? Some other registry 0x8F942C20D02bEfc377D41445793068908E2250D0, Curve V2?
const CURVE_V2_FACTORY = '0x8F942C20D02bEfc377D41445793068908E2250D0';
// 6: ? Crypto Factory, 20 pools
const CURVE_CRYPTO_FACTORY = '0xf18056bbd320e96a48e3fbf8bc061322531aac99';
const ETH = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
const WETH = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
/* TODO
*
FEI-3Crv $109m
0x06cb22615BA53E60D67Bf6C341a0fD5E718E1655 is in META_FACTORY
but not in Registry
*/
const normalizeCoins = (coins: string[]): string[] => coins.map(c => (c === ETH ? WETH : c));
const determineRegistryCurveInfos = (pool: Array<RegistryPool>): CurveInfo[] => {
return pool.map(p => {
const isUnderlying = p.underlyingCoins.length > 1 && !p.underlyingCoins.every(c => p.coins.includes(c));
if (p.isMeta || isUnderlying) {
// Meta indicates Stable + 3Crv
// Typically that would be
// coins [Stable, 3Crv]
// underlyingCoins [Stable, DAI, USDC, USDT]
const info: CurveInfo = {
exchangeFunctionSelector: CurveFunctionSelectors.exchange_underlying,
sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy_underlying,
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
tokens: normalizeCoins(p.underlyingCoins),
metaTokens: undefined,
poolAddress: p.pool,
gasSchedule: p.isMeta ? 300e3 : 600e3,
};
return info;
} else {
const info: CurveInfo = {
exchangeFunctionSelector: CurveFunctionSelectors.exchange,
sellQuoteFunctionSelector: CurveFunctionSelectors.get_dy,
buyQuoteFunctionSelector: CurveFunctionSelectors.None,
tokens: normalizeCoins(p.coins),
metaTokens: undefined,
poolAddress: p.pool,
gasSchedule: 200e3,
};
return info;
}
});
};
const determineFactoryCurveInfos = (pool: Array<RegistryPool>): CurveInfo[] => {
return [];
};
// tslint:disable no-floating-promises
(async () => {
try {
// Appears to be 2 different factories, cryptoFactory pools looks like the latest and greatest
const cryptoFactoryPools = await getCurveFactoryPools(CURVE_CRYPTO_FACTORY);
const metaFactoryPools = await getCurveRegistryPools(CURVE_META_FACTORY);
const v2FactoryPools = await getCurveFactoryPools(CURVE_V2_FACTORY);
const registryPools = await getCurveRegistryPools(CURVE_REGISTRY);
const factoryPools = [...cryptoFactoryPools, ...metaFactoryPools, ...v2FactoryPools, ...registryPools];
// console.log(Object.values(CURVE_MAINNET_INFOS));
// console.log('\n');
console.log(determineRegistryCurveInfos(registryPools));
const currentlyMappedAddresses = Object.values(CURVE_MAINNET_INFOS).map(info => info.poolAddress);
const missingPools = _.chain(factoryPools)
.filter(({ pool, hasBalance }) => !currentlyMappedAddresses.includes(pool) && hasBalance)
.uniqBy('pool')
.value();
// tslint:disable no-console
// console.log(inspect(_.groupBy(missingPools, 'codeHash'), { showHidden: false, depth: null, colors: true }));
// console.log(`Found ${missingPools.length} missing pools`);
} catch (e) {
console.log(e);
throw e;
}
})();

View File

@@ -4,5 +4,6 @@
* -----------------------------------------------------------------------------
*/
export * from '../generated-wrappers/balance_checker';
export * from '../generated-wrappers/curve_pool_factory_reader';
export * from '../generated-wrappers/erc20_bridge_sampler';
export * from '../generated-wrappers/fake_taker';

View File

@@ -11,6 +11,7 @@ import * as BalancerSampler from '../test/generated-artifacts/BalancerSampler.js
import * as BalancerV2Sampler from '../test/generated-artifacts/BalancerV2Sampler.json';
import * as BancorSampler from '../test/generated-artifacts/BancorSampler.json';
import * as CompoundSampler from '../test/generated-artifacts/CompoundSampler.json';
import * as CurvePoolFactoryReader from '../test/generated-artifacts/CurvePoolFactoryReader.json';
import * as CurveSampler from '../test/generated-artifacts/CurveSampler.json';
import * as DODOSampler from '../test/generated-artifacts/DODOSampler.json';
import * as DODOV2Sampler from '../test/generated-artifacts/DODOV2Sampler.json';
@@ -76,6 +77,7 @@ export const artifacts = {
UniswapV2Sampler: UniswapV2Sampler as ContractArtifact,
UniswapV3Sampler: UniswapV3Sampler as ContractArtifact,
UtilitySampler: UtilitySampler as ContractArtifact,
CurvePoolFactoryReader: CurvePoolFactoryReader as ContractArtifact,
IBalancer: IBalancer as ContractArtifact,
IBancor: IBancor as ContractArtifact,
ICurve: ICurve as ContractArtifact,

View File

@@ -9,6 +9,7 @@ export * from '../test/generated-wrappers/balancer_sampler';
export * from '../test/generated-wrappers/balancer_v2_sampler';
export * from '../test/generated-wrappers/bancor_sampler';
export * from '../test/generated-wrappers/compound_sampler';
export * from '../test/generated-wrappers/curve_pool_factory_reader';
export * from '../test/generated-wrappers/curve_sampler';
export * from '../test/generated-wrappers/d_o_d_o_sampler';
export * from '../test/generated-wrappers/d_o_d_o_v2_sampler';

View File

@@ -4,6 +4,7 @@
"include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"],
"files": [
"generated-artifacts/BalanceChecker.json",
"generated-artifacts/CurvePoolFactoryReader.json",
"generated-artifacts/ERC20BridgeSampler.json",
"generated-artifacts/FakeTaker.json",
"test/generated-artifacts/ApproximateBuys.json",
@@ -12,6 +13,7 @@
"test/generated-artifacts/BalancerV2Sampler.json",
"test/generated-artifacts/BancorSampler.json",
"test/generated-artifacts/CompoundSampler.json",
"test/generated-artifacts/CurvePoolFactoryReader.json",
"test/generated-artifacts/CurveSampler.json",
"test/generated-artifacts/DODOSampler.json",
"test/generated-artifacts/DODOV2Sampler.json",