TempleGold

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

Malicious user can inflate checkpoints of any user which can lead to DOS on some chains which have lesser block gas limit

Summary

A user can stake for another user and create new checkpoints, which can be exploited by an attacker to perform a DOS attack on the voting mechanism for any staker

Vulnerability Details

function stakeFor(address _for, uint256 _amount) public whenNotPaused {
if (_amount == 0) revert CommonEventsAndErrors.ExpectedNonZero();
..................................
_moveDelegates(address(0), delegates[_for], _amount);
}

If the amount is non-zero, an attacker can deposit for any user _for and create new checkpoints

function _writeCheckpoint(
address delegatee,
uint256 nCheckpoints,
uint256 oldVotes,
uint256 newVotes
) internal {
if (nCheckpoints > 0 && _checkpoints[delegatee][nCheckpoints - 1].fromBlock == block.number) {
_checkpoints[delegatee][nCheckpoints - 1].votes = newVotes;
} else {
_checkpoints[delegatee][nCheckpoints] = Checkpoint(block.number, newVotes);
numCheckpoints[delegatee] = nCheckpoints + 1;
}
emit DelegateVotesChanged(delegatee, oldVotes, newVotes);
}

If block.number is different from the previous block, it creates new checkpoint.

function getPriorVotes(address account, uint256 blockNumber) external override view returns (uint256) {
if (blockNumber >= block.number) { revert InvalidBlockNumber(); }
uint256 nCheckpoints = numCheckpoints[account];
if (nCheckpoints == 0) {
return 0;
}
// First check most recent balance
if (_checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) {
return _checkpoints[account][nCheckpoints - 1].votes;
}
// Next check implicit zero balance
if (_checkpoints[account][0].fromBlock > blockNumber) {
return 0;
}
uint256 lower = 0;
uint256 upper = nCheckpoints - 1;
while (upper > lower) {
uint256 center = upper - (upper - lower) / 2; // ceil, avoiding overflow//MLOAD
Checkpoint memory cp = _checkpoints[account][center]; //SLOAD
if (cp.fromBlock == blockNumber) {//MLOAD
return cp.votes;
} else if (cp.fromBlock < blockNumber) {//MLOAD
lower = center;//MSTORE
} else {
upper = center - 1;//MSTORE
}
}
return _checkpoints[account][lower].votes; //SLOAD
}

As stated in the documentation, the contract supports every EVM chain, and some L2s may have a lower block gas limit, such as zksync-era etc. An attacker can use this vulnerability to DoS the original user from voting. Although the binary search is highly optimized, if the block gas limit is low, even 25-30 iterations of the while loop will cause the transaction to revert due to an out-of-gas error

Since even a 1 wei amount can be deposited and the gas fees on L2 are low, the attack cost is minimal, making it feasible if the attacker stands to gain by preventing the original users from voting.

A user who holds majority of totalSupply can be prevented from voting mechanism since getPriorVotes(address account, uint256 blockNumber) will revert due to OOG error.

Impact

Attacker can prevent user from voting

Tools Used

Manual

Recommendations

mplement a minimum stake value that can be staked via stakeFor, which will increase the attack cost and make the attack unprofitable for the attacker.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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