code-423n4 / 2024-05-bakerfi-findings

4 stars 4 forks source link

Incorrect integration with UniswapRouter causes all swaps to revert. #42

Closed c4-bot-5 closed 5 months ago

c4-bot-5 commented 5 months ago

Lines of code

https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/hooks/UseSwapper.sol#L67-L76 https://github.com/code-423n4/2024-05-bakerfi/blob/main/contracts/core/hooks/UseSwapper.sol#L84-L93

Vulnerability details

Impact

Core functions on the Strategy contract are impacted because the Strategy won't be able to do swaps caused an incorrect integration with the UniswapRouter.

Proof of Concept

The UseSwapper._swap() function makes use of the UniswapRouter when executing swaps. The problem is that the parameters that are passed to the UniswapRouter are missing the deadline parameter, on both types of swaps (EXACT_INPUT && EXACT_OUTPUT).

> UseSwapper.sol

function _swap(
    ISwapHandler.SwapParams memory params
) internal override returns (uint256 amountOut) {
    ...

    // Exact Input
    if (params.mode == ISwapHandler.SwapType.EXACT_INPUT) {
        amountOut = _uniRouter.exactInputSingle(
          //@audit-issue => Not passing the deadline parameter
            IV3SwapRouter.ExactInputSingleParams({
                tokenIn: params.underlyingIn,
                tokenOut: params.underlyingOut,
                amountIn: params.amountIn,
                amountOutMinimum: 0,
                fee: fee,
                recipient: address(this),
                sqrtPriceLimitX96: 0
            })
        );
        if (amountOut == 0) {
            revert SwapFailed();
        }
        emit Swap(params.underlyingIn, params.underlyingOut, params.amountIn, amountOut);
        // Exact Output
    } else if (params.mode == ISwapHandler.SwapType.EXACT_OUTPUT) {
        uint256 amountIn = _uniRouter.exactOutputSingle(
          //@audit-issue => Not passing the deadline parameter
            IV3SwapRouter.ExactOutputSingleParams({
                tokenIn: params.underlyingIn,
                tokenOut: params.underlyingOut,
                fee: fee,
                recipient: address(this),
                amountOut: params.amountOut,
                amountInMaximum: params.amountIn,
                sqrtPriceLimitX96: 0
            })
        );
        ...
    }
}

If we take a look at the code of the SwapRouter, we can notice that the deadline is a parameter that must be sent as part of the params when calling the exactInputSingle() or exactOutputSingle() functions.

function exactInputSingle(ExactInputSingleParams calldata params)
    external
    payable
    override
    //@audit-issue => Because of this missing parameter, the whole tx will be reverted
    checkDeadline(params.deadline)
    returns (uint256 amountOut)
{
    ...
}

function exactOutputSingle(ExactOutputSingleParams calldata params)
    external
    payable
    override
    //@audit-issue => Because of this missing parameter, the whole tx will be reverted
    checkDeadline(params.deadline)
    returns (uint256 amountIn)
{
    ...
}

Coded PoC

I coded a PoC in Foundry to demonstrate the integration error, and show that the missing deadline parameter causes the tx to be reverted.

First of all, help me to create a new folder where we are going to create a couple of test files for this PoC. Create the new folder under the tests folder

Before running the PoC, is required to change the visibility of the uniRouter variable in the UseSwapper.sol contract. Update it to internal instead of private

Run the PoC with this command forge test --match-test test_swapFunctionFromUseSwapperContract_PoC -vvvv

Output of running the PoC
``` forge test --match-test test_swapFunctionFromUseSwapperContract_PoC -vvvv [⠘] Compiling... No files changed, compilation skipped Ran 1 test for test/foundryUseSwapPoC/UseSwapper.t.sol:TestUseSwapper [PASS] test_swapFunctionFromUseSwapperContract_PoC() (gas: 804034) Traces: [804034] TestUseSwapper::test_swapFunctionFromUseSwapperContract_PoC() ├─ [121365] → new SwapRouterMock@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f │ └─ ← [Return] 606 bytes of code ├─ [564600] → new UseSwapperPoC@0x2e234DAe75C793f67A35089C9d99245E1C58470b │ └─ ← [Return] 2820 bytes of code ├─ [44634] UseSwapperPoC::setUniRouter(SwapRouterMock: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f]) │ └─ ← [Stop] //@audit => First call - Missing deadline parameter ├─ [0] VM::expectRevert(custom error f4844814:) │ └─ ← [Return] ├─ [1556] UseSwapperPoC::swapReverts() │ ├─ [80] SwapRouterMock::exactInputSingle(ExactInputSingleParams({ tokenIn: 0x0000000000000000000000000000000000000014, tokenOut: 0x000000000000000000000000000000000000000A, fee: 500, recipient: 0x2e234DAe75C793f67A35089C9d99245E1C58470b, amountIn: 1000, amountOutMinimum: 0, sqrtPriceLimitX96: 0 })) │ │ └─ ← [Revert] EvmError: Revert │ └─ ← [Revert] EvmError: Revert //@audit => Second call - Passing all the required parameters! ├─ [2910] UseSwapperPoC::swapSucceeds() │ ├─ [1365] SwapRouterMock::exactInputSingle(ExactInputSingleParams({ tokenIn: 0x0000000000000000000000000000000000000014, tokenOut: 0x000000000000000000000000000000000000000A, fee: 500, recipient: 0x2e234DAe75C793f67A35089C9d99245E1C58470b, deadline: 1, amountIn: 1000, amountOutMinimum: 0, sqrtPriceLimitX96: 0 })) │ │ └─ ← [Return] 1000 │ └─ ← [Stop] └─ ← [Return] 0 Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.59ms (1.74ms CPU time) Ran 1 test suite in 361.52ms (6.59ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests) ```

Tools Used

Manual Audit, Foundry & SwapRouter contract

Recommended Mitigation Steps

The mitigation for this integration error is to pass all the required parameters when calling the functions of the SwapRouter. Specifically, update the parameters that are sent to the SwapRouter like this: (We are going to use the original ISwapRouter.ExactInputSingleParams to prevent any errors) Also, make sure that the UniRouter variable is of the type ISwapRouter (the same interface used by the UniswapRouter!), insteaf of IV3SwapRouter.

Note: Make sure to have imported the correct ISwapRouter interface

abstract contract UseSwapper is ISwapHandler, Initializable {

-   IV3SwapRouter private _uniRouter;
+   ISwapRouter private _uniRouter;

    function _initUseSwapper(ServiceRegistry registry) internal onlyInitializing {
-       _uniRouter = IV3SwapRouter(registry.getServiceFromHash(UNISWAP_ROUTER_CONTRACT));
+       _uniRouter = ISwapRouter(registry.getServiceFromHash(UNISWAP_ROUTER_CONTRACT));
        if (address(_uniRouter) == address(0)) revert InvalidUniRouterContract();
    }

-   function uniRouter() public view returns (IV3SwapRouter) {
-   function uniRouter() public view returns (ISwapRouter) {  
        return _uniRouter;
    }

    function uniRouterA() public view returns (address) {
        return address(_uniRouter);
    }

    function _swap(
        ISwapHandler.SwapParams memory params
    ) internal override returns (uint256 amountOut) {
        ...

        // Exact Input
        if (params.mode == ISwapHandler.SwapType.EXACT_INPUT) {
            amountOut = _uniRouter.exactInputSingle(
-               IV3SwapRouter.ExactInputSingleParams({
+               ISwapRouter.ExactInputSingleParams({
                    tokenIn: params.underlyingIn,
                    tokenOut: params.underlyingOut,
                    amountIn: params.amountIn,
                    amountOutMinimum: 0,
                    fee: fee,
                    recipient: address(this),
+                   deadline: block.timestamp,                    
                    sqrtPriceLimitX96: 0
                })
            );
            ...

        } else if (params.mode == ISwapHandler.SwapType.EXACT_OUTPUT) {
            uint256 amountIn = _uniRouter.exactOutputSingle(
-               IV3SwapRouter.ExactOutputSingleParams({
+               ISwapRouter.ExactOutputSingleParams({  
                    tokenIn: params.underlyingIn,
                    tokenOut: params.underlyingOut,
                    fee: fee,
                    recipient: address(this),
                    amountOut: params.amountOut,
                    amountInMaximum: params.amountIn,
+                   deadline: block.timestamp,     
                    sqrtPriceLimitX96: 0
                })
            );
            ...
        }
    }
}

Assessed type

Uniswap

c4-judge commented 5 months ago

0xleastwood marked the issue as primary issue

c4-judge commented 5 months ago

0xleastwood marked the issue as selected for report

ickas commented 5 months ago

Fixed → https://github.com/baker-fi/bakerfi-contracts/pull/43

t0x1cC0de commented 5 months ago

Hi @0xleastwood, I think it's an interesting report. May I ask however if the current issue is in any way different from the invalidated one which was raised in the Decent protocol audit contest? I found one of the comments there by the warden; another pretty involved comment by a different warden here which finds the judge's response here & a final comment by the judge here, but haven't had the chance to sift through all the nuances of the discussion as there are multiple interlinked threads. All I could quickly see at first glance was that the judge invalidated the primary issue for some reason. Is there any difference here for it to not be considered invalid?

stalinMacias commented 5 months ago

Hi @0xleastwood, I think it's an interesting report. May I ask however if the current issue is in any way different from the invalidated one which was raised in the Decent protocol audit contest? I found one of the comments there by the warden; another pretty involved comment by a different warden here which finds the judge's response here & a final comment by the judge here, but haven't had the chance to sift through all the nuances of the discussion as there are multiple interlinked threads. All I could quickly see at first glance was that the judge invalidated the primary issue for some reason. Is there any difference here for it to not be considered invalid?

Hey @t0x1cC0de Thanks for pointing out to those reports of the Decent contest. This is why all the reports about not setting deadline were invalidated, including one I reported back then, because I also failed to point out the real problem, and, instead, I just mentioned the possible mev problem (which is totally incorrect)

@0xleastwood This is exactly what I said in my comment on issue #12

12 does not point out the real problem of not passing the deadline parameter, instead, it just talks about the possible mev issue.

t0x1cC0de commented 5 months ago

Thanks @stalinMacias for the explanation. I think https://github.com/code-423n4/2024-01-decent-findings/issues/117 (and the analysis in 172) cite a different reason for invalidation. But will let @0xleastwood analyse it further. Nothing more from my side.

Thanks

hvasconcelos commented 5 months ago

We are using SwapRouter02 instead of SwapRouter , on new deployments like Base the SwapRouter is not deployed anymore by uniswap team , so we decided to use this contract:

https://github.com/Uniswap/swap-router-contracts/blob/v1.1.0/contracts/interfaces/IV3SwapRouter.sol

to be compatible with all the deployments(Ethereum, Base, Optimism, Arbitrum,... ).

We already tested integrations with production chains(Ethereum, Artbitrum) and everything looks to work fine.

0xleastwood commented 5 months ago

As per sponsor's comment, #42 and #12 are invalid.

c4-judge commented 5 months ago

0xleastwood marked the issue as unsatisfactory: Invalid