Solidity 中如何实现一个去中心化交易所(DEX)的核心功能?
去中心化交易所(DEX)是 DeFi 生态的核心组件。实现一个基础的 DEX 需要理解自动做市商(AMM)机制、流动性池、价格计算等核心概念。1. DEX 核心概念/*DEX 核心组件:1. 流动性池(Liquidity Pool) - 包含两种代币的储备 - 使用恒定乘积公式:x * y = k2. 自动做市商(AMM) - 无需订单簿 - 算法自动定价 - 流动性提供者赚取手续费3. 价格计算 - 基于储备比例 - 考虑滑点和手续费4. 流动性代币(LP Token) - 代表流动性份额 - 可赎回底层资产*/2. 基础 AMM 实现contract BasicAMM { // 代币接口 IERC20 public token0; IERC20 public token1; // 储备量 uint256 public reserve0; uint256 public reserve1; // 流动性代币总量 uint256 public totalSupply; mapping(address => uint256) public balanceOf; // 手续费(0.3% = 30 / 10000) uint256 public constant FEE = 30; uint256 public constant FEE_DENOMINATOR = 10000; // 最小流动性(防止除零) uint256 public constant MINIMUM_LIQUIDITY = 1000; // 事件 event Mint(address indexed sender, uint256 amount0, uint256 amount1); event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to); event Swap( address indexed sender, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out, address indexed to ); event Sync(uint256 reserve0, uint256 reserve1); constructor(address _token0, address _token1) { token0 = IERC20(_token0); token1 = IERC20(_token1); } // 添加流动性 function addLiquidity( uint256 _amount0Desired, uint256 _amount1Desired, uint256 _amount0Min, uint256 _amount1Min, address _to ) external returns (uint256 liquidity) { // 计算需要添加的数量 (uint256 amount0, uint256 amount1) = _calculateLiquidity( _amount0Desired, _amount1Desired, _amount0Min, _amount1Min ); // 转账 token0.transferFrom(msg.sender, address(this), amount0); token1.transferFrom(msg.sender, address(this), amount1); // 计算流动性代币 if (totalSupply == 0) { liquidity = sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY; _mint(address(0), MINIMUM_LIQUIDITY); // 永久锁定 } else { liquidity = min( (amount0 * totalSupply) / reserve0, (amount1 * totalSupply) / reserve1 ); } require(liquidity > 0, "Insufficient liquidity minted"); _mint(_to, liquidity); // 更新储备 _updateReserves(); emit Mint(msg.sender, amount0, amount1); } // 移除流动性 function removeLiquidity( uint256 _liquidity, uint256 _amount0Min, uint256 _amount1Min, address _to ) external returns (uint256 amount0, uint256 amount1) { // 计算可赎回的数量 uint256 balance0 = token0.balanceOf(address(this)); uint256 balance1 = token1.balanceOf(address(this)); amount0 = (_liquidity * balance0) / totalSupply; amount1 = (_liquidity * balance1) / totalSupply; require(amount0 >= _amount0Min, "Insufficient amount0"); require(amount1 >= _amount1Min, "Insufficient amount1"); // 销毁流动性代币 _burn(msg.sender, _liquidity); // 转账 token0.transfer(_to, amount0); token1.transfer(_to, amount1); // 更新储备 _updateReserves(); emit Burn(msg.sender, amount0, amount1, _to); } // 交换代币(token0 -> token1) function swap0For1( uint256 _amount0In, uint256 _amount1OutMin, address _to ) external returns (uint256 amount1Out) { require(_amount0In > 0, "Insufficient input amount"); // 计算输出数量(考虑手续费) amount1Out = getAmountOut(_amount0In, reserve0, reserve1); require(amount1Out >= _amount1OutMin, "Insufficient output amount"); // 转账 token0.transferFrom(msg.sender, address(this), _amount0In); token1.transfer(_to, amount1Out); // 更新储备 _updateReserves(); emit Swap(msg.sender, _amount0In, 0, 0, amount1Out, _to); } // 交换代币(token1 -> token0) function swap1For0( uint256 _amount1In, uint256 _amount0OutMin, address _to ) external returns (uint256 amount0Out) { require(_amount1In > 0, "Insufficient input amount"); amount0Out = getAmountOut(_amount1In, reserve1, reserve0); require(amount0Out >= _amount0OutMin, "Insufficient output amount"); token1.transferFrom(msg.sender, address(this), _amount1In); token0.transfer(_to, amount0Out); _updateReserves(); emit Swap(msg.sender, 0, _amount1In, amount0Out, 0, _to); } // 计算输出数量(恒定乘积公式) function getAmountOut( uint256 _amountIn, uint256 _reserveIn, uint256 _reserveOut ) public pure returns (uint256 amountOut) { require(_amountIn > 0, "Insufficient input amount"); require(_reserveIn > 0 && _reserveOut > 0, "Insufficient liquidity"); uint256 amountInWithFee = _amountIn * (FEE_DENOMINATOR - FEE); uint256 numerator = amountInWithFee * _reserveOut; uint256 denominator = (_reserveIn * FEE_DENOMINATOR) + amountInWithFee; amountOut = numerator / denominator; } // 计算输入数量 function getAmountIn( uint256 _amountOut, uint256 _reserveIn, uint256 _reserveOut ) public pure returns (uint256 amountIn) { require(_amountOut > 0, "Insufficient output amount"); require(_reserveIn > 0 && _reserveOut > 0, "Insufficient liquidity"); uint256 numerator = _reserveIn * _amountOut * FEE_DENOMINATOR; uint256 denominator = (_reserveOut - _amountOut) * (FEE_DENOMINATOR - FEE); amountIn = (numerator / denominator) + 1; } // 计算流动性 function _calculateLiquidity( uint256 _amount0Desired, uint256 _amount1Desired, uint256 _amount0Min, uint256 _amount1Min ) internal view returns (uint256 amount0, uint256 amount1) { if (reserve0 == 0 && reserve1 == 0) { // 首次添加流动性 (amount0, amount1) = (_amount0Desired, _amount1Desired); } else { // 按比例添加 uint256 amount1Optimal = (_amount0Desired * reserve1) / reserve0; if (amount1Optimal <= _amount1Desired) { require(amount1Optimal >= _amount1Min, "Insufficient amount1"); (amount0, amount1) = (_amount0Desired, amount1Optimal); } else { uint256 amount0Optimal = (_amount1Desired * reserve0) / reserve1; assert(amount0Optimal <= _amount0Desired); require(amount0Optimal >= _amount0Min, "Insufficient amount0"); (amount0, amount1) = (amount0Optimal, _amount1Desired); } } } // 更新储备 function _updateReserves() internal { reserve0 = token0.balanceOf(address(this)); reserve1 = token1.balanceOf(address(this)); emit Sync(reserve0, reserve1); } // 流动性代币操作 function _mint(address _to, uint256 _amount) internal { totalSupply += _amount; balanceOf[_to] += _amount; } function _burn(address _from, uint256 _amount) internal { balanceOf[_from] -= _amount; totalSupply -= _amount; } // 数学函数 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; }}interface IERC20 { function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); function transfer(address to, uint256 amount) external returns (bool); function transferFrom(address from, address to, uint256 amount) external returns (bool);}3. 路由合约contract Router { address public factory; constructor(address _factory) { factory = _factory; } // 添加流动性 function addLiquidity( address _tokenA, address _tokenB, uint256 _amountADesired, uint256 _amountBDesired, uint256 _amountAMin, uint256 _amountBMin, address _to, uint256 _deadline ) external returns (uint256 amountA, uint256 amountB, uint256 liquidity) { require(block.timestamp <= _deadline, "Expired"); // 获取或创建交易对 address pair = Factory(factory).getPair(_tokenA, _tokenB); if (pair == address(0)) { pair = Factory(factory).createPair(_tokenA, _tokenB); } // 转账代币到交易对 IERC20(_tokenA).transferFrom(msg.sender, pair, _amountADesired); IERC20(_tokenB).transferFrom(msg.sender, pair, _amountBDesired); // 添加流动性 liquidity = BasicAMM(pair).addLiquidity( _amountADesired, _amountBDesired, _amountAMin, _amountBMin, _to ); // 返回实际添加的数量 amountA = _amountADesired; amountB = _amountBDesired; } // 移除流动性 function removeLiquidity( address _tokenA, address _tokenB, uint256 _liquidity, uint256 _amountAMin, uint256 _amountBMin, address _to, uint256 _deadline ) external returns (uint256 amountA, uint256 amountB) { require(block.timestamp <= _deadline, "Expired"); address pair = Factory(factory).getPair(_tokenA, _tokenB); require(pair != address(0), "Pair does not exist"); // 转账 LP 代币到交易对 IERC20(pair).transferFrom(msg.sender, pair, _liquidity); // 移除流动性 (amountA, amountB) = BasicAMM(pair).removeLiquidity( _liquidity, _amountAMin, _amountBMin, _to ); } // 交换代币(精确输入) function swapExactTokensForTokens( uint256 _amountIn, uint256 _amountOutMin, address[] calldata _path, address _to, uint256 _deadline ) external returns (uint256[] memory amounts) { require(block.timestamp <= _deadline, "Expired"); require(_path.length >= 2, "Invalid path"); amounts = getAmountsOut(_amountIn, _path); require(amounts[amounts.length - 1] >= _amountOutMin, "Insufficient output"); // 转账第一个代币到第一个交易对 IERC20(_path[0]).transferFrom( msg.sender, Factory(factory).getPair(_path[0], _path[1]), amounts[0] ); // 执行交换 _swap(amounts, _path, _to); } // 交换代币(精确输出) function swapTokensForExactTokens( uint256 _amountOut, uint256 _amountInMax, address[] calldata _path, address _to, uint256 _deadline ) external returns (uint256[] memory amounts) { require(block.timestamp <= _deadline, "Expired"); require(_path.length >= 2, "Invalid path"); amounts = getAmountsIn(_amountOut, _path); require(amounts[0] <= _amountInMax, "Excessive input"); IERC20(_path[0]).transferFrom( msg.sender, Factory(factory).getPair(_path[0], _path[1]), amounts[0] ); _swap(amounts, _path, _to); } // 内部交换函数 function _swap( uint256[] memory _amounts, address[] memory _path, address _to ) internal { for (uint i = 0; i < _path.length - 1; i++) { (address input, address output) = (_path[i], _path[i + 1]); address pair = Factory(factory).getPair(input, output); uint256 amountOut = _amounts[i + 1]; (uint256 amount0Out, uint256 amount1Out) = input < output ? (uint256(0), amountOut) : (amountOut, uint256(0)); address to = i < _path.length - 2 ? Factory(factory).getPair(output, _path[i + 2]) : _to; BasicAMM(pair).swap(amount0Out, amount1Out, to); } } // 计算输出数量(多跳) function getAmountsOut( uint256 _amountIn, address[] memory _path ) public view returns (uint256[] memory amounts) { require(_path.length >= 2, "Invalid path"); amounts = new uint256[](_path.length); amounts[0] = _amountIn; for (uint i = 0; i < _path.length - 1; i++) { address pair = Factory(factory).getPair(_path[i], _path[i + 1]); require(pair != address(0), "Pair does not exist"); (uint256 reserveIn, uint256 reserveOut) = getReserves(pair, _path[i], _path[i + 1]); amounts[i + 1] = BasicAMM(pair).getAmountOut(amounts[i], reserveIn, reserveOut); } } // 计算输入数量(多跳) function getAmountsIn( uint256 _amountOut, address[] memory _path ) public view returns (uint256[] memory amounts) { require(_path.length >= 2, "Invalid path"); amounts = new uint256[](_path.length); amounts[amounts.length - 1] = _amountOut; for (uint i = _path.length - 1; i > 0; i--) { address pair = Factory(factory).getPair(_path[i - 1], _path[i]); require(pair != address(0), "Pair does not exist"); (uint256 reserveIn, uint256 reserveOut) = getReserves(pair, _path[i - 1], _path[i]); amounts[i - 1] = BasicAMM(pair).getAmountIn(amounts[i], reserveIn, reserveOut); } } // 获取储备 function getReserves( address _pair, address _tokenA, address _tokenB ) internal view returns (uint256 reserveA, uint256 reserveB) { (uint256 reserve0, uint256 reserve1) = BasicAMM(_pair).getReserves(); (reserveA, reserveB) = _tokenA < _tokenB ? (reserve0, reserve1) : (reserve1, reserve0); }}// 工厂合约contract Factory { mapping(address => mapping(address => address)) public getPair; address[] public allPairs; event PairCreated(address indexed token0, address indexed token1, address pair, uint256); function createPair(address _tokenA, address _tokenB) external returns (address pair) { require(_tokenA != _tokenB, "Identical addresses"); (address token0, address token1) = _tokenA < _tokenB ? (_tokenA, _tokenB) : (_tokenB, _tokenA); require(token0 != address(0), "Zero address"); require(getPair[token0][token1] == address(0), "Pair exists"); // 创建交易对合约(简化版,实际使用 create2) bytes memory bytecode = type(BasicAMM).creationCode; bytes32 salt = keccak256(abi.encodePacked(token0, token1)); assembly { pair := create2(0, add(bytecode, 32), mload(bytecode), salt) } BasicAMM(pair).initialize(token0, token1); getPair[token0][token1] = pair; getPair[token1][token0] = pair; allPairs.push(pair); emit PairCreated(token0, token1, pair, allPairs.length); }}4. 价格预言机contract PriceOracle { // 累积价格 uint256 public price0CumulativeLast; uint256 public price1CumulativeLast; // 上次更新时间 uint32 public blockTimestampLast; // 储备量 uint112 public reserve0; uint112 public reserve1; // 更新累积价格 function _update( uint256 _balance0, uint256 _balance1, uint112 _reserve0, uint112 _reserve1 ) internal { require( _balance0 <= type(uint112).max && _balance1 <= type(uint112).max, "Overflow" ); uint32 blockTimestamp = uint32(block.timestamp % 2**32); uint32 timeElapsed = blockTimestamp - blockTimestampLast; if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) { // 累积价格 = 价格 * 时间 price0CumulativeLast += uint256(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed; price1CumulativeLast += uint256(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed; } reserve0 = uint112(_balance0); reserve1 = uint112(_balance1); blockTimestampLast = blockTimestamp; } // 获取当前价格 function getCurrentPrice() external view returns (uint256 price0, uint256 price1) { price0 = (uint256(reserve1) * 1e18) / reserve0; price1 = (uint256(reserve0) * 1e18) / reserve1; } // 计算 TWAP(时间加权平均价格) function consult( address _token, uint256 _amountIn ) external view returns (uint256 amountOut) { // 简化的 TWAP 计算 if (_token == token0) { amountOut = (_amountIn * reserve1) / reserve0; } else { amountOut = (_amountIn * reserve0) / reserve1; } }}// UQ112x112 库(定点数运算)library UQ112x112 { uint224 constant Q112 = 2**112; function encode(uint112 y) internal pure returns (uint224 z) { z = uint224(y) * Q112; } function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) { z = x / uint224(y); }}5. 闪电贷功能contract FlashSwap { // 闪电贷回调接口 interface IFlashSwapCallee { function uniswapV2Call( address sender, uint256 amount0, uint256 amount1, bytes calldata data ) external; } // 执行闪电贷 function swap( uint256 _amount0Out, uint256 _amount1Out, address _to, bytes calldata _data ) external { require(_amount0Out > 0 || _amount1Out > 0, "Insufficient output"); // 转账给接收者 if (_amount0Out > 0) token0.transfer(_to, _amount0Out); if (_amount1Out > 0) token1.transfer(_to, _amount1Out); // 如果有数据,执行回调 if (_data.length > 0) { IFlashSwapCallee(_to).uniswapV2Call( msg.sender, _amount0Out, _amount1Out, _data ); } // 验证偿还 uint256 balance0 = token0.balanceOf(address(this)); uint256 balance1 = token1.balanceOf(address(this)); uint256 amount0In = balance0 > reserve0 - _amount0Out ? balance0 - (reserve0 - _amount0Out) : 0; uint256 amount1In = balance1 > reserve1 - _amount1Out ? balance1 - (reserve1 - _amount1Out) : 0; require(amount0In > 0 || amount1In > 0, "Insufficient input"); // 验证恒定乘积 uint256 balance0Adjusted = balance0 * 1000 - amount0In * 3; uint256 balance1Adjusted = balance1 * 1000 - amount1In * 3; require( balance0Adjusted * balance1Adjusted >= uint256(reserve0) * reserve1 * 1000**2, "K" ); _update(balance0, balance1, reserve0, reserve1); }}// 闪电贷套利示例contract Arbitrageur is IFlashSwapCallee { address public owner; constructor() { owner = msg.sender; } function executeArbitrage( address _pair, uint256 _amount0, uint256 _amount1, bytes calldata _data ) external { // 触发闪电贷 FlashSwap(_pair).swap(_amount0, _amount1, address(this), _data); } function uniswapV2Call( address _sender, uint256 _amount0, uint256 _amount1, bytes calldata _data ) external override { // 解码数据 (address targetPair, uint256 repayAmount) = abi.decode(_data, (address, uint256)); // 执行套利逻辑 // 1. 在其他交易所卖出 // 2. 获得利润 // 3. 偿还闪电贷 // 偿还贷款(加上手续费) if (_amount0 > 0) { IERC20(token0).transfer(msg.sender, repayAmount); } if (_amount1 > 0) { IERC20(token1).transfer(msg.sender, repayAmount); } // 发送利润给所有者 // ... }}6. DEX 安全考虑contract DEXSecurity { /* DEX 安全要点: 1. 重入攻击防护 - 使用 Checks-Effects-Interactions 模式 - 使用 ReentrancyGuard 2. 价格操纵防护 - 使用 TWAP 预言机 - 设置最大滑点 - 交易限额 3. 无常损失管理 - 流动性提供者教育 - 激励设计 4. 前端运行防护 - 提交-揭示模式 - 批量拍卖 5. 溢出检查 - Solidity 0.8.0+ 自动检查 - 或使用 SafeMath */}// 安全增强的 AMMcontract SecureAMM is ReentrancyGuard { // 最大滑点(1%) uint256 public constant MAX_SLIPPAGE = 100; uint256 public constant SLIPPAGE_DENOMINATOR = 10000; // 交易限额 uint256 public maxSwapAmount; // 暂停功能 bool public paused; address public guardian; modifier whenNotPaused() { require(!paused, "Paused"); _; } modifier onlyGuardian() { require(msg.sender == guardian, "Not guardian"); _; } // 带滑点保护的交换 function swapWithSlippageProtection( uint256 _amountIn, uint256 _minAmountOut, address _to ) external nonReentrant whenNotPaused { require(_amountIn <= maxSwapAmount, "Exceeds max swap"); uint256 amountOut = getAmountOut(_amountIn); // 计算预期输出 uint256 expectedOut = (_amountIn * reserve1) / reserve0; uint256 slippage = ((expectedOut - amountOut) * SLIPPAGE_DENOMINATOR) / expectedOut; require(slippage <= MAX_SLIPPAGE, "Slippage too high"); require(amountOut >= _minAmountOut, "Insufficient output"); // 执行交换... } // 紧急暂停 function pause() external onlyGuardian { paused = true; } function unpause() external onlyGuardian { paused = false; }}import "@openzeppelin/contracts/security/ReentrancyGuard.sol";7. 总结DEX 核心功能实现要点:AMM 机制:恒定乘积公式 x * y = k自动价格发现无需订单簿流动性管理:添加/移除流动性LP 代币代表份额按比例存入交易功能:精确输入/输出交换多跳路由滑点保护高级功能:价格预言机闪电贷治理集成安全考虑:重入防护价格操纵防护紧急暂停访问控制Gas 优化:使用自定义错误批量操作存储优化