使用现代 Solidity(或者如果你真的想挑战高难度模式,可以使用 Huff)从头开始重建 Uniswap v2 是非常有教育意义的。以下是实现这一目标的一些提示和建议。
- 使用更新版本的 Solidity。请注意,这将导致语法上的变化。
- 将定点数替换为自定义数据类型。
- ERC20 代币使用 Solady 的 ERC20 实现,以节省 gas。
- 不要使用 Uniswap V2 当前的重入保护,它不再具有 gas 效率了,请使用 OpenZeppelin 的或其他替代方案。
- 请注意在价格预言机中添加 unchecked,因为它预期会发生溢出。
- 在 Solidity 0.8.0 或更高版本中不要使用
safeMath。 - 如果你不单独实现 router,则需要将针对滑点的安全检查内置到合约中。EOA 无法在交易中一并发送代币。
- 确保将重入锁放在正确的位置。Uniswap V2 是否容易受到只读重入攻击?为什么会或为什么不会?
- 由于 Solidity 的更新,factory 合约可以在不使用 assembly 的情况下进行简化。
- 当心 fee-on-transfer 代币或 re-basing 代币。
- 使用 Solady 库可以更高效地实现平方根函数,但请确保你的舍入方向是正确的。
- Uniswap 使用魔法数字硬编码了手续费,这并不是一种理想的编码方式。
- 不要忘记遵循专业的 Solidity 风格指南(Uniswap V2 并没有遵循)。
- factory 追踪 pair 的方式在 gas 上并不高效,请尝试改进它。
- 原始实现中的一些 storage 变量可以设置为 immutable(Uniswap V2 发布时 immutable 变量还不可用)。
- (通常情况下)相比于 require 语句,自定义错误会带来更低的部署成本。
- 在销毁初始供应量时,确保
totalSupply不会降为零。否则,针对首次存款攻击的防御将失效。一些 burn 的实现会减少总供应量,而不是像 Uniswap 预期的那样锁定资金。 - 在计算份额时,请注意按正确的顺序执行
burn、mint和update储备金。 - Uniswap V2 的
_safeTransfer容易受到内存扩展攻击(发生概率极低,但仍需防范)。因为只会读取一个 bool 值,所以最好仅通过returndatacopy复制一个 word。尽量避免养成从其他合约读取全部返回数据的习惯。 - 一个很好的不变量测试是:如果没有调用
burn,liquidity 绝对不应该下降。 - 不要将余额变量或池子余额作为预言机,因为它们容易受到闪电贷攻击。
- 记住,在进行交易或铸造/销毁份额时,始终要朝着有利于池子的方向进行舍入。
- 不要忘记编写单元测试。
- 阅读我们关于 Solidity gas optimization 的文章,获取如何提高性能的灵感(建议在你完成第二版或第三版 Uniswap 克隆版代码并编写完测试之后进行)。如果你想挑战高难度模式,这里有几个机会可以使用 assembly 来优化 gas,而不会显著牺牲可读性。
- 尝试一下 Damn Vulnerable Defi Puppet V2。对现在的你来说,解决它应该很容易。
在 RareSkills 了解更多
本材料是我们高级 Solidity Bootcamp 的一部分。请查看该项目以了解更多信息。
初次发布于 2023 年 11 月 1 日