Revert Lend - Topmark's results

A lending protocol specifically designed for liquidity providers on Uniswap v3.

General Information

Platform: Code4rena

Start Date: 04/03/2024

Pot Size: $88,500 USDC

Total HM: 31

Participants: 105

Period: 11 days

Judge: ronnyx2017

Total Solo HM: 7

Id: 342

League: ETH

Revert

Findings Distribution

Researcher Performance

Rank: 54/105

Findings: 2

Award: $102.40

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: Aymen0909

Also found by: KupiaSec, Topmark, befree3x, kennedy1030, linmiaomiao, pynschon

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
sufficient quality report
:robot:_87_group
duplicate-324

Awards

92.1136 USDC - $92.11

External Links

Lines of code

https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/V3Vault.sol#L906 https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/V3Vault.sol#L301-L320

Vulnerability details

Impact

Deposit would Not Revert when Total Supply is too Excessive to handle globalLendLimit and in addition It would Revert when Total Supply is within accurate range, this problem is due to oversight in validation implementation

Proof of Concept

function _deposit(address receiver, uint256 amount, bool isShare, bytes memory permitData)
        internal
        returns (uint256 assets, uint256 shares)
    {
        (, uint256 newLendExchangeRateX96) = _updateGlobalInterest();

        _resetDailyLendIncreaseLimit(newLendExchangeRateX96, false);

        if (isShare) {
            shares = amount;
            assets = _convertToAssets(shares, newLendExchangeRateX96, Math.Rounding.Up);
        } else {
            assets = amount;
            shares = _convertToShares(assets, newLendExchangeRateX96, Math.Rounding.Down);
        }

        if (permitData.length > 0) {
            (ISignatureTransfer.PermitTransferFrom memory permit, bytes memory signature) =
                abi.decode(permitData, (ISignatureTransfer.PermitTransferFrom, bytes));
            permit2.permitTransferFrom(
                permit, ISignatureTransfer.SignatureTransferDetails(address(this), assets), msg.sender, signature
            );
        } else {
            // fails if not enough token approved
            SafeERC20.safeTransferFrom(IERC20(asset), msg.sender, address(this), assets);
        }

        _mint(receiver, shares);

>>>        if (totalSupply() > globalLendLimit) {
            revert GlobalLendLimit();
        }

The code above shows how _deposit(...) function is implemented in the v3Vault contract, the point of interest from the pointer is the validation that is done to ensure that total supply is not above the global lend limit. However there is an oversight in this implementation, Total supply should be first converted to Asset before the validation is done, a reference to similar circumstance where totalsupply() and globalLendLimit are used can be noted from the maxDeposit(...) & maxMint(...) functions provided below. totalSupply() was first converted to asset and the resulting value was used for the validation, total supply was not used directly as they represent different variables.

   function maxDeposit(address) external view override returns (uint256) {
        (, uint256 lendExchangeRateX96) = _calculateGlobalInterest();
>>>        uint256 value = _convertToAssets(totalSupply(), lendExchangeRateX96, Math.Rounding.Up);
>>>        if (value >= globalLendLimit) {
            return 0;
        } else {
            return globalLendLimit - value;
        }
    }

    /// @inheritdoc IERC4626
    function maxMint(address) external view override returns (uint256) {
        (, uint256 lendExchangeRateX96) = _calculateGlobalInterest();
>>>        uint256 value = _convertToAssets(totalSupply(), lendExchangeRateX96, Math.Rounding.Up);
>>>        if (value >= globalLendLimit) {
            return 0;
        } else {
            return _convertToShares(globalLendLimit - value, lendExchangeRateX96, Math.Rounding.Down);
        }
    }

Tools Used

Manual Review

The correct implementation should be adjusted to the lend limit validation as provided in the code below. Total supply should be converted to asset before validation

function _deposit(address receiver, uint256 amount, bool isShare, bytes memory permitData)
        internal
        returns (uint256 assets, uint256 shares)
    {
        (, uint256 newLendExchangeRateX96) = _updateGlobalInterest();

        _resetDailyLendIncreaseLimit(newLendExchangeRateX96, false);

       ...

        _mint(receiver, shares);

---        if (totalSupply() > globalLendLimit) {
+++        if (_convertToAssets(totalSupply(), lendExchangeRateX96, Math.Rounding.Up) > globalLendLimit) {
            revert GlobalLendLimit();
        }

Assessed type

Invalid Validation

#0 - c4-pre-sort

2024-03-18T19:02:45Z

0xEVom marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-03-18T19:03:56Z

0xEVom marked the issue as duplicate of #324

#2 - c4-judge

2024-04-01T07:41:35Z

jhsagd76 marked the issue as satisfactory

#3 - c4-judge

2024-04-01T15:41:48Z

jhsagd76 changed the severity to 2 (Med Risk)

Awards

10.2896 USDC - $10.29

Labels

bug
downgraded by judge
grade-b
QA (Quality Assurance)
sufficient quality report
:robot:_85_group
duplicate-254
Q-40

External Links

Lines of code

https://github.com/code-423n4/2024-03-revert-lend/blob/main/src/V3Vault.sol#L1025

Vulnerability details

Impact

Wrong and Inconsistent lent value Calculation in contract which would cause wrong data updated in relation to functions calling _getAvailableBalance(...) function in V3Vault contract.

Proof of Concept

 function _getAvailableBalance(uint256 debtExchangeRateX96, uint256 lendExchangeRateX96)
        internal
        view
        returns (uint256 balance, uint256 available, uint256 reserves)
    {
        balance = totalAssets();

        uint256 debt = _convertToAssets(debtSharesTotal, debtExchangeRateX96, Math.Rounding.Up);
>>>        uint256 lent = _convertToAssets(totalSupply(), lendExchangeRateX96, Math.Rounding.Down);

        reserves = balance + debt > lent ? balance + debt - lent : 0;
        available = balance > reserves ? balance - reserves : 0;
    }

The function above shows how _getAvailableBalance(...) function is implemented in the V3Vault.sol contract, as noted from the pointer lent is calculated by converting totalSupply() to Assets, the problem is that Math.Rounding.Down was used as the third parameter instead of Math.Rounding.Up which can be proven by other similar scenarios, which appeared in 9 different places in the contract, Math.Rounding.Up was used all through in similar situations except in the _getAvailableBalance(...) function as noted above. As can be confirmed from L213, L303, L314, L1134, L1225, L1250 & L1262, this mistake is totally wrong and inconsistent with lent calculation.

Tools Used

Manual Review

The necessary adjustment should be done to the _getAvailableBalance(...) function as provided below, Math.Rounding.Up should be used instead of Math.Rounding.Down

 function _getAvailableBalance(uint256 debtExchangeRateX96, uint256 lendExchangeRateX96)
        internal
        view
        returns (uint256 balance, uint256 available, uint256 reserves)
    {
        balance = totalAssets();

        uint256 debt = _convertToAssets(debtSharesTotal, debtExchangeRateX96, Math.Rounding.Up);
---        uint256 lent = _convertToAssets(totalSupply(), lendExchangeRateX96, Math.Rounding.Down); 
+++        uint256 lent = _convertToAssets(totalSupply(), lendExchangeRateX96, Math.Rounding.Up);

        reserves = balance + debt > lent ? balance + debt - lent : 0;
        available = balance > reserves ? balance - reserves : 0;
    }

Assessed type

Error

#0 - c4-pre-sort

2024-03-21T09:34:05Z

0xEVom marked the issue as duplicate of #254

#1 - c4-pre-sort

2024-03-21T09:34:08Z

0xEVom marked the issue as sufficient quality report

#2 - c4-judge

2024-03-31T15:42:59Z

jhsagd76 changed the severity to QA (Quality Assurance)

#3 - c4-judge

2024-03-31T15:43:29Z

jhsagd76 marked the issue as grade-a

#4 - c4-judge

2024-04-01T07:46:14Z

jhsagd76 marked the issue as grade-c

#5 - Topmark1

2024-04-02T13:50:48Z

Please I would implore Judge to have a look at the Judgement and grading of this issue as it is a duplicate of https://github.com/code-423n4/2024-03-revert-lend-findings/issues/254 , as can be noted from C4 ruling at
https://docs.code4rena.com/awarding/judging-criteria/supreme-court-decisions-fall-2023#verdict-similar-exploits-under-a-single-issue Verdict: Similar exploits under a single issue The findings are duplicates if they share the same root cause. More specifically, if fixing the Root Cause (in a reasonable manner) would cause the finding to no longer be exploitable, then the findings are duplicates. Would implore Judge to please reconsider, thanks

#6 - c4-judge

2024-04-04T08:45:32Z

jhsagd76 marked the issue as grade-b

#7 - jhsagd76

2024-04-04T08:46:02Z

This is because I can only retain a rating for one QA report.

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter