From 474b48b6b24a4972ef55c8468eafa20643c64808 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 31 Jul 2024 21:57:53 +0100 Subject: [PATCH 1/5] wip with curve_rate_provider logic reusing --- contracts/oracles/CurveOracle.sol | 217 +++++++++++------------------- test/oracles/CurveOracle.js | 7 +- 2 files changed, 87 insertions(+), 137 deletions(-) diff --git a/contracts/oracles/CurveOracle.sol b/contracts/oracles/CurveOracle.sol index ce0759e..4138d71 100644 --- a/contracts/oracles/CurveOracle.sol +++ b/contracts/oracles/CurveOracle.sol @@ -11,160 +11,105 @@ import "../interfaces/ICurveSwap.sol"; import "../libraries/OraclePrices.sol"; import "../helpers/Blacklist.sol"; -contract CurveOracle is IOracle, Blacklist { +interface IMetaregistry { + function find_pools_for_coins(address srcToken, address dstToken) external view returns (address[] memory); + function get_coin_indices(address _pool, address _from, address _to) external view returns (int128, int128, bool); + function get_underlying_balances(address _pool) external view returns (uint256[8] memory); +} + +interface ICurvePool { + function allowed_extra_profit() external view returns (uint256); + function get_rate_mul() external view returns (uint256); +} + +interface IStableSwapMeta { + function get_dy_underlying(int128,int128,uint256) external view returns (uint256); +} + +interface IStableSwap { + function get_dy(int128,int128,uint256) external view returns (uint256); +} + +interface ICryptoSwap { + function get_dy(uint256,uint256,uint256) external view returns (uint256); +} + +contract CurveOracle is IOracle { using OraclePrices for OraclePrices.Data; using Math for uint256; - enum CurveRegistryType { - MAIN_REGISTRY, - METAPOOL_FACTORY, - CRYPTOSWAP_REGISTRY, - CRYPTOPOOL_FACTORY, - METAREGISTRY, - CRVUSD_PLAIN_POOLS, - CURVE_TRICRYPTO_FACTORY, - STABLESWAP_FACTORY, - L2_FACTORY, - CRYPTO_FACTORY - } + IERC20 private constant _NONE = IERC20(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF); + IERC20 private constant _ETH = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); + + IMetaregistry public immutable CURVE_METAREGISTRY; - struct FunctionSelectorsInfo { - bytes4 balanceFunc; - bytes4 dyFuncInt128; - bytes4 dyFuncUint256; + constructor(IMetaregistry curveMetaregistry) { + CURVE_METAREGISTRY = curveMetaregistry; } - IERC20 private constant _NONE = IERC20(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF); + function _getPoolType(address pool) private view returns (uint8) { + // 0 for stableswap, 1 for cryptoswap, 2 for LLAMMA. - uint256 public immutable MAX_POOLS; - uint256 public immutable REGISTRIES_COUNT; - ICurveRegistry[11] public registries; - CurveRegistryType[11] public registryTypes; - - constructor( - ICurveProvider _addressProvider, - uint256 _maxPools, - uint256[] memory _registryIds, - CurveRegistryType[] memory _registryTypes, - address[] memory initialBlacklist, - address owner - ) Blacklist(initialBlacklist, owner) { - MAX_POOLS = _maxPools; - REGISTRIES_COUNT = _registryIds.length; - unchecked { - for (uint256 i = 0; i < REGISTRIES_COUNT; i++) { - registries[i] = ICurveRegistry(_addressProvider.get_address(_registryIds[i])); - registryTypes[i] = _registryTypes[i]; - } + // check if cryptoswap + (bool success, bytes memory data) = pool.staticcall(abi.encodeWithSelector(ICurvePool.allowed_extra_profit.selector)); + if (success && data.length >= 32) { // vyper could return redundant bytes + return 1; + } + + // check if llamma + (success, data) = pool.staticcall(abi.encodeWithSelector(ICurvePool.get_rate_mul.selector)); + if (success && data.length >= 32) { // vyper could return redundant bytes + return 2; } + + return 0; } function getRate(IERC20 srcToken, IERC20 dstToken, IERC20 connector, uint256 thresholdFilter) external view override returns (uint256 rate, uint256 weight) { if(connector != _NONE) revert ConnectorShouldBeNone(); - OraclePrices.Data memory ratesAndWeights = OraclePrices.init(MAX_POOLS); - FunctionSelectorsInfo memory info; - uint256 index = 0; - for (uint256 i = 0; i < REGISTRIES_COUNT && index < MAX_POOLS; i++) { - uint256 registryIndex = 0; - address pool = registries[i].find_pool_for_coins(address(srcToken), address(dstToken), registryIndex); - while (pool != address(0) && index < MAX_POOLS) { - if (blacklisted[pool]) { - pool = registries[i].find_pool_for_coins(address(srcToken), address(dstToken), ++registryIndex); - continue; - } - index++; - // call `get_coin_indices` and set (srcTokenIndex, dstTokenIndex, isUnderlying) variables - bool isUnderlying; - int128 srcTokenIndex; - int128 dstTokenIndex; - (bool success, bytes memory data) = address(registries[i]).staticcall(abi.encodeWithSelector(ICurveRegistry.get_coin_indices.selector, pool, address(srcToken), address(dstToken))); - if (success && data.length >= 64) { - if ( - registryTypes[i] == CurveRegistryType.CRYPTOSWAP_REGISTRY || - registryTypes[i] == CurveRegistryType.CRYPTOPOOL_FACTORY || - registryTypes[i] == CurveRegistryType.CURVE_TRICRYPTO_FACTORY - ) { - (srcTokenIndex, dstTokenIndex) = abi.decode(data, (int128, int128)); - } else { - // registryTypes[i] == CurveRegistryType.MAIN_REGISTRY || - // registryTypes[i] == CurveRegistryType.METAPOOL_FACTORY || - // registryTypes[i] == CurveRegistryType.METAREGISTRY || - // registryTypes[i] == CurveRegistryType.CRVUSD_PLAIN_POOLS || - // registryTypes[i] == CurveRegistryType.STABLESWAP_FACTORY || - // registryTypes[i] == CurveRegistryType.L2_FACTORY || - // registryTypes[i] == CurveRegistryType.CRYPTO_FACTORY - (srcTokenIndex, dstTokenIndex, isUnderlying) = abi.decode(data, (int128, int128, bool)); - } - } else { - pool = registries[i].find_pool_for_coins(address(srcToken), address(dstToken), ++registryIndex); - continue; - } + address[] memory pools = CURVE_METAREGISTRY.find_pools_for_coins(address(srcToken), address(dstToken)); + if (pools.length == 0) { + return (0, 0); + } - if (!isUnderlying) { - info = FunctionSelectorsInfo({ - balanceFunc: ICurveRegistry.get_balances.selector, - dyFuncInt128: ICurveSwapInt128.get_dy.selector, - dyFuncUint256: ICurveSwapUint256.get_dy.selector - }); - } else { - info = FunctionSelectorsInfo({ - balanceFunc: ICurveRegistry.get_underlying_balances.selector, - dyFuncInt128: ICurveSwapInt128.get_dy_underlying.selector, - dyFuncUint256: ICurveSwapUint256.get_dy_underlying.selector - }); - } + OraclePrices.Data memory ratesAndWeights = OraclePrices.init(pools.length); + for (uint256 k = 0; k < pools.length; k++) { + // get coin indices + int128 i; + int128 j; + bool isUnderlying = false; + (i, j, isUnderlying) = CURVE_METAREGISTRY.get_coin_indices(pools[k], address(srcToken), address(dstToken)); - // call `balanceFunc` (`get_balances` or `get_underlying_balances`) and decode results - uint256[] memory balances; - (success, data) = address(registries[i]).staticcall(abi.encodeWithSelector(info.balanceFunc, pool)); - if (success && data.length >= 64) { - // registryTypes[i] == CurveRegistryType.MAIN_REGISTRY || - // registryTypes[i] == CurveRegistryType.CRYPTOSWAP_REGISTRY || - // registryTypes[i] == CurveRegistryType.METAREGISTRY - uint256 length = 8; - if (!isUnderlying) { - if ( - registryTypes[i] == CurveRegistryType.METAPOOL_FACTORY || - registryTypes[i] == CurveRegistryType.CRVUSD_PLAIN_POOLS || - registryTypes[i] == CurveRegistryType.STABLESWAP_FACTORY || - registryTypes[i] == CurveRegistryType.L2_FACTORY || - registryTypes[i] == CurveRegistryType.CRYPTO_FACTORY - ) { - length = 4; - } else if (registryTypes[i] == CurveRegistryType.CURVE_TRICRYPTO_FACTORY) { - length = 3; - } else if (registryTypes[i] == CurveRegistryType.CRYPTOPOOL_FACTORY) { - length = 2; - } - } - - assembly ("memory-safe") { // solhint-disable-line no-inline-assembly - balances := data - mstore(balances, length) - } - } else { - pool = registries[i].find_pool_for_coins(address(srcToken), address(dstToken), ++registryIndex); - continue; - } + // get balances + uint256[8] memory balances = CURVE_METAREGISTRY.get_underlying_balances(pools[k]); + // skip if pool is too small + balances[uint128(i)] = balances[uint128(i)] / 1e4; + balances[uint128(j)] = balances[uint128(j)] / 1e4; + if (balances[uint128(i)] == 0 || balances[uint128(j)] == 0) { + continue; + } - uint256 w = (balances[uint128(srcTokenIndex)] * balances[uint128(dstTokenIndex)]).sqrt(); - uint256 b0 = balances[uint128(srcTokenIndex)] / 10000; - uint256 b1 = balances[uint128(dstTokenIndex)] / 10000; - - if (b0 != 0 && b1 != 0) { - (success, data) = pool.staticcall(abi.encodeWithSelector(info.dyFuncInt128, srcTokenIndex, dstTokenIndex, b0)); - if (!success || data.length < 32) { - (success, data) = pool.staticcall(abi.encodeWithSelector(info.dyFuncUint256, uint128(srcTokenIndex), uint128(dstTokenIndex), b0)); - } - if (success && data.length >= 32) { // vyper could return redundant bytes - b1 = abi.decode(data, (uint256)); - ratesAndWeights.append(OraclePrices.OraclePrice(Math.mulDiv(b1, 1e18, b0), w)); - } + // choose the right abi: + uint8 poolType = _getPoolType(pools[k]); + bytes4 selector; + if (poolType == 0 && isUnderlying) { + selector = IStableSwapMeta.get_dy_underlying.selector; + } else if (poolType == 0 && !isUnderlying) { + selector = IStableSwap.get_dy.selector; + } else { + selector = ICryptoSwap.get_dy.selector; + } + (bool success, bytes memory data) = pools[k].staticcall(abi.encodeWithSelector(selector, uint128(i), uint128(j), balances[uint128(i)])); + if (success && data.length >= 32) { // vyper could return redundant bytes + uint256 amountOut = abi.decode(data, (uint256)); + if (amountOut > 0) { + rate = amountOut * 1e18 / balances[uint128(i)]; + weight = (balances[uint128(i)] * balances[uint128(j)]).sqrt(); + ratesAndWeights.append(OraclePrices.OraclePrice(rate, weight)); } - pool = registries[i].find_pool_for_coins(address(srcToken), address(dstToken), ++registryIndex); } } - (rate, weight) = ratesAndWeights.getRateAndWeight(thresholdFilter); + return ratesAndWeights.getRateAndWeight(thresholdFilter); } } diff --git a/test/oracles/CurveOracle.js b/test/oracles/CurveOracle.js index 99299e3..ee74a85 100644 --- a/test/oracles/CurveOracle.js +++ b/test/oracles/CurveOracle.js @@ -12,7 +12,7 @@ const { describe('CurveOracle', function () { async function initContracts () { - const curveOracle = await deployContract('CurveOracle', [Curve.provider, Curve.maxPools, Curve.registryIds, Curve.registryTypes, [], constants.EEE_ADDRESS]); + const curveOracle = await deployContract('CurveOracle', ['0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC']); const uniswapV3Oracle = await deployContract('UniswapV3LikeOracle', [UniswapV3.factory, UniswapV3.initcodeHash, UniswapV3.fees]); return { curveOracle, uniswapV3Oracle }; } @@ -32,6 +32,11 @@ describe('CurveOracle', function () { await testRate(tokens.WBTC, tokens.WETH, tokens.NONE, curveOracle, uniswapV3Oracle); }); + it('USDT -> USDC', async function () { + const { curveOracle, uniswapV3Oracle } = await loadFixture(initContracts); + await testRate(tokens.USDT, tokens.USDC, tokens.NONE, curveOracle, uniswapV3Oracle); + }); + it('should use correct `get_dy` selector when vyper return redundant bytes', async function () { const { curveOracle } = await loadFixture(initContracts); const rate = await curveOracle.getRate(tokens.BEAN, tokens['3CRV'], tokens.NONE, thresholdFilter); From ce9d889632fd75de9d6c24ac5d3ea90fbb17d2b5 Mon Sep 17 00:00:00 2001 From: Denis Date: Fri, 2 Aug 2024 21:04:24 +0100 Subject: [PATCH 2/5] Cleanup and add some checks --- contracts/interfaces/ICurveMetaregistry.sol | 11 +++ contracts/interfaces/ICurvePool.sol | 22 ++++++ contracts/interfaces/ICurveRegistry.sol | 27 ------- contracts/interfaces/ICurveSwap.sol | 17 ----- contracts/oracles/CurveOracle.sol | 54 +++++-------- test/helpers.js | 25 +----- test/oracles/CurveOracle.js | 84 +-------------------- 7 files changed, 55 insertions(+), 185 deletions(-) create mode 100644 contracts/interfaces/ICurveMetaregistry.sol create mode 100644 contracts/interfaces/ICurvePool.sol delete mode 100644 contracts/interfaces/ICurveRegistry.sol delete mode 100644 contracts/interfaces/ICurveSwap.sol diff --git a/contracts/interfaces/ICurveMetaregistry.sol b/contracts/interfaces/ICurveMetaregistry.sol new file mode 100644 index 0000000..5b01070 --- /dev/null +++ b/contracts/interfaces/ICurveMetaregistry.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.23; + +// solhint-disable func-name-mixedcase + +interface ICurveMetaregistry { + function find_pools_for_coins(address srcToken, address dstToken) external view returns (address[] memory); + function get_coin_indices(address _pool, address _from, address _to) external view returns (int128, int128, bool); + function get_underlying_balances(address _pool) external view returns (uint256[8] memory); +} diff --git a/contracts/interfaces/ICurvePool.sol b/contracts/interfaces/ICurvePool.sol new file mode 100644 index 0000000..3f473a8 --- /dev/null +++ b/contracts/interfaces/ICurvePool.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.23; + +// solhint-disable func-name-mixedcase + +interface ICurvePool { + function allowed_extra_profit() external view returns (uint256); + function get_rate_mul() external view returns (uint256); +} + +interface IStableSwapMeta { + function get_dy_underlying(int128,int128,uint256) external view returns (uint256); +} + +interface IStableSwap { + function get_dy(int128,int128,uint256) external view returns (uint256); +} + +interface ICryptoSwap { + function get_dy(uint256,uint256,uint256) external view returns (uint256); +} diff --git a/contracts/interfaces/ICurveRegistry.sol b/contracts/interfaces/ICurveRegistry.sol deleted file mode 100644 index 3f3141b..0000000 --- a/contracts/interfaces/ICurveRegistry.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.23; - -// solhint-disable func-name-mixedcase - -interface ICurveRegistry { - function pool_count() external view returns (uint256); - function pool_list(uint256 index) external view returns (address); - - // MAIN_REGISTRY, METAPOOL_FACTORY, CRYPTOSWAP_REGISTRY, CRYPTOPOOL_FACTORY, METAREGISTRY, CRVUSD_PLAIN_POOLS, CURVE_TRICRYPTO_FACTORY - function find_pool_for_coins(address _srcToken, address _dstToken, uint256 _index) external view returns (address); - - // MAIN_REGISTRY, METAPOOL_FACTORY, METAREGISTRY, CRVUSD_PLAIN_POOLS - function get_coin_indices(address _pool, address _srcToken, address _dstToken) external view returns (int128, int128, bool); - // CRYPTOSWAP_REGISTRY, CRYPTOPOOL_FACTORY, CURVE_TRICRYPTO_FACTORY - returns (uint256,uint256); - - // MAIN_REGISTRY, CRYPTOSWAP_REGISTRY, METAREGISTRY - function get_balances(address _pool) external view returns (uint256[8] memory); - // METAPOOL_FACTORY, CRVUSD_PLAIN_POOLS - returns (uint256[4]); - // CURVE_TRICRYPTO_FACTORY - returns (uint256[3]); - // CRYPTOPOOL_FACTORY - returns (uint256[2]); - - // MAIN_REGISTRY, METAPOOL_FACTORY, METAREGISTRY, CRVUSD_PLAIN_POOLS - function get_underlying_balances(address _pool) external view returns (uint256[8] memory); - // CRYPTOSWAP_REGISTRY, CRYPTOPOOL_FACTORY - NO METHOD -} diff --git a/contracts/interfaces/ICurveSwap.sol b/contracts/interfaces/ICurveSwap.sol deleted file mode 100644 index 48ca591..0000000 --- a/contracts/interfaces/ICurveSwap.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MIT - -// solhint-disable one-contract-per-file - -pragma solidity 0.8.23; - -// solhint-disable func-name-mixedcase - -interface ICurveSwapInt128 { - function get_dy(int128 _from, int128 _to, uint256 _amount) external view returns (uint256); - function get_dy_underlying(int128 _from, int128 _to, uint256 _amount) external view returns (uint256); -} - -interface ICurveSwapUint256 { - function get_dy(uint256 _from, uint256 _to, uint256 _amount) external view returns (uint256); - function get_dy_underlying(uint256 _from, uint256 _to, uint256 _amount) external view returns (uint256); -} diff --git a/contracts/oracles/CurveOracle.sol b/contracts/oracles/CurveOracle.sol index 4138d71..78d6d68 100644 --- a/contracts/oracles/CurveOracle.sol +++ b/contracts/oracles/CurveOracle.sol @@ -6,33 +6,9 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; import "../interfaces/IOracle.sol"; import "../interfaces/ICurveProvider.sol"; -import "../interfaces/ICurveRegistry.sol"; -import "../interfaces/ICurveSwap.sol"; +import "../interfaces/ICurveMetaregistry.sol"; +import "../interfaces/ICurvePool.sol"; import "../libraries/OraclePrices.sol"; -import "../helpers/Blacklist.sol"; - -interface IMetaregistry { - function find_pools_for_coins(address srcToken, address dstToken) external view returns (address[] memory); - function get_coin_indices(address _pool, address _from, address _to) external view returns (int128, int128, bool); - function get_underlying_balances(address _pool) external view returns (uint256[8] memory); -} - -interface ICurvePool { - function allowed_extra_profit() external view returns (uint256); - function get_rate_mul() external view returns (uint256); -} - -interface IStableSwapMeta { - function get_dy_underlying(int128,int128,uint256) external view returns (uint256); -} - -interface IStableSwap { - function get_dy(int128,int128,uint256) external view returns (uint256); -} - -interface ICryptoSwap { - function get_dy(uint256,uint256,uint256) external view returns (uint256); -} contract CurveOracle is IOracle { using OraclePrices for OraclePrices.Data; @@ -40,11 +16,14 @@ contract CurveOracle is IOracle { IERC20 private constant _NONE = IERC20(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF); IERC20 private constant _ETH = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); + uint256 private constant _METAREGISTRY_ID = 7; - IMetaregistry public immutable CURVE_METAREGISTRY; + ICurveMetaregistry public immutable CURVE_METAREGISTRY; + uint256 public immutable MAX_POOLS; - constructor(IMetaregistry curveMetaregistry) { - CURVE_METAREGISTRY = curveMetaregistry; + constructor(ICurveProvider curveProvider, uint256 maxPools) { + CURVE_METAREGISTRY = ICurveMetaregistry(curveProvider.get_address(_METAREGISTRY_ID)); + MAX_POOLS = maxPools; } function _getPoolType(address pool) private view returns (uint8) { @@ -73,8 +52,15 @@ contract CurveOracle is IOracle { return (0, 0); } + uint256 amountIn; + if (srcToken == _ETH) { + amountIn = 10**18; + } else { + amountIn = 10**IERC20Metadata(address(srcToken)).decimals(); + } + OraclePrices.Data memory ratesAndWeights = OraclePrices.init(pools.length); - for (uint256 k = 0; k < pools.length; k++) { + for (uint256 k = 0; k < pools.length && ratesAndWeights.size < MAX_POOLS; k++) { // get coin indices int128 i; int128 j; @@ -84,9 +70,7 @@ contract CurveOracle is IOracle { // get balances uint256[8] memory balances = CURVE_METAREGISTRY.get_underlying_balances(pools[k]); // skip if pool is too small - balances[uint128(i)] = balances[uint128(i)] / 1e4; - balances[uint128(j)] = balances[uint128(j)] / 1e4; - if (balances[uint128(i)] == 0 || balances[uint128(j)] == 0) { + if (balances[uint128(i)] <= amountIn || balances[uint128(j)] == 0) { continue; } @@ -100,11 +84,11 @@ contract CurveOracle is IOracle { } else { selector = ICryptoSwap.get_dy.selector; } - (bool success, bytes memory data) = pools[k].staticcall(abi.encodeWithSelector(selector, uint128(i), uint128(j), balances[uint128(i)])); + (bool success, bytes memory data) = pools[k].staticcall(abi.encodeWithSelector(selector, uint128(i), uint128(j), amountIn)); if (success && data.length >= 32) { // vyper could return redundant bytes uint256 amountOut = abi.decode(data, (uint256)); if (amountOut > 0) { - rate = amountOut * 1e18 / balances[uint128(i)]; + rate = amountOut * 1e18 / amountIn; weight = (balances[uint128(i)] * balances[uint128(j)]).sqrt(); ratesAndWeights.append(OraclePrices.OraclePrice(rate, weight)); } diff --git a/test/helpers.js b/test/helpers.js index 66a9a92..0565d8f 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -82,19 +82,6 @@ const contracts = { chaiPot: '0x197E90f9FAD81970bA7976f33CbD77088E5D7cf7', }; -const CurveRegistryType = { - MAIN_REGISTRY: 0, - METAPOOL_FACTORY: 1, - CRYPTOSWAP_REGISTRY: 2, - CRYPTOPOOL_FACTORY: 3, - METAREGISTRY: 4, - CRVUSD_PLAIN_POOLS: 5, - CURVE_TRICRYPTO_FACTORY: 6, - STABLESWAP_FACTORY: 7, - L2_FACTORY: 8, - CRYPTO_FACTORY: 9, -}; - const deployParams = { AaveWrapperV2: { lendingPool: '0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9', @@ -129,18 +116,8 @@ const deployParams = { initcodeHash: '0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f', }, Curve: { - provider: '0x0000000022D53366457F9d5E68Ec105046FC4383', + provider: '0x5ffe7FB82894076ECB99A30D6A32e969e6e35E98', maxPools: 100, - registryIds: [0, 3, 5, 6, 7, 8, 11], - registryTypes: [ - CurveRegistryType.MAIN_REGISTRY, - CurveRegistryType.METAPOOL_FACTORY, - CurveRegistryType.CRYPTOSWAP_REGISTRY, - CurveRegistryType.CRYPTOPOOL_FACTORY, - CurveRegistryType.METAREGISTRY, - CurveRegistryType.CRVUSD_PLAIN_POOLS, - CurveRegistryType.CURVE_TRICRYPTO_FACTORY, - ], }, Dodo: { dodoZoo: '0x3A97247DF274a17C59A3bd12735ea3FcDFb49950', diff --git a/test/oracles/CurveOracle.js b/test/oracles/CurveOracle.js index ee74a85..0be1cf5 100644 --- a/test/oracles/CurveOracle.js +++ b/test/oracles/CurveOracle.js @@ -12,7 +12,7 @@ const { describe('CurveOracle', function () { async function initContracts () { - const curveOracle = await deployContract('CurveOracle', ['0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC']); + const curveOracle = await deployContract('CurveOracle', [Curve.provider, Curve.maxPools]); const uniswapV3Oracle = await deployContract('UniswapV3LikeOracle', [UniswapV3.factory, UniswapV3.initcodeHash, UniswapV3.fees]); return { curveOracle, uniswapV3Oracle }; } @@ -63,92 +63,12 @@ describe('CurveOracle', function () { }); }); - describe('Doesn\'t ruin various registry with different selectors', function () { - it('Main Registry', async function () { - await testNotRuins(0, 2n); - }); - - it('Metapool Factory', async function () { - await testNotRuins(1, 2n); - }); - - it('Cryptoswap Registry', async function () { - await testNotRuins(2, 2n); - }); - - it('Cryptopool Factory', async function () { - await testNotRuins(3, 2n); - }); - - it('Metaregistry', async function () { - await testNotRuins(4, 2n); - }); - - it('crvUSD Plain Pools', async function () { - await testNotRuins(5, 2n); - }); - - it('Curve Tricrypto Factory', async function () { - await testNotRuins(6, 2n); - }); - - async function testNotRuins (registryIndex, testPoolsAmount) { - const poolAbiUint256 = [ - { - name: 'coins', - type: 'function', - inputs: [{ type: 'uint256', name: 'arg0' }], - outputs: [{ type: 'address', name: 'value' }], - stateMutability: 'view', - }, - ]; - const poolAbiInt128 = [ - { - name: 'coins', - type: 'function', - inputs: [{ type: 'int128', name: 'arg0' }], - outputs: [{ type: 'address', name: 'value' }], - stateMutability: 'view', - }, - ]; - - const curveOracle = await deployContract('CurveOracle', [ - Curve.provider, Curve.maxPools, [Curve.registryIds[registryIndex]], [Curve.registryTypes[registryIndex]], [], constants.EEE_ADDRESS, - ]); - const curveProvider = await ethers.getContractAt('ICurveProvider', Curve.provider); - const registryAddress = await curveProvider.get_address(Curve.registryIds[registryIndex]); - const registry = await ethers.getContractAt('ICurveRegistry', registryAddress); - - const poolCount = await registry.pool_count(); - - // we check only `testPoolsAmount` random pools from the registry to save time - for (let i = 0n; i < poolCount; i += (poolCount / testPoolsAmount)) { - const poolAddress = await registry.pool_list(i); - let token0, token1; - try { - const poolUint256 = await ethers.getContractAt(poolAbiUint256, poolAddress); - token0 = await poolUint256.coins(0); - token1 = await poolUint256.coins(1); - } catch (e) { - try { - const poolInt128 = await ethers.getContractAt(poolAbiInt128, poolAddress); - token0 = await poolInt128.coins(0); - token1 = await poolInt128.coins(1); - } catch (e) { - expect.fail(`pool ${i} ${poolAddress} doesn't work with uint256 and int128 selectors of \`coins\` method`); - } - } - await curveOracle.getRate(token0, token1, tokens.NONE, thresholdFilter); - } - } - }); - describe('CurveOracle doesn\'t ruin rates', function () { async function initContracts () { const [deployer] = await ethers.getSigners(); const uniswapV2LikeOracle = await deployContract('UniswapV2LikeOracle', [UniswapV2.factory, UniswapV2.initcodeHash]); - const curveOracle = await deployContract('CurveOracle', [Curve.provider, Curve.maxPools, Curve.registryIds, Curve.registryTypes, [], constants.EEE_ADDRESS]); + const curveOracle = await deployContract('CurveOracle', [Curve.provider, Curve.maxPools]); const uniswapOracle = await deployContract('UniswapOracle', [Uniswap.factory]); const mooniswapOracle = await deployContract('MooniswapOracle', [tokens.oneInchLP1]); const wethWrapper = await deployContract('BaseCoinWrapper', [tokens.ETH, tokens.WETH]); From 8fef2351bbf6200c9c366d95862b626221618c3e Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 7 Aug 2024 17:37:54 +0100 Subject: [PATCH 3/5] Add measure gas test for current implementation --- test/OffchainOracle.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/OffchainOracle.js b/test/OffchainOracle.js index 18c4107..318cb96 100644 --- a/test/OffchainOracle.js +++ b/test/OffchainOracle.js @@ -1,4 +1,5 @@ const hre = require('hardhat'); +const fs = require('fs'); const { ethers } = hre; const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); const { expect, ether, assertRoughlyEqualValues, deployContract } = require('@1inch/solidity-utils'); @@ -262,5 +263,37 @@ describe('OffchainOracle', function () { const rateReverse = await offchainOracle.getRateWithCustomConnectors(tokens.wstETH, tokens.WETH, true, [], thresholdFilter); assertRoughlyEqualValues(rateForward * rateReverse / BigInt(1e18), BigInt(1e18), 5e-18); }); + + it.skip('measure gas of current implementation', async function () { + // NOTE: This test is skipped because it is too slow. Change hardhat config `mocha > timeout > 360000` to run it. + const gasEstimator = await deployContract('GasEstimator'); + + const offchainOracleDeployment = JSON.parse(fs.readFileSync('deployments/mainnet/OffchainOracle.json', 'utf8')); + const offchainOracle = await ethers.getContractAt('OffchainOracle', offchainOracleDeployment.address); + + // Uncomment and edit it to test with replaced oracles + // const [,account] = await ethers.getSigners(); + // const ownerAddress = offchainOracleDeployment.args[5]; + // const curveOracle = await deployContract('CurveOracle', [Curve.provider, Curve.maxPools]); + // await account.sendTransaction({ to: ownerAddress, value: ether('100') }); + // const owner = await ethers.getImpersonatedSigner(ownerAddress); + // const curveOracleDeployment = JSON.parse(fs.readFileSync(`deployments/mainnet/CurveOracle.json`, 'utf8')); + // await offchainOracle.connect(owner).removeOracle(curveOracleDeployment.address, '0'); + // await offchainOracle.connect(owner).addOracle(curveOracle, '0'); + + const getRateToEthResult = await gasEstimator.gasCost( + await offchainOracle.getAddress(), + offchainOracle.interface.encodeFunctionData('getRateToEthWithThreshold', [tokens.DAI, true, thresholdFilter]), + ); + console.log(`OffchainOracle getRateToEthWithThreshold(DAI,true,${thresholdFilter}): ${getRateToEthResult.gasUsed}`); + expect(getRateToEthResult.success).to.eq(true); + + const getRateResult = await gasEstimator.gasCost( + await offchainOracle.getAddress(), + offchainOracle.interface.encodeFunctionData('getRateWithThreshold', [tokens.USDC, tokens.USDe, true, thresholdFilter]), + ); + console.log(`OffchainOracle getRateWithThreshold(USDC,USDe,true,${thresholdFilter}): ${getRateResult.gasUsed}`); + expect(getRateResult.success).to.eq(true); + }); }); }); From c29ba76f62009068c3a02f771b5c350f529f3ae7 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 7 Aug 2024 17:49:52 +0100 Subject: [PATCH 4/5] Fix linter --- test/oracles/CurveOracle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/oracles/CurveOracle.js b/test/oracles/CurveOracle.js index 0be1cf5..5ae3d5e 100644 --- a/test/oracles/CurveOracle.js +++ b/test/oracles/CurveOracle.js @@ -1,6 +1,6 @@ const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); const { ethers } = require('hardhat'); -const { expect, deployContract, constants } = require('@1inch/solidity-utils'); +const { expect, deployContract } = require('@1inch/solidity-utils'); const { tokens, deployParams: { AaveWrapperV2, Curve, Uniswap, UniswapV2, UniswapV3 }, From 773783073a51afc214c3611b85f303b882c2c003 Mon Sep 17 00:00:00 2001 From: Denis Date: Wed, 7 Aug 2024 18:37:32 +0100 Subject: [PATCH 5/5] Fix tests --- test/oracles/SlipstreamOracle.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/oracles/SlipstreamOracle.js b/test/oracles/SlipstreamOracle.js index dd7e0d4..b3f8d5b 100644 --- a/test/oracles/SlipstreamOracle.js +++ b/test/oracles/SlipstreamOracle.js @@ -45,14 +45,14 @@ describe('SlipstreamOracle', function () { await testRate(tokens.optimistic.WETH, tokens.optimistic.OP, tokens.NONE, uniswapV3Oracle, slipstreamOracle); }); - it('WETH -> USDC -> DAI', async function () { + it('WETH -> USDC -> OP', async function () { const { uniswapV3Oracle, slipstreamOracle } = await loadFixture(initContracts); - await testRate(tokens.optimistic.WETH, tokens.optimistic.DAI, tokens.optimistic.USDC, uniswapV3Oracle, slipstreamOracle); + await testRate(tokens.optimistic.WETH, tokens.optimistic.OP, tokens.optimistic.USDC, uniswapV3Oracle, slipstreamOracle); }); - it('DAI -> USDC -> WETH', async function () { + it('OP -> USDC -> WETH', async function () { const { uniswapV3Oracle, slipstreamOracle } = await loadFixture(initContracts); - await testRate(tokens.optimistic.DAI, tokens.optimistic.WETH, tokens.optimistic.USDC, uniswapV3Oracle, slipstreamOracle); + await testRate(tokens.optimistic.OP, tokens.optimistic.WETH, tokens.optimistic.USDC, uniswapV3Oracle, slipstreamOracle); }); describe('Measure gas', function () {