E5C2 GitHub - Enricrypto/Decentralised-Exchange: A Solidity-based decentralized exchange (DEX) for trustless token swaps and liquidity provision. Includes Factory, Pair, and Router contracts inspired by Uniswap V2. Supports ERC-20 tokens with slippage protection and gas optimisations. A solid foundation for building DeFi apps. · GitHub
[go: up one dir, main page]

Skip to content

Enricrypto/Decentralised-Exchange

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

37 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Decentralized Exchange (DEX) - Uniswap V2 Architecture

A production-ready Solidity implementation of an Automated Market Maker (AMM) DEX featuring Uniswap V2 architecture. Includes liquidity pools, multi-hop routing, and constant product market making with comprehensive security patterns and gas optimizations.

Solidity Foundry License: MIT GitHub last commit

🌟 Overview

This DEX implements the proven Uniswap V2 architecture with automated market-making capabilities. Users can create liquidity pools, provide liquidity to earn fees, and swap tokens through an intuitive router interface with multi-hop support for complex trading paths.

Key Features

  • 🏊 Liquidity Pools - Create and manage token pair pools
  • 💱 Token Swaps - Automated market-making with 0.3% fee
  • 🌊 Multi-Hop Routing - Complex swap paths through multiple pools
  • 💰 LP Tokens - ERC-20 tokens representing pool ownership
  • 🛡️ Slippage Protection - User-defined minimum output amounts
  • Gas Optimized - Efficient storage and computation patterns
  • 🎯 Deterministic Deployment - CREATE2 for predictable addresses

📊 AMM Mechanism

Constant Product Formula

The DEX uses the constant product market maker (x × y = k):

Reserve_A × Reserve_B = k (constant)

Where:
- Reserve_A: Amount of Token A in pool
- Reserve_B: Amount of Token B in pool
- k: Constant product (invariant)

Example Swap:

Initial: 100 ETH × 200,000 USDC = 20,000,000
User swaps: 10 ETH
New reserves: 110 ETH × ~181,818 USDC = 20,000,000
User receives: ~18,182 USDC (minus 0.3% fee)

Fee Structure

  • Swap Fee: 0.3% (goes to liquidity providers)
  • Fee Distribution: Proportional to LP token ownership
  • No Protocol Fee: 100% of fees to LPs

🏗️ Architecture

┌─────────────────────────────────────────────────────┐
│                   Router.sol                        │
│         (User-Facing Interface)                     │
│  • Add/Remove Liquidity                             │
│  • Token Swaps                                      │
│  • Multi-Hop Routing                                │
│  • Slippage Protection                              │
└───────────────┬─────────────────────────────────────┘
                │
        ┌───────▼──────┐
        │ Factory.sol  │
        │              │
        │ • Create     │
        │   Pairs      │
        │ • Track      │
        │   All Pools  │
        │ • CREATE2    │
        └──────┬───────┘
               │
        ┌──────▼──────┐  ┌──────────┐  ┌──────────┐
        │  Pair.sol   │  │ Pair.sol │  │ Pair.sol │
        │  ETH/USDC   │  │ ETH/DAI  │  │ DAI/USDC │
        │             │  │          │  │          │
        │ • Reserves  │  │ • Swaps  │  │ • LP     │
        │ • Swaps     │  │ • LP     │  │   Tokens │
        └─────────────┘  └──────────┘  └──────────┘

📂 Core Contracts

1. Pair.sol - Liquidity Pool

The fundamental building block representing a liquidity pool for two tokens.

Key Responsibilities:

  • Manages reserves for token pairs
  • Handles token swaps with fee calculation
  • Mints/burns LP tokens for liquidity providers
  • Maintains constant product invariant

Core Functions:

function swap(
    uint amount0Out,
    uint amount1Out,
    address to
) external;

function mint(address to) external returns (uint liquidity);

function burn(address to) external returns (uint amount0, uint amount1);

function sync() external; // Re-sync reserves

State Variables:

uint112 private reserve0;  // Token0 reserves (gas optimized)
uint112 private reserve1;  // Token1 reserves (gas optimized)
uint32 private blockTimestampLast;  // TWAP oracle support

2. Factory.sol - Pair Factory

Creates and tracks all liquidity pairs with deterministic addressing.

Key Features:

  • Ensures one unique pair per token combination
  • Uses CREATE2 for predictable addresses
  • Maintains registry of all pairs

Core Functions:

function createPair(
    address tokenA,
    address tokenB
) external returns (address pair);

function getPair(
    address tokenA,
    address tokenB
) external view returns (address pair);

CREATE2 Benefits:

  • Address predictability before deployment
  • Same pair address across different chains
  • No need to query factory for pair address

3. Router.sol - User Interface

Simplifies interactions with pairs and implements advanced features.

Key Features:

  • User-friendly liquidity management
  • Slippage protection on all operations
  • Multi-hop swap routing
  • Optimal liquidity calculations

Core Functions:

Liquidity Management:

function addLiquidity(
    address tokenA,
    address tokenB,
    uint amountADesired,
    uint amountBDesired,
    uint amountAMin,
    uint amountBMin,
    address to,
    uint deadline
) external returns (uint amountA, uint amountB, uint liquidity);

function removeLiquidity(
    address tokenA,
    address tokenB,
    uint liquidity,
    uint amountAMin,
    uint amountBMin,
    address to,
    uint deadline
) external returns (uint amountA, uint amountB);

Token Swaps:

function swapExactTokensForTokens(
    uint amountIn,
    uint amountOutMin,
    address[] calldata path,
    address to,
    uint deadline
) external returns (uint[] memory amounts);

function swapTokensForExactTokens(
    uint amountOut,
    uint amountInMax,
    address[] calldata path,
    address to,
    uint deadline
) external returns (uint[] memory amounts);

Helper Functions:

function getAmountOut(
    uint amountIn,
    uint reserveIn,
    uint reserveOut
) public pure returns (uint amountOut);

function getAmountsOut(
    uint amountIn,
    address[] memory path
) public view returns (uint[] memory amounts);

💡 Key Features Explained

1. Constant Product AMM

Formula: x × y = k

How It Works:

Initial Pool: 100 ETH × 200,000 USDC

Swap 10 ETH for USDC:
1. k = 100 × 200,000 = 20,000,000
2. New ETH reserve = 110
3. New USDC reserve = k / 110 = 181,818.18
4. USDC out = 200,000 - 181,818 = 18,182
5. Apply 0.3% fee
6. User receives: ~18,127 USDC

Price Impact: Larger trades relative to pool size have greater price impact (slippage).


2. Liquidity Provision

Adding Liquidity:

// 1. Approve tokens
tokenA.approve(router, amountA);
tokenB.approve(router, amountB);

// 2. Add liquidity
router.addLiquidity(
    tokenA,
    tokenB,
    amountA,  // 10 ETH
    amountB,  // 20,000 USDC
    minA,     // Slippage protection
    minB,     // Slippage protection
    msg.sender,
    deadline
);

// 3. Receive LP tokens representing pool share

LP Token Value:

LP_tokens = sqrt(amountA × amountB)  // For initial deposit
LP_tokens = min(
    (amountA / reserveA) × totalSupply,
    (amountB / reserveB) × totalSupply
)  // For subsequent deposits

3. Multi-Hop Swaps

Example: Swap USDT → USDC (no direct pair)

Route: USDT → ETH → USDC

address[] memory path = new address[](3);
path[0] = USDT;
path[1] = WETH;
path[2] = USDC;

router.swapExactTokensForTokens(
    1000e6,      // 1000 USDT in
    minUSDCOut,  // Minimum USDC out
    path,
    msg.sender,
    deadline
);

Benefits:

  • Access to indirect trading pairs
  • Capital efficiency (fewer pools needed)
  • Automatic route finding

4. Slippage Protection

Why It Matters: Large trades move prices. Protection ensures fair execution.

Implementation:

// Example: Swap with max 1% slippage
uint expectedOut = getAmountOut(amountIn, reserveIn, reserveOut);
uint minOut = expectedOut * 99 / 100;  // 1% slippage tolerance

router.swapExactTokensForTokens(
    amountIn,
    minOut,  // ← Reverts if actual output < minOut
    path,
    msg.sender,
    deadline
);

5. Deterministic Pair Addresses (CREATE2)

Benefits:

// Calculate pair address off-chain
address predictedPair = computePairAddress(tokenA, tokenB);

// Create pair
factory.createPair(tokenA, tokenB);

// Address matches prediction!
assert(factory.getPair(tokenA, tokenB) == predictedPair);

Use Cases:

  • Frontend can display pair info before creation
  • Cross-chain address consistency
  • Reduced RPC calls

🚀 Getting Started

Prerequisites

# Install Foundry
curl -L https://foundry.paradigm.xyz | bash
foundryup

Installation

# Clone repository
git clone https://github.com/Enricrypto/Decentralised-Exchange.git
cd Decentralised-Exchange

# Install dependencies
forge install

# Build contracts
forge build

Testing

# Run all tests
forge test

# Run with verbosity
forge test -vvv

# Run specific test file
forge test --match-path test/Router.t.sol

# Coverage
forge coverage

# Gas report
forge test --gas-report

📖 Usage Examples

Deploy Contracts

// 1. Deploy Factory
Factory factory = new Factory();

// 2. Deploy Router
Router router = new Router(address(factory));

// 3. Create pair
address pairAddress = factory.createPair(tokenA, tokenB);

Add Liquidity (First Time)

// 1. Approve tokens
IERC20(tokenA).approve(address(router), 100 ether);
IERC20(tokenB).approve(address(router), 200000 ether);

// 2. Add liquidity
(uint amountA, uint amountB, uint liquidity) = router.addLiquidity(
    tokenA,
    tokenB,
    100 ether,      // Desired amount A
    200000 ether,   // Desired amount B
    95 ether,       // Min amount A (5% slippage)
    190000 ether,   // Min amount B (5% slippage)
    msg.sender,
    block.timestamp + 300  // 5 min deadline
);

// 3. Receive LP tokens
// liquidity = sqrt(100 * 200000) ≈ 4,472 LP tokens

Swap Tokens (Single Hop)

// Swap 10 tokenA for tokenB
IERC20(tokenA).approve(address(router), 10 ether);

address[] memory path = new address[](2);
path[0] = tokenA;
path[1] = tokenB;

uint[] memory amounts = router.swapExactTokensForTokens(
    10 ether,       // Amount in
    18000 ether,    // Min amount out
    path,
    msg.sender,
    block.timestamp + 300
);

// amounts[0] = 10 ether (amount in)
// amounts[1] = ~19,636 ether (amount out, minus fees)

Swap Tokens (Multi-Hop)

// Swap USDT → WETH → DAI
address[] memory path = new address[](3);
path[0] = USDT;
path[1] = WETH;
path[2] = DAI;

router.swapExactTokensForTokens(
    1000e6,         // 1000 USDT
    minDAIOut,      // Calculated minimum
    path,
    msg.sender,
    deadline
);

Remove Liquidity

// 1. Approve LP tokens
Pair pair = Pair(factory.getPair(tokenA, tokenB));
pair.approve(address(router), lpAmount);

// 2. Remove liquidity
(uint amountA, uint amountB) = router.removeLiquidity(
    tokenA,
    tokenB,
    lpAmount,       // LP tokens to burn
    minAmountA,     // Slippage protection
    minAmountB,     // Slippage protection
    msg.sender,
    deadline
);

// 3. Receive underlying tokens proportional to pool share

🧪 Testing Strategy

Test Coverage

  • Unit Tests: Individual function testing
  • Integration Tests: Multi-contract interactions
  • Fuzz Tests: Random input testing
  • Invariant Tests: Mathematical guarantees
  • Scenario Tests: Real-world user flows

Key Invariants

// 1. Constant Product (after fee)
assert(reserve0 * reserve1 >= k);

// 2. LP Token Supply
assert(totalSupply <= sqrt(reserve0 * reserve1));

// 3. No Value Extraction
assert(userBalanceAfter <= userBalanceBefore + fairOutput);

// 4. Reserve Sync
assert(pair.balance(token0) >= reserve0);
assert(pair.balance(token1) >= reserve1);

📊 Gas Optimizations

Implemented Optimizations

  1. Packed Storage:

    uint112 private reserve0;  // Instead of uint256
    uint112 private reserve1;
    uint32 private blockTimestampLast;
    // All fit in one storage slot
  2. Minimal External Calls:

    • Batch operations where possible
    • Cache frequently accessed values
  3. Efficient Math:

    • Use bit shifts for multiplications by powers of 2
    • Minimize division operations
  4. CREATE2 Deployment:

    • Deterministic addresses eliminate lookups

Gas Benchmarks

Operation Gas Cost (approx)
Create Pair ~2.5M gas
Add Liquidity (first) ~180k gas
Add Liquidity (subsequent) ~130k gas
Swap (single hop) ~90k gas
Swap (multi-hop, 3 pairs) ~250k gas
Remove Liquidity ~120k gas

🔒 Security Features

Implemented Protections

  • Reentrancy Guards on critical functions
  • Deadline Checks prevent stale transactions
  • Slippage Protection on all user operations
  • Overflow Protection (Solidity 0.8+)
  • Minimum Liquidity Lock (first deposit)
  • Balance Verification before swaps
  • Invariant Checks after operations

Attack Vectors Considered

  1. Sandwich Attacks - Mitigated by slippage protection
  2. Flash Loan Attacks - Invariant checks prevent manipulation
  3. Reentrancy - Guards on all state-changing functions
  4. Price Manipulation - Large trades have proportional impact
  5. LP Token Inflation - Minimum liquidity locked forever

Audit Status

⚠️ Not professionally audited. This is an educational/portfolio project. Do not use in production with real funds without a security audit.


📁 Project Structure

decentralised-exchange/
├── src/
│   ├── Factory.sol              # Pair creation & registry
│   ├── Pair.sol                 # Liquidity pool logic
│   ├── Router.sol               # User-facing interface
│   └── interfaces/
│       ├── IFactory.sol
│       ├── IPair.sol
│       └── IRouter.sol
├── test/
│   ├── Factory.t.sol
│   ├── Pair.t.sol
│   ├── Router.t.sol
│   └── Integration.t.sol
├── script/
│   └── Deploy.s.sol
└── README.md

🎓 Learning Resources

Understanding AMMs

  • Constant Product Formula: x × y = k
  • Impermanent Loss: Risk for liquidity providers
  • Arbitrage: Keeps prices aligned with other exchanges
  • Slippage: Price impact of trade size

Recommended Reading


🚧 Roadmap

Phase 1: Core AMM ✅

  • Pair contract with swaps
  • Factory for pair creation
  • Router with multi-hop
  • LP token system

Phase 2: Advanced Features (Planned)

  • Flash swaps (flash loans)
  • Price oracle (TWAP)
  • Fee switch for protocol
  • Concentrated liquidity

Phase 3: Optimization (Planned)

  • Gas optimizations
  • L2 deployment
  • Cross-chain support

🤝 Contributing

Contributions welcome! Please:

  1. Fork the repository
  2. Create feature branch (git checkout -b feature/Enhancement)
  3. Commit changes (git commit -m 'Add Enhancement')
  4. Push to branch (git push origin feature/Enhancement)
  5. Open Pull Request

📝 License

This project is licensed under the MIT License - see the LICENSE file for details.


🙏 Acknowledgments


📧 Contact

GitHub: @Enricrypto

Project Link: https://github.com/Enricrypto/Decentralised-Exchange


⭐ If you find this project useful, please consider giving it a star!

About

A Solidity-based decentralized exchange (DEX) for trustless token swaps and liquidity provision. Includes Factory, Pair, and Router contracts inspired by Uniswap V2. Supports ERC-20 tokens with slippage protection and gas optimisations. A solid foundation for building DeFi apps.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

0