File Location: protocol/contracts/templegold/TempleGoldStaking.sol#L515-L529
Vulnerability Details
The '_notifyReward
' function in the 'TempleGoldStaking
' contract contains two instances where subtraction operations may underflow, leading to unintended behavior in the reward distribution mechanism.
Impact
If the result of 'rewardData.rewardRate * rewardDuration
' exceeds the 'amount
' in the first subtraction or 'amount + leftover
' in the second subtraction, an underflow will occur. This can lead to the following impacts:
Incorrect 'nextRewardAmount
' calculation, potentially resulting in a large positive value due to underflow.
Malicious exploitation to manipulate rewards distribution in favor of an attacker.
Overall compromise of the staking mechanism, affecting its reliability and security.
Tools Used
Solidity
Inspeksi manual
Foundry
Recommendations
To fix the problem it is necessary to ensure that the subtraction operation does not cause negative results. We can add checks to ensure that the reduction is safe to do.
Code snippet:
L515-L529 look L519 and L525
function _notifyReward(uint256 amount) private {
if (block.timestamp >= rewardData.periodFinish) {
rewardData.rewardRate = uint216(amount / rewardDuration);
nextRewardAmount = amount - (rewardData.rewardRate * rewardDuration);
} else {
uint256 remaining = uint256(rewardData.periodFinish) - block.timestamp;
uint256 leftover = remaining * rewardData.rewardRate;
rewardData.rewardRate = uint216((amount + leftover) / rewardDuration);
nextRewardAmount = (amount + leftover) - (rewardData.rewardRate * rewardDuration);
}
rewardData.lastUpdateTime = uint40(block.timestamp);
rewardData.periodFinish = uint40(block.timestamp + rewardDuration);
}
Fixed Code:
function _notifyReward(uint256 amount) private {
if (block.timestamp >= rewardData.periodFinish) {
rewardData.rewardRate = uint216(amount / rewardDuration);
if (amount >= rewardData.rewardRate * rewardDuration) {
nextRewardAmount = amount - (rewardData.rewardRate * rewardDuration);
} else {
nextRewardAmount = 0;
}
} else {
uint256 remaining = uint256(rewardData.periodFinish) - block.timestamp;
uint256 leftover = remaining * rewardData.rewardRate;
rewardData.rewardRate = uint216((amount + leftover) / rewardDuration);
if ((amount + leftover) >= (rewardData.rewardRate * rewardDuration)) {
nextRewardAmount = (amount + leftover) - (rewardData.rewardRate * rewardDuration);
} else {
nextRewardAmount = 0;
}
}
rewardData.lastUpdateTime = uint40(block.timestamp);
rewardData.periodFinish = uint40(block.timestamp + rewardDuration);
}
Explanation:
In both condition blocks ('if' and 'else'), a check is added to see if 'amount' or '(amount + leftover)' is greater than or equal to 'rewardData.rewardRate * rewardDuration' before performing the subtraction.
If the condition is not met, 'nextRewardAmount' is set to 0 to avoid underflow.
Code when testing using foundry:
contract TempleGoldStaking {
struct RewardData {
uint216 rewardRate;
uint40 lastUpdateTime;
uint40 periodFinish;
}
RewardData public rewardData;
uint256 public nextRewardAmount;
uint256 public rewardDuration = 7 days;
function _notifyReward(uint256 amount) private {
if (block.timestamp >= rewardData.periodFinish) {
rewardData.rewardRate = uint216(amount / rewardDuration);
if (amount >= rewardData.rewardRate * rewardDuration) {
nextRewardAmount = amount - (rewardData.rewardRate * rewardDuration);
} else {
nextRewardAmount = 0;
}
} else {
uint256 remaining = uint256(rewardData.periodFinish) - block.timestamp;
uint256 leftover = remaining * rewardData.rewardRate;
rewardData.rewardRate = uint216((amount + leftover) / rewardDuration);
if ((amount + leftover) >= (rewardData.rewardRate * rewardDuration)) {
nextRewardAmount = (amount + leftover) - (rewardData.rewardRate * rewardDuration);
} else {
nextRewardAmount = 0;
}
}
rewardData.lastUpdateTime = uint40(block.timestamp);
rewardData.periodFinish = uint40(block.timestamp + rewardDuration);
}
function testNotifyReward(uint256 amount) external {
_notifyReward(amount);
}
}
Foundry output:
Ran 1 test for test/TempleGoldStaking.t.sol:TempleGoldStakingTest
[PASS] testNotifyReward() (gas: 86115)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 60.50ms (13.63ms CPU time)
Ran 1 test for src/TempleGoldStaking.sol:TempleGoldStaking
```[PASS] testNotifyReward(uint256) (runs: 256, μ: 69608, ~: 69997)`
```Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 64.91ms (21.16ms CPU time)`
Ran 2 test suites in 118.01ms (125.41ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)