veRWA - Topmark's results

Incentivization Primitive for Real World Assets on Canto

General Information

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

Canto

Findings Distribution

Researcher Performance

Rank: 55/125

Findings: 2

Award: $20.06

QA:
grade-b

🌟 Selected for report: 0

πŸš€ Solo Findings: 0

Awards

15.8333 USDC - $15.83

Labels

bug
3 (High Risk)
partial-50
upgraded by judge
edited-by-warden
duplicate-182

External Links

Lines of code

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

Vulnerability details

Impact

Locked Account Owner will have funds Locked Forever Even after Expiration day has elapsed if Delegetee Loses Private key or refuse to withdraw

Proof of Concept

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

  1. Alice delegated to Bob ( Bob account end is longer)
  2. Now Alice wants to undelegate Bob to Alice with same condition ( Alice must be longer but Bob account end is still longer and L384 condition will not pass this time around) you can't have it both ways! However there is a safe spot if Bob first withdraw his own funds which resets his account end back to zero based on L336
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.

Tools Used

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");

Assessed type

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)

Lines of code

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

Vulnerability details

Impact

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

Proof of Concept

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

Tools Used

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.

Assessed type

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)

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax Β© 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter