Platform: Code4rena
Start Date: 07/08/2023
Pot Size: $36,500 USDC
Total HM: 11
Participants: 125
Period: 3 days
Judge: alcueca
Total Solo HM: 4
Id: 274
League: ETH
Rank: 32/125
Findings: 2
Award: $109.13
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: thekmj
Also found by: 0xCiphky, 0xDetermination, 0xbrett8571, Eeyore, Team_Rocket, Tripathi, bart1e, deadrxsezzz, immeas, ltyu, mert_eren, pep7siup, popular00
99.3104 USDC - $99.31
https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/GaugeController.sol#L211-L213 https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/GaugeController.sol#L238-L242
In the vote_for_gauge_weights
function, users can vote to allocate their voting power towards a specific gauge by specifying its address and the weight of their vote. The function contains a check to ensure that the gauge being voted for is valid via the isValidGauge
mapping.
However, there's an oversight. If a gauge gets delisted (i.e., removed from the valid gauges list), any user who had previously allocated voting power to this gauge cannot reassign their voting power elsewhere. This is because the function will revert when attempting to set the weight to any value, including 0, for an invalid gauge.
Users who have allocated voting power towards a gauge which later gets delisted are locked out from reallocating their voting power. This means they lose their influence in future voting events.
gauge1
and gauge2
.user1
) creates a lock and votes for both gauges with a weight of 5000
each. Their total voting power becomes 10000
.gauge2
.user1
attempts to reassign their voting power from the removed gauge2
, they're met with a revert due to "Invalid gauge address". Their voting power is effectively stuck.Add test to src/test/GaugeController.t.sol
and run forge test --match-test testMultiVoteWithRemovedGauge
.
function testMultiVoteWithRemovedGauge() public { vm.startPrank(gov); gc.add_gauge(gauge1); gc.add_gauge(gauge2); vm.stopPrank(); vm.startPrank(user1); ve.createLock{value: 1 ether}(1 ether); gc.vote_for_gauge_weights(gauge1, 5000); gc.vote_for_gauge_weights(gauge2, 5000); vm.stopPrank(); assertEq(gc.vote_user_power(user1), 10000); uint256 power; (,power,) = gc.vote_user_slopes(user1, gauge1); assertEq(power, 5000); (,power,) = gc.vote_user_slopes(user1, gauge2); assertEq(power, 5000); vm.startPrank(gov); gc.remove_gauge(gauge2); vm.stopPrank(); vm.startPrank(user1); // should revert when gauge removed and weight is not 0 vm.expectRevert("Invalid gauge address"); gc.vote_for_gauge_weights(gauge2, 2000); // should allow to remove power from removed gauge when weight is 0 and power > 0 gc.vote_for_gauge_weights(gauge2, 0); // should revert when gauge removed and weight is 0 vm.expectRevert("Invalid gauge address"); gc.vote_for_gauge_weights(gauge2, 0); // should be able to allocate released power gc.vote_for_gauge_weights(gauge1, 7000); vm.stopPrank(); assertEq(gc.vote_user_power(user1), 7000); (,power,) = gc.vote_user_slopes(user1, gauge1); assertEq(power, 7000); (,power,) = gc.vote_user_slopes(user1, gauge2); assertEq(power, 0); }
To address this issue, the function's require statement should allow users to set their weight to 0
even if the gauge address is no longer valid:
- require(isValidGauge[_gauge_addr], "Invalid gauge address"); + require(isValidGauge[_gauge_addr] || (_user_weight == 0 && vote_user_slopes[msg.sender][_gauge_addr].power > 0), "Invalid gauge address");
Invalid Validation
#0 - c4-pre-sort
2023-08-12T15:16:02Z
141345 marked the issue as duplicate of #62
#1 - c4-judge
2023-08-25T11:10:16Z
alcueca marked the issue as satisfactory
#2 - c4-judge
2023-08-25T22:43:22Z
alcueca changed the severity to 2 (Med Risk)
#3 - c4-judge
2023-08-25T22:43:36Z
alcueca changed the severity to 3 (High Risk)
🌟 Selected for report: RED-LOTUS-REACH
Also found by: 0x3b, 0x4non, 0xCiphky, 0xDING99YA, 0xDetermination, 0xE1, 0xG0P1, 0xStalin, 0xWaitress, 0xbrett8571, 0xhacksmithh, 0xkazim, 0xmuxyz, 0xweb3boy, 14si2o_Flint, AlexCzm, Alhakista, Bube, Bughunter101, Deekshith99, Eeyore, Giorgio, HChang26, InAllHonesty, JP_Courses, KmanOfficial, MatricksDeCoder, Mike_Bello90, MrPotatoMagic, Naubit, QiuhaoLi, RHaO-sec, Raihan, Rolezn, SUPERMAN_I4G, Shubham, Silverskrrrt, Strausses, T1MOH, Topmark, Tripathi, Watermelon, _eperezok, aakansha, auditsea, audityourcontracts, ayden, carlos__alegre, castle_chain, cducrest, ch0bu, d23e, deadrxsezzz, deth, devival, erebus, fatherOfBlocks, halden, hassan-truscova, hpsb, hunter_w3b, imkapadia, immeas, jat, kaden, kaveyjoe, klau5, koxuan, kutugu, ladboy233, lanrebayode77, leasowillow, lsaudit, markus_ether, matrix_0wl, merlin, nemveer, ni8mare, nonseodion, oakcobalt, owadez, p_crypt0, pipidu83, piyushshukla, popular00, ppetrov, rjs, sandy, sl1, supervrijdag, tay054, thekmj, wahedtalash77, windhustler, zhaojie
9.8204 USDC - $9.82
VotingEscrow#increaseAmount
from expired delegatee lockIn the increaseAmount
function, if a user's lock is delegated to a delegatee whose lock has expired, the function will revert. This prevents the user from increasing their lock amount until they change their delegatee.
Provide a mechanism to allow users to increase their lock amount even if their delegatee's lock has expired. This can be done by automatically undelegating the user's lock back to the user.
In the increaseAmount
function, the Deposit
event is emitted with the incorrect unlockTime
for the user. The event uses the old unlock time instead of the new one.
Ensure that the Deposit
event emits the correct unlock time.
- uint256 unlockTime = locked_.end; LockAction action = LockAction.INCREASE_AMOUNT; LockedBalance memory newLocked = _copyLock(locked_); newLocked.amount += int128(int256(_value)); newLocked.end = _floorToWeek(block.timestamp + LOCKTIME); + uint256 unlockTime = newLocked.end;
GaugeController#change_gauge_weight
In the change_gauge_weight
function, there's no check to ensure that the provided gauge address is valid before changing its weight.
Add a require
statement to check if the provided _gauge
address is a valid gauge. This will prevent potential misuse or errors.
function change_gauge_weight(address _gauge, uint256 _weight) public onlyGovernance { + require(isValidGauge[_gauge], "Invalid gauge address");
Throughout the contracts, there are variable and function names that are more reminiscent of Vyper naming conventions (e.g., unlock_time
, uepoch
) rather than the typical camelCase naming convention in Solidity.
To maintain consistency and readability, rename these variables and functions to follow the camelCase convention.
For example:
- uint256 unlock_time = ... + uint256 unlockTime = ...
- function vote_for_gauge_weights(...) + function voteForGaugeWeights(...)
LockAction
enum, the INCREASE_TIME
and QUIT
are not usedIn the VotingEscrow
contract, the LockAction
enum has two values INCREASE_TIME
and QUIT
which are not being used in the contract. These are just remnants of an earlier version of the contract.
Remove unused enum values.
enum LockAction { CREATE, INCREASE_AMOUNT, INCREASE_AMOUNT_AND_DELEGATION, - INCREASE_TIME, WITHDRAW, - QUIT, DELEGATE, UNDELEGATE }
IVotingEscrow
interface with documentation about its contentsThe contract has comments indicating a reference to the IVotingEscrow
interface, but this interface is not provided in the given code. This makes it challenging to understand the expected behavior and interface of the VotingEscrow
contract.
Ensure that the IVotingEscrow
interface is included when sharing or deploying the contract. This aids in understanding the expected methods and their signatures.
Throughout the VotingEscrow
contract, single-line comments using //
are used for function documentation instead of the appropriate NatSpec format using ///
or /** */
.
Using the correct format is important for generating the contract's interface specification and documentation, ensuring clarity and consistency.
Replace single-line comments //
with the appropriate NatSpec format ///
for single-line comments or /** */
for multi-line comments before function definitions.
For instance:
- // @dev Floors a timestamp to the nearest weekly increment + /// @dev Floors a timestamp to the nearest weekly increment
Similarly, make similar adjustments throughout the contract to conform to the NatSpec format.
GaugeController#vote_for_gauge_weights
In the vote_for_gauge_weights
function, a check is present to ensure the _user_weight
is non-negative. However, _user_weight
is of type uint256
, which is always non-negative.
require(_user_weight >= 0 && _user_weight <= 10_000, "Invalid user weight");
The check _user_weight >= 0
is redundant and can be safely removed.
- require(_user_weight >= 0 && _user_weight <= 10_000, "Invalid user weight"); + require(_user_weight <= 10_000, "Invalid user weight");
#0 - 141345
2023-08-13T07:56:02Z
#1 - c4-judge
2023-08-22T13:43:14Z
alcueca marked the issue as grade-a