Uniswap V2 核心必学知识点笔记

Uniswap必学知识点

一、基础认知

1. 什么是 Uniswap?

  • 去中心化交易所(DEX),基于「自动做市商(AMM)」机制,无需对手方,用户直接与智能合约资金池交易
  • 前端核心交互场景:代币兑换、添加/移除流动性、查询价格、闪电兑换调用
  • 相关的核心依赖:智能合约ABI、链上数据读取(价格、储备金)、交易发送(签名+上链)

2. V1 vs V2 核心差异

对比维度 Uniswap V1 Uniswap V2 前端关注重点
开发语言 Vyper(类Python) Solidity(主流) 后续交互、调试均基于Solidity合约,需熟悉Solidity基础语法
预言机功能 实时价格,易操纵 TWAP时间加权平均价,抗操纵 前端价格查询逻辑变更,需实现「打点存累加值」逻辑
核心功能 基础代币兑换 闪电兑换、双价格跟踪、非标ERC20兼容 新增闪电兑换回调合约调用、计价单位切换逻辑
存储优化 无打包存储 256位插槽打包存储 前端读取储备金、价格数据时,需对应合约存储结构

3. 关键技术选型原因

  • V2 改用 Solidity:因 Vyper 当时不支持「非标准 ERC20 返回值解析」「内联汇编调用 chainid」—— 前端调用代币合约时,需处理非标代币的兼容问题(比如有些代币 transfer 不返回布尔值)

二、AMM 底层逻辑

1. 恒定乘积公式(x*y=k)

  • 核心规则:资金池内两种代币余额乘积恒定(x=代币A余额,y=代币B余额,k=常数)
  • 前端视角:用户兑换时,合约根据 x*y=k 计算兑换数量,前端需实时展示「预估到账金额」「滑点」
  • 举例:用户用1 ETH换USDT,合约减少ETH余额、增加USDT余额,确保交易后 x*y 仍等于k

2. 套利定价机制(价格稳定的核心)

  • 逻辑:当 Uniswap 价格与市场价格偏离(偏差>手续费),套利者低买高卖,将价格拉回真实水平
  • 前端价值:Uniswap 价格可作为「链上价格源」,前端可基于此实现价格监控、套利工具等功能
  • 关键概念:套利者(arbitrageurs)、手续费(0.3%)、滑点(价格波动导致实际到账与预估差异)

三、V1 预言机漏洞

1. 漏洞本质(面试高频)

  • V1 直接用「实时交易价格」作为预言机数据,无抗操纵能力
  • 前端风险:若前端集成 V1 价格数据做业务逻辑(如衍生品结算、借贷抵押评估),会导致资金风险

2. 攻击流程(必须背会)

  1. 攻击者在 V1 资金池买入大量代币 → 人为抬高价格(x*y=k,买入方代币减少,价格上涨)
  2. 触发依赖该价格的合约结算(如衍生品合约)→ 按虚假高价获利
  3. 攻击者立即卖回代币 → 价格回归,全程通过「原子交易」完成(要么全成,要么全败)
  4. 前端视角:需能识别「不安全的价格源」,避免在产品中集成 V1 预言机

四、V2 预言机改进:TWAP 时间加权平均价(重中之重)

1. 核心目标:解决 V1 操纵问题,提供安全价格源

  • 前端应用场景:DeFi 产品定价(如借贷利率、衍生品结算)、前端价格展示(避免实时价格波动过大)

2. TWAP 原理

(1)核心工具:价格累加器(accumulator)

  • 合约逻辑:记录「每个区块起始价格 × 价格持续时间」,并累计求和
  • 前端关键认知:合约不存储历史累加值,需前端在「时间段起始时刻」主动调用合约,读取并存储累加值(aₜ₁)

(2)TWAP 计算步骤

  1. 初始化:在 t₁ 时刻调用合约 getReserves()priceCumulativeLast(),存储累加值 aₜ₁ 和当前时间戳 t₁
  2. 结束:在 t₂ 时刻再次调用合约,获取累加值 aₜ₂ 和时间戳 t₂
  3. 计算平均价:P_TWAP = (aₜ₂ - aₜ₁) / (t₂ - t₁)
  • 前端注意:需处理「定点数转换」(后续详细讲),且时间差单位为秒

(3)代码示例

// 假设使用 Ethers.js 交互
const getTWAP = async (pairContract, t1, a1) => {
  // t1:起始时间戳,a1:起始累加值(前端已存储)
  const [a2, t2] = await Promise.all([
    pairContract.priceCumulativeLast(), // 获取当前累加值
    Math.floor(Date.now() / 1000) // 当前时间戳(秒)
  ]);
  // 计算 TWAP(注意:需处理 BigInt 避免溢出)
  const twap = (a2 - a1) / BigInt(t2 - t1);
  // 转换为可读价格(UQ112.112 转小数)
  const readableTWAP = twap / BigInt(2 ** 112);
  return readableTWAP;
};

3. 计价单位坑

  • 核心问题:A/B 现货价格 = 1 / B/A 现货价格,但「算术平均价不互为倒数」
  • 举例:ETH/USDT 价格 1000 和 3000,平均价 2000;USDT/ETH 平均价 ≈ 0.000666,≠ 1/2000
  • 前端解决方案:V2 同时存储 A/B 和 B/A 两个累加器,前端需根据用户选择的「计价单位」调用对应数据

4. 恶意转账防护

  • 攻击原理:用户直接向资金池合约转代币,改变余额但不触发合约交互,导致价格异常
  • V2 解决方案:合约缓存储备金(每次交互后更新),用缓存数据计算价格,而非实时余额
  • 前端视角:无需额外开发,但需知道「读取储备金应调用 getReserves() 而非直接查合约余额」

五、定点数存储:UQ112.112

1. 为什么需要定点数?

  • Solidity 无原生小数类型,用「二进制定点数」编码价格,避免精度丢失
  • 前端核心工作:从合约读取定点数后,转换为用户可读的小数价格

2. UQ112.112 详解

  • 格式含义:U(无符号)、Q(定点数)、112.112(整数112位 + 小数112位)
  • 存储:占用 uint224(112+112=224位),剩余32位用于存储时间戳、溢出位
  • 转换逻辑:

<!---->

    • 合约返回的价格是「放大 2¹¹² 倍的整数」
    • 转换公式:可读价格 = 合约返回值 / 2¹¹²
    • 代码示例(Ethers.js):
// 假设从合约读取到 UQ112.112 格式的价格(BigInt)
const encodedPrice = await pairContract.price0CumulativeLast();
// 转换为可读价格(ETH/USDT 为例)
const decimalPrice = encodedPrice / BigInt(1n &lt;&lt; 112n);
console.log("可读价格:", decimalPrice.toString());

六、闪电兑换(Flash Swaps)

1. 核心价值(解决 V1 痛点)

  • V1 限制:必须先转付款代币,才能拿到目标代币,无法满足「先拿币再交易」场景
  • 闪电兑换:允许用户「先接收代币→执行操作→同一原子交易内还款」
  • 应用场景:套利工具、清算机器人、无本金链上操作(如用闪电兑换的代币做抵押)

2. 执行流程

  1. 调用 V2 合约 swap() 函数,指定:
    • 要借的代币数量、目标代币
    • 回调合约地址(用户自定义,包含还款逻辑)
  1. 合约逻辑:
    • 先转出目标代币给用户
    • 调用回调合约,用户在回调中执行操作(如套利、平仓)
    • 回调结束后,检查资金池余额是否满足 x*y≥k(确保还款)
  1. 关键注意点:
    • 回调合约必须实现 uniswapV2Call() 接口(否则调用失败)
    • 需预估足够 Gas(闪电兑换涉及多步操作,Gas 不足会回滚)
    • 原子交易特性:要么全成,要么全败,无中间状态风险

3. 调用示例(简化版)

const flashSwap = async (pairContract, borrowToken, borrowAmount, callbackContract) => {
  // 构造交易参数
  const params = [
    borrowToken.address, // 要借的代币地址
    ethers.utils.parseUnits(borrowAmount, 18), // 借款数量(需处理小数)
    "0x", // 额外数据(可选)
    callbackContract.address // 回调合约地址
  ];
  // 发送交易(需用户签名)
  const tx = await pairContract.swap(...params, {
    gasLimit: 300000 // 预估足够 Gas
  });
  await tx.wait();
  console.log("闪电兑换成功");
};

七、Uniswap V2 交互核心技能

1. 合约交互基础

  • 必备工具:Ethers.js/Web3.js(前端链上交互库)、MetaMask(钱包连接)
  • 核心步骤:
    1. 连接钱包(获取用户地址、链ID)
    2. 加载合约ABI(从Etherscan下载或项目源码获取)
    3. 读取数据(价格、累加值、储备金,call 方法,无需Gas)
    4. 发送交易(兑换、闪电兑换,send 方法,需用户签名+Gas)

2. 关键合约方法

方法用途 合约方法 前端调用场景
查询储备金 getReserves() 计算实时价格、展示资金池规模
查询价格累加值 price0CumulativeLast()/price1CumulativeLast() 计算 TWAP 平均价
代币兑换 swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) 普通兑换、闪电兑换
添加流动性 addLiquidity(address tokenA, address tokenB, uint amountADesired, uint amountBDesired, uint amountAMin, uint amountBMin, address to, uint deadline) 前端添加流动性功能

3. 常见问题与解决方案

  • 问题1:价格转换精度错误 → 解决方案:严格按 UQ112.112 格式转换,使用 BigInt 避免溢出
  • 问题2:交易回滚 → 解决方案:检查 Gas 充足性、代币授权(approve)、参数格式(如金额单位为 wei)
  • 问题3:非标 ERC20 兼容 → 解决方案:调用代币合约时,处理「transfer 不返回布尔值」的情况(用 staticCall 预校验)

八、面试高频考点速记

  1. V1 预言机漏洞原理及攻击流程(必背)
  2. V2 TWAP 计算逻辑及前端实现步骤
  3. 闪电兑换的核心特性(原子交易、回调合约、还款约束)
  4. UQ112.112 格式的含义及前端转换方法
  5. V2 为什么同时存储 A/B 和 B/A 两个价格累加器
  6. 恶意转账攻击的防护逻辑(缓存储备金)

九、学习资源推荐

  1. 合约交互实践:Ethers.js 官方文档 + Uniswap V2 合约ABI(Etherscan下载)
  2. 可视化工具:Uniswap Info(查看链上数据,对照前端交互逻辑)
  3. 实战项目:仿写简易 Uniswap 前端(实现兑换、价格查询功能)
  4. 避坑指南:Uniswap 官方文档「Frontend Integration」章节
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
mengbuluo222
mengbuluo222
0x9ff1...faa5
前端开发求职中... 8年+开发经验,拥有丰富的开发经验,擅长VUE、React开发。