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.
Removing the from argument (it's redundant since the owner is calling the sendTransaction()
, removing .provider
and adding the address (addr1.address
) fixed it:
const params = { to: addr1.address, value: ethers.utils.parseUnits("1", "ether").toHexString()};
const txHash = await owner.sendTransaction(params);
console.log("transactionHash is " + txHash);
Results in:
$ npx hardhat test
HandleEther contract
Deployment
transactionHash is {
hash: '0xbc9a920f404109c6c97a3acbe3f2470864a2a650a0034faf353e235290b40be4',
type: 2,
accessList: [],
blockHash: '0xd95b35db38076c84bc26be176d9305a862e2851436509eeafc913b8853300ba5',
blockNumber: 2,
transactionIndex: 0,
confirmations: 1,
from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
gasPrice: BigNumber { _hex: '0x6944bd18', _isBigNumber: true },
maxPriorityFeePerGas: BigNumber { _hex: '0x3b9aca00', _isBigNumber: true },
maxFeePerGas: BigNumber { _hex: '0x96eeb030', _isBigNumber: true },
gasLimit: BigNumber { _hex: '0x5209', _isBigNumber: true },
to: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
value: BigNumber { _hex: '0x0de0b6b3a7640000', _isBigNumber: true },
nonce: 1,
data: '0x',
r: '0x7b7bf73f8f68b55148d0333b50deaccbf066b5a4268dba03cd9a7dfe3a8ba811',
s: '0x7f0a57ac7646890fc748c22599f28a2da318e580890ca24f1e2bf704ce19bfbe',
v: 0,
creates: null,
chainId: 31337,
wait: [Function (anonymous)]
}
✓ Should send ether to contract after deployment
1 passing (441ms)
Best Answer
Use
hardhat_reset
in abeforeEach
hook: