sherlock-audit/2023-04-blueberry-judging

Bauer - Dos attack to openPositionFarm()

sherlock-admin opened this issue · 3 comments

Bauer

high

Dos attack to openPositionFarm()

Summary

A bad actor can transfer 1 wei worth of the corresponding token to the protocol before user calling the openPositionFarm() function, in order to increase the protocol's balance and build an LP position to call ICurvePool(pool).add_liquidity(), since the protocol only allows the curvel pool to spend the borrowed token, this will cause an error when Curve attempts to transfer other tokens out of the protocol.

Vulnerability Detail

The openPositionFarm() function is used to add liquidity to Curve pool with 2 underlying tokens, with staking to Curve gauge. When add liquidity on curve ,the protocol use the borrowed token and the collateral token, it checks the number of tokens in the pool and creates an array of the supplied token amounts to be passed to the add_liquidity function.If the pool contains three tokens, the process is repeated with an array of three elements, and if the pool contains four tokens, an array of four elements is created and used. Here is the problem,a bad actor may transfer 1 wei worth of the corresponding token to the protocol before user calling the openPositionFarm() function, in order to increase the protocol's balance and build an LP position to call ICurvePool(pool).add_liquidity(). However, since the protocol only allows the curvel pool to spend the borrowed token, this will cause an error when Curve attempts to transfer other tokens out of the protocol.

  uint256 borrowBalance = _doBorrow(
            param.borrowToken,
            param.borrowAmount
        );

        // 3. Add liquidity on curve
        _ensureApprove(param.borrowToken, pool, borrowBalance);
        if (tokens.length == 2) {
            uint256[2] memory suppliedAmts;
            for (uint256 i = 0; i < 2; i++) {
                suppliedAmts[i] = IERC20Upgradeable(tokens[i]).balanceOf(
                    address(this)
                );
            }
            ICurvePool(pool).add_liquidity(suppliedAmts, minLPMint);
        } else if (tokens.length == 3) {
            uint256[3] memory suppliedAmts;
            for (uint256 i = 0; i < 3; i++) {
                suppliedAmts[i] = IERC20Upgradeable(tokens[i]).balanceOf(
                    address(this)
                );
            }
            ICurvePool(pool).add_liquidity(suppliedAmts, minLPMint);
        } else if (tokens.length == 4) {
            uint256[4] memory suppliedAmts;
            for (uint256 i = 0; i < 4; i++) {
                suppliedAmts[i] = IERC20Upgradeable(tokens[i]).balanceOf(
                    address(this)
                );
            }
            ICurvePool(pool).add_liquidity(suppliedAmts, minLPMint);
        }

Impact

User will not able to call the openPositionFarm() function to add liquidity to Curve pool

Code Snippet

https://github.com/sherlock-audit/2023-04-blueberry/blob/main/blueberry-core/contracts/spell/CurveSpell.sol#L84-L115

Tool used

Manual Review

Recommendation

We are using already registered liquidity pools that won't run into the issue of having a 1wei balance and having the 0 depositor issue.

Escalate for 10 USDC

Every time a user calls CurveSpell.openPositionFarm(), the protocol checks the balance of the tokens in the pool regardless of whether they are zero or not, and creates an array of the supplied token amounts to be passed to the add_liquidity function.

// 3. Add liquidity on curve
        _ensureApprove(param.borrowToken, pool, borrowBalance);
        if (tokens.length == 2) {
            uint256[2] memory suppliedAmts;
            for (uint256 i = 0; i < 2; i++) {
                suppliedAmts[i] = IERC20Upgradeable(tokens[i]).balanceOf(
                    address(this)
                );
            }
            ICurvePool(pool).add_liquidity(suppliedAmts, minLPMint);
        } else if (tokens.length == 3) {
            uint256[3] memory suppliedAmts;
            for (uint256 i = 0; i < 3; i++) {
                suppliedAmts[i] = IERC20Upgradeable(tokens[i]).balanceOf(
                    address(this)
                );
            }
            ICurvePool(pool).add_liquidity(suppliedAmts, minLPMint);
        } else if (tokens.length == 4) {
            uint256[4] memory suppliedAmts;
            for (uint256 i = 0; i < 4; i++) {
                suppliedAmts[i] = IERC20Upgradeable(tokens[i]).balanceOf(
                    address(this)
                );
            }
            ICurvePool(pool).add_liquidity(suppliedAmts, minLPMint);
        }

When adding liquidity to the Curve pool, the Curve protocol calls the safeTransferFrom() function to withdraw funds from the CurveSpell protocol.

for i in range(N_COINS):
        in_amount: uint256 = amounts[i]
        if token_supply == 0:
            assert in_amount > 0  # dev: initial deposit requires all coins
        in_coin: address = self.coins[i]

        # Take coins from the sender
        if in_amount > 0:
            if i == FEE_INDEX:
                in_amount = ERC20(in_coin).balanceOf(self)

            # "safeTransferFrom" which works for ERC20s which return bool or not
            _response: Bytes[32] = raw_call(
                in_coin,
                concat(
                    method_id("transferFrom(address,address,uint256)"),
                    convert(msg.sender, bytes32),
                    convert(self, bytes32),
                    convert(amounts[i], bytes32),
                ),
                max_outsize=32,
            )  # dev: failed transfer

If the Curve pool is composed of four tokens and the user only borrows borrowToken by calling CurveSpell.openPositionFarm() while setting the rest of the tokens to 0, the CurveSpell protocol will only allow the curve pool to spend borrowToken.

// 3. Add liquidity on curve
        _ensureApprove(param.borrowToken, pool, borrowBalance);

If a bad actor discovers this transaction in the mempool and transfers 1 wei of another token to CurveSpell in advance, then when adding liquidity, CurveSpell will have one token that has not allowed the Curve pool to spend, causing the safeTransferFrom() function called by the Curve pool to fail when withdrawing funds from the CurveSpell protocol, and the user will be unable to call CurveSpell.openPositionFarm() to add liquidity.

Closing this issue based on Sponsor comment.
Please provide more details and a valid POC during the actual escalation period.
the above comment is not a valid escalation as it was commented prior to the escalation period.