When providing liquidity from a smart contract, the most important thing to keep in mind is that tokens deposited into a pool at any rate other than the current reserve ratio are vulnerable to being arbitraged . As an example, if the ratio of x:y in a pair is 10:2 (i.e. the price is 5), and someone naively adds liquidity at 5:2 (a price of 2.5), the contract will simply accept all tokens (changing the price to 3.75 and opening up the market to arbitrage), but only issue pool tokens entitling the sender to the amount of assets sent at the proper ratio, in this case, 5:1. To avoid donating to arbitrageurs, it is imperative to add liquidity at the current price. Luckily, it's easy to ensure that this condition is met!
The easiest way to safely add liquidity to a pool is to use the router contract, which provides a simple method to safely add liquidity to a pool, add_liquidity.
The caller needs to commit to a belief about the current price , which is encoded in the amount**Desired parameters. Typically, it's fairly safe to assume that the current fair market price is around what the current reserve ratio is for a pair (because of arbitrage). So, if a user wants to add 1 ETH to a pool, and the current DAI/WETH ratio of the pool is 4000/1, it's reasonable to calculate that 4000 DAI must be sent along with 1 ETH, which is an implicit commitment to the price of 4000 DAI/1 WETH. However, it's important to note that this must be calculated before the transaction is submitted . It is not safe to look up the reserve ratio from within a transaction and rely on it as a price belief, as this ratio can be cheaply manipulated to your detriment.
However, it is still possible to submit a transaction that encodes a belief about the price which ends up being wrong because of a larger change in the true market price before the transaction is confirmed. For that reason, it's necessary to pass an additional set of parameters that encode the caller's tolerance to price changes. These amount**Min parameters should typically be set to percentages of the calculated desired price. So, at a 1% tolerance level, if our user sends a transaction with 1 ETH and 4000 DAI, amountETHMin should be set to e.g. .99 ETH, and amountTokenMin should be set to 3960 DAI. This means that, at worst, liquidity will be added at a rate between 3960 DAI/1 ETH and 4040.40 DAI/1 ETH (4000 DAI/.99 ETH).
Once the price calculations have been made, it's important to ensure that your contract
- 1.controls at least as many tokens/ETH as were passed as amount*Desired parameters, and
- 2.has granted approval to the router to withdraw this many tokens:
IERC20.approve(contract_address = DAI_address, spender = router, amount = DAI_amount)
IERC20.approve(contract_address = WETH_address, spender = router, amount = WETH_amount)
let (contract_address) = get_contract_address()
let (deadline) = get_block_timestamp()
let(amountA:Uint256, amountB:Uint256, liquidity:Uint256) = IRouter.add_liquidity(contract_address = router, tokenA = DAI_address, tokenB = WETH_address, amountADesired = DAI_amount, amountBDesired = WETH_amount, amountAMin = DAI_amount_min, amountBMin = WETH_amount_min, to = contract_address, deadline = deadline )