Open c4-submissions opened 9 months ago
0xleastwood marked the issue as duplicate of #63
It seems pretty severe that requestWithdraw()
would fail when the lockedBalances
array is empty right?
0xleastwood marked the issue as selected for report
elmutt (sponsor) confirmed
It seems pretty severe that
requestWithdraw()
would fail when thelockedBalances
array is empty right?
Absolutely agree. we are working on a fix to address this issue and will post it here in the coming days when ready
It seems pretty severe that
requestWithdraw()
would fail when thelockedBalances
array is empty right?
Giving the potential DoS here, do you think this could be upgraded to a high?
Giving the potential DoS here, do you think this could be upgraded to a high?
So I think this DoS is recoverable. If all locks are unlocked/expired, then it will not be possible to request a withdrawal. However, if relock()
is called, then additional requests to withdraw can be processed and the final impact is that the request is delayed more than it needs to be.
It's not possible to prevent withdrawal finalisation by calling relock()
first either because cvxUnlockObligations
is used to reserve cvx that is owed to existing withdrawal requests.
I think I will leave it as is unless there is a way to brick funds by preventing users from finalising their withdrawals
Lines of code
https://github.com/code-423n4/2023-09-asymmetry/blob/main/contracts/strategies/votium/VotiumStrategy.sol#L54-L103
Vulnerability details
Summary
Withdrawals in VotiumStrategy are executed in queue since CVX tokens are potentially locked in Convex. However, the implementation fails to consider the case where unlocked assets are already enough to cover the withdrawal, leading to different issues.
Impact
VotiumStrategy withdrawals are executed in queue since the underlying CVX tokens may be locked in the Convex platform. Depositors must request a withdrawal and wait in queue until the epoch associated with their withdrawal is reached in order to exit their position. The core of this logic is present in the function
requestWithdraw()
:https://github.com/code-423n4/2023-09-asymmetry/blob/main/contracts/strategies/votium/VotiumStrategy.sol#L54-L103
The implementation first considers available tokens that should be ready to be withdrawn. Line 74-75 sets
totalLockedBalancePlusUnlockable
to the amount of unlockable tokens (expired tokens in Convex that can be withdrawn) plus any available CVX balance in the contract.However, the implementation fails to consider that this available balance may be already enough to cover the withdrawal, and proceeds to search within the locked balances by epoch. This will lead to different issues:
Proof of Concept
Let's illustrate the denial of service case. We assume all deposits in VotiumStrategy were done before the 16 weeks period, which means all vlCVX should be in an unlocked state.
requestWithdraw(amount)
.ILockedCvx.lockedBalances()
. This will return the entire position asunlockable
and an empty array forlockedBalances
.totalLockedBalancePlusUnlockable
asunlockable + IERC20(CVX_ADDRESS).balanceOf(address(this))
. This should be enough to cover the requested amount by the user.lockedBalances
, but since this array is empty the for loop is never executed.InvalidLockedAmount()
error.Recommendation
Before searching through the
lockedBalances
array, check if there available unlocked tokens are enough to cover the withdrawal. If so, the withdrawal can be set for the current epoch. This will fix the unnecessary delay and the potential denial of service.Assessed type
Other