DeFiHardhat
35,000 USDC
View results
Submission Details
Severity: low
Invalid

[M] Volatile prices of stETH will bypass redemption rate of 1:1 leading to incorrect valuation of wstETH:ETH.

Summary

During the redemption process, a 1:1 ratio is established. However, the price of stETH is volatile and can deviate from the 1:1 ratio, leading to incorrect valuation of wstETH:ETH. Additionally, wstETH:stETH does not have a 1:1 ratio.

Vulnerability Details

The main concern arises when the market rate of stETH:ETH deviates from 1:1, which is often the case due to staking mechanics and market dynamics. This deviation can introduce a level of inaccuracy in the calculated wstETH:ETH rate within Beanstalk, particularly if one of the oracle sources becomes unreliable or significantly diverges from actual market conditions.

Here are examples of when stETH:ETH rate can deviate from 1:1 https://dune.com/LidoAnalytical/Curve-ETHstETH,
the lowest was 0.9361.

Given the reliance on Chainlink for the stETH:ETH rate and the potential for significant market volatility, there's a risk that the wstETH:ETH price used by Beanstalk may not accurately reflect the current market value.

This situation could be exacerbated during periods of high volatility or if the Uniswap V3 pool data becomes manipulated due to low liquidity or flash loan instances, further exacerbating the valuation of wstETH:ETH.

Considering that Beanstalk relies on the wstETH:ETH oracle to derive the DeltaB of the wstETH:BEAN well for minting and converts, as well as the amount of Fertilizer issued to a fertilizer buyer, an inaccurate valuation of wstETH:ETH will lead to incorrect minting, conversion, and Fertilizer issuance.

library LibWstethEthOracle {
using SafeMath for uint256;
// The maximum percent difference such that the oracle assumes no manipulation is occuring.
uint256 constant MAX_DIFFERENCE = 0.01e18; // 1%
uint256 constant CHAINLINK_DENOMINATOR = 1e6;
uint128 constant ONE = 1e18;
uint128 constant AVERAGE_DENOMINATOR = 2;
uint128 constant PRECISION_DENOMINATOR = 1e12;
/////////////////// ORACLES ///////////////////
address constant WSTETH_ETH_CHAINLINK_PRICE_AGGREGATOR =
0x86392dC19c0b719886221c78AB11eb8Cf5c52812;
address internal constant WSTETH_ETH_UNIV3_01_POOL = 0x109830a1AAaD605BbF02a9dFA7B0B92EC2FB7dAa; // 0.01% pool
///////////////////////////////////////////////
/**
* @dev Returns the instantaneous wstETH/ETH price
* Return value has 6 decimal precision.
* Returns 0 if the either the Chainlink Oracle or Uniswap Oracle cannot fetch a valid price.
**/
function getWstethEthPrice() internal view returns (uint256) {
return getWstethEthPrice(0);
}
/**
* @dev Returns the wstETH/ETH price with the option of using a TWA lookback.
* Return value has 6 decimal precision.
* Returns 0 if the either the Chainlink Oracle or Uniswap Oracle cannot fetch a valid price.
**/
function getWstethEthPrice(uint256 lookback) internal view returns (uint256 wstethEthPrice) {
uint256 chainlinkPrice = lookback == 0 ?
LibChainlinkOracle.getPrice(WSTETH_ETH_CHAINLINK_PRICE_AGGREGATOR, LibChainlinkOracle.FOUR_DAY_TIMEOUT) :
LibChainlinkOracle.getTwap(WSTETH_ETH_CHAINLINK_PRICE_AGGREGATOR, LibChainlinkOracle.FOUR_DAY_TIMEOUT, lookback);
// Check if the chainlink price is broken or frozen.
if (chainlinkPrice == 0) return 0;
uint256 stethPerWsteth = IWsteth(C.WSTETH).stEthPerToken();
chainlinkPrice = chainlinkPrice.mul(stethPerWsteth).div(CHAINLINK_DENOMINATOR);
// Uniswap V3 only supports a uint32 lookback.
if (lookback > type(uint32).max) return 0;
uint256 uniswapPrice = LibUniswapOracle.getTwap(
lookback == 0 ? LibUniswapOracle.FIFTEEN_MINUTES :
uint32(lookback),
WSTETH_ETH_UNIV3_01_POOL, C.WSTETH, C.WETH, ONE
);
// Check if the uniswapPrice oracle fails.
if (uniswapPrice == 0) return 0;
if (LibOracleHelpers.getPercentDifference(chainlinkPrice, uniswapPrice) < MAX_DIFFERENCE) {
wstethEthPrice = chainlinkPrice.add(uniswapPrice).div(AVERAGE_DENOMINATOR);
if (wstethEthPrice > stethPerWsteth) wstethEthPrice = stethPerWsteth;
wstethEthPrice = wstethEthPrice.div(PRECISION_DENOMINATOR);
}
}

Impact

If the Chainlink oracle or if the Uniswap Oracle return >1, then Beanstalk falls back to the assumed 1:1 redemption, in this case wont be representative. Using a Chainlink price feed for stETH:ETH as a proxy for wstETH:ETH introduces a level of inaccuracy when the assumption is that stETH:ETH is a 1:1 ratio. This inaccuracy can lead to incorrect minting, conversions, Fertilizer issuances to buyers and deriving the DeltaB of the
wstETH:BEAN well.

Tools Used

Manual Review/Dune Analytics

Recommendations

It is prudent to accurately ascertain the value of wstETH:ETH by introducing a more robust oraclefor example a direct price feed for ``wstETH:ETH``` by the likes of Compound(link here).

Alternatively, you could query the redemption rate of stETH:ETH from the Lido contract and use this rate to ascertain any significant deviations from the 1:1 ratio. https://dune.com/LidoAnalytical/Curve-ETHstETH.

If it deviates beyond an acceptable range, then revert. This would provide a more accurate representation of the wstETH:ETH rate.

Updates

Lead Judging Commences

giovannidisiena Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Design choice
Assigned finding tags:

stETH redemption rate

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.