I'm trying to understand exactly how all these fee on swap tokens work and how they are deployed so I am trying to go through the process of deploying a test one myself and scripting the whole process using hardhat and a local testnet.

I have created a deployment script that deploys the original HATE contracts and it works fine until the point that I try to add liquidity to the Uniswap pair that was created on contract creation. From the looks of it, during deployment createPair is called and an LP pair with no liquidity is created and stored in the contract - you're able to get the address from the UniswapV2Pair function - this all works fine with no problems and I'm able to get the pair address.

The relevant section of the deploy code looks like this:

 //Deploy the contract

 const shitcoin = await deploy(name, [routerAddress,feeDecimals, feePercentage, minTokensBeforeSwap, false]);

  // BURN 50% of the supply
  const pairAddress = await shitcoin.uniswapV2Pair();

  const supply = await shitcoin.totalSupply();
  const halfSupply = supply.div(2);
  const remainingSupply = supply.sub(halfSupply);

  await shitcoin.transfer(constants.AddressZero, halfSupply);

  //Connect uniswap router to deploying wallet
  const router = await uRouter.connect(signer);

  //Approve token for use by Uniswap router so that liq can be added
  const approved = await shitcoin.approve(routerAddress, constants.MaxUint256);

  //Approving the LP token itself also makes no difference
  //Tried this bc of SE Q&A - made no diff
  const approved2 = await shitcoin.approve(pairAddress, constants.MaxUint256);

  const baseCurToAdd = ethers.utils.parseEther("5.0")

  const lpAddTx = await router.populateTransaction.addLiquidityETH(
            (Date.now() + 100000)
  await signer.call(lpAddTx);

However I find that lpAddTx causes this error:

- From `hardhat chain`

eth_call   Contract call:       <UnrecognizedContract>   From:         0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266   To:                  0x7a250d5630b4cf539739df2c5dacb4c659f2488d

  Error: VM Exception while processing transaction: reverted with reason string 'ds-math-sub-underflow'
      at <UnrecognizedContract>.<unknown> (0x65556fc49c3a0664e7fbde761fc2ba5d0e6cbbed)
      at <UnrecognizedContract>.<unknown> (0x7a250d5630b4cf539739df2c5dacb4c659f2488d)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)
      at HardhatNode.runCall (/Users/vegtam/devspace/crypto-bots/rugger/node_modules/hardhat/src/internal/hardhat-network/provider/node.ts:511:20)
      at EthModule._callAction (/Users/vegtam/devspace/crypto-bots/rugger/node_modules/hardhat/src/internal/hardhat-network/provider/modules/eth.ts:353:9)
      at HardhatNetworkProvider._sendWithLogging (/Users/vegtam/devspace/crypto-bots/rugger/node_modules/hardhat/src/internal/hardhat-network/provider/provider.ts:127:22)
      at HardhatNetworkProvider.request (/Users/vegtam/devspace/crypto-bots/rugger/node_modules/hardhat/src/internal/hardhat-network/provider/provider.ts:104:18)
      at JsonRpcHandler._handleRequest (/Users/vegtam/devspace/crypto-bots/rugger/node_modules/hardhat/src/internal/hardhat-network/jsonrpc/handler.ts:188:20)
      at JsonRpcHandler._handleSingleRequest (/Users/vegtam/devspace/crypto-bots/rugger/node_modules/hardhat/src/internal/hardhat-network/jsonrpc/handler.ts:167:17)

and from the script itself:

pair 0x65556Fc49C3A0664E7FBDE761FC2ba5d0E6CbBed {   data: '0xf305d7190000000000000000000000000e801d84fa97b50751dbf25036d067dcf18858bf00000000000000000000000000000000000000000000001b1ae4d6e2ef50000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000004563918244f40000000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000017af8d63bc4', to: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',   from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' } ProviderError: Error: VM Exception while processing transaction: reverted with reason string 'ds-math-sub-underflow'
    at HttpProvider.request (/Users/vegtam/devspace/crypto-bots/rugger/node_modules/hardhat/internal/core/providers/http.js:50:23)
    at GanacheGasMultiplierProvider.request (/Users/vegtam/devspace/crypto-bots/rugger/node_modules/hardhat/internal/core/providers/gas-providers.js:215:38)
    at processTicksAndRejections (node:internal/process/task_queues:96:5) error Command failed with exit code 1. info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Where I'm printing the address of the pair and the unsigned transaction for verification (looks correct to me). At first I believed that this may be caused by the fee on transfer being enabled as that caused some problems with the token burn but disabling fees until the final step made no difference.

I also saw this question + answer suggesting that it may be caused because I did not approve the pair token for spending so I added the approve2 call so I tried that but it also made no difference and gave the same error. That said, I'm also a bit confused why this would even be the case because when adding liquidity the actual token is the thing being spend not the LP token. I couldn't really find any more info about this in the Uniswap docs

I'm thinking it could be related to the fact that I am trying to do this on a hardhat local blockchain being forked from mainnet but it seems that if that was the issue that I wouldn't be able to create a uniswap pair in the first place.

I have also tried using the addLiquidity function instead of addLiquidityETH and passing in the wETH address but ran into the same error.

Has anyone experienced this problem before or know what I could be doing to debug the issue?

  • 1
    I have a similar setup and the same error on addLiquidity function call, still trying to make it work. Aug 4, 2021 at 23:50

1 Answer 1


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.

  • link <- this answer gave me the right hint :) thx @SergioGaitan
    – rassi
    Dec 24, 2021 at 13:43
  • But how come the pool doesn't have balance? It should have been funded by the router when the addLiquidity or addLiquidityETH method has been called, no?
    – Mouradif
    May 13, 2023 at 0:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.