🔄 第17课:去中心化交易所

DeFi 阶段四 ✅ 验证通过

🎯 学习目标:理解AMM(自动做市商)核心原理,掌握恒定乘积公式x×y=k,实现简易DEX合约,理解流动性提供、手续费、滑点保护机制。

📖 一、中心化交易所 vs 去中心化交易所

特性CEX(币安等)DEX(Uniswap等)
资金控制交易所控制私钥用户自控私钥
撮合方式订单簿(买卖挂单)AMM(算法定价)
上币门槛审核制,需付费任何人可创建交易对
交易对手其他交易者流动性池
交易速度毫秒级秒级(取决于区块链)
隐私性KYC实名无需KYC
风险交易所跑路/被黑智能合约漏洞/无常损失

📖 二、AMM核心原理

2.1 恒定乘积公式

x × y = k —— 流动性池中两种代币数量的乘积保持恒定。

恒定乘积做市商 (Constant Product AMM) 初始状态: 100 ETH + 200,000 USDC k = 100 × 200,000 = 20,000,000 价格曲线 USDC │ ╲ 200K │ ╲ │ ╲ 150K │ ╲ │ ╲ 100K │ ╲ │ ╲ 50K │ ╲ │ ╲ 0 └──────────────────╲── ETH 0 50 100 150 200 Alice用10 ETH买USDC: 新的ETH数量 = 100 + 10 = 110 新的USDC数量 = k / 110 = 20,000,000 / 110 ≈ 181,818 Alice获得 = 200,000 - 181,818 = 18,182 USDC 平均价格 = 18,182 / 10 = 1,818 USDC/ETH ⚠️ 注意: 初始价格 = 200,000/100 = 2,000 USDC/ETH 实际成交价 = 1,818 USDC/ETH (滑点9.1%)

2.2 滑点与价格影响

滑点 = 实际成交价与预期价的偏差

• 交易量越大 → 滑点越大(价格影响越大)
• 流动性池越大 → 同样交易量的滑点越小
• 这就是为什么TVL(总锁仓量)对DEX至关重要

滑点保护:用户设置最低可接受价格,超出则交易回滚

📖 三、实现简易DEX

// contracts/SimpleDEX.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

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

/**
 * @title SimpleDEX
 * @dev 基于恒定乘积公式的去中心化交易所
 */
contract SimpleDEX {
    using SafeERC20 for IERC20;

    address public immutable tokenA;  // 例如: WETH
    address public immutable tokenB;  // 例如: USDC
    uint256 public reserveA;          // TokenA储备量
    uint256 public reserveB;          // TokenB储备量
    uint256 public feeRate;           // 手续费率(基点,如30=0.3%)
    uint256 public constant FEE_DENOMINATOR = 10000;

    mapping(address => uint256) public liquidity;   // LP份额
    uint256 public totalLiquidity;                          // 总LP份额

    event Swap(address indexed user, address tokenIn, uint256 amountIn, address tokenOut, uint256 amountOut);
    event LiquidityAdded(address indexed provider, uint256 amountA, uint256 amountB, uint256 shares);
    event LiquidityRemoved(address indexed provider, uint256 amountA, uint256 amountB, uint256 shares);

    constructor(address _tokenA, address _tokenB, uint256 _feeRate) {
        require(_tokenA != _tokenB, "Same token");
        require(_feeRate < 1000, "Fee too high"); // 最高10%
        tokenA = _tokenA;
        tokenB = _tokenB;
        feeRate = _feeRate;
    }

    // ═══════ 添加流动性 ═══════
    function addLiquidity(uint256 amountA, uint256 amountB) external returns (uint256 shares) {
        require(amountA > 0 && amountB > 0, "Zero amounts");

        if (totalLiquidity == 0) {
            // 首次添加流动性:份额 = √(amountA × amountB)
            shares = sqrt(amountA * amountB);
        } else {
            // 后续添加:按比例计算份额
            shares = min(
                (amountA * totalLiquidity) / reserveA,
                (amountB * totalLiquidity) / reserveB
            );
        }

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

        // 转入代币
        IERC20(tokenA).safeTransferFrom(msg.sender, address(this), amountA);
        IERC20(tokenB).safeTransferFrom(msg.sender, address(this), amountB);

        // 更新状态
        reserveA += amountA;
        reserveB += amountB;
        liquidity[msg.sender] += shares;
        totalLiquidity += shares;

        emit LiquidityAdded(msg.sender, amountA, amountB, shares);
    }

    // ═══════ 移除流动性 ═══════
    function removeLiquidity(uint256 shares) external returns (uint256 amountA, uint256 amountB) {
        require(shares > 0, "Zero shares");
        require(liquidity[msg.sender] >= shares, "Insufficient shares");

        // 按份额比例计算可取回的代币
        amountA = (shares * reserveA) / totalLiquidity;
        amountB = (shares * reserveB) / totalLiquidity;

        // 更新状态
        liquidity[msg.sender] -= shares;
        totalLiquidity -= shares;
        reserveA -= amountA;
        reserveB -= amountB;

        // 转出代币
        IERC20(tokenA).safeTransfer(msg.sender, amountA);
        IERC20(tokenB).safeTransfer(msg.sender, amountB);

        emit LiquidityRemoved(msg.sender, amountA, amountB, shares);
    }

    // ═══════ 交换代币 ═══════
    function swapAForB(uint256 amountIn, uint256 minAmountOut) external returns (uint256 amountOut) {
        require(amountIn > 0, "Zero input");

        // 计算手续费后的输入
        uint256 amountInWithFee = amountIn * (FEE_DENOMINATOR - feeRate) / FEE_DENOMINATOR;
        
        // 恒定乘积公式: (reserveA + amountInWithFee) * (reserveB - amountOut) = reserveA * reserveB
        amountOut = (reserveB * amountInWithFee) / (reserveA + amountInWithFee);
        
        // 滑点保护
        require(amountOut >= minAmountOut, "Slippage exceeded");

        // 转入tokenA,转出tokenB
        IERC20(tokenA).safeTransferFrom(msg.sender, address(this), amountIn);
        IERC20(tokenB).safeTransfer(msg.sender, amountOut);

        // 更新储备量
        reserveA += amountIn;
        reserveB -= amountOut;

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

    function swapBForA(uint256 amountIn, uint256 minAmountOut) external returns (uint256 amountOut) {
        require(amountIn > 0, "Zero input");

        uint256 amountInWithFee = amountIn * (FEE_DENOMINATOR - feeRate) / FEE_DENOMINATOR;
        amountOut = (reserveA * amountInWithFee) / (reserveB + amountInWithFee);
        require(amountOut >= minAmountOut, "Slippage exceeded");

        IERC20(tokenB).safeTransferFrom(msg.sender, address(this), amountIn);
        IERC20(tokenA).safeTransfer(msg.sender, amountOut);

        reserveB += amountIn;
        reserveA -= amountOut;

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

    // ═══════ 查询函数 ═══════
    function getAmountOut(address tokenIn, uint256 amountIn) external view returns (uint256) {
        uint256 amountInWithFee = amountIn * (FEE_DENOMINATOR - feeRate) / FEE_DENOMINATOR;
        if (tokenIn == tokenA) {
            return (reserveB * amountInWithFee) / (reserveA + amountInWithFee);
        } else {
            return (reserveA * amountInWithFee) / (reserveB + amountInWithFee);
        }
    }

    function getPrice() external view returns (uint256) {
        require(reserveA > 0, "No liquidity");
        return (reserveB * 1e18) / reserveA;  // 价格精度18位小数
    }

    // ═══════ 辅助函数 ═══════
    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;
    }
}

📖 四、无常损失

无常损失(Impermanent Loss):流动性提供者因价格变动而遭受的相对损失。

示例:
• 你存入 1 ETH + 2000 USDC(价格 = $2000)
• ETH涨到 $3000,套利者买入ETH直到池子价格匹配
• 此时池子有 0.816 ETH + 2449 USDC
• 你的LP份额价值 = 0.816 × 3000 + 2449 = $4897
• 如果只是持有:1 × 3000 + 2000 = $5000
• 无常损失 = $5000 - $4897 = $103(约2%)

关键规律:
• 价格变动1.25x → 无常损失0.6%
• 价格变动1.5x → 无常损失2.0%
• 价格变动2x → 无常损失5.7%
• 价格变动3x → 无常损失13.4%
• 价格变动5x → 无常损失25.5%

📖 五、Uniswap演进

版本创新特点
Uniswap V1ETH/ERC20交易对只能与ETH配对
Uniswap V2任意ERC20对ERC20/ERC20直接交易,闪电兑
Uniswap V3集中流动性LP选择价格区间,资本效率提升4000x
Uniswap V4Hooks自定义池逻辑,单一合约管理所有池

🧪 练习

1. AMM中x×y=k的k代表什么? 2. 为什么大额交易会有较大滑点? 3. 无常损失在什么情况下会消失?

📖 八、自动做市商数学深入

// 恒定乘积公式推导
// 初始: x₀ * y₀ = k
// 用Δx换Δy: (x₀ + Δx) * (y₀ - Δy) = k
// Δy = y₀ - k/(x₀ + Δx)
// Δy = y₀ * Δx / (x₀ + Δx)

// 考虑手续费(0.3%):
// 有效输入: Δx * (1 - 0.003) = Δx * 997/1000
// Δy = y₀ * (Δx * 997/1000) / (x₀ + Δx * 997/1000)

// 价格影响(滑点):
// priceImpact = 1 - Δy/Δx / (y₀/x₀)
// = 1 - y₀*x₀ / ((x₀+Δx)*y₀/Δy) ≈ Δx/x₀ (小量近似)

// ✅ 池子越大, 同等交易量滑点越小
// 这就是为什么流动性深度如此重要!
🔄

🏆 成就解锁:流动性提供者

你已掌握去中心化交易所核心原理!从AMM恒定乘积到完整DEX实现,从滑点保护到无常损失,你理解了DeFi交易的基础设施。

关键收获:

✅ AMM恒定乘积公式x×y=k
✅ 流动性添加/移除机制
✅ 交换函数与滑点保护
✅ 无常损失原理与影响
✅ Uniswap V1-V4演进历程

📋 课程目录