文章目录
- [4. 技术架构详解](#4. 技术架构详解)
-
- [4.1 智能合约架构](#4.1 智能合约架构)
-
- [4.1.1 V3合约架构图](#4.1.1 V3合约架构图)
- [4.1.2 核⼼合约详解](#4.1.2 核⼼合约详解)
- [4.2 前端架构](#4.2 前端架构)
-
- [4.2.1 前端技术栈](#4.2.1 前端技术栈)
- [4.3 安全机制](#4.3 安全机制)
-
- [4.3.1 重⼊攻击防护](#4.3.1 重⼊攻击防护)
- [4.3.2 价格操纵防护](#4.3.2 价格操纵防护)
- [4.3.3 闪电贷防护](#4.3.3 闪电贷防护)
- [4.4 Gas优化技术](#4.4 Gas优化技术)
-
- [4.4.1 存储优化](#4.4.1 存储优化)
- [4.4.2 批量操作优化](#4.4.2 批量操作优化)
- [4.5 跨链架构](#4.5 跨链架构)
-
- [4.5.1 多链部署架构](#4.5.1 多链部署架构)
- [4.5.2 跨链路由](#4.5.2 跨链路由)
4. 技术架构详解
4.1 智能合约架构
4.1.1 V3合约架构图
合约架构设计理念:
Uniswap V3的合约架构采⽤模块化设计,每个组件都有明确的职责。这种设计的优势:
- 可维护性:每个合约职责单⼀,便于理解和维护
- 可升级性:可以独⽴升级某个组件,不影响其他组件
- 安全性:核⼼逻辑在Pool合约,外围合约即使有问题也不影响核⼼
- Gas优化:Library合约可以被多个Pool合约共享,节省部署成本
各层详细说明:
Factory层:
- 职责:创建和管理Pool合约
- 关键功能:确保每个交易对只有⼀个Pool(每个费率⼀个)
- 使⽤场景:当有⼈想创建新的ETH/USDC 0.3%费率池⼦时,Factory检查是否已存在,如果不存在则部署新Pool
Pool层:
- 职责:存储流动性、执⾏交换、管理价格
- 关键功能:每个Pool合约管理⼀个交易对的⼀个费率层级
- 使⽤场景:ETH/USDC可能有3个Pool:0.05%、0.3%、1%,每个都是独⽴合约
- 为什么这样设计:不同费率需要不同的tickSpacing,分开管理更清晰
Periphery层:
- 职责:提供⽤户友好的接⼝,处理复杂逻辑
- SwapRouter:处理代币授权、滑点检查、多跳交换
- NFT Position Manager:管理LP头⼨,铸造NFT
- Quoter:查询价格,不实际执⾏交换
- 使⽤场景:⽤户通过Router交换,Router内部可能调⽤多个Pool
Library层:
-
职责:提供数学计算函数
-
TickMath:价格和tick转换,这是V3的核⼼数学
-
SqrtPriceMath:平⽅根价格计算
-
LiquidityMath:流动性计算
-
SwapMath:交换计算
-
为什么⽤Library:这些函数被多个合约使⽤,Library可以节省Gas
实际交互流程举例:
场景:⽤户创建新池⼦并添加流动性
- ⽤户调⽤Factory.createPool(ETH, USDC, 3000) // 0.3%费率
- Factory检查:getPool[ETH][USDC][3000]是否存在
- 如果不存在,Factory部署新的Pool合约
- Factory记录:getPool[ETH][USDC][3000] = 新Pool地址
- ⽤户通过NFT Position Manager添加流动性
- Position Manager调⽤Pool.mint()添加流动性
- Pool使⽤LiquidityMath计算所需代币数量
- Pool使⽤TickMath更新价格和tick
- Position Manager铸造NFT给⽤户
V3合约架构图:

4.1.2 核⼼合约详解
Factory合约:
c
// Uniswap V3 Factory核⼼逻辑
contract UniswapV3Factory is IUniswapV3Factory {
// 池⼦地址映射: (token0, token1, fee) => pool address
mapping(address => mapping(address => mapping(uint24 => address)))
public override getPool;
// 创建池⼦
function createPool(
address tokenA,
address tokenB,
uint24 fee
) external override returns (address pool) {
require(tokenA != tokenB, 'IA'); // Identical addresses
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), 'ZA'); // Zero address
require(fee > 0, 'F0'); // Fee must be positive
require(getPool[token0][token1][fee] == address(0), 'PE'); // Pool exists
// 部署新池⼦合约
pool = address(
new UniswapV3Pool{
salt: keccak256(abi.encode(token0, token1, fee))
}()
);
// 记录池⼦地址
getPool[token0][token1][fee] = pool;
getPool[token1][token0][fee] = pool; // 填充反向映射
emit PoolCreated(token0, token1, fee, pool);
return pool;
}
// 设置协议⼿续费率
function setOwner(address _owner) external override {
require(owner == msg.sender, 'FNA'); // Forbidden
owner = _owner;
}
function enableFeeAmount(uint24 fee, int24 tickSpacing) external override {
require(msg.sender == owner, 'FNA');
require(fee < 1000000, 'F'); // Fee too high
require(tickSpacing > 0 && tickSpacing < 16384, 'TS'); // Tick spacing invalid
feeAmountTickSpacing[fee] = tickSpacing;
emit FeeAmountEnabled(fee, tickSpacing);
}
}
Pool合约核⼼状态:
c
// Uniswap V3 Pool核⼼状态和函数
contract UniswapV3Pool is IUniswapV3Pool {
// 池⼦参数
address public override factory;
address public override token0;
address public override token1;
uint24 public override fee;
int24 public override tickSpacing;
// 全局状态
uint128 public override liquidity; // 当前活跃流动性
uint160 public override sqrtPriceX96; // 当前价格(Q64.96格式)
int24 public override tick; // 当前tick
// Tick信息
mapping(int24 => Tick.Info) public override ticks;
mapping(int16 => uint256) public override tickBitmap;
// 观察者(⽤于价格预⾔机)
struct Observation {
uint32 blockTimestamp;
int56 tickCumulative;
uint160 secondsPerLiquidityCumulativeX128;
bool initialized;
}
Observation[65535] public override observations;
// 协议⼿续费
uint128 public override protocolFeesToken0;
uint128 public override protocolFeesToken1;
// 交换函数
function swap(
address recipient,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes calldata data
) external override returns (int256 amount0, int256 amount1) {
// 实现交换逻辑
// ...
}
// 观察函数(⽤于价格预⾔机)
function observe(uint32[] calldata secondsAgos) external view override
returns (
int56[] memory tickCumulatives,
uint160[] memory secondsPerLiquidityCumulativeX128s
)
{
// 实现观察逻辑
// ...
}
}
4.1.3 数学库详解
TickMath库详解:
TickMath是V3最核⼼的数学库之⼀,它处理价格和tick之间的转换。理解这个库对于理解V3的价格机制⾄关重要。
为什么需要Tick?
在V3中,价格不是连续的值,⽽是离散的tick。这样设计的原因:
- Gas优化:离散值可以⽤整数存储,计算更快
- 精度控制:tick spacing控制价格精度
- 流动性管理:每个tick可以存储流动性
Tick和价格的关系:
price = 1.0001^tick
例如:
- tick = 0: price = 1.0001^0 = 1.0
- tick = 6931: price = 1.0001^6931 ≈ 2.0
- tick = -6931: price = 1.0001^(-6931) ≈ 0.5
为什么⽤1.0001?
1.0001是⼀个精⼼选择的常数:
如果tick spacing = 60,相邻tick的价格差异约为0.6%
这个差异⾜够⼩(滑点可控),⼜⾜够⼤(Gas可接受)
TickMath库:处理价格和tick之间的转换
下⾯的代码展示了TickMath库的核⼼实现。这个实现使⽤了⾼效的位操作和数学技巧,确保计算的准确性和Gas效率。
c
// TickMath库:价格和tick转换
library TickMath {
// 最⼩tick(对应最⼩价格)
int24 internal constant MIN_TICK = -887272;
// 最⼤tick(对应最⼤价格)
int24 internal constant MAX_TICK = -MIN_TICK;
// 最⼩价格(sqrtPrice)
uint160 internal constant MIN_SQRT_RATIO = 4295128739;
// 最⼤价格(sqrtPrice)
uint160 internal constant MAX_SQRT_RATIO =
1461446703485210103287273052203988822378723970342;
// 从tick计算sqrtPrice
function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96)
{
require(tick >= MIN_TICK && tick <= MAX_TICK, 'T'); // Tick out of range
uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
require(absTick <= uint256(int256(MAX_TICK)), 'T');
uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001: 0x100000000000000000000000000000000;
if (absTick & 0x2 != 0)
ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
if (absTick & 0x4 != 0)
ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
// ... 更多位操作
if (tick > 0) ratio = type(uint256).max / ratio;
sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));
}
// 从sqrtPrice计算tick
function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick)
{
require(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO,'R');
uint256 ratio = uint256(sqrtPriceX96) << 32;
uint256 r = ratio;
uint256 msb = 0;
// 找到最⾼有效位
// ...
r = (r * 0x5d6af8dedb81196699c329225ee604) >> 128;
r = (r * 0x2216e584f5fa1ea926041bedfe98) >> 128;
r = (r * 0x48a170391f7dc42444e8fa2) >> 128;
int256 log_2 = (int256(msb) - 128) << 64;
// ... 计算log
tick = int24((log_2 - 3402992956809132418596140100660240240) >> 128);
}
}
SwapMath库详解:
SwapMath是V3交换计算的核⼼库,它负责计算单步交换的输⼊输出量。这个库的复杂性在于需要处理多种情况:
- 精确输⼊ vs 精确输出:⽤户可能指定输⼊量或输出量
- 价格限制:⽤户可能设置价格上限或下限
- 流动性变化:交换过程中可能跨越多个tick,流动性会变化
- ⼿续费计算:需要准确计算⼿续费
交换步骤的计算过程:
在V3中,⼀次交换可能跨越多个tick,每个tick内的流动性是固定的。SwapMath计算的是单个tick内的交换步骤:
- 计算⽬标价格:根据剩余输⼊量和当前流动性,计算能到达的价格
- 检查边界:如果⽬标价格超出当前tick,只能到达tick边界
- 计算输⼊输出:根据价格变化计算实际的输⼊输出量
- 计算⼿续费:根据输⼊量和费率计算⼿续费
实际计算示例:
场景:在单个tick内交换
当前状态:
- 当前价格:sqrtPrice = 44.72(对应$2000)
- 当前tick:6931
- 当前流动性:L = 1000
- ⽤户输⼊:1000 USDC(精确输⼊)
计算过程:
- 计算⽬标价格:根据L和输⼊量,计算新的sqrtPrice
- 检查是否超出tick:如果新价格仍在tick 6931内,继续;否则只能到达tick边界
- 计算输出量:根据价格变化计算ETH输出量
- 计算⼿续费:1000 USDC × 0.3% = 3 USDC
SwapMath库:计算交换步骤
下⾯的代码展示了SwapMath库的核⼼实现。这个实现处理了精确输⼊、精确输出、价格限制等多种情况。
c
// SwapMath库:交换计算
library SwapMath {
// 计算单步交换
function computeSwapStep(
uint160 sqrtRatioCurrentX96,
uint160 sqrtRatioTargetX96,
uint128 liquidity,
int256 amountRemaining,
uint24 feePips
)
internal pure returns (
uint160 sqrtRatioNextX96,
uint256 amountIn,
uint256 amountOut,
uint256 feeAmount
)
{
bool zeroForOne = sqrtRatioCurrentX96 >= sqrtRatioTargetX96;
bool exactIn = amountRemaining >= 0;
uint256 amountRemainingLessFee = FullMath.mulDiv(uint256(amountRemaining < 0 ? -amountRemaining : amountRemaining),1e6 - feePips,1e6 );
if (exactIn) {
amountIn = zeroForOne ? SqrtPriceMath.getAmount0Delta(sqrtRatioTargetX96,sqrtRatioCurrentX96,liquidity,true): SqrtPriceMath.getAmount1Delta(sqrtRatioCurrentX96,sqrtRatioTargetX96,liquidity,true);
if (amountRemainingLessFee >= amountIn) {
sqrtRatioNextX96 = sqrtRatioTargetX96;
} else {
sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromInput(sqrtRatioCurrentX96,liquidity, amountRemainingLessFee,zeroForOne);
}
} else {
amountOut = zeroForOne ? SqrtPriceMath.getAmount1Delta(sqrtRatioTargetX96,sqrtRatioCurrentX96,liquidity,false ): SqrtPriceMath.getAmount0Delta(sqrtRatioCurrentX96,sqrtRatioTargetX96,liquidity,false);
if (uint256(-amountRemaining) >= amountOut) {
sqrtRatioNextX96 = sqrtRatioTargetX96;
} else {
sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtRatioCurrentX96,liquidity, uint256(-amountRemaining),zeroForOne);
}
}
bool max = sqrtRatioTargetX96 == sqrtRatioNextX96;
// 计算实际输⼊输出量
if (zeroForOne) {
amountIn = max && exactIn ? amountIn : SqrtPriceMath.getAmount0Delta( sqrtRatioNextX96, sqrtRatioCurrentX96,liquidity, true );
amountOut = max && !exactIn ? amountOut : SqrtPriceMath.getAmount1Delta( sqrtRatioNextX96, sqrtRatioCurrentX96,liquidity,false );
} else {
amountIn = max && exactIn? amountIn : SqrtPriceMath.getAmount1Delta(sqrtRatioCurrentX96,sqrtRatioNextX96, liquidity,true );
amountOut = max && !exactIn ? amountOut: SqrtPriceMath.getAmount0Delta( sqrtRatioCurrentX96,sqrtRatioNextX96, liquidity, false);
}
// 计算⼿续费
if (!exactIn && amountOut > uint256(-amountRemaining)) {
amountOut = uint256(-amountRemaining);
}
if (exactIn && sqrtRatioNextX96 != sqrtRatioTargetX96) {
feeAmount = uint256(amountRemaining) - amountIn;
} else {
feeAmount = FullMath.mulDivRoundingUp(amountIn,feePips,1e6 - feePips);
}
}
}
4.2 前端架构
4.2.1 前端技术栈
前端架构设计说明:
Uniswap的前端架构采⽤现代化的React技术栈,结合Web3库实现与区块链的交互。这个架构的设计考虑了:
- ⽤户体验:快速响应、流畅交互、清晰反馈
- 开发效率:使⽤成熟的库和框架,减少开发时间
- 可维护性:模块化设计,便于团队协作
- 性能优化:缓存、懒加载、代码分割
各层详细说明:
UI层:
React组件:可复⽤的UI组件,如TokenInput、SwapButton等
Tailwind CSS:实⽤优先的CSS框架,快速构建界⾯
React Router:单⻚应⽤路由,实现⻚⾯导航
使⽤场景:⽤户打开Uniswap界⾯,看到交换⻚⾯,点击"Pool"跳转到流动性⻚⾯
状态管理:
Zustand/Redux:全局状态管理,存储⽤户选择的代币、⾦额等
TanStack Query:数据获取和缓存,缓存价格、余额等数据
Wagmi状态:钱包连接状态、当前账户等
使⽤场景:⽤户选择USDC作为输⼊代币,状态更新,触发价格查询
区块链交互:
Wagmi Hooks:React Hooks封装,简化Web3交互
Viem:类型安全的以太坊库,⽐ethers.js更现代
Uniswap SDK:Uniswap官⽅SDK,处理路由计算、价格计算等
使⽤场景:⽤户点击"交换"按钮,前端使⽤Wagmi发送交易
数据层:
The Graph API:查询历史交易、流动性数据等
Uniswap Routing API:获取最优交换路径
价格API:实时价格数据
使⽤场景:前端需要显示"24⼩时交易量",调⽤The Graph API获取数据
实际应⽤流程举例:
场景:⽤户完成⼀次交换
- ⽤户输⼊:⽤户在界⾯输⼊"100 USDC换ETH"
- 状态更新:Zustand更新tokenIn和amountIn状态
- 触发查询:TanStack Query⾃动调⽤Routing API查询最优路径
- 显示结果:前端显示预估输出"0.05 ETH"
- ⽤户确认:⽤户点击"交换"按钮
- 检查授权:Wagmi检查USDC授权额度
- 授权不⾜:如果授权不⾜,弹出授权交易
- 执⾏交换:授权后,发送交换交易
- 等待确认:显示"交易确认中..."
- 完成:交易确认后,显示成功,更新余额
前端技术栈:

4.3 安全机制
4.3.1 重⼊攻击防护
c
// Uniswap V3 重⼊攻击防护
contract UniswapV3Pool {
// 使⽤ReentrancyGuard
modifier lock() {
require(unlocked == 1, 'LOK'); // Locked
unlocked = 0;
_;
unlocked = 1;
}
uint8 private unlocked = 1;
function swap(...) external override lock returns (...) {
// 交换逻辑
}
function mint(...) external override lock returns (...) {
// 添加流动性逻辑
}
}
4.3.2 价格操纵防护
价格操纵攻击的威胁:
在AMM中,价格由池⼦中的代币⽐例决定,这使得价格操纵成为可能。攻击者可以通过⼤额交易临时操纵价格,然后利⽤这个价格在其他协议中获利。
攻击场景举例:
场景1:借贷协议价格操纵攻击
- 攻击者观察:Aave使⽤Uniswap的现货价格计算抵押率
- 攻击者在Uniswap上⽤⼤额资⾦买⼊ETH,将价格从2000推到2100
- 攻击者⽴即在Aave上⽤ETH作为抵押品借出USDC
- 由于ETH价格被操纵到$2100,攻击者可以借出更多USDC
- 攻击者卖出ETH,价格回到$2000
- 攻击者获利:借出的USDC - 实际ETH价值
防护措施:
- 价格限制(sqrtPriceLimitX96):⽤户可以设置价格变化的上限
- 例如:⽤户设置价格不能超过$2050
- 如果交换会导致价格超过$2050,交易失败
- 这保护了⽤户,但也限制了交换的灵活性
- TWAP价格预⾔机:使⽤时间加权平均价格,⽽不是现货价格
- 短期价格操纵⽆法影响TWAP
- 攻击者需要维持操纵价格30分钟以上
- 这⼤⼤增加了攻击成本
- 滑点保护:⽤户设置最⼩输出量
- 如果实际输出低于最⼩值,交易失败
- 这保护⽤户免受价格突然变化的影响
实际应⽤场景 :
场景:⽤户设置价格限制交换
⽤户想⽤100万USDC换ETH,担⼼价格被操纵:
- ⽤户设置价格限制:不能超过2050(当前价格2000)
- 如果交换会导致价格超过$2050,交易失败
- 这保护了⽤户,但也可能因为正常的价格波动导致交易失败
- 建议:⼤额交易使⽤价格限制,⼩额交易可以放宽
价格操纵防护机制:
下⾯的流程图展示了价格限制检查的完整流程。这个机制确保了价格不会超出⽤户设定的范围。

代码实现:
c
// 价格限制检查
function swap(
address recipient,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96, // 价格限制
bytes calldata data
) external override lock returns (int256 amount0, int256 amount1) {
// 1. 检查价格限制
require(
zeroForOne
? sqrtPriceLimitX96 < sqrtPriceX96 && sqrtPriceLimitX96 >
TickMath.MIN_SQRT_RATIO
: sqrtPriceLimitX96 > sqrtPriceX96 && sqrtPriceLimitX96 <
TickMath.MAX_SQRT_RATIO,
'SPL' // Square root price limit
);
// 2. 执⾏交换
// ...
}
4.3.3 闪电贷防护
功能作⽤: 闪电贷是DeFi的创新⾦融⼯具,允许⽤户在单个交易中⽆抵押借⼊⼤量资⾦,只要在同⼀个交易中归还即可。这种机制为套利、债务重组、清算等操作提供了强⼤的⼯具。
核⼼特点:
- ⽆抵押借贷:不需要提供任何抵押品
- 即时执⾏:借款和还款在同⼀个交易中完成
- 原⼦性操作:要么全部成功,要么全部失败
- 低成本:通常只收取0.05%-0.1%的⼿续费
- ⾼流动性:可以借⼊协议中的全部可⽤资⾦
闪电贷执⾏流程详解
对接流程:
- 发起请求:⽤户调⽤闪电贷合约的借款函数
- 资⾦转移:协议将资⾦转移到⽤户指定的合约
- 回调执⾏:协议调⽤⽤户合约的executeOperation函数
- ⽤户逻辑:⽤户在回调中执⾏套利、清算等操作
- 余额检查:协议检查资⾦和⼿续费是否已归还
- 交易完成:如果归还成功,交易确认;否则整个交易回滚

c
// 闪电贷检查
function swap(...) external override lock returns (...) {
// 记录交换前的余额
uint256 balance0Before = IERC20(token0).balanceOf(address(this));
uint256 balance1Before = IERC20(token1).balanceOf(address(this));
// 执⾏交换逻辑
// ...
// 验证余额变化(防⽌闪电贷攻击)
require(
balance0After >= balance0Before,
'IIA' // Insufficient input amount
);
require(
balance1After >= balance1Before,
'IIA'
);
}
4.4 Gas优化技术
4.4.1 存储优化
c
// 使⽤打包存储减少SSTORE操作
struct Slot0 {
uint160 sqrtPriceX96; // 20 bytes
int24 tick; // 3 bytes
uint16 observationIndex; // 2 bytes
uint16 observationCardinality; // 2 bytes
uint16 observationCardinalityNext; // 2 bytes
uint8 feeProtocol; // 1 byte
bool unlocked; // 1 byte
} // 总共31字节,可以打包在⼀个slot中
Slot0 public slot0; // 单个SSTORE操作
4.4.2 批量操作优化
c
// V4 Flash Accounting:批量操作
function multicall(bytes[] calldata data) external payable returns (bytes[] memory results) {
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
(bool success, bytes memory result) = address(this).delegatecall(data[i]);
require(success, 'MC'); // Multicall failed
results[i] = result;
}
}
4.5 跨链架构
4.5.1 多链部署架构

4.5.2 跨链路由
c
// 跨链交换实现
async function crossChainSwap(
tokenIn: Token,
tokenOut: Token,
amountIn: bigint,
sourceChain: Chain,
targetChain: Chain
) {
// 1. 在主⽹执⾏交换
if (sourceChain.id === 1) {
await swapOnMainnet(tokenIn, tokenOut, amountIn);
}
// 2. 跨链桥接
await bridgeToken(tokenOut, sourceChain, targetChain);
// 3. 在⽬标链执⾏交换
if (targetChain.id !== 1) {
await swapOnL2(tokenOut, targetToken, targetChain);
}
}