diff --git a/src/modules/price-router/Extensions/Curve/Curve2PoolExtension.sol b/src/modules/price-router/Extensions/Curve/Curve2PoolExtension.sol index 56c31fac2..61413db44 100644 --- a/src/modules/price-router/Extensions/Curve/Curve2PoolExtension.sol +++ b/src/modules/price-router/Extensions/Curve/Curve2PoolExtension.sol @@ -37,6 +37,18 @@ contract Curve2PoolExtension is Extension { address pool; bool isCorrelated; // TODO if we store the coins0 and coins1 here then we can store the underlying or constituent, and or rate provider contracts too. + // kinda like the above idea of saving the underlying or constituent in here.... + } + + struct NewExtensionStorage { + address pool; + address underlyingOrConstituent0; + address underlyingOrConstituent1; + bool divideRate0; // If we only have the market price of the underlying, and there is a rate with the underlying, then divide out the rate + bool divideRate1; // If we only new the safe price of sDAI, then we need to divide out the rate stored in the curve pool + bool isCorrelated; // but if we know the safe market price of DAI then we can just use that. + // TODO if we store the coins0 and coins1 here then we can store the underlying or constituent, and or rate provider contracts too. + // kinda like the above idea of saving the underlying or constituent in here.... } /** @@ -96,7 +108,11 @@ contract Curve2PoolExtension is Extension { uint256 price1 = priceRouter.getPriceInUSD(getCoins(pool, 1)); uint256 minPrice = price0 < price1 ? price0 : price1; price = minPrice.mulDivDown(pool.get_virtual_price(), 10 ** curveDecimals); + // TODO add in new underlying or constituent logic here with rates. } else { + // TODO I wonder if rates would also need to come up here, like if this new pool ever gave the lp_price without adjusting for the rate, then we would need to? + // TODO ^^^ I dont think this makes sense cuz the point of it acconting for the rate is to keep the liquidity concentrated at that rate + // but you can only do that with correlated pairs. price = pool.lp_price().mulDivDown(priceRouter.getPriceInUSD(getCoins(pool, 0)), 10 ** curveDecimals); } } diff --git a/src/modules/price-router/Extensions/Curve/CurveEMAExtension.sol b/src/modules/price-router/Extensions/Curve/CurveEMAExtension.sol index 38c2d66a2..9c89d2cd7 100644 --- a/src/modules/price-router/Extensions/Curve/CurveEMAExtension.sol +++ b/src/modules/price-router/Extensions/Curve/CurveEMAExtension.sol @@ -89,10 +89,12 @@ contract CurveEMAExtension is Extension { return address(coins0) == CURVE_ETH ? WETH : coins0; } + // TODO this code needs to change so that is can optionally handle tokens with rates, and basically take the price_oracle value and multiply by the rate. + // Examples are ETHx, sDAI, sFRAX. /** * @notice Helper function to get the price of an asset using a Curve EMA Oracle. */ - function getPriceFromCurvePool(CurvePool pool, uint8 index, bool needIndex) public view returns (uint256) { + function getPriceFromCurvePool(CurvePool pool, uint8 index, bool needIndex) public view returns (uint256 price) { return needIndex ? pool.price_oracle(index) : pool.price_oracle(); } } diff --git a/test/resources/MainnetAddresses.sol b/test/resources/MainnetAddresses.sol index 1ab33fc7a..42d879ec0 100644 --- a/test/resources/MainnetAddresses.sol +++ b/test/resources/MainnetAddresses.sol @@ -51,6 +51,9 @@ contract MainnetAddresses { ERC20 public CVX = ERC20(0x4e3FBD56CD56c3e72c1403e103b45Db9da5B9D2B); ERC20 public FRXETH = ERC20(0x5E8422345238F34275888049021821E8E08CAa1f); ERC20 public CRVUSD = ERC20(0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E); + ERC20 public OETH = ERC20(0x856c4Efb76C1D1AE02e20CEB03A2A6a08b0b8dC3); + ERC20 public MKUSD = ERC20(0x4591DBfF62656E7859Afe5e45f6f47D3669fBB28); + ERC20 public YETH = ERC20(0x1BED97CBC3c24A4fb5C069C6E311a967386131f7); // Chainlink Datafeeds address public WETH_USD_FEED = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; @@ -206,6 +209,23 @@ contract MainnetAddresses { address public WethCvxPool = 0xB576491F1E6e5E62f1d8F26062Ee822B40B0E0d4; address public WethCvxToken = 0x3A283D9c08E8b55966afb64C515f5143cf907611; address public WethCvxGauge = 0x7E1444BA99dcdFfE8fBdb42C02F0005D14f13BE1; + address public EthStethNgPool = 0x21E27a5E5513D6e65C4f830167390997aA84843a; + address public EthStethNgToken = 0x21E27a5E5513D6e65C4f830167390997aA84843a; + address public EthStethNgGauge = 0x79F21BC30632cd40d2aF8134B469a0EB4C9574AA; + address public EthOethPool = 0x94B17476A93b3262d87B9a326965D1E91f9c13E7; + address public EthOethToken = 0x94B17476A93b3262d87B9a326965D1E91f9c13E7; + address public EthOethGauge = 0xd03BE91b1932715709e18021734fcB91BB431715; + address public FraxCrvUsdPool = 0x0CD6f267b2086bea681E922E19D40512511BE538; + address public FraxCrvUsdToken = 0x0CD6f267b2086bea681E922E19D40512511BE538; + address public FraxCrvUsdGauge = 0x96424E6b5eaafe0c3B36CA82068d574D44BE4e3c; + address public mkUsdFraxUsdcPool = 0x0CFe5C777A7438C9Dd8Add53ed671cEc7A5FAeE5; + address public mkUsdFraxUsdcToken = 0x0CFe5C777A7438C9Dd8Add53ed671cEc7A5FAeE5; + address public mkUsdFraxUsdcGauge = 0xF184d80915Ba7d835D941BA70cDdf93DE36517ee; + address public WethYethPool = 0x69ACcb968B19a53790f43e57558F5E443A91aF22; + address public WethYethToken = 0x69ACcb968B19a53790f43e57558F5E443A91aF22; + address public WethYethGauge = 0x138cC21D15b7A06F929Fc6CFC88d2b830796F4f1; + + address public WethMkUsdPool = 0xc89570207c5BA1B0E3cD372172cCaEFB173DB270; // Uniswap V3 address public WSTETH_WETH_100 = 0x109830a1AAaD605BbF02a9dFA7B0B92EC2FB7dAa; diff --git a/test/testAdaptors/CurveAdaptor.t.sol b/test/testAdaptors/CurveAdaptor.t.sol index f23058f5b..60bbf50ff 100644 --- a/test/testAdaptors/CurveAdaptor.t.sol +++ b/test/testAdaptors/CurveAdaptor.t.sol @@ -37,6 +37,9 @@ contract CurveAdaptorTest is MainnetStarterTest, AdaptorHelperFunctions { uint32 private fraxPosition = 7; uint32 private frxethPosition = 8; uint32 private cvxPosition = 9; + uint32 private oethPosition = 21; + uint32 private mkUsdPosition = 23; + uint32 private yethPosition = 25; uint32 private UsdcCrvUsdPoolPosition = 10; uint32 private WethRethPoolPosition = 11; uint32 private UsdtCrvUsdPoolPosition = 12; @@ -46,6 +49,11 @@ contract CurveAdaptorTest is MainnetStarterTest, AdaptorHelperFunctions { uint32 private EthFrxethPoolPosition = 16; uint32 private StethFrxethPoolPosition = 17; uint32 private WethCvxPoolPosition = 18; + uint32 private EthStethNgPoolPosition = 19; + uint32 private EthOethPoolPosition = 20; + uint32 private fraxCrvUsdPoolPosition = 22; + uint32 private mkUsdFraxUsdcPoolPosition = 24; + uint32 private WethYethPoolPosition = 26; uint32 private slippage = 0.9e4; uint256 public initialAssets; @@ -140,6 +148,33 @@ contract CurveAdaptorTest is MainnetStarterTest, AdaptorHelperFunctions { settings = PriceRouter.AssetSettings(EXTENSION_DERIVATIVE, address(curveEMAExtension)); priceRouter.addAsset(CVX, settings, abi.encode(cStor), price); + // Add OETH + cStor.pool = EthOethPool; + cStor.index = 0; + cStor.needIndex = false; + price = curveEMAExtension.getPriceFromCurvePool(CurvePool(cStor.pool), cStor.index, cStor.needIndex); + price = price.mulDivDown(priceRouter.getPriceInUSD(WETH), 1e18); + settings = PriceRouter.AssetSettings(EXTENSION_DERIVATIVE, address(curveEMAExtension)); + priceRouter.addAsset(OETH, settings, abi.encode(cStor), price); + + // Add mkUsd + cStor.pool = WethMkUsdPool; + cStor.index = 0; + cStor.needIndex = false; + price = curveEMAExtension.getPriceFromCurvePool(CurvePool(cStor.pool), cStor.index, cStor.needIndex); + price = price.mulDivDown(priceRouter.getPriceInUSD(WETH), 1e18); + settings = PriceRouter.AssetSettings(EXTENSION_DERIVATIVE, address(curveEMAExtension)); + priceRouter.addAsset(MKUSD, settings, abi.encode(cStor), price); + + // Add yETH + cStor.pool = WethYethPool; + cStor.index = 0; + cStor.needIndex = false; + price = curveEMAExtension.getPriceFromCurvePool(CurvePool(cStor.pool), cStor.index, cStor.needIndex); + price = price.mulDivDown(priceRouter.getPriceInUSD(WETH), 1e18); + settings = PriceRouter.AssetSettings(EXTENSION_DERIVATIVE, address(curveEMAExtension)); + priceRouter.addAsset(YETH, settings, abi.encode(cStor), price); + // Add 2pools. // UsdcCrvUsdPool // UsdcCrvUsdToken @@ -177,6 +212,26 @@ contract CurveAdaptorTest is MainnetStarterTest, AdaptorHelperFunctions { // WethCvxToken // WethCvxGauge _add2PoolAssetToPriceRouter(WethCvxPool, WethCvxToken, false, 154e8); + // EthStethNgPool + // EthStethNgToken + // EthStethNgGauge + _add2PoolAssetToPriceRouter(EthStethNgPool, EthStethNgToken, true, 1_800e8); + // EthOethPool + // EthOethToken + // EthOethGauge + _add2PoolAssetToPriceRouter(EthOethPool, EthOethToken, true, 1_800e8); + // FraxCrvUsdPool + // FraxCrvUsdToken + // FraxCrvUsdGauge + _add2PoolAssetToPriceRouter(FraxCrvUsdPool, FraxCrvUsdToken, true, 1e8); + // mkUsdFraxUsdcPool + // mkUsdFraxUsdcToken + // mkUsdFraxUsdcGauge + _add2PoolAssetToPriceRouter(mkUsdFraxUsdcPool, mkUsdFraxUsdcToken, true, 1e8); + // WethYethPool + // WethYethToken + // WethYethGauge + _add2PoolAssetToPriceRouter(WethYethPool, WethYethToken, true, 1_800e8); // Add positions to registry. registry.trustAdaptor(address(curveAdaptor)); @@ -190,6 +245,9 @@ contract CurveAdaptorTest is MainnetStarterTest, AdaptorHelperFunctions { registry.trustPosition(fraxPosition, address(erc20Adaptor), abi.encode(FRAX)); registry.trustPosition(frxethPosition, address(erc20Adaptor), abi.encode(FRXETH)); registry.trustPosition(cvxPosition, address(erc20Adaptor), abi.encode(CVX)); + registry.trustPosition(oethPosition, address(erc20Adaptor), abi.encode(OETH)); + registry.trustPosition(mkUsdPosition, address(erc20Adaptor), abi.encode(MKUSD)); + registry.trustPosition(yethPosition, address(erc20Adaptor), abi.encode(YETH)); registry.trustPosition( UsdcCrvUsdPoolPosition, @@ -237,6 +295,41 @@ contract CurveAdaptorTest is MainnetStarterTest, AdaptorHelperFunctions { abi.encode(WethCvxPool, WethCvxToken, WethCvxGauge, CurvePool.claim_admin_fees.selector) ); + registry.trustPosition( + EthStethNgPoolPosition, + address(curveAdaptor), + abi.encode(EthStethNgPool, EthStethNgToken, EthStethNgGauge, CurvePool.withdraw_admin_fees.selector) + ); + + registry.trustPosition( + EthOethPoolPosition, + address(curveAdaptor), + abi.encode(EthOethPool, EthOethToken, EthOethGauge, CurvePool.withdraw_admin_fees.selector) + ); + + registry.trustPosition( + fraxCrvUsdPoolPosition, + address(curveAdaptor), + abi.encode(FraxCrvUsdPool, FraxCrvUsdToken, FraxCrvUsdGauge, CurvePool.withdraw_admin_fees.selector) + ); + + registry.trustPosition( + mkUsdFraxUsdcPoolPosition, + address(curveAdaptor), + abi.encode( + mkUsdFraxUsdcPool, + mkUsdFraxUsdcToken, + mkUsdFraxUsdcGauge, + CurvePool.withdraw_admin_fees.selector + ) + ); + + registry.trustPosition( + WethYethPoolPosition, + address(curveAdaptor), + abi.encode(WethYethPool, WethYethToken, WethYethGauge, CurvePool.withdraw_admin_fees.selector) + ); + string memory cellarName = "Curve Cellar V0.0"; uint256 initialDeposit = 1e6; uint64 platformCut = 0.75e18; @@ -265,8 +358,8 @@ contract CurveAdaptorTest is MainnetStarterTest, AdaptorHelperFunctions { USDC.safeApprove(address(cellar), type(uint256).max); - for (uint32 i = 2; i < 19; ++i) cellar.addPositionToCatalogue(i); - for (uint32 i = 2; i < 19; ++i) cellar.addPosition(0, i, abi.encode(false), false); + for (uint32 i = 2; i < 27; ++i) cellar.addPositionToCatalogue(i); + for (uint32 i = 2; i < 27; ++i) cellar.addPosition(0, i, abi.encode(false), false); cellar.setRebalanceDeviation(0.030e18); @@ -312,16 +405,41 @@ contract CurveAdaptorTest is MainnetStarterTest, AdaptorHelperFunctions { _manageLiquidityIn2PoolNoETH(assets, WethCvxPool, WethCvxToken, 0.0050e18); } - function testManagingLiquidityIn2PoolCorrelatedWithETH0(uint256 assets) external { + function testManagingLiquidityIn2PoolNoETH7(uint256 assets) external { + assets = bound(assets, 1e6, 100_000e6); + _manageLiquidityIn2PoolNoETH(assets, FraxCrvUsdPool, FraxCrvUsdToken, 0.0005e18); + } + + function testManagingLiquidityIn2PoolNoETH8(uint256 assets) external { + assets = bound(assets, 1e6, 100_000e6); + _manageLiquidityIn2PoolNoETH(assets, mkUsdFraxUsdcPool, mkUsdFraxUsdcToken, 0.0050e18); + } + + function testManagingLiquidityIn2PoolNoETH9(uint256 assets) external { + assets = bound(assets, 1e6, 100_000e6); + _manageLiquidityIn2PoolNoETH(assets, WethYethPool, WethYethToken, 0.0050e18); + } + + function testManagingLiquidityIn2PoolWithETH0(uint256 assets) external { assets = bound(assets, 1e6, 1_000_000e6); _manageLiquidityIn2PoolWithETH(assets, EthStethPool, EthStethToken, 0.0030e18); } - function testManagingLiquidityIn2PoolCorrelatedWithETH1(uint256 assets) external { + function testManagingLiquidityIn2PoolWithETH1(uint256 assets) external { assets = bound(assets, 1e6, 1_000_000e6); _manageLiquidityIn2PoolWithETH(assets, EthFrxethPool, EthFrxethToken, 0.0010e18); } + function testManagingLiquidityIn2PoolWithETH2(uint256 assets) external { + assets = bound(assets, 1e6, 1_000_000e6); + _manageLiquidityIn2PoolWithETH(assets, EthStethNgPool, EthStethNgToken, 0.0025e18); + } + + function testManagingLiquidityIn2PoolWithETH3(uint256 assets) external { + assets = bound(assets, 1e6, 1_000_000e6); + _manageLiquidityIn2PoolWithETH(assets, EthOethPool, EthOethToken, 0.0010e18); + } + // TODO for sDAI and sFRAX pools, I think that they are a special pool type, where there is no LP price, // so in pricing we need to either use the price of the underlying, or take the sDAI price, and divide out the rate. @@ -343,6 +461,7 @@ contract CurveAdaptorTest is MainnetStarterTest, AdaptorHelperFunctions { } else { assets = priceRouter.getValue(USDC, assets, coins0); if (coins0 == STETH) _takeSteth(assets, address(cellar)); + else if (coins0 == OETH) _takeOeth(assets, address(cellar)); else deal(address(coins0), address(cellar), assets); } deal(address(USDC), address(cellar), 0); @@ -383,8 +502,10 @@ contract CurveAdaptorTest is MainnetStarterTest, AdaptorHelperFunctions { orderedTokenAmounts[0] = assets / 4; orderedTokenAmounts[1] = coins1Amount; if (coins0 == STETH) _takeSteth(assets / 4, address(cellar)); + else if (coins0 == OETH) _takeOeth(assets / 4, address(cellar)); else deal(address(coins0), address(cellar), assets / 4); if (coins1 == STETH) _takeSteth(coins1Amount, address(cellar)); + else if (coins1 == OETH) _takeOeth(coins1Amount, address(cellar)); else deal(address(coins1), address(cellar), coins1Amount); } { @@ -454,6 +575,7 @@ contract CurveAdaptorTest is MainnetStarterTest, AdaptorHelperFunctions { } else { assets = priceRouter.getValue(USDC, assets, coins0); if (coins0 == STETH) _takeSteth(assets, address(cellar)); + else if (coins0 == OETH) _takeOeth(assets, address(cellar)); else deal(address(coins0), address(cellar), assets); } deal(address(USDC), address(cellar), 0); @@ -504,8 +626,10 @@ contract CurveAdaptorTest is MainnetStarterTest, AdaptorHelperFunctions { orderedTokenAmounts[0] = assets / 4; orderedTokenAmounts[1] = coins1Amount; if (coins0 == STETH) _takeSteth(assets / 4, address(cellar)); + else if (coins0 == OETH) _takeOeth(assets / 4, address(cellar)); else deal(address(coins0), address(cellar), assets / 4); if (coins1 == STETH) _takeSteth(coins1Amount, address(cellar)); + else if (coins1 == OETH) _takeOeth(coins1Amount, address(cellar)); else deal(address(coins1), address(cellar), coins1Amount); } { @@ -605,4 +729,11 @@ contract CurveAdaptorTest is MainnetStarterTest, AdaptorHelperFunctions { vm.prank(stethWhale); STETH.safeTransfer(to, amount); } + + function _takeOeth(uint256 amount, address to) internal { + // STETH does not work with DEAL, so steal STETH from a whale. + address oethWhale = 0xEADB3840596cabF312F2bC88A4Bb0b93A4E1FF5F; + vm.prank(oethWhale); + OETH.safeTransfer(to, amount); + } }