DeFi 阶段四 ✅ 验证通过
| 特性 | CEX(币安等) | DEX(Uniswap等) |
|---|---|---|
| 资金控制 | 交易所控制私钥 | 用户自控私钥 |
| 撮合方式 | 订单簿(买卖挂单) | AMM(算法定价) |
| 上币门槛 | 审核制,需付费 | 任何人可创建交易对 |
| 交易对手 | 其他交易者 | 流动性池 |
| 交易速度 | 毫秒级 | 秒级(取决于区块链) |
| 隐私性 | KYC实名 | 无需KYC |
| 风险 | 交易所跑路/被黑 | 智能合约漏洞/无常损失 |
x × y = k —— 流动性池中两种代币数量的乘积保持恒定。
// 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;
}
}
| 版本 | 创新 | 特点 |
|---|---|---|
| Uniswap V1 | ETH/ERC20交易对 | 只能与ETH配对 |
| Uniswap V2 | 任意ERC20对 | ERC20/ERC20直接交易,闪电兑 |
| Uniswap V3 | 集中流动性 | LP选择价格区间,资本效率提升4000x |
| Uniswap V4 | Hooks | 自定义池逻辑,单一合约管理所有池 |
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演进历程