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: 26/125
Findings: 3
Award: $164.39
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: SpicyMeatball
Also found by: 0xComfyCat, GREY-HAWK-REACH, Yanchuan, cducrest, immeas, kaden, mert_eren, nonseodion, pep7siup, popular00, ppetrov
71.5198 USDC - $71.52
https://github.com/code-423n4/2023-08-verwa/blob/main/src/LendingLedger.sol#L174
Claiming in a lump sum within the lending market can cause loyal lenders' income to be diluted at the last second of an epoch. This issue disincentivizes lenders from staying invested in the lending market for the entire epoch, or even longer. Malicious actors can exploit this vulnerability by entering the market at the last moment, claiming rewards disproportionately, and then swiftly moving their liquidity to another platform. This practice leads to the unfair distribution of rewards and undermines the attractiveness and fairness of the lending platform.
// Findings are labeled with '<= FOUND' // File: src/LendingLedger.sol 152: function claim( 153: ... 174: cantoToSend += (marketWeight * userBalance * ri.amount) / (1e18 * marketBalance); // <= FOUND: Lender's latest rewards can be diluted by late comer 179: ... 182: }
Line 174 shows that a userBalance
recorded in the last second of the epoch is the same as one recorded from the epoch begin. This leads to the same cantoToSend
amount to claim.
Below is the exploit scenario:
userBalance
and marketBalance
as 1e18 each.currEpoch + WEEK - 1
, Bob joins the market by depositing 10e18 tokens.userBalance
and marketBalance
.userBalance
contribution of 10e18 tokens significantly impacts the rewards allocation.Manual Review
Implement a more equitable rewards distribution mechanism. The Ledger should track the timestamp when a user joins the market, and the sync_ledger
function should use this timestamp to gradually increase the eligible balance for rewards allocation. This increase should follow a slope of _delta / WEEK
to ensure that latecomers' rewards are proportionate to their contributions and that loyal lenders' rewards are not unfairly diluted.
_________ ________ | / | / _____| ___ / S,M epoch S M (M - S = 1 WEEK)
S: sync_ledger timestamp M: balance mature timestamp
MEV
#0 - c4-pre-sort
2023-08-12T07:23:15Z
141345 marked the issue as duplicate of #71
#1 - c4-judge
2023-08-25T11:01:28Z
alcueca changed the severity to 3 (High Risk)
#2 - c4-judge
2023-08-25T11:03:36Z
alcueca marked the issue as partial-50
🌟 Selected for report: ltyu
Also found by: 0xDING99YA, 3docSec, KmanOfficial, MrPotatoMagic, RED-LOTUS-REACH, Tendency, Yuki, bart1e, bin2chen, carrotsmuggler, cducrest, kaden, mert_eren, pep7siup, popular00, qpzm, seerether, zhaojie
43.2097 USDC - $43.21
https://github.com/code-423n4/2023-08-verwa/tree/main/src/VotingEscrow.sol#L383
Users, who delegated their lock and have their lock expired, are DOSed when attempting to withdraw their funds. The user is effectively locked out of their funds and any interaction with the contract is rendered impossible.
// File: src/VotingEscrow.sol 356: function delegate(address _addr) external nonReentrant { 357: ... 382: require(toLocked.amount > 0, "Delegatee has no lock"); 383: require(toLocked.end > block.timestamp, "Delegatee lock expired"); // <= FOUND: user expired lock is DOS from withdrawal 384: require(toLocked.end >= fromLocked.end, "Only delegate to longer lock"); 385: _delegate(delegatee, fromLocked, value, LockAction.UNDELEGATE); 386: _delegate(_addr, toLocked, value, LockAction.DELEGATE); 387: }
// File: src/test/VotingEscrow.t.sol function testUnDelegateExpired() public { // delegate from user1 to user2 vm.prank(user1); ve.createLock{value: LOCK_AMT}(LOCK_AMT); vm.prank(user2); ve.createLock{value: LOCK_AMT}(LOCK_AMT); vm.prank(user1); ve.delegate(user2); (, uint256 end, , address delegatee) = ve.locked(user1); assertEq(delegatee, user2); (, , int128 delegated, ) = ve.locked(user2); assertEq(delegated, int128(int256(LOCK_AMT * 2))); // user1's lock expired vm.warp(end + 1); // undelegate to prepare for withdrawal, etc. vm.startPrank(user1); vm.expectRevert("Delegatee lock expired"); ve.delegate(user1); // @audit since Undelegation is DOSed, all functions regarding lock management are rendered inoperable vm.expectRevert("Lock delegated"); ve.withdraw(); vm.expectRevert("Lock expired"); ve.increaseAmount{value: 1}(1); vm.expectRevert("Lock exists"); ve.createLock{value: 1}(1); vm.stopPrank(); }
Manual Review
Allow lock owners to undelegate their locks even if the locks have already expired.
DoS
#0 - c4-pre-sort
2023-08-11T11:56:42Z
141345 marked the issue as duplicate of #223
#1 - c4-pre-sort
2023-08-13T12:00:33Z
141345 marked the issue as duplicate of #112
#2 - c4-judge
2023-08-24T07:15:57Z
alcueca marked the issue as duplicate of #82
#3 - c4-judge
2023-08-24T07:20:40Z
alcueca changed the severity to 2 (Med Risk)
#4 - c4-judge
2023-08-24T07:41:01Z
alcueca marked the issue as satisfactory
#5 - c4-pre-sort
2023-08-24T08:20:01Z
141345 marked the issue as not a duplicate
#6 - c4-pre-sort
2023-08-24T08:20:26Z
141345 marked the issue as not a duplicate
#7 - c4-pre-sort
2023-08-24T08:22:09Z
141345 marked the issue as duplicate of #211
#8 - c4-judge
2023-08-26T21:24:29Z
alcueca changed the severity to 3 (High Risk)
🌟 Selected for report: thekmj
Also found by: 0xCiphky, 0xDetermination, 0xbrett8571, Eeyore, Team_Rocket, Tripathi, bart1e, deadrxsezzz, immeas, ltyu, mert_eren, pep7siup, popular00
49.6552 USDC - $49.66
https://github.com/code-423n4/2023-08-verwa/blob/main/src/GaugeController.sol#L213 https://github.com/code-423n4/2023-08-verwa/blob/main/src/GaugeController.sol#L238-L242
A user who has voted on a gauge that gets removed becomes unable to revoke their votes. This vulnerability can lead to a loss of up to 100% of the user's votes. In addition, the user's voting power becomes tied to the removed gauge, preventing them from using their votes on other gauges effectively. This could result in the user's veRWA becoming less powerful or even entirely useless.
vote_user_power
is recorded as 1,000.vote_for_gauge_weights
function with a weight of 0 to revoke her votes from the removed gauge, the transaction reverts at Line 213 which effectively denies a vote_user_power
reset at Line 242.// Findings are labeled with '<= FOUND' // File: src/GaugeController.sol 211: function vote_for_gauge_weights(address _gauge_addr, uint256 _user_weight) external { 212: require(_user_weight >= 0 && _user_weight <= 10_000, "Invalid user weight"); 213: require(isValidGauge[_gauge_addr], "Invalid gauge address"); // <= FOUND: user cannot revoke their votes on removed gauge 212: ... 238: // Check and update powers (weights) used 239: uint256 power_used = vote_user_power[msg.sender]; 240: power_used = power_used + new_slope.power - old_slope.power; 241: require(power_used >= 0 && power_used <= 10_000, "Used too much power"); 242: vote_user_power[msg.sender] = power_used; // <= FOUND: cannot be reset 243: ... 278: }
This vulnerability leaves Alice's veRWA less influential or completely ineffective, as her voting power is tied to a non-existent gauge.
Manual Review
To address this vulnerability, the contract should implement a revoke_votes_for_gauge
function that allows users to reset their vote_user_power
and vote_user_slopes
for a gauge that has been removed. This function should be designed to work only when the user has previously voted on the specific gauge and when the gauge has been removed.
function revoke_votes_for_gauge(address _gauge_addr) external { require(!isValidGauge[_gauge_addr], "Gauge must be removed"); require(vote_user_power[msg.sender] > 0, "No power used"); uint256 stuck_power = vote_user_slopes[msg.sender][_gauge_addr].power; require(stuck_power > 0, "No power voted on this gauge"); vote_user_power[msg.sender] -= stuck_power; delete vote_user_slopes[msg.sender][_gauge_addr]; }
Other
#0 - c4-pre-sort
2023-08-12T04:39:42Z
141345 marked the issue as duplicate of #62
#1 - c4-judge
2023-08-25T11:11:16Z
alcueca marked the issue as partial-50
#2 - c4-judge
2023-08-25T22:43:36Z
alcueca changed the severity to 3 (High Risk)