Uniswap的Q64.96解读:Hook开发者的基本安全提示

  • bloqarl
  • 发布于 2024-07-31 13:46
  • 阅读 38

本文深入探讨了Uniswap中Q64.96数字的概念及其在智能合约中的应用,强调了其在流动性管理与安全编码中的重要性。作者详细阐述了Q64.96数字的定义、工作原理、常见陷阱和安全隐患,并提供了最佳实践与测试策略以确保安全性,有助于开发者更好地理解和使用这一高级技术。

嘿,DeFi爱好者们!如果你在探索Uniswap的世界,你可能听说过ticks、流动性池,甚至可能还有一些叫做Q64.96的数字。别担心,如果最后一个听起来像是秘密代码——我们马上就要揭开它的神秘面纱!

你可能在想:“哦,太好了,又一次深入Uniswap的ticks?”但等等!虽然我们会提到ticks(因为,坦率地说,它们非常重要),但我们在这里谈论的是另一件同样重要但常常被忽视的事情:Q64.96数字。

“我为什么要关心这些Q什么的数字?”我听到你问。

好吧,如果你在Uniswap上构建或创建hooks,理解Q64.96数字可能是让一个安全、高效的合约与……嗯,反正你不想站在那个方程的另一边之间的区别。

所以,系好安全带!我们即将踏上探索Q64.96数字的旅程,看看如何安全地使用它们,并在途中避免一些棘手的陷阱。

简要回顾:Uniswap中的Ticks和Q64.96数字

好的,我们先来个快速回顾。你知道Uniswap v3中的ticks吗?不知道?好的,这里是重点总结:

Ticks就像Uniswap的流动性曲线上的价格点。每个tick代表0.01%的价格变化。这是Uniswap如何允许流动性提供者将资金集中在特定价格区间的方式。挺不错的,对吧?

但这里才有趣。你认为Uniswap是如何在内部表示这些价格的?常规小数?整数?不!有了Q64.96数字。

“Q64.96?听起来像个星际大战中的机器人!”你可能开玩笑道。

接近,但还不完全。Q64.96数字是一种使用整数表示小数的特殊方法。为什么?因为用于以太坊智能合约的语言Solidity与小数不太兼容。

喜欢吗?很好!因为我们即将深入这些神秘的数字。

理解Q64.96数字

好的,让我们深入研究Q64.96数字。但等一下,它们究竟是什么,我们为什么需要它们?

Q64.96数字是一种特定类型的定点数表示。把它们想象成一种在仅理解整数的系统中表示小数的方法。

但为什么是64和96呢?好问题!

“64”表示用于整数部分的位数,而“96”是用于小数部分的位数。总共有160位。你可能在想:“这不是过分了么?我们为什么需要这么多位?”好吧,在Uniswap中,我们处理的是广泛的价格和流动性金额。我们需要足够的精度来处理几美分的微小分数,但也需要足够的范围来表示数十亿美元。这就是Q64.96发挥作用的地方。

让我们通过一个例子来分解一下。我们如何将数字1.5表示为Q64.96数字?

操作步骤如下:

  1. 从1.5开始
  2. 将其乘以2⁹⁶(即2的96次方)
  3. 结果就是我们的Q64.96表示

在代码中,它看起来像这样:

uint256 onePointFive = 1.5 * 2**96;
// 这相当于 118842243771396506390315925504

现在你可能在想:“为了表示1.5,这个数字也太大了!”你说得对,确实大。然而,这可以让我们在不因舍入误差而损失精度的情况下进行精确计算。

但关键是——我们怎么在计算中实际使用这些数字?别担心,我们将在下一节中讨论这个问题。

常见问题和安全漏洞

既然我们了解了Q64.96数字是什么,那就来谈谈使用它们时可能出现的问题。毕竟,高精度意味着高责任,对吧?

首先:溢出和下溢。

你可能熟悉常规整数数学中的这些情况,但在Q64.96中,风险更大。为什么?因为我们默认处理的是更大的数字。

假设你试图将两个Q64.96数字相乘。你可能会想:“没问题,我会像普通整数那样相乘。”但等等!记住这些数字有多大吗?直接相乘几乎肯定会导致溢出。

以下是一个不该这样做的示例:

uint256 a = 1.5 * 2**96;  // 1.5的Q64.96表示
uint256 b = 2.0 * 2**96;  // 2.0的Q64.96表示
uint256 result = a * b;   // 别这么做!

你可能会在想:“好吧,那我们怎么安全地相乘呢?”

好问题!我们需要使用特殊函数来正确处理数学运算。我们将在最佳实践部分中讨论这个问题。

另一个常见问题是精度损失。

你可能在想:“我只需在最后除以2⁹⁶就可以回到普通数字。”但这没那么简单。在Solidity中,除法会截断结果,这意味着你可能失去重要的精度。

例如:

uint256 x = (1 * 2**96) / 3;  // 尝试表示1/3
uint256 y = x * 3;  // 你可能期望这个等于1 * 2**96,但实际上并不是!

最后,让我们谈谈类型混淆。

很容易忘记你在处理Q64.96数字,把它们当作普通整数。这可能会导致一些非常奇怪的结果。

例如:

uint256 price = 1500 * 2**96;  // 1500的Q64.96表示
require(price > 1000, "价格太低");  // 这将始终为真!

你可能会在想:“但1500明显大于1000,那么问题出在哪里?”

问题在于我们在将Q64.96数字与常规整数进行比较。由于乘以2⁹⁶,Q64.96数字要大得多。

那么,我们如何避免这些陷阱呢?别担心,在下一节的最佳实践中有应对策略。

使用Q64.96数字进行安全编码的最佳实践

好了,现在我们已经让你对可能出错的事情感到害怕,让我们谈谈如何正确处理。别担心,不全是悲观绝望!

首先,你可能会想:“我不能像处理常规整数那样使用SafeMath吗?”好的直觉!但不幸的是,SafeMath并不适用于Q64.96数字。那么我们该用什么呢?

欢迎使用FullMath和FixedPoint96库!这些将是你在处理Q64.96数字时的新好朋友。让我们看一个示例:

import '@uniswap/v3-core/contracts/libraries/FullMath.sol';
import '@uniswap/v3-core/contracts/libraries/FixedPoint96.sol';

function multiplyQ64x96(uint256 a, uint256 b) internal pure returns (uint256) {
    return FullMath.mulDiv(a, b, FixedPoint96.Q96);
}

“但等一下,”你可能会说,“这个mulDiv函数是什么?为什么不直接相乘和除法?”

好问题!mulDiv是一个特殊函数,它以避免溢出并保留精度的方式执行乘法和除法。

那么,除法呢?还记得之前提到的精度损失问题吗?我们是这样处理的:

function divideQ64x96(uint256 a, uint256 b) internal pure returns (uint256) {
    return FullMath.mulDiv(a, FixedPoint96.Q96, b);
}

看到我们怎么做了吗?在除法之前乘以Q96。这有助于保留精度。

最后,让我们谈谈比较。还记得我们的类型混淆问题吗?以下是正确比较Q64.96数字的方法:

function isGreaterThan(uint256 a, uint256 b) internal pure returns (bool) {
    return a > b;  // 假设a和b都是Q64.96数字
}

简单,对吧?关键是确保在比较之前,两个数字都必须处于Q64.96格式。

测试和审计策略

此时,你可能会在想:“我如何确保我的Q64.96实现真的安全?”

好问题!虽然单元测试很重要,但在DeFi安全中,我们需要使用更强大的工具:模糊测试和形式化验证。

模糊测试: 找出未知的未知

模糊测试就像向你的代码投掷上千只猴子,看看能破坏什么。 好吧,不是字面意义上,而是差不多!它涉及生成大量随机(或半随机)输入来测试你的函数。这对于Q64.96操作尤其重要,因为边缘情况可能会非常尖锐。

例如,模糊测试工具可能会用极端值测试你的multiplyQ64x96函数:

function testFuzzMultiplyQ64x96(uint256 a, uint256 b) public {
    vm.assume(a <= type(uint256).max / 2**96);
    vm.assume(b <= type(uint256).max / 2**96);
    uint256 result = multiplyQ64x96(a, b);
    assert(result <= type(uint256).max);
}

像Echidna或Foundry内置模糊测试工具这样的工具可以运行成千上万的这些测试,可能会发现你之前未曾考虑的漏洞。

形式化验证: 数学的确定性

现在,如果模糊测试像是投掷猴子在你的代码上,那么形式化验证就像是一群数学家来证明你的代码是正确的。它使用数学方法来证明或反驳你的算法的正确性。

对于Q64.96数字,形式化验证尤其强大。它可以证明你的操作永远不会溢出、损失精度或违反你指定的任何其他属性,涵盖所有可能的输入。

这就是为什么事情变得更有趣的地方:Zealynx Security专门应用这些先进技术于DeFi协议。我们利用模糊测试和形式化验证揭示了其他方法未能发现的微妙错误。看看这个 高级安全测试套件 是如何为这个DeFi协议实现的。

https://github.com/ZealynxSecurity/Possum-Labs/tree/main/test/V2MultiAsset

综合方法

当然,最佳策略是结合多种方法:

  1. 首先进行全面的单元测试以确保基本正确性。
  2. 应用模糊测试以发现意想不到的边缘情况。
  3. 使用形式化验证确保数学确定性。
  4. 最后,考虑专业审计,以捕捉你可能遗漏的任何内容。

记住,在DeFi安全方面,你决不能太谨慎。风险高,甚至极小的Q64.96计算错误也可能导致重大损失。

通过结合这些先进的测试策略,你不仅仅是在编写安全代码——你是在为一个更强大、更可信的DeFi生态系统奠定基础。

结论

呼!我们已经覆盖了很多内容,对吧?让我们快速回顾一下:

  1. Q64.96数字是Uniswap和类似DeFi协议进行精确计算的强大工具。
  2. 它们伴随有自己的陷阱,包括溢出/下溢风险和精度损失。
  3. 使用正确的库并遵循最佳实践可以帮助你避免这些问题。
  4. 彻底的测试和专业审计对于确保你的Q64.96计算的安全至关重要。

记住,在DeFi中,精确和安全是密不可分的。通过掌握Q64.96数字,你不仅是在编写更好的代码——你还在为一个更安全、更可靠的DeFi生态系统做出贡献。

让我们联系:

https://x.com/TheBlockChainer

https://www.linkedin.com/in/carlos-vendrell-felici/

https://linktr.ee/bloqarl

如果你需要帮助,请协作:

  • 智能合约审计(Solidity、Rust、Cairo)
  • 高级安全测试套件(模糊测试、辨别测试+形式化验证)
  • 智能合约开发
  • Web2渗透测试

https://github.com/ZealynxSecurity

https://x.com/ZealynxSecurity

  • 原文链接: medium.com/@bloqarl/unis...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
bloqarl
bloqarl
江湖只有他的大名,没有他的介绍。