文章目录
- [3. 核⼼业务逻辑详解](#3. 核⼼业务逻辑详解)
-
- [3.1 代币交换(Swap)业务](#3.1 代币交换(Swap)业务)
-
- [3.1.1 交换业务的核⼼价值](#3.1.1 交换业务的核⼼价值)
- [3.1.2 V3交换流程详解](#3.1.2 V3交换流程详解)
- [3.1.3 交换核心代码实现](#3.1.3 交换核心代码实现)
- [3.1.4 滑点保护机制](#3.1.4 滑点保护机制)
- [3.2 流动性提供(Liquidity Provision)业务](#3.2 流动性提供(Liquidity Provision)业务)
-
- [3.2.1 流动性提供的核⼼价值](#3.2.1 流动性提供的核⼼价值)
- [3.2.2 V3添加流动性流程](#3.2.2 V3添加流动性流程)
- [3.2.4 ⽆常损失(Impermanent Loss)详解](#3.2.4 ⽆常损失(Impermanent Loss)详解)
- [3.3 路由算法详解](#3.3 路由算法详解)
-
- [3.3.1 路由算法的核⼼价值](#3.3.1 路由算法的核⼼价值)
- [3.3.2 路由算法架构](#3.3.2 路由算法架构)
- [3.3.3 路径发现算法](#3.3.3 路径发现算法)
- [3.3.4 最优路径选择](#3.3.4 最优路径选择)
- [3.4 价格预⾔机详解](#3.4 价格预⾔机详解)
-
- [3.4.1 Uniswap价格预⾔机的价值](#3.4.1 Uniswap价格预⾔机的价值)
- [3.4.2 V3 TWAP实现](#3.4.2 V3 TWAP实现)
3. 核⼼业务逻辑详解
3.1 代币交换(Swap)业务
3.1.1 交换业务的核⼼价值
代币交换是Uniswap最核⼼的功能,允许⽤户⽤⼀种代币换取另⼀种代币。与传统中⼼化交易所不同,Uniswap的交换具有以下特点:
- ⽆需订单簿:通过AMM公式⾃动定价,⽆需等待对⼿⽅
- 即时成交:只要有流动性,交换⽴即执⾏
- ⽆需KYC:完全去中⼼化,⽆需身份验证
- 可组合性:可以与其他DeFi协议组合使⽤
3.1.2 V3交换流程详解
交换流程的重要性:
理解Uniswap的交换流程对于开发者和⽤户都⾄关重要。这个流程涉及多个组件的协作,每⼀步都有其特定的⽬的和安全检查。
流程详细说明:
- ⽤户发起请求:⽤户在界⾯上输⼊要交换的代币和数量
- 例如:⽤户输⼊"⽤100 USDC换ETH"
- 前端⽴即调⽤Quoter获取预估输出量,显示给⽤户
- 路由计算:Router需要找到最优的交换路径
- 可能有多条路径:USDC→ETH(直接)、USDC→DAI→ETH(两跳)
- Router会计算每条路径的输出量,选择最优的
- 价格查询:Quoter模拟交换,获取预估价格
- Quoter不实际执⾏交换,只是模拟
- 这样可以快速给⽤户反馈,⽆需等待链上确认
- 滑点检查:确保实际输出量不低于⽤户设定的最⼩值
- ⽤户通常设置0.5%的滑点容忍度
- 如果预估输出量变化超过这个值,交易会失败
- 执⾏交换:实际调⽤Pool合约执⾏交换
- Pool合约更新价格和流动性
- 转移代币给⽤户
实际应⽤场景:
场景1:简单单跳交换
⽤户⽤100 USDC换ETH:
- Router查询发现USDC→ETH直接路径最优
- Quoter模拟交换,预估输出0.05 ETH
- ⽤户确认,设置最⼩输出0.0495 ETH(1%滑点)
- Router调⽤Pool合约执⾏交换
- ⽤户收到0.05 ETH(如果价格没变)
场景2:复杂多跳交换
⽤户⽤1000 USDC换某个新代币NEW:
- Router查询发现没有USDC→NEW直接路径
- Router找到路径:USDC→ETH→WETH→NEW
- Quoter模拟三跳交换,预估输出500 NEW
- ⽤户确认,Router依次执⾏三笔交换
- ⽤户收到NEW代币
场景3:⼤额交换(滑点保护)
⽤户⽤100万USDC换ETH:
- Router查询最优路径
- Quoter模拟发现,由于流动性限制,预估输出490 ETH
- 但实际执⾏时,价格可能已经变化
- 如果实际输出<485 ETH(⽤户设定的最⼩值),交易失败
- 这保护了⽤户免受过⼤滑点
V3交换流程详解:

3.1.3 交换核心代码实现
Router合约实现:
c
// Uniswap V3 Router核⼼交换逻辑
contract SwapRouter {
ISwapRouter public immutable swapRouter;
// 单路径交换
function swapExactInputSingle(
address tokenIn,
address tokenOut,
uint24 fee,
address recipient,
uint256 amountIn,
uint256 amountOutMinimum,
uint160 sqrtPriceLimitX96
) external returns (uint256 amountOut) {
// 1. 授权代币
IERC20(tokenIn).approve(address(swapRouter), amountIn);
// 2. 构建交换参数
ISwapRouter.ExactInputSingleParams memory params =
ISwapRouter.ExactInputSingleParams({
tokenIn: tokenIn,
tokenOut: tokenOut,
fee: fee,
recipient: recipient,
deadline: block.timestamp + 300, // 5分钟超时
amountIn: amountIn,
amountOutMinimum: amountOutMinimum,
sqrtPriceLimitX96: sqrtPriceLimitX96
});
// 3. 执⾏交换
amountOut = swapRouter.exactInputSingle(params);
return amountOut;
}
// 多路径交换(通过多个池⼦)
function swapExactInputMultihop(
address[] memory tokens,
uint24[] memory fees,
address recipient,
uint256 amountIn,
uint256 amountOutMinimum
) external returns (uint256 amountOut) {
// 1. 授权第⼀个代币
IERC20(tokens[0]).approve(address(swapRouter), amountIn);
// 2. 构建多跳路径
bytes memory path = abi.encodePacked(tokens[0]);
for (uint i = 0; i < fees.length; i++) {
path = abi.encodePacked(path, fees[i], tokens[i + 1]);
}
// 3. 执⾏多跳交换
ISwapRouter.ExactInputParams memory params =
ISwapRouter.ExactInputParams({
path: path,
recipient: recipient,
deadline: block.timestamp + 300,
amountIn: amountIn,
amountOutMinimum: amountOutMinimum
};
amountOut = swapRouter.exactInput(params);
return amountOut;
}
}
前端实现详解:
下⾯的代码展示了如何在前端实现Uniswap交换功能。这个实现使⽤了Wagmi(React Hooks for Ethereum)和Viem(现代化的以太坊库),⽐传统的ethers.js更类型安全和易⽤。
代码设计要点:
- 授权检查:在交换前检查代币授权,避免⽤户进⾏不必要的授权交易
- 错误处理:完善的错误处理,给⽤户友好的错误提示
- 状态管理:使⽤React Hooks管理交易状态(pending、confirming、success)
- ⽤户体验:显示交易状态,让⽤户知道交易进展
实际使⽤场景:
场景:⽤户在Uniswap界⾯交换代币
- ⽤户输⼊"⽤100 USDC换ETH"
- 前端调⽤ useSwap Hook
- Hook检查USDC授权额度
- 如果授权不⾜,弹出授权交易
- ⽤户确认授权,等待确认
- 授权确认后,⾃动发起交换交易
- 显示"交换中...",等待确认
- 交易确认后,显示"交换成功",更新余额
前端实现:交换交互:
c
// 使⽤Wagmi和Viem实现交换
import { useWriteContract, useWaitForTransaction } from 'wagmi';
import { parseUnits, formatUnits } from 'viem';
// 交换Hook
export function useSwap() {
const { writeContract, data: hash, isPending } = useWriteContract();
const { isLoading: isConfirming, isSuccess } = useWaitForTransaction({ hash });
const swap = async (
tokenIn: string,
tokenOut: string,
amountIn: string,
amountOutMin: string,
fee: number
) => {
try {
// 1. 检查并授权代币
const tokenInContract = {
address: tokenIn as `0x${string}`,
abi: ERC20_ABI,
};
// 检查授权额度
const allowance = await readContract({
...tokenInContract,
functionName: 'allowance',
args: [address, SWAP_ROUTER_ADDRESS],
});
const amountInWei = parseUnits(amountIn, 18);
if (allowance < amountInWei) {
// 需要授权
await writeContract({
...tokenInContract,
functionName: 'approve',
args: [SWAP_ROUTER_ADDRESS, amountInWei],
});
}
// 2. 执⾏交换
await writeContract({
address: SWAP_ROUTER_ADDRESS,
abi: SWAP_ROUTER_ABI,
functionName: 'exactInputSingle',
args: [
{
tokenIn,
tokenOut,
fee,
recipient: address,
deadline: BigInt(Math.floor(Date.now() / 1000) + 300),
amountIn: amountInWei,
amountOutMinimum: parseUnits(amountOutMin, 18),
sqrtPriceLimitX96: 0, // ⽆价格限制
},
],
});
} catch (error) {
console.error('交换失败:', error);
throw error;
}
};
return { swap, isPending, isConfirming, isSuccess };
}
3.1.4 滑点保护机制
滑点是指实际成交价格与预期价格的偏差。Uniswap通过 amountOutMinimum 参数提供滑点保护。
c
// 滑点保护计算
function calculateSlippage(
expectedAmountOut: bigint,
actualAmountOut: bigint
): number {
if (expectedAmountOut === 0n) return 0;
const slippage = Number(((expectedAmountOut - actualAmountOut) * 10000n) / expectedAmountOut) / 100;
return slippage;
}
// 计算最⼩输出量(考虑滑点容忍度)
function calculateMinAmountOut(
expectedAmountOut: bigint,
slippageTolerance: number // 例如 0.5 表示 0.5%
): bigint {
const slippageBps = BigInt(Math.floor(slippageTolerance * 100));
const minAmountOut = (expectedAmountOut * (10000n - slippageBps)) / 10000n;
return minAmountOut;
}
3.2 流动性提供(Liquidity Provision)业务
3.2.1 流动性提供的核⼼价值
流动性提供者(LP)是Uniswap⽣态的重要组成部分,他们通过提供代币对获得⼿续费收益。
LP的收益来源:
- 交易⼿续费:每次交换的0.05%-1%⼿续费分配给LP
- 流动性挖矿:部分协议提供额外代币奖励
- 价格优势:在价格区间内提供流动性可以获得更好的价格
LP⾯临的⻛险:
- ⽆常损失(Impermanent Loss):当代币价格变化时,LP的资产价值可能低于持有
- 价格区间⻛险:如果价格超出LP设置的范围,流动性⽆法被使⽤
- 智能合约⻛险:虽然Uniswap经过审计,但仍存在⻛险
3.2.2 V3添加流动性流程

3.2.4 ⽆常损失(Impermanent Loss)详解
⽆常损失概念:
⽆常损失是指LP的资产价值相对于简单持有代币的损失。当代币价格变化时,AMM会⾃动调整池⼦中的代币⽐例,导致LP的资产组合发⽣变化。
⽆常损失计算公式:
c
// 计算⽆常损失
function calculateImpermanentLoss(
priceRatio: number // 当前价格 / 初始价格
): number {
// IL = 2 * sqrt(priceRatio) / (1 + priceRatio) - 1
const sqrtPriceRatio = Math.sqrt(priceRatio);
const il = (2 * sqrtPriceRatio) / (1 + priceRatio) - 1;
return il * 100; // 转换为百分⽐
}
// 示例计算
const scenarios = [
{ priceChange: 0.5, il: calculateImpermanentLoss(0.5) }, // 价格下跌50%
{ priceChange: 0.8, il: calculateImpermanentLoss(0.8) }, // 价格下跌20%
{ priceChange: 1.0, il: calculateImpermanentLoss(1.0) }, // 价格不变
{ priceChange: 1.25, il: calculateImpermanentLoss(1.25) }, // 价格上涨25%
{ priceChange: 2.0, il: calculateImpermanentLoss(2.0) }, // 价格上涨100%
];
// 结果:
// 价格下跌50%: IL = -5.72%
// 价格下跌20%: IL = -0.60%
// 价格不变: IL = 0%
// 价格上涨25%: IL = -0.60%
// 价格上涨100%: IL = -5.72%
⽆常损失可视化:

3.3 路由算法详解
3.3.1 路由算法的核⼼价值
Uniswap的路由算法负责找到最优的交换路径,可能涉及多个池⼦和多个代币。好的路由算法可以:
- 最⼩化滑点:找到价格最好的路径
- 最⼤化输出:确保⽤户获得最多的代币
- 降低Gas成本:选择Gas效率⾼的路径
- 处理复杂路径:⽀持多跳交换
3.3.2 路由算法架构
路由算法的重要性:
路由算法是Uniswap的核⼼竞争⼒之⼀。⼀个好的路由算法可以:
- 节省⽤户资⾦:找到最优路径可以节省1-5%的成本
- 提升⽤户体验:⾃动找到最优路径,⽤户⽆需⼿动⽐较
- 提⾼资本效率:充分利⽤所有可⽤的流动性
路由算法的挑战:
- 路径数量爆炸:对于有N个代币的⽹络,可能的路径数量是O(N!)
- 例如:10个代币,3跳路径可能有数千条
- 需要智能算法筛选,不能全部计算
- 实时性要求:价格和流动性实时变化,算法必须快速
- ⽤户等待时间不能超过1秒
- 需要在速度和准确性之间平衡
- Gas成本考虑:多跳路径虽然可能价格更好,但Gas成本更⾼
- 需要综合考虑价格优势和Gas成本
- ⼩额交易时,Gas成本可能超过价格优势
实际应⽤举例:
场景1:简单路径最优
⽤户想⽤USDC换ETH:
- 直接路径:USDC→ETH,输出0.5 ETH,Gas 100k
- 两跳路径:USDC→DAI→ETH,输出0.501 ETH,Gas 150k
- 选择:直接路径(Gas节省超过价格优势)
场景2:多跳路径更优
⽤户想⽤100万USDC换ETH:
- 直接路径:输出490 ETH,Gas 100k,总成本490.02 ETH
- 两跳路径:USDC→USDT→ETH,输出492 ETH,Gas 150k,总成本492.015 ETH
- 选择:两跳路径(价格优势1.98 ETH > Gas成本0.015 ETH)
场景3:复杂多跳路径
⽤户想⽤新代币A换新代币B:
-
没有直接路径
-
路径1:A→ETH→B,输出100 B
-
路径2:A→USDC→ETH→B,输出102 B
-
路径3:A→USDC→DAI→ETH→B,输出103 B,但Gas成本⾼
-
选择:根据交易⾦额选择,⼤额选路径3,⼩额选路径1
路由算法架构:
下⾯的架构图展示了路由算法的完整流程。这个流程确保了找到最优路径,同时保持合理的响应时间。

3.3.3 路径发现算法
路径发现算法的重要性:
路径发现是路由算法的核⼼。⼀个好的路径发现算法可以:
- 找到所有可能路径:不遗漏任何潜在的最优路径
- 快速执⾏:在合理时间内完成(<100ms)
- 避免循环:不产⽣循环路径(如A→B→A)
算法选择:深度优先搜索(DFS)vs ⼴度优先搜索(BFS)
DFS(深度优先):
- 优点:内存占⽤少,适合深度搜索
- 缺点:可能找到的路径不是最短的
- 适⽤:路径⻓度不重要,更关注价格
BFS(⼴度优先):
- 优点:找到的路径是最短的(跳数最少)
- 缺点:内存占⽤⼤,不适合深度搜索
- 适⽤:优先考虑Gas成本(跳数少=Gas少)
实际应⽤举例:
场景:查找USDC到NEW代币的路径
代币关系图:
- USDC ↔ ETH
- USDC ↔ DAI
- ETH ↔ WBTC
- DAI ↔ USDT
- WBTC ↔ NEW
- USDT ↔ NEW
DFS搜索过程:
- 从USDC开始
- 探索USDC→ETH→WBTC→NEW(3跳)
- 探索USDC→DAI→USDT→NEW(3跳)
- 探索USDC→ETH→...(继续搜索)
BFS搜索过程:
- 第1层:USDC的邻居(ETH, DAI)
- 第2层:ETH的邻居(WBTC),DAI的邻居(USDT)
- 第3层:WBTC的邻居(NEW),USDT的邻居(NEW)
- 找到两条3跳路径
算法优化技巧:
- 剪枝:如果当前路径的Gas成本已经超过已知最优路径,停⽌搜索
- 缓存:缓存常⽤路径,避免重复计算
- 并⾏搜索:使⽤多线程并⾏搜索不同分⽀
- 启发式搜索:优先搜索流动性⼤的池⼦
深度优先搜索(DFS)实现:
下⾯的代码展示了使⽤DFS算法发现所有可能路径的实现。这个实现包含了循环检测、路径记录等关键功能。
c
// 路由路径发现
interface Route {
path: string[]; // 代币地址路径
fees: number[]; // 每个池⼦的⼿续费率
pools: string[]; // 池⼦地址
}
class RouteFinder {
private pools: Map<string, PoolInfo> = new Map();
// 查找所有可能的路径
findRoutes(
tokenIn: string,
tokenOut: string,
maxHops: number = 3
): Route[] {
const routes: Route[] = [];
const visited = new Set<string>();
// DFS搜索
this.dfs(
tokenIn,
tokenOut,
[tokenIn],
[],
[],
routes,
visited,
maxHops
);
return routes;
}
private dfs(
current: string,
target: string,
path: string[],
fees: number[],
pools: string[],
routes: Route[],
visited: Set<string>,
maxHops: number
) {
// 找到⽬标
if (current === target) {
routes.push({
path: [...path],
fees: [...fees],
pools: [...pools],
});
return;
}
// 超过最⼤跳数
if (path.length > maxHops) {
return;
}
// 获取当前代币的所有池⼦
const connectedPools = this.getConnectedPools(current);
for (const pool of connectedPools) {
const nextToken = pool.token0 === current ? pool.token1 : pool.token0;
const key = `${current}-${nextToken}`;
// 避免循环
if (visited.has(key)) {
continue;
}
visited.add(key);
path.push(nextToken);
fees.push(pool.fee);
pools.push(pool.address);
// 递归搜索
this.dfs(
nextToken,
target,
path,
fees,
pools,
routes,
visited,
maxHops
);
// 回溯
visited.delete(key);
path.pop();
fees.pop();
pools.pop();
}
}
private getConnectedPools(token: string): PoolInfo[] {
// 从缓存或链上获取连接池⼦
return Array.from(this.pools.values()).filter(
(pool) => pool.token0 === token || pool.token1 === token
);
}
}
3.3.4 最优路径选择
路径选择的重要性:
找到所有可能路径后,需要选择最优路径。最优路径的定义不是唯⼀的,需要综合考虑:
- 输出量:⽤户最关⼼的,能获得多少代币
- Gas成本:影响实际收益
- 路径可靠性:路径中的池⼦是否稳定
- 执⾏时间:多跳路径需要更多时间
评分算法设计:
路径评分需要考虑多个因素,常⻅的评分公式:
score = (amountOut - gasCostInToken) / gasCostInToken
其中:
- amountOut: 预估输出量
- gasCostInToken: Gas成本(转换为代币单位)
这个公式的含义是:每单位Gas成本能获得多少代币。分数越⾼,路径越优。
实际应⽤举例:
场景:选择USDC到ETH的路径
找到3条路径:
- 路径1:USDC→ETH(直接)
- 输出:0.5 ETH
- Gas:100k(约20,按ETH=2000计算,0.01 ETH)
- 评分:(0.5 - 0.01) / 0.01 = 49
- 路径2:USDC→DAI→ETH(两跳)
- 输出:0.501 ETH
- Gas:150k(约$30,0.015 ETH)
- 评分:(0.501 - 0.015) / 0.015 ≈ 32.4
- 路径3:USDC→USDT→DAI→ETH(三跳)
- 输出:0.502 ETH
- Gas:200k(约$40,0.02 ETH)
- 评分:(0.502 - 0.02) / 0.02 = 24.1
选择:路径1评分最⾼,选择直接路径
但如果是⼤额交易(100万USDC):
- 路径1:输出490 ETH,Gas 0.01 ETH,净收益489.99 ETH
- 路径2:输出492 ETH,Gas 0.015 ETH,净收益491.985 ETH
- 路径3:输出493 ETH,Gas 0.02 ETH,净收益492.98 ETH
选择:路径2净收益最⾼,选择两跳路径
路径评分算法:
下⾯的代码展示了路径评分和选择的完整实现。这个实现考虑了输出量、Gas成本等多个因素。
c
// 路径评分和选择
interface PathScore {
route: Route;
amountOut: bigint;
gasEstimate: bigint;
score: number; // 综合评分
}
class OptimalRouteSelector {
// 选择最优路径
selectOptimalRoute(
tokenIn: string,
amountIn: bigint,
tokenOut: string,
routes: Route[]
): Route {
const scores: PathScore[] = [];
for (const route of routes) {
// 1. 计算输出量
const amountOut = this.simulateSwap(route, amountIn);
// 2. 估算Gas成本
const gasEstimate = this.estimateGas(route);
// 3. 计算综合评分
// 评分 = 输出量 / (Gas成本 * Gas价格)
const gasPrice = 20n * 10n ** 9n; // 20 Gwei
const score = Number(amountOut) / Number(gasEstimate * gasPrice);
scores.push({
route,
amountOut,
gasEstimate,
score,
});
}
// 按评分排序,选择最优
scores.sort((a, b) => b.score - a.score);
return scores[0].route;
}
// 模拟交换计算输出量
private simulateSwap(route: Route, amountIn: bigint): bigint {
let amountOut = amountIn;
for (let i = 0; i < route.path.length - 1; i++) {
const pool = this.getPool(route.pools[i]);
amountOut = pool.getAmountOut(
amountOut,
route.path[i],
route.path[i + 1]
);
}
return amountOut;
}
// 估算Gas成本
private estimateGas(route: Route): bigint {
const baseGas = 21000n; // 基础交易Gas
const swapGas = 100000n; // 每次交换的Gas
const hopGas = 50000n; // 每跳的额外Gas
return baseGas + BigInt(route.path.length - 1) * swapGas +
BigInt(route.path.length - 2) * hopGas;
}
}
3.4 价格预⾔机详解
3.4.1 Uniswap价格预⾔机的价值
Uniswap的价格预⾔机是DeFi⽣态的重要基础设施,为其他协议提供可靠的价格数据。
应⽤场景:
- 借贷协议:计算抵押品价值和清算价格
- 衍⽣品协议:结算期货和期权合约
- 聚合器:⽐较不同DEX的价格
- 算法稳定币:维持稳定币锚定
3.4.2 V3 TWAP实现
TWAP的重要性:
时间加权平均价格(TWAP)是DeFi⽣态的重要基础设施。与现货价格不同,TWAP通过时间加权平均,可以有效抵抗价格操纵攻击。
为什么需要TWAP?
在DeFi中,价格操纵是⼀个严重问题:
- 场景1:攻击者⽤⼤额资⾦在Uniswap上操纵价格,然后从借贷协议中借出更多资⾦
- 场景2:攻击者操纵价格触发清算,然后低价买⼊资产
TWAP通过时间加权平均,使得短期价格操纵变得困难:
- 攻击者需要维持操纵价格⼀段时间(通常30分钟以上)
- 这⼤⼤增加了攻击成本,使得攻击不经济
TWAP的⼯作原理:
TWAP不是简单的平均价格,⽽是时间加权平均。这意味着:
- 如果价格在2000维持了10分钟,然后跳到2100维持了1分钟
- 平均价格 = (2000 * 10 + 2100 * 1) / 11 ≈ $2009
- ⽽不是简单的 (2000 + 2100) / 2 = $2050
实际应⽤场景:
场景1:借贷协议使⽤TWAP
Aave协议使⽤Uniswap的TWAP价格:
- ⽤户抵押100 ETH,想借出USDC
- Aave查询Uniswap的30分钟TWAP价格:$2000
- Aave计算:100 ETH * 2000 \* 75% = 150,000可借额度
- 即使⽤户在Uniswap上操纵价格到2100,只要操纵时间\<30分钟,TWAP价格仍然接近2000
- 结果:攻击失败,⽤户⽆法借出更多资⾦
场景2:算法稳定币使⽤TWAP
算法稳定币协议使⽤TWAP维持锚定:
- ⽬标价格:$1.00
- 当前TWAP价格:$0.98(低于⽬标)
- 协议⾃动增发代币,降低价格
- 如果使⽤现货价格,可能被操纵;使⽤TWAP,操纵成本⾼
时间加权平均价格(TWAP)原理:
下⾯的流程图展示了TWAP的计算过程。理解这个过程对于使⽤Uniswap价格预⾔机的开发者很重要。

代码实现:
c
// Uniswap V3 价格预⾔机
library OracleLibrary {
// 查询TWAP价格
function consult(
address pool,
uint32 secondsAgo
) internal view returns (int24 timeWeightedAverageTick) {
require(secondsAgo > 0, 'BP');
uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = secondsAgo;
secondsAgos[1] = 0;
// 获取累积的tick和时间戳
(int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = IUniswapV3Pool(pool).observe(secondsAgos);
int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
uint32 timeDelta = secondsAgo;
// 计算平均tick
timeWeightedAverageTick = int24(tickCumulativesDelta / int56(uint56(timeDelta)));
// 处理溢出情况
if (tickCumulativesDelta < 0 && (tickCumulativesDelta % int56(uint56(timeDelta)) != 0)) {
timeWeightedAverageTick--;
}
}
// 从tick计算价格
function getQuoteAtTick(
int24 tick,
uint128 baseAmount,
address baseToken,
address quoteToken
) internal pure returns (uint256 quoteAmount) {
uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(tick);
// 计算价格⽐例
uint256 ratioX192 = uint256(sqrtRatioX96) * sqrtRatioX96;
quoteAmount = baseToken < quoteToken ? FullMath.mulDiv(ratioX192, baseAmount, 1 << 192) : FullMath.mulDiv(1 << 192, baseAmount, ratioX192);
}
}
// 使⽤示例
contract PriceOracle {
function getTWAP(
address pool,
address tokenIn,
address tokenOut,
uint32 twapInterval
) external view returns (uint256 price) {
// 1. 获取平均tick
int24 tick = OracleLibrary.consult(pool, twapInterval);
// 2. 计算价格(使⽤1e18作为基础单位)
uint128 baseAmount = 1e18;
price = OracleLibrary.getQuoteAtTick(
tick,
baseAmount,
tokenIn,
tokenOut
);
return price;
}
}