Univ2ZapRouter
is a smart contract designed to simplify liquidity provision and removal on Uniswap V2. It enables users to "zap" into or out of a liquidity pool using a single input token (either ETH or an ERC20 token). The contract automatically handles the necessary swaps and liquidity operations, and for "zap-ins," it aims to calculate an optimal swap amount to maximize the LP tokens received by the user, considering Uniswap's 0.3% trading fee.
This project is built using Foundry.
- Single Token Zap-In: Add liquidity to a Uniswap V2 pair using a single input token (ETH or ERC20).
- Calculates an optimal amount of the input token to swap for the other token in the pair to maximize LP tokens minted.
- Single Token Zap-Out: Remove liquidity and receive a single output token (ETH or ERC20).
- Withdraws both tokens from the pair and swaps one into the desired output token.
- Handles ETH and ERC20 tokens: Automatically wraps/unwraps ETH to WETH where necessary.
- Fee-on-Transfer Token Support: Includes logic paths for interacting with tokens that may have fees on transfer.
- Gas Optimized: Implemented with various gas-saving techniques (see Gas Optimizations section).
- Reentrancy Protected: Uses OpenZeppelin's
ReentrancyGuard
for enhanced security. - Event Emissions: Emits
ZapIn
andZapOut
events for off-chain tracking.
- The user provides a single input token (
tokenIn
) and specifies the pair (tokenA
,tokenB
) they want to add liquidity to. - The contract calculates an optimal amount (
toSwap
) oftokenIn
to swap for the other token in the pair. This calculation aims to provide liquidity in the new optimal ratio post-swap, maximizing LP tokens received. - If
tokenIn
is not one of the pair tokens, this scenario is not directly supported byzapInSingleToken
(it assumestokenIn
is eithertokenA
ortokenB
). - The
toSwap
amount oftokenIn
is swapped for the other pair token. - The remaining
amountIn - toSwap
oftokenIn
and the received amount of the other token are then added as liquidity to the Uniswap V2 pair. - LP tokens are sent to the user.
- The user provides LP tokens for a specific pair (
tokenA
,tokenB
) and specifies the single output token (tokenOut
) they wish to receive. - The contract removes liquidity, receiving
tokenA
andtokenB
from the pair. - If
tokenOut
istokenA
, the receivedtokenB
is swapped fortokenA
. The totaltokenA
(initial + swapped) is sent to the user. - If
tokenOut
istokenB
, the receivedtokenA
is swapped fortokenB
. The totaltokenB
(initial + swapped) is sent to the user.
Compile the smart contracts:
forge build
Run the test suite:
forge test
To see gas reports:
forge test --gas-report
Or, for more detailed verbosity on tests:
forge test -vvv
Ensure code is formatted correctly:
forge fmt
Objective: To reduce the gas consumption of key functions in Univ2ZapRouter.sol
and its deployment cost.
Optimizations Applied:
- Address Caching: Cached
address(this)
andaddress(router)
in local stack variables withinzapInSingleToken
andzapOutSingleToken
to reduce repeated lookups. - Efficient Address Passing: Passed cached addresses as arguments to internal helper functions (
_swapForPair
,_addLiquidity
) to avoid redundant lookups within them. - Targeted
unchecked
Math: Appliedunchecked
to the arithmeticamountIn - toSwap
in_swapForPair
, as its safety is guaranteed by priorrequire
statements. - Optimized Fee-on-Transfer Swap Logic: Refined
_swapForPair
to calculate the received amount for fee-on-transfer tokens by taking a balance snapshot before and after the swap, ensuring accurate accounting.
Gas Consumption Comparison:
Metric | Original State | Optimized State | Savings | % Savings (Approx) |
---|---|---|---|---|
Deployment Cost (gas) | 2,572,729 | 2,489,135 | 83,594 gas | 3.25% |
Deployment Size (bytes) | 12,415 | 11,939 | 476 bytes | 3.83% |
zapInSingleToken |
||||
Avg Gas per Call | 318,653 | 317,954 | 699 gas | 0.22% |
zapOutSingleToken |
||||
Avg Gas per Call | 256,524 | 255,749 | 775 gas | 0.30% |
Summary: The applied optimizations resulted in notable improvements:
- A significant reduction in deployment cost by 83,594 gas and contract size by 476 bytes.
- Modest but consistent gas savings for runtime function calls:
zapInSingleToken
is now cheaper by approximately 699 gas on average per call.zapOutSingleToken
is now cheaper by approximately 775 gas on average per call.
These changes enhance the efficiency of the Univ2ZapRouter
contract.
The _optimalSwap(amountIn, reserveIn)
function calculates the portion of amountIn
that should be swapped to the other token in the pair to maximize LP tokens received, assuming a 0.3% trading fee (where tokenIn
is one of the assets in the pair, and reserveIn
is its corresponding reserve in the pool).
The formula implemented is:
toSwap = (sqrt(reserveIn * (amountIn * 3988000 + reserveIn * 3988009)) - reserveIn * 1997) / 1994
The constants (3988000, 3988009, 1997, 1994) are derived from algebraic manipulation of the Uniswap V2 constant product formula, accounting for the 0.3% swap fee, to find the value of toSwap
(x
in many derivations) that maximizes the liquidity provided.
*(A more detailed step-by-step derivation can be added here if desired. This often involves:
- Defining reserves before swap (
R_in
,R_out
). - Defining amounts after swap (
R_in - toSwap + amountSwappedIn
,R_out - amountReceivedFromSwap
). - Accounting for Uniswap fee:
amountReceivedFromSwap = (toSwap * 0.997 * R_out) / (R_in + toSwap * 0.997)
. - Maximizing the product of the new reserves (or a function proportional to LP tokens, like
sqrt((R_in - toSwap) * R_out_effective)
iftokenIn
istokenA
, whereR_out_effective
is the amount oftokenB
after addingamountReceivedFromSwap
to it). This optimization problem is typically solved using calculus (taking the derivative with respect totoSwap
and setting to zero) or algebraic methods to find the optimaltoSwap
value.)*
This is experimental software and is provided on an "as is" and "as available" basis. Interacting with DeFi protocols carries inherent risks. Always do your own research and exercise caution. The author is not responsible for any losses incurred.