29052 - [SC - Medium] Pool funds could be locked due to Division by zero
Submitted on Mar 5th 2024 at 22:04:05 UTC by @DuckAstronomer for Boost | ZeroLend
Report ID: #29052
Report type: Smart Contract
Report severity: Medium
Target: https://github.com/zerolend/governance
Impacts:
Theft of unclaimed yield
Temporary freezing of funds for at least 1 hour
Description
Vulnerability Details
Affected asset: governance-main/contracts/voter/gauge/GaugeIncentiveController.sol
The rewardPerToken() function in GaugeIncentiveController verifies totalSupply for zero, but utilizes derivedSupply for calculation of the rewardPerToken value. If derivedSupply equals zero, rewardPerToken() reverts due to division by zero.
https://github.com/zerolend/governance/blob/main/contracts/voter/gauge/GaugeIncentiveController.sol#L48
function rewardPerToken(
IERC20 token
) public view override returns (uint256) {
if (totalSupply == 0) return rewardPerTokenStored[token];
// derivedSupply is used instead of totalSupply to modify for ve-BOOST
return
rewardPerTokenStored[token] +
(((lastTimeRewardApplicable(token) - lastUpdateTime[token]) *
rewardRate[token] *
PRECISION) / derivedSupply);
}The handleAction() function in GaugeIncentiveController serves as a callback for AToken to invoke upon changes in the user's balance (such as minting or burning). Within handleAction(), it initially triggers _updateReward(), which subsequently calls rewardPerToken().
https://github.com/zerolend/governance/blob/main/contracts/voter/gauge/GaugeIncentiveController.sol#L87
https://github.com/zerolend/governance/blob/main/contracts/voter/gauge/GaugeIncentiveController.sol#L109
https://github.com/zerolend/governance/blob/main/contracts/voter/gauge/GaugeIncentiveController.sol#L117
In scenarios where totalSupply is not equal to zero, and if derivedSupply does equal zero, any alterations in AToken (e.g., minting or burning) will result in a revert. This effectively locks the user's funds.
The value of derivedSupply is determined through governance-main/contracts/voter/eligibility/EligibilityCriteria.sol.
https://github.com/zerolend/governance/blob/main/contracts/voter/gauge/GaugeIncentiveController.sol#L68
For instance, to qualify, a user must mint AToken and stake over 5% of ZeroLend tokens. https://github.com/zerolend/governance/blob/main/contracts/voter/eligibility/EligibilityCriteria.sol#L50
In summary, an attacker could block other users' funds and receive rewards from the gauge by being the first to invoke handleAction() or updateUser().
The attack scenario is the following:
A pool is assigned a new gauge (gauge change occurs). At that point
totalSupplyandderivedSupplyare 0.The attacker becomes the first AToken minter after gauge change (or just calls
updateUser(address who)).Attacker doesn't stake 5% of ZeroLend, so
totalSupply > 0, butderivedSupply == 0.Subsequent AToken actions within the pool will trigger reverts.
Consequently, the attacker locks users' funds in the pool and earns rewards from the gauge as the sole staker.
Proof of Concept
To run the Poc:
Put the code from below to the
governance-main/test/Gauge.poc.2.tsfile.Generate random private key.
Modify
governance-main/contracts/voter/eligibility/MockEligibilityCriteria.solfile so thecheckEligibility()function returns 0 instead of 1e18.Issue the following command:
PoC scenario:
Ant is the first who mints AToken in a pool with new Gauge.
Ant isn't eligible for reward, but it has AToken amount. So,
totalSupply > 0, butderivedSupply == 0.Whale aren't able to mint AToken due to division by zero panic.
Last updated
Was this helpful?