Skip to content

SwapAction.sol#balancerSwap does not support native ETH as input token. #70

@c4-bot-1

Description

@c4-bot-1

Lines of code

https://github.com/code-423n4/2024-07-loopfi/blob/main/src/proxy/SwapAction.sol#L263

Vulnerability details

Impact

SwapAction.sol#balancerSwap does not support native ETH as input token.

Bug Description

SwapAction is used to swap tokens using Balancer/Uniswap or join/exit a Pendle pool. Pendle accepts native ETH as input token when joining a Pendle pool, and Balancer accepts native ETH during swap.

We can check that the SwapAction contract also supports passing native ETH as input token, because 1) swap() function, which serves as the entry function, is payable; 2) pendleJoin() passes msg.value along when calling pendleRouter.addLiquiditySingleToken().

However, the issue is that when performing a balancer swap by balancerVault.batchSwap, the msg.value is not passed along.

    function swap(SwapParams memory swapParams) public payable returns (uint256 retAmount) {
        if (swapParams.swapProtocol == SwapProtocol.BALANCER) {
            (bytes32[] memory poolIds, address[] memory assetPath) = abi.decode(
                swapParams.args,
                (bytes32[], address[])
            );
            retAmount = balancerSwap(
                swapParams.swapType,
                swapParams.assetIn,
                poolIds,
                assetPath,
                swapParams.amount,
                swapParams.limit,
                swapParams.recipient,
                swapParams.deadline
            );
        } else if (swapParams.swapProtocol == SwapProtocol.UNIV3) {
            retAmount = uniV3Swap(
                swapParams.swapType,
                swapParams.assetIn,
                swapParams.amount,
                swapParams.limit,
                swapParams.recipient,
                swapParams.deadline,
                swapParams.args
            );
        } else if (swapParams.swapProtocol == SwapProtocol.PENDLE_IN) {
            retAmount = pendleJoin(swapParams.recipient, swapParams.limit, swapParams.args);
        } else if (swapParams.swapProtocol == SwapProtocol.PENDLE_OUT) {
            retAmount = pendleExit(swapParams.recipient, swapParams.amount, swapParams.args);
        } else revert SwapAction__swap_notSupported();
        // Transfer any remaining tokens to the recipient
        if (swapParams.swapType == SwapType.EXACT_OUT && swapParams.recipient != address(this)) {
            IERC20(swapParams.assetIn).safeTransfer(swapParams.recipient, swapParams.limit - retAmount);
        }
    }

    function balancerSwap(
        SwapType swapType,
        address assetIn,
        bytes32[] memory poolIds,
        address[] memory assets,
        uint256 amount,
        uint256 limit,
        address recipient,
        uint256 deadline
    ) internal returns (uint256) {
        ...
        return
            abs(
                // @auditnote: BUG. Does not pass msg.value.
@>              balancerVault.batchSwap(
                    kind,
                    swaps,
                    assets,
                    FundManagement({
                        sender: address(this),
                        fromInternalBalance: false,
                        recipient: payable(recipient),
                        toInternalBalance: false
                    }),
                    limits,
                    deadline
                )[pathLength]
            );
    }

    function pendleJoin(address recipient, uint256 minOut, bytes memory data) internal returns (uint256 netLpOut){
        (
            address market,
            ApproxParams memory guessPtReceivedFromSy,
            TokenInput memory input,
            LimitOrderData memory limit
        ) = abi.decode(data, (address, ApproxParams, TokenInput , LimitOrderData));
        
        if (input.tokenIn != address(0)) {
                input.netTokenIn = IERC20(input.tokenIn).balanceOf(address(this));
                IERC20(input.tokenIn).forceApprove(address(pendleRouter),input.netTokenIn);
            }

        (netLpOut,,) = pendleRouter.addLiquiditySingleToken{value: msg.value}(recipient, market, minOut, guessPtReceivedFromSy, input, limit);
    }

Proof of Concept

N/A

Tools Used

Manual Review

Recommended Mitigation Steps

-              balancerVault.batchSwap(
+              balancerVault.batchSwap{value: msg:value}(

Assessed type

Other

Metadata

Metadata

Assignees

No one assigned

    Labels

    2 (Med Risk)Assets not at direct risk, but function/availability of the protocol could be impacted or leak value🤖_121_groupAI based duplicate group recommendation🤖_primaryAI based primary recommendationM-37bugSomething isn't workingprimary issueHighest quality submission among a set of duplicatessatisfactorysatisfies C4 submission criteria; eligible for awardsselected for reportThis submission will be included/highlighted in the audit reportsponsor confirmedSponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity")sufficient quality reportThis report is of sufficient quality

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions