veRWA - 0xDING99YA'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: 37/125

Findings: 3

Award: $68.86

QA:
grade-a

🌟 Selected for report: 0

πŸš€ Solo Findings: 0

Awards

43.2097 USDC - $43.21

Labels

bug
3 (High Risk)
satisfactory
upgraded by judge
duplicate-268

External Links

Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/VotingEscrow.sol#L326-L349 https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/VotingEscrow.sol#L356-L387

Vulnerability details

Impact

When a user create a lock and then delegate the votes to another user, and after the lockEnd time when the user want to withdraw the transaction will revert, there is no way to get the token back and the fund is locked forever.

Proof of Concept

In withdraw() in VotingEscrow.sol, when withdrawing it requires the delegate is the msg.sender itself:

require(locked_.delegatee == msg.sender, "Lock delegated");

And if a user want to delegate to any user, that user (including the user self) lockEnd must larger than current timestamp:

require(toLocked.end > block.timestamp, "Delegatee lock expired");

So it's possible that user1 delegate to user2, when user1 wants to withdraw after lockEnd, user1 must delegate back to self, however, this is impossible since delegate always check user1.lockEnd > current timestamp.

POC in Foundry is below, just added it to VotingEscrow.t.sol and run:

function testWithdrawRevert() public { // user1 create lock vm.prank(user1); ve.createLock{value: LOCK_AMT}(LOCK_AMT); // user1 lockEnd is 157248000 // make the block.timestamp to 10000000, then user2 create lock vm.warp(10000000); vm.prank(user2); ve.createLock{value: LOCK_AMT}(LOCK_AMT); // user2 lockEnd is 167529600 // user1 delegate to user2 vm.prank(user1); ve.delegate(user2); // make the block.timestamp to 157248001, which is 1s larger than user1.lockEnd vm.warp(157248001); // user1 cannot withdraw directly since delegatee is not the user self vm.expectRevert("Lock delegated"); vm.prank(user1); ve.withdraw(); // user1 can't delegate back to self, but withdraw require(delegatee == msg.sender), so user1 cannot get token back forever vm.expectRevert("Delegatee lock expired"); vm.prank(user1); ve.delegate(user1); }

Tools Used

Manual Review, Foundry

Enable users to delegate back to self even after the lockEnd

Assessed type

Other

#0 - c4-pre-sort

2023-08-12T10:29:03Z

141345 marked the issue as duplicate of #223

#1 - c4-pre-sort

2023-08-13T12:00:38Z

141345 marked the issue as duplicate of #112

#2 - c4-judge

2023-08-24T07:16:03Z

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:38:20Z

alcueca marked the issue as satisfactory

#5 - c4-pre-sort

2023-08-24T08:20:04Z

141345 marked the issue as not a duplicate

#6 - c4-pre-sort

2023-08-24T08:20:27Z

141345 marked the issue as not a duplicate

#7 - c4-pre-sort

2023-08-24T08:22:30Z

141345 marked the issue as duplicate of #211

#8 - c4-judge

2023-08-26T21:24:29Z

alcueca changed the severity to 3 (High Risk)

Awards

15.8333 USDC - $15.83

Labels

bug
3 (High Risk)
partial-50
sponsor confirmed
upgraded by judge
duplicate-182

External Links

Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/VotingEscrow.sol#L356-L387

Vulnerability details

Impact

After a user delegate the balance to another user, the user can't delegate back to self unless increase own balance.

Proof of Concept

In delegate() in VotingEscrow.sol, it requires the lockEnd of new delegatee > lockEnd of current delegatee:

require(toLocked.end > block.timestamp, "Delegatee lock expired");

So if a user delegate balance to another address, to delegate back the user has to call increaseAmount() to update the lockEnd

Tools Used

Manual Review

This is a weird design, not sure the reason to do this. Remove the requirement.

Assessed type

Other

#0 - c4-pre-sort

2023-08-12T10:57:07Z

141345 marked the issue as primary issue

#2 - c4-sponsor

2023-08-16T14:14:25Z

OpenCoreCH marked the issue as sponsor confirmed

#3 - c4-judge

2023-08-24T07:14:15Z

alcueca marked the issue as duplicate of #82

#4 - c4-judge

2023-08-24T07:20:31Z

alcueca changed the severity to 3 (High Risk)

#5 - c4-judge

2023-08-24T07:20:40Z

alcueca changed the severity to 2 (Med Risk)

#6 - c4-judge

2023-08-24T07:38:08Z

alcueca marked the issue as partial-50

#7 - c4-judge

2023-08-29T06:37:35Z

alcueca changed the severity to 3 (High Risk)

Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/VotingEscrow.sol#L133-L135

Vulnerability details

Impact

After a user making a deposit, the user will incur an immediate loss in the balance (voting power).

Proof of Concept

By design, the balance of a user will decrease linearly by time. For example, if a user deposit 1 ether, then the 1 ether balance will decrease by each single second. However, at the same timestamp of depositing the balance should still be 1 ether, this is not true in current implementation.

if (_newLocked.end > block.timestamp && _newLocked.delegated > 0) { userNewPoint.slope = _newLocked.delegated / int128(int256(LOCKTIME)); userNewPoint.bias = userNewPoint.slope * int128(int256(_newLocked.end - block.timestamp)); }

As we can see in _checkPoint(), slope is delegated/LOCKTIME, then current bias is slope*(lockEnd-block.timestamp), the question is when making a deposit the lockEnd is not always block.timestamp+LOCKTIME, it will round to the nearest multiple of WEEK, which can result in a loss in bias and the bias will be less than the current deposit.

Foundry POC is below, just added it to VotingEscrow.t.sol.

function testDepositImmediateLoss() public { // We choose a random block timestamp which is not a multiple of WEEK vm.warp(604799); // user1 make a deposit vm.prank(user1); ve.createLock{value: LOCK_AMT}(LOCK_AMT); // In the same block timestamp user's balance is already less than the value deposited, which is 1 ether uint256 user1Balance = ve.balanceOf(user1); assertLt(user1Balance, 1 ether); }

Tools Used

Manual Review, Foundry

Make sure the user won't loss balance immediately at the same timestamp of deposit.

Assessed type

Other

#0 - 141345

2023-08-13T15:54:15Z

not big round error

QA might be more appropriate.

#1 - OpenCoreCH

2023-08-16T14:49:58Z

Small rounding difference because of epochs, not really a problem in my opinion

#2 - c4-sponsor

2023-08-16T14:50:04Z

OpenCoreCH marked the issue as sponsor acknowledged

#3 - c4-sponsor

2023-08-16T14:50:09Z

OpenCoreCH marked the issue as disagree with severity

#4 - alcueca

2023-08-24T06:13:25Z

The premise for voting is that it is fair, and this vulnerability affects all voters equally. Therefore, I agree with the sponsor with this not being an actual issue, although the mitigation could still be followed for code cleanliness.

#5 - c4-judge

2023-08-24T06:13:30Z

alcueca changed the severity to QA (Quality Assurance)

#6 - c4-judge

2023-08-24T06:13:34Z

alcueca marked the issue as grade-a

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