Basin - alexzoid's results

A composable EVM-native decentralized exchange protocol.

General Information

Platform: Code4rena

Start Date: 03/07/2023

Pot Size: $40,000 USDC

Total HM: 14

Participants: 74

Period: 7 days

Judge: alcueca

Total Solo HM: 9

Id: 259

League: ETH

Basin

Findings Distribution

Researcher Performance

Rank: 43/74

Findings: 1

Award: $17.52

QA:
grade-a

🌟 Selected for report: 0

🚀 Solo Findings: 0

[01] Potential loss of tokens for first liquidity provider during zero LP minting

In the dual token pool, the first liquidity provider may inadvertently lose tokens when they assign a value to one token and set the second token amount to zero. Notably, in the _addLiquidity() function on line https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L441 when the _mint() method is invoked, there is no validation check for the lpAmountOut variable to ensure it is not zero.

Please find below a Proof of Concept. Create a new file named like Well.AddLiquidityAttack.t.sol and include the following context.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

import {console} from "forge-std/Test.sol";
import {TestHelper, IERC20, Call, Balances} from "test/TestHelper.sol";
import {AddLiquidityAction, RemoveLiquidityAction, LiquidityHelper} from "test/LiquidityHelper.sol";
import {Aquifer} from "src/Aquifer.sol";
import {SwapHelper, SwapAction, Snapshot} from "test/SwapHelper.sol";

contract WellAddLiquidityTest is LiquidityHelper {

    function _setupWellWithoutAddingLiquidity(Call memory _wellFunction, Call[] memory _pumps, IERC20[] memory _tokens) internal {
        tokens = _tokens;
        wellFunction = _wellFunction;
        for (uint256 i; i < _pumps.length; i++) {
            pumps.push(_pumps[i]);
        }

        initUser();

        wellImplementation = deployWellImplementation();
        aquifer = new Aquifer();
        well = encodeAndBoreWell(address(aquifer), wellImplementation, tokens, _wellFunction, _pumps, bytes32(0));

        // Mint mock tokens to user
        mintTokens(user, initialLiquidity);
        mintTokens(user2, initialLiquidity);
        approveMaxTokens(user, address(well));
        approveMaxTokens(user2, address(well));

        // Mint mock tokens to TestHelper
        mintTokens(address(this), initialLiquidity);
        approveMaxTokens(address(this), address(well));

        // Do not add initial liquidity from TestHelper
        // addLiquidityEqualAmount(address(this), initialLiquidity);
    }

    function setUp() public {
        _setupWellWithoutAddingLiquidity(deployWellFunction(), deployPumps(1), deployMockTokens(2));
    }

    function _addLiq(address targetUser, uint256 token0, uint256 token1) internal returns(uint256 minted) {
        uint256[] memory amounts = new uint256[](tokens.length);
        amounts[0] = token0;
        amounts[1] = token1;
        minted = well.addLiquidity(amounts, 0, targetUser, type(uint256).max); 
    }

    function testZeroMint() public {
        vm.startPrank(user);
        uint256 userBalanceToken0Before = IERC20(tokens[0]).balanceOf(address(user));
        _addLiq(user, 1e18, 0);
        uint256 userBalanceToken0After = IERC20(tokens[0]).balanceOf(address(user));
        assert(
            // Spent `1e18` token0
            userBalanceToken0Before - userBalanceToken0After == 1e18
            // Got zero LP tokens
            && well.balanceOf(user) == 0
            );
    }
}

Begin by executing forge test -vv --match-test testZeroMint. Here is an example output:

Running 1 test for test/Well.AddLiquidityAttack.t.sol:WellAddLiquidityTest
[PASS] testZeroMint() (gas: 138179)
Test result: ok. 1 passed; 0 failed; finished in 4.36ms

I advise that transactions be reverted when zero LP tokens are being minted.

[02] Minting the first MINIMUM_LIQUIDITY tokens to the zero address

The practice of minting the first MINIMUM_LIQUIDITY tokens (1000 tokens in the case of Uniswap) to the zero address(0x0) when initializing a new liquidity pool is a design choice utilized by the Uniswap protocol. This mechanism is used to prevent anyone from ever owning a 100% share of the liquidity pool, which could potentially manipulate the price of tokens in the pool.

Implementing this feature can bring a number of benefits including increased robustness against potential pricing manipulation.

I recommend that the _addLiquidity() function at line https://github.com/code-423n4/2023-07-basin/blob/main/src/Well.sol#L413 be updated to mint the first MINIMUM_LIQUIDITY tokens to the zero address, as is done in Uniswap.

#0 - c4-pre-sort

2023-07-13T14:19:53Z

141345 marked the issue as high quality report

#1 - 141345

2023-07-13T14:21:50Z

#2 - c4-sponsor

2023-08-02T23:10:46Z

publiuss marked the issue as sponsor acknowledged

#3 - c4-judge

2023-08-05T10:29:28Z

alcueca changed the severity to 2 (Med Risk)

#4 - c4-judge

2023-08-05T10:29:28Z

alcueca changed the severity to 2 (Med Risk)

#5 - c4-judge

2023-08-05T10:30:00Z

alcueca marked the issue as satisfactory

#6 - lokithe5th

2023-08-11T07:02:20Z

Although the PoC is correct here, this only holds true in the case where the first liquidity provider has specifically stated that their minLpAmountOut is allowed to be 0, in their call to addLiquidity(uint256[] memory tokenAmountsIn, uint256 minLpAmountOut, address recipient, uint256 deadline)

This is due to this check:

if (lpAmountOut < minLpAmountOut) { revert SlippageOut(lpAmountOut, minLpAmountOut); }

The warden's submission as QA severity seems appropriate here because:

  1. There is no unexpected loss of funds and the contract behaves as expected, as the caller has to have acknowledged the possibility of getting 0 tokens out.

  2. This requires the first liquidity provider to add only one-sided liquidity (which doesn't make sense for initializing an LP)

#7 - alcueca

2023-08-15T20:56:18Z

I didn't notice the detail of minLpAmountOut. Thanks for pointing it out. I agree that if the LP accepts the result might be zero, it can't complain if it actually is.

#8 - c4-judge

2023-08-15T20:57:14Z

alcueca changed the severity to QA (Quality Assurance)

#9 - c4-judge

2023-08-15T20:57:27Z

alcueca marked the issue as grade-a

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter