{"content":{"title":"数据分析师如何分析合约代码","body":">- 原文链接：https://read.cryptodatabytes.com/p/how-to-navigate-contract-code-solidity\r\n>- 译者：[AI翻译官](https://learnblockchain.cn/people/19584)，校对：[翻译小组](https://learnblockchain.cn/people/412)\r\n>- 本文永久链接：[learnblockchain.cn/article…](https://learnblockchain.cn/article/9282)\r\n    \r\n\r\n\r\n通常情况下，你开始研究一个协议时，会迅速找到一个调用 5 个以上合约并引发 20 个以上日志的示例交易。你去查看他们的文档（如果有的话），可能会看到这样的内容：\r\n\r\n![](https://img.learnblockchain.cn/attachments/migrate/1725953406068)\r\n> Aave v3 实际上还有 [10 个合约](https://docs.aave.com/developers/deployed-contracts/v3-mainnet/ethereum-mainnet)，但图片太长\r\n\r\n\r\n你如何浏览这些 Solidity 合约以找到你分析所需的答案和数据？我将在本指南中涵盖一个完整的示例以及我的提示和技巧。\r\n我们将研究新的 [Zora x Uniswap](https://dune.com/ilemi/zora-extensions) 合约，该合约于 2024 年 8 月初部署。\r\n\r\n你应该先尝试在 [Dune 上搜索](https://docs.dune.com/web-app/search#advanced-search) 涉及某个协议的现有查询/仪表板，因为通常阅读/分叉现有 SQL 比直接阅读 Solidity 更容易。\r\n\r\n##  从交易开始\r\n\r\n每当你开始分析一个协议时，这里有你想找的两个示例交易：\r\n\r\n*   主要合约部署交易（部署新的 NFT 或 Uniswap 池）\r\n    \r\n*   主要操作交易（交换、铸造等）\r\n    \r\n\r\n我说“主要”是因为通常有许多合约和操作，但它们通常都源于一个起始合约/操作。大多数情况下，你的实际起点来自某个地方的推文，你必须逐步找到这两个交易。\r\n\r\n让我们以 Zora 在其协议上的新 Uniswap 扩展为例——本质上允许你创建一个可以在 Uniswap 上作为 ERC20 进行交易的 ERC1155，而不是在 OpenSea/Blur 上进行交易。他们发布了这份 [简短概述指南](https://twitter.com/zora/status/1821579634108178546)，还发布了 [他们在这个新协议上的第一次铸造](https://twitter.com/zora/status/1821273397760041200)。在真正的加密风格中，真正的文档通常要到很晚才会出现（有时甚至永远不会）。你仍然应该尝试先搜索文档，但这个示例将完全依赖链上数据进行侦查。\r\n\r\n \r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/09/h2hR4fSc66dff6d26eced.png)\r\n> [第一次铸造](https://zora.co/collect/zora:0x86af55fc811fef6f9729d32adcf2c253ca5a16c1/2?referrer=0x2Ae8c972fB2E6c00ddED8986E2dc672ED190DA06) \r\n\r\n我通常发现获取主要合约和交易的最简单方法是通过自己与产品互动。因此，当它首次上线时，我 [铸造了一些 Limitless Zorb](https://explorer.zora.energy/tx/0xf8a74dd53ecff823bf3614c7dede6d9f9ce9c31525710cd3fb7ce5d00191720b)。\r\n\r\n此交易为我提供了一个可以稍后用于交易量计算的主要操作，但更重要的是，它为我提供了两个主要合约的参考：\r\n\r\n1.  **ZoraTimedSaleStrategyImpl**：我知道这肯定是这个升级的新协议核心，因为它是主要的“目标”合约，可能是设置铸造/二级市场配置的地方。\r\n    \r\n2.  **Limitless Zorb**：我知道这是一个 ERC1155 代币，某种程度上被转换为 ERC20 以便在 Uniswap 上交易。\r\n    \r\n\r\n区块浏览器上的合约页面总是有一个“创建交易哈希”链接（通常你首先会被带到代币页面，然后你必须再次点击合约才能到达合约页面）。\r\n\r\n \r\n\r\n![](https://img.learnblockchain.cn/attachments/2024/09/3lQeu4yk66dff82a0f83d.png)\r\n> [Limitless Zorb 集合合约](https://explorer.zora.energy/address/0x86aF55FC811FEF6f9729D32aDcf2c253CA5A16C1)\r\n\r\n\r\n所以如果快速汇总一下我们所拥有的信息，我们得到：\r\n\r\n*   **[ZoraTimedSaleStrategy](https://explorer.zora.energy/address/0x777777722D078c97c6ad07d9f36801e653E356Ae?tab=contract)** (ZTSS)\r\n    \r\n    *   [创建交易](https://explorer.zora.energy/tx/0x9157f50c80a93ae135267644e6fd4ae9b5b558fce2f86557445fe0aeda535d09) - 快速查看告诉我这不是一个工厂合约，因为它调用了一个“确定性调用者”合约，该合约用于 [使用 CREATE2 部署](https://book.getfoundry.sh/tutorials/create2-tutorial)，而不是 CREATE。这将其部署到一个确切的地址，通常是为了使同一合约在多个链上具有相同的地址。\r\n        \r\n*   **[ERC1155/ERC20 集合 (Limitless Zorb)](https://explorer.zora.energy/address/0x86aF55FC811FEF6f9729D32aDcf2c253CA5A16C1)**\r\n    \r\n    *   [创建交易](https://explorer.zora.energy/tx/0x8cd4b29e5ab191d8fa805c829f7a89a3e2fe186acbd934f5b98e8862e54cc9a4?tab=index) - 我看到调用了一个“工厂”来部署这个，所以这可能是一个工厂创建的合约，每个集合都有自己的部署。\r\n        \r\n    *   [铸造交易](https://explorer.zora.energy/tx/0xf8a74dd53ecff823bf3614c7dede6d9f9ce9c31525710cd3fb7ce5d00191720b?tab=index) - 我从产品界面发送的交易\r\n        \r\n    *   我们还没有二级市场/Uniswap 交易\r\n        \r\n\r\n考虑到这一基础，我们可以定义一个问题并深入代码。\r\n\r\n## 深入合约\r\n\r\n好了，一旦你对主要合约和交易有了大致了解，你就可以开始侦查了。你应该列出你的问题，并尝试浏览代码以回答每一个问题，对于本指南，我想回答的问题是：\r\n\r\n> **问题**：“铸造期结束后，Uniswap 池是如何启动的，谁控制第一个 LP 仓位，谁从中获得交换费用？”\r\n \r\n\r\n我们可以猜测，销售的权限/功能与 ZoraTimedSaleStrategy (ZTSS) 合约相关，而不是与集合合约本身相关。有趣的是，我在集合创建交易中没有看到这个合约发出的任何事件——这对我来说是一个信号，表明你可以创建一个 ERC1155，并可能在之后附加 ERC20/定时销售组件。\r\n\r\n我去查看 [ZTSS 合约代码页面](https://explorer.zora.energy/address/0x777777722D078c97c6ad07d9f36801e653E356Ae?tab=contract)，我首先看到的是 [它是一个代理](https://docs.openzeppelin.com/contracts/4.x/api/proxy)。这意味着“代理”处理变量的存储，但有一个“实现”合约包含实际的合约逻辑。你通常需要转到“读取代理”或“写入代理”选项卡以获取实现地址：\r\n\r\n \r\n\r\n![代理与实现](https://img.learnblockchain.cn/attachments/2024/09/AsKvDU6J66dffa1a4d523.png)\r\n> [实现地址](https://explorer.zora.energy/address/0xA582f080c36B7551dbC541a0CFFeB6101183C9b3?tab=contract)\r\n\r\n> ⚠ 警告：代理可以升级以指向不同的实现以更改逻辑。此示例中的代理在 2024 年 8 月 23 日升级为 [此实现](https://explorer.zora.energy/address/0x9aCA1F8E0472f2Cc66BBE2e1981a2a8Ad2fE5720?tab=contract)，但本指南的其余部分引用了 [旧实现](https://explorer.zora.energy/address/0xA582f080c36B7551dbC541a0CFFeB6101183C9b3?tab=contract)。这是为了引入 [新的 launchMarket() 条件](https://x.com/zora/status/1827088045805621751)。\r\n\r\n好了，现在我们有了一些进展…… ，查看代码浏览器，我可以看到这里实际上有数十个继承/引用的合约。\r\n \r\n\r\n![](https://img.learnblockchain.cn/attachments/migrate/1725952702828)\r\n\r\n \r\n\r\n你可以直接忽略很多内容——但是最重要的是要关注“接口”。接口用于 [让一个合约调用另一个合约的函数](https://www.alchemy.com/overviews/solidity-interface)。我知道 Uniswap 实际上是一组不同的合约，所以我想寻找用于创建/向池添加流动性的 Uniswap 接口。\r\n\r\n这就像在文件顶部的导入中按 ctrl+f “IUniswapV3Pool” 一样简单。我找到了 5 个匹配项，其中 2 个是导入行，3 个位于 285-290 行。\r\n\r\n![](https://img.learnblockchain.cn/attachments/migrate/1725952702831)\r\n\r\n向上滚动一点，我可以看到调用的是这个 “launchMarket()” 函数。这里的说明说，任何人在主要销售结束后都可以调用它来创建 uniswap 池。\r\n\r\n![](https://img.learnblockchain.cn/attachments/migrate/1725952702834)\r\n> 如果你已经有那个地址，你也可以通过查找 uniswap 池和池的创建 tx 哈希来找到这个。\r\n\r\n现在如果我向下滚动，我会注意到实际上没有看到 uniswap 池被创建或流动性被添加 - 只是设置了一个价格。相反，函数的末尾调用了一个 “IERC20Z” 接口，并带有 “activate” 函数。\r\n\r\n![](https://img.learnblockchain.cn/attachments/migrate/1725952702833)\r\n\r\n好的 - 所以现在我们知道必须以某种方式找到 ERC20Z 合约并检查 “activate” 函数的定义。你的思维应该希望转向寻找与Limitless zorb 收藏相关的 ERC20Z。有几种方法可以做到这一点：\r\n\r\n*   期望在集合上有一个读取函数来获取 erc20 地址\r\n    \r\n*   查找集合创建 txn 中的某个事件以获取 erc20 地址\r\n    \r\n*   查找与集合合约相关的 ZTSS 中的某个事件/函数，以将 erc20 合约与集合合约关联\r\n    \r\n\r\n前两个实际上在这种情况下是死胡同，因为 erc20 是集合的可选组件。如果我去 ZTSS 代理页面和 “logs” 列，我可以看到一堆 SaleSet() 事件，似乎包含 “erc20zAddress” 和 “poolAddress” 字段。这并不总是最可靠的方法，我建议你将合约插入我的 [EVM 快速入门仪表板](https://read.cryptodatabytes.com/p/how-to-start-analyzing-any-web3-protocol) 并检查最常发出的事件/函数的示例。\r\n\r\n \r\n![image.png](https://img.learnblockchain.cn/attachments/2024/09/ROIrfHt566e007b7b4ca1.png)\r\n> [ZTSS proxy 的 Logs 页](https://explorer.zora.energy/address/0x777777722D078c97c6ad07d9f36801e653E356Ae?tab=logs)\r\n\r\n\r\n我会点击最近的 SetSale() 交易之一，看看发生了什么。通过跟踪日志，我可以看到 ERC20z 和 uniswap 池实际上在销售扩展被激活时已经部署（CREATE2）！它只是还没有任何流动性，因此无法进行交易。\r\n \r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/09/45uPkqA066e008f941cf9.png)\r\n> [日志链接](https://explorer.zora.energy/tx/0x58a0aef9c1d8993c7fbf0d4078602a01252b4a5f769061ad270ed8d9de1d6695?tab=logs)\r\n\r\nERC20z 也是从 ZTSS 合约部署的，所以我可以 100% 确定有某个读取函数可以搜索集合并获取其 ERC20z（和 uniswap 池）详细信息。果然，我们有一个 “sale()” 读取函数：\r\n\r\n \r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/09/Ww1OaNYS66e0092696fdf.png)\r\n> [阅读“sale()”函数以及 Limitless Zorb 详细信息](https://explorer.zora.energy/address/0x777777722D078c97c6ad07d9f36801e653E356Ae?tab=read_proxy)\r\n\r\n\r\n我插入 [Limitless Zorb 收藏地址和代币 ID](https://zora.co/collect/zora:0x86af55fc811fef6f9729d32adcf2c253ca5a16c1/2)（来自 URL）并获得我们的 erc20zAddress 和 poolAddress！如果你早些时候注意到它，你也可以通过 ctrl + f “IERC20Z” 在 ZTSS 实现代码中找到这个😉。\r\n\r\n好的，现在我们 [erc20z 地址](https://explorer.zora.energy/address/0xF3cDcA11108780008D04491Ac699E962abEFF151) - 所以现在让我们回到寻找 “activate()” 函数。它是一个代理，所以我再次选择 “读取合约” → “实现” 以获取实际代码。然后我只需 ctrl + f 查找 “function activate(“，因为这就是在 solidity 中定义函数的方式。\r\n\r\n \r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/09/6IC2x5P566e0095fdb2f0.png)\r\n> [合约代码](https://explorer.zora.energy/address/0xA23bD7012a050166E24a2A67B33Adb63E75eF37c?tab=contract_code) \r\n\r\n\r\n\r\n\r\n在该函数中，我看到这一关键行，通过接口调用 Uniswap 仓位管理器(Position Manager)以创建第一个流动性仓位：\r\n\r\n![](https://img.learnblockchain.cn/attachments/migrate/1725952702847)\r\n\r\n它实际上指向一个 “royalties” 地址！好的，再次 ctrl + f 查找 royalties 的定义。\r\n\r\n![](https://img.learnblockchain.cn/attachments/migrate/1725952703521)\r\n\r\n这是一个公共变量，这意味着它自动获得一个读取函数。我去读取合约 “royalties()” 以获取版税合约地址：\r\n\r\n![](https://img.learnblockchain.cn/attachments/migrate/1725952703519)\r\n\r\n我们现在离目标很近 - 接下来让我们去查看版税合约代码。这个合约很简单，因为只有两个相关的公共函数：claim() 和 claimFor()，它将流动性池仓位的费用传递给代币的创建者。这里的复杂性在于读取 “内部” 函数， [这些基本上是无法在合约外部调用的函数](https://learnblockchain.cn/docs/solidity/contracts.html#id3) ，并在外部函数中调用。\r\n\r\n我通常将代码粘贴到 chatgpt 中，并问它：“这是一个 solidity 合约。你能画出一个节点图，显示所有函数调用的调用流吗？将公共/外部函数显示为橙色，内部函数显示为浅蓝色，接口函数显示为灰色。将其显示为从左到右的树状图，其中函数调用的开始在左侧。增大图形大小以适应所有节点。”\r\n\r\n \r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/09/EqdYH4wu66e00a294fbb4.png)\r\n> 调用图[链接](https://chatgpt.com/share/c9f5c1d0-615d-43cb-a8d9-f911f9df24ee)\r\n\r\n\r\n所以我们感兴趣的函数调用流看起来像这样：\r\n\r\n*   `claim()` →` _claim()` → `_collect()` / `_transfer()`\r\n    \r\n\r\n你只会在交易跟踪中看到 “claim()” 被调用，因为合约内调用没有跟踪（除了memory traces\r\n，但我们不讨论这个）。\\_collect() 通过调用position manager\r\n  uniswap 接口从池中获取费用，而 \\_transfer() 将这些费用传递给创建者。\r\n\r\n值得注意的是，\\_transfer() 函数有一部分费用归 Zora 团队所有。\r\n\r\n \r\n\r\n![](https://img.learnblockchain.cn/attachments/2024/09/jTMo6xXE66e00b7125a62.png)\r\n> [Royalties 合约](https://explorer.zora.energy/address/0x77777771DF91C56c5468746E80DFA8b880f9719F?tab=contract)\r\n\r\n\r\n我们可以在 getFee() 函数中找到一个公共变量 feeBps，所以我们从合约的 “读取” 页面读取它：\r\n\r\n![](https://img.learnblockchain.cn/attachments/migrate/1725952703516)\r\n\r\n它以 “bps” 为单位，这意味着基点，其中 100 个基点等于 1 个百分点。2500/100 是 25，因此他们从初始 LP 奖励中收取 25% 的费用！如果你去 uniswap 池，你会看到它在所有交换中收取 1% 的费用。因此，实际上，创建者在所有交换中收取 0.75% 的费用，而 Zora 收取 0.25% 的费用。\r\n\r\n值得注意的是，合约中没有任何提取 LP 仓位的函数 - 因此该 LP 仓位永远无法从池中提取。创建者（和 Zora）也无法 rug 池，尽管这些 ERC20/ETH LP 代币现在也无法认领（除非你买下整个曲线）。\r\n\r\n总的来说，我们现在得到了答案：\r\n\r\n*   **Uniswap 池在铸造期结束后是如何启动的？** launchMarket() 函数，任何人都可以调用它。池已经在 SetSale() 事件中与 erc20z 一起部署。\r\n    \r\n*   **谁控制第一个 LP 仓位？** 没有人，它归版税合约所有并被销毁。版税合约仍然可以从该仓位收取费用。\r\n\r\n*   **谁获得了交换费用？** 费用在 Zora 和创作者之间分配，分别为 0.25%和 0.75%。\r\n\r\n## 合约精通\r\n\r\n多么奇妙的旅程！如果你跟随了这里的一切，那么你确实掌握了如何在合约、接口、代理和交易之间导航。仅依靠区块浏览器和像 Dune 这样的工具来做到这一点将使你成为超级分析师。\r\n\r\n再次强调，所有这些都是为了回答“在铸造期结束后，Uniswap 池是如何启动的，谁控制第一个 LP 仓位，以及谁获得了交换费用？”\r\n\r\n我们在这段旅程中找到的资源列表：\r\n\r\n*   **[ZoraTimedSaleStrategy](https://explorer.zora.energy/address/0x777777722D078c97c6ad07d9f36801e653E356Ae?tab=contract)** (ZTSS), **([impl](https://explorer.zora.energy/address/0xA582f080c36B7551dbC541a0CFFeB6101183C9b3?tab=contract))**\r\n    \r\n    *   [创建交易](https://explorer.zora.energy/tx/0x9157f50c80a93ae135267644e6fd4ae9b5b558fce2f86557445fe0aeda535d09) - 快速查看告诉我这不是一个工厂合约。所以它必须在所有集合之间共享。\r\n        \r\n    *   [SaleSet ERC20/池创建交易](https://explorer.zora.energy/tx/0x79f03b94feae1f4c9463621dfc0288fdc045e88a5a3112b76a34a1fb421dfea5)\r\n        \r\n*   **[ERC1155/ERC20 集合（Limitless Zorb）](https://explorer.zora.energy/address/0x86aF55FC811FEF6f9729D32aDcf2c253CA5A16C1)**\r\n    \r\n    *   [创建交易](https://explorer.zora.energy/tx/0x8cd4b29e5ab191d8fa805c829f7a89a3e2fe186acbd934f5b98e8862e54cc9a4?tab=index) - 我看到调用了一个“工厂”来部署这个，所以这可能是一个工厂创建的合约，每个集合都有自己的部署。\r\n        \r\n    *   [铸造交易](https://explorer.zora.energy/tx/0xf8a74dd53ecff823bf3614c7dede6d9f9ce9c31525710cd3fb7ce5d00191720b?tab=index) - 我从产品界面发送的那个\r\n        \r\n    *   [erc20z 地址](https://explorer.zora.energy/address/0xF3cDcA11108780008D04491Ac699E962abEFF151) ([impl](https://explorer.zora.energy/address/0xA23bD7012a050166E24a2A67B33Adb63E75eF37c?tab=contract_code))\r\n        \r\n*   [版税合约](https://explorer.zora.energy/address/0x77777771DF91C56c5468746E80DFA8b880f9719F?tab=contract)（不是代理）\r\n\r\n你会在深入数据时发现函数结构和系统参数、部署管理等模式。在进行研究时保持像我上面那样的笔记清单会让你的生活轻松很多。\r\n\r\n> 其他初学者资源：\r\n> \r\n> *   [加密数据分析师路线图](https://roadmap.sh/r?id=65fee5b66deb533d6e19fb88)\r\n>     \r\n> *   [初学者 SQL 和以太坊指南](https://read.cryptodatabytes.com/p/a-basic-wizard-guide-to-dune-sql)\r\n>     \r\n> *   [阅读区块浏览器的基础知识](https://learnblockchain.cn/article/9230)\r\n>     \r\n> \r\n> 与社区一起构建更容易、更有趣， [来加入 Bytexplorers](https://read.cryptodatabytes.com/p/join-the-bytexplorers)，在你的数据旅程中学习和赚钱。\r\n\r\n> 我是 [AI 翻译官](https://learnblockchain.cn/people/19584)，为大家转译优秀英文文章，如有翻译不通的地方，在[这里](https://github.com/lbc-team/Pioneer/blob/master/translations/9282.md)修改，还请包涵～"},"author":{"user":"https://learnblockchain.cn/people/22567","address":null},"history":"bafkreidyirh5igb4uzlctfmfh3xkgy6vumqk35a7njmowqoekh3dicr4qe","timestamp":1725959552,"version":1}