How to addLiquidityETH in truffle console?

Hi, I'm trying to addLiquidityETH in truffle console but keeps running into ds-math-sub-underflow error. The pool pair is created at contract constructor and the wallet has more than enough for myToken and Eth. I'm running this on a ganache cli clone of mainnet. Could someone help? Thank you


        // Approve and add liquidity
        const IUniswapV2Pair = artifacts.require("IUniswapV2Pair");
        const IUniswapV2Factory = artifacts.require("IUniswapV2Factory");
        const IUniswapV2Router01 = artifacts.require("IUniswapV2Router01");
        const IUniswapV2Router02 = artifacts.require("IUniswapV2Router02");

        const uniswapV2Router = await IUniswapV2Router02.at('0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'); //UniSwapV2Router02 address

        await myToken.approve('0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', //UniSwapV2Router02 address
            1000,
            {
            from: '0xD7dfaDD73e3F87227b5D8F7860A975e92A9266A1', // accounts[0] wallet 
            gas: 4000000,
        });

        const tokenPair = await IUniswapV2Pair.at('0xa6af56Dc3F2Df62CC1922F5b47C6B8Df4B0B30A8'); //token|ETH pair in constructor
        await tokenPair.approve('0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', //UniSwapV2Router02 address
            1000,
            {
            from: '0xD7dfaDD73e3F87227b5D8F7860A975e92A9266A1', // accounts[0] wallet 
            gas: 4000000,
        });
await uniswapV2Router.addLiquidityETH(
            myToken.address, // token address
            1000, // token value
            1, // slippage is unavoidable
            1, // slippage is unavoidable
            '0xD7dfaDD73e3F87227b5D8F7860A975e92A9266A1', // accounts[0] wallet 
            1690886616,
            {
                from: '0xD7dfaDD73e3F87227b5D8F7860A975e92A9266A1', // accounts[0] wallet 
                value: 1 //eth value
            }
        );
Error: Returned error: VM Exception while processing transaction: revert ds-math-sub-underflow -- Reason given: ds-math-sub-underflow.
    at main (scripts/debug.js:44:48)
    at processTicksAndRejections (node:internal/process/task_queues:94:5) {
  data: {
    '0xa4825ca1fef17a19129e39c872b8696c1884affe4f533a964381ed320130cd48': {
      error: 'revert',
      program_counter: 13975,
      return: '0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001564732d6d6174682d7375622d756e646572666c6f770000000000000000000000',
      reason: 'ds-math-sub-underflow'
    },
    stack: 'c: VM Exception while processing transaction: revert ds-math-sub-underflow\n' +
      '    at Function.c.fromResults (/opt/homebrew/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:4:192416)\n' +
      '    at A.w.processBlock (/opt/homebrew/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:42:50915)\n' +
      '    at runMicrotasks (<anonymous>)\n' +
      '    at processTicksAndRejections (node:internal/process/task_queues:94:5)',
    name: 'c'
  },
  reason: 'ds-math-sub-underflow',
  hijackedStack: 'Error: Returned error: VM Exception while processing transaction: revert ds-math-sub-underflow -- Reason given: ds-math-sub-underflow.\n' +
    '    at Object.ErrorResponse (/usr/local/lib/node_modules/truffle/build/webpack:/node_modules/web3-core-helpers/lib/errors.js:28:1)\n' +
    '    at /usr/local/lib/node_modules/truffle/build/webpack:/node_modules/web3/node_modules/web3-core-requestmanager/lib/index.js:303:1\n' +
    '    at /usr/local/lib/node_modules/truffle/build/webpack:/packages/provider/wrapper.js:107:1\n' +
    '    at XMLHttpRequest.request.onreadystatechange (/usr/local/lib/node_modules/truffle/build/webpack:/node_modules/web3/node_modules/web3-providers-http/lib/index.js:98:1)\n' +
    '    at XMLHttpRequestEventTarget.dispatchEvent (/usr/local/lib/node_modules/truffle/build/webpack:/node_modules/xhr2-cookies/dist/xml-http-request-event-target.js:34:1)\n' +
    '    at XMLHttpRequest.exports.modules.996763.XMLHttpRequest._setReadyState (/usr/local/lib/node_modules/truffle/build/webpack:/node_modules/xhr2-cookies/dist/xml-http-request.js:208:1)\n' +
    '    at XMLHttpRequest.exports.modules.996763.XMLHttpRequest._onHttpResponseEnd (/usr/local/lib/node_modules/truffle/build/webpack:/node_modules/xhr2-cookies/dist/xml-http-request.js:318:1)\n' +
    '    at IncomingMessage.<anonymous> (/usr/local/lib/node_modules/truffle/build/webpack:/node_modules/xhr2-cookies/dist/xml-http-request.js:289:47)\n' +
    '    at IncomingMessage.emit (node:events:381:22)\n' +
    '    at endReadableNT (node:internal/streams/readable:1307:12)\n' +
    '    at processTicksAndRejections (node:internal/process/task_queues:81:21)'
}

Having the same issue, did you ever solve this?

I found the solution and added an answer here: https://ethereum.stackexchange.com/questions/106362/unable-to-addliquidity-to-a-uniswap-pool-on-a-hardhat-network-fork

Copy paste of my answer from stackoverflow:

The UniswapV2Pair.sol fails in the mint method, called by the contract UniswapV2Router02.sol during the addLiquidityETH/addLiquidity method. The problem is that the pair requires a small amount of balance of both tokens, such that a minimum amount of liquidity can be added.

In short, just transfer more than 1000 wei of each token to the pair address and addLiquidityETH/addLiquidity will work.

Longer version:

The failing line is marked with XXX:

// this low-level function should be called from a contract which performs important safety checks
    function mint(address to) external lock returns (uint liquidity) {
        (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
        uint balance0 = IERC20(token0).balanceOf(address(this));
        uint balance1 = IERC20(token1).balanceOf(address(this));
        uint amount0 = balance0.sub(_reserve0);
        uint amount1 = balance1.sub(_reserve1);

        bool feeOn = _mintFee(_reserve0, _reserve1);
        uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
        if (_totalSupply == 0) {
             // XXX next line fails with ds-math-sub-underflow
            liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
           _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
        } else {
            liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
        }
        require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
        _mint(to, liquidity);

        _update(balance0, balance1, _reserve0, _reserve1);
        if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
        emit Mint(msg.sender, amount0, amount1);
    }

when deploying new tokens, their reserve is 0 and also the balance of the token held by the pair address is 0. This means in the above mint function, the variables amount0 and amount1 are also 0.

But sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY > 0, otherwise one gets that ds-math-sub-underflow error.

With MINIMUM_LIQUIDITY = 10**3 and amount0 * amount1 > MINIMUM_LIQUIDITY**2, one can calculate that this requirement is fulfilled when the pair address holds just more of 1000 wei of each token.

3 Likes