1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
| // SPDX-License-Identifier: MIT pragma solidity ^0.8.19;
/* 教学目的简化版 Uniswap V3 Pool - 省略很多安全检查、优化与完整数据结构 - 保留核心 swap 流程与关键变量以便逐行解释 */
interface IERC20 { function transfer(address to, uint256 amount) external returns (bool); function transferFrom(address from, address to, uint256 amount) external returns (bool); }
interface IUniswapV3SwapCallback { // Pool 会在 swap 中调用此 callback,要求调用者在回调中把 tokenIn 发送给 pool function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external; }
contract SimpleUniswapV3Pool { // --- 基本存储(极度简化) --- IERC20 public token0; IERC20 public token1;
// 当前 sqrt price (Q64.96 format in real Uniswap) —— 这里用 uint256 表示 uint256 public sqrtPriceX96; // 当前 tick(仅教学用) int24 public currentTick; // 当前 active 流动性 L(简化:全局单个流动性) uint128 public liquidity; // 全局手续费累积(简化) uint256 public feeGrowthGlobal0; uint256 public feeGrowthGlobal1; // 每个 tick 的边界价格(在真实合约里有复杂 tickmap 结构) mapping(int24 => uint256) public tickSqrtPriceX96;
// pool 的 fee(以百万分之一为单位,例如 3000 表示 0.3%) uint24 public fee;
address public owner;
constructor(IERC20 _t0, IERC20 _t1, uint256 _sqrtPriceX96, uint128 _liquidity, uint24 _fee) { token0 = _t0; token1 = _t1; sqrtPriceX96 = _sqrtPriceX96; liquidity = _liquidity; fee = _fee; owner = msg.sender; }
// ----------------------- // 主要函数:swap // ----------------------- /* recipient: swap 输出代币接收方 zeroForOne: true 表示 token0 -> token1(卖 token0),false 表示 token1 -> token0 amountSpecified: 如果为正数,表示 exact input(输入量),如果为负数,表示 exact output(指定输出量) sqrtPriceLimitX96: 价格上/下限,防止滑点(简化处理) data: callback 数据,传给回调 */ function swap( address recipient, bool zeroForOne, int256 amountSpecified, uint256 sqrtPriceLimitX96, bytes calldata data ) external { // --- 1) 初始化临时状态 SwapState --- // amountSpecifiedRemaining:在循环中需要还未处理的输入/输出数量 int256 amountSpecifiedRemaining = amountSpecified; // amountCalculated:累积计算的反向量(如果输入指定则为输出的累计,反之亦然) int256 amountCalculated = 0; // 本轮工作价格(从存储中读取当前价格) uint256 _sqrtPriceX96 = sqrtPriceX96; uint128 _liquidity = liquidity; int24 _tick = currentTick;
// --- 2) 进入循环:按 tick range 逐步推进价格,直到满足条件 --- // 在真实实现中 loop 条件与边界判断更复杂,这里把逻辑线性化便于教学 while (amountSpecifiedRemaining != 0) { // 2.1 计算当前 price 到下一个 tick 边界(或价格限制)可以交换的最大量(简化) // nextTickPrice:当前 tick 的下一边界价格(取 mapping) uint256 nextTickSqrtPrice = tickSqrtPriceX96[_tick + (zeroForOne ? -1 : 1)]; if (nextTickSqrtPrice == 0) { // 如果没有下一个 tick 的价格边界,使用用户传入的 price limit 或直接认为可以无限推进 nextTickSqrtPrice = sqrtPriceLimitX96 == 0 ? (_sqrtPriceX96 * 2) : sqrtPriceLimitX96; }
// 2.2 计算在当前流动性下,把价格移动到 nextTickPrice 时需要的输入量和输出量 // 这里使用简化公式示意:dx = L * (sqrtP_next - sqrtP_current) / (sqrtP_next * sqrtP_current) // dy = L * (sqrtP_current - sqrtP_next) // 注意:真实 Uniswap 用 Q64.96 精度和更精确的边界算式 bool hitPriceLimit = false;
// 假设我们正在做 token0 -> token1(卖 token0),那么用户支付的是 token0(dx),收到 token1(dy) if (zeroForOne) { // 计算将价格从 _sqrtPriceX96 推到 nextTickSqrtPrice 时需要的 token0 输入(dx)和产生的 token1 输出(dy) // 为了防止除零和复杂精度,这里做安全保护(教学目的) uint256 dx = uint256(_liquidity) * (nextTickSqrtPrice > _sqrtPriceX96 ? (nextTickSqrtPrice - _sqrtPriceX96) : (_sqrtPriceX96 - nextTickSqrtPrice)); uint256 dy = uint256(_liquidity) * ( _sqrtPriceX96 > nextTickSqrtPrice ? (_sqrtPriceX96 - nextTickSqrtPrice) : (nextTickSqrtPrice - _sqrtPriceX96) );
// 这里把 dx, dy 当作“当前区间可消耗最大量”(极为简化) // 判断 amountSpecifiedRemaining 是按 input 还是 output 模式 if (amountSpecified > 0) { // exact input 模式(用户给定输入量,需要尽可能把 input 花掉) uint256 amountInRemaining = uint256(amountSpecifiedRemaining); if (amountInRemaining >= dx) { // 本区间足够消耗,价格移动到 nextTick,扣除 dx,累加 output dy amountSpecifiedRemaining -= int256(dx); // 减掉消耗的输入 amountCalculated += int256(dy); // 累计得到的输出 // move price to nextTick _sqrtPriceX96 = nextTickSqrtPrice; // cross tick -> 更新 liquidity(教学:假设每个 tick 会撤掉一部分 liquidity) // 假设 tick 上的 liquidityNet 为 0(这里省略实际的 net 操作) _tick = _tick + (zeroForOne ? -1 : 1); } else { // 本区间不足以耗尽到下一个 tick,价格只移动到某个中间价格,消耗完剩余 amount,算出对应 output // 用简化比例:dy = amountInRemaining * (dy/dx) uint256 partialDy = (uint256(amountInRemaining) * dy) / dx; amountCalculated += int256(partialDy); // 所有输入用完,退出循环 amountSpecifiedRemaining = 0; // 更新价格到某个中间值(在真实合约中需精确计算 sqrtPriceX96) _sqrtPriceX96 = _sqrtPriceX96 + ((nextTickSqrtPrice - _sqrtPriceX96) * amountInRemaining) / dx; hitPriceLimit = true; // 结束循环 } } else { // exact output 模式(用户指定想要拿到多少 output) uint256 amountOutRemaining = uint256(-amountSpecifiedRemaining); // 负数表示输出需求 if (amountOutRemaining <= dy) { // 在本区间可以得到足够的输出,消耗部分输入 uint256 neededIn = (uint256(amountOutRemaining) * dx) / dy; amountSpecifiedRemaining += int256(neededIn); // closer to zero amountCalculated -= int256(amountOutRemaining); // 累计消耗的输出(负) // update price partially _sqrtPriceX96 = _sqrtPriceX96 + ((nextTickSqrtPrice - _sqrtPriceX96) * neededIn) / dx; hitPriceLimit = true; // done } else { // 本区间不够产出,跨 tick amountSpecifiedRemaining += int256(dx); // 消耗输入 dx (since amountSpecifiedRemaining is negative) amountCalculated -= int256(dy); _sqrtPriceX96 = nextTickSqrtPrice; _tick = _tick + (zeroForOne ? -1 : 1); } } } else { // token1 -> token0 的方向:对称计算(为了篇幅这里省略,真实合约包含对称处理) // 在教学中认为处理逻辑对称,只是变量互换 revert("only zeroForOne implemented in this simplified sample"); }
if (hitPriceLimit) { break; } } // end while
// --- 3) 在循环结束后我们有了最终的 amountCalculated(这是输出或输入的对向量) --- // 例如:如果用户给定 exact input (amountSpecified > 0),那么 amountCalculated 是累积的 output(正数) // 如果用户给定 exact output (amountSpecified < 0),那么 amountCalculated 是累积的 input(负数)
// --- 4) 回调:要求 msg.sender(通常是 Router)把需要的 tokenIn 转给 Pool --- // Uniswap 机制:Pool 在做账面计算后会调用 callback,callback 中 Router 调用 transferFrom 将 tokenIn 发送到 Pool // 这里我们调用接口(简化) // 传递的 delta 数值按照 token0/token1 的 signed delta 形式(pool 会传入两个 delta) int256 amount0Delta; int256 amount1Delta; if (/* zeroForOne */ true) { // token0 被消费,token1 被输出 // amount0Delta: input(正数表示池子应当收到),amount1Delta: output(负数表示池子将支付) // 在真实 Uniswap:正负号语义有所差别,这里简化: amount0Delta = amountSpecified > 0 ? amountSpecified - amountSpecifiedRemaining : -amountCalculated; amount1Delta = amountCalculated > 0 ? -amountCalculated : int256(0); } else { amount0Delta = 0; amount1Delta = 0; }
// 调用回调(Router 必须在回调中把 token0 转给 pool) IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback(amount0Delta, amount1Delta, data);
// --- 5) 转账:把计算好的 tokenOut 发送给 recipient --- // 根据上面累积的 amountCalculated(示例中为 token1 输出) uint256 amountOut = uint256(amountCalculated > 0 ? amountCalculated : -amountCalculated); // 取绝对值演示 if (amountOut > 0) { // 为了教学简洁,我们只处理 token1 支付 require(token1.transfer(recipient, amountOut), "transfer out failed"); }
// --- 6) 更新池子状态(价格、tick、liquidity、feeGrowth 等) --- sqrtPriceX96 = _sqrtPriceX96; currentTick = _tick; liquidity = _liquidity; // 更新 feeGrowthGlobal(非常简化:按 fee * amountIn) // 假设 amount0Delta 为池子收到 token0(正数),则按 fee 计入 feeGrowthGlobal0 if (amount0Delta > 0) { feeGrowthGlobal0 += uint256(amount0Delta) * fee / 1_000_000; } if (amount1Delta > 0) { feeGrowthGlobal1 += uint256(amount1Delta) * fee / 1_000_000; }
// swap 完成 —— 在真实合约还会 emit Swap 事件并校验最终收支平衡 } }
|