sherlock-audit/2023-06-unstoppable-judging

kutugu - _account_for_withdraw_liquidity rounding in the wrong direction will run out of the vault

Opened this issue · 2 comments

kutugu

high

_account_for_withdraw_liquidity rounding in the wrong direction will run out of the vault

Summary

Vault implementers should be aware of the need for specific, opposing rounding directions across the different mutable and view methods, as it is considered most secure to favor the Vault itself during calculations over its users:
If (1) it’s calculating how many shares to issue to a user for a certain amount of the underlying tokens they provide or (2) it’s determining the amount of the underlying tokens to transfer to them for returning a certain amount of shares, it should round down.
If (1) it’s calculating the amount of shares a user has to supply to receive a given amount of the underlying tokens or (2) it’s calculating the amount of underlying tokens a user has to provide to receive a certain amount of shares, it should round up.

When the user extracts liquidity, due to the existence of share, the calculation has precision errors, should pay special attention to the rounding direction. Users withdraw a specific amount of liquidity, user shares should be rounded up, subtract 1 more shares, to avoid spending more than they earn, resulting in the eventual run out of the vault.

Vulnerability Detail

@internal
def _account_for_withdraw_liquidity(
    _token: address, _amount: uint256, _is_safety_module: bool
):
    self._update_debt(_token)
    if _is_safety_module:
        shares: uint256 = self._amount_to_lp_shares(_token, _amount, True)
        assert (shares <= self.safety_module_lp_shares[_token][msg.sender]), "cannot withdraw more than you own"
        self.safety_module_lp_total_amount[_token] -= _amount
        self.safety_module_lp_total_shares[_token] -= shares
        self.safety_module_lp_shares[_token][msg.sender] -= shares

    else:
        shares: uint256 = self._amount_to_lp_shares(_token, _amount, False)
        assert (shares <= self.base_lp_shares[_token][msg.sender]), "cannot withdraw more than you own"
        self.base_lp_total_amount[_token] -= _amount
        self.base_lp_total_shares[_token] -= shares
        self.base_lp_shares[_token][msg.sender] -= shares

@internal
@view
def _amount_to_lp_shares(
    _token: address, _amount: uint256, _is_safety_module: bool
) -> uint256:
    if _is_safety_module:
        # initial shares == wei
        if self.safety_module_lp_total_shares[_token] == 0:
            return _amount * PRECISION

        wei_per_share: uint256 = self._amount_per_safety_module_lp_share(_token)
        new_shares: uint256 = _amount * PRECISION * PRECISION / wei_per_share
        return new_shares

    else:  # base_lp
        # initial shares == wei
        if self.base_lp_total_shares[_token] == 0:
            return _amount * PRECISION

        wei_per_share: uint256 = self._amount_per_base_lp_share(_token)
        new_shares: uint256 = _amount * PRECISION * PRECISION / wei_per_share
        return new_shares

_amount_to_lp_shares calculates the amount of share to be subtracted, which should round up, otherwise the user account share will be 1 more than it actually is, and the difference will gradually become larger as users interact with vault over time, eventually run out of the vault.

Impact

Rounding in the wrong direction, when users withdraw liquidity, the shares are greater than actual value, and gradually accumulate as interactions, eventually causing the vault to be run out of and unable to repay all the liquidity.

Code Snippet

Tool used

Manual Review

Recommendation

Round up in _amount_to_lp_shares

Fixed by subtracting a share from the calculation of self._amount_to_lp_shares to account for the exposed rounding error