Closed sherlock-admin4 closed 1 month ago
Invalid/low severity, there is no indication that the minimum lock period is 1 week, only a max of 52 weeks. Also, the rewards are scaled timewise and checkpointed accordingly based on end period, so there is no unfairness here when rewards are distributed within reward distributor contract
@nevillehuang
I agree with you, but this report is in two folds for visibility I'll paste the second part of the report below
This can also be detrimental for honest lockers as because the calculated unlock time can be less than users intended lock time making the user lock for less than their intended period.
A coded POC to back up this claim is pasted below:
function testLockEndIsBroken() public {
flowDaiPair.approve(address(escrow), TOKEN_1);
vm.warp(((block.timestamp / ONE_WEEK ) * ONE_WEEK) + 4 days);
uint256 lockDuration = 7 * 24 * 3600; // lock for 7 days
// Balance should be zero before and 1 after creating the lock
assertEq(escrow.balanceOf(address(owner)), 0);
uint lockedToken = escrow.create_lock(TOKEN_1, lockDuration); // create lock for 3 days
(int128 amt, uint end) = escrow.locked(lockedToken);
assertEq(end, block.timestamp + 3 days); // lock ends in the next 3 days
}
Kindly review.
@Audinarey Unfortunately, I still don't see how the PoC shows unfairness and address my comments here
@nevillehuang
Please don't get me wrong the unfairness is one fold which I agree with your comment, the second part of the report talks about broken core protocol functionality, in fact I mentioned in the impact that
honest users could be forced to lock for less than 1 week against their will due to rounding error
the POC shows that the create_lock(...)
functionality is broken for honest users
I'ld appreciate @dawiddrzala input on this as well.
@nevillehuang
Please find below the POC for the second part of the report.
Ad it to the VotingEscrow.t.sol
and run forge test --mt testLockEndIsBroken -vvv
function testLockEndIsBroken() public {
flowDaiPair.approve(address(escrow), TOKEN_1);
vm.warp(((block.timestamp / ONE_WEEK ) * ONE_WEEK) + 4 days);
uint256 lockDuration = 7 * 24 * 3600; // lock for 7 days
// Balance should be zero before and 1 after creating the lock
assertEq(escrow.balanceOf(address(owner)), 0);
uint lockedToken = escrow.create_lock(TOKEN_1, lockDuration); // create lock for 3 days
(int128 amt, uint end) = escrow.locked(lockedToken);
assertEq(end, block.timestamp + 3 days); // lock ends in the next 3 days
}
Audinarey
High
Locks can be created for less than 1 epoch (1 WEEK)
Summary
The
ve
LpToken has a minimum lock period of 1 epoch (1 weeks). However, a flaw allows malicious actors to lock their Ll tokens for less than 1 week, resulting in unfair FLOW emission reward distribution. This means a malicious actor can unfairly claim rewards without meeting the minimum 1-week lock requirementVulnerability Detail
The problem is that the
unlock_time
calculation in the internal_create_lock(...)
function is rounded down and is the validation of the calculatedunlock_time
only checks if this time is more than the current time. This wrong_lock_duration
= 3 days(block.timestamp + _lock_duration) / WEEK * WEEK
rounds down to Thu Aug 1 2024 00:00:00 UTCunlock_time > block.timestamp,
passes the check and lock is createdThis can also be detrimental for honest lockers as because the calculated unlock time can be less than users intended lock time making the user lock for less than their intended period.
_lock_duration
= 7 days(block.timestamp + _lock_duration) / WEEK * WEEK
rounds down to Thu Aug 1 2024 00:00:00 UTCunlock_time > block.timestamp,
passes the check and lock is createdCODED POC Add the test case below to
VotingEscrow.t.sol
and runforge test --mt testCreateLockBelowOneWeek -vvv
Impact
Code Snippet
https://github.com/sherlock-audit/2024-06-velocimeter/blob/main/v4-contracts/contracts/VotingEscrow.sol#L839-L845
Tool used
Foundry test
Recommendation
In the
_create_lock(...)
function to ensure the check handles the full range of the users lock time parammeter and it must not be less than one week