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: 31/125
Findings: 2
Award: $110.19
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: deadrxsezzz
Also found by: 0xComfyCat, Brenzee, Team_Rocket, Yanchuan, auditsea, bin2chen, cducrest, markus_ether, oakcobalt
105.9553 USDC - $105.96
Votes cast for newly added gauges are irretrievably lost. The Governance must call the change_gauge_weight
method to allow any votes getting counted.
While the add_gauge
function whitelists a new Gauge, it overlooks one crucial step.
The core issue is that the function add_gauge
does not set the time_weight
of the new gauge, as the original vyper code does.
As a result, the get_weight
function prematurely returns, failing to allocate any rewards.
By calling change_gauge_weight
, the government can finally set the time index enabling new voter to cast their votes.
Here is a POC showcasing the bug:
// prepare vm.startPrank(gov); gc.add_gauge(user1); vm.deal(user1, 100 ether); vm.stopPrank(); // issue vm.startPrank(user1); uint256 v = 10 ether; ve.createLock{value: v}(v); gc.vote_for_gauge_weights(user1, 100); assertEq(gc.get_gauge_weight(user1), 0); // note the user voted, but had no effect
Manual review
There are two potential fixes.
function vote_for_gauge_weights(address _gauge_addr, uint256 _user_weight) external { + require(time_weight[_gauge_addr] > 0, "weight must be non-zero");
function add_gauge(address _gauge) external onlyGovernance { require(!isValidGauge[_gauge], "Gauge already exists"); isValidGauge[_gauge] = true; + _change_gauge_weight(_gauge, 0); emit NewGauge(_gauge); }
Governance
#0 - c4-pre-sort
2023-08-12T14:46:31Z
141345 marked the issue as duplicate of #169
#1 - c4-pre-sort
2023-08-12T14:52:05Z
141345 marked the issue as duplicate of #288
#2 - c4-judge
2023-08-25T10:27:11Z
alcueca marked the issue as duplicate of #401
#3 - c4-judge
2023-08-25T10:28:11Z
alcueca marked the issue as duplicate of #288
#4 - c4-judge
2023-08-25T10:35:17Z
alcueca marked the issue as partial-50
#5 - c4-judge
2023-08-25T22:45:02Z
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
4.2289 USDC - $4.23
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#L276-L277
Governance has the ability to modify gauge weights under emergency circumstances. However, an attacker can counteract this adjustment by recasting their vote for the gauge, hereby restoring the gauge weight.
The governance sets the weight of a malicous gauge to zero by calling change_gauge_weight
. Now the attacker can reclaim their voting power
by setting their voting power to zero. Then they can vote for the vault again, sidestepping the previous government action.
Here's a demonstration highlighting this vulnerability:
function testVoteExploit() public { // prepare vm.startPrank(gov); gc.add_gauge(user1); gc.change_gauge_weight(user1, 100); vm.deal(user1, 100 ether); vm.stopPrank(); // user votes for gauge vm.startPrank(user1); uint256 v = 10 ether; ve.createLock{value: v}(v); gc.vote_for_gauge_weights(user1, 100); assertTrue(gc.get_gauge_weight(user1) > 100); vm.stopPrank(); // government sets weights to zero vm.startPrank(gov); gc.change_gauge_weight(user1, 0); assertEq(gc.get_gauge_weight(user1), 0); vm.stopPrank(); // user claim back voting power vm.startPrank(user1); gc.vote_for_gauge_weights(user1, 0); assertEq(gc.vote_user_power(user1), 0); // user gives gauge weight back gc.vote_for_gauge_weights(user1, 100); assertTrue(gc.get_gauge_weight(user1) > 0); vm.stopPrank(); }
Manual review
To counter this exploit, it's essential to limit the number of times a user can vote in a given period. It's suggested that users are not permitted to vote more than once a week. As a point of comparison, Curve has limited users to vote once every 10 days.
A proposed code modification is:
function vote_for_gauge_weights(address _gauge_addr, uint256 _user_weight) external { + require(block.timestamp >= last_user_vote[msg.sender][_gauge_addr] + WEIGHT_VOTE_DELAY, "Cannot vote so often");
In this context, the WEIGHT_VOTE_DELAY should be set to span beyond one week.
Governance
#0 - c4-pre-sort
2023-08-12T16:44:56Z
141345 marked the issue as duplicate of #331
#1 - c4-judge
2023-08-25T10:46:58Z
alcueca marked the issue as not a duplicate
#2 - alcueca
2023-08-25T10:47:35Z
@OpenCoreCH, I don't think this is exactly a duplicate, as it works on already initialized markets, that were disabled by governance.
#3 - OpenCoreCH
2023-08-25T16:54:31Z
Then they can vote for the vault again, sidestepping the previous government action.
Yes that is true if governance only set the weights to 0 (i.e., calls change_gauge_weight
like in the PoC). However, if governance calls remove_gauge
which sets the weights to zero and removes the gauge from the whitelist (i.e., the intended action when no votes should be possible anymore), this is not the case, as the gauge will not be whitelisted when the user tries to vote again.
#4 - c4-judge
2023-08-25T21:57:02Z
alcueca changed the severity to QA (Quality Assurance)
#5 - c4-judge
2023-08-25T21:57:08Z
alcueca marked the issue as grade-b
#6 - alcueca
2023-08-25T21:57:33Z
Valid QA for the sponsor to document