Повернутися до матеріалів

SimpleDEX Stage 1

Етап 1: Базова версія DEX з повним функціоналом (add/remove liquidity + swap)

✅ Код скопійовано в буфер обміну!

⚙️ Функції Stage 1

  • addLiquidity() - додати ліквідність
  • removeLiquidity() - видалити ліквідність
  • swap() - обмін з захистом від slippage
  • Базова AMM формула x*y=k
  • 0.3% комісія

🎯 Ціль етапу

  • 📚 Stage 1: Базовий
  • ~110 рядків коду
  • Повний функціонал DEX
  • 🎯 Безпека: 40%

🔒 Безпека Stage 1

  • ✅ Integer Overflow (Solidity 0.8+)
  • ✅ Front-Running (minAmountOut)
  • ❌ Reentrancy Attack
  • ❌ Price Manipulation
📄 SimpleDEX-Stage1.sol
📊 ~130 рядків
🔧 Solidity 0.8.20
⏱️ Stage 1/3
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

/**
 * @title SimpleDEX - Stage 1: Базова версія
 * @dev Базовий DEX з повним функціоналом але без оптимізацій
 *
 * Етап 1 (20 хвилин): Створюємо базовий робочий DEX
 * - Додавання ліквідності (addLiquidity)
 * - Видалення ліквідності (removeLiquidity)
 * - Обмін токенів (swap з minAmountOut)
 * - ~110 рядків коду
 */
contract SimpleDEX_Stage1 is ERC20 {
    // Токени пари
    IERC20 public immutable token0;
    IERC20 public immutable token1;

    // Резерви токенів
    uint256 public reserve0;
    uint256 public reserve1;

    // Комісія 0.3%
    uint256 public constant FEE_PERCENT = 30;
    uint256 public constant FEE_DENOMINATOR = 10000;

    // Events
    event LiquidityAdded(address indexed provider, uint256 amount0, uint256 amount1, uint256 liquidity);
    event LiquidityRemoved(address indexed provider, uint256 amount0, uint256 amount1, uint256 liquidity);
    event Swap(address indexed user, address indexed tokenIn, uint256 amountIn, uint256 amountOut);

    constructor(address _token0, address _token1) ERC20("SimpleDEX LP", "SDEX-LP") {
        require(_token0 != address(0) && _token1 != address(0), "Invalid tokens");
        require(_token0 != _token1, "Identical tokens");

        token0 = IERC20(_token0);
        token1 = IERC20(_token1);
    }

    /**
     * @dev Додати ліквідність в пул
     */
    function addLiquidity(uint256 amount0, uint256 amount1) external returns (uint256 liquidity) {
        require(amount0 > 0 && amount1 > 0, "Invalid amounts");

        // Переказуємо токени від користувача
        token0.transferFrom(msg.sender, address(this), amount0);
        token1.transferFrom(msg.sender, address(this), amount1);

        // Розраховуємо LP токени
        uint256 totalSupply = totalSupply();

        if (totalSupply == 0) {
            // Перший постачальник ліквідності
            liquidity = sqrt(amount0 * amount1);
        } else {
            // Наступні постачальники
            liquidity = min(
                (amount0 * totalSupply) / reserve0,
                (amount1 * totalSupply) / reserve1
            );
        }

        require(liquidity > 0, "Insufficient liquidity");

        // Мінтимо LP токени
        _mint(msg.sender, liquidity);

        // Оновлюємо резерви
        reserve0 += amount0;
        reserve1 += amount1;

        emit LiquidityAdded(msg.sender, amount0, amount1, liquidity);
    }

    /**
     * @dev Видалити ліквідність з пулу
     */
    function removeLiquidity(uint256 liquidity) external returns (uint256 amount0, uint256 amount1) {
        require(liquidity > 0, "Invalid liquidity");
        require(balanceOf(msg.sender) >= liquidity, "Insufficient LP tokens");

        uint256 _totalSupply = totalSupply();

        // Розраховуємо суми токенів для повернення
        amount0 = (liquidity * reserve0) / _totalSupply;
        amount1 = (liquidity * reserve1) / _totalSupply;

        require(amount0 > 0 && amount1 > 0, "Insufficient amounts");

        // Спалюємо LP токени
        _burn(msg.sender, liquidity);

        // Переказуємо токени користувачу
        token0.transfer(msg.sender, amount0);
        token1.transfer(msg.sender, amount1);

        // Оновлюємо резерви
        reserve0 -= amount0;
        reserve1 -= amount1;

        emit LiquidityRemoved(msg.sender, amount0, amount1, liquidity);
    }

    /**
     * @dev Обміняти токени
     * @param tokenIn Адреса токену для обміну
     * @param amountIn Кількість вхідних токенів
     * @param minAmountOut Мінімальна кількість вихідних токенів (захист від slippage)
     */
    function swap(address tokenIn, uint256 amountIn, uint256 minAmountOut) external returns (uint256 amountOut) {
        require(amountIn > 0, "Invalid amount");
        require(tokenIn == address(token0) || tokenIn == address(token1), "Invalid token");

        // Визначаємо напрямок свапу
        bool isToken0 = tokenIn == address(token0);
        (IERC20 tokenInContract, IERC20 tokenOutContract, uint256 reserveIn, uint256 reserveOut) =
            isToken0 ? (token0, token1, reserve0, reserve1) : (token1, token0, reserve1, reserve0);

        // Переказуємо вхідні токени
        tokenInContract.transferFrom(msg.sender, address(this), amountIn);

        // Розраховуємо вихід з комісією (x*y=k формула)
        uint256 amountInWithFee = amountIn * (FEE_DENOMINATOR - FEE_PERCENT);
        amountOut = (amountInWithFee * reserveOut) / (reserveIn * FEE_DENOMINATOR + amountInWithFee);

        require(amountOut > 0, "Insufficient output");
        require(amountOut >= minAmountOut, "Slippage too high");

        // Переказуємо вихідні токени
        tokenOutContract.transfer(msg.sender, amountOut);

        // Оновлюємо резерви
        if (isToken0) {
            reserve0 += amountIn;
            reserve1 -= amountOut;
        } else {
            reserve1 += amountIn;
            reserve0 -= amountOut;
        }

        emit Swap(msg.sender, tokenIn, amountIn, amountOut);
    }

    // Helper functions
    function sqrt(uint256 y) internal pure returns (uint256 z) {
        if (y > 3) {
            z = y;
            uint256 x = y / 2 + 1;
            while (x < z) {
                z = x;
                x = (y / x + x) / 2;
            }
        } else if (y != 0) {
            z = 1;
        }
    }

    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }
}