DeFiHardhatFoundry
250,000 USDC
View results
Submission Details
Severity: low
Invalid

Sunrise on L2 will face DoS and issue incorrect beans as a reward

Summary

Timing assumptions on Ethereum do not directly translate to L2s.

Block Numbers: On L2s like Arbitrum and Optimism, multiple L2 blocks can exist within a single Ethereum block. Therefore, block.number can return the same value across different L2 blocks, leading to potential inaccuracies.

Block Timestamps: L2s also have their own rules for block.timestamp. For example, on Arbitrum, the sequencer sets the timestamp. If the sequencer fails to post batches to Ethereum in a timely manner, it can adjust timestamps within set boundaries (currently up to 24 hours earlier or 1 hour later than the current time) to prevent chain reorganization.

Beanstalk relies heavily on block.number and block.timestamp to perform the sunrise call. For example:

  • Calculating whether sunrise can be called:

function seasonTime() public view virtual returns (uint32) {
if (block.timestamp < s.sys.season.start) return 0;
if (s.sys.season.period == 0) return type(uint32).max;
@> return uint32((block.timestamp - s.sys.season.start) / s.sys.season.period); //
}
  • Calculating the Bean rewards for calling sunrise:

@> uint256 secondsLate = block.timestamp.sub(
@> s.sys.season.start.add(s.sys.season.period.mul(s.sys.season.current))
);
// reset USD Token prices and TWA reserves in storage for all whitelisted Well LP Tokens.
address[] memory whitelistedWells = LibWhitelistedTokens.getWhitelistedWellLpTokens();
for (uint256 i; i < whitelistedWells.length; i++) {
LibWell.resetUsdTokenPriceForWell(whitelistedWells[i]);
LibWell.resetTwaReservesForWell(whitelistedWells[i]);
}
@> uint256 incentiveAmount = LibIncentive.determineReward(secondsLate);
  • Calculating pod demand based on the sowTime

function _saveSowTime() private {
AppStorage storage s = LibAppStorage.diamondStorage();
// s.sys.soil is now the soil remaining after this Sow.
if (s.sys.soil > SOIL_SOLD_OUT_THRESHOLD || s.sys.weather.thisSowTime < type(uint32).max) {
// haven't sold enough soil, or already set thisSowTime for this Season.
return;
}
@> s.sys.weather.thisSowTime = uint32(block.timestamp.sub(s.sys.season.timestamp));
}
  • Calculating morning temperature delta

function morningTemperature() internal view returns (uint256) {
AppStorage storage s = LibAppStorage.diamondStorage();
@> uint256 delta = block.number.sub(s.sys.season.sunriseBlock).mul(L2_BLOCK_TIME).div(
L1_BLOCK_TIME
);
...

Impact

Given the scenario above, Beanstalk will be susceptible to many issues related to block.number and block.timestamp. Among them:

  • Sunrise will issue the incorrect amount of Beans when called(due to block.number / block.timestamp not being reflecting the current ones).

  • When sequencers set a block.timestamp that is delayed, meaning that the current timestamp is + 1 hour since the last season, but sequencer block.timestamp is only +5min, sunrise will be temporarily facing DoS until the sequencer time is adjusted.

  • Calculation of temperature/sow/demand for pods will also be affected(as they also rely on block.timestamp).

Tools Used

Manual Review

Recommendations

  • Avoid utilizing block.number and block.timestamp directly. For Arbitrum for example, the block number and timestamp from Arbitrum blocks can be directly fetched through ArbSys.

References:

  • https://detectors.auditbase.com/blocknumber-variation-on-l2-solidity

  • https://docs.arbitrum.io/build-decentralized-apps/arbitrum-vs-ethereum/block-numbers-and-time#block-numbers-arbitrum-vs-ethereum

  • MulticallV2 for Arbitrum - https://arbiscan.io/address/0x7eCfBaa8742fDf5756DAC92fbc8b90a19b8815bF#code

Updates

Lead Judging Commences

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Known - LightChaser

Support

FAQs

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