去中心化交易所 #
一、DEX概述 #
1.1 AMM机制 #
自动做市商(AMM)使用数学公式确定价格,无需订单簿。
text
恒定乘积公式:x * y = k
x: 代币A数量
y: 代币B数量
k: 恒定乘积
1.2 核心概念 #
| 概念 | 说明 |
|---|---|
| 流动性池 | 代币对的储备池 |
| LP代币 | 流动性提供者凭证 |
| 手续费 | 交易手续费 |
| 价格滑点 | 价格变化幅度 |
二、简单DEX实现 #
2.1 基础合约 #
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract SimpleDEX is ERC20 {
IERC20 public token0;
IERC20 public token1;
uint256 public constant FEE_NUMERATOR = 997;
uint256 public constant FEE_DENOMINATOR = 1000;
constructor(address _token0, address _token1) ERC20("LP Token", "LP") {
token0 = IERC20(_token0);
token1 = IERC20(_token1);
}
function addLiquidity(
uint256 amount0,
uint256 amount1
) public returns (uint256 liquidity) {
token0.transferFrom(msg.sender, address(this), amount0);
token1.transferFrom(msg.sender, address(this), amount1);
uint256 totalSupply_ = totalSupply();
if (totalSupply_ == 0) {
liquidity = sqrt(amount0 * amount1);
} else {
liquidity = min(
(amount0 * totalSupply_) / token0.balanceOf(address(this)),
(amount1 * totalSupply_) / token1.balanceOf(address(this))
);
}
require(liquidity > 0, "Insufficient liquidity");
_mint(msg.sender, liquidity);
}
function removeLiquidity(uint256 liquidity) public returns (uint256 amount0, uint256 amount1) {
uint256 totalSupply_ = totalSupply();
amount0 = (liquidity * token0.balanceOf(address(this))) / totalSupply_;
amount1 = (liquidity * token1.balanceOf(address(this))) / totalSupply_;
_burn(msg.sender, liquidity);
token0.transfer(msg.sender, amount0);
token1.transfer(msg.sender, amount1);
}
function swap(
address tokenIn,
uint256 amountIn,
uint256 amountOutMin
) public returns (uint256 amountOut) {
require(tokenIn == address(token0) || tokenIn == address(token1), "Invalid token");
IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
bool isToken0 = tokenIn == address(token0);
(IERC20 tokenIn_, IERC20 tokenOut_) = isToken0
? (token0, token1)
: (token1, token0);
uint256 reserveIn = tokenIn_.balanceOf(address(this));
uint256 reserveOut = tokenOut_.balanceOf(address(this));
amountOut = getAmountOut(amountIn, reserveIn, reserveOut);
require(amountOut >= amountOutMin, "Slippage too high");
tokenOut_.transfer(msg.sender, amountOut);
}
function getAmountOut(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) public pure returns (uint256) {
uint256 amountInWithFee = (amountIn * FEE_NUMERATOR) / FEE_DENOMINATOR;
return (amountInWithFee * reserveOut) / (reserveIn + amountInWithFee);
}
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;
}
}
三、价格计算 #
3.1 恒定乘积公式 #
solidity
// x * y = k
// 价格 = y / x
function getPrice(
uint256 reserveIn,
uint256 reserveOut
) public pure returns (uint256) {
return (reserveOut * 1e18) / reserveIn;
}
3.2 滑点计算 #
solidity
function calculateSlippage(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) public pure returns (uint256) {
uint256 amountOut = getAmountOut(amountIn, reserveIn, reserveOut);
uint256 spotPrice = reserveOut / reserveIn;
uint256 effectivePrice = amountOut / amountIn;
return ((spotPrice - effectivePrice) * 100) / spotPrice;
}
四、最佳实践 #
4.1 安全考虑 #
| 考虑点 | 说明 |
|---|---|
| 重入保护 | 使用ReentrancyGuard |
| 滑点保护 | 设置最小输出 |
| 价格操纵 | 使用TWAP预言机 |
| 闪电贷攻击 | 实施保护措施 |
4.2 Gas优化 #
solidity
// 使用缓存减少存储读取
function swapOptimized(
address tokenIn,
uint256 amountIn,
uint256 amountOutMin
) public returns (uint256 amountOut) {
uint256 reserve0 = token0.balanceOf(address(this));
uint256 reserve1 = token1.balanceOf(address(this));
// 使用缓存的值进行计算
// ...
}
五、总结 #
DEX要点:
| 功能 | 说明 |
|---|---|
| AMM | 自动做市商机制 |
| 流动性 | 添加和移除流动性 |
| 交易 | 代币交换 |
| 价格 | 恒定乘积公式 |
恭喜你完成了Solidity智能合约完全指南的学习!
最后更新:2026-03-27