From 6f75e6190a90d6386847ef304516f1f973f2bba5 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 13 Aug 2024 11:18:48 +0200 Subject: [PATCH 1/4] add uniswap fix --- protocol/contracts/libraries/Oracle/LibUsdOracle.sol | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/protocol/contracts/libraries/Oracle/LibUsdOracle.sol b/protocol/contracts/libraries/Oracle/LibUsdOracle.sol index 148bf45201..f4a66f41a9 100644 --- a/protocol/contracts/libraries/Oracle/LibUsdOracle.sol +++ b/protocol/contracts/libraries/Oracle/LibUsdOracle.sol @@ -135,7 +135,16 @@ library LibUsdOracle { 0, lookback ); - return tokenPrice.mul(chainlinkTokenPrice).div(CHAINLINK_DENOMINATOR); + + // if token decimals != 0, Beanstalk is attempting to query the USD/TOKEN price, and + // thus the price needs to be inverted. + if (tokenDecimals != 0) { + tokenPrice = (10 ** (6 + tokenDecimals)) / tokenPrice; + return (tokenPrice * chainlinkTokenPrice) / (10 ** tokenDecimals); + } else { + // return the TOKEN/USD price. + return (tokenPrice * chainlinkTokenPrice) / CHAINLINK_DENOMINATOR; + } } // If the oracle implementation address is not set, use the current contract. From af666185d144f9d09a38afe9a74f328c154be062 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 13 Aug 2024 11:49:41 +0200 Subject: [PATCH 2/4] update libWstethEthOracle to include ETH/WSTETH --- .../libraries/Oracle/LibWstethEthOracle.sol | 60 +++++++++++++++++++ protocol/contracts/mocks/MockWsteth.sol | 4 ++ 2 files changed, 64 insertions(+) diff --git a/protocol/contracts/libraries/Oracle/LibWstethEthOracle.sol b/protocol/contracts/libraries/Oracle/LibWstethEthOracle.sol index 96e578be94..31f9ee721a 100644 --- a/protocol/contracts/libraries/Oracle/LibWstethEthOracle.sol +++ b/protocol/contracts/libraries/Oracle/LibWstethEthOracle.sol @@ -13,6 +13,8 @@ import {LibOracleHelpers} from "contracts/libraries/Oracle/LibOracleHelpers.sol" interface IWsteth { function stEthPerToken() external view returns (uint256); + + function tokensPerStEth() external view returns (uint256); } /** @@ -43,6 +45,7 @@ library LibWstethEthOracle { uint128 constant ONE = 1e18; uint128 constant AVERAGE_DENOMINATOR = 2; uint128 constant PRECISION_DENOMINATOR = 1e12; + uint256 constant PRECISION_18 = 1e18; /** * @dev Returns the instantaneous wstETH/ETH price @@ -95,4 +98,61 @@ library LibWstethEthOracle { wstethEthPrice = wstethEthPrice.div(PRECISION_DENOMINATOR); } } + + /** + * @dev Returns the instantaneous ETH/WSTETH price + * Return value has 6 decimal precision. + * Returns 0 if the either the Chainlink Oracle or Uniswap Oracle cannot fetch a valid price. + **/ + function getEthWstethPrice() internal view returns (uint256) { + return getEthWstethPrice(0); + } + + /** + * @dev Returns the wstETH/ETH price with the option of using a TWA lookback. + * Return value has decimal precision. + * Returns 0 if the either the Chainlink Oracle or Uniswap Oracle cannot fetch a valid price. + **/ + function getEthWstethPrice(uint256 lookback) internal view returns (uint256 ethWstethPrice) { + // fetch the eth/wsteth chainlink price. + uint256 chainlinkPrice = LibChainlinkOracle.getTokenPrice( + C.WSTETH_ETH_CHAINLINK_PRICE_AGGREGATOR, + LibChainlinkOracle.FOUR_DAY_TIMEOUT, + 18, + lookback + ); + + // Check if the chainlink price is broken or frozen. + if (chainlinkPrice == 0) return 0; + + // fetch the wsteth/steth price. + uint256 wstethPerSteth = IWsteth(C.WSTETH).tokensPerStEth(); + chainlinkPrice = chainlinkPrice.mul(wstethPerSteth).div(PRECISION_18); + + // Uniswap V3 only supports a uint32 lookback. + if (lookback > type(uint32).max) return 0; + + // fetch the eth/wsteth uniswap twap. + uint256 uniswapPrice = LibUniswapOracle.getTwap( + lookback == 0 ? LibUniswapOracle.FIFTEEN_MINUTES : uint32(lookback), + C.WSTETH_ETH_UNIV3_01_POOL, + C.WETH, + C.WSTETH, + ONE + ); + + // uniswap price is a 1e6, need to convert to 1e18, multiply by 1e12 + uniswapPrice = uniswapPrice * PRECISION_DENOMINATOR; + + // Check if the uniswapPrice oracle fails. + if (uniswapPrice == 0) return 0; + + if (LibOracleHelpers.getPercentDifference(chainlinkPrice, uniswapPrice) < MAX_DIFFERENCE) { + ethWstethPrice = chainlinkPrice.add(uniswapPrice).div(AVERAGE_DENOMINATOR); + + // if the wstethPerSteth is greater than than the eth/wsteth price, set the price to the wstethPerSteth. + if (wstethPerSteth > ethWstethPrice) ethWstethPrice = wstethPerSteth; + ethWstethPrice = ethWstethPrice.div(PRECISION_DENOMINATOR); + } + } } diff --git a/protocol/contracts/mocks/MockWsteth.sol b/protocol/contracts/mocks/MockWsteth.sol index 53d7822aac..1c4701ff93 100644 --- a/protocol/contracts/mocks/MockWsteth.sol +++ b/protocol/contracts/mocks/MockWsteth.sol @@ -25,4 +25,8 @@ contract MockWsteth is MockToken { function stEthPerToken() external view returns (uint256) { return _stEthPerToken; } + + function tokensPerStEth() external view returns (uint256) { + return 1e36 / _stEthPerToken; + } } From ad68a7deb0d681bfae194bcd513e2ee3babe17fa Mon Sep 17 00:00:00 2001 From: pizzaman1337 Date: Sun, 18 Aug 2024 22:52:50 +0300 Subject: [PATCH 3/4] Add token/usd from external uniswap test --- protocol/test/foundry/silo/Oracle.t.sol | 71 +++++++++++++++++-------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/protocol/test/foundry/silo/Oracle.t.sol b/protocol/test/foundry/silo/Oracle.t.sol index 2bc7462105..cf3883eab9 100644 --- a/protocol/test/foundry/silo/Oracle.t.sol +++ b/protocol/test/foundry/silo/Oracle.t.sol @@ -32,29 +32,7 @@ contract OracleTest is TestHelper { uint256 price = OracleFacet(BEANSTALK).getUsdTokenPrice(WBTC); assertEq(price, 0.00002e8, "price using encode type 0x01"); - // change encode type to 0x02: - vm.prank(BEANSTALK); - bs.updateOracleImplementationForToken( - WBTC, - IMockFBeanstalk.Implementation( - WBTC_USDC_03_POOL, - bytes4(0), - bytes1(0x02), - abi.encode(LibChainlinkOracle.FOUR_HOUR_TIMEOUT) - ) - ); - - // also uniswap relies on having a chainlink oracle for the dollar-denominated token, in this case USDC - vm.prank(BEANSTALK); - bs.updateOracleImplementationForToken( - C.USDC, - IMockFBeanstalk.Implementation( - USDC_USD_CHAINLINK_PRICE_AGGREGATOR, - bytes4(0), - bytes1(0x01), - abi.encode(LibChainlinkOracle.FOUR_DAY_TIMEOUT) - ) - ); + setupUniswapWBTCOracleImplementation(); price = OracleFacet(BEANSTALK).getTokenUsdPrice(WBTC); // 1 USDC will get ~500 satoshis of BTC at $50k @@ -66,6 +44,28 @@ contract OracleTest is TestHelper { assertApproxEqRel(price, 50000e6, 0.001e18, "price using encode type 0x02"); } + function test_uniswap_external() public { + setupUniswapWBTCOracleImplementation(); + + // exercise TokenUsd price and UsdToken price + uint256 tokenUsdPriceFromExternal = OracleFacet(BEANSTALK).getTokenUsdPriceFromExternal( + WBTC, + 0 + ); + assertApproxEqRel( + tokenUsdPriceFromExternal, + 50000e6, + 0.001e18, + "tokenUsdPriceFromExternal" + ); + + uint256 usdTokenPriceFromExternal = OracleFacet(BEANSTALK).getUsdTokenPriceFromExternal( + WBTC, + 0 + ); + assertEq(usdTokenPriceFromExternal, 0.00002e6, "usdTokenPriceFromExternal"); + } + /** * @notice verifies functionality with LSDChainlinkOracle.sol. */ @@ -282,4 +282,29 @@ contract OracleTest is TestHelper { uint256 priceWBTC = OracleFacet(BEANSTALK).getUsdTokenPrice(WBTC); assertEq(priceWBTC, 0.00002e8); // adjusted to 8 decimals } + + function setupUniswapWBTCOracleImplementation() public { + vm.prank(BEANSTALK); + bs.updateOracleImplementationForToken( + WBTC, + IMockFBeanstalk.Implementation( + WBTC_USDC_03_POOL, + bytes4(0), + bytes1(0x02), + abi.encode(LibChainlinkOracle.FOUR_HOUR_TIMEOUT) + ) + ); + + // also uniswap relies on having a chainlink oracle for the dollar-denominated token, in this case USDC + vm.prank(BEANSTALK); + bs.updateOracleImplementationForToken( + C.USDC, + IMockFBeanstalk.Implementation( + USDC_USD_CHAINLINK_PRICE_AGGREGATOR, + bytes4(0), + bytes1(0x01), + abi.encode(LibChainlinkOracle.FOUR_DAY_TIMEOUT) + ) + ); + } } From 7d712ab89436e0af6245ae30a8a5db43f30ba1fe Mon Sep 17 00:00:00 2001 From: pizzaman1337 Date: Tue, 20 Aug 2024 14:24:01 +0300 Subject: [PATCH 4/4] Remove MockChecker, update authors --- .../contracts/beanstalk/farm/TractorFacet.sol | 2 +- protocol/contracts/libraries/LibTractor.sol | 2 +- protocol/contracts/mocks/MockChecker.sol | 26 ------------------- 3 files changed, 2 insertions(+), 28 deletions(-) delete mode 100644 protocol/contracts/mocks/MockChecker.sol diff --git a/protocol/contracts/beanstalk/farm/TractorFacet.sol b/protocol/contracts/beanstalk/farm/TractorFacet.sol index b03dda3284..9dfc80a5e1 100644 --- a/protocol/contracts/beanstalk/farm/TractorFacet.sol +++ b/protocol/contracts/beanstalk/farm/TractorFacet.sol @@ -18,7 +18,7 @@ import {ReentrancyGuard} from "contracts/beanstalk/ReentrancyGuard.sol"; /** * @title TractorFacet handles tractor and blueprint operations. - * @author funderberker, 0xm00neth + * @author funderberker */ contract TractorFacet is Invariable, ReentrancyGuard { using LibBytes for bytes32; diff --git a/protocol/contracts/libraries/LibTractor.sol b/protocol/contracts/libraries/LibTractor.sol index 11c507a592..d77d926519 100644 --- a/protocol/contracts/libraries/LibTractor.sol +++ b/protocol/contracts/libraries/LibTractor.sol @@ -8,7 +8,7 @@ import {C} from "contracts/C.sol"; /** * @title Lib Tractor - * @author funderbrker, 0xm00neth + * @author funderbrker **/ library LibTractor { enum CounterUpdateType { diff --git a/protocol/contracts/mocks/MockChecker.sol b/protocol/contracts/mocks/MockChecker.sol deleted file mode 100644 index ff9240f972..0000000000 --- a/protocol/contracts/mocks/MockChecker.sol +++ /dev/null @@ -1,26 +0,0 @@ -/* - SPDX-License-Identifier: MIT -*/ - -pragma solidity ^0.8.20; - -/** - * @author 0xm00neth - * @title Mock Contract which checks external function approval - **/ -contract MockChecker { - bool approve; - - function setApprove(bool _approve) external { - approve = _approve; - } - - function check( - address, - bytes calldata, - bytes calldata, - bytes calldata _stateData - ) external view returns (bytes memory) { - return abi.encode(approve, _stateData); - } -}