Platform: Code4rena
Start Date: 13/12/2023
Pot Size: $36,500 USDC
Total HM: 18
Participants: 110
Period: 8 days
Judge: 0xTheC0der
Id: 311
League: ETH
Rank: 23/110
Findings: 1
Award: $296.99
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: bart1e
Also found by: 0xDING99YA, BowTiedOriole, Ward
296.992 USDC - $296.99
VotesUpgradeable automatically delegates a user's votes to themselves if a delegatee is not set.
function delegates(address account) public view virtual returns (address) { VotesStorage storage $ = _getVotesStorage(); return $._delegatee[account] == address(0) ? account : $._delegatee[account]; }
If a user delegates their votes to the zero address, the above logic will still return that a user has delegated their votes to themselves.
This will mess up the logic in _moveDelegateVotes()
VotesUpgradeable.sol#L233-L253
function _moveDelegateVotes(address from, address to, uint256 amount) private { VotesStorage storage $ = _getVotesStorage(); if (from != to && amount > 0) { if (from != address(0)) { (uint256 oldValue, uint256 newValue) = _push( $._delegateCheckpoints[from], _subtract, SafeCast.toUint208(amount) ); emit DelegateVotesChanged(from, oldValue, newValue); } if (to != address(0)) { (uint256 oldValue, uint256 newValue) = _push( $._delegateCheckpoints[to], _add, SafeCast.toUint208(amount) ); emit DelegateVotesChanged(to, oldValue, newValue); } } }
If a user tries to redelegate to themselves, _moveDelegateVotes()
will receive the same address for from
and to
and skip the delegation.
If a user tries to delegate to another address, it will view from
as having zero delegates (or not enough delegates) and will revert due to underflow.
If a user delegates their votes to the zero address, those votes will be locked forever. Additionally, that user will no longer be able to delegate their votes to another address.
This also makes misuse and phishing attacks more feasible because it involves interacting directly with the trusted contract.
function testVotesDelegatedToAddressZeroLostForever() public { // Mint tokens to the user uint256 mintAmount = 1000 * 1e18; vm.prank(address(erc20TokenEmitter)); erc20Token.mint(address(this), mintAmount); assertEq(erc20Token.getVotes(address(this)), mintAmount); // Delegate voting power to address 0 erc20Token.delegate(address(0)); assertEq(erc20Token.getVotes(address(this)), 0); // Try to delegate back to user and also other address erc20Token.delegate(address(this)); assertEq(erc20Token.getVotes(address(this)), 0); vm.expectRevert(); erc20Token.delegate(address(10)); assertEq(erc20Token.getVotes(address(10)), 0); assertEq(erc20Token.getVotes(address(this)), 0); // Mint more tokens to the user vm.prank(address(erc20TokenEmitter)); erc20Token.mint(address(this), mintAmount); assertEq(erc20Token.balanceOf(address(this)), mintAmount * 2); // User can no longer delegate votes vm.expectRevert(); erc20Token.delegate(address(10)); // Only newly minted tokens are accounted for assertEq(erc20Token.getVotes(address(10)), 0); assertEq(erc20Token.getVotes(address(this)), mintAmount); }
Manual Review
Check that a user does not delegate their votes to the zero address.
VotesUpgradeable.sol#L172-L175
function delegate(address delegatee) public virtual { + require(delegatee != address(0), "Cannot delegate to address(0)"); address account = _msgSender(); _delegate(account, delegatee); }
Under/Overflow
#0 - c4-pre-sort
2023-12-23T03:14:51Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2023-12-23T03:15:14Z
raymondfam marked the issue as duplicate of #49
#2 - c4-judge
2024-01-06T23:29:03Z
MarioPoneder marked the issue as unsatisfactory: Out of scope
#3 - c4-judge
2024-01-10T19:58:24Z
MarioPoneder changed the severity to 3 (High Risk)
#4 - c4-judge
2024-01-10T19:58:30Z
MarioPoneder marked the issue as partial-25