H-02 described how the lastUpkeepTime started counting from the moment the contract was deployed. By the time the ballot is finalized, significant rewards are accrued and can be claimed by the first liquidity provider.
Suggested fix was to reset the timer just before the initial distribution.
Mitigation
performUpKeep() is now called by the protocol at the start, inside BootstrapBallot::finalizeBallot(). This resets the timer, ensuring that a very small amount of rewards is sent to the staking contract if called again.
// Ensures that the completionTimestamp has been reached and then calls InitialDistribution.distributionApproved and DAO.initialGeoExclusion if the voters have approved the ballot.
function finalizeBallot() external nonReentrant
{
require( ! ballotFinalized, "Ballot has already been finalized" );
require( block.timestamp >= completionTimestamp, "Ballot is not yet complete" );
if ( startExchangeYes > startExchangeNo )
{
// First call performUpkeep() to reset the emissions timers so the first liquidity rewards claimers don't claim a full days worth of the bootstrap rewards
exchangeConfig.upkeep().performUpkeep(); <--------------- fix applied
exchangeConfig.initialDistribution().distributionApproved();
exchangeConfig.dao().pools().startExchangeApproved();
startExchangeApproved = true;
}
emit BallotFinalized(startExchangeApproved);
ballotFinalized = true;
}
Lines of code
Vulnerability details
Summary
H-02 described how the lastUpkeepTime started counting from the moment the contract was deployed. By the time the ballot is finalized, significant rewards are accrued and can be claimed by the first liquidity provider. Suggested fix was to reset the timer just before the initial distribution.
Mitigation
performUpKeep()
is now called by the protocol at the start, insideBootstrapBallot::finalizeBallot()
. This resets the timer, ensuring that a very small amount of rewards is sent to the staking contract if called again.Conclusion
LGTM