From 5d1b03b3dc2b39b2a8109b2a7699a8b36793a90d Mon Sep 17 00:00:00 2001 From: kasperpawlowski Date: Thu, 23 Oct 2025 15:12:50 +0200 Subject: [PATCH] feat: deprecate curve oracle adapter --- src/adapter/curve/CurveEMAOracle.sol | 66 -------------- src/adapter/curve/ICurvePool.sol | 9 -- test/adapter/curve/CurveAddresses.sol | 10 --- test/adapter/curve/CurveEMAOracle.fork.t.sol | 95 -------------------- test/adapter/curve/CurveEMAOracle.prop.t.sol | 45 ---------- test/adapter/curve/CurveEMAOracle.unit.t.sol | 65 -------------- test/adapter/curve/CurveEMAOracleHelper.sol | 74 --------------- 7 files changed, 364 deletions(-) delete mode 100644 src/adapter/curve/CurveEMAOracle.sol delete mode 100644 src/adapter/curve/ICurvePool.sol delete mode 100644 test/adapter/curve/CurveAddresses.sol delete mode 100644 test/adapter/curve/CurveEMAOracle.fork.t.sol delete mode 100644 test/adapter/curve/CurveEMAOracle.prop.t.sol delete mode 100644 test/adapter/curve/CurveEMAOracle.unit.t.sol delete mode 100644 test/adapter/curve/CurveEMAOracleHelper.sol diff --git a/src/adapter/curve/CurveEMAOracle.sol b/src/adapter/curve/CurveEMAOracle.sol deleted file mode 100644 index 5b98e125..00000000 --- a/src/adapter/curve/CurveEMAOracle.sol +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {ScaleUtils, Scale} from "../../lib/ScaleUtils.sol"; -import {BaseAdapter, Errors, IPriceOracle} from "../BaseAdapter.sol"; -import {ICurvePool} from "./ICurvePool.sol"; - -/// @title CurveEMAOracle -/// @custom:security-contact security@euler.xyz -/// @author Euler Labs (https://www.eulerlabs.com/) -/// @notice Adapter utilizing the EMA price oracle in Curve pools. -contract CurveEMAOracle is BaseAdapter { - /// @inheritdoc IPriceOracle - string public constant name = "CurveEMAOracle"; - /// @notice The address of the Curve pool. - address public immutable pool; - /// @notice The address of the base asset. - address public immutable base; - /// @notice The address of the quote asset, must be `pool.coins[0]`. - address public immutable quote; - /// @notice The index in `price_oracle` corresponding to the base asset. - /// @dev Note that indices in `price_oracle` are shifted by 1, i.e. `0` corresponds to `coins[1]`. - /// @dev If type(uint256).max, then the adapter will call `price_oracle()`. - /// @dev Else the adapter will call the indexed price method `price_oracle(priceOracleIndex)`. - uint256 public immutable priceOracleIndex; - /// @notice The scale factors used for decimal conversions. - Scale internal immutable scale; - - /// @notice Deploy a CurveEMAOracle. - /// @param _pool The address of the Curve pool. - /// @param _base The address of the base asset. - /// @param _priceOracleIndex The index in `price_oracle` corresponding to the base asset. - /// @dev The quote is always `pool.coins[0]`. - /// If `priceOracleIndex` is `type(uint256).max`, then the adapter will call the non-indexed price method `price_oracle()` - /// WARNING: Some StableSwap-NG pools deployed before Dec-12-2023 have a known oracle vulerability. - /// See (https://docs.curve.fi/stableswap-exchange/stableswap-ng/pools/oracles/#price-oracles) for more details. - /// Additionally, verify that the pool has enough liquidity before deploying this adapter. - constructor(address _pool, address _base, uint256 _priceOracleIndex) { - pool = _pool; - base = _base; - // The EMA oracle returns a price quoted in `coins[0]`. - quote = ICurvePool(_pool).coins(0); - priceOracleIndex = _priceOracleIndex; - uint8 baseDecimals = _getDecimals(base); - uint8 quoteDecimals = _getDecimals(quote); - scale = ScaleUtils.calcScale(baseDecimals, quoteDecimals, 18); - } - - /// @notice Get a quote by calling the Curve oracle. - /// @param inAmount The amount of `base` to convert. - /// @param _base The token that is being priced. - /// @param _quote The token that is the unit of account. - /// @return The converted amount using the Curve EMA oracle. - function _getQuote(uint256 inAmount, address _base, address _quote) internal view override returns (uint256) { - bool inverse = ScaleUtils.getDirectionOrRevert(_base, base, _quote, quote); - - uint256 unitPrice; - if (priceOracleIndex == type(uint256).max) { - unitPrice = ICurvePool(pool).price_oracle(); - } else { - unitPrice = ICurvePool(pool).price_oracle(priceOracleIndex); - } - - return ScaleUtils.calcOutAmount(inAmount, unitPrice, scale, inverse); - } -} diff --git a/src/adapter/curve/ICurvePool.sol b/src/adapter/curve/ICurvePool.sol deleted file mode 100644 index 3822f6f2..00000000 --- a/src/adapter/curve/ICurvePool.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -interface ICurvePool { - function coins(uint256 i) external view returns (address); - function price_oracle(uint256 i) external view returns (uint256); - function price_oracle() external view returns (uint256); - function lpPrice() external view returns (uint256); -} diff --git a/test/adapter/curve/CurveAddresses.sol b/test/adapter/curve/CurveAddresses.sol deleted file mode 100644 index acf07521..00000000 --- a/test/adapter/curve/CurveAddresses.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -address constant CURVE_PT_WSTUSR_WSTUSR_POOL = 0x0d89f4583a6b5eceb76551d573ad49cD435f6064; -address constant SPECTRA_PT_WSTUSR = 0xD0097149AA4CC0d0e1fC99B8BD73fC17dC32C1E9; -address constant WSTUSR = 0x1202F5C7b4B9E47a1A484E8B270be34dbbC75055; - -address constant CURVE_TRICRYPTOV2_POOL = 0xD51a44d3FaE010294C616388b506AcdA1bfAAE46; -address constant CURVE_CRVUSD_USDC_POOL = 0x4DEcE678ceceb27446b35C672dC7d61F30bAD69E; -address constant CURVE_STABLENG_USD0_POOL = 0x1d08E7adC263CfC70b1BaBe6dC5Bb339c16Eec52; diff --git a/test/adapter/curve/CurveEMAOracle.fork.t.sol b/test/adapter/curve/CurveEMAOracle.fork.t.sol deleted file mode 100644 index aa362393..00000000 --- a/test/adapter/curve/CurveEMAOracle.fork.t.sol +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {ForkTest} from "test/utils/ForkTest.sol"; -import { - CURVE_PT_WSTUSR_WSTUSR_POOL, - SPECTRA_PT_WSTUSR, - WSTUSR, - CURVE_TRICRYPTOV2_POOL, - CURVE_CRVUSD_USDC_POOL, - CURVE_STABLENG_USD0_POOL -} from "test/adapter/curve/CurveAddresses.sol"; -import {WETH, WBTC, USDT, CRVUSD, USDC, USD0, USD0PP} from "test/utils/EthereumAddresses.sol"; -import {CurveEMAOracle} from "src/adapter/curve/CurveEMAOracle.sol"; - -contract CurveEMAOracleForkTest is ForkTest { - /// @dev 1% - uint256 constant REL_PRECISION = 0.01e18; - - CurveEMAOracle oracle; - - function setUp() public { - _setUpFork(21708630); - } - - function test_TriCryptoV2_3Coins_WBTC_USDT() public { - oracle = new CurveEMAOracle(CURVE_TRICRYPTOV2_POOL, WBTC, 0); - assertEq(oracle.pool(), CURVE_TRICRYPTOV2_POOL); - assertEq(oracle.base(), WBTC); - assertEq(oracle.quote(), USDT); - assertEq(oracle.priceOracleIndex(), 0); - - uint256 outAmount = oracle.getQuote(1e8, WBTC, USDT); - assertApproxEqRel(outAmount, 104574e6, REL_PRECISION); - - uint256 outAmountInv = oracle.getQuote(outAmount, USDT, WBTC); - assertApproxEqRel(outAmountInv, 1e8, REL_PRECISION); - } - - function test_TriCryptoV2_3Coins_WETH_USDT() public { - oracle = new CurveEMAOracle(CURVE_TRICRYPTOV2_POOL, WETH, 1); - assertEq(oracle.pool(), CURVE_TRICRYPTOV2_POOL); - assertEq(oracle.base(), WETH); - assertEq(oracle.quote(), USDT); - assertEq(oracle.priceOracleIndex(), 1); - - uint256 outAmount = oracle.getQuote(1e18, WETH, USDT); - assertApproxEqRel(outAmount, 3300e6, REL_PRECISION); - - uint256 outAmountInv = oracle.getQuote(outAmount, USDT, WETH); - assertApproxEqRel(outAmountInv, 1e18, REL_PRECISION); - } - - function test_CRVUSD_CRVUSD_USDC() public { - oracle = new CurveEMAOracle(CURVE_CRVUSD_USDC_POOL, CRVUSD, type(uint256).max); - assertEq(oracle.pool(), CURVE_CRVUSD_USDC_POOL); - assertEq(oracle.base(), CRVUSD); - assertEq(oracle.quote(), USDC); - assertEq(oracle.priceOracleIndex(), type(uint256).max); - - uint256 outAmount = oracle.getQuote(1e18, CRVUSD, USDC); - assertApproxEqRel(outAmount, 1e6, REL_PRECISION); - - uint256 outAmountInv = oracle.getQuote(outAmount, USDC, CRVUSD); - assertApproxEqRel(outAmountInv, 1e18, REL_PRECISION); - } - - function test_Special_SpectraPool_WSTUSR() public { - oracle = new CurveEMAOracle(CURVE_PT_WSTUSR_WSTUSR_POOL, SPECTRA_PT_WSTUSR, type(uint256).max); - assertEq(oracle.pool(), CURVE_PT_WSTUSR_WSTUSR_POOL); - assertEq(oracle.base(), SPECTRA_PT_WSTUSR); - assertEq(oracle.quote(), WSTUSR); - assertEq(oracle.priceOracleIndex(), type(uint256).max); - - uint256 outAmount = oracle.getQuote(1e18, SPECTRA_PT_WSTUSR, WSTUSR); - assertApproxEqRel(outAmount, 0.92201e18, REL_PRECISION); - - uint256 outAmountInv = oracle.getQuote(outAmount, WSTUSR, SPECTRA_PT_WSTUSR); - assertApproxEqRel(outAmountInv, 1e18, REL_PRECISION); - } - - function test_StableNG_USD0() public { - oracle = new CurveEMAOracle(CURVE_STABLENG_USD0_POOL, USD0PP, 0); - assertEq(oracle.pool(), CURVE_STABLENG_USD0_POOL); - assertEq(oracle.base(), USD0PP); - assertEq(oracle.quote(), USD0); - assertEq(oracle.priceOracleIndex(), 0); - - uint256 outAmount = oracle.getQuote(1e18, USD0PP, USD0); - assertApproxEqRel(outAmount, 0.9237e18, REL_PRECISION); - - uint256 outAmountInv = oracle.getQuote(outAmount, USD0, USD0PP); - assertApproxEqRel(outAmountInv, 1e18, REL_PRECISION); - } -} diff --git a/test/adapter/curve/CurveEMAOracle.prop.t.sol b/test/adapter/curve/CurveEMAOracle.prop.t.sol deleted file mode 100644 index 7400fcd5..00000000 --- a/test/adapter/curve/CurveEMAOracle.prop.t.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {AdapterPropTest} from "test/adapter/AdapterPropTest.sol"; -import {CurveEMAOracleHelper} from "test/adapter/curve/CurveEMAOracleHelper.sol"; -import {CurveEMAOracle} from "src/adapter/curve/CurveEMAOracle.sol"; - -contract CurveEMAOraclePropTest is CurveEMAOracleHelper, AdapterPropTest { - function testProp_Bidirectional(FuzzableState memory s, Prop_Bidirectional memory p) public { - setUpPropTest(s); - checkProp(p); - } - - function testProp_NoOtherPaths(FuzzableState memory s, Prop_NoOtherPaths memory p) public { - setUpPropTest(s); - checkProp(p); - } - - function testProp_IdempotentQuoteAndQuotes(FuzzableState memory s, Prop_IdempotentQuoteAndQuotes memory p) public { - setUpPropTest(s); - checkProp(p); - } - - function testProp_SupportsZero(FuzzableState memory s, Prop_SupportsZero memory p) public { - setUpPropTest(s); - checkProp(p); - } - - function testProp_ContinuousDomain(FuzzableState memory s, Prop_ContinuousDomain memory p) public { - setUpPropTest(s); - checkProp(p); - } - - function testProp_OutAmountIncreasing(FuzzableState memory s, Prop_OutAmountIncreasing memory p) public { - setUpPropTest(s); - checkProp(p); - } - - function setUpPropTest(FuzzableState memory s) internal { - setUpState(s); - adapter = address(oracle); - base = s.base; - quote = s.coins_0; - } -} diff --git a/test/adapter/curve/CurveEMAOracle.unit.t.sol b/test/adapter/curve/CurveEMAOracle.unit.t.sol deleted file mode 100644 index 8da4fa28..00000000 --- a/test/adapter/curve/CurveEMAOracle.unit.t.sol +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {CurveEMAOracleHelper} from "test/adapter/curve/CurveEMAOracleHelper.sol"; -import {boundAddr} from "test/utils/TestUtils.sol"; -import {CurveEMAOracle} from "src/adapter/curve/CurveEMAOracle.sol"; -import {Errors} from "src/lib/Errors.sol"; - -contract CurveEMAOracleTest is CurveEMAOracleHelper { - function test_Constructor_Integrity(FuzzableState memory s) public { - setUpState(s); - assertEq(CurveEMAOracle(oracle).pool(), s.pool); - assertEq(CurveEMAOracle(oracle).base(), s.base); - assertEq(CurveEMAOracle(oracle).quote(), s.coins_0); - assertEq(CurveEMAOracle(oracle).priceOracleIndex(), s.priceOracleIndex); - } - - function test_Constructor_Integrity_LPMode(FuzzableState memory s) public { - setBehavior(Behavior.Constructor_LpMode, true); - setUpState(s); - assertEq(CurveEMAOracle(oracle).pool(), s.pool); - assertEq(CurveEMAOracle(oracle).base(), s.base); - assertEq(CurveEMAOracle(oracle).quote(), s.coins_0); - } - - function test_Quote_RevertsWhen_InvalidTokens(FuzzableState memory s, address otherA, address otherB) public { - setUpState(s); - otherA = boundAddr(otherA); - otherB = boundAddr(otherB); - vm.assume(otherA != s.base && otherA != s.coins_0); - vm.assume(otherB != s.base && otherB != s.coins_0); - expectNotSupported(s.inAmount, s.base, s.base); - expectNotSupported(s.inAmount, s.coins_0, s.coins_0); - expectNotSupported(s.inAmount, s.base, otherA); - expectNotSupported(s.inAmount, otherA, s.base); - expectNotSupported(s.inAmount, s.coins_0, otherA); - expectNotSupported(s.inAmount, otherA, s.coins_0); - expectNotSupported(s.inAmount, otherA, otherA); - expectNotSupported(s.inAmount, otherA, otherB); - } - - function test_Quote_Integrity(FuzzableState memory s) public { - setUpState(s); - - uint256 expectedOutAmount = calcOutAmount(s); - uint256 outAmount = CurveEMAOracle(oracle).getQuote(s.inAmount, s.base, s.coins_0); - assertEq(outAmount, expectedOutAmount); - - (uint256 bidOutAmount, uint256 askOutAmount) = CurveEMAOracle(oracle).getQuotes(s.inAmount, s.base, s.coins_0); - assertEq(bidOutAmount, expectedOutAmount); - assertEq(askOutAmount, expectedOutAmount); - } - - function test_Quote_Integrity_Inverse(FuzzableState memory s) public { - setUpState(s); - - uint256 expectedOutAmount = calcOutAmountInverse(s); - uint256 outAmount = CurveEMAOracle(oracle).getQuote(s.inAmount, s.coins_0, s.base); - assertEq(outAmount, expectedOutAmount); - - (uint256 bidOutAmount, uint256 askOutAmount) = CurveEMAOracle(oracle).getQuotes(s.inAmount, s.coins_0, s.base); - assertEq(bidOutAmount, expectedOutAmount); - assertEq(askOutAmount, expectedOutAmount); - } -} diff --git a/test/adapter/curve/CurveEMAOracleHelper.sol b/test/adapter/curve/CurveEMAOracleHelper.sol deleted file mode 100644 index f38f8169..00000000 --- a/test/adapter/curve/CurveEMAOracleHelper.sol +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {IERC20} from "forge-std/interfaces/IERC20.sol"; -import {FixedPointMathLib} from "@solady/utils/FixedPointMathLib.sol"; -import {AdapterHelper} from "test/adapter/AdapterHelper.sol"; -import {boundAddr, distinct} from "test/utils/TestUtils.sol"; -import {CurveEMAOracle} from "src/adapter/curve/CurveEMAOracle.sol"; -import {ICurvePool} from "src/adapter/curve/ICurvePool.sol"; - -contract CurveEMAOracleHelper is AdapterHelper { - struct FuzzableState { - // Config - address coins_0; - address pool; - address base; - uint256 priceOracleIndex; - uint8 baseDecimals; - uint8 quoteDecimals; - // Pool Oracle - uint256 price; - // Environment - uint256 inAmount; - } - - function setUpState(FuzzableState memory s) internal { - s.coins_0 = boundAddr(s.coins_0); - s.pool = boundAddr(s.pool); - s.base = boundAddr(s.base); - - vm.assume(distinct(s.base, s.coins_0, s.pool)); - - if (behaviors[Behavior.Constructor_LpMode]) { - s.priceOracleIndex = type(uint256).max; - } else { - vm.assume(s.priceOracleIndex != type(uint256).max); - } - - vm.mockCall(s.pool, abi.encodeWithSelector(ICurvePool.coins.selector, 0), abi.encode(s.coins_0)); - - s.baseDecimals = uint8(bound(s.baseDecimals, 6, 18)); - s.quoteDecimals = uint8(bound(s.quoteDecimals, 6, 18)); - - vm.mockCall(s.base, abi.encodeWithSelector(IERC20.decimals.selector), abi.encode(s.baseDecimals)); - vm.mockCall(s.coins_0, abi.encodeWithSelector(IERC20.decimals.selector), abi.encode(s.quoteDecimals)); - - oracle = address(new CurveEMAOracle(s.pool, s.base, s.priceOracleIndex)); - - s.price = bound(s.price, 1, 1e27); - s.inAmount = bound(s.inAmount, 0, type(uint128).max); - - if (s.priceOracleIndex == type(uint256).max) { - vm.mockCall(s.pool, abi.encodeWithSelector(bytes4(keccak256("price_oracle()"))), abi.encode(s.price)); - } else { - vm.mockCall( - s.pool, - abi.encodeWithSelector(bytes4(keccak256("price_oracle(uint256)")), s.priceOracleIndex), - abi.encode(s.price) - ); - } - } - - function calcOutAmount(FuzzableState memory s) internal pure returns (uint256) { - return FixedPointMathLib.fullMulDiv( - s.inAmount, uint256(s.price) * 10 ** s.quoteDecimals, 10 ** (18 + s.baseDecimals) - ); - } - - function calcOutAmountInverse(FuzzableState memory s) internal pure returns (uint256) { - return FixedPointMathLib.fullMulDiv( - s.inAmount, 10 ** (18 + s.baseDecimals), (uint256(s.price) * 10 ** s.quoteDecimals) - ); - } -}