DeFiHardhatFoundry
250,000 USDC
View results
Submission Details
Severity: high
Valid

ReseedSilo doesn't update total balances of Stalk and Roots

Summary

ReseedSilo is used to migrate Silo deposits owned by EOA. It writes Stalk associated with migrated deposits and misses to write Roots (but that's another issue submitted in another report). However it doesn't update total supply

Vulnerability Details

As you can see this function doesn't increase global values of s.sys.silo.stalk and s.sys.silo.roots:
https://github.com/Cyfrin/2024-05-beanstalk-the-finale/blob/df2dd129a878d16d4adc75049179ac0029d9a96b/protocol/contracts/beanstalk/init/reseed/L2/ReseedSilo.sol#L109-L180

Impact

As you can see after migration s.sys.silo.stalk will be much much lower. This value is used in Gauge Point system.

It is used to calculate s.sys.seedGauge.averageGrownStalkPerBdvPerSeason:
https://github.com/Cyfrin/2024-05-beanstalk-the-finale/blob/df2dd129a878d16d4adc75049179ac0029d9a96b/protocol/contracts/libraries/LibGauge.sol#L343-L352

function updateAverageStalkPerBdvPerSeason() internal {
...
@> s.sys.seedGauge.averageGrownStalkPerBdvPerSeason = uint128(
@> getAverageGrownStalkPerBdv().mul(BDV_PRECISION).div(
s.sys.seedGaugeSettings.targetSeasonsToCatchUp
)
);
...
}
function getAverageGrownStalkPerBdv() internal view returns (uint256) {
AppStorage storage s = LibAppStorage.diamondStorage();
uint256 totalBdv = getTotalBdv();
if (totalBdv == 0) return 0;
@> return s.sys.silo.stalk.div(totalBdv).sub(STALK_BDV_PRECISION);
}

Then s.sys.seedGauge.averageGrownStalkPerBdvPerSeason is used to calculate amount of Grown Stalk for the next season:
https://github.com/Cyfrin/2024-05-beanstalk-the-finale/blob/df2dd129a878d16d4adc75049179ac0029d9a96b/protocol/contracts/libraries/LibGauge.sol#L267-L269

function updateGrownStalkEarnedPerSeason(
uint256 maxLpGpPerBdv,
LpGaugePointData[] memory lpGpData,
uint256 totalGaugePoints,
uint256 totalLpBdv
) internal {
...
// update the average grown stalk per BDV per Season.
// beanstalk must exist for a minimum of the catchup season in order to update the average.
if (s.sys.season.current > s.sys.seedGaugeSettings.targetSeasonsToCatchUp) {
@> updateAverageStalkPerBdvPerSeason();
}
// Calculate grown stalk issued this season and GrownStalk Per GaugePoint.
@> uint256 newGrownStalk = uint256(s.sys.seedGauge.averageGrownStalkPerBdvPerSeason)
.mul(totalGaugeBdv)
.div(BDV_PRECISION);
...
}

As you remember total Stalk is much lower than it should be, it means Stalk rewards in future seasons will be much lower too. It will lead to imbalance in voting because Stalk is governance token.

Additionally Stalk is associated with Roots. There will also be imbalance in distribution of Beans minted to Silo at the start of the season.

Tools Used

Manual Review

Recommendations

Here is solution to set Stalk. You should also increase global Roots.

function reseedSiloDeposit(SiloDeposits calldata siloDeposit) internal {
uint256 totalCalcDeposited;
uint256 totalCalcDepositedBdv;
uint256 stalkIssuedPerBdv = s.sys.silo.assetSettings[siloDeposit.token].stalkIssuedPerBdv;
+ uint256 totalStalk;
for (uint256 i; i < siloDeposit.siloDepositsAccount.length; i++) {
...
}
...
// set stalk for account.
s.accts[deposits.accounts].stalk = accountStalk;
+ totalStalk += accountStalk;
}
...
// set global state
s.sys.silo.balances[siloDeposit.token].deposited = siloDeposit.totalDeposited;
s.sys.silo.balances[siloDeposit.token].depositedBdv = siloDeposit.totalDepositedBdv;
+ s.sys.silo.stalk += totalStalk;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

ReseedSilo doesn't update global `s.sys.silo.stalk` and `s.sys.silo.roots`

Support

FAQs

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