Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forced relock in VotiumStrategy withdrawal causes denial of service if Convex locking contract is shutdown #50

Open
c4-submissions opened this issue Sep 27, 2023 · 4 comments
Labels
2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working M-03 primary issue Highest quality submission among a set of duplicates selected for report This submission will be included/highlighted in the audit report sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity")

Comments

@c4-submissions
Copy link
Contributor

Lines of code

https://github.com/code-423n4/2023-09-asymmetry/blob/main/contracts/strategies/votium/VotiumStrategy.sol#L135-L149

Vulnerability details

Summary

The VotiumStrategy withdrawal process involves relocking CVX tokens, which can potentially lead to a denial of service and loss of user funds if the underlying vlCVX contract is shutdown.

Impact

When withdrawals are executed in VotiumStrategy, the implementation of withdraw() will call relock() in order to relock any available excess (i.e. expired tokens minus the pending obligations) of CVX tokens to lock them again in the Convex protocol.

https://github.com/code-423n4/2023-09-asymmetry/blob/main/contracts/strategies/votium/VotiumStrategy.sol#L135-L149

135:     function relock() public {
136:         (, uint256 unlockable, , ) = ILockedCvx(VLCVX_ADDRESS).lockedBalances(
137:             address(this)
138:         );
139:         if (unlockable > 0)
140:             ILockedCvx(VLCVX_ADDRESS).processExpiredLocks(false);
141:         uint256 cvxBalance = IERC20(CVX_ADDRESS).balanceOf(address(this));
142:         uint256 cvxAmountToRelock = cvxBalance > cvxUnlockObligations
143:             ? cvxBalance - cvxUnlockObligations
144:             : 0;
145:         if (cvxAmountToRelock > 0) {
146:             IERC20(CVX_ADDRESS).approve(VLCVX_ADDRESS, cvxAmountToRelock);
147:             ILockedCvx(VLCVX_ADDRESS).lock(address(this), cvxAmountToRelock, 0);
148:         }
149:     }

This seems fine at first, but if we dig into the implementation of lock() we can see that the preconditions of this function requires the contract not to be shutdown:

https://etherscan.io/address/0x72a19342e8F1838460eBFCCEf09F6585e32db86E#code#L1469

521:     function _lock(address _account, uint256 _amount, uint256 _spendRatio, bool _isRelock) internal {
522:         require(_amount > 0, "Cannot stake 0");
523:         require(_spendRatio <= maximumBoostPayment, "over max spend");
524:         require(!isShutdown, "shutdown");

Which means that any call to lock() after the contract is shutdown will revert. This is particularly bad because relock() is called as part of the withdraw process. If the vlCVX contract is shutdown, VotiumStrategy depositors won't be able to withdraw their position, causing a potential loss of funds.

Note that this also will cause a DoS while depositing rewards, since depositRewards() in VotiumStrategy also calls ILockedCvx::lock().

Proof of Concept

  1. vlCVX contract is shutdown.
  2. A user requests a withdrawal using requestWithdrawal()
  3. The user calls withdraw() when the withdrawal epoch is reached.
  4. The implementation will call relock(), which will call ILockedCvx::lock().
  5. The implementation of lock() will throw an error because the vault is already shutdown.
  6. The transaction will be reverted.

Recommendation

Add a condition to check if the contract is shutdown to avoid the call to lock() and the potential denial of service.

    function relock() public {
        (, uint256 unlockable, , ) = ILockedCvx(VLCVX_ADDRESS).lockedBalances(
            address(this)
        );
        if (unlockable > 0)
            ILockedCvx(VLCVX_ADDRESS).processExpiredLocks(false);
        uint256 cvxBalance = IERC20(CVX_ADDRESS).balanceOf(address(this));
        uint256 cvxAmountToRelock = cvxBalance > cvxUnlockObligations
            ? cvxBalance - cvxUnlockObligations
            : 0;
-       if (cvxAmountToRelock > 0) {
+       if (cvxAmountToRelock > 0 && !ILockedCvx(VLCVX_ADDRESS).isShutdown()) {
            IERC20(CVX_ADDRESS).approve(VLCVX_ADDRESS, cvxAmountToRelock);
            ILockedCvx(VLCVX_ADDRESS).lock(address(this), cvxAmountToRelock, 0);
        }
    }

Assessed type

DoS

@c4-submissions c4-submissions added 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working labels Sep 27, 2023
c4-submissions added a commit that referenced this issue Sep 27, 2023
@elmutt
Copy link

elmutt commented Sep 29, 2023

@c4-judge c4-judge added the primary issue Highest quality submission among a set of duplicates label Oct 3, 2023
@c4-judge
Copy link
Contributor

c4-judge commented Oct 3, 2023

0xleastwood marked the issue as primary issue

@c4-judge c4-judge added the selected for report This submission will be included/highlighted in the audit report label Oct 4, 2023
@c4-judge
Copy link
Contributor

c4-judge commented Oct 4, 2023

0xleastwood marked the issue as selected for report

@c4-sponsor c4-sponsor added the sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity") label Oct 4, 2023
@c4-sponsor
Copy link

elmutt (sponsor) confirmed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working M-03 primary issue Highest quality submission among a set of duplicates selected for report This submission will be included/highlighted in the audit report sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity")
Projects
None yet
Development

No branches or pull requests

5 participants