A production-grade Uniswap V2-style Automated Market Maker with extensions including TWAP oracle, adjustable fees, and sandwich protection. Built with Foundry.
┌─────────────────────────────────────────────────────────────────────────────┐
│ AMM SYSTEM OVERVIEW │
└─────────────────────────────────────────────────────────────────────────────┘
┌──────────────────┐
│ AMMRouter │
│ │
│ • addLiquidity │
│ • removeLiquidity│
│ • swap* │
│ • getAmounts* │
└───────┬──────────┘
│
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ AMMPair │ │ AMMPair │ │ AMMPair │
│ (A/B) │ │ (B/C) │ │ (A/WETH) │
│ │ │ │ │ │
│ • LP tokens │ │ • LP tokens │ │ • LP tokens │
│ • TWAP oracle│ │ • TWAP oracle│ │ • TWAP oracle│
│ • swap() │ │ • swap() │ │ • swap() │
│ • mint() │ │ • mint() │ │ • mint() │
│ • burn() │ │ • burn() │ │ • burn() │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
└─────────────────┼─────────────────┘
│
┌───────▼───────┐
│ AMMFactory │
│ │
│ • createPair │
│ • getPair │
│ • setFeeTo │
│ • setSwapFee │
└───────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ SWAP FLOW │
└─────────────────────────────────────────────────────────────────────────────┘
User Router Pair Tokens
│ │ │ │
│ swapExact...() │ │ │
│ ──────────────────►│ │ │
│ │ │ │
│ │ transferFrom(user) │ │
│ │ ◄────────────────────┼─────────────────────│
│ │ │ │
│ │ transfer(pair) │ │
│ │ ─────────────────────┼────────────────────►│
│ │ │ │
│ │ swap(out, to) │ │
│ │ ────────────────────►│ │
│ │ │ │
│ │ │ verify K invariant │
│ │ │ update reserves │
│ │ │ update TWAP │
│ │ │ │
│ │ │ transfer(user) │
│ │ │ ────────────────────►
│ receive tokens │ │ │
│ ◄──────────────────┼──────────────────────┼─────────────────────│
The core invariant of the AMM:
x × y = k
where:
x = reserve of token A
y = reserve of token B
k = constant (increases with fees)
For a swap of Δx tokens in:
Δy = (y × Δx × (10000 - fee)) / (x × 10000 + Δx × (10000 - fee))
where:
Δx = input amount
Δy = output amount
fee = swap fee in basis points (default: 30 = 0.3%)
Price Impact = 1 - (output_amount / (input_amount × spot_price))
Spot Price = reserve_out / reserve_in
First deposit:
LP_tokens = sqrt(amount0 × amount1) - MINIMUM_LIQUIDITY
Subsequent deposits:
LP_tokens = min(
(amount0 × totalSupply) / reserve0,
(amount1 × totalSupply) / reserve1
)
Time-Weighted Average Price calculation:
price0CumulativeLast += (reserve1 / reserve0) × timeElapsed
price1CumulativeLast += (reserve0 / reserve1) × timeElapsed
TWAP = (priceCumulative_end - priceCumulative_start) / timeElapsed
- Constant Product AMM: x × y = k invariant
- LP Tokens: ERC-20 liquidity provider tokens
- TWAP Oracle: Cumulative price tracking for external use
- Flash Loans: Borrow tokens within a single transaction
- Skim/Sync: Balance recovery utilities
- Pair Creation: CREATE2 for deterministic addresses
- Adjustable Fees: Configurable swap fee (max 1%)
- Protocol Fees: Optional fee recipient for protocol revenue
- Liquidity Management: Add/remove with slippage protection
- Token Swaps: Single and multi-hop swaps
- ETH Support: Native ETH via WETH wrapper
- Deadline Protection: Transaction expiration
# Clone the repository
git clone https://github.com/Kazopl/amm-uniswap-v2-plus.git
cd amm-uniswap-v2-plus
# Install dependencies
forge install OpenZeppelin/[email protected]
forge install foundry-rs/forge-std
# Build
forge build
# Run tests
forge testCreate a .env file:
PRIVATE_KEY=your_private_key_here
ARBITRUM_SEPOLIA_RPC_URL=https://sepolia-rollup.arbitrum.io/rpc
ARBISCAN_API_KEY=your_arbiscan_api_key
WETH_ADDRESS=0x... # WETH on Arbitrum Sepolia# Run all tests
forge test
# Run with verbosity
forge test -vvv
# Run unit tests
forge test --match-path "test/unit/*"
# Run fuzz tests
forge test --match-path "test/fuzz/*"
# Run invariant tests
forge test --match-path "test/invariant/*"
# Gas report
forge test --gas-reportsource .env
WETH_ADDRESS=0x... forge script script/DeployAll.s.sol:DeployAll \
--rpc-url $ARBITRUM_SEPOLIA_RPC_URL \
--private-key $PRIVATE_KEY \
--broadcast \
--verify \
--etherscan-api-key $ARBISCAN_API_KEY| Threat | Mitigation |
|---|---|
| Reentrancy | ReentrancyGuard on all state-changing functions |
| Price Manipulation | TWAP oracle, not spot price for external use |
| Sandwich Attacks | Slippage protection via amountMin parameters |
| Flash Loan Attacks | K invariant check after callback |
| Integer Overflow | Solidity 0.8.24 built-in checks |
| Front-Running | Deadline parameter, user-set slippage |
| LP Drain | Minimum liquidity permanently locked |
- MEV/Sandwich: Mitigated by slippage parameters
- Oracle Manipulation: TWAP smooths price over time
- Liquidity Removal: Proportional burning ensures fairness
- Flash Loans: K invariant must hold after callback
reserve0 × reserve1never decreases (K invariant)- LP total supply ≥ MINIMUM_LIQUIDITY
- Token balances ≥ reserves
- Swap fee ≤ MAX_SWAP_FEE_BPS
| Function | Gas (approx) |
|---|---|
createPair() |
~2,500,000 |
addLiquidity() |
~150,000 |
removeLiquidity() |
~120,000 |
swapExactTokensForTokens() |
~100,000 |
mint() |
~120,000 |
swap() |
~80,000 |
amm-uniswap-v2-plus/
├── src/
│ ├── AMMPair.sol # Pair with LP tokens + TWAP
│ ├── AMMFactory.sol # Pair factory
│ ├── AMMRouter.sol # User-facing router
│ ├── interfaces/
│ │ ├── IAMMPair.sol
│ │ ├── IAMMFactory.sol
│ │ ├── IAMMRouter.sol
│ │ └── IWETH.sol
│ ├── libraries/
│ │ └── AMMLibrary.sol # Math helpers
│ └── mocks/
│ ├── MockERC20.sol
│ └── WETH.sol
├── test/
│ ├── BaseTest.sol
│ ├── unit/
│ │ ├── AMMFactory.t.sol
│ │ ├── AMMPair.t.sol
│ │ └── AMMRouter.t.sol
│ ├── fuzz/
│ │ └── AMMFuzz.t.sol
│ └── invariant/
│ └── AMMInvariant.t.sol
├── script/
│ ├── DeployAll.s.sol
│ └── Interactions.s.sol
├── foundry.toml
└── README.md
MIT License - see LICENSE for details.
- Uniswap V2 - Original AMM design
- OpenZeppelin Contracts
- Foundry
- Cyfrin - DeFi course patterns