去中心化交易所(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 集中流动性的优势
- 资本效率提升:LP可以将资金集中在活跃价格区间
- 更低滑点:在常用价格范围内流动性更深
- 更高收益:同样的资金可以赚取更多手续费
示例:
- 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)。
攻击场景:
- 用户提交大额买单
- MEV机器人检测到
- 机器人抢先买入,推高价格
- 用户以更高价格成交
- 机器人卖出获利
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通过智能合约实现了去中心化交易,核心技术包括:
- AMM算法(恒定乘积、稳定币曲线、集中流动性)
- 流动性管理(LP激励、无常损失)
- 闪电兑换
- 价格预言机(TWAP)
- MEV防护
下一章将对比CEX与DEX的优缺点,并探讨混合交易所(Hybrid Exchange)的设计。