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: 55/125
Findings: 2
Award: $20.06
π Selected for report: 0
π Solo Findings: 0
π 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
15.8333 USDC - $15.83
https://github.com/code-423n4/2023-08-verwa/blob/main/src/VotingEscrow.sol#L331 https://github.com/code-423n4/2023-08-verwa/blob/main/src/VotingEscrow.sol#L384
Locked Account Owner will have funds Locked Forever Even after Expiration day has elapsed if Delegetee Loses Private key or refuse to withdraw
Analyzing this complex code base specifically the VotingEscrow.sol contract shows a complex delegation system but there is contradiction which would affect the ability of a Locked account owner to withdraw it funds even after the LOCK_TIME period has expired.
326. function withdraw() external nonReentrant { 327. LockedBalance memory locked_ = locked[msg.sender]; 328. // Validate inputs 329. require(locked_.amount > 0, "No lock"); 330. require(locked_.end <= block.timestamp, "Lock not expired"); 331. require(locked_.delegatee == msg.sender, "Lock delegated");
A quick look at L331 of the withdraw(...) function above shows that Account owner ( msg.sender) must undelegated the delegatee (locked_.delegatee) back to his own address for the condition on L331 to pass and the only way to do that is the delegate(...) function at L356-L387.
356. function delegate(address _addr) external nonReentrant { .... 367. if (delegatee == msg.sender) { 368. // Delegate 369. fromLocked = locked_; 370. toLocked = locked[_addr]; 371. } else if (_addr == msg.sender) { 372. // Undelegate 373. fromLocked = locked[delegatee]; 374. toLocked = locked_; 375. } else { ..... } 382. require(toLocked.amount > 0, "Delegatee has no lock"); 383. require(toLocked.end > block.timestamp, "Delegatee lock expired"); 384. require(toLocked.end >= fromLocked.end, "Only delegate to longer lock"); <--- vulnerability point .... 387. }
looking at the conditions inside the delegate function, since the user wants to undelegate back to himself, we are interested with the condition at L371 since _addr parameter would equal msg.sender, the content of this condition has a flaw as it contradicts the required condition on L384 which simply mandates the lock end period of the locked account to be updated to be longer that fromLocked.end value. but the problem is this same condition was passed when
336. newLocked.end = 0;
with this Alice can undelegate Bob to Alice as (Alice is now higher since Bob is now zero) But the big question is, what happens if Bob refuse to withdraw or loses his private key completely, then Alice funds is inaccessible or doomed as the case may be.
Solidity, Hardhat
This vulnerability is centered on the condition on L384 of the VotingEscrow.sol contract which does not factor in when user wants to undelegate back to themselves, a good solution would be to have this condition specific to each of the condition at L387, L371 & L375, more importantly L371 which will have it own require conditions in opposite direction i.e
367. if (delegatee == msg.sender) { 368. // Delegate 369. fromLocked = locked_; 370. toLocked = locked[_addr]; +++ require(toLocked.end >= fromLocked.end, "Only delegate to longer lock"); 371. } else if (_addr == msg.sender) { 372. // Undelegate 373. fromLocked = locked[delegatee]; 374. toLocked = locked_; +++ if(Bob has withdrawn ){ +++ require(toLocked.end <= fromLocked.end, "Only delegate to Longer lock");//<--when Bob has withdrawn} +++ if( When Bob has not withdrawn ){ +++ require(toLocked.end <= fromLocked.end, "Only delegate to Shorter lock");//<-Bob has not withdrawn} 375. } else { 376. // Re-delegate 377. fromLocked = locked[delegatee]; 378. toLocked = locked[_addr]; 379. // Update owner lock if not involved in delegation 380. locked[msg.sender] = locked_; +++ require(toLocked.end >= fromLocked.end, "Only delegate to longer lock"); 381. } 382. require(toLocked.amount > 0, "Delegatee has no lock"); 383. require(toLocked.end > block.timestamp, "Delegatee lock expired"); 384. --- require(toLocked.end >= fromLocked.end, "Only delegate to longer lock");
Access Control
#0 - c4-pre-sort
2023-08-12T11:09:52Z
141345 marked the issue as duplicate of #223
#1 - c4-pre-sort
2023-08-13T11:45:11Z
141345 marked the issue as not a duplicate
#2 - c4-pre-sort
2023-08-13T16:18:09Z
141345 marked the issue as duplicate of #178
#3 - 141345
2023-08-13T16:19:07Z
can increaseAmount(), although not a good solution.
not exactly the same as https://github.com/code-423n4/2023-08-verwa-findings/issues/178, but this one has pointed out the issue
#4 - c4-judge
2023-08-24T07:14:00Z
alcueca marked the issue as duplicate of #82
#5 - c4-judge
2023-08-24T07:20:40Z
alcueca changed the severity to 2 (Med Risk)
#6 - c4-judge
2023-08-24T07:35:20Z
alcueca marked the issue as partial-50
#7 - c4-judge
2023-08-29T06:37: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
4.2289 USDC - $4.23
https://github.com/code-423n4/2023-08-verwa/blob/main/src/LendingLedger.sol#L54-L63 https://github.com/code-423n4/2023-08-verwa/blob/main/src/LendingLedger.sol#L82-L86 https://github.com/code-423n4/2023-08-verwa/blob/main/src/LendingLedger.sol#L116-L123
Code Application or Comment is Wrong in L54-L63 of the _checkpoint_lender(...) function in the LendingLedger.sol contract which could cause misleading functionality Error
https://github.com/code-423n4/2023-08-verwa/blob/main/src/LendingLedger.sol#L54-L63
54. /// @param _forwardTimestampLimit Until which epoch (provided as timestamp) should the update be applied. If it is higher than the current epoch timestamp, this will be used. 55. function _checkpoint_lender( 56. address _market, 57. address _lender, 58. uint256 _forwardTimestampLimit 59. ) private { 60. uint256 currEpoch = (block.timestamp / WEEK) * WEEK; 61. 62. uint256 lastUserUpdateEpoch = lendingMarketBalancesEpoch[_market][_lender]; 63. uint256 updateUntilEpoch = Math.min(currEpoch, _forwardTimestampLimit);
The comment at L54 shows that the developer intends to use higher value in relation to _forwardTimestampLimit variable when needed but the opposite of the application of this intention was done at L63 as Math.min() function was used which selects the minimum value of the two variable. This Error can be spotted at two other instances at L82-L86 & L116-L123 of the LendingLedger.sol contract
Solidity, Manual Review
Math.max should be used if that is the intention of the developer, if not the comment should be corrected to avoid misleading functionality.
Error
#0 - 141345
2023-08-12T06:59:37Z
misleading comment
QA might be more appropriate.
#1 - c4-pre-sort
2023-08-13T05:10:10Z
141345 marked the issue as primary issue
#2 - c4-sponsor
2023-08-16T14:20:09Z
OpenCoreCH marked the issue as sponsor acknowledged
#3 - c4-sponsor
2023-08-16T14:21:38Z
OpenCoreCH marked the issue as disagree with severity
#4 - OpenCoreCH
2023-08-16T14:22:48Z
Implementation is correct, the comment is a bit ambiguous. The "this" in the comment refers to the current epoch timestamp. So the comment should be read as:
Until which epoch (provided as timestamp) should the update be applied. If it is higher than the current epoch timestamp, the current epoch timestamp will be used.
#5 - alcueca
2023-08-24T06:21:18Z
Sponsor to fix comment.
#6 - c4-judge
2023-08-24T06:21:28Z
alcueca changed the severity to QA (Quality Assurance)
#7 - c4-judge
2023-08-24T06:21:32Z
alcueca marked the issue as grade-b
#8 - c4-judge
2023-08-24T06:27:34Z
This previously downgraded issue has been upgraded by alcueca
#9 - c4-judge
2023-08-24T06:28:00Z
alcueca changed the severity to QA (Quality Assurance)