HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 交易所技术完整体系

    • 交易所技术完整体系
    • 交易所技术架构总览
    • 交易基础概念
    • 撮合引擎原理
    • 撮合引擎实现-内存撮合
    • 撮合引擎优化 - 延迟与吞吐
    • 撮合引擎高可用
    • 清算系统设计
    • 风控系统设计
    • 资金管理系统
    • 行情系统设计
    • 去中心化交易所(DEX)设计
    • 合约交易系统
    • 数据库设计与优化
    • 缓存与消息队列
    • 用户系统与KYC
    • 交易所API设计
    • 监控与告警系统
    • 安全防护与攻防
    • 高可用架构设计
    • 压力测试与性能优化
    • 项目实战-完整交易所实现

去中心化交易所(DEX)设计

DeFi Summer期间,去中心化交易所(DEX)快速崛起,Uniswap V3日交易量一度超过Coinbase。DEX不再是边缘产品,而是加密货币交易的重要基础设施。

DEX的设计理念与CEX完全不同——无需撮合引擎,无需订单簿,一切通过智能合约和数学公式自动完成。本章参考Uniswap、SushiSwap、Curve、Balancer等主流DEX的实现,从零开始讲解如何设计一个完整的AMM DEX,涵盖AMM原理、流动性管理、价格预言机、闪电兑换等核心技术。

1. DEX vs CEX

1.1 核心差异

维度CEX (中心化交易所)DEX (去中心化交易所)
资产托管用户资产由交易所托管用户资产在自己钱包
交易方式订单簿 + 撮合引擎自动化做市商(AMM)
信任模型信任交易所信任智能合约
KYC要求必须不需要
交易速度毫秒级秒级(取决于区块链)
Gas费用无每笔交易需支付
流动性做市商提供流动性池
审查抵抗可被审查抗审查

1.2 DEX的优势与劣势

优势:

  • 用户完全控制资产
  • 无需KYC,保护隐私
  • 抗审查,无法被关闭
  • 透明,所有交易上链
  • 任何人都能添加流动性

劣势:

  • 交易速度慢
  • Gas费用高(尤其在以太坊主网)
  • 滑点较大
  • 用户体验复杂
  • 前端运行(Front-running)风险

2. AMM原理

2.1 恒定乘积做市商 (Constant Product Market Maker)

Uniswap V2采用的核心算法:

x * y = k
  • x:代币A的数量
  • y:代币B的数量
  • k:恒定常数

关键特性:

  • 无论如何交易,k值保持不变
  • 价格由供需关系自动决定
  • 不需要订单簿

2.2 Solidity实现

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

contract UniswapV2Pair is ERC20, ReentrancyGuard {
    IERC20 public token0;
    IERC20 public token1;

    uint112 private reserve0;
    uint112 private reserve1;
    uint32 private blockTimestampLast;

    uint public kLast; // reserve0 * reserve1 最近一次更新的值

    event Mint(address indexed sender, uint amount0, uint amount1);
    event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
    event Swap(
        address indexed sender,
        uint amount0In,
        uint amount1In,
        uint amount0Out,
        uint amount1Out,
        address indexed to
    );
    event Sync(uint112 reserve0, uint112 reserve1);

    constructor(address _token0, address _token1) ERC20("Uniswap V2", "UNI-V2") {
        token0 = IERC20(_token0);
        token1 = IERC20(_token1);
    }

    // 获取当前储备量
    function getReserves() public view returns (
        uint112 _reserve0,
        uint112 _reserve1,
        uint32 _blockTimestampLast
    ) {
        _reserve0 = reserve0;
        _reserve1 = reserve1;
        _blockTimestampLast = blockTimestampLast;
    }

    // 更新储备量
    function _update(uint balance0, uint balance1) private {
        require(balance0 <= type(uint112).max && balance1 <= type(uint112).max, "OVERFLOW");

        reserve0 = uint112(balance0);
        reserve1 = uint112(balance1);
        blockTimestampLast = uint32(block.timestamp);

        emit Sync(reserve0, reserve1);
    }

    // 添加流动性
    function mint(address to) external nonReentrant returns (uint liquidity) {
        (uint112 _reserve0, uint112 _reserve1,) = getReserves();

        uint balance0 = token0.balanceOf(address(this));
        uint balance1 = token1.balanceOf(address(this));

        uint amount0 = balance0 - _reserve0;
        uint amount1 = balance1 - _reserve1;

        uint _totalSupply = totalSupply();

        if (_totalSupply == 0) {
            // 首次添加流动性
            liquidity = sqrt(amount0 * amount1);
        } else {
            // 后续添加流动性
            liquidity = min(
                (amount0 * _totalSupply) / _reserve0,
                (amount1 * _totalSupply) / _reserve1
            );
        }

        require(liquidity > 0, "INSUFFICIENT_LIQUIDITY_MINTED");

        _mint(to, liquidity);
        _update(balance0, balance1);

        kLast = uint(reserve0) * uint(reserve1);

        emit Mint(msg.sender, amount0, amount1);
    }

    // 移除流动性
    function burn(address to) external nonReentrant returns (uint amount0, uint amount1) {
        uint balance0 = token0.balanceOf(address(this));
        uint balance1 = token1.balanceOf(address(this));
        uint liquidity = balanceOf(address(this));

        uint _totalSupply = totalSupply();

        // 按比例返还代币
        amount0 = (liquidity * balance0) / _totalSupply;
        amount1 = (liquidity * balance1) / _totalSupply;

        require(amount0 > 0 && amount1 > 0, "INSUFFICIENT_LIQUIDITY_BURNED");

        _burn(address(this), liquidity);

        token0.transfer(to, amount0);
        token1.transfer(to, amount1);

        balance0 = token0.balanceOf(address(this));
        balance1 = token1.balanceOf(address(this));

        _update(balance0, balance1);

        kLast = uint(reserve0) * uint(reserve1);

        emit Burn(msg.sender, amount0, amount1, to);
    }

    // 兑换
    function swap(uint amount0Out, uint amount1Out, address to) external nonReentrant {
        require(amount0Out > 0 || amount1Out > 0, "INSUFFICIENT_OUTPUT_AMOUNT");

        (uint112 _reserve0, uint112 _reserve1,) = getReserves();

        require(amount0Out < _reserve0 && amount1Out < _reserve1, "INSUFFICIENT_LIQUIDITY");

        // 转账
        if (amount0Out > 0) token0.transfer(to, amount0Out);
        if (amount1Out > 0) token1.transfer(to, amount1Out);

        uint balance0 = token0.balanceOf(address(this));
        uint balance1 = token1.balanceOf(address(this));

        uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
        uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;

        require(amount0In > 0 || amount1In > 0, "INSUFFICIENT_INPUT_AMOUNT");

        // 验证 k 值(考虑0.3%手续费)
        {
            uint balance0Adjusted = (balance0 * 1000) - (amount0In * 3);
            uint balance1Adjusted = (balance1 * 1000) - (amount1In * 3);
            require(
                balance0Adjusted * balance1Adjusted >= uint(_reserve0) * uint(_reserve1) * (1000**2),
                "K"
            );
        }

        _update(balance0, balance1);

        emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
    }

    // 计算输出数量
    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pure returns (uint amountOut) {
        require(amountIn > 0, "INSUFFICIENT_INPUT_AMOUNT");
        require(reserveIn > 0 && reserveOut > 0, "INSUFFICIENT_LIQUIDITY");

        // 考虑0.3%手续费
        uint amountInWithFee = amountIn * 997;
        uint numerator = amountInWithFee * reserveOut;
        uint denominator = (reserveIn * 1000) + amountInWithFee;

        amountOut = numerator / denominator;
    }

    // 辅助函数
    function sqrt(uint y) internal pure returns (uint z) {
        if (y > 3) {
            z = y;
            uint x = y / 2 + 1;
            while (x < z) {
                z = x;
                x = (y / x + x) / 2;
            }
        } else if (y != 0) {
            z = 1;
        }
    }

    function min(uint x, uint y) internal pure returns (uint z) {
        z = x < y ? x : y;
    }
}

3. 流动性管理

3.1 流动性提供者(LP)激励

contract LiquidityMining {
    IERC20 public rewardToken; // 奖励代币
    IERC20 public lpToken;     // LP代币

    uint public rewardPerBlock; // 每区块奖励数量
    uint public lastRewardBlock;
    uint public accRewardPerShare; // 累计每份额奖励

    struct UserInfo {
        uint amount;        // 用户质押的LP代币数量
        uint rewardDebt;    // 已领取奖励
    }

    mapping(address => UserInfo) public userInfo;

    event Deposit(address indexed user, uint amount);
    event Withdraw(address indexed user, uint amount);
    event Claim(address indexed user, uint reward);

    constructor(address _rewardToken, address _lpToken, uint _rewardPerBlock) {
        rewardToken = IERC20(_rewardToken);
        lpToken = IERC20(_lpToken);
        rewardPerBlock = _rewardPerBlock;
        lastRewardBlock = block.number;
    }

    // 更新奖励池
    function updatePool() public {
        if (block.number <= lastRewardBlock) {
            return;
        }

        uint lpSupply = lpToken.balanceOf(address(this));
        if (lpSupply == 0) {
            lastRewardBlock = block.number;
            return;
        }

        uint blocks = block.number - lastRewardBlock;
        uint reward = blocks * rewardPerBlock;

        accRewardPerShare += (reward * 1e12) / lpSupply;
        lastRewardBlock = block.number;
    }

    // 质押LP代币
    function deposit(uint _amount) external {
        UserInfo storage user = userInfo[msg.sender];

        updatePool();

        // 领取之前的奖励
        if (user.amount > 0) {
            uint pending = (user.amount * accRewardPerShare) / 1e12 - user.rewardDebt;
            if (pending > 0) {
                rewardToken.transfer(msg.sender, pending);
            }
        }

        lpToken.transferFrom(msg.sender, address(this), _amount);

        user.amount += _amount;
        user.rewardDebt = (user.amount * accRewardPerShare) / 1e12;

        emit Deposit(msg.sender, _amount);
    }

    // 取回LP代币
    function withdraw(uint _amount) external {
        UserInfo storage user = userInfo[msg.sender];
        require(user.amount >= _amount, "INSUFFICIENT_BALANCE");

        updatePool();

        uint pending = (user.amount * accRewardPerShare) / 1e12 - user.rewardDebt;
        if (pending > 0) {
            rewardToken.transfer(msg.sender, pending);
        }

        user.amount -= _amount;
        user.rewardDebt = (user.amount * accRewardPerShare) / 1e12;

        lpToken.transfer(msg.sender, _amount);

        emit Withdraw(msg.sender, _amount);
    }

    // 查询待领取奖励
    function pendingReward(address _user) external view returns (uint) {
        UserInfo storage user = userInfo[_user];

        uint _accRewardPerShare = accRewardPerShare;
        uint lpSupply = lpToken.balanceOf(address(this));

        if (block.number > lastRewardBlock && lpSupply != 0) {
            uint blocks = block.number - lastRewardBlock;
            uint reward = blocks * rewardPerBlock;
            _accRewardPerShare += (reward * 1e12) / lpSupply;
        }

        return (user.amount * _accRewardPerShare) / 1e12 - user.rewardDebt;
    }
}

3.2 无常损失 (Impermanent Loss)

当LP提供流动性后,如果价格变化,可能遭受无常损失。

计算公式:

假设初始提供1 ETH + 100 USDT(价格1 ETH = 100 USDT),k = 1 * 100 = 100

如果ETH价格涨到400 USDT:

  • 新的储备量:x * y = 100,且 y/x = 400
  • 解得:x = 0.5 ETH, y = 200 USDT
  • LP资产价值:0.5 * 400 + 200 = 400 USDT
  • 如果不提供流动性:1 * 400 + 100 = 500 USDT
  • 无常损失:(400 - 500) / 500 = -20%
// Go语言计算无常损失
package main

import (
	"fmt"
	"math"
)

func calculateImpermanentLoss(priceChange float64) float64 {
	// priceChange: 价格变化倍数,如2表示价格翻倍

	// IL = 2 * sqrt(priceChange) / (1 + priceChange) - 1
	il := 2*math.Sqrt(priceChange)/(1+priceChange) - 1

	return il * 100 // 转换为百分比
}

func main() {
	priceChanges := []float64{1.25, 1.5, 2, 3, 4, 5}

	fmt.Println("Price Change | Impermanent Loss")
	fmt.Println("---------------------------------")

	for _, pc := range priceChanges {
		il := calculateImpermanentLoss(pc)
		fmt.Printf("%.2fx        | %.2f%%\n", pc, il)
	}
}

// 输出:
// Price Change | Impermanent Loss
// ---------------------------------
// 1.25x        | -0.60%
// 1.50x        | -2.02%
// 2.00x        | -5.72%
// 3.00x        | -13.40%
// 4.00x        | -20.00%
// 5.00x        | -25.46%

4. Uniswap V3:集中流动性

Uniswap V3引入了集中流动性(Concentrated Liquidity),LP可以在特定价格区间提供流动性。

4.1 核心概念

contract UniswapV3Pool {
    // 价格刻度(Tick)
    int24 public tick;

    // 每个Tick的流动性信息
    struct TickInfo {
        uint128 liquidityGross;  // 总流动性
        int128 liquidityNet;     // 净流动性变化
        uint256 feeGrowthOutside0; // 手续费累计
        uint256 feeGrowthOutside1;
    }

    mapping(int24 => TickInfo) public ticks;

    // 用户仓位
    struct Position {
        uint128 liquidity;      // 流动性数量
        int24 tickLower;        // 价格下限
        int24 tickUpper;        // 价格上限
        uint256 feeGrowthInside0Last;
        uint256 feeGrowthInside1Last;
    }

    mapping(bytes32 => Position) public positions;

    // 添加流动性到指定价格区间
    function mint(
        address recipient,
        int24 tickLower,
        int24 tickUpper,
        uint128 amount
    ) external returns (uint256 amount0, uint256 amount1) {
        require(tickLower < tickUpper, "INVALID_TICKS");
        require(amount > 0, "ZERO_LIQUIDITY");

        // 计算需要的代币数量
        (amount0, amount1) = _getAmountsForLiquidity(
            tickLower,
            tickUpper,
            amount
        );

        // 更新Tick信息
        _updateTick(tickLower, amount, false);
        _updateTick(tickUpper, amount, true);

        // 记录仓位
        bytes32 positionKey = keccak256(abi.encodePacked(recipient, tickLower, tickUpper));
        Position storage position = positions[positionKey];
        position.liquidity += amount;
        position.tickLower = tickLower;
        position.tickUpper = tickUpper;

        // 转入代币
        if (amount0 > 0) token0.transferFrom(msg.sender, address(this), amount0);
        if (amount1 > 0) token1.transferFrom(msg.sender, address(this), amount1);
    }

    // 计算价格区间内的代币数量
    function _getAmountsForLiquidity(
        int24 tickLower,
        int24 tickUpper,
        uint128 liquidity
    ) private view returns (uint256 amount0, uint256 amount1) {
        uint160 sqrtPriceLower = _getSqrtRatioAtTick(tickLower);
        uint160 sqrtPriceUpper = _getSqrtRatioAtTick(tickUpper);
        uint160 sqrtPriceCurrent = _getSqrtRatioAtTick(tick);

        if (sqrtPriceCurrent <= sqrtPriceLower) {
            // 当前价格低于区间,全部是token0
            amount0 = _getAmount0Delta(sqrtPriceLower, sqrtPriceUpper, liquidity);
        } else if (sqrtPriceCurrent < sqrtPriceUpper) {
            // 当前价格在区间内
            amount0 = _getAmount0Delta(sqrtPriceCurrent, sqrtPriceUpper, liquidity);
            amount1 = _getAmount1Delta(sqrtPriceLower, sqrtPriceCurrent, liquidity);
        } else {
            // 当前价格高于区间,全部是token1
            amount1 = _getAmount1Delta(sqrtPriceLower, sqrtPriceUpper, liquidity);
        }
    }

    function _getSqrtRatioAtTick(int24 tick) private pure returns (uint160) {
        // 复杂的数学计算,将tick转换为sqrtPrice
        // 详见Uniswap V3白皮书
    }
}

4.2 集中流动性的优势

  1. 资本效率提升:LP可以将资金集中在活跃价格区间
  2. 更低滑点:在常用价格范围内流动性更深
  3. 更高收益:同样的资金可以赚取更多手续费

示例:

  • Uniswap V2:在0到∞的价格区间提供流动性
  • Uniswap V3:在$1,900-$2,100的区间提供ETH/USDT流动性

如果ETH价格在$2,000附近波动,V3的LP能赚取更多手续费。

5. 闪电兑换 (Flash Swap)

Uniswap允许用户先获得代币,稍后再支付,只要在同一笔交易中完成。

5.1 实现

// 闪电兑换接口
interface IUniswapV2Callee {
    function uniswapV2Call(
        address sender,
        uint amount0,
        uint amount1,
        bytes calldata data
    ) external;
}

// 在UniswapV2Pair中
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external {
    // ... 前面的检查

    // 先转出代币
    if (amount0Out > 0) token0.transfer(to, amount0Out);
    if (amount1Out > 0) token1.transfer(to, amount1Out);

    // 如果有回调数据,执行闪电兑换回调
    if (data.length > 0) {
        IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
    }

    // 检查余额,确保代币已归还
    uint balance0 = token0.balanceOf(address(this));
    uint balance1 = token1.balanceOf(address(this));

    uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
    uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;

    require(amount0In > 0 || amount1In > 0, "INSUFFICIENT_INPUT_AMOUNT");

    // 验证k值
    // ...
}

5.2 闪电兑换套利示例

contract FlashSwapArbitrage is IUniswapV2Callee {
    IUniswapV2Pair public uniswapPair;
    IUniswapV2Pair public sushiswapPair;

    IERC20 public token0;
    IERC20 public token1;

    // 套利函数
    function arbitrage(uint amount) external {
        // 从Uniswap闪电兑换借出token0
        bytes memory data = abi.encode(amount);
        uniswapPair.swap(amount, 0, address(this), data);
    }

    // Uniswap回调
    function uniswapV2Call(
        address sender,
        uint amount0,
        uint amount1,
        bytes calldata data
    ) external override {
        require(msg.sender == address(uniswapPair), "UNAUTHORIZED");

        // 1. 收到了amount0的token0

        // 2. 在Sushiswap上卖出token0,买入token1
        token0.transfer(address(sushiswapPair), amount0);
        (uint reserve0, uint reserve1,) = sushiswapPair.getReserves();
        uint amount1Out = getAmountOut(amount0, reserve0, reserve1);
        sushiswapPair.swap(0, amount1Out, address(this), "");

        // 3. 在Uniswap上卖出token1,买回token0
        token1.transfer(address(uniswapPair), amount1Out);
        // ... 计算需要归还的token0数量 + 手续费
        uint amountToRepay = amount0 * 1000 / 997 + 1; // 加上0.3%手续费

        // 4. 如果有盈利,转给调用者
        uint profit = token0.balanceOf(address(this)) - amountToRepay;
        if (profit > 0) {
            token0.transfer(sender, profit);
        }

        // 5. 归还借款(已在步骤3完成)
    }

    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint) {
        uint amountInWithFee = amountIn * 997;
        uint numerator = amountInWithFee * reserveOut;
        uint denominator = reserveIn * 1000 + amountInWithFee;
        return numerator / denominator;
    }
}

6. 价格预言机

6.1 时间加权平均价格 (TWAP)

Uniswap V2内置TWAP预言机,防止价格操纵。

contract UniswapV2Oracle {
    IUniswapV2Pair public pair;

    uint public price0CumulativeLast;
    uint public price1CumulativeLast;
    uint32 public blockTimestampLast;

    uint public price0Average;
    uint public price1Average;

    constructor(address _pair) {
        pair = IUniswapV2Pair(_pair);

        price0CumulativeLast = pair.price0CumulativeLast();
        price1CumulativeLast = pair.price1CumulativeLast();

        (,, blockTimestampLast) = pair.getReserves();
    }

    // 更新价格
    function update() external {
        (uint112 reserve0, uint112 reserve1, uint32 blockTimestamp) = pair.getReserves();

        uint32 timeElapsed = blockTimestamp - blockTimestampLast;

        if (timeElapsed > 0 && reserve0 != 0 && reserve1 != 0) {
            // 累计价格
            price0Average = (pair.price0CumulativeLast() - price0CumulativeLast) / timeElapsed;
            price1Average = (pair.price1CumulativeLast() - price1CumulativeLast) / timeElapsed;

            // 更新状态
            price0CumulativeLast = pair.price0CumulativeLast();
            price1CumulativeLast = pair.price1CumulativeLast();
            blockTimestampLast = blockTimestamp;
        }
    }

    // 获取平均价格
    function consult(address token, uint amountIn) external view returns (uint amountOut) {
        if (token == pair.token0()) {
            amountOut = (amountIn * price0Average) >> 112;
        } else {
            amountOut = (amountIn * price1Average) >> 112;
        }
    }
}

7. 前端运行(MEV)防护

7.1 问题说明

在以太坊上,交易在被打包前会在mempool中公开。MEV机器人可以看到你的交易,并抢先执行(Front-Running)。

攻击场景:

  1. 用户提交大额买单
  2. MEV机器人检测到
  3. 机器人抢先买入,推高价格
  4. 用户以更高价格成交
  5. 机器人卖出获利

7.2 防护措施

contract PrivateSwap {
    // 使用Flashbots等私有交易池
    // 或设置滑点保护

    function swapWithSlippageProtection(
        address tokenIn,
        address tokenOut,
        uint amountIn,
        uint minAmountOut, // 最小输出数量
        uint deadline
    ) external {
        require(block.timestamp <= deadline, "EXPIRED");

        // ... 执行兑换

        require(amountOut >= minAmountOut, "SLIPPAGE_TOO_HIGH");
    }
}

小结

DEX通过智能合约实现了去中心化交易,核心技术包括:

  1. AMM算法(恒定乘积、稳定币曲线、集中流动性)
  2. 流动性管理(LP激励、无常损失)
  3. 闪电兑换
  4. 价格预言机(TWAP)
  5. MEV防护

下一章将对比CEX与DEX的优缺点,并探讨混合交易所(Hybrid Exchange)的设计。

Prev
行情系统设计
Next
合约交易系统