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

_amountOut is representing assets and shares at the same time in the liquidate function #427

Open
code423n4 opened this issue Jul 14, 2023 · 5 comments
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working H-03 primary issue Highest quality submission among a set of duplicates satisfactory satisfies C4 submission criteria; eligible for awards 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

@code423n4
Copy link
Contributor

Lines of code

https://github.com/GenerationSoftware/pt-v5-vault/blob/b1deb5d494c25f885c34c83f014c8a855c5e2749/src/Vault.sol#L550-L587

Vulnerability details

Impact

In the liquidate function from the Vault contract, the input argument _amountOut is used as if it was representing a value of asset amount and share amount at the same time which is impossible a there a conversion rate between them, this error will make liquidate function behave in an expected manner, not the one that was intended.

Proof of Concept

The issue is occurring in the liquidate function below :

function liquidate(
    address _account,
    address _tokenIn,
    uint256 _amountIn,
    address _tokenOut,
    uint256 _amountOut
) public virtual override returns (bool) {
    _requireVaultCollateralized();
    if (msg.sender != address(_liquidationPair))
      revert LiquidationCallerNotLP(msg.sender, address(_liquidationPair));
    if (_tokenIn != address(_prizePool.prizeToken()))
      revert LiquidationTokenInNotPrizeToken(_tokenIn, address(_prizePool.prizeToken()));
    if (_tokenOut != address(this))
      revert LiquidationTokenOutNotVaultShare(_tokenOut, address(this));
    if (_amountOut == 0) revert LiquidationAmountOutZero();
    
    uint256 _liquidableYield = _liquidatableBalanceOf(_tokenOut);
    
    // @audit _amountOut compared with _liquidableYield which represents an asset amount
    // @audit _amountOut is considered as an asset amount
    if (_amountOut > _liquidableYield)
      revert LiquidationAmountOutGTYield(_amountOut, _liquidableYield);
    
    _prizePool.contributePrizeTokens(address(this), _amountIn);
    
    if (_yieldFeePercentage != 0) {
        // @audit _amountOut used to increase fee shares so considered as representing a share amount
        _increaseYieldFeeBalance(
            (_amountOut * FEE_PRECISION) / (FEE_PRECISION - _yieldFeePercentage) - _amountOut
        );
    }
    
    uint256 _vaultAssets = IERC20(asset()).balanceOf(address(this));
    
    // @audit _amountOut compared with _vaultAssets which represents an asset amount
    // @audit _amountOut is considered as an asset amount
    if (_vaultAssets != 0 && _amountOut >= _vaultAssets) {
      _yieldVault.deposit(_vaultAssets, address(this));
    }
    
    // @audit _amountOut representing a share amount minted to the _account
    _mint(_account, _amountOut);
    
    return true;
}

As you can see from the code above, the value of the argument _amountOut is used multiple times in the function logic and each time it is representing either an asset amount or a share amount which is impossible as there a conversion formula used to transform asset amount into share amount (and inversely) with the function _convertToShares (or _convertToAssets).

From the function comments i couldn't figure out what the value of _amountOut actually represents, but because there is also another argument given to the liquidate function which is _tokenOut == address(this), I'm supposing that _amountOut is representing a share amount which will mean that all the instances highlighted in the code above when _amountOut is considered as an asset amount are wrong.

  • Instance 1 :
// @audit _amountOut compared with _liquidableYield which represents an asset amount
if (_amountOut > _liquidableYield)
  revert LiquidationAmountOutGTYield(_amountOut, _liquidableYield);
  • Instance 2 :
// @audit _amountOut compared with _vaultAssets which represents an asset amount
if (_vaultAssets != 0 && _amountOut >= _vaultAssets) {
  _yieldVault.deposit(_vaultAssets, address(this));
}

And before comparing _amountOut to the asset amount values : _vaultAssets and _liquidableYield, its value should be converted to an asset amount with the function _convertToAssets.

This issue will cause problems for the protocol working as the liquidate function logic will not behave as expected (because it's comparing values that represents different things).

** Note : if _amountOut is actually representing an asset amount (not a share amount as i supposed), the issue is still valid because _amountOut is also used as being a share amount inside the liquidate function, in that case it should first be converted to a share amount with _convertToShares in order to get the correct behavior of the liquidate function.

Tools Used

Manual review

Recommended Mitigation Steps

To solve this issue i recommend to first convert the value of _amountOut in the liquidate function to an asset amount and store it in a local variable _amountOutToAsset, and in the function logic use the correct variable (either _amountOut or _amountOutToAsset) when interacting with a share amount or an asset amount.

Assessed type

Error

@code423n4 code423n4 added 3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working labels Jul 14, 2023
code423n4 added a commit that referenced this issue Jul 14, 2023
@c4-judge
Copy link
Contributor

Picodes marked the issue as duplicate of #5

@c4-judge
Copy link
Contributor

Picodes marked the issue as selected for report

@c4-judge c4-judge reopened this Jul 14, 2023
@c4-judge c4-judge added primary issue Highest quality submission among a set of duplicates selected for report This submission will be included/highlighted in the audit report labels Jul 14, 2023
@c4-sponsor
Copy link

asselstine marked the issue as sponsor confirmed

@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 Jul 20, 2023
@PierrickGT
Copy link
Member

Fixed in this PR: GenerationSoftware/pt-v5-vault#6

@c4-judge
Copy link
Contributor

c4-judge commented Aug 5, 2023

Picodes marked the issue as satisfactory

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working H-03 primary issue Highest quality submission among a set of duplicates satisfactory satisfies C4 submission criteria; eligible for awards 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