50f0d87b1708cd980a55603143ce0c75.jpg

5763c02a41c45a1d75b488265c265cb2.jpg

d70ebb94a77915e821d895ca2f6c275e.jpg

逻辑流程 :

  1. 用户调用 Router 发起 swap
  2. Router 检查授权并转发到 Pool
  3. Pool 创建 SwapState
  4. Pool 进入 tick-by-tick 的循环
  5. 计算当前流动性下可交换的量
  6. 判断是否跨 tick
  7. 跨 tick 时更新 liquidity
  8. 累积手续费
  9. 回调向 Router 收取 tokenIn
  10. Pool 向用户支付 tokenOut,并更新价格与 tick
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 事件并校验最终收支平衡
}
}