Revolution Protocol - BowTiedOriole's results

A protocol to empower communities to raise funds, fairly distribute governance, and maximize their impact in the world.

General Information

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

Collective

Findings Distribution

Researcher Performance

Rank: 23/110

Findings: 1

Award: $296.99

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: bart1e

Also found by: 0xDING99YA, BowTiedOriole, Ward

Labels

bug
3 (High Risk)
insufficient quality report
partial-25
upgraded by judge
duplicate-49

Awards

296.992 USDC - $296.99

External Links

Lines of code

https://github.com/code-423n4/2023-12-revolutionprotocol/blob/main/packages/revolution/src/base/VotesUpgradeable.sol#L164-L167

Vulnerability details

Bug Description

VotesUpgradeable automatically delegates a user's votes to themselves if a delegatee is not set.

VotesUpgradeable.sol#L165-168

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.

Impact

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.

Proof of Concept

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);
}

Tools Used

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);
}

Assessed type

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

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter