TempleGold

TempleDAO
Foundry
25,000 USDC
View results
Submission Details
Severity: high
Invalid

Underflow Vulnerability in '_notifyReward' Function

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);
// collect dust
nextRewardAmount = amount - (rewardData.rewardRate * rewardDuration);
} else {
uint256 remaining = uint256(rewardData.periodFinish) - block.timestamp;
uint256 leftover = remaining * rewardData.rewardRate;
rewardData.rewardRate = uint216((amount + leftover) / rewardDuration);
// collect dust
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);
// collect dust
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);
// collect dust
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);
// collect dust
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);
// collect dust
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 to allow testing of _notifyReward
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)

Updates

Lead Judging Commences

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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