Platform: Code4rena
Start Date: 25/08/2022
Pot Size: $75,000 USDC
Total HM: 35
Participants: 147
Period: 7 days
Judge: 0xean
Total Solo HM: 15
Id: 156
League: ETH
Rank: 22/147
Findings: 2
Award: $1,107.27
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: hansfriese
Also found by: V_B, berndartmueller, csanuragjain, m9800, zzzitron
https://github.com/code-423n4/2022-08-olympus/blob/b5e139d732eb4c07102f149fb9426d356af617aa/src/policies/Governance.sol#L181 https://github.com/code-423n4/2022-08-olympus/blob/b5e139d732eb4c07102f149fb9426d356af617aa/src/policies/Governance.sol#L259 https://github.com/code-423n4/2022-08-olympus/blob/b5e139d732eb4c07102f149fb9426d356af617aa/src/policies/Governance.sol#L302-L304
Submitted proposals require a certain amount of user endorsements before being activated. Users can endorse as many proposals as they like. The current balance of VOTES
tokens of a user is used as a measure of endorsement by a user.
Once a proposal is activated, users can vote with their VOTES
token balance. When votes are cast, the current user balance of VOTES
tokens used to vote on the proposal is locked in the contract until the proposal is no longer active. Once a proposal is no longer active, either by being executed or replaced by a new proposal, users may redeem their votes back from the contract.
Once voted on an active proposal, VOTES
tokens are locked in the contract. Users cannot endorse other proposals and must wait until they can redeem their votes.
Worst case, all users with VOTES
tokens voted on an active proposal and have their tokens locked. Users cannot endorse new proposals. Hence no new proposal can be activated until the currently active proposal is executed or expired.
The VOTES
balance of a user (VOTES.balanceOf(msg.sender)
) is used as a measure of endorsement from a user for a proposal:
policies/Governance.endorseProposal
function endorseProposal(uint256 proposalId_) external { uint256 userVotes = VOTES.balanceOf(msg.sender); [...] }
Voting for a proposal will lock all VOTES
token (userVotes
) from a user:
function vote(bool for_) external { uint256 userVotes = VOTES.balanceOf(msg.sender); if (activeProposal.proposalId == 0) { revert NoActiveProposalDetected(); } if (userVotesForProposal[activeProposal.proposalId][msg.sender] > 0) { revert UserAlreadyVoted(); } if (for_) { yesVotesForProposal[activeProposal.proposalId] += userVotes; } else { noVotesForProposal[activeProposal.proposalId] += userVotes; } userVotesForProposal[activeProposal.proposalId][msg.sender] = userVotes; VOTES.transferFrom(msg.sender, address(this), userVotes); emit WalletVoted(activeProposal.proposalId, msg.sender, for_, userVotes); }
Votes are locked in the Governance
contract once voted for a proposal and can be redeemed later.
policies/Governance.reclaimVotes
function reclaimVotes(uint256 proposalId_) external { uint256 userVotes = userVotesForProposal[proposalId_][msg.sender]; if (userVotes == 0) { revert CannotReclaimZeroVotes(); } if (proposalId_ == activeProposal.proposalId) { revert CannotReclaimTokensForActiveVote(); } [...] }
Manual review
Consider using, in addition to the freely available votes, the locked votes of a user for endorsing a proposal:
function endorseProposal(uint256 proposalId_) external { - uint256 userVotes = VOTES.balanceOf(msg.sender); + uint256 userVotes = VOTES.balanceOf(msg.sender) + userVotesForProposal[activeProposal.proposalId][msg.sender]; if (proposalId_ == 0) { revert CannotEndorseNullProposal(); } [...] }
#0 - bahurum
2022-09-02T12:10:10Z
Duplicate of #362
#1 - fullyallocated
2022-09-04T02:29:09Z
Duplicate of #376
#2 - 0xean
2022-09-19T15:00:49Z
upgrading to H to match #376
🌟 Selected for report: zzzitron
Also found by: Ruhum, Trust, berndartmueller, csanuragjain, pashov, sorrynotsorry
The permissioned TRSRY.setApprovalFor
function sets approval for specific withdrawer addresses.
However, changing an allowance with this method brings the risk that a withdrawer may use both the old and the new allowance by unfortunate transaction ordering (see the same issue within OZ ERC20)
A treasury withdrawer can front-run transactions with setApprovalFor(..)
and withdraw both the old and the new allowance amount.
/// @notice Sets approval for specific withdrawer addresses function setApprovalFor( address withdrawer_, ERC20 token_, uint256 amount_ ) external permissioned { withdrawApproval[withdrawer_][token_] = amount_; emit ApprovedForWithdrawal(withdrawer_, token_, amount_); }
Manual review
Consider implementing increasing and decreasing allowance functions (similar to the OZ ERC20 implementation) to mitigate the race condition.
#0 - 0xean
2022-09-16T21:05:30Z
dupe of #410