<?xml version="1.0" encoding="utf-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><title>区块链技术网</title><link>http://htzkw.com/</link><description>区块链技术中文社区</description><item><title>编码代理如何重塑工程、产品和设计</title><link>http://htzkw.com/post/9724.html</link><description>&lt;p&gt;&lt;img src=&quot;https://img.learnblockchain.cn/2026/03/11/32650233_image.jpg&quot; alt=&quot;Image&quot; class=&quot;aligncenter&quot;&gt;&lt;/p&gt; 
&lt;p&gt;软件公司的 EPD (Engineering, Product, and Design) 部门旨在创建优质软件。尽管存在不同的角色，但最终目标是开发出能解决业务问题并可供用户使用的功能性软件。归根结底，这只是一堆代码。认识到 EPD 作为一个职能部门所构建的产出仅仅是代码这一点很重要，因为…… &lt;strong&gt;编码代理&lt;/strong&gt;（coding agents）突然让代码编写变得异常容易。那么，这会如何改变 EPD 的角色呢？&lt;/p&gt; 
&lt;p&gt;不断变化的过程：&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;PRD 已死&lt;/li&gt; 
 &lt;li&gt;瓶颈从实现转移到评审&lt;/li&gt; 
 &lt;li&gt;PRD 万岁&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;对角色的影响：&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;strong&gt;通才&lt;/strong&gt;（Generalists）比以往任何时候都更有价值&lt;/li&gt; 
 &lt;li&gt;&lt;strong&gt;编码代理&lt;/strong&gt;是必需品&lt;/li&gt; 
 &lt;li&gt;好的 PM 很棒，差的 PM 很糟糕&lt;/li&gt; 
 &lt;li&gt;每个人都需要&lt;strong&gt;产品感&lt;/strong&gt;（product sense）&lt;/li&gt; 
 &lt;li&gt;专业化的门槛更高了&lt;/li&gt; 
 &lt;li&gt;你不是建造者就是评审者&lt;/li&gt; 
 &lt;li&gt;每个人都认为他们的角色最受&lt;strong&gt;编码代理&lt;/strong&gt;的优势——而且他们是对的&lt;/li&gt; 
&lt;/ul&gt; 
&lt;h2&gt;PRD 已死&lt;/h2&gt; 
&lt;p&gt;PRD (Product Requirement Documents) 在 &lt;strong&gt;Claude 时代&lt;/strong&gt;之前是构建软件的焦点。EPD 流程大体上是：&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;某人（通常是产品经理）有一个想法&lt;/li&gt; 
 &lt;li&gt;产品经理编写 PRD&lt;/li&gt; 
 &lt;li&gt;设计部门根据 PRD 创建模型&lt;/li&gt; 
 &lt;li&gt;工程部门将模型转化为代码&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;&lt;img src=&quot;https://img.learnblockchain.cn/2026/03/11/23285538_image.png&quot; alt=&quot;Image&quot; class=&quot;aligncenter&quot;&gt;&lt;/p&gt; 
&lt;p&gt;这并非一成不变的规则（在初创公司，这些步骤会融合在一起，最好的构建者能够同时完成多项工作），但这是教科书式的构建方式。&lt;/p&gt; 
&lt;p&gt;之所以需要这样做，是因为构建软件（和构建模型）需要大量的时间和精力。因此，为了专注于这些工作而创建了不同的学科。随着人们变得更加专业化，就需要跨学科进行沟通。PRD 是这种沟通的基础，它启动了一切。然后，它会&lt;strong&gt;瀑布式&lt;/strong&gt;（waterfall）地传递给设计部门，设计部门会将优美的文字转化为漂亮的 UI 和流畅的 UX。最后，工程部门会将其变为现实。&lt;/p&gt; 
&lt;p&gt;&lt;strong&gt;编码代理&lt;/strong&gt;改变了这一切。&lt;strong&gt;编码代理&lt;/strong&gt;可以根据一个想法创建功能性软件。当我和其他人说“PRD 已死”时，我们真正的意思是这种传统的软件构建方式，即从编写 PRD 开始，已经消亡了。&lt;/p&gt; 
&lt;h2&gt;瓶颈从实现转移到评审&lt;/h2&gt; 
&lt;p&gt;现在任何人都可以编写代码，这意味着任何人都可以构建东西。但这并不意味着构建出来的东西架构良好、解决了正确的问题或易于使用。工程、产品和设计部门应该是这些领域的评审者和仲裁者。问题在于，生成的代码并非总是“优秀”的。EPD 的角色变成了评审并确保它是“优秀”的。“优秀”可以意味着以下几点：&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;从工程系统角度看架构良好：它是以可扩展、高性能、健壮的方式编写的吗？&lt;/li&gt; 
 &lt;li&gt;从产品角度看考虑周全：这解决了用户痛点吗？&lt;/li&gt; 
 &lt;li&gt;设计良好：界面是否易于使用且直观？&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;由于创建代码初始版本的成本如此之低，我们看到创建了更多的原型。这些原型随后成为焦点，由产品、工程和设计部门进行评审。&lt;/p&gt; 
&lt;p&gt;&lt;img src=&quot;https://img.learnblockchain.cn/2026/03/11/50918915_image.png&quot; alt=&quot;Image&quot; class=&quot;aligncenter&quot;&gt;&lt;/p&gt; 
&lt;p&gt;问题在于——生成代码太容易了。以前，创建代码需要一段时间——所以作为评审者，你案头需要评审的项目数量有限。然而现在，任何人都可以编写代码。这意味着正在进行的项目数量正在增加。我们已经看到（所有三个职能部门）的瓶颈是评审——获取原型并确保它们是“好的”。&lt;/p&gt; 
&lt;h2&gt;PRD 万岁&lt;/h2&gt; 
&lt;p&gt;&lt;strong&gt;Claude 时代&lt;/strong&gt;之前（以 PRD 开始）的软件构建方式已经消失了。但是，描述产品需求的文档仍然至关重要。&lt;/p&gt; 
&lt;p&gt;假设某人有一个想法并迅速构建了一个原型。这如何投入生产？它需要由 EPD 的其他成员进行评审。在此过程中，书面文档总是有帮助的，并且通常是必不可少的。当其他人进行评审时，他们如何知道代码的某个部分是偶然存在还是故意为之？这取决于意图。需要对此意图进行某种沟通。&lt;/p&gt; 
&lt;p&gt;我认为传统的 PRD 流程（PRD → 模型 → 代码）已经消亡了。但描述产品需求的文本却非常有生命力。在将其交付评审之前，这份相关文档应作为原型的必要伴侣。&lt;/p&gt; 
&lt;p&gt;最标准的格式将是文档，但有一些有趣的想法是共享用于创建此功能的&lt;strong&gt;提示&lt;/strong&gt;（prompts），以此来传达。如果未来的 PRD 只是结构化、版本化的&lt;strong&gt;提示&lt;/strong&gt;（prompts）呢？&lt;/p&gt; 
&lt;p&gt;&lt;img src=&quot;https://img.learnblockchain.cn/2026/03/11/70023977_image.png&quot; alt=&quot;Image&quot; class=&quot;aligncenter&quot;&gt;&lt;/p&gt; 
&lt;h2&gt;通才比以往任何时候都更有价值&lt;/h2&gt; 
&lt;p&gt;我所说的&lt;strong&gt;通才&lt;/strong&gt;（generalists）是指对产品、工程和设计三个领域都有良好理解的人。这些人一直都很有价值且具有影响力——但有了&lt;strong&gt;编码代理&lt;/strong&gt;，他们更是如此。为什么？&lt;/p&gt; 
&lt;p&gt;沟通是所有事情中最困难的部分。它会减慢速度。一个能够同时进行产品、设计和工程工作的人会比一个三人团队行动更快，因为沟通开销更小。&lt;/p&gt; 
&lt;p&gt;以前，当实现是障碍时，这个&lt;strong&gt;通才&lt;/strong&gt;（generalist）仍然需要与其他人沟通才能完成工作。现在他们只需要与&lt;strong&gt;代理&lt;/strong&gt;（agents）沟通。这意味着他们自己就能比以往任何时候都产生更大的影响力。&lt;/p&gt; 
&lt;h2&gt;编码代理是必需品&lt;/h2&gt; 
&lt;p&gt;由于&lt;strong&gt;编码代理&lt;/strong&gt;使得实现成本低廉，使用它们是必需的。能够采用&lt;strong&gt;编码代理&lt;/strong&gt;的人将能够独自完成更多工作：&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;采用&lt;strong&gt;编码代理&lt;/strong&gt;的 PM 可以通过直接构建原型来验证想法，而无需编写&lt;strong&gt;规范&lt;/strong&gt;（spec）和等待&lt;/li&gt; 
 &lt;li&gt;采用&lt;strong&gt;编码代理&lt;/strong&gt;的设计师可以在代码中进行迭代，而不仅仅是在 Figma 中&lt;/li&gt; 
 &lt;li&gt;采用&lt;strong&gt;编码代理&lt;/strong&gt;的工程师可以将时间从实现转移到&lt;strong&gt;系统思考&lt;/strong&gt;（system thinking）上&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;采用&lt;strong&gt;编码代理&lt;/strong&gt;是必需的，因为这样做并不难，如果你不这样做，你就会被那些做的人取代。&lt;/p&gt; 
&lt;h2&gt;好的 PM 很棒，差的 PM 很糟糕&lt;/h2&gt; 
&lt;p&gt;优秀的&lt;strong&gt;产品思维&lt;/strong&gt;（product thinking）比以往任何时候都更有价值——你可以构建有用的东西。糟糕的&lt;strong&gt;产品思维&lt;/strong&gt;（product thinking）比以往任何时候都更浪费。如果某人有一个糟糕的产品想法，他们可以带着原型出现——但那个原型将是一个无用或构思不佳的功能。这些原型现在需要更多的评审——来自工程、产品和设计。这会浪费时间和资源。也存在更大的惯性将这些原型投入生产（“它已经存在了！我们合并它就行了！”）。这可能会导致创建出更糟糕或臃肿的产品。&lt;/p&gt; 
&lt;p&gt;&lt;img src=&quot;https://img.learnblockchain.cn/2026/03/11/50036721_image.png&quot; alt=&quot;Image&quot; class=&quot;aligncenter&quot;&gt;&lt;/p&gt; 
&lt;h2&gt;系统思考是需要磨练的技能&lt;/h2&gt; 
&lt;p&gt;在一个执行成本低廉的世界里，&lt;strong&gt;系统思考&lt;/strong&gt;（system thinking）成为了区别。你应该专注于擅长&lt;strong&gt;系统思考&lt;/strong&gt;（system thinking），并对你的特定&lt;strong&gt;领域&lt;/strong&gt;（domain）有一个清晰的&lt;strong&gt;心智模型&lt;/strong&gt;（mental model）：&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;工程：对如何&lt;strong&gt;架构服务&lt;/strong&gt;（architect services）以及 &lt;strong&gt;API&lt;/strong&gt; 和&lt;strong&gt;数据库&lt;/strong&gt;（databases）有非常好的&lt;strong&gt;心智模型&lt;/strong&gt;（mental model）&lt;/li&gt; 
 &lt;li&gt;产品：对用户实际需要什么（而不是他们口头说想要什么）有非常好的&lt;strong&gt;心智模型&lt;/strong&gt;（mental model）&lt;/li&gt; 
 &lt;li&gt;设计：对为什么某些东西看起来和感觉用起来正确有非常好的&lt;strong&gt;心智模型&lt;/strong&gt;（mental model）&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;&lt;strong&gt;系统思考&lt;/strong&gt;（System thinking）一直很重要——那么改变了什么？实现的成本大大降低了。这意味着实现某些东西比以往任何时候都容易——但这并不意味着它很棒。成为一个优秀的&lt;strong&gt;系统思考者&lt;/strong&gt;（system thinker）可以让你确保你&lt;strong&gt;从一开始&lt;/strong&gt;（upfront）就构建正确的东西。它也让你可以在事后评审他人的工作。这两点都意味着成为一个优秀的&lt;strong&gt;系统思考者&lt;/strong&gt;（system thinker）的重要性增加了。&lt;/p&gt; 
&lt;h2&gt;每个人都需要产品感&lt;/h2&gt; 
&lt;p&gt;&lt;strong&gt;编码代理&lt;/strong&gt;（Coding agents）仍然需要有人&lt;strong&gt;提示&lt;/strong&gt;（prompt）它们。有人告诉它们该做什么。如果你告诉它们构建错误的东西——你会给其他人带来更多的&lt;strong&gt;负担&lt;/strong&gt;（slop）去评审。知道要告诉代理构建什么（“&lt;strong&gt;产品感&lt;/strong&gt;”，product sense）是必需的，否则你将成为组织的累赘。这适用于工程、设计以及（显而易见的）产品部门。&lt;/p&gt; 
&lt;p&gt;EPD 的一个重要部分现在是评审原型。如果你有&lt;strong&gt;产品感&lt;/strong&gt;（product sense），即使是评审设计/工程，评审也会更容易。如果你没有&lt;strong&gt;产品感&lt;/strong&gt;（product sense），你需要在原型旁边附带一份超详细的产品文档。如果你有&lt;strong&gt;产品感&lt;/strong&gt;（product sense），你就能以最少的&lt;strong&gt;规范&lt;/strong&gt;（spec）理解功能的意图，从而加快沟通、评审和交付。&lt;/p&gt; 
&lt;h2&gt;专业化的门槛更高了&lt;/h2&gt; 
&lt;p&gt;你需要知道如何使用&lt;strong&gt;编码代理&lt;/strong&gt;（coding agents）。你需要&lt;strong&gt;产品感&lt;/strong&gt;（product sense）。所有角色都在融合。&lt;/p&gt; 
&lt;p&gt;角色之间一直存在重叠。设计和产品长期以来都是相互关联的——在某些公司，如苹果和爱彼迎，设计师担任产品经理。“&lt;strong&gt;设计工程师&lt;/strong&gt;”（Design engineer）作为一个角色在 Vercel 等公司越来越受欢迎。&lt;/p&gt; 
&lt;p&gt;这并不意味着没有专业化的空间。一位只关注&lt;strong&gt;系统架构&lt;/strong&gt;（system architecture）的资深工程师仍然很有价值。一位没有掌握&lt;strong&gt;气氛编码&lt;/strong&gt;（vibe coding）但对客户问题和要构建什么有非常清晰的&lt;strong&gt;心智模型&lt;/strong&gt;（mental model）的 PM 也是如此。同样，一位即使仍在 Figma 中也能理解和模拟用户旅程和交互的设计师也很有价值。&lt;/p&gt; 
&lt;p&gt;但专业化的门槛要高得多。你不仅必须在你的&lt;strong&gt;领域&lt;/strong&gt;（domain）表现出色，还必须在评审方面非常迅速，并且是一位出色的沟通者。而且在任何一家公司，这种角色可能都不会太多。&lt;/p&gt; 
&lt;h2&gt;你不是建造者就是评审者&lt;/h2&gt; 
&lt;p&gt;我们看到 EPD 中出现了两种不同类型的角色。&lt;/p&gt; 
&lt;p&gt;第一种：&lt;strong&gt;建造者&lt;/strong&gt;（builder）。这种&lt;strong&gt;原型&lt;/strong&gt;（archetype）具有良好的&lt;strong&gt;产品思维&lt;/strong&gt;（product thinking），能够使用&lt;strong&gt;编码代理&lt;/strong&gt;（coding agents），并且具备基本的&lt;strong&gt;设计直觉&lt;/strong&gt;（design intuition）。在他们的&lt;strong&gt;防护栏&lt;/strong&gt;（guardrails）（&lt;strong&gt;测试套件&lt;/strong&gt;，test suite；&lt;strong&gt;组件库&lt;/strong&gt;，component library）的保护下，他们可以将小型功能从想法变为生产，并&lt;strong&gt;原型化&lt;/strong&gt;（prototype）大型功能的可行版本。&lt;/p&gt; 
&lt;p&gt;第二种：&lt;strong&gt;评审者&lt;/strong&gt;（reviewer）。对于大型复杂功能，需要详细的 EPD 评审。这方面的门槛很高——你必须是你的&lt;strong&gt;领域&lt;/strong&gt;（domain）中出色的&lt;strong&gt;系统思考者&lt;/strong&gt;（systems thinker）。你还必须以快速的节奏工作——有很多东西需要评审。&lt;/p&gt; 
&lt;p&gt;如果你现在是一名工程师——你应该要么致力于在&lt;strong&gt;系统设计&lt;/strong&gt;（system design）方面变得出色，并习惯评审架构，并致力于成为一名评审者……要么尝试培养你的产品/设计技能，成为一名建造者。&lt;/p&gt; 
&lt;p&gt;如果你在产品或设计部门——你必须对产品/设计有出色的&lt;strong&gt;心智模型&lt;/strong&gt;（mental model）并主要进行评审，或者投入&lt;strong&gt;编码代理&lt;/strong&gt;（coding agents）并提高你的&lt;strong&gt;编码能力&lt;/strong&gt;（coding chops）。&lt;/p&gt; 
&lt;p&gt;&lt;img src=&quot;https://img.learnblockchain.cn/2026/03/11/29491125_image.jpg&quot; alt=&quot;Image&quot; class=&quot;aligncenter&quot;&gt;&lt;/p&gt; 
&lt;p&gt;有趣的是，角色正在某种程度上崩溃，如上面图表所示，所有 EPD 成员都处于某个位置。角色可以开始融合——工程师有更多时间，可以更多地思考产品和设计。产品和设计可以创建代码。&lt;/p&gt; 
&lt;h2&gt;每个人都认为他们的角色最受编码代理的优势——而且他们是对的&lt;/h2&gt; 
&lt;p&gt;Twitter 上有一篇&lt;strong&gt;很棒的帖子&lt;/strong&gt;（great post on Twitter），内容是关于最受&lt;strong&gt;编码代理&lt;/strong&gt;（coding agents）优势的人：&lt;/p&gt; 
&lt;blockquote&gt; 
 &lt;p&gt;那些对产品现状、其&lt;strong&gt;不足之处&lt;/strong&gt;（soft）、其&lt;strong&gt;出彩之处&lt;/strong&gt;（sings）以及如何迭代使其更&lt;strong&gt;锐利&lt;/strong&gt;（sharper）有直观理解的人。&lt;/p&gt; 
 &lt;p&gt;这种人中最稀有的一种处于文化与&lt;strong&gt;深度技术&lt;/strong&gt;（deep technology）的交汇点。一个真正的&lt;strong&gt;双语者&lt;/strong&gt;（bilingual）。他们知道什么是技术上可能的，也知道哪些文化潮流是&lt;strong&gt;真实&lt;/strong&gt;（real）的，哪些是&lt;strong&gt;短暂&lt;/strong&gt;（ephemeral）的。这种组合正是将那些感觉&lt;strong&gt;不可避免&lt;/strong&gt;（inevitable）的产品与那些感觉&lt;strong&gt;拼凑而成&lt;/strong&gt;（assembled）的产品区分开来的原因。&lt;/p&gt; 
&lt;/blockquote&gt; 
&lt;p&gt;这篇帖子很好地概括了这个新世界，并且&lt;strong&gt;半病毒式传播&lt;/strong&gt;（semi-viral）。它之所以能&lt;strong&gt;病毒式传播&lt;/strong&gt;（viral）的一部分原因是，所有阅读它的人都认为它指的是他们自己或他们的角色。我看到产品经理、设计师、&lt;strong&gt;设计工程师&lt;/strong&gt;（design engineers）、&lt;strong&gt;创始人&lt;/strong&gt;（founders）……所有人都认为它适用于他们和他们的角色。&lt;/p&gt; 
&lt;p&gt;而且他们可能都说对了！我认为这个新世界最棒、最令人兴奋的地方在于，背景变得不那么重要了。我真诚地相信这种&lt;strong&gt;原型人物&lt;/strong&gt;（archetype of person）可以来自产品、设计或工程部门。这并不意味着每个人都会成为这样的人——说起来容易做起来难。真正的&lt;strong&gt;独角兽&lt;/strong&gt;（unicorns）少之又少。&lt;/p&gt; 
&lt;p&gt;这是一个令人兴奋的构建时代 :)&lt;/p&gt; 
&lt;blockquote&gt; 
 &lt;ul&gt; 
  &lt;li&gt;原文链接： x.com/hwchase17/status/2...&lt;/li&gt; 
  &lt;li&gt;登链社区 AI 助手，为大家转译优秀英文文章，如有翻译不通的地方，还请包涵～&lt;/li&gt; 
 &lt;/ul&gt; 
&lt;/blockquote&gt;</description><pubDate>Fri, 20 Mar 2026 01:40:35 +0800</pubDate></item><item><title>一张图看懂Web3稳定币格局</title><link>http://htzkw.com/post/9723.html</link><description>&lt;p&gt;&lt;img src=&quot;https://img.learnblockchain.cn/attachments/2026/03/2PgtC5uF69aff7552e4c0.png&quot; alt=&quot;whiteboard_exported_image.png&quot; class=&quot;aligncenter&quot;&gt;&lt;/p&gt; 
&lt;p&gt;&amp;lt;!--StartFragment--&amp;gt;&lt;/p&gt; 
&lt;p&gt;美国立法强制规定，国内私人稳定币与美元国债挂钩，换汤不换药，背书美元货币霸权。 欧盟，新加坡在美国私人稳定币框架玩法内维护国家货币主权。 中国应对： 1.2026年初全面叫停人民币稳定币。 2.开放港元稳定币参与试水积极参与。 3.发行数字人民币（央行背书国家信用）和美国私人化发行稳定币的框架对打，维护货币主权，定义Web3时代货币格局，包括央行数字货币桥等政策。 总的来说，目前中国以吸收私人稳定币技术优势，如分布式账本，区块链技术等，助推数字人民币全球化竞争力，未开放私人稳定币应用。&lt;/p&gt; 
&lt;p&gt;&amp;lt;!--EndFragment--&amp;gt;&lt;/p&gt;</description><pubDate>Fri, 20 Mar 2026 01:40:35 +0800</pubDate></item><item><title>nonReentrant 挡不住的重入：两种你可能忽略的场景</title><link>http://htzkw.com/post/9722.html</link><description>&lt;h1&gt;nonReentrant 挡不住的重入：两种你可能忽略的场景&lt;/h1&gt; 
&lt;blockquote&gt; 
 &lt;p&gt;作者：Mingyang Fan (@SymmaTe) 发布时间：2026-03&lt;/p&gt; 
&lt;/blockquote&gt; 
&lt;hr&gt; 
&lt;p&gt;在审计智能合约的过程中，我发现一个很普遍的误区：很多开发者看到合约加了 &lt;code&gt;nonReentrant&lt;/code&gt;，就认为重入问题已经解决了。但在实际的漏洞案例里，我见过两类&quot;加了 &lt;code&gt;nonReentrant&lt;/code&gt; 依然出问题&quot;的场景，它们的根本原因不同，但都指向同一个结论：&lt;strong&gt;锁只能防止重入这个动作，防不了外部调用期间合约处于中间状态这个事实&lt;/strong&gt;。&lt;/p&gt; 
&lt;p&gt;本文默认你已经了解经典重入攻击和 CEI 原则，直接进入这两种场景。&lt;/p&gt; 
&lt;hr&gt; 
&lt;h2&gt;场景一：CEI 根本无法遵守——Balance Diff 模式&lt;/h2&gt; 
&lt;h3&gt;为什么 CEI 在这里失效&lt;/h3&gt; 
&lt;p&gt;CEI 的核心是&quot;先改完所有状态，再做外部调用&quot;。但有一类合约的业务逻辑天生无法遵守这个原则——以 &lt;strong&gt;balance diff（余额差值）&lt;/strong&gt; 来记账的合约：&lt;/p&gt; 
&lt;pre&gt;&lt;code&gt;记录调用前余额（balanceBefore）
↓
执行外部操作（swap、transfer 等）  ← 外部调用必须在这里
↓
记录调用后余额（balanceAfter）
↓
用差值作为实际转入/转出量&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;这个模式是处理 fee-on-transfer 代币的标准做法，外部调用必须夹在两次余额读取之间，无法前移。这意味着在外部调用发生期间，合约的最终状态&lt;strong&gt;永远是未确定的&lt;/strong&gt;——这是结构决定的，加 &lt;code&gt;nonReentrant&lt;/code&gt; 解决不了这个问题。&lt;/p&gt; 
&lt;h3&gt;案例：Notional Finance redeemNative()&lt;/h3&gt; 
&lt;blockquote&gt; 
 &lt;p&gt;原始报告：Notional Finance · Sherlock audit https://solodit.cyfrin.io/issues/redeemnative-reentrancy-enables-permanent-fund-freeze-systemic-misaccounting-and-liquidation-cascades-mixbytes-none-notional-finance-markdown&lt;/p&gt; 
&lt;/blockquote&gt; 
&lt;p&gt;攻击者构造了一条包含恶意 token 的 swap 路径（ETH → 恶意Token → WETH），在恶意 token 的回调里注入攻击逻辑。&lt;/p&gt; 
&lt;p&gt;&lt;strong&gt;没有 &lt;code&gt;nonReentrant&lt;/code&gt; 时，经典的重入攻击：&lt;/strong&gt;&lt;/p&gt; 
&lt;pre&gt;&lt;code&gt;攻击者调用 redeemNative()
    ↓
合约记录：balanceBefore = 1000 ETH
    ↓
合约执行 swap：ETH → 恶意Token → WETH
    ↓  ← 恶意Token的回调触发
    |
    └→ 【重入】攻击者再次调用 redeemNative()
           内层：balanceBefore = 1000 ETH（ETH 还没出去！）
           内层：执行 swap，ETH 转出 → balanceAfter = 900 ETH
           内层：diff = 100 ETH，给攻击者 100 份额
           ↓
    回到外层：balanceAfter = 900 ETH（ETH 已被内层取走）
    外层：diff = 100 ETH，再给攻击者 100 份额
    ↓
结果：攻击者用 100 ETH 的代价拿到了 200 份额的价值&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;&lt;strong&gt;为什么能成功？&lt;/strong&gt;&lt;/p&gt; 
&lt;p&gt;关键在于 &lt;code&gt;balanceBefore&lt;/code&gt; 是一个快照——它在函数最开始就拍下了&quot;合约有 1000 ETH&quot;。外层调用拍完快照之后，控制权通过回调交给了攻击者，攻击者的内层调用趁机把 ETH 取走了。等外层调用拿到 &lt;code&gt;balanceAfter&lt;/code&gt; 时，ETH 已经少了，但外层的 &lt;code&gt;balanceBefore&lt;/code&gt; 还是 1000，所以 diff 被算成了 100，实际上那 100 ETH 早就被内层拿走了。两层各自算了一遍 diff，100 ETH 被记了两次账。&lt;/p&gt; 
&lt;hr&gt; 
&lt;p&gt;&lt;strong&gt;加了 &lt;code&gt;nonReentrant&lt;/code&gt; 之后：&lt;/strong&gt;&lt;/p&gt; 
&lt;p&gt;内层再次调用 &lt;code&gt;redeemNative()&lt;/code&gt; 被锁拒绝，上面这条路被堵住了。&lt;/p&gt; 
&lt;p&gt;但攻击者只需换一条路：&lt;strong&gt;在回调里不重入 &lt;code&gt;redeemNative()&lt;/code&gt;，而是调用合约里其他没有被同一个锁保护的函数&lt;/strong&gt;。&lt;/p&gt; 
&lt;p&gt;举例：假设合约里还有一个 &lt;code&gt;claimRewards()&lt;/code&gt; 没有被同一个锁保护：&lt;/p&gt; 
&lt;pre&gt;&lt;code&gt;合约记录 balanceBefore = 1000 ETH
    ↓
合约执行 swap，恶意Token的回调触发
    ↓
    └→ 攻击者在回调里调用 claimRewards()
       claimRewards() 从合约取走 50 ETH 给攻击者
    ↓
回到 redeemNative()：
  balanceAfter = 850 ETH（100 ETH swap 花掉 + 50 ETH 被 claimRewards 取走）
  diff = 1000 - 850 = 150 ETH
  合约认为攻击者赎回了 150 份额的资产
  但攻击者实际只花了 100 ETH，额外拿走了 50 ETH&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;&lt;strong&gt;为什么 nonReentrant 没用？&lt;/strong&gt;&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;nonReentrant&lt;/code&gt; 只是给 &lt;code&gt;redeemNative()&lt;/code&gt; 这一个函数加了锁，但攻击者在回调里调用的是 &lt;code&gt;claimRewards()&lt;/code&gt;——完全不同的函数，没有被同一把锁保护。锁拦截不了这条路。&lt;/p&gt; 
&lt;p&gt;根本问题没有改变：balanceBefore 和 balanceAfter 之间存在一个外部调用窗口，攻击者只要能控制这个窗口里发生的事，就能让 diff 的计算结果偏大。&lt;/p&gt; 
&lt;h3&gt;真正的修复&lt;/h3&gt; 
&lt;p&gt;不允许用户指定 swap 路径中的 token 地址，或对 token 合约做白名单限制——切断攻击者对外部调用行为的控制权。只要攻击者无法在回调里插入自己的代码，diff 的计算就是安全的。&lt;/p&gt; 
&lt;hr&gt; 
&lt;h2&gt;场景二：遵守了 CEI 依然出问题——跨合约只读重入&lt;/h2&gt; 
&lt;p&gt;场景一的根本原因是业务逻辑&lt;strong&gt;无法&lt;/strong&gt;遵守 CEI。场景二更反直觉：&lt;strong&gt;代码遵守了 CEI，但依然有漏洞&lt;/strong&gt;。&lt;/p&gt; 
&lt;p&gt;CEI 定义：先做 Effect（状态更新），再做 Interaction（外部调用）。&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;_burn&lt;/code&gt; = Effect（修改 totalSupply）&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;_transferAssets&lt;/code&gt; = Interaction（发送 ETH，触发外部回调）&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;先 burn 后 transfer，Effect 在前 Interaction 在后——&lt;strong&gt;这正是 CEI 要求的顺序&lt;/strong&gt;。但它仍然产生了漏洞。&lt;/p&gt; 
&lt;h3&gt;具体场景&lt;/h3&gt; 
&lt;pre&gt;&lt;code class=&quot;language-solidity&quot;&gt;// ️ 遵守了 CEI，但仍然危险
function redeem(uint256 shares, address receiver) external nonReentrant {
    _burn(msg.sender, shares);    // Effect：totalSupply↓，但 totalAssets 未变
    // ← 此刻 sharePrice = totalAssets / totalSupply 偏高
    _transferAssets(receiver);    // Interaction：发送 ETH，触发攻击者的 receive()
}&lt;/code&gt;&lt;/pre&gt; 
&lt;pre&gt;&lt;code&gt;_burn 执行：totalSupply = 900，totalAssets = 1000
    ↓
sharePrice = 1000 / 900 = 1.11  ← 偏高（份额少了但资产还在）
    ↓
_transferAssets 发送 ETH，触发攻击者的 receive()
    ↓
攻击者的 receive() {
    // 此时 ETH 还没真正转出去，receive() 是在传输途中触发的
    // ERC4626 状态：totalSupply=900，totalAssets=1000，sharePrice=1.11
    lendingProtocol.borrow(ERC4626_share_as_collateral);
    // 借贷协议读到 sharePrice=1.11
    // 攻击者手里 100 shares，本来只值 100 USD
    // 现在被估值为 111 USD → 多借出 11 USD
}
    ↓
receive() 执行完，ETH 真正转出
totalAssets = 900，sharePrice 恢复正常
但攻击者已经用偏高的价格多借走了资产&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;&lt;strong&gt;为什么 nonReentrant 没用？&lt;/strong&gt;&lt;/p&gt; 
&lt;p&gt;ERC4626 合约的 &lt;code&gt;nonReentrant&lt;/code&gt; 保护的是 ERC4626 自身不被重入。但攻击者在 &lt;code&gt;receive()&lt;/code&gt; 里调用的是&lt;strong&gt;借贷协议&lt;/strong&gt;，完全是另一个合约——ERC4626 的锁管不到它。&lt;/p&gt; 
&lt;p&gt;借贷协议自己就算加了 &lt;code&gt;nonReentrant&lt;/code&gt; 也没用，因为它的锁防的是借贷协议自身被重入，不影响它去读取 ERC4626 的状态数据。两个合约各自有各自的锁，没有任何一把锁能阻止这个跨合约的状态读取。&lt;/p&gt; 
&lt;h3&gt;修复&lt;/h3&gt; 
&lt;p&gt;调换顺序，先 transfer 后 burn：&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-solidity&quot;&gt;//  安全顺序
function redeem(uint256 shares, address receiver) external nonReentrant {
    _transferAssets(receiver);    // 先发送 ETH，触发 receive()
    // ← receive() 触发时：totalSupply 和 totalAssets 都没有变化
    // ← sharePrice 完全正常，攻击者在回调里什么都做不了
    _burn(msg.sender, shares);    // receive() 执行完后再销毁 shares
    // ← burn 之后没有任何外部调用，无法被利用
}&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;&lt;strong&gt;为什么安全？&lt;/strong&gt;&lt;/p&gt; 
&lt;p&gt;先执行 &lt;code&gt;_transferAssets&lt;/code&gt;，ETH 开始发送，&lt;code&gt;receive()&lt;/code&gt; 被触发。此时 &lt;code&gt;_burn&lt;/code&gt; 还没有执行，所以 totalSupply 和 totalAssets 都没有任何变化，sharePrice 完全正常。攻击者在回调里读到的是正常价格，借贷协议不会给出超额授信。&lt;/p&gt; 
&lt;p&gt;等 &lt;code&gt;receive()&lt;/code&gt; 执行完，ETH 才真正到账，然后 &lt;code&gt;_burn&lt;/code&gt; 执行销毁份额。整个过程里，外部调用触发的那一刻状态是完整的，没有任何中间状态可以被利用。&lt;/p&gt; 
&lt;hr&gt; 
&lt;h2&gt;两个场景的统一视角&lt;/h2&gt; 
&lt;p&gt;表面上看两个场景很不一样，但背后的逻辑是一致的：&lt;/p&gt; 
&lt;table&gt; 
 &lt;thead&gt; 
  &lt;tr&gt; 
   &lt;th&gt;&lt;/th&gt; 
   &lt;th&gt;场景一（balance diff）&lt;/th&gt; 
   &lt;th&gt;场景二（只读重入）&lt;/th&gt; 
  &lt;/tr&gt; 
 &lt;/thead&gt; 
 &lt;tbody&gt; 
  &lt;tr&gt; 
   &lt;td&gt;CEI 的情况&lt;/td&gt; 
   &lt;td&gt;无法遵守，业务逻辑决定的&lt;/td&gt; 
   &lt;td&gt;遵守了 CEI，但依然有漏洞&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;外部调用来源&lt;/td&gt; 
   &lt;td&gt;用户构造的恶意 swap 路径&lt;/td&gt; 
   &lt;td&gt;ETH transfer 触发的 receive()&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;攻击者在回调里做什么&lt;/td&gt; 
   &lt;td&gt;调用其他函数污染 balanceAfter&lt;/td&gt; 
   &lt;td&gt;调用第三方合约读取偏高价格&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;nonReentrant 有没有用&lt;/td&gt; 
   &lt;td&gt;没用，换条路就能绕过&lt;/td&gt; 
   &lt;td&gt;没用，根本不需要重入&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;修复方向&lt;/td&gt; 
   &lt;td&gt;限制外部调用的可控范围&lt;/td&gt; 
   &lt;td&gt;调换顺序，让外部调用触发时状态完全正常&lt;/td&gt; 
  &lt;/tr&gt; 
 &lt;/tbody&gt; 
&lt;/table&gt; 
&lt;p&gt;两者都利用了&quot;外部调用期间合约处于中间状态&quot;这个窗口。场景二更值得警惕——代码遵守了 CEI，看起来没有问题，但只要外部调用触发时有任何状态不一致，就可能被跨合约读取利用。&lt;/p&gt; 
&lt;hr&gt; 
&lt;h2&gt;审计 Checklist&lt;/h2&gt; 
&lt;p&gt;&lt;strong&gt;看到 balance diff 模式：&lt;/strong&gt;&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;balanceBefore&lt;/code&gt; / &lt;code&gt;balanceAfter&lt;/code&gt; 之间有外部调用吗？&lt;/li&gt; 
 &lt;li&gt;外部调用的 token 地址或 swap 路径可以由用户指定吗？&lt;/li&gt; 
 &lt;li&gt;合约里有没有其他没被同一个锁保护的函数可以被回调利用？&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;&lt;strong&gt;看到 ERC4626 或类似金库：&lt;/strong&gt;&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;burn 和 transfer 的顺序是什么？先 burn 后 transfer = 危险&lt;/li&gt; 
 &lt;li&gt;中间有外部调用（包括 ETH 转账触发的 receive）吗？&lt;/li&gt; 
 &lt;li&gt;有没有第三方协议在读取这个合约的 sharePrice 或类似数据？&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;&lt;strong&gt;看到 nonReentrant 时不要放心，要问：&lt;/strong&gt;&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;外部调用期间，合约的哪些状态是不一致的？&lt;/li&gt; 
 &lt;li&gt;攻击者在回调里不重入任何函数，直接作恶，锁还有用吗？&lt;/li&gt; 
 &lt;li&gt;有没有跨合约读取中间状态的可能？&lt;/li&gt; 
&lt;/ul&gt; 
&lt;hr&gt; 
&lt;p&gt;重入攻击在 2016 年因为 The DAO 事件广为人知，但到今天仍然是审计中最容易被低估的一类漏洞。&lt;code&gt;nonReentrant&lt;/code&gt; 是必要条件，但远不是充分条件。真正需要关注的不是&quot;有没有加锁&quot;，而是&lt;strong&gt;外部调用发生的那一刻，合约的状态是否完整一致&lt;/strong&gt;。&lt;/p&gt;</description><pubDate>Fri, 20 Mar 2026 01:40:34 +0800</pubDate></item><item><title>零知识API使用信用：大型语言模型及其他应用</title><link>http://htzkw.com/post/9721.html</link><description>&lt;p&gt;markdown&lt;/p&gt; 
&lt;h2&gt;ZK API 使用积分：LLMs 及其他&lt;/h2&gt; 
&lt;p&gt;&lt;strong&gt;Davide Crapis 和 Vitalik Buterin&lt;/strong&gt;&lt;/p&gt; 
&lt;p&gt;&lt;strong&gt;这是 v2 版——与 v1 版 相比，它将客户端的退款票据列表替换为服务器签名的同态退款运行总额，因此用户不再需要存储（或证明）不断增长的退款历史。&lt;/strong&gt;&lt;/p&gt; 
&lt;p&gt;API 计量中的一个核心挑战是同时实现&lt;strong&gt;隐私&lt;/strong&gt;、&lt;strong&gt;安全&lt;/strong&gt;和&lt;strong&gt;效率&lt;/strong&gt;。这对于使用 LLMs 进行 AI 推理尤其关键，因为用户会提交高度敏感的个人数据，但它也普遍适用于任何高频数字服务。目前，API 提供商被迫在两条次优路径中进行选择：&lt;/p&gt; 
&lt;ol&gt; 
 &lt;li&gt;&lt;strong&gt;Web2 身份验证&lt;/strong&gt;：需要身份验证（电子邮件/信用卡），这将每个请求与真实世界的身份关联起来，造成巨大的隐私泄露和画像风险。&lt;/li&gt; 
 &lt;li&gt;&lt;strong&gt;链上支付&lt;/strong&gt;：每个请求都需要一笔交易，这会极其缓慢、昂贵，并且难以模糊用户的完整交易图谱。&lt;/li&gt; 
&lt;/ol&gt; 
&lt;p&gt;我们需要一个系统，用户可以&lt;strong&gt;一次性存入资金，然后匿名、安全、高效地进行数千次 API 调用&lt;/strong&gt;。提供商必须获得付款保证并防止垃圾邮件，而用户则必须获得保证，其请求不会与其身份或彼此关联。我们将 LLM 推理作为主要的用例，但该方法是通用的，也适用于 RPC 调用或任何其他固定成本 API、图像生成、云计算服务、VPN、公共数据 API 等。&lt;/p&gt; 
&lt;p&gt;&lt;strong&gt;示例：&lt;/strong&gt;&lt;/p&gt; 
&lt;ol&gt; 
 &lt;li&gt;&lt;strong&gt;LLM 推理&lt;/strong&gt;：用户将 $100$ USDC 存入智能合约，并对托管的 LLM 进行 $500$ 次查询。提供商收到 $500$ 个有效且已付费的请求，但无法将它们与同一存款人（或彼此）关联起来，同时用户的提示仍然无法与用户身份关联。&lt;/li&gt; 
 &lt;li&gt;&lt;strong&gt;以太坊 RPC&lt;/strong&gt;：用户存入 $10$ USDC，并向以太坊 RPC 节点（例如，&lt;code&gt;eth_call&lt;/code&gt; / &lt;code&gt;eth_getLogs&lt;/code&gt;）发出 $10,000$ 个请求，以支持钱包、索引器或机器人。RPC 提供商受到垃圾邮件防护并保证获得付款，但无法将请求关联成持久的用户档案。&lt;/li&gt; 
&lt;/ol&gt; 
&lt;p&gt;&lt;strong&gt;提案概述：&lt;/strong&gt; 我们利用 Rate-Limit Nullifiers (RLN) 将匿名性与经济权益绑定：遵守协议限制的诚实用户保持&lt;strong&gt;不可关联&lt;/strong&gt;，而双重花费（或以其他方式超出其允许容量）的用户则会通过密码学方式披露其密钥，从而实现罚没。我们将协议设计为在 API 使用产生可变成本时也能工作，但它也直接支持更简单的固定成本每次调用作为特例。&lt;/p&gt; 
&lt;p&gt;我们采用一种灵活的记账协议，其中每个请求预先设置了每次调用的最大成本，一旦在调用结束时确定了实际成本，服务器就会发出退款。服务器会更新（并签名）一个同态加密的退款运行总额，用户可以在请求之间携带该总额。双重质押机制让服务器可以在执行合规策略的同时保持公开问责。&lt;/p&gt; 
&lt;h3&gt;ZK API 使用积分协议&lt;/h3&gt; 
&lt;p&gt;该协议利用&lt;strong&gt;服务器退款&lt;/strong&gt;，并结合&lt;strong&gt;服务器签名的同态退款运行总额&lt;/strong&gt;，用户可以在请求之间私下携带该总额。该模型通过要求用户证明其累计支出（由其当前的&lt;strong&gt;票据索引&lt;/strong&gt;表示）严格在其初始存款和已验证的退款历史范围内来强制执行偿付能力。&lt;/p&gt; 
&lt;p&gt;反垃圾邮件保护是通过经济方式强制执行的：用户的吞吐量自然受到其可用存款缓冲的限制，同时任何尝试重复使用特定票据索引（双重花费）的行为都通过 Rate-Limit Nullifier 阻止。&lt;/p&gt; 
&lt;h4&gt;&lt;strong&gt;原语&lt;/strong&gt;&lt;/h4&gt; 
&lt;ul&gt; 
 &lt;li&gt;$k$：用户密钥。&lt;/li&gt; 
 &lt;li&gt;$D$：初始存款。&lt;/li&gt; 
 &lt;li&gt;&lt;strong&gt;$C_{max}$&lt;/strong&gt;：每个请求的最大成本（预先扣除）。&lt;/li&gt; 
 &lt;li&gt;&lt;strong&gt;$i$&lt;/strong&gt;：票据索引（一个严格递增的计数器：$0, 1, 2, \dots$）。&lt;/li&gt; 
 &lt;li&gt;$E(R)$：用户迄今收到的总退款的同态加密（例如，Pedersen commitment 或基于格的 HE）。&lt;/li&gt; 
 &lt;li&gt;$\sigma_{srv}$：服务器对当前加密总额 $E(R)$ 发出的签名。&lt;/li&gt; 
&lt;/ul&gt; 
&lt;h4&gt;&lt;strong&gt;协议流程&lt;/strong&gt;&lt;/h4&gt; 
&lt;p&gt;&lt;strong&gt;注册&lt;/strong&gt;&lt;/p&gt; 
&lt;p&gt;用户生成密钥 $k$，推导出身份承诺 $ID = Hash(k)$，并将 $D$ 存入智能合约。合约将 $ID$ 插入链上 Merkle Tree。&lt;/p&gt; 
&lt;p&gt;&lt;strong&gt;重新随机化状态&lt;/strong&gt;&lt;/p&gt; 
&lt;p&gt;用户选择一个新的随机致盲因子 $\eta'$ 并推导出一个新的匿名承诺：$E(R)_{anon} = E(R) \oplus E(0; \eta')$。&lt;/p&gt; 
&lt;p&gt;&lt;strong&gt;请求生成&lt;/strong&gt;&lt;/p&gt; 
&lt;p&gt;用户选择下一个可用的票据索引 $i$。用户生成一个 ZK-STARK $\pi_{req}$ 来证明：&lt;/p&gt; 
&lt;ol&gt; 
 &lt;li&gt;&lt;strong&gt;成员资格&lt;/strong&gt;：$ID \in MerkleRoot$。&lt;/li&gt; 
 &lt;li&gt;&lt;strong&gt;状态一致性&lt;/strong&gt;：匿名 $E(R)&lt;em&gt;{anon}$ 是先前由服务器使用 $\sigma&lt;/em&gt;{srv}$ 签名的承诺 $E(R)$ 的有效重新随机化。&lt;/li&gt; 
 &lt;li&gt;&lt;strong&gt;偿付能力（信用检查）：&lt;/strong&gt; $$ (i + 1) \cdot C_{max} \le D + R $$ (&lt;strong&gt;在索引 $i$ 处的总潜在支出由存款加上所有已验证退款的总和覆盖。&lt;/strong&gt;)&lt;/li&gt; 
 &lt;li&gt;&lt;strong&gt;RLN Share 和 Nullifier：&lt;/strong&gt; 
  &lt;ul&gt; 
   &lt;li&gt;斜率：$a = Hash(k, i)$ 
    &lt;ul&gt; 
     &lt;li&gt;&lt;strong&gt;注意：&lt;/strong&gt; 与索引 $i$ 关联而不是与之前的哈希关联，以允许并行生成。&lt;/li&gt; 
    &lt;/ul&gt;&lt;/li&gt; 
   &lt;li&gt;Signal：$y = k + a \cdot Hash(M)$&lt;/li&gt; 
   &lt;li&gt;Nullifier：$Nullifier = Hash(a)$&lt;/li&gt; 
  &lt;/ul&gt;&lt;/li&gt; 
&lt;/ol&gt; 
&lt;p&gt;&lt;strong&gt;提交&lt;/strong&gt;&lt;/p&gt; 
&lt;p&gt;用户发送：Payload ($M$) + Nullifier + Signal ($x, y$) + Proof + 当前 $E(R)_{anon}$。&lt;/p&gt; 
&lt;p&gt;&lt;strong&gt;验证与罚没&lt;/strong&gt;&lt;/p&gt; 
&lt;p&gt;服务器在其“已用票据”数据库中检查 Nullifier：&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;strong&gt;分叉/双重花费检查：&lt;/strong&gt; 如果 Nullifier 存在但具有不同的 $x$（消息），则用户尝试在两个不同的请求上花费相同的票据。解出 $k$ 并罚没。&lt;/li&gt; 
 &lt;li&gt;&lt;strong&gt;偿付能力检查：&lt;/strong&gt; 验证 $\pi_{req}$ 以确保票据索引 $i$ 得到了用户当前资金水平的授权。&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;&lt;strong&gt;结算与退款更新&lt;/strong&gt;&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;服务器执行请求并确定实际退款 $r = (C&lt;em&gt;{max} - C&lt;/em&gt;{actual})$。&lt;/li&gt; 
 &lt;li&gt;&lt;strong&gt;同态更新：&lt;/strong&gt; 服务器将 $r$ 同态添加到当前加密中：$E(R^{new}) = E(R)_{anon} \oplus E(r)$。&lt;/li&gt; 
 &lt;li&gt;&lt;strong&gt;签名：&lt;/strong&gt; 服务器签署新的总额 $E(R^{new})$ 并将其（连同新签名 $\sigma^{new}$）发送回用户。&lt;/li&gt; 
&lt;/ul&gt; 
&lt;h3&gt;服务器端问责（双重质押）&lt;/h3&gt; 
&lt;p&gt;为了阻止超出简单限速的 API 滥用（例如，违反服务条款、生成非法内容或越狱尝试），我们引入了一个辅助质押层。例如，用户可能会提交一个提示，要求模型生成制造武器的说明，或者帮助他们绕过安全控制——这些请求将违反许多提供商的使用政策，并且提供商可能希望阻止。&lt;/p&gt; 
&lt;p&gt;用户存入总金额 $Total = D + S$。&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;strong&gt;$D$ (RLN 质押)&lt;/strong&gt;：受协议数学原理管辖。可以由&lt;strong&gt;任何&lt;/strong&gt;提供双重信号数学证明（已揭示密钥 $k$）的人（包括服务器）认领。&lt;/li&gt; 
 &lt;li&gt;&lt;strong&gt;$S$ (策略质押)&lt;/strong&gt;：受服务器策略管辖。如果用户违反使用政策，服务器可以罚没（销毁），但&lt;strong&gt;不能认领&lt;/strong&gt;。&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;这样做，而不是简单地提高 $D$，是为了消除服务器欺诈性地没收用户存款的动机，因为存款金额可能很高，具体取决于其大小。&lt;/p&gt; 
&lt;h4&gt;S 的罚没机制&lt;/h4&gt; 
&lt;p&gt;如果用户提交了一个违反策略的有效 RLN 请求（但没有触发数学上的双重花费陷阱）：&lt;/p&gt; 
&lt;ol&gt; 
 &lt;li&gt;&lt;strong&gt;违规&lt;/strong&gt;：服务器在请求 payload 中检测到策略违规（例如，禁止内容）。&lt;/li&gt; 
 &lt;li&gt;&lt;strong&gt;销毁交易&lt;/strong&gt;：服务器在智能合约上调用 &lt;code&gt;slashPolicyStake()&lt;/code&gt; 函数。 
  &lt;ul&gt; 
   &lt;li&gt;&lt;strong&gt;输入&lt;/strong&gt;：违规请求的 &lt;code&gt;Nullifier&lt;/code&gt; 和 &lt;code&gt;ViolationEvidence&lt;/code&gt;（可选哈希/原因）。&lt;/li&gt; 
   &lt;li&gt;&lt;strong&gt;操作&lt;/strong&gt;：合约从用户的存款中销毁金额 $S$。&lt;/li&gt; 
   &lt;li&gt;&lt;strong&gt;约束&lt;/strong&gt;：服务器&lt;strong&gt;不能&lt;/strong&gt;为自己认领 $S$，它被发送到一个销毁地址。这可以防止服务器为了利润而虚假地封禁用户。&lt;/li&gt; 
  &lt;/ul&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;strong&gt;公开问责&lt;/strong&gt;：罚没事件与相关的 &lt;code&gt;Nullifier&lt;/code&gt; 一起记录在链上。虽然用户的身份仍然隐藏，但社区可以审计服务器销毁质押的&lt;strong&gt;频率&lt;/strong&gt;以及这些销毁所发布的证据。&lt;/li&gt; 
&lt;/ol&gt; 
&lt;blockquote&gt; 
 &lt;ul&gt; 
  &lt;li&gt;原文链接： hackmd.io/3da7PaYmTqmNTT...&lt;/li&gt; 
  &lt;li&gt;登链社区 AI 助手，为大家转译优秀英文文章，如有翻译不通的地方，还请包涵～&lt;/li&gt; 
 &lt;/ul&gt; 
&lt;/blockquote&gt;</description><pubDate>Fri, 20 Mar 2026 01:40:33 +0800</pubDate></item><item><title>Snap v2：以 BALs 取代 Trie 愈合 — 执行层研究</title><link>http://htzkw.com/post/9720.html</link><description>&lt;p&gt;快照同步（&lt;code&gt;snap/1&lt;/code&gt;）在 Geth v1.10.0 中发布后，极大地改善了以太坊节点的同步。但它有一个众所周知的阿喀琉斯之踵：&lt;strong&gt;trie 修复阶段&lt;/strong&gt;，这是一个迭代过程，同步节点在此阶段一次一个 trie 节点地发现并修复状态不一致性。这个阶段导致节点在修复过程中停滞数天或数周，并被认为是社区希望消除的问题。&lt;/p&gt; 
&lt;p&gt;随着 EIP-7928（区块级访问列表）的引入，一种新方法成为可能：&lt;strong&gt;完全用顺序 BAL 应用取代 trie 修复&lt;/strong&gt;。本文将解释快照同步目前的工作方式，trie 修复存在的问题，以及拟议的 &lt;code&gt;snap/2&lt;/code&gt; 协议升级将如何解决这个问题。&lt;/p&gt; 
&lt;h2&gt;第 1 部分：快照同步目前的工作方式&lt;/h2&gt; 
&lt;h3&gt;快照同步解决的问题&lt;/h3&gt; 
&lt;p&gt;一个新的以太坊节点需要当前状态：每个账户余额、存储槽和合约字节码。这种状态存在于一个&lt;strong&gt;默克尔帕特里夏树&lt;/strong&gt;中，其中账户 trie 的饱和深度约为 7 层（EF 博客：快照加速），包含数亿个节点。&lt;/p&gt; 
&lt;p&gt;旧的方法（“快速同步”，&lt;code&gt;eth/63&lt;/code&gt;–&lt;code&gt;66&lt;/code&gt;）从根开始&lt;strong&gt;逐个节点&lt;/strong&gt;下载这个 trie。在约 11,177,000 区块时，状态包含 6.17 亿个 trie 节点，同步它们需要下载 43.8 GB 的数据，分布在 1,607M 个数据包中，总计约 10 小时 50 分钟的同步时间。&lt;/p&gt; 
&lt;p&gt;快照同步的关键洞察：&lt;strong&gt;完全跳过中间 trie 节点，将叶子（账户、存储）作为连续范围下载&lt;/strong&gt;，然后在本地重建 trie。这要求服务节点维护一个动态快照，这是一个扁平的键值存储，可以在约 7 分钟内迭代账户，而原始 trie 迭代需要约 9.5 小时（参见 snap.md）。&lt;/p&gt; 
&lt;p&gt;比较快速同步与快照同步，我们得到了以下改进：&lt;/p&gt; 
&lt;table&gt; 
 &lt;thead&gt; 
  &lt;tr&gt; 
   &lt;th&gt;指标&lt;/th&gt; 
   &lt;th&gt;快速同步&lt;/th&gt; 
   &lt;th&gt;快照同步&lt;/th&gt; 
   &lt;th&gt;改进&lt;/th&gt; 
  &lt;/tr&gt; 
 &lt;/thead&gt; 
 &lt;tbody&gt; 
  &lt;tr&gt; 
   &lt;td&gt;下载&lt;/td&gt; 
   &lt;td&gt;43.8 GB&lt;/td&gt; 
   &lt;td&gt;20.44 GB&lt;/td&gt; 
   &lt;td&gt;-53%&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;上传&lt;/td&gt; 
   &lt;td&gt;20.38 GB&lt;/td&gt; 
   &lt;td&gt;0.15 GB&lt;/td&gt; 
   &lt;td&gt;-99.3%&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;数据包&lt;/td&gt; 
   &lt;td&gt;1,607M&lt;/td&gt; 
   &lt;td&gt;0.099M&lt;/td&gt; 
   &lt;td&gt;-99.99%&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;服务磁盘读取&lt;/td&gt; 
   &lt;td&gt;15.68 TB&lt;/td&gt; 
   &lt;td&gt;0.096 TB&lt;/td&gt; 
   &lt;td&gt;-99.4%&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;时间&lt;/td&gt; 
   &lt;td&gt;10 小时 50 分钟&lt;/td&gt; 
   &lt;td&gt;2 小时 6 分钟&lt;/td&gt; 
   &lt;td&gt;-80.6%&lt;/td&gt; 
  &lt;/tr&gt; 
 &lt;/tbody&gt; 
&lt;/table&gt; 
&lt;blockquote&gt; 
 &lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：这些基准测试来自约 11.2M 区块（2020 年末）。自那时起，状态有所增长，但相对改进仍然具有代表性。在良好硬件上，现代快照同步通常总共需要 2-3 小时。&lt;/p&gt; 
&lt;/blockquote&gt; 
&lt;h3&gt;三个阶段&lt;/h3&gt; 
&lt;p&gt;快照同步分三个阶段进行：&lt;/p&gt; 
&lt;p&gt;&lt;img src=&quot;https://img.learnblockchain.cn/2026/03/10/4f2de2552f992cfb582be1397cd9aee7e8575c27.png&quot; alt=&quot;sync&quot; class=&quot;aligncenter&quot;&gt;\ sync690×260 28.6 KB&lt;/p&gt; 
&lt;h4&gt;阶段 1 - 区块头下载&lt;/h4&gt; 
&lt;p&gt;使用 &lt;code&gt;eth&lt;/code&gt; 协议下载所有区块头，构建一个已验证的链。CL 驱动 EL，这意味着第一个 HEAD 从 CL 接收，然后 EL 从最新的区块头开始向后下载所有父区块头。&lt;/p&gt; 
&lt;h4&gt;阶段 2 - 状态下载&lt;/h4&gt; 
&lt;p&gt;节点选择一个&lt;strong&gt;枢轴区块 P&lt;/strong&gt;（通常是 HEAD−64），并下载 P 处的完整状态：&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;GetAccountRange&lt;/code&gt; (0x00)：下载连续哈希范围内的账户，每个响应在边界处经过默克尔证明以防止间隙攻击&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;GetStorageRanges&lt;/code&gt; (0x02)：下载合约的存储槽，多个小合约可以批量处理到一个请求中&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;GetByteCodes&lt;/code&gt; (0x04)：下载合约代码，通过代码哈希比较进行验证&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;每个响应都以字节大小为上限（而不是数量），以实现可预测的带宽，并且不同的对等节点可以并发地服务不同的范围。服务节点为最近的 128 个区块（在 每槽 12 秒 的情况下，约 25.6 分钟）保留快照。&lt;/p&gt; 
&lt;p&gt;枢轴区块是链尖端足够远的过去区块，以确保我们不会下载因稍后重组的区块而产生的状态。64 个区块深度的重组实际上是不可能的。即使发生这样的重组，已下载的状态也无需丢弃，可以通过迭代获取所需的 trie 节点来修复。&lt;/p&gt; 
&lt;p&gt;关键在于，当每个状态范围被接收时，节点在本地重建并持久化该段的中间 trie 节点，而不是通过网络获取它们。到阶段 2 结束时，大部分 trie 已正确构建，显著减少了修复工作负载，只需修复因下载窗口期间发生的状态更改而变得不一致的节点。&lt;/p&gt; 
&lt;h4&gt;阶段 3 - 修复&lt;/h4&gt; 
&lt;p&gt;当阶段 2 运行时，链从 P 推进到 P+K，使下载的状态过时。修复解决了这个问题，但这也是问题开始的地方。&lt;/p&gt; 
&lt;h3&gt;trie 修复的工作方式&lt;/h3&gt; 
&lt;p&gt;修复阶段使用 &lt;code&gt;GetTrieNodes&lt;/code&gt; (0x06) / &lt;code&gt;TrieNodes&lt;/code&gt; (0x07) 迭代地发现并获取已更改的 trie 节点：&lt;/p&gt; 
&lt;p&gt;&lt;img src=&quot;https://img.learnblockchain.cn/2026/03/10/b8d33a08af08bf78a80c28971710173539f1_2_690x462.png&quot; alt=&quot;healing&quot; class=&quot;aligncenter&quot;&gt;\ healing910×610 45.9 KB&lt;/p&gt; 
&lt;h3&gt;为什么 trie 修复是瓶颈&lt;/h3&gt; 
&lt;ol&gt; 
 &lt;li&gt;&lt;strong&gt;迭代发现。&lt;/strong&gt; 同步节点在查看之前不知道什么发生了变化。每一轮 &lt;code&gt;GetTrieNodes&lt;/code&gt; 都会揭示下一组差异，需要另一次往返。这根本上是顺序的。&lt;/li&gt; 
 &lt;li&gt;&lt;strong&gt;小负载，多次往返。&lt;/strong&gt; 单个 trie 节点为 100–500 字节。即使是批量处理，每次往返的数据量相对于网络延迟来说也非常小。&lt;/li&gt; 
 &lt;li&gt;&lt;strong&gt;移动的目标。&lt;/strong&gt; 在 12 秒槽的情况下，每个区块大约删除 1,000 个 trie 节点并添加 2,000 个。修复必须超越这个速度，否则永远不会收敛。&lt;/li&gt; 
 &lt;li&gt;&lt;strong&gt;随机磁盘访问。&lt;/strong&gt; 服务 &lt;code&gt;GetTrieNodes&lt;/code&gt; 需要随机数据库读取。与 &lt;code&gt;GetAccountRange&lt;/code&gt; 使用的顺序读取相比，这成本高昂。&lt;/li&gt; 
 &lt;li&gt;&lt;strong&gt;进度不可知。&lt;/strong&gt; 正如 Geth 文档所指出：“无法监控状态修复的进度，因为在当前状态已经重新生成之前，错误的程度无法得知。”&lt;/li&gt; 
&lt;/ol&gt; 
&lt;p&gt;实际影响可能很严重。例如，节点在修复过程中停滞了 2 周以上（4300 万个 trie 节点，下载了 11.7 GiB；吞吐量下降到约每秒 2 个 trie 节点），在修复期间停滞了 4 天或 6 天。&lt;/p&gt; 
&lt;p&gt;发布时的基准测试显示，在约 11.2M 区块时，修复增加了 约 541,260 个 trie 节点（约 160 MiB），但随着今天更大的状态和更高的区块 gas 限制，修复负担已经大大加重，并将随着 gas 限制的进一步增加而恶化。&lt;/p&gt; 
&lt;h2&gt;第 2 部分：区块级访问列表 (BAL)&lt;/h2&gt; 
&lt;p&gt;EIP-7928 引入了&lt;strong&gt;区块级访问列表 (BAL)&lt;/strong&gt;：记录区块执行期间访问的每个账户和存储位置以及执行后值的数据结构。每个区块头通过放置在区块头中的新 &lt;code&gt;block_access_list_hash&lt;/code&gt; 字段提交其 BAL：&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;block_access_list_hash = keccak256(rlp.encode(block_access_list))&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;一个 BAL 包含每个访问过的账户：&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;strong&gt;存储更改&lt;/strong&gt;：每个槽的执行后值，按导致更改的交易索引&lt;/li&gt; 
 &lt;li&gt;&lt;strong&gt;存储读取&lt;/strong&gt;：已读取但未修改的槽&lt;/li&gt; 
 &lt;li&gt;&lt;strong&gt;余额/nonce/代码更改&lt;/strong&gt;：交易后值&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;BAL 是 RLP 编码的，确定性排序的（账户按地址字典序排列，更改按交易索引），并且是完整的。状态差异是 BAL 的一个子集，因此可以在同步期间提供帮助。&lt;/p&gt; 
&lt;h3&gt;BAL 大小&lt;/h3&gt; 
&lt;p&gt;对在 60M 区块 gas 限制下 1,000 个主网区块的经验分析表明，BAL 平均大小约为 72.4 KiB。&lt;/p&gt; 
&lt;p&gt;节点必须至少保留 BAL 弱主观性周期（高达 3,533 个纪元，按当前验证者集大小计算约 15.7 天）。&lt;/p&gt; 
&lt;h2&gt;第 3 部分：snap/2：基于 BAL 的状态修复&lt;/h2&gt; 
&lt;p&gt;&lt;code&gt;snap/2&lt;/code&gt; 没有迭代地发现和获取 trie 节点，而是&lt;strong&gt;颠倒了 &lt;code&gt;snap/1&lt;/code&gt; 的模式&lt;/strong&gt;。在 &lt;code&gt;snap/1&lt;/code&gt; 中，trie 在下载期间增量构建，扁平状态从中导出。在 &lt;code&gt;snap/2&lt;/code&gt; 中，&lt;strong&gt;只同步扁平状态（叶子）&lt;/strong&gt;，BAL 差异直接应用于其上，并且&lt;strong&gt;trie 从完整状态重建一次&lt;/strong&gt;，从而消除了增量 trie 构建及其所需的复杂修复。&lt;/p&gt; 
&lt;p&gt;具体而言，节点不是迭代地发现和获取 trie 节点，而是&lt;strong&gt;下载同步期间前进的每个区块的 BAL，并顺序应用状态差异&lt;/strong&gt;。区块集是预先知道的。每个 BAL 都根据其区块头承诺进行验证。这消除了迭代发现的需要。&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;snap/2&lt;/code&gt; 移除了 trie 修复消息，并用 BAL 取代它们，重用相同的消息 ID：&lt;/p&gt; 
&lt;table&gt; 
 &lt;thead&gt; 
  &lt;tr&gt; 
   &lt;th&gt;ID&lt;/th&gt; 
   &lt;th&gt;snap/1&lt;/th&gt; 
   &lt;th&gt;snap/2&lt;/th&gt; 
  &lt;/tr&gt; 
 &lt;/thead&gt; 
 &lt;tbody&gt; 
  &lt;tr&gt; 
   &lt;td&gt;0x00–0x05&lt;/td&gt; 
   &lt;td&gt;账户/存储/字节码下载&lt;/td&gt; 
   &lt;td&gt;&lt;strong&gt;不变&lt;/strong&gt;&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;0x06&lt;/td&gt; 
   &lt;td&gt;&lt;code&gt;GetTrieNodes&lt;/code&gt;&lt;/td&gt; 
   &lt;td&gt;&lt;code&gt;GetBlockAccessLists&lt;/code&gt;&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;0x07&lt;/td&gt; 
   &lt;td&gt;&lt;code&gt;TrieNodes&lt;/code&gt;&lt;/td&gt; 
   &lt;td&gt;&lt;code&gt;BlockAccessLists&lt;/code&gt;&lt;/td&gt; 
  &lt;/tr&gt; 
 &lt;/tbody&gt; 
&lt;/table&gt; 
&lt;p&gt;请注意，重用消息 ID 是安全的，因为 &lt;code&gt;snap/2&lt;/code&gt; 是在 RLPx 握手期间协商的新协议版本。&lt;code&gt;snap/1&lt;/code&gt; 对等节点永远不会看到 &lt;code&gt;snap/2&lt;/code&gt; 消息。&lt;/p&gt; 
&lt;h3&gt;新消息&lt;/h3&gt; 
&lt;h4&gt;GetBlockAccessLists (0x06)&lt;/h4&gt; 
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;[请求 ID: P, [区块哈希₁: B_32, 区块哈希₂: B_32, ...]]&lt;/code&gt;&lt;/pre&gt; 
&lt;h4&gt;BlockAccessLists (0x07)&lt;/h4&gt; 
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;[请求 ID: P, [区块访问列表₁, 区块访问列表₂, ...]]&lt;/code&gt;&lt;/pre&gt; 
&lt;ul&gt; 
 &lt;li&gt;节点必须始终响应&lt;/li&gt; 
 &lt;li&gt;对于不可用的 BAL，使用空条目（零长度字节）&lt;/li&gt; 
 &lt;li&gt;响应保留请求顺序，并且可以从尾部截断&lt;/li&gt; 
 &lt;li&gt;建议的软限制为每个响应 2 MiB，这与现有消息（例如区块、区块头或收据）一致。&lt;/li&gt; 
&lt;/ul&gt; 
&lt;h3&gt;新同步算法&lt;/h3&gt; 
&lt;p&gt;&lt;img src=&quot;https://img.learnblockchain.cn/2026/03/10/953d52840ef8a4632b67ff1f426a819e68d5_2_690x288.png&quot; alt=&quot;sync2&quot; class=&quot;aligncenter&quot;&gt;\ sync2910×380 28.2 KB&lt;/p&gt; 
&lt;p&gt;&lt;strong&gt;值得注意的是，由于 BAL 通过共识（BAL 哈希与规范区块的哈希检查）保证是正确的，因此状态根也保证匹配；因此，客户端甚至可以跳过最终的状态根比较步骤。&lt;/strong&gt;&lt;/p&gt; 
&lt;h3&gt;为什么这有效&lt;/h3&gt; 
&lt;p&gt;使用 &lt;code&gt;snap/2&lt;/code&gt;，修复窗口是有限且已知的。对于 HEAD−64 处的枢轴：&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;strong&gt;64 个区块 × 约 72.4 KiB&lt;/strong&gt;（预计 60M gas）≈ &lt;strong&gt;4.5 MiB&lt;/strong&gt; 总 BAL 数据&lt;/li&gt; 
 &lt;li&gt;在 2 MiB 软限制下，适合&lt;strong&gt;2–3 个响应&lt;/strong&gt;&lt;/li&gt; 
 &lt;li&gt;服务 BAL 只需几次磁盘查找，而不是为每个已更改的 trie 节点查找&lt;/li&gt; 
 &lt;li&gt;&lt;strong&gt;总共 1–3 次往返&lt;/strong&gt;（包括应用期间到达的任何“尾部”区块）&lt;/li&gt; 
 &lt;li&gt;从 BAL 中提取状态差异&lt;strong&gt;纯粹是本地计算&lt;/strong&gt;。无需 trie 遍历。&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;与 &lt;code&gt;snap/1&lt;/code&gt; 相比，&lt;code&gt;snap/2&lt;/code&gt; 的修复更高效，需要的磁盘读取和往返次数更少。理论上，有了 &lt;code&gt;snap/2&lt;/code&gt;，链条就不可能超越同步了。&lt;/p&gt; 
&lt;h3&gt;与 eth/71 的关系&lt;/h3&gt; 
&lt;p&gt;EIP-8159 将 BAL 交换作为消息 0x12/0x13 添加到 &lt;code&gt;eth&lt;/code&gt; 协议中。两者存在的原因不同：&lt;/p&gt; 
&lt;table&gt; 
 &lt;thead&gt; 
  &lt;tr&gt; 
   &lt;th&gt;&lt;/th&gt; 
   &lt;th&gt;eth/71&lt;/th&gt; 
   &lt;th&gt;snap/2&lt;/th&gt; 
  &lt;/tr&gt; 
 &lt;/thead&gt; 
 &lt;tbody&gt; 
  &lt;tr&gt; 
   &lt;td&gt;&lt;strong&gt;目的&lt;/strong&gt;&lt;/td&gt; 
   &lt;td&gt;用于并行执行、重组处理的最新 BAL&lt;/td&gt; 
   &lt;td&gt;同步：修复期间批量 BAL 下载&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;&lt;strong&gt;数量&lt;/strong&gt;&lt;/td&gt; 
   &lt;td&gt;一次 1–3 个 BAL&lt;/td&gt; 
   &lt;td&gt;一次多个 BAL&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;&lt;strong&gt;协议&lt;/strong&gt;&lt;/td&gt; 
   &lt;td&gt;所有节点必须使用&lt;/td&gt; 
   &lt;td&gt;可选的卫星协议&lt;/td&gt; 
  &lt;/tr&gt; 
 &lt;/tbody&gt; 
&lt;/table&gt; 
&lt;p&gt;&lt;code&gt;eth/71&lt;/code&gt; 和 &lt;code&gt;snap/2&lt;/code&gt; 中的消息重复是为了确保 &lt;code&gt;snap&lt;/code&gt; 保持一个独立的卫星协议，并允许 snap 独立演进，例如，在未来版本中只提供状态差异而不是完整的 BAL，而无需更改 eth。&lt;/p&gt; 
&lt;h2&gt;第 4 部分：比较&lt;/h2&gt; 
&lt;h3&gt;修复阶段：snap/1 vs snap/2&lt;/h3&gt; 
&lt;table&gt; 
 &lt;thead&gt; 
  &lt;tr&gt; 
   &lt;th&gt;属性&lt;/th&gt; 
   &lt;th&gt;snap/1 (trie 修复)&lt;/th&gt; 
   &lt;th&gt;snap/2 (BAL 修复)&lt;/th&gt; 
  &lt;/tr&gt; 
 &lt;/thead&gt; 
 &lt;tbody&gt; 
  &lt;tr&gt; 
   &lt;td&gt;发现&lt;/td&gt; 
   &lt;td&gt;迭代：节点在查看之前不知道什么发生了变化&lt;/td&gt; 
   &lt;td&gt;确定性：P+1..P+K 区块是预先知道的&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;往返次数&lt;/td&gt; 
   &lt;td&gt;数百次以上（问题报告数百万个 trie 节点）&lt;/td&gt; 
   &lt;td&gt;最终数量待定，但估计只有几次&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;验证&lt;/td&gt; 
   &lt;td&gt;复杂的 trie 重建 + 根比较&lt;/td&gt; 
   &lt;td&gt;&lt;code&gt;keccak256(rlp(bal)) == header.block_access_list_hash&lt;/code&gt;&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;移动的目标&lt;/td&gt; 
   &lt;td&gt;每次修复轮次 + 链条前进 → 更多修复&lt;/td&gt; 
   &lt;td&gt;BAL 应用是本地且快速的；尾部很小&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;收敛保证&lt;/td&gt; 
   &lt;td&gt;弱：修复必须超越链条增长&lt;/td&gt; 
   &lt;td&gt;强：确定性、有限的工作&lt;/td&gt; 
  &lt;/tr&gt; 
 &lt;/tbody&gt; 
&lt;/table&gt; 
&lt;p&gt;&lt;strong&gt;比较 &lt;code&gt;snap/1&lt;/code&gt; 与 &lt;code&gt;snap/2&lt;/code&gt; 的完整流程如下：&lt;/strong&gt;&lt;/p&gt; 
&lt;p&gt;&lt;img src=&quot;https://img.learnblockchain.cn/2026/03/10/2e4a686c3d3f88396bbb74b1d94b27874f4_2_373x500.jpeg&quot; alt=&quot;snapv2flowdiagram&quot; class=&quot;aligncenter&quot;&gt;\ snapv2flowdiagram1455×1995 294 KB&lt;/p&gt; 
&lt;h3&gt;故障模式&lt;/h3&gt; 
&lt;table&gt; 
 &lt;thead&gt; 
  &lt;tr&gt; 
   &lt;th&gt;故障&lt;/th&gt; 
   &lt;th&gt;snap/1&lt;/th&gt; 
   &lt;th&gt;snap/2&lt;/th&gt; 
  &lt;/tr&gt; 
 &lt;/thead&gt; 
 &lt;tbody&gt; 
  &lt;tr&gt; 
   &lt;td&gt;修复无法收敛&lt;/td&gt; 
   &lt;td&gt;真实风险：trie 修复速度慢到链条可以超越它&lt;/td&gt; 
   &lt;td&gt;几乎消除：只有 BAL 下载需要网络&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;不可用数据&lt;/td&gt; 
   &lt;td&gt;无：snap/1 只要求修复超越链条&lt;/td&gt; 
   &lt;td&gt;弱主观性周期（约 15.7 天）很宽裕&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;坏数据&lt;/td&gt; 
   &lt;td&gt;默克尔证明捕获坏 trie 节点&lt;/td&gt; 
   &lt;td&gt;哈希比较捕获坏 BAL&lt;/td&gt; 
  &lt;/tr&gt; 
  &lt;tr&gt; 
   &lt;td&gt;重组越过枢轴&lt;/td&gt; 
   &lt;td&gt;可恢复：trie 修复根据新的规范链解决状态&lt;/td&gt; 
   &lt;td&gt;如果保留了孤立的 BAL 则可恢复；否则需要同步重启&lt;/td&gt; 
  &lt;/tr&gt; 
 &lt;/tbody&gt; 
&lt;/table&gt; 
&lt;h3&gt;具体示例&lt;/h3&gt; 
&lt;p&gt;考虑在区块 22,000,000 处的枢轴，当状态下载完成时，链条已领先 200 个区块：&lt;/p&gt; 
&lt;p&gt;&lt;strong&gt;snap/1：&lt;/strong&gt; 从区块 22,000,200 的状态根开始 trie 遍历。每一轮发现更多差异，深入更深。同时，修复期间有多个新区块到达。在最佳情况下，这需要几分钟；在病态情况（慢磁盘、慢网络）下，它需要几天。&lt;/p&gt; 
&lt;p&gt;&lt;strong&gt;snap/2：&lt;/strong&gt; 请求多个区块的 BAL。在 60M 区块 gas 限制下，这约为 4–5 MiB，适合几个响应。在本地应用 BAL，可选地验证状态根是否匹配。应用期间又到达了几个区块？获取 2–3 个更多 BAL。总计：2–3 次往返，几秒钟完成。&lt;/p&gt; 
&lt;blockquote&gt; 
 &lt;ul&gt; 
  &lt;li&gt;原文链接： ethresear.ch/t/snap-v2-r...&lt;/li&gt; 
  &lt;li&gt;登链社区 AI 助手，为大家转译优秀英文文章，如有翻译不通的地方，还请包涵～&lt;/li&gt; 
 &lt;/ul&gt; 
&lt;/blockquote&gt;</description><pubDate>Fri, 20 Mar 2026 01:40:33 +0800</pubDate></item><item><title>中本聪留给读者的习题</title><link>http://htzkw.com/post/9719.html</link><description>&lt;blockquote&gt; 
 &lt;p&gt;&lt;em&gt;作者：Gloria Zhao&lt;/em&gt;&lt;/p&gt; 
 &lt;p&gt;&lt;em&gt;来源： https://learnblockchain.cn/article/24359&lt;/em&gt;&lt;/p&gt; 
&lt;/blockquote&gt; 
&lt;p&gt;比特币白皮书对比特币的核心特性的说明非常清楚：它是免许可的。世界上的任何人，只要加入这个点对点网络并广播一笔交易，就能给另一个人支付（不管他是谁）。工作量证明共识机制甚至让任何人都能成为一个区块生产者，并且意味着：逆转一笔支付的唯一办法，就是控制超过其他所有人的算力。&lt;/p&gt; 
&lt;p&gt;但是，工作量证明只定义了如何在相互竞争的链条间挑出一个胜出者；它并不能帮助一个节点发现这样一条区块链。如果一个攻击者能够阻止目标节点们知道这些相互见证的链条，那么 “51% 攻击” —— 或者说 100% 攻击 —— 就容易多了。这个发现的工作，就落到了点对点模块上，它杂糅了许多自相矛盾的工作：在一个节点们总是来来去去的网络中，找出诚实的对等节点，但又不能依靠身份认证或者声誉机制；时刻留意区块和交易，但如果收到的大部分数据都是垃圾，也保持淡定；要强壮到能在最极端的敌意条件下生存，又要轻量到能够运行在树莓派（Raspberry Pi）单主板电脑上。&lt;/p&gt; 
&lt;p&gt;这个免许可的点对点网络的实现细节，在白皮书中毫无着墨，却成了今天的比特币节点软件的复杂性的大头。&lt;/p&gt; 
&lt;h2&gt;过滤器与滥发&lt;/h2&gt; 
&lt;p&gt;比特币白皮书承认公开交易的转发是比特币的抗审查性的基石，但是，关于它应该如何运行，就只有寥寥数语：“新交易会广播给所有节点。每个节点都会收集新交易、打包到一个区块中。每个节点都要持续运算，以为自己的区块找到一个工作量证明。” 1&lt;/p&gt; 
&lt;p&gt;许多人都觉得中本聪的这个想法很 “有趣”：每个节点都要挖矿。由于挖矿波动性的中心化压力，今天，网络中的绝大部分节点都不挖矿（寻找工作量证明）。也许，这是一个可以接受的结果（甚至是经济激励机制成功运行的结果），我们牺牲了一部分去中心化程度，换来了更多的哈希算力以及由此而来的安全性。然而，如果我们也放弃了交易转发的去中心化，比特币的抗审查性就会崩溃。&lt;/p&gt; 
&lt;p&gt;但是，我们对交易转发节点的宽阔暂存池的需求，必须根据计算机们每日暴露在免许可的网络中、处理来自匿名对等节点的数据的具体情形来取舍。这种威胁模型是独一无二的，并且需要非常谨慎的编程。&lt;/p&gt; 
&lt;p&gt;在区块下载方面，一个区块的工作量证明，已经优雅地充任了 “拒绝服务式攻击（DoS）” 的防范措施，以及一种衡量数据有用性的明确方法。相反，待确认的交易数据，是几乎可以免费创建的，而且可能是滥发的（spam）。 比如说，我们不可能知道一笔交易是否满足了其输入的花费条件，除非我们加载（这些输入所指向的）UTXO，这可能需要从磁盘中读取出来。攻击者几乎毫无成本，就能触发这种相对高时延的活动：他们可以创建大体积的交易，使用不属于他们自己的输入，甚至是完全不存在的输入。&lt;/p&gt; 
&lt;p&gt;对交易的验证步骤，比如签名签名，以及交易依赖性的管理，可以消耗大量计算。很著名的一个例子是，带有大量传统的（隔离见证以前的）签名的一笔交易 ，在某些硬件上可能需要几分钟来验证 2，所以绝大部分节点都会过滤掉大体积的交易。这些资源的消耗不会仅仅局限在节点内部：被一个节点（的交易池）接纳的交易通常也会传播给其它对等节点，使用的带宽将与网络中的节点数量成正比。&lt;/p&gt; 
&lt;p&gt;节点保护自己的手段是限制未确认交易和验证队列的内存用量、为每个对等节点限制交易的处理速度，并且在共识规则之上实施交易池规则。虽然这些限制，如果没有仔细设计的话，可能会带来审查界面。诸如不下载此前已经拒绝过的交易、限制一个对等节点传输的交易队列的体积、在下载失败之后抛弃请求，这样的简单逻辑都有可能导致节点无法看见某一笔交易。这些 bug ，一旦被有心的攻击者利用，就会变成意料之外的审查界面。&lt;/p&gt; 
&lt;p&gt;由此可见，虽然不保留彼此重复花费的未确认交易是完全符合逻辑的（因为最终只有其中一笔交易会是有效的）， 但是，拒绝重复花费的交易，也就意味着早广播的那笔交易，就能阻止晚广播的那笔交易被挖出（得到区块确认）。而重复花费，也可能是伪造支付的有意之举；或者，当一个 UTXO（未花费的交易输出）属于多个人时，可能有人会利用节点的交易池策略来拖延或阻止其他人的二层协议结算交易被挖出，这被称为 “钉死攻击（pinning attack）”。 那么，节点应该怎么做呢？&lt;/p&gt; 
&lt;p&gt;这个问题向我们揭示了交易转发的第二个元素：激励兼容性 3。虽然交易手续费在共识上，除了限制一个矿工能拿走多少区块奖励，别无其它意义，但它们在交易池队则中，作为一个衡量有用性的指标，扮演了重大角色。假设矿工们是由经济激励驱动的，节点就能估计哪些交易是最吸引矿工打包的、然后将最不吸引矿工的交易抛弃。如果多笔交易花费了同一个 UTXO，那么节点可以保留最吸引矿工的（打包起来最有利可图的）那一笔。虽然节点并不能收到交易手续费，它们可以将零手续费的交易当成滥发：它们会可能会使用网络资源，但永远不会被挖出，也就是几乎不花费任何成本就能创造出来。&lt;/p&gt; 
&lt;p&gt;这两个设计目标 —— DoS 抗性和激励兼容性 —— 也有矛盾之处。虽然允许用更高手续费的版本来替换已经出现的交易版本，对矿工有吸引力，但是，只需追加少量手续费就能重复替换交易，也可能会浪费网络的带宽。考虑待确认的交易之间的依赖性，可以带来挖矿收益更高的区块（并启用 “子为亲偿（CPFP）” 特性），但在复杂的交易间拓扑中，也可能会消耗大量资源。&lt;/p&gt; 
&lt;p&gt;（译者注：CPFP 指通过携带更高费率的子交易来提高打包父交易的吸引力，也即提高父交易的确认优先级。）&lt;/p&gt; 
&lt;p&gt;在历史上，节点依赖于启发式分析和依赖性限制，它们导致了用户体验摩擦，并且带来了新的钉死攻击界面。跟踪交易族群（cluster）的交易池实现可以更准确地估计激励兼容性，但依然必须限制交易池内的交易间依赖。这些类型的限制为涉及互不信任的多方的交易创造了钉死攻击界面：攻击者可以通过独自用尽配额来阻止参与共同交易的其他人使用 CPFP。&lt;/p&gt; 
&lt;p&gt;这些问题很容易被轻视：钉死攻击是一种小众的审查攻击类型，只能运用在多方共享的交易中，而且通常只会导致短暂的推迟确认。为了帮助不挖矿的节点节约几个聪的手续费，值得付出努力吗？&lt;/p&gt; 
&lt;h2&gt;那么 Mevil 呢？&lt;/h2&gt; 
&lt;p&gt;多方共享的交易，是基于 UTXO 混合的隐私解决方案以及二层协议的骨架。许多开发工作，都是致力于在二层协议上创建可扩展、隐私和特性丰富的应用；而这些协议会回到一层（也就是区块链内）来结算。一种常用的模式是暂时推迟取款（或者说结算），从而允许参与者们在一个时间窗口内应对其他人的不轨行为。但许多设计 —— 也包括那些用来鼓吹共识变更的设计 —— 都忽略了在这些场景中如何为交易追加手续费的问题。&lt;/p&gt; 
&lt;p&gt;防止不轨行为的时间窗口，也是攻击者的机会窗口。这两个条件 —— 多方共享交易和防止不轨行为的截止时间 —— 创造了完美的风暴，让钉死攻击能够从暂时推迟交易确认（嗯哼？）升级成潜在的盗窃（不是吧！）。&lt;/p&gt; 
&lt;p&gt;钉死攻击已经是多年的研究和开发的主题，从中产生除了 “拓扑受限直至确认（TRUC）” 交易格式 4，“支付到锚点（P2A）” 输出类型 5， “临时粉尘”交易池规则 6，“族群交易池” 实现 7，受限的交易包转发 8，以及许多对交易转发可靠性的提升。这些特性的设计目的是为传播共享交易的更高手续费替代版本提高更加强壮的保证。&lt;/p&gt; 
&lt;p&gt;但是，合理的手续费管理也有代价，比如更大体积的交易、更复杂的钱包软件逻辑、对不太可能发生的罕见情形的处理。想要轻松的捷径，也有，那就是跟一个矿工达成交易：你支付手续费，矿工保证你的交易会被优先打包。这种解决方案可能比使用点对点网络更加可靠 —— 后者可能会因为网络中的节点使用不同质的交易池策略，而有更高的时延和更慢的传播速度。&lt;/p&gt; 
&lt;p&gt;直接提交给矿工这种手法，可能会被迅速采用，毕竟有商业利益。交易所需要发送大量以，而且，比起优化手续费，可能更偏好可预测的确认时机。热门的应用可能会对钉死攻击不胜其烦，或者希望使用通行的交易池规则所禁止的非标准交易格式。担心量子短程攻击（quantum short-range attacks）的公司们和托管商们可能会跟一个矿工达成私下交易。&lt;/p&gt; 
&lt;p&gt;随着隐秘的 “矿工可抽取价值（MEVil）” 9 成为矿工保持竞争力的必要手段，比特币网络可能会走向中心化的区块空间经纪人模式。这些服务可能会成为攻击者和政府指令的目标，从而动摇 “无需许可即可成为矿工” 的承诺。&lt;/p&gt; 
&lt;p&gt;如果交易转发网络对于节点的运营变得无关紧要，那么可能参与其中也会显得毫无必要。在这种假想的未来，我们会不会像今天看待中本聪的想法（每个节点都是一个矿工）那样，认为 “每个节点都转发待确认的交易” 同样不可思议？&lt;/p&gt; 
&lt;p&gt;讽刺之处在于，挖矿中心化的压力并不起源于串通或监管压力。它竟来自一些理性的捷径：更高效的合约、定制化的转发路径，或者对参与者们有好处的性能优化。没有人能阻止这些合约出现。但我们可以尝试削弱这些私有服务对公开网络的竞争优势：在考虑可能提高 MEVil 潜力的共识变更以前，先驱逐交易池的钉死攻击界面；让公开的交易转发网络，成为竞标区块空间（以及更新竞标）的高效市场。&lt;/p&gt; 
&lt;p&gt;点对点网络是让比特币的许多核心意识形态获得生命的场所。它也是一个持续的工程挑战，要在节点高效运行、抗审查性、激励兼容性和协议复杂性之间作出艰难的取舍。随着比特币的状态，它只会变得更难。应该如何协调这些相互竞争的设计目标，是留给读者的习题。&lt;/p&gt; 
&lt;p&gt;（完）&lt;/p&gt; 
&lt;h2&gt;参考文献&lt;/h2&gt; 
&lt;ol&gt; 
 &lt;li&gt; &lt;p&gt;https://bitcoin.org/bitcoin.pdf ↩&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;https://delvingbitcoin.org/t/great-consensus-cleanup-revival/710 （ 中文译本） ↩&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;https://delvingbitcoin.org/t/mempool-incentive-compatibility/553 ↩&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;https://github.com/bitcoin/bips/blob/master/bip-0431.mediawiki ↩&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;https://github.com/bitcoin/bitcoin/pull/30352 ↩&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;https://bitcoinops.org/en/topics/ephemeral-anchors/ ↩&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;https://delvingbitcoin.org/t/an-overview-of-the-cluster-mempool-proposal/393?u=glozow （ 中文译本） ↩&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;https://bitcoinops.org/en/topics/package-relay/ ↩&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;https://bluematt.bitcoin.ninja/2024/04/16/stop-calling-it-mev/ （ 中文译本） ↩&lt;/p&gt; &lt;/li&gt; 
&lt;/ol&gt; 
&lt;blockquote&gt; 
 &lt;ul&gt; 
  &lt;li&gt;本文转载自： btcstudy.org/2026/03/10/... , 如有侵权请联系管理员删除。&lt;/li&gt; 
 &lt;/ul&gt; 
&lt;/blockquote&gt;</description><pubDate>Fri, 20 Mar 2026 01:40:33 +0800</pubDate></item><item><title>REVM源码阅读- Interpreter</title><link>http://htzkw.com/post/9718.html</link><description>&lt;h2&gt;Interpreter&lt;/h2&gt; 
&lt;p&gt;&lt;code&gt;REVM&lt;/code&gt; 是台计算机的话, &lt;code&gt;Interpreter&lt;/code&gt; 就是 &lt;code&gt;CPU&lt;/code&gt; .&lt;br&gt; 由它来负责对字节码进行解释执行,完成本次交易想要实现的目的.&lt;br&gt; 前面的内容最多算准备工作,提供 &lt;code&gt;Interpreter&lt;/code&gt; 执行所需要的内容.&lt;br&gt; 理解了它,才算真正入门了 &lt;code&gt;REVM&lt;/code&gt;.&lt;/p&gt; 
&lt;p&gt;Interpreter 存在于每个 Frame 中,每次新建 Frame 会创建新的 Interpreter.&lt;br&gt; 并不是整个交易共享一个 Interpreter.&lt;/p&gt; 
&lt;p&gt;先看下结构体定义中的类型,部分类型前面已经讲过,这里再概括下.&lt;br&gt; 没讲的类型在下面展开讲.&lt;br&gt; 把各个类型的功能和作用讲清楚,后面执行逻辑就是对各种类型的操作.&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;GasParams&lt;/code&gt; 这个类型,在 Frame 中已经讲过,保存 Opcode 静态和动态的 Gas 消耗.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;Bytecode&lt;/code&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;Gas&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;Stack&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;ReturnData&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;Memory&lt;/code&gt; 共享内存Buffer, 每个 Frame 指定一个范围进行数据临时缓存.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;Input&lt;/code&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;RuntimeFlag&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;Extend&lt;/code&gt;&lt;/li&gt; 
&lt;/ul&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/interpreter.rs
#[derive(Debug, Clone)]
#[cfg_attr(feature = &quot;serde&quot;, derive(serde::Serialize, serde::Deserialize))]
pub struct Interpreter&amp;amp;lt;WIRE: InterpreterTypes = EthInterpreter&amp;gt; {
    /// Gas table for dynamic gas constants.
    pub gas_params: GasParams,
    /// Bytecode being executed.
    pub bytecode: WIRE::Bytecode,
    /// Gas tracking for execution costs.
    pub gas: Gas,
    /// EVM stack for computation.
    pub stack: WIRE::Stack,
    /// Buffer for return data from calls.
    pub return_data: WIRE::ReturnData,
    /// EVM memory for data storage.
    pub memory: WIRE::Memory,
    /// Input data for current execution context.
    pub input: WIRE::Input,
    /// Runtime flags controlling execution behavior.
    pub runtime_flag: WIRE::RuntimeFlag,
    /// Extended functionality and customizations.
    pub extend: WIRE::Extend,
}
&lt;/code&gt;&lt;/pre&gt; 
&lt;h2&gt;ExtBytecode&lt;/h2&gt; 
&lt;p&gt;上面的类型是 &lt;code&gt;Bytecode&lt;/code&gt;, 但是使用的 &lt;code&gt;ExtBytecode&lt;/code&gt;.&lt;br&gt; ExtBytecode 是在 ByteCode 的基础上再进行一层封装,分担了一部分 Interpreter 的工作.&lt;/p&gt; 
&lt;h4&gt;Bytecode&lt;/h4&gt; 
&lt;p&gt;先讲下 &lt;code&gt;Bytecode&lt;/code&gt; 的部分&lt;br&gt; &lt;code&gt;Bytecode&lt;/code&gt; 不是结构体,是枚举 &lt;code&gt;Enum&lt;/code&gt;,分为两种:&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;Eip7702Bytecode&lt;/code&gt; EIP-7702类型,合约账户使用 
  &lt;ul&gt; 
   &lt;li&gt;&lt;code&gt;delegated_address&lt;/code&gt; 委托地址,就是我调用&lt;/li&gt; 
   &lt;li&gt;&lt;code&gt;version&lt;/code&gt; 版本,现在只有一个版本0.&lt;/li&gt; 
   &lt;li&gt;&lt;code&gt;raw&lt;/code&gt; &lt;code&gt;23字节&lt;/code&gt;的字节码.内容 为 0xef01(固定) + 00(版本) + address(20字节的委托地址)&lt;/li&gt; 
  &lt;/ul&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;LegacyAnalyzedBytecode&lt;/code&gt; 传统字节码(现有的合约字节码) 
  &lt;ul&gt; 
   &lt;li&gt;&lt;code&gt;bytecode&lt;/code&gt; 字节码&lt;/li&gt; 
   &lt;li&gt;&lt;code&gt;original_len&lt;/code&gt; 原始长度( &lt;code&gt;bytecode&lt;/code&gt; 为了防止 &lt;code&gt;pc&lt;/code&gt; 指向非法,会在尾部填充 &lt;code&gt;STOP(0x00)&lt;/code&gt;)&lt;/li&gt; 
   &lt;li&gt;&lt;code&gt;jump_table&lt;/code&gt; &lt;code&gt;JUMP&lt;/code&gt; 只能跳转到 &lt;code&gt;JUMPDEST&lt;/code&gt; 指令,这里提前扫描保存了所有可跳转点. 
    &lt;ul&gt; 
     &lt;li&gt;jump_table 不是直接插入保存每个 &lt;code&gt;JUMPDEST&lt;/code&gt; 的位置.&lt;/li&gt; 
     &lt;li&gt;是用一个长度等于 &lt;code&gt;bytecode&lt;/code&gt; 的 &lt;code&gt;bitvec&amp;amp;lt;u8&amp;gt;&lt;/code&gt;,初始默认0,在 &lt;code&gt;jumpdest&lt;/code&gt; 的位置标记为1,具体在后面解释.&lt;/li&gt; 
    &lt;/ul&gt;&lt;/li&gt; 
  &lt;/ul&gt;&lt;/li&gt; 
&lt;/ul&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/bytecode/src/bytecode.rs
pub enum Bytecode {
    /// EIP-7702 delegated bytecode
    Eip7702(Arc&amp;amp;lt;Eip7702Bytecode&amp;gt;),
    /// The bytecode has been analyzed for valid jump destinations.
    LegacyAnalyzed(Arc&amp;amp;lt;LegacyAnalyzedBytecode&amp;gt;),
}
// crates/bytecode/src/eip7702.rs
pub struct Eip7702Bytecode {
    pub delegated_address: Address,
    pub version: u8,
    pub raw: Bytes,
}
// crates/bytecode/src/legacy/analyzed.rs
pub struct LegacyAnalyzedBytecode {
    bytecode: Bytes,
    original_len: usize,
    jump_table: JumpTable,
}&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;&lt;code&gt;Eip7702Bytecode&lt;/code&gt; 的实现部分很简单.简单的 &lt;code&gt;Getter&lt;/code&gt;.&lt;br&gt; &lt;code&gt;new&lt;/code&gt; 和 &lt;code&gt;new_raw&lt;/code&gt; 是在构造上面说的 &lt;code&gt;raw&lt;/code&gt; 格式的内容.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;impl Eip7702Bytecode {
    pub fn new_raw(raw: Bytes) -&amp;gt; Result&amp;amp;lt;Self, Eip7702DecodeError&amp;gt; {
        if raw.len() != 23 {
            return Err(Eip7702DecodeError::InvalidLength);
        }
        if !raw.starts_with(&amp;amp;EIP7702_MAGIC_BYTES) {
            return Err(Eip7702DecodeError::InvalidMagic);
        }

        // Only supported version is version 0.
        if raw[2] != EIP7702_VERSION {
            return Err(Eip7702DecodeError::UnsupportedVersion);
        }

        Ok(Self {
            delegated_address: Address::new(raw[3..].try_into().unwrap()),
            version: raw[2],
            raw,
        })
    }
    pub fn new(address: Address) -&amp;gt; Self {
        let mut raw = EIP7702_MAGIC_BYTES.to_vec();
        raw.push(EIP7702_VERSION);
        raw.extend(&amp;amp;address);
        Self {
            delegated_address: address,
            version: EIP7702_VERSION,
            raw: raw.into(),
        }
    }
    pub fn raw(&amp;amp;self) -&amp;gt; &amp;amp;Bytes {
        &amp;amp;self.raw
    }
    pub fn address(&amp;amp;self) -&amp;gt; Address {
        self.delegated_address
    }
    pub fn version(&amp;amp;self) -&amp;gt; u8 {
        self.version
    }
}&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;看下 &lt;code&gt;LegacyAnalyzedBytecode&lt;/code&gt; 的实现.&lt;/p&gt; 
&lt;p&gt;只需要关注 &lt;code&gt;analyze_legacy&lt;/code&gt; , 其他的都是 &lt;code&gt;Getter&lt;/code&gt;.&lt;br&gt; 跳到&lt;code&gt;super::analysis::analyze_legacy&lt;/code&gt; 的具体实现&lt;br&gt; 这个函数是为了获取前面的 &lt;code&gt;jump_table&lt;/code&gt; , &lt;code&gt;jump_table&lt;/code&gt; 是一个 &lt;code&gt;bitvec&amp;amp;lt;u8&amp;gt;&lt;/code&gt;.&lt;/p&gt; 
&lt;p&gt;看清楚这里是&lt;code&gt;bitvec&lt;/code&gt;,而不是vec.这是一个坑,我一开始就搞错了.&lt;br&gt; 它实际保存的是位图,不过用 &lt;code&gt;u8&lt;/code&gt; 来实现.一个 &lt;code&gt;u8&lt;/code&gt; 实际可以保存8个bool.&lt;br&gt; 也就是这里jump_table中的操作都是位操作.一定要记住. pc 为 8 的jumpdest位置是在第2个字节,而不是第8个字节.&lt;br&gt; 而且在字节中的顺序是逆序的,pc为8的话是第二个字节最右边的最低位,而不是最左边的最高位.&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;let range = bytecode.as_ptr_range();&lt;/code&gt; 这里返回了 &lt;code&gt;bytecode&lt;/code&gt; 的 &lt;code&gt;起始指针&lt;/code&gt; 和 &lt;code&gt;结束指针&lt;/code&gt;.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;iterator&lt;/code&gt; 是指针位置.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;while&lt;/code&gt; 循环并对 &lt;code&gt;iterator&lt;/code&gt; 解引用获取所在位置的 &lt;code&gt;Opcode&lt;/code&gt;. 
  &lt;ul&gt; 
   &lt;li&gt;判断 &lt;code&gt;Opcode&lt;/code&gt; 是否 &lt;code&gt;JUMPDEST&lt;/code&gt;, 是则将 &lt;code&gt;jumps&lt;/code&gt; 中的该位置设置为 &lt;code&gt;True&lt;/code&gt;. 
    &lt;ul&gt; 
     &lt;li&gt;&lt;code&gt;set_unchecked&lt;/code&gt; 设置指定位置为 True.&lt;/li&gt; 
     &lt;li&gt;&lt;code&gt;iterator.offset_from_unsigned(start)&lt;/code&gt; ,计算 &lt;code&gt;iterator - start&lt;/code&gt; 的值.&lt;/li&gt; 
    &lt;/ul&gt;&lt;/li&gt; 
   &lt;li&gt;判断 &lt;code&gt;Opcode&lt;/code&gt; 是否 &lt;code&gt;PUSH1-PUSH32 (0x60 - 0x7f)&lt;/code&gt; 
    &lt;ul&gt; 
     &lt;li&gt;如果是, &lt;code&gt;iterator + push_offset + 2&lt;/code&gt;&lt;/li&gt; 
    &lt;/ul&gt;&lt;/li&gt; 
  &lt;/ul&gt;&lt;/li&gt; 
 &lt;li&gt;在 bytecode 后面补充&lt;code&gt;0x00 (STOP)&lt;/code&gt;,避免后面执行代码时 &lt;code&gt;PC&lt;/code&gt; 越界. 
  &lt;ul&gt; 
   &lt;li&gt;&lt;code&gt;let padding = (iterator as usize) - (end as usize) + (opcode != opcode::STOP) as usize&lt;/code&gt; 
    &lt;ul&gt; 
     &lt;li&gt;这里是计算要补充几个 &lt;code&gt;0x00&lt;/code&gt;.为什么不是只补充一个 &lt;code&gt;0x00&lt;/code&gt; 我们讲下: 
      &lt;ul&gt; 
       &lt;li&gt;正常没有 PUSH 指令, &lt;code&gt;iterator&lt;/code&gt; 递增,不会有啥问题.&lt;/li&gt; 
       &lt;li&gt;如果最后一个 &lt;code&gt;OP&lt;/code&gt; 是 &lt;code&gt;PUSH32&lt;/code&gt;, &lt;code&gt;bytecode&lt;/code&gt; 又被异常截断.导致数据不够,&lt;code&gt;iterator&lt;/code&gt;跳转后会超出 &lt;code&gt;end&lt;/code&gt;.&lt;/li&gt; 
       &lt;li&gt;这段代码就是处理这种异常情况,保证异常时也能执行.不够的数据填充&lt;code&gt;0x00&lt;/code&gt;.&lt;/li&gt; 
      &lt;/ul&gt;&lt;/li&gt; 
    &lt;/ul&gt;&lt;/li&gt; 
   &lt;li&gt; &lt;/li&gt; 
  &lt;/ul&gt;&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;&lt;strong&gt;注:&lt;/strong&gt; &lt;code&gt;opcode.wrapping_sub(opcode::PUSH1)&lt;/code&gt; &lt;code&gt;PUSH1-PUSH32&lt;/code&gt;是连续的 &lt;code&gt;Opcode&lt;/code&gt; ,减去 &lt;code&gt;PUSH1&lt;/code&gt;得到的值是在 &lt;code&gt;0-31&lt;/code&gt; ,说明就是 &lt;code&gt;PUSH1-PUSH32&lt;/code&gt;.&lt;br&gt; &lt;code&gt;PUSH&lt;/code&gt; 的指令比较特殊.它会将数据直接编码到指令后面. &lt;code&gt;PUSHx&lt;/code&gt; 后面的 &lt;code&gt;x个字节&lt;/code&gt; 都是它的数据.&lt;br&gt; 所以 &lt;code&gt;PUSH&lt;/code&gt; 指令的 &lt;code&gt;iterator&lt;/code&gt; 是`iterator + (push_offset + 1) + 1&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/bytecode/src/legacy/analyzed.rs
impl LegacyAnalyzedBytecode {
    pub fn analyze(bytecode: Bytes) -&amp;gt; Self {
        let original_len = bytecode.len();
        let (jump_table, padded_bytecode) = super::analysis::analyze_legacy(bytecode);
        Self::new(padded_bytecode, original_len, jump_table)
    }
}

// crates/bytecode/src/legacy/analysis.rs
pub fn analyze_legacy(bytecode: Bytes) -&amp;gt; (JumpTable, Bytes) {
    if bytecode.is_empty() {
        return (JumpTable::default(), Bytes::from_static(&amp;amp;[opcode::STOP]));
    }

    let mut jumps: BitVec&amp;amp;lt;u8&amp;gt; = bitvec![u8, Lsb0; 0; bytecode.len()];
    let range = bytecode.as_ptr_range();
    let start = range.start;
    let mut iterator = start;
    let end = range.end;
    let mut opcode = 0;

    while iterator &amp;amp;lt; end {
        opcode = unsafe { *iterator };
        if opcode == opcode::JUMPDEST {
            // SAFETY: Jumps are max length of the code
            unsafe { jumps.set_unchecked(iterator.offset_from_unsigned(start), true) }
            iterator = unsafe { iterator.add(1) };
        } else {
            let push_offset = opcode.wrapping_sub(opcode::PUSH1);
            if push_offset &amp;amp;lt; 32 {
                // SAFETY: Iterator access range is checked in the while loop
                iterator = unsafe { iterator.add(push_offset as usize + 2) };
            } else {
                // SAFETY: Iterator access range is checked in the while loop
                iterator = unsafe { iterator.add(1) };
            }
        }
    }

    let padding = (iterator as usize) - (end as usize) + (opcode != opcode::STOP) as usize;
    let bytecode = if padding &amp;gt; 0 {
        let mut padded = Vec::with_capacity(bytecode.len() + padding);
        padded.extend_from_slice(&amp;amp;bytecode);
        padded.resize(padded.len() + padding, 0);
        Bytes::from(padded)
    } else {
        bytecode
    };

    (JumpTable::new(jumps), bytecode)
}&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;前面讲的是 &lt;code&gt;Eip7702Bytecode&lt;/code&gt; 和 &lt;code&gt;LegacyAnalyzedBytecode&lt;/code&gt; 的实现.&lt;br&gt; 接下来继续讲下 Bytecode 的实现.&lt;br&gt; 函数内容都一目了然,这里只给出函数列表和功能.&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;new()&lt;/code&gt; - 创建一个新的默认 &lt;code&gt;Bytecode&lt;/code&gt;，包含一个 &lt;code&gt;STOP&lt;/code&gt; 操作码的 &lt;code&gt;legacy&lt;/code&gt; 分析字节码&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;legacy_jump_table()&lt;/code&gt; - 返回跳转表（如果是已分析的 &lt;code&gt;legacy&lt;/code&gt; 字节码），否则返回 &lt;code&gt;None&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;hash_slow()&lt;/code&gt; - 计算字节码的 &lt;code&gt;Keccak-256&lt;/code&gt; 哈希值（如果为空则返回 &lt;code&gt;KECCAK_EMPTY&lt;/code&gt;）&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;is_eip7702()&lt;/code&gt; - 判断字节码是否为 &lt;code&gt;EIP-7702&lt;/code&gt; 类型&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;new_legacy(raw)&lt;/code&gt; - 从原始字节创建一个新的 &lt;code&gt;legacy&lt;/code&gt; 字节码（会自动分析）&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;new_raw(bytecode)&lt;/code&gt; - 从原始字节创建字节码，自动检测类型（&lt;code&gt;EIP-7702&lt;/code&gt; 或 &lt;code&gt;legacy&lt;/code&gt;），格式错误时会 &lt;code&gt;panic&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;new_raw_checked(bytes)&lt;/code&gt;- 从原始字节创建字节码，自动检测类型，格式错误时返回 Err&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;new_eip7702(address)&lt;/code&gt; - 从地址创建一个新的 &lt;code&gt;EIP-7702&lt;/code&gt; 委托字节码&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;new_analyzed(bytecode, original_len, jump_table)&lt;/code&gt; - 直接创建已分析的 &lt;code&gt;legacy&lt;/code&gt; 字节码（需提供跳转表）&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;bytecode()&lt;/code&gt; - 返回字节码的引用&lt;code&gt;（&amp;amp;Bytes）&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;bytecode_ptr()&lt;/code&gt; - 返回可执行字节码的原始指针&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;bytes()&lt;/code&gt; - 返回字节码的克隆&lt;code&gt;（Bytes）&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;bytes_ref()&lt;/code&gt; - 返回原始字节的引用&lt;code&gt;（&amp;amp;Bytes）&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;bytes_slice()&lt;/code&gt; - 返回原始字节切片&lt;code&gt;（&amp;amp;[u8]）&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;original_bytes()&lt;/code&gt; - 返回原始字节码（未填充的，克隆）&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;original_byte_slice()&lt;/code&gt; - 返回原始字节码切片（未填充的）&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;len()&lt;/code&gt; - 返回原始字节码的长度&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;is_empty()&lt;/code&gt; - 判断字节码是否为空&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;iter_opcodes()&lt;/code&gt; - 返回一个迭代器，遍历字节码中的操作码（跳过立即数）&lt;/li&gt; 
&lt;/ul&gt; 
&lt;h4&gt;ExtBytecode&lt;/h4&gt; 
&lt;p&gt;照例先看下结构的部分:&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;instruction_pointer&lt;/code&gt; 指令指针位置.类似刚才讲的 &lt;code&gt;analyze_legacy&lt;/code&gt; 中的 &lt;code&gt;iterator&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;continue_execution&lt;/code&gt; 当前 Frame 是否结束.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;bytecode_hash&lt;/code&gt; 当前 &lt;code&gt;bytecode&lt;/code&gt; 的 &lt;code&gt;keccak256&lt;/code&gt; 值.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;action&lt;/code&gt; 返回结果,当前 &lt;code&gt;Frame&lt;/code&gt; 是结束,还是创建 &lt;code&gt;新的Frame&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;base&lt;/code&gt; 执行的字节码&lt;/li&gt; 
&lt;/ul&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/interpreter/ext_bytecode.rs
pub struct ExtBytecode {
    instruction_pointer: *const u8,
    continue_execution: bool,
    bytecode_hash: Option&amp;amp;lt;B256&amp;gt;,
    pub action: Option&amp;amp;lt;InterpreterAction&amp;gt;,
    base: Bytecode,
}&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;关键实现&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;pc&lt;/code&gt; 程序计数器,指示当前正在执行的字节码位置. 
  &lt;ul&gt; 
   &lt;li&gt;用&lt;code&gt;instruction_pointer - 字节码起始位置&lt;/code&gt; 得到的值&lt;/li&gt; 
  &lt;/ul&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;opcode&lt;/code&gt; 当前执行的字节码&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;relative_jump&lt;/code&gt; 相对跳转,以当前 &lt;code&gt;instruction_pointer&lt;/code&gt; 指向为基础进行 &lt;code&gt;offset&lt;/code&gt; 的跳转.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;absolute_jump&lt;/code&gt; 绝对条转,以字节码的起始位置进行 &lt;code&gt;offset&lt;/code&gt; 的跳转. &lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;is_valid_legacy_jump&lt;/code&gt; 是否合法跳转.通过查找跳转位置是否在 &lt;code&gt;JumpTable&lt;/code&gt; 中.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;read_u16&lt;/code&gt; 从 &lt;code&gt;instruction_pointer&lt;/code&gt; 指向位置读取2个字节. 
  &lt;ul&gt; 
   &lt;li&gt;&lt;code&gt;EOF&lt;/code&gt; 格式专用,但 &lt;code&gt;EOF&lt;/code&gt; 还没启用.&lt;/li&gt; 
  &lt;/ul&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;read_u8&lt;/code&gt; 从 &lt;code&gt;instruction_pointer&lt;/code&gt; 指向位置读取1个字节.用于获取 &lt;code&gt;Opcode&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;read_slice&lt;/code&gt; 从 &lt;code&gt;instruction_pointer&lt;/code&gt; 指向位置读取指定长度字节. 用于 &lt;code&gt;PUSH&lt;/code&gt; 类的 &lt;code&gt;Opcode&lt;/code&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;read_offset_u16&lt;/code&gt; 从指定位置读取2个字节.&lt;code&gt;EOF&lt;/code&gt; 格式专用, &lt;code&gt;EOF&lt;/code&gt; 还没启用&lt;/li&gt; 
&lt;/ul&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/interpreter/ext_bytecode.rs
impl Jumps for ExtBytecode {
    fn relative_jump(&amp;amp;mut self, offset: isize) {
        self.instruction_pointer = unsafe { self.instruction_pointer.offset(offset) };
    }
    fn absolute_jump(&amp;amp;mut self, offset: usize) {
        self.instruction_pointer = unsafe { self.base.bytes_ref().as_ptr().add(offset) };
    }
    fn is_valid_legacy_jump(&amp;amp;mut self, offset: usize) -&amp;gt; bool {
        self.base
            .legacy_jump_table()
            .expect(&quot;Panic if not legacy&quot;)
            .is_valid(offset)
    }
    fn opcode(&amp;amp;self) -&amp;gt; u8 {
        // SAFETY: `instruction_pointer` always point to bytecode.
        unsafe { *self.instruction_pointer }
    }
    fn pc(&amp;amp;self) -&amp;gt; usize {
        unsafe {
            self.instruction_pointer
                .offset_from_unsigned(self.base.bytes_ref().as_ptr())
        }
    }
}
impl Immediates for ExtBytecode {
    fn read_u16(&amp;amp;self) -&amp;gt; u16 {
        unsafe { read_u16(self.instruction_pointer) }
    }
    fn read_u8(&amp;amp;self) -&amp;gt; u8 {
        unsafe { *self.instruction_pointer }
    }
    fn read_slice(&amp;amp;self, len: usize) -&amp;gt; &amp;amp;[u8] {
        unsafe { core::slice::from_raw_parts(self.instruction_pointer, len) }
    }
    fn read_offset_u16(&amp;amp;self, offset: isize) -&amp;gt; u16 {
        unsafe {
            read_u16(
                self.instruction_pointer
                    // Offset for max_index that is one byte
                    .offset(offset),
            )
        }
    }
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h2&gt;Gas&lt;/h2&gt; 
&lt;p&gt;用于记录当前 Frame 执行过程的 Gas 消耗和 剩余记录.&lt;/p&gt; 
&lt;p&gt;看下结构体&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;limit&lt;/code&gt; 这里是当前 Frame 能用到的Gas Limit, 不是整个交易的&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;remaining&lt;/code&gt; 当前GasLimit 剩余&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;refunded&lt;/code&gt; 退款.(清零Storage之类的会退款)&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;memory&lt;/code&gt; 记录内存扩展花费.在 &lt;code&gt;EVM&lt;/code&gt; 中内存扩展会花费,且成本不是线性,扩展越多花费越高.&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;实现都比较简单.只说下:&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;new(limit)&lt;/code&gt;&amp;nbsp;创建新的 Gas 实例，remaining&amp;nbsp;初始等于 limit&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;new_spent(limit)&lt;/code&gt;&amp;nbsp;创建已耗尽的 Gas 实例，remaining&amp;nbsp;= 0&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;limit()&lt;/code&gt;&amp;nbsp;返回当前 frame&amp;nbsp;的 gas 上限&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;memory()&lt;/code&gt;&amp;nbsp;返回&amp;nbsp;MemoryGas 的不可变引用&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;memory_mut()&lt;/code&gt;&amp;nbsp;返回&amp;nbsp;MemoryGas 的可变引用&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;refunded()&lt;/code&gt;&amp;nbsp;返回累计的退款金额&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;spent()&lt;/code&gt;&amp;nbsp;返回已消耗的 gas&amp;nbsp;(limit - remaining)&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;used()&lt;/code&gt;&amp;nbsp;返回实际使用的 gas (spent - refunded)&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;spent_sub_refunded()&lt;/code&gt;&amp;nbsp;同&amp;nbsp;used()，返回扣除退款后的消耗量&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;remaining()&lt;/code&gt;&amp;nbsp;返回剩余可用的 gas&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;erase_cost(returned)&lt;/code&gt;&amp;nbsp;把&amp;nbsp;gas 加回来，用于子调用返回时归还未用完的 gas&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;spend_all()&lt;/code&gt;&amp;nbsp;消耗所有剩余 gas，设置 remaining&amp;nbsp;= 0&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;record_refund(refund)&lt;/code&gt;&amp;nbsp;记录退款值（可正可负）&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;set_final_refund(is_london)&lt;/code&gt;&amp;nbsp;设置最终退款，应用 EIP-3529 上限&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;set_refund(refund)&lt;/code&gt;&amp;nbsp;直接覆盖设置退款值&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;set_spent(spent)&lt;/code&gt;&amp;nbsp;直接设置已消耗的 gas 量&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;record_cost(cost)&lt;/code&gt;&amp;nbsp;扣除 gas，成功返回 true，不足返回 false&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;record_cost_unsafe(cost)&lt;/code&gt;&amp;nbsp;不安全版本的扣费，即使不足也执行减法，返回是否&amp;nbsp;OOG&lt;/li&gt; 
&lt;/ul&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/gas.rs
pub struct Gas {
    limit: u64,
    remaining: u64,
    refunded: i64,
    memory: MemoryGas,
}&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;看下内存 Memory 扩展的Gas消耗规则.&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;words_num&lt;/code&gt; &lt;code&gt;word&lt;/code&gt; 数量,这里的 &lt;code&gt;word&lt;/code&gt; 是32字节.64字节对应的就是2.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;expansion_cost&lt;/code&gt; 当前累计的内存扩展成本&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;set_words_num&lt;/code&gt; 设置内存字数和扩展成本，返回新旧成本差值&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;record_new_len&lt;/code&gt; 记录新内存长度，返回额外&amp;nbsp;gas 成本或&amp;nbsp;None 
  &lt;ul&gt; 
   &lt;li&gt;关键公式:&lt;code&gt;crate::gas::calc::memory_gas(new_num, linear_cost, quadratic_cost)&lt;/code&gt;&lt;/li&gt; 
   &lt;li&gt;点进去查看 &lt;code&gt;memory_gas&lt;/code&gt; 的具体实现:&lt;code&gt;linear_cost.saturating_mul(num_words).saturating_add(num_words.saturating_mul(num_words) / quadratic_cost)&lt;/code&gt;&lt;/li&gt; 
   &lt;li&gt;相当于 &lt;code&gt;linear_cost&amp;nbsp;×&amp;nbsp;words&amp;nbsp;+&amp;nbsp;words²&amp;nbsp;/ quadratic_cost&lt;/code&gt;&lt;/li&gt; 
   &lt;li&gt;&lt;code&gt;linear_cost&lt;/code&gt; 和 &lt;code&gt;quadratic_cost&lt;/code&gt; 是固定值&lt;/li&gt; 
   &lt;li&gt;实际并没有使用这个函数,具体的计算在 &lt;code&gt;crates/interpreter/src/gas/params.rs&lt;/code&gt;中,但是公式还是一样的.&lt;/li&gt; 
  &lt;/ul&gt;&lt;/li&gt; 
&lt;/ul&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/gas.rs
pub struct MemoryGas {
    pub words_num: usize,
    pub expansion_cost: u64,
}

impl MemoryGas {
    #[inline]
    pub const fn new() -&amp;gt; Self {
        Self {
            words_num: 0,
            expansion_cost: 0,
        }
    }

    #[inline]
    pub fn set_words_num(&amp;amp;mut self, words_num: usize, mut expansion_cost: u64) -&amp;gt; Option&amp;amp;lt;u64&amp;gt; {
        self.words_num = words_num;
        core::mem::swap(&amp;amp;mut self.expansion_cost, &amp;amp;mut expansion_cost);
        self.expansion_cost.checked_sub(expansion_cost)
    }

    #[inline]
    pub fn record_new_len(
        &amp;amp;mut self,
        new_num: usize,
        linear_cost: u64,
        quadratic_cost: u64,
    ) -&amp;gt; Option&amp;amp;lt;u64&amp;gt; {
        if new_num &amp;amp;lt;= self.words_num {
            return None;
        }
        self.words_num = new_num;
        let mut cost = crate::gas::calc::memory_gas(new_num, linear_cost, quadratic_cost);
        core::mem::swap(&amp;amp;mut self.expansion_cost, &amp;amp;mut cost);
        Some(self.expansion_cost - cost)
    }
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h2&gt;U256&lt;/h2&gt; 
&lt;p&gt;数据在内存中的两个排序方式:&lt;br&gt; &lt;code&gt;大端排序&lt;/code&gt;: &lt;strong&gt;高位&lt;/strong&gt; 字节在&lt;code&gt;低地址&lt;/code&gt;，&lt;strong&gt;低位&lt;/strong&gt; 字节在&lt;code&gt;高地址&lt;/code&gt;&lt;br&gt; &lt;code&gt;小端排序&lt;/code&gt;: &lt;strong&gt;低位&lt;/strong&gt; 字节在&lt;code&gt;低地址&lt;/code&gt;，&lt;strong&gt;高位&lt;/strong&gt; 字节在&lt;code&gt;高地址&lt;/code&gt;&lt;br&gt; 低地址在左边,高地址在右边&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;U256&lt;/code&gt; 不是在 REVM 源码里的, 属于 &lt;code&gt;alloy&lt;/code&gt; 写的一个 &lt;code&gt;Crate&lt;/code&gt;.&lt;br&gt; 看下&lt;code&gt;U256&lt;/code&gt;的类型.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// primitives-1.5.0/src/aliases.rs
macro_rules! int_aliases {
    ($($unsigned:ident, $signed:ident&amp;amp;lt;$BITS:literal, $LIMBS:literal&amp;gt;),* $(,)?) =&amp;gt; {$(
        #[doc = concat!($BITS, &quot;-bit [unsigned integer type][Uint], consisting of &quot;, $LIMBS, &quot;, 64-bit limbs.&quot;)]
        pub type $unsigned = Uint&amp;amp;lt;$BITS, $LIMBS&amp;gt;;

        #[doc = concat!($BITS, &quot;-bit [signed integer type][Signed], consisting of &quot;, $LIMBS, &quot;, 64-bit limbs.&quot;)]
        pub type $signed = Signed&amp;amp;lt;$BITS, $LIMBS&amp;gt;;

        const _: () = assert!($LIMBS == ruint::nlimbs($BITS));
    )*};
}
int_aliases! {
    U8,   I8&amp;amp;lt;  8, 1&amp;gt;,
    ~~~
    U256, I256&amp;amp;lt;256, 4&amp;gt;,
    ~~~
    U512, I512&amp;amp;lt;512, 8&amp;gt;,
}&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;宏展开以后是下面这样的形式&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;Uint&amp;amp;lt;256, 4&amp;gt;&lt;/code&gt; 中的 256 是位长. 4 是 &lt;code&gt;limbs&lt;/code&gt; 数量.&lt;br&gt; 1 个 &lt;code&gt;limb&lt;/code&gt; 是 64位. 256 位 也就是 4.如果有不足 &lt;code&gt;64位&lt;/code&gt; 的,则按 1 算.&lt;br&gt; &lt;code&gt;limbs = CEIL(BITS / 64)&lt;/code&gt;.&lt;br&gt; &lt;code&gt;U256&lt;/code&gt; 你可以理解成 4 个 &lt;code&gt;U64&lt;/code&gt; 组成.&lt;/p&gt; 
&lt;p&gt;容易混淆的地方在这里.&lt;br&gt; U64 ,也就是一个 &lt;code&gt;libm&lt;/code&gt;,它内部是大端排序的,高位数据在低位地址.&lt;br&gt; 但他们组成 U256 的时候,这 4 个 limb 又是小端排序的,高位数据在高位地址.&lt;/p&gt; 
&lt;p&gt;例如数据 &lt;code&gt;0x0102030405060708_0910111213141516_1718192021222324_2526272829303132&lt;/code&gt;&lt;br&gt; 横线是为了方便理解&lt;/p&gt; 
&lt;p&gt;分成4个 U64,也就是&lt;br&gt; &lt;code&gt;0x0102030405060708&lt;/code&gt;, &lt;code&gt;0x0910111213141516&lt;/code&gt;, &lt;code&gt;0x1718192021222324&lt;/code&gt;,&lt;code&gt;0x2526272829303132&lt;/code&gt;.&lt;br&gt; 分割后还是&lt;strong&gt;大端排序&lt;/strong&gt;, 高位在低地址.&lt;/p&gt; 
&lt;p&gt;他们实际在栈中保存时候的顺序是&lt;br&gt; &lt;code&gt;0x2526272829303132&lt;/code&gt;,&lt;code&gt;0x1718192021222324&lt;/code&gt;,&lt;code&gt;0x0910111213141516&lt;/code&gt;,&lt;code&gt;0x0102030405060708&lt;/code&gt;&lt;br&gt; 也即是&lt;strong&gt;小端排序&lt;/strong&gt;,低位在低地址.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;pub type U256 = Uint&amp;amp;lt;256, 4&amp;gt;;
const _: () = assert!(4 == ruint::nlimbs(256));  // 验证 limb 数量正确&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;这时候你再去看下面 &lt;code&gt;push_slice_&lt;/code&gt; 的代码.&lt;br&gt; 先从左到右按 &lt;code&gt;32&lt;/code&gt; 个字节分割出 &lt;code&gt;U256&lt;/code&gt; 的类型.&lt;br&gt; 再从右到到左分割 &lt;code&gt;U64&lt;/code&gt; 类型并写入.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;let words = slice.chunks_exact(32);
// 上面不足32个字节剩下的部分
let partial_last_word = words.remainder();

for word in words {
    // 这里是rchunks_exact,多了r,是从右向左.就是上面说的小端排序的原因
    // 这里i没有置零,原因就是栈是用vec模拟的,是连续的块内存.
    // 一直往后写入就行
    for l in word.rchunks_exact(8) {
        dst.add(i).write(u64::from_be_bytes(l.try_into().unwrap()));
        i += 1;
    }
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h2&gt;Stack&lt;/h2&gt; 
&lt;p&gt;栈的特性大家都理解了吧,先进后出.&lt;br&gt; EVM 是栈虚拟机,自然所有的计算、数据传递、控制流都要通过它来完成.&lt;/p&gt; 
&lt;p&gt;这里先不介绍具体虚拟机执行过程,介绍完基本类型之后再讲下这部分.&lt;br&gt; REVM 使用 Vec 对栈进行模拟,栈中所有的数据都是 256位(32字节)的.&lt;br&gt; REVM 栈中的数据是&lt;strong&gt;大端排序&lt;/strong&gt;.数据的&lt;strong&gt;高位&lt;/strong&gt;字节放在&lt;strong&gt;低位&lt;/strong&gt;地址.&lt;strong&gt;低位&lt;/strong&gt;字节放&lt;strong&gt;高位&lt;/strong&gt;地址.&lt;br&gt; 栈的最大深度为 1024.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;pub struct Stack {
    data: Vec&amp;amp;lt;U256&amp;gt;,
}&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;大部分函数实现都很简单,只说下&lt;code&gt;push_slice_&lt;/code&gt;.&lt;br&gt; 传入的是 u8 数组类型,也就是一个字节数组.&lt;br&gt; 一个栈的单元存储的是 32 自己的数据,所以插入时要计算要计算插入的格数.&lt;br&gt; 具体逻辑直接在代码里注释解释方便点.&lt;/p&gt; 
&lt;p&gt;EVM中数据的存储是大端排序,高位字节在前（索引 0），低位字节在后（索引 31）.&lt;br&gt; 但 REVM 中的 U256 是由&lt;code&gt;4&lt;/code&gt;个 &lt;code&gt;u64&lt;/code&gt; 构成的.这4个&lt;code&gt;u64&lt;/code&gt;又是小端排序的,这里要注意.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/interpreter/stack.rs
fn push_slice_(&amp;amp;mut self, slice: &amp;amp;[u8]) -&amp;gt; bool {
    if slice.is_empty() {
        return true;
    }
    // 计算要插入的格数,
    let n_words = slice.len().div_ceil(32);
    // 计算插入后的长度,此时栈长度还是原来的
    let new_len = self.data.len() + n_words;
    if new_len &amp;gt; STACK_LIMIT {
        return false;
    }
    debug_assert!(self.data.capacity() &amp;gt;= new_len);
    unsafe {
        // self.data.as_mut_ptr() 数组0的位置, 加上栈长度就是指向栈顶.
        let dst = self.data.as_mut_ptr().add(self.data.len()).cast::&amp;amp;lt;u64&amp;gt;();
        self.data.set_len(new_len);
        let mut i = 0;
        // 从左向右,每32个字节,组成一个块.
        let words = slice.chunks_exact(32);
        // 上面不足32个字节剩下的部分
        let partial_last_word = words.remainder();

        for word in words {
            // 这里是rchunks_exact,多了r,是从右向左.就是上面说的小端排序的原因
            // 这里i没有置零,原因就是栈是用vec模拟的,是连续的块内存.
            // 一直往后写入就行
            for l in word.rchunks_exact(8) {
                dst.add(i).write(u64::from_be_bytes(l.try_into().unwrap()));
                i += 1;
            }
        }
        if partial_last_word.is_empty() {
            return true;
        }

        // 之前剩下不足32字节的,按8个字节进行分组.
        // 写入跟前面的方法一样.
        let limbs = partial_last_word.rchunks_exact(8);
        let partial_last_limb = limbs.remainder();
        for l in limbs {
            dst.add(i).write(u64::from_be_bytes(l.try_into().unwrap()));
            i += 1;
        }
        // 剩下不足8个字节的
        if !partial_last_limb.is_empty() {
            // 创建一个存有8个u8类型0的数组
            let mut tmp = [0u8; 8];
            // 把剩余字节右对齐复制到 tmp 高位（左边补 0）
            // 转成 u64 写入（高位补 0，保证大端序）。
            tmp[8 - partial_last_limb.len()..].copy_from_slice(partial_last_limb);
            dst.add(i).write(u64::from_be_bytes(tmp));
            i += 1;
        }
        debug_assert_eq!(i.div_ceil(4), n_words, &quot;wrote too much&quot;);
        // 一个u256类型是4个u64,这里是如果最后一个word没写满,就补0
        let m = i % 4; // 32 / 8
        if m != 0 {
            dst.add(i).write_bytes(0, 4 - m);
        }
    }
    true
}&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;其他的函数功能:&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;len()&lt;/code&gt; 返回栈中当前元素数量&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;is_empty()&lt;/code&gt; 检查栈是否为空&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;data()&lt;/code&gt;&amp;nbsp;返回底层数据的不可变引用&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;data_mut()&lt;/code&gt;&amp;nbsp;返回底层数据的可变引用&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;into_data()&lt;/code&gt;&amp;nbsp;消耗栈并返回底层 &lt;code&gt;Vec&lt;/code&gt; 数据&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;peek(n)&lt;/code&gt;&amp;nbsp;查看从栈顶往下第 n 个元素（不弹出）&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;pop()&lt;/code&gt; 弹出栈顶元素，栈空返回 &lt;code&gt;StackUnderflow&lt;/code&gt; 错误&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;pop_unsafe()&lt;/code&gt; 无检查弹出栈顶&lt;code&gt;（unsafe）&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;top_unsafe()&lt;/code&gt; 返回栈顶元素的可变引用&lt;code&gt;（unsafe）&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;popn::&amp;amp;lt;N&amp;gt;()&lt;/code&gt; 弹出栈顶 N 个元素并返回数组&lt;code&gt;（unsafe）&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;popn_top::&amp;amp;lt;POPN&amp;gt;()&lt;/code&gt; 弹出 &lt;code&gt;POPN&lt;/code&gt; 个元素，同时返回新栈顶的可变引用&lt;code&gt;（unsafe）&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;push(value)&lt;/code&gt;&amp;nbsp;压入一个 &lt;code&gt;U256&lt;/code&gt; 值，栈满返回 &lt;code&gt;false&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;push_slice(slice)&lt;/code&gt;&amp;nbsp;压入字节切片，按&amp;nbsp;32 字节分割，返回 &lt;code&gt;Result&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;push_slice_(slice)&lt;/code&gt;&amp;nbsp;内部方法，压入字节切片，返回 &lt;code&gt;bool&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;dup(n)&lt;/code&gt; 复制从栈顶往下第 n 个元素并压入栈顶（对应 &lt;code&gt;DUP1~DUP16&lt;/code&gt;）&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;swap(n)&lt;/code&gt;&amp;nbsp;交换栈顶与第&amp;nbsp;n 个元素（对应&amp;nbsp;&lt;code&gt;SWAP1~SWAP16&lt;/code&gt;）&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;exchange(n,&amp;nbsp;m)&lt;/code&gt;&amp;nbsp;交换栈中位置 n 和位置 &lt;code&gt;n+m&lt;/code&gt; 的元素（对应 &lt;code&gt;EXCHANGE&lt;/code&gt;&amp;nbsp;指令）&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;set(n, val)&lt;/code&gt;&amp;nbsp;设置从栈顶往下第&amp;nbsp;n 个位置的值&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;clear()&lt;/code&gt;&amp;nbsp;清空栈中所有元素&lt;/li&gt; 
&lt;/ul&gt; 
&lt;h2&gt;ReturnData&lt;/h2&gt; 
&lt;p&gt;上一次 CALL/RETURN 返回的数据缓冲区。 供 RETURNDATACOPY / RETURNDATALOAD 使用。 也用于当前帧的 RETURN 数据输出。&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;pub struct ReturnDataImpl(pub Bytes);
impl ReturnData for ReturnDataImpl {
    fn buffer(&amp;amp;self) -&amp;gt; &amp;amp;Bytes {
        &amp;amp;self.0
    }

    fn set_buffer(&amp;amp;mut self, bytes: Bytes) {
        self.0 = bytes;
    }
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h2&gt;InputsImpl&lt;/h2&gt; 
&lt;p&gt;当前调用帧的输入数据上下文.&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;target_address&lt;/code&gt; 目标地址.这里指的是实际修改状态的地址.例如合约A &lt;code&gt;CALL&lt;/code&gt; 合约B,修改的是合约B的状态.如果是合约A &lt;code&gt;DELEGATECALL&lt;/code&gt; 合约B,修改的是合约A的状态.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;bytecode_address&lt;/code&gt; 字节码地址,也就是调用的合约地址.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;caller_address&lt;/code&gt; 调用这次合约的地址.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;input&lt;/code&gt; &lt;code&gt;CallInput&lt;/code&gt; 类型,在 &lt;code&gt;Frame&lt;/code&gt; 中详细介绍过了&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;call_value&lt;/code&gt; 本地调用发送的 &lt;code&gt;Wei&lt;/code&gt; 数量.&lt;/li&gt; 
&lt;/ul&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;pub struct InputsImpl {
    pub target_address: Address,
    pub bytecode_address: Option&amp;amp;lt;Address&amp;gt;,
    pub caller_address: Address,
    pub input: CallInput,
    pub call_value: U256,
}

impl InputsTr for InputsImpl {
    fn target_address(&amp;amp;self) -&amp;gt; Address {
        self.target_address
    }

    fn caller_address(&amp;amp;self) -&amp;gt; Address {
        self.caller_address
    }

    fn bytecode_address(&amp;amp;self) -&amp;gt; Option&amp;amp;lt;&amp;amp;Address&amp;gt; {
        self.bytecode_address.as_ref()
    }

    fn input(&amp;amp;self) -&amp;gt; &amp;amp;CallInput {
        &amp;amp;self.input
    }

    fn call_value(&amp;amp;self) -&amp;gt; U256 {
        self.call_value
    }
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h2&gt;RuntimeFlags&lt;/h2&gt; 
&lt;p&gt;&lt;code&gt;is_static&lt;/code&gt;: 如果为 &lt;code&gt;true&lt;/code&gt;，表示当前执行上下文处于&lt;code&gt;只读&lt;/code&gt;模式.假设A合约通过 &lt;code&gt;StaticCall&lt;/code&gt; 调用B合约, &lt;code&gt;is_static&lt;/code&gt; 为 &lt;code&gt;True&lt;/code&gt; ,调用过程中任何修改B合约状态的操作都会出错.&lt;br&gt; spec_id: EVM 硬分叉版本&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/interpreter/runtime_flags.rs
pub struct RuntimeFlags {
    pub is_static: bool,
    pub spec_id: SpecId,
}

impl RuntimeFlag for RuntimeFlags {
    fn is_static(&amp;amp;self) -&amp;gt; bool {
        self.is_static
    }

    fn spec_id(&amp;amp;self) -&amp;gt; SpecId {
        self.spec_id
    }
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h2&gt;Host&lt;/h2&gt; 
&lt;p&gt;Host 封装了 Interpreter 执行过程中需要的所有函数.&lt;br&gt; 大概看下这些的名字,会发现都很熟悉,又很混杂.&lt;br&gt; 因为这个 Trait 是为了加一层封装,实际调用的是内部属性的方法.&lt;/p&gt; 
&lt;p&gt;在 &lt;code&gt;REVM&lt;/code&gt; 中, 实现了 &lt;code&gt;Host&lt;/code&gt; 这个 &lt;code&gt;Trait&lt;/code&gt; 的是 &lt;code&gt;Context&lt;/code&gt;.&lt;br&gt; 实际调用的 &lt;code&gt;Context&lt;/code&gt; 中 &lt;code&gt;Block&lt;/code&gt; 、&lt;code&gt;Transaction&lt;/code&gt; 、&lt;code&gt;Config&lt;/code&gt;、&lt;code&gt;Database&lt;/code&gt;、&lt;code&gt;Journal&lt;/code&gt;、&lt;code&gt;Log&lt;/code&gt;等中的方法.&lt;br&gt; 大部分方法在之前都讲过了,这里直接略过&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/context/interface/src/host.rs
pub trait Host {
    fn basefee(&amp;amp;self) -&amp;gt; U256;
    fn blob_gasprice(&amp;amp;self) -&amp;gt; U256;
    fn gas_limit(&amp;amp;self) -&amp;gt; U256;
    fn difficulty(&amp;amp;self) -&amp;gt; U256;
    fn prevrandao(&amp;amp;self) -&amp;gt; Option&amp;amp;lt;U256&amp;gt;;
    fn block_number(&amp;amp;self) -&amp;gt; U256;
    fn timestamp(&amp;amp;self) -&amp;gt; U256;
    fn beneficiary(&amp;amp;self) -&amp;gt; Address;
    fn chain_id(&amp;amp;self) -&amp;gt; U256;
    fn effective_gas_price(&amp;amp;self) -&amp;gt; U256;
    fn caller(&amp;amp;self) -&amp;gt; Address;
    fn blob_hash(&amp;amp;self, number: usize) -&amp;gt; Option&amp;amp;lt;U256&amp;gt;;
    fn max_initcode_size(&amp;amp;self) -&amp;gt; usize;
    fn block_hash(&amp;amp;mut self, number: u64) -&amp;gt; Option&amp;amp;lt;B256&amp;gt;;
    fn selfdestruct(
        &amp;amp;mut self,
        address: Address,
        target: Address,
        skip_cold_load: bool,
    ) -&amp;gt; Result&amp;amp;lt;StateLoad&amp;amp;lt;SelfDestructResult&amp;gt;, LoadError&amp;gt;;
    fn log(&amp;amp;mut self, log: Log);
    fn sstore_skip_cold_load(
        &amp;amp;mut self,
        address: Address,
        key: StorageKey,
        value: StorageValue,
        skip_cold_load: bool,
    ) -&amp;gt; Result&amp;amp;lt;StateLoad&amp;amp;lt;SStoreResult&amp;gt;, LoadError&amp;gt;;
    fn sstore(
        &amp;amp;mut self,
        address: Address,
        key: StorageKey,
        value: StorageValue,
    ) -&amp;gt; Option&amp;amp;lt;StateLoad&amp;amp;lt;SStoreResult&amp;gt;&amp;gt; {
        self.sstore_skip_cold_load(address, key, value, false).ok()
    }
    fn sload_skip_cold_load(
        &amp;amp;mut self,
        address: Address,
        key: StorageKey,
        skip_cold_load: bool,
    ) -&amp;gt; Result&amp;amp;lt;StateLoad&amp;amp;lt;StorageValue&amp;gt;, LoadError&amp;gt;;
    fn sload(&amp;amp;mut self, address: Address, key: StorageKey) -&amp;gt; Option&amp;amp;lt;StateLoad&amp;amp;lt;StorageValue&amp;gt;&amp;gt; {
        self.sload_skip_cold_load(address, key, false).ok()
    }
    fn tstore(&amp;amp;mut self, address: Address, key: StorageKey, value: StorageValue);
    fn tload(&amp;amp;mut self, address: Address, key: StorageKey) -&amp;gt; StorageValue;
    fn load_account_info_skip_cold_load(
        &amp;amp;mut self,
        address: Address,
        load_code: bool,
        skip_cold_load: bool,
    ) -&amp;gt; Result&amp;amp;lt;AccountInfoLoad&amp;amp;lt;'_&amp;gt;, LoadError&amp;gt;;
    #[inline]
    fn balance(&amp;amp;mut self, address: Address) -&amp;gt; Option&amp;amp;lt;StateLoad&amp;amp;lt;U256&amp;gt;&amp;gt; {
        self.load_account_info_skip_cold_load(address, false, false)
            .ok()
            .map(|load| load.into_state_load(|i| i.balance))
    }
    #[inline]
    fn load_account_delegated(&amp;amp;mut self, address: Address) -&amp;gt; Option&amp;amp;lt;StateLoad&amp;amp;lt;AccountLoad&amp;gt;&amp;gt; {
        let account = self
            .load_account_info_skip_cold_load(address, true, false)
            .ok()?;

        let mut account_load = StateLoad::new(
            AccountLoad {
                is_delegate_account_cold: None,
                is_empty: account.is_empty,
            },
            account.is_cold,
        );
        if let Some(Bytecode::Eip7702(code)) = &amp;amp;account.code {
            let address = code.address();
            let delegate_account = self
                .load_account_info_skip_cold_load(address, true, false)
                .ok()?;
            account_load.data.is_delegate_account_cold = Some(delegate_account.is_cold);
            account_load.data.is_empty = delegate_account.is_empty;
        }

        Some(account_load)
    }
    #[inline]
    fn load_account_code(&amp;amp;mut self, address: Address) -&amp;gt; Option&amp;amp;lt;StateLoad&amp;amp;lt;Bytes&amp;gt;&amp;gt; {
        self.load_account_info_skip_cold_load(address, true, false)
            .ok()
            .map(|load| {
                load.into_state_load(|i| {
                    i.code
                        .as_ref()
                        .map(|b| b.original_bytes())
                        .unwrap_or_default()
                })
            })
    }
    #[inline]
    fn load_account_code_hash(&amp;amp;mut self, address: Address) -&amp;gt; Option&amp;amp;lt;StateLoad&amp;amp;lt;B256&amp;gt;&amp;gt; {
        self.load_account_info_skip_cold_load(address, false, false)
            .ok()
            .map(|load| {
                load.into_state_load(|i| {
                    if i.is_empty() {
                        B256::ZERO
                    } else {
                        i.code_hash
                    }
                })
            })
    }
}

// crates/context/src/context.rs
impl&amp;amp;lt;
        BLOCK: Block,
        TX: Transaction,
        CFG: Cfg,
        DB: Database,
        JOURNAL: JournalTr&amp;amp;lt;Database = DB&amp;gt;,
        CHAIN,
        LOCAL: LocalContextTr,
    &amp;gt; Host for Context&amp;amp;lt;BLOCK, TX, CFG, DB, JOURNAL, CHAIN, LOCAL&amp;gt;
{
    ...
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h2&gt;InstructionTable&lt;/h2&gt; 
&lt;p&gt;&lt;code&gt;InstructionTable&lt;/code&gt; 是一个 &lt;code&gt;Instruction&lt;/code&gt; 数组, 存储了 Opcode 的静态 Gas 和对应函数指针.&lt;br&gt; 在 Interpreter 执行过程中,执行到指定 &lt;code&gt;Opcode&lt;/code&gt; ,从 InstructionTable 找到指定的处理函数并运行.&lt;/p&gt; 
&lt;p&gt;先看 &lt;code&gt;Instruction&lt;/code&gt; 结构体的定义.&lt;br&gt; 就两个属性字段, &lt;code&gt;fn_&lt;/code&gt; 和 &lt;code&gt;static_gas&lt;/code&gt;.&lt;br&gt; &lt;code&gt;fn(InstructionContext&amp;amp;lt;'_, H, W&amp;gt;)&lt;/code&gt; 就是接受一个 &lt;code&gt;InstructionContext&amp;amp;lt;'_, H, W&amp;gt;&lt;/code&gt; 类型参数的函数指针.&lt;br&gt; &lt;code&gt;static_gas&lt;/code&gt; 就是当前 &lt;code&gt;Opcode&lt;/code&gt; 的 &lt;code&gt;静态Gas&lt;/code&gt; 消耗.&lt;/p&gt; 
&lt;p&gt;再看下 &lt;code&gt;InstructionContext&lt;/code&gt; 结构体的定义.&lt;br&gt; 两个属性字段, &lt;code&gt;interpreter&lt;/code&gt; 和 &lt;code&gt;host&lt;/code&gt;&lt;br&gt; &lt;code&gt;interpreter&lt;/code&gt; 要求实现了 &lt;code&gt;InterpreterTypes&lt;/code&gt;,就是当前我们讲的 Interpreter.&lt;br&gt; &lt;code&gt;host&lt;/code&gt; 这里只要求实现了 &lt;code&gt;?Sized&lt;/code&gt;&lt;br&gt; 如果是 &lt;code&gt;Sized&lt;/code&gt;,表示类型在编译时大小是已知. &lt;code&gt;?Sized&lt;/code&gt; 则表示编译时大小未知,也就是动态大小,例如str(不是&amp;amp;str).&lt;br&gt; 估计是为了支持其他链的不同需求,所以没有直接指明实现 &lt;code&gt;Host&lt;/code&gt; Trait, &lt;code&gt;REVM&lt;/code&gt; 中的抽象太多.&lt;/p&gt; 
&lt;p&gt;继续 &lt;code&gt;Instruction&lt;/code&gt; 的实现.&lt;br&gt; &lt;code&gt;impl&amp;amp;lt;W: InterpreterTypes, H: Host + ?Sized&amp;gt; Instruction&amp;amp;lt;W, H&amp;gt;&lt;/code&gt;,这里的 &lt;code&gt;H&lt;/code&gt; 就要求必须要实现了 &lt;code&gt;Host&lt;/code&gt;.&lt;br&gt; 具体方法都比较简单,就不细讲了.&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;InstructionTable&lt;/code&gt; 的部分内容比较明了,但是长度长,这里略过.&lt;br&gt; 返回的是一个 &lt;code&gt;256&lt;/code&gt; 长度的数组. 数组的 &lt;code&gt;Index&lt;/code&gt; 对应的 &lt;code&gt;Opcode&lt;/code&gt; 保存的是对应的 &lt;code&gt;Instruction&lt;/code&gt;&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/instruction_context.rs
pub struct InstructionContext&amp;amp;lt;'a, H: ?Sized, ITy: InterpreterTypes&amp;gt; {
    /// Reference to the interpreter containing execution state (stack, memory, gas, etc).
    pub interpreter: &amp;amp;'a mut Interpreter&amp;amp;lt;ITy&amp;gt;,
    /// Reference to the host interface for accessing external blockchain state.
    pub host: &amp;amp;'a mut H,
}

// crates/interpreter/src/instructions.rs
pub struct Instruction&amp;amp;lt;W: InterpreterTypes, H: ?Sized&amp;gt; {
    fn_: fn(InstructionContext&amp;amp;lt;'_, H, W&amp;gt;),
    static_gas: u64,
}
impl&amp;amp;lt;W: InterpreterTypes, H: Host + ?Sized&amp;gt; Instruction&amp;amp;lt;W, H&amp;gt; {
    #[inline]
    pub const fn new(fn_: fn(InstructionContext&amp;amp;lt;'_, H, W&amp;gt;), static_gas: u64) -&amp;gt; Self {
        Self { fn_, static_gas }
    }
    #[inline]
    pub const fn unknown() -&amp;gt; Self {
        Self {
            fn_: control::unknown,
            static_gas: 0,
        }
    }
    #[inline(always)]
    pub fn execute(self, ctx: InstructionContext&amp;amp;lt;'_, H, W&amp;gt;) {
        (self.fn_)(ctx)
    }
    #[inline(always)]
    pub const fn static_gas(&amp;amp;self) -&amp;gt; u64 {
        self.static_gas
    }
}

pub type InstructionTable&amp;amp;lt;W, H&amp;gt; = [Instruction&amp;amp;lt;W, H&amp;gt;; 256];

#[inline]

pub const fn instruction_table&amp;amp;lt;WIRE: InterpreterTypes, H: Host&amp;gt;() -&amp;gt; [Instruction&amp;amp;lt;WIRE, H&amp;gt;; 256] {

const { instruction_table_impl::&amp;amp;lt;WIRE, H&amp;gt;() }

}&lt;/code&gt;&lt;/pre&gt; 
&lt;h2&gt;InstructionResult&lt;/h2&gt; 
&lt;p&gt;&lt;code&gt;InstructionResult&lt;/code&gt; 保存的是 &lt;code&gt;Opcode&lt;/code&gt; 执行的返回结果.&lt;br&gt; 这篇没有啥特别要讲的,只贴下 &lt;code&gt;Enum&lt;/code&gt; 中各个类型的意义.&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;Stop&lt;/code&gt; 遇到了 STOP opcode（0x00），当前调用帧正常停止执行，不返回任何数据（默认值）&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;Return&lt;/code&gt; 执行到 RETURN opcode（0xf3），当前调用帧正常返回数据，将内存中指定范围的内容作为返回值返回给调用者&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;SelfDestruct&lt;/code&gt; 执行到 SELFDESTRUCT opcode（0xff），当前合约标记为自毁，余额转给指定受益者地址，合约代码将在交易结束时被删除&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;Revert&lt;/code&gt; 执行到 REVERT opcode（0xfd），当前调用帧回滚，将内存中指定范围的内容作为 revert reason 返回给调用者&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;CallTooDeep&lt;/code&gt; 调用深度超过 EVM 限制（通常 1024 层），无法继续执行 CALL / CREATE 等操作&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;OutOfFunds&lt;/code&gt; 转账或 CALL 时，发送者余额不足以支付 value + gas cost&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;CreateInitCodeStartingEF00&lt;/code&gt; CREATE / CREATE2 的 initcode 以 0xEF00 开头（EIP-3541 规则），视为无效&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;InvalidEOFInitCode&lt;/code&gt; EOF 格式的 initcode 无效（格式错误、section 不完整等）&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;InvalidExtDelegateCallTarget&lt;/code&gt; ExtDelegateCall（EIP-7702 相关）调用的目标不是有效的 EOF 合约&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;OutOfGas&lt;/code&gt; gas 不足（通用 Out of Gas），执行过程中任何 gas 扣除超过剩余 gas 都会触发&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;MemoryOOG&lt;/code&gt; 内存扩展时 gas 不足（quadratic cost 导致）&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;MemoryLimitOOG&lt;/code&gt; 内存扩展超过了配置的内存上限（memory_limit）&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;PrecompileOOG&lt;/code&gt; precompile 执行时 gas 不足（precompile 有独立 gas 计算规则）&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;InvalidOperandOOG&lt;/code&gt; 操作数无效导致 gas 溢出或计算时 OOG&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;ReentrancySentryOOG&lt;/code&gt; 重入检查（reentrancy sentry）时 gas 不足&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;OpcodeNotFound&lt;/code&gt; 遇到了未知或未实现的 opcode（保留的或 fork 未激活的 opcode）&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;CallNotAllowedInsideStatic&lt;/code&gt; 在 static call（STATICCALL）中尝试执行修改状态的操作（如 SSTORE、CREATE、SELFDESTRUCT）&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;StateChangeDuringStaticCall&lt;/code&gt; static call 期间发生了状态变更（违反 static 规则）&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;InvalidFEOpcode&lt;/code&gt; 遇到了未定义的 opcode（0xfe，通常是 INVALID）&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;InvalidJump&lt;/code&gt; JUMP / JUMPI 的目标地址不是 JUMPDEST（0x5b），或越界&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;NotActivated&lt;/code&gt; 尝试使用当前 fork 未激活的功能或 opcode（如 EOF opcode 在 legacy fork 中）&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;StackUnderflow&lt;/code&gt; 栈下溢（POP、ADD 等操作时栈元素不足）&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;StackOverflow&lt;/code&gt; 栈上溢（PUSH 等操作时栈深度超过 1024）&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;OutOfOffset&lt;/code&gt; 内存/存储偏移量无效（负数或超大）&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;CreateCollision&lt;/code&gt; CREATE / CREATE2 时目标地址已存在（地址冲突）&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;OverflowPayment&lt;/code&gt; 支付金额溢出（value + gas_cost 超过 u256 最大值）&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;PrecompileError&lt;/code&gt; precompile 执行内部错误（输入非法、计算失败等）&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;NonceOverflow&lt;/code&gt; nonce 溢出（u64::MAX）&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;CreateContractSizeLimit&lt;/code&gt; 创建的合约代码大小超过限制（EIP-3860 等）&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;CreateContractStartingWithEF&lt;/code&gt; 创建的合约代码以 0xEF 开头（EIP-3541 规则，防止 EOF 冲突）&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;CreateInitCodeSizeLimit&lt;/code&gt; initcode 大小超过限制（EIP-3860）&lt;/p&gt; &lt;/li&gt; 
 &lt;li&gt; &lt;p&gt;&lt;code&gt;FatalExternalError&lt;/code&gt; 外部数据库或其他宿主环境返回致命错误（db 读写失败等）&lt;/p&gt; &lt;/li&gt; 
&lt;/ul&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;pub enum InstructionResult {
    #[default]
    Stop = 1, // Start at 1 so that `Result&amp;amp;lt;(), _&amp;gt;::Ok(())` is 0.
    Return,
    SelfDestruct,

    Revert = 0x10,
    CallTooDeep,
    OutOfFunds,
    CreateInitCodeStartingEF00,
    InvalidEOFInitCode,
    InvalidExtDelegateCallTarget,

    // Error Codes
    OutOfGas = 0x20,
    MemoryOOG,
    MemoryLimitOOG,
    PrecompileOOG,
    InvalidOperandOOG,
    ReentrancySentryOOG,
    OpcodeNotFound,
    CallNotAllowedInsideStatic,
    StateChangeDuringStaticCall,
    InvalidFEOpcode,
    InvalidJump,
    NotActivated,
    StackUnderflow,
    StackOverflow,
    OutOfOffset,
    CreateCollision,
    OverflowPayment,
    PrecompileError,
    NonceOverflow,
    CreateContractSizeLimit,
    CreateContractStartingWithEF,
    CreateInitCodeSizeLimit,
    FatalExternalError,
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h2&gt;InterpreterAction&lt;/h2&gt; 
&lt;p&gt;&lt;code&gt;InterpreterAction&lt;/code&gt; 是当前 &lt;code&gt;Frame&lt;/code&gt; 中的 &lt;code&gt;Interpreter&lt;/code&gt; 执行指定Opcode后要求上层执行的动作.&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;NewFrame&lt;/code&gt; 是创建一个新的 Frame. &lt;code&gt;Call&lt;/code&gt; 和 &lt;code&gt;Create&lt;/code&gt; 相关的 &lt;code&gt;Opcode&lt;/code&gt; 执行后的要求.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;Return&lt;/code&gt; &lt;code&gt;Interpreter&lt;/code&gt; 执行完成.&lt;br&gt; 实现部分都比较简单,这里略过了.&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;&lt;code&gt;FrameInput&lt;/code&gt; 在之前已经讲过了,不再复述.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = &quot;serde&quot;, derive(serde::Serialize, serde::Deserialize))]
pub enum InterpreterAction {
    NewFrame(FrameInput),
    Return(InterpreterResult),
}
#[inline]
pub fn is_call(&amp;amp;self) -&amp;gt; bool {
    matches!(self, InterpreterAction::NewFrame(FrameInput::Call(..)))
}
#[inline]
pub fn is_create(&amp;amp;self) -&amp;gt; bool {
    matches!(self, InterpreterAction::NewFrame(FrameInput::Create(..)))
}
#[inline]
pub fn is_return(&amp;amp;self) -&amp;gt; bool {
    matches!(self, InterpreterAction::Return { .. })
}
#[inline]
pub fn gas_mut(&amp;amp;mut self) -&amp;gt; Option&amp;amp;lt;&amp;amp;mut Gas&amp;gt; {
    match self {
        InterpreterAction::Return(result) =&amp;gt; Some(&amp;amp;mut result.gas),
        _ =&amp;gt; None,
    }
}
#[inline]
pub fn into_result_return(self) -&amp;gt; Option&amp;amp;lt;InterpreterResult&amp;gt; {
    match self {
        InterpreterAction::Return(result) =&amp;gt; Some(result),
        _ =&amp;gt; None,
    }
}
#[inline]
pub fn instruction_result(&amp;amp;self) -&amp;gt; Option&amp;amp;lt;InstructionResult&amp;gt; {
    match self {
        InterpreterAction::Return(result) =&amp;gt; Some(result.result),
        _ =&amp;gt; None,
    }
}
#[inline]
pub fn new_frame(frame_input: FrameInput) -&amp;gt; Self {
    Self::NewFrame(frame_input)
}
#[inline]
pub fn new_halt(result: InstructionResult, gas: Gas) -&amp;gt; Self {
    Self::Return(InterpreterResult::new(result, Bytes::new(), gas))
}
#[inline]
pub fn new_return(result: InstructionResult, output: Bytes, gas: Gas) -&amp;gt; Self {
    Self::Return(InterpreterResult::new(result, output, gas))
}
#[inline]
pub fn new_stop() -&amp;gt; Self {
    Self::Return(InterpreterResult::new(
        InstructionResult::Stop,
        Bytes::new(),
        Gas::new(0),
    ))
}&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;看下 &lt;code&gt;InterpreterResult&lt;/code&gt; 的部分&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;result&lt;/code&gt; 指令执行的返回结果,类型在前面讲过.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;output&lt;/code&gt; 指令执行的返回数据. 
  &lt;ul&gt; 
   &lt;li&gt;RETURN / REVERT 时：内存中返回或回滚的数据（revert reason 或 return value）。 
    &lt;ul&gt; 
     &lt;li&gt;CALL / CREATE 成功时：子调用返回的数据（如果有）。&lt;/li&gt; 
    &lt;/ul&gt;&lt;/li&gt; 
   &lt;li&gt;其他情况下：通常为空 Bytes（空字节数组）&lt;/li&gt; 
  &lt;/ul&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;gas&lt;/code&gt; gas使用情况,类型在前面讲过.&lt;/li&gt; 
&lt;/ul&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/interpreter.rs
pub struct InterpreterResult {
    pub result: InstructionResult,
    pub output: Bytes,
    pub gas: Gas,
}

impl InterpreterResult {
    pub fn new(result: InstructionResult, output: Bytes, gas: Gas) -&amp;gt; Self {
        Self {
            result,
            output,
            gas,
        }
    }
    #[inline]
    pub const fn is_ok(&amp;amp;self) -&amp;gt; bool {
        self.result.is_ok()
    }
    #[inline]
    pub const fn is_revert(&amp;amp;self) -&amp;gt; bool {
        self.result.is_revert()
    }
    #[inline]
    pub const fn is_error(&amp;amp;self) -&amp;gt; bool {
        self.result.is_error()
    }
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h2&gt;Outcome&lt;/h2&gt; 
&lt;p&gt;Frame的返回值&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;result&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;memory_offset&lt;/code&gt; 返回数据在父调用帧内存中的写入范围&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;was_precompile_called&lt;/code&gt; 表示本次子调用是否执行了 precompile 合约&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;precompile_call_logs&lt;/code&gt; 如果子调用是 precompile 且产生了日志事件,这里保存这些日志。&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;address&lt;/code&gt; 新创建合约的地址（成功时为 Some(address)，失败时为 None）&lt;/li&gt; 
&lt;/ul&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/interpreter_action/call_outcome.rs
pub struct CallOutcome {
    pub result: InterpreterResult,
    pub memory_offset: Range&amp;amp;lt;usize&amp;gt;,
    pub was_precompile_called: bool,
    pub precompile_call_logs: Vec&amp;amp;lt;Log&amp;gt;,
}
// crates/interpreter/src/interpreter_action/create_outcome.rs
pub struct CreateOutcome {
    pub result: InterpreterResult,
    pub address: Option&amp;amp;lt;Address&amp;gt;,
}&lt;/code&gt;&lt;/pre&gt;</description><pubDate>Fri, 20 Mar 2026 01:40:32 +0800</pubDate></item><item><title>REVM源码阅读- Interpreter(2)</title><link>http://htzkw.com/post/9717.html</link><description>&lt;h2&gt;前言&lt;/h2&gt; 
&lt;p&gt;年前又爆仓,加上过年事情多.一直没有时间继续往下写.&lt;br&gt; 过年回来调整了下状态,也为了爆仓后重新找份工作,接着写完这篇.&lt;/p&gt; 
&lt;p&gt;上一篇介绍了 &lt;code&gt;Interpreter&lt;/code&gt; 的成员属性.这一篇写执行流程和Opcode.&lt;br&gt; 为了配合这篇文章,写了个简单的网站来模拟 EVM 的执行过程.&lt;br&gt; 这篇文章跟着模拟走,执行到哪就讲涉及到的Opcode.&lt;br&gt; 网站:https://evm-ani.vercel.app/ 交易:https://etherscan.io/tx/0x005347d49f1fbcb3f0409dff3ef5e1c6f0186a2efb6be97a91facb0e8fdcbdd3&lt;br&gt; 主合约:https://etherscan.io/address/0xa842927c9c9712394c57264716593db383c3a5cf#code&lt;br&gt; 副合约:https://etherscan.io/address/0x0fb8f3bae4005f8ee539c20e6273b2aac25e5ad2#code&lt;/p&gt; 
&lt;h2&gt;网站介绍&lt;/h2&gt; 
&lt;p&gt;&lt;img src=&quot;https://img.learnblockchain.cn/attachments/2026/03/ErrIX5Q669b0c789977d9.png&quot; alt=&quot;website.png&quot; class=&quot;aligncenter&quot;&gt;&lt;/p&gt; 
&lt;ol&gt; 
 &lt;li&gt;每次 &lt;code&gt;Call&lt;/code&gt; 和 &lt;code&gt;Create&lt;/code&gt; 都会创建一个 &lt;code&gt;Frame&lt;/code&gt;.这里就是对应的 &lt;code&gt;Frame&lt;/code&gt;. 
  &lt;ol&gt; 
   &lt;li&gt;&lt;strong&gt;注&lt;/strong&gt;: 为了方便理解才会每次都生成一个新的 &lt;code&gt;Frame&lt;/code&gt;,实际可能调用的都是同一个地址的合约.&lt;/li&gt; 
  &lt;/ol&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;Opcode&lt;/code&gt; 列表,也就是调用的合约的字节码对应的Opcode列表.左边是 PC, 右边是具体OpCode.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;Stack&lt;/code&gt; &lt;code&gt;Interpreter&lt;/code&gt; 执行过程中的栈数据.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;Memory&lt;/code&gt; &lt;code&gt;Interpreter&lt;/code&gt; 执行过程中的内存数据.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;Storage&lt;/code&gt; 合约的Storage数据,这里的数据我暂时没写.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;ReturnData&lt;/code&gt; 每一次合约 &lt;code&gt;Call&lt;/code&gt; 后的返回数据.&lt;/li&gt; 
 &lt;li&gt;执行到哪一步时,Opcode 的作用解释.这里的 &lt;code&gt;index&lt;/code&gt; 和 &lt;code&gt;界面3的index&lt;/code&gt; 是相反的,这里的 &lt;code&gt;index是从顶开始算&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;Context&lt;/code&gt; 对应的 &lt;code&gt;Frame Id&lt;/code&gt;.&lt;code&gt;calldata&lt;/code&gt; 是这次交易发送的数据.&lt;/li&gt; 
&lt;/ol&gt; 
&lt;h2&gt;执行过程&lt;/h2&gt; 
&lt;p&gt;在 &lt;code&gt;instruction_table_impl&lt;/code&gt; 中可以看到.&lt;br&gt; PUSH、DUP、SWAP、POP类的指令,直接调用stack的操作.&lt;br&gt; 后续会直接跳过相关的操作.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/instructions.rs
const fn instruction_table_impl&amp;amp;lt;WIRE: InterpreterTypes, H: Host&amp;gt;() -&amp;gt; [Instruction&amp;amp;lt;WIRE, H&amp;gt;; 256] {
    use bytecode::opcode::*;
    let mut table = [Instruction::unknown(); 256];
    table[POP as usize] = Instruction::new(stack::pop, 2);

    table[PUSH0 as usize] = Instruction::new(stack::push0, 2);
    table[PUSH1 as usize] = Instruction::new(stack::push::&amp;amp;lt;1, _, _&amp;gt;, 3);

    table[DUP1 as usize] = Instruction::new(stack::dup::&amp;amp;lt;1, _, _&amp;gt;, 3);
    table[DUP2 as usize] = Instruction::new(stack::dup::&amp;amp;lt;2, _, _&amp;gt;, 3);

    table[SWAP1 as usize] = Instruction::new(stack::swap::&amp;amp;lt;1, _, _&amp;gt;, 3);
    table[SWAP2 as usize] = Instruction::new(stack::swap::&amp;amp;lt;2, _, _&amp;gt;, 3);
}&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;在网站中左上角,查看Opcodes的前3步.&lt;br&gt; 分别是 &lt;code&gt;PUSH1 0x80&lt;/code&gt;、&lt;code&gt;PUSH1 0x40&lt;/code&gt;、&lt;code&gt;MSTORE&lt;/code&gt;.&lt;br&gt; 这三个 OpCode 合起来是在内存中 0x40 的位置 写入0x80.&lt;br&gt; 为什么这样做呢?这是因为 Solidity 的规定:&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;0x00 - 0x3f&lt;/code&gt; 这64个字节的位置为临时读写空间.主要勇于哈希计算.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;0x40 - 0x5f&lt;/code&gt; 这32个字节为自由内存指针.告诉EVM,在这位置之后的空间你可以使用. 
  &lt;ul&gt; 
   &lt;li&gt;前面的三个Opcode对应的就是这里,指示 0x80 后面的空间都可以使用.&lt;/li&gt; 
  &lt;/ul&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;0x60-0x7f&lt;/code&gt; 零值空间,不被使用,永远为0.&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;当然,你在合约的 &lt;code&gt;solidity&lt;/code&gt; 源码和 &lt;code&gt;REVM&lt;/code&gt; 代码里都找不到关于这些的定义.&lt;br&gt; 因为这部分写在编译器里了.&lt;br&gt; 在将 &lt;code&gt;solidity&lt;/code&gt; 源码编译成字节码的过程中,会自动进行内存指针的管理并写到字节码中.&lt;br&gt; REVM 只负责执行编译好的字节码.&lt;/p&gt; 
&lt;h4&gt;常用宏&lt;/h4&gt; 
&lt;p&gt;在讲其他 &lt;code&gt;Opcode&lt;/code&gt; 前,先讲下几个宏 &lt;/p&gt; 
&lt;h5&gt;popn&lt;/h5&gt; 
&lt;p&gt;功能很简单,就是从 &lt;code&gt;interpreter&lt;/code&gt; 的栈中弹出 n 个元素.&lt;br&gt; &lt;code&gt;#[macro_export]&lt;/code&gt; 指示当前宏可以在其他crate中使用.&lt;br&gt; &lt;code&gt;([ $($x:ident),* ],$interpreter:expr $(,$ret:expr)? )&lt;/code&gt; 这段不了解Rust宏的&lt;code&gt;模式匹配&lt;/code&gt;看着是真头疼.&lt;br&gt; 了解正则的话,可能会好点.一步一步拆分它&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;[ $($x:ident),* ]&lt;/code&gt; 
  &lt;ul&gt; 
   &lt;li&gt;&lt;code&gt;$x:ident&lt;/code&gt;, 相当于捕获一个 ident(变量名、函数名) 类型的语法片段,命名为 $x.&lt;/li&gt; 
   &lt;li&gt;&lt;code&gt;[ $($x:ident),* ]&lt;/code&gt; 这里的 * 是重复捕获.匹配零次或者多次.&lt;/li&gt; 
  &lt;/ul&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;$interpreter:expr&lt;/code&gt; 捕获一个 expr(表达式) 类型的语法片段.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;$(,$ret:expr)?&lt;/code&gt; 可选捕获,也就是0 或者 1个.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;let Some([$( $x ),*])&lt;/code&gt; 前面匹配到的 $x 会传入到数组中.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;$interpreter.stack.popn()&lt;/code&gt; 会根据匹配到的数组数量弹出相同的数量.如果数量不足,&lt;code&gt;则返回halt_underflow&lt;/code&gt;.&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;&lt;code&gt;popn!([offset, value], context.interpreter)&lt;/code&gt; 大部分 &lt;code&gt;popn&lt;/code&gt; 宏的使用形式.从&lt;code&gt;context.interpreter&lt;/code&gt; 弹出两个元素,赋值到 &lt;code&gt;offset&lt;/code&gt;、&lt;code&gt;value&lt;/code&gt;.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/instructions/macros.rs
#[macro_export]
#[collapse_debuginfo(yes)]
macro_rules! popn {
    ([ $($x:ident),* ],$interpreter:expr $(,$ret:expr)? ) =&amp;gt; {
        let Some([$( $x ),*]) = $interpreter.stack.popn() else {
            $interpreter.halt_underflow();
            return $($ret)?;
        };
    };
}
// crates/interpreter/src/interpreter/stack.rs
#[inline]
fn popn&amp;amp;lt;const N: usize&amp;gt;(&amp;amp;mut self) -&amp;gt; Option&amp;amp;lt;[U256; N]&amp;gt; {
    if self.len() &amp;amp;lt; N {
        return None;
    }
    // SAFETY: Stack length is checked above.
    Some(unsafe { self.popn::&amp;amp;lt;N&amp;gt;() })
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h5&gt;popn_top&lt;/h5&gt; 
&lt;p&gt;也是从 stack 弹出 n 个元素.不同的是这里会返回 栈顶 的指针.&lt;br&gt; 不深入讲解了.和上面的逻辑差不多.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;#[macro_export]
#[collapse_debuginfo(yes)]
macro_rules! popn_top {
    ([ $($x:ident),* ], $top:ident, $interpreter:expr $(,$ret:expr)? ) =&amp;gt; {
        /*
        let Some(([$( $x ),*], $top)) = $interpreter.stack.popn_top() else {
            $interpreter.halt($crate::InstructionResult::StackUnderflow);
            return $($ret)?;
        };
        */

        // Workaround for https://github.com/rust-lang/rust/issues/144329.
        if $interpreter.stack.len() &amp;amp;lt; (1 + $crate::_count!($($x)*)) {
            $interpreter.halt_underflow();
            return $($ret)?;
        }
        let ([$( $x ),*], $top) = unsafe { $interpreter.stack.popn_top().unwrap_unchecked() };
    };
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h5&gt;resize_memory&lt;/h5&gt; 
&lt;p&gt;如果有需要的话会调整内存的大小.&lt;br&gt; &lt;code&gt;$crate&lt;/code&gt; 指的是当前宏所在的 &lt;code&gt;crate&lt;/code&gt;.&lt;br&gt; 如果传入的是 3 个参数,加一个()再调用一遍宏.&lt;br&gt; 如果是 4 个参数则调用 &lt;code&gt;interpreter&lt;/code&gt; 下的 &lt;code&gt;resize_memory&lt;/code&gt;.&lt;br&gt; &lt;code&gt;let new_num_words = num_words(offset.saturating_add(len));&lt;/code&gt; 这里是用&lt;code&gt;(offset + len)/32&lt;/code&gt; 并向上取整来计算需要的 words.&lt;/p&gt; 
&lt;p&gt;例如我在 &lt;code&gt;0x65(101)&lt;/code&gt; 写入 &lt;code&gt;32&lt;/code&gt; 个字节. &lt;code&gt;new_num_words = (101 + 32)/32 = 5&lt;/code&gt;.&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;gas&lt;/code&gt; 消耗的计算公式在前面说过了$$Cost = (size^2 / 512) + (3 \times size)$$&lt;br&gt; &lt;code&gt;memory.resize(new_num_words * 32)&lt;/code&gt; 来实际调整大小.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/instructions/macros.rs
macro_rules! resize_memory {
    ($interpreter:expr, $offset:expr, $len:expr) =&amp;gt; {
        $crate::resize_memory!($interpreter, $offset, $len, ())
    };
    ($interpreter:expr, $offset:expr, $len:expr, $ret:expr) =&amp;gt; {
        if let Err(result) = $crate::interpreter::resize_memory(
            &amp;amp;mut $interpreter.gas,
            &amp;amp;mut $interpreter.memory,
            &amp;amp;$interpreter.gas_params,
            $offset,
            $len,
        ) {
            $interpreter.halt(result);
            return $ret;
        }
    };
}
//crates/interpreter/src/interpreter.rs
pub fn resize_memory(&amp;amp;mut self, offset: usize, len: usize) -&amp;gt; bool {
    if let Err(result) = resize_memory(
        &amp;amp;mut self.gas,
        &amp;amp;mut self.memory,
        &amp;amp;self.gas_params,
        offset,
        len,
    ) {
        self.halt(result);
        return false;
    }
    true
}
// crates/interpreter/src/interpreter/shared_memory.rs
pub fn resize_memory&amp;amp;lt;Memory: MemoryTr&amp;gt;(
    gas: &amp;amp;mut crate::Gas,
    memory: &amp;amp;mut Memory,
    gas_table: &amp;amp;GasParams,
    offset: usize,
    len: usize,
) -&amp;gt; Result&amp;amp;lt;(), InstructionResult&amp;gt; {
    #[cfg(feature = &quot;memory_limit&quot;)]
    if memory.limit_reached(offset, len) {
        return Err(InstructionResult::MemoryLimitOOG);
    }

    let new_num_words = num_words(offset.saturating_add(len));
    if new_num_words &amp;gt; gas.memory().words_num {
        return resize_memory_cold(gas, memory, gas_table, new_num_words);
    }

    Ok(())
}
fn resize_memory_cold&amp;amp;lt;Memory: MemoryTr&amp;gt;(
    gas: &amp;amp;mut crate::Gas,
    memory: &amp;amp;mut Memory,
    gas_table: &amp;amp;GasParams,
    new_num_words: usize,
) -&amp;gt; Result&amp;amp;lt;(), InstructionResult&amp;gt; {
    let cost = gas_table.memory_cost(new_num_words);
    let cost = unsafe {
        gas.memory_mut()
            .set_words_num(new_num_words, cost)
            .unwrap_unchecked()
    };

    if !gas.record_cost(cost) {
        return Err(InstructionResult::MemoryOOG);
    }
    memory.resize(new_num_words * 32);
    Ok(())
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h5&gt;gas&lt;/h5&gt; 
&lt;p&gt;判断 Gas 是否足够支付,并记录Gas消耗.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/instructions/macros.rs
#[macro_export]
#[collapse_debuginfo(yes)]
macro_rules! gas {
    ($interpreter:expr, $gas:expr) =&amp;gt; {
        $crate::gas!($interpreter, $gas, ())
    };
    ($interpreter:expr, $gas:expr, $ret:expr) =&amp;gt; {
        if !$interpreter.gas.record_cost($gas) {
            $interpreter.halt_oog();
            return $ret;
        }
    };
}
// crates/interpreter/src/gas.rs
pub fn record_cost(&amp;amp;mut self, cost: u64) -&amp;gt; bool {
        if let Some(new_remaining) = self.remaining.checked_sub(cost) {
            self.remaining = new_remaining;
            return true;
        }
        false
    }&lt;/code&gt;&lt;/pre&gt; 
&lt;h4&gt;MSTORE&lt;/h4&gt; 
&lt;p&gt;看下代码逻辑:&lt;br&gt; &lt;code&gt;popn!([offset, value], context.interpreter)&lt;/code&gt; 从 &lt;code&gt;Stack&lt;/code&gt; 中弹出两个元素.&lt;br&gt; 栈顶是 &lt;code&gt;offset&lt;/code&gt;,接着是 &lt;code&gt;value&lt;/code&gt;.&lt;br&gt; &lt;code&gt;as_usize_or_fail!&lt;/code&gt; 用于将 U256 类型转换成 usize.这里不进行内部解释了.&lt;br&gt; &lt;code&gt;resize_memory!(context.interpreter, offset, 32);&lt;/code&gt; 检测是否需要调整内存大小.&lt;br&gt; &lt;code&gt;context.interpreter.memory.set(offset, &amp;amp;value.to_be_bytes::&amp;amp;lt;32&amp;gt;());&lt;/code&gt; 在 &lt;code&gt;offset&lt;/code&gt; 中填入 &lt;code&gt;value&lt;/code&gt;.&lt;br&gt; 在网站中跳到&lt;code&gt;第4步(第三步执行完成)&lt;/code&gt;.可以看到内存中出现了数据,最后一位为 &lt;code&gt;80&lt;/code&gt;.&lt;/p&gt; 
&lt;p&gt;这篇文章是为了跟随网站的模拟过程,所以暂时不讨论其他的内存相关的Opcode.&lt;br&gt; 感兴趣的自己可以先看下,跟这个逻辑类似.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/instructions/memory.rs
pub fn mstore&amp;amp;lt;WIRE: InterpreterTypes, H: ?Sized&amp;gt;(context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;) {
    popn!([offset, value], context.interpreter);
    let offset = as_usize_or_fail!(context.interpreter, offset);
    resize_memory!(context.interpreter, offset, 32);
    context
        .interpreter
        .memory
        .set(offset, &amp;amp;value.to_be_bytes::&amp;amp;lt;32&amp;gt;());
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h4&gt;MLOAD&lt;/h4&gt; 
&lt;p&gt;从内存中加载数据.&lt;/p&gt; 
&lt;p&gt;逻辑比较简单,就不解释了.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;pub fn mload&amp;amp;lt;WIRE: InterpreterTypes, H: ?Sized&amp;gt;(context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;) {
    popn_top!([], top, context.interpreter);
    let offset = as_usize_or_fail!(context.interpreter, top);
    resize_memory!(context.interpreter, offset, 32);
    *top =
        U256::try_from_be_slice(context.interpreter.memory.slice_len(offset, 32).as_ref()).unwrap()
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h4&gt;CALLDATASIZE&lt;/h4&gt; 
&lt;p&gt;清晰明了,计算出 &lt;code&gt;input&lt;/code&gt; 的 &lt;code&gt;len&lt;/code&gt; 并 &lt;code&gt;push&lt;/code&gt; 到 &lt;code&gt;stack&lt;/code&gt; 中.&lt;br&gt; 执行到 &lt;code&gt;第8步&lt;/code&gt; ,栈中插入 &lt;code&gt;0x24&lt;/code&gt;.&lt;br&gt; 因为 calldata 是&lt;code&gt;0x769c02d50000000000000000000000000000000000000000000000000000000000000001&lt;/code&gt;.&lt;br&gt; 36个字节,前面是函数的abi encode,后面是函数参数.转成16进制就是0x24.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;pub fn calldatasize&amp;amp;lt;WIRE: InterpreterTypes, H: ?Sized&amp;gt;(context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;) {
    push!(
        context.interpreter,
        U256::from(context.interpreter.input.input().len())
    );
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h2&gt;LT&lt;/h2&gt; 
&lt;p&gt;比较栈顶两个元素的大小.&lt;br&gt; 为了方便理解,后面栈顶用 &lt;code&gt;index&lt;/code&gt; 为 1 表示,次栈顶用 &lt;code&gt;index&lt;/code&gt; 为 2 表示,以此类推.&lt;br&gt; popn_top 弹出两个元素并返回栈顶的指针.&lt;br&gt; 假设栈顶为a,次栈顶为b, &lt;code&gt;if a &amp;amp;lt; b, then 1, else 0&lt;/code&gt;.&lt;br&gt; 并将比较结果插入栈顶.&lt;br&gt; 执行第6,7步,栈中元素从上到下是 &lt;code&gt;0x24&lt;/code&gt; , &lt;code&gt;0x4&lt;/code&gt;, &lt;code&gt;0x24 &amp;gt; 0x4&lt;/code&gt;, 所以结果是0.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/instructions/bitwise.rs
pub fn lt&amp;amp;lt;WIRE: InterpreterTypes, H: ?Sized&amp;gt;(context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;) {
    popn_top!([op1], op2, context.interpreter);
    *op2 = U256::from(op1 &amp;amp;lt; *op2);
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h4&gt;JUMPI&lt;/h4&gt; 
&lt;p&gt;条件跳转Op.&lt;br&gt; 栈顶为跳转目的地,次栈顶为跳转条件.&lt;br&gt; 如果次栈顶为&lt;code&gt;1&lt;/code&gt;,则跳转到目的地.如果为 &lt;code&gt;0&lt;/code&gt;,则&lt;code&gt;PC + 1&lt;/code&gt;&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;jump_table&lt;/code&gt; 我在上一章中可能写错,改了但是不确定有没有改完.&lt;br&gt; &lt;code&gt;jump_table&lt;/code&gt; 是 用 u8 实现的位图. 是 &lt;code&gt;bitvec&amp;amp;lt;u8&amp;gt;&lt;/code&gt;. &lt;code&gt;jumpdest&lt;/code&gt; 的 &lt;code&gt;pc&lt;/code&gt; 对应的第几位(bit),而不是第几个byte.&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;is_valid_legacy_jump&lt;/code&gt; 最终调用 &lt;code&gt;JUMPTABLE&lt;/code&gt;中的 is_valid.&lt;br&gt; &lt;code&gt;table_ptr: jump_table 指针. &lt;/code&gt;pc &amp;gt;&amp;gt; 3&lt;code&gt;, 右移3位, 相当于除以8. 因为一个字节就是 8 位.这里是找出位于第几个字节. &lt;/code&gt;1 &amp;lt;&amp;lt; (pc &amp;amp; 7)&lt;code&gt;,&lt;/code&gt;pc &amp;amp; 7&lt;code&gt;这里的功能相当于&lt;/code&gt;% 8&lt;code&gt;, 找出字节中第几位是 dest.结果是0-7. &lt;/code&gt;1 &amp;lt;&amp;lt;` 是因为上一章我们说过JumpTable的排序在字节之间是顺序的,在字节内部是逆序的.也就是从低位到高位,而不是符合阅读习惯的从高到低.这个操作同时保证了一个字节中只有一位是1.&lt;/p&gt; 
&lt;p&gt;在网站中,这次跳转条件不满足,&lt;code&gt;PC + 1&lt;/code&gt;往下运行.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/instructions/control.rs
pub fn jumpi&amp;amp;lt;WIRE: InterpreterTypes, H: ?Sized&amp;gt;(context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;) {
    popn!([target, cond], context.interpreter);
    if !cond.is_zero() {
        jump_inner(context.interpreter, target);
    }
}
fn jump_inner&amp;amp;lt;WIRE: InterpreterTypes&amp;gt;(interpreter: &amp;amp;mut Interpreter&amp;amp;lt;WIRE&amp;gt;, target: U256) {
    let target = as_usize_or_fail!(interpreter, target, InstructionResult::InvalidJump);
    if !interpreter.bytecode.is_valid_legacy_jump(target) {
        interpreter.halt(InstructionResult::InvalidJump);
        return;
    }
    // SAFETY: `is_valid_jump` ensures that `dest` is in bounds.
    interpreter.bytecode.absolute_jump(target);
}
//crates/bytecode/src/legacy/jump_map.rs
pub fn is_valid(&amp;amp;self, pc: usize) -&amp;gt; bool {
        pc &amp;amp;lt; self.len &amp;amp;&amp;amp; unsafe { *self.table_ptr.add(pc &amp;gt;&amp;gt; 3) &amp;amp; (1 &amp;amp;lt;&amp;amp;lt; (pc &amp;amp; 7)) != 0 }
    }&lt;/code&gt;&lt;/pre&gt; 
&lt;h4&gt;CALLDATALOAD&lt;/h4&gt; 
&lt;p&gt;&lt;code&gt;offset_ptr&lt;/code&gt; 保存的是栈顶指针.&lt;br&gt; &lt;code&gt;let offset = as_usize_saturated!(offset_ptr)&lt;/code&gt; 这里传入的是指针,但会自动解引用获取栈顶的元素,并转成 usize. offset 保存的是要读取 &lt;code&gt;calldata&lt;/code&gt; 的偏移位置.&lt;br&gt; &lt;code&gt;let count = 32.min(input_len - offset);&lt;/code&gt; 默认读取32字节,如果剩余不足则读取剩余的.&lt;br&gt; 判断 input 类型&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;Bytes 类型,直接复制 &lt;code&gt;count&lt;/code&gt; 字节的数据到 word 中.&lt;/li&gt; 
 &lt;li&gt;SharedBuffer 类型,从内存中读取数据后再复制到word中.&lt;br&gt; &lt;code&gt;*offset_ptr = word.into()&lt;/code&gt; 将 &lt;code&gt;word&lt;/code&gt; 的数据写入到栈顶.&lt;br&gt; 网站中运行到11步,可以看到栈中数据为:&lt;br&gt; &lt;code&gt;0x769c02d500000000000000000000000000000000000000000000000000000000&lt;/code&gt;.&lt;/li&gt; 
&lt;/ul&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/instructions/system.rs
pub fn calldataload&amp;amp;lt;WIRE: InterpreterTypes, H: ?Sized&amp;gt;(context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;) {
    popn_top!([], offset_ptr, context.interpreter);
    let mut word = B256::ZERO;
    let offset = as_usize_saturated!(offset_ptr);
    let input = context.interpreter.input.input();
    let input_len = input.len();
    if offset &amp;amp;lt; input_len {
        let count = 32.min(input_len - offset);

        match context.interpreter.input.input() {
            CallInput::Bytes(bytes) =&amp;gt; {
                unsafe {
                    ptr::copy_nonoverlapping(bytes.as_ptr().add(offset), word.as_mut_ptr(), count)
                };
            }
            CallInput::SharedBuffer(range) =&amp;gt; {
                let input_slice = context.interpreter.memory.global_slice(range.clone());
                unsafe {
                    ptr::copy_nonoverlapping(
                        input_slice.as_ptr().add(offset),
                        word.as_mut_ptr(),
                        count,
                    )
                };
            }
        }
    }
    *offset_ptr = word.into();
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h4&gt;SHR&lt;/h4&gt; 
&lt;p&gt;右移操作.将数据向右移动 n 位.移出的低位将被丢弃.高位部分补0.&lt;br&gt; 这里是按位进行计算,不是按字节.&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;check!(context.interpreter, CONSTANTINOPLE)&lt;/code&gt; &lt;code&gt;check&lt;/code&gt;宏检测指定硬分叉版本是否启用.&lt;br&gt; &lt;code&gt;popn_top!([op1], op2, context.interpreter)&lt;/code&gt; 弹出栈顶到 &lt;code&gt;op1&lt;/code&gt; , 保存的是要移动的位数.&lt;br&gt; &lt;code&gt;op2&lt;/code&gt; 保存的是弹出移动位数后的栈顶指针.&lt;br&gt; 判断要移动的位数是否小于 256.是则右移指定位数并保存回栈顶.&lt;/p&gt; 
&lt;p&gt;网站执行11步将 &lt;code&gt;0xe0&lt;/code&gt; 写入栈顶.执行12步进行右移操作.&lt;br&gt; &lt;code&gt;0x769c02d500000000000000000000000000000000000000000000000000000000&lt;/code&gt; 右移 0xe0(224位,28个字节).结果是 &lt;code&gt;0x769c02d5&lt;/code&gt;.再将 &lt;code&gt;0x769c02d5&lt;/code&gt; 写入栈顶.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/instructions/bitwise.rs
pub fn shr&amp;amp;lt;WIRE: InterpreterTypes, H: ?Sized&amp;gt;(context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;) {
    check!(context.interpreter, CONSTANTINOPLE);
    popn_top!([op1], op2, context.interpreter);
    let shift = as_usize_saturated!(op1);
    *op2 = if shift &amp;amp;lt; 256 {
        *op2 &amp;gt;&amp;gt; shift
    } else {
        U256::ZERO
    }
}&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;一直执行到17步,前面那几步是为了找到 &lt;code&gt;0x769c02d5(runFullOpcodeSuite)&lt;/code&gt; 函数对应的PC位置.&lt;br&gt; 这里开始进行跳转.&lt;br&gt; 跳转后指定函数后,还会进行一系列的检查和加载数据.&lt;br&gt; 加载函数的参数、检测payable等等.我们的任务不是学习反编译.所以这部分都跳过.&lt;br&gt; 直接跳到 104 步,这里是函数主体开始的地方.&lt;/p&gt; 
&lt;h4&gt;ADDRESS&lt;/h4&gt; 
&lt;p&gt;将 &lt;code&gt;target_address&lt;/code&gt; 地址入栈.&lt;br&gt; 如果你去看网站对应的 &lt;code&gt;solidity&lt;/code&gt; 源码,你要搞清楚.&lt;br&gt; 这里 &lt;code&gt;ADDRESS&lt;/code&gt; 出现是因为 &lt;code&gt;this&lt;/code&gt;,而不是因为&lt;code&gt;address(this)&lt;/code&gt; 中的 &lt;code&gt;address&lt;/code&gt;.&lt;br&gt; solidity 中 address 只是类型转换. &lt;code&gt;this&lt;/code&gt; 是获取 &lt;code&gt;target_address&lt;/code&gt;.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/instructions/system.rs
pub fn address&amp;amp;lt;WIRE: InterpreterTypes, H: ?Sized&amp;gt;(context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;) {
    push!(
        context.interpreter,
        context
            .interpreter
            .input
            .target_address()
            .into_word()
            .into()
    );
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h4&gt;CALLVALUE&lt;/h4&gt; 
&lt;p&gt;将 &lt;code&gt;Transaction&lt;/code&gt; 发送时带的 &lt;code&gt;Value&lt;/code&gt; 压入栈.&lt;/p&gt; 
&lt;p&gt;网站发送的交易我发送了 &lt;code&gt;0.00001 ETH&lt;/code&gt;(&lt;code&gt;10000000000000 WEI&lt;/code&gt;, &lt;code&gt;0x9184e72a000&lt;/code&gt;).&lt;br&gt; 执行108步,将 &lt;code&gt;0x9184e72a000&lt;/code&gt; 压入栈.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/instructions/system.rs
pub fn callvalue&amp;amp;lt;WIRE: InterpreterTypes, H: ?Sized&amp;gt;(context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;) {
    push!(context.interpreter, context.interpreter.input.call_value());
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h4&gt;GASPRICE&lt;/h4&gt; 
&lt;p&gt;将 Transacton 发送时带的 GasPrice 压入栈.&lt;/p&gt; 
&lt;p&gt;真实交易的 &lt;code&gt;GasPrice&lt;/code&gt; 是&lt;code&gt;2032443413 Wei&lt;/code&gt;&lt;br&gt; 网站上112步模拟的交易入栈的是 &lt;code&gt;0x79aadd5e&lt;/code&gt;(&lt;code&gt;2041240926&lt;/code&gt;)&lt;br&gt; 是因为模拟的时候可能 &lt;code&gt;Block&lt;/code&gt; 的 &lt;code&gt;Basefee&lt;/code&gt; 和 &lt;code&gt;Tx&lt;/code&gt; 的 &lt;code&gt;PriorityFee&lt;/code&gt; 会有不同.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/instructions/tx_info.rs
pub fn gasprice&amp;amp;lt;WIRE: InterpreterTypes, H: Host + ?Sized&amp;gt;(
    context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;,
) {
    push!(context.interpreter, context.host.effective_gas_price());
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h4&gt;ADDMOD&lt;/h4&gt; 
&lt;p&gt;将两个数相加并对另一个数取模&lt;/p&gt; 
&lt;p&gt;源码中是将 msg.value 和 函数参数进行相加,并对 100 就行取模.&lt;br&gt; 网站中拖到 125 步. 此时栈顶自上而下是&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;0x0000000000000000000000000000000000000000000000000000000000000001(1)&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;0x000000000000000000000000000000000000000000000000000009184e72a000(10000000000000)&lt;/code&gt;&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;0x0000000000000000000000000000000000000000000000000000000000000064(100)&lt;/code&gt;&lt;br&gt; 相当于 (1 + 10000000000000)%100 = 1&lt;br&gt; 执行125后,栈顶变成 &lt;code&gt;0x0000000000000000000000000000000000000000000000000000000000000001&lt;/code&gt;.&lt;/li&gt; 
&lt;/ul&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;//crates/interpreter/src/instructions/arithmetic.rs
pub fn addmod&amp;amp;lt;WIRE: InterpreterTypes, H: ?Sized&amp;gt;(context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;) {
    popn_top!([op1, op2], op3, context.interpreter);
    *op3 = op1.add_mod(op2, *op3)
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h4&gt;AND&lt;/h4&gt; 
&lt;p&gt;执行到 162 步会遇到 AND 操作.&lt;br&gt; 比较简单就不解释了&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/instructions/bitwise.rs
pub fn bitand&amp;amp;lt;WIRE: InterpreterTypes, H: ?Sized&amp;gt;(context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;) {
    popn_top!([op1], op2, context.interpreter);
    *op2 = op1 &amp;amp; *op2;
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h4&gt;SSTORE&lt;/h4&gt; 
&lt;p&gt;将值写入存储槽.&lt;/p&gt; 
&lt;p&gt;&lt;strong&gt;EIP-2200&lt;/strong&gt; 优化 Gas 消耗,之前重复对一个槽位修改n次会消耗固定Gas * n.之后改成第一次消耗固定Gas,后面的每一次只消耗少部分gas.&lt;br&gt; &lt;strong&gt;BERLIN升级(EIP-2929)&lt;/strong&gt; 增加了&lt;code&gt;Cold、Warm&lt;/code&gt;概念, &lt;code&gt;Address&lt;/code&gt; 和 &lt;code&gt;Storage&lt;/code&gt; 第一次访问属于 &lt;code&gt;冷访问&lt;/code&gt;, Gas消耗会高些,后续访问属于热访问,Gas消耗会少些.&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;require_non_staticcall&lt;/code&gt; 判断当前 &lt;code&gt;CALL&lt;/code&gt; 是否是 &lt;code&gt;STATICCALL&lt;/code&gt;, &lt;code&gt;STATICCALL&lt;/code&gt; 只能访问不能修改数据.&lt;br&gt; &lt;code&gt;target&lt;/code&gt; 要实际修改的合约地址.(CALL和DELEGATECALL的关键就是这里不一样)&lt;/p&gt; 
&lt;p&gt;每个OpCode都有静态固定Gas消耗,这里是判断是否足够.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;gas!(
        context.interpreter,
        context.interpreter.gas_params.sstore_static_gas()
    );&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;&lt;code&gt;cold_storage_additional_cost&lt;/code&gt; 冷访问的额外消耗.&lt;br&gt; 这里这样计算是因为不管冷热都会扣除 &lt;code&gt;WARM_STORAGE_READ_COST&lt;/code&gt;,会在其他地方统一扣除&lt;code&gt;WARM_STORAGE_READ_COST&lt;/code&gt;,所以这里减去.&lt;br&gt; &lt;code&gt;COLD_SLOAD_COST = 2100&lt;/code&gt;, &lt;code&gt;WARM_STORAGE_READ_COST =100&lt;/code&gt;&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;table[GasId::cold_storage_additional_cost().as_usize()] =
                gas::COLD_SLOAD_COST - gas::WARM_STORAGE_READ_COST;&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;&lt;code&gt;skip_cold&lt;/code&gt; 这里的判断会有些奇怪.正常 &lt;code&gt;Gas不足&lt;/code&gt; 是直接报错,这里当参数传入.&lt;br&gt; 这是因为这里保存已加载过的 &lt;code&gt;地址&lt;/code&gt; 和 &lt;code&gt;Storage&lt;/code&gt; 是在 &lt;code&gt;journal&lt;/code&gt; 中,这里并不知道有没有加载过,所以不能直接报错.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;pub fn sstore&amp;amp;lt;WIRE: InterpreterTypes, H: Host + ?Sized&amp;gt;(context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;) {
    require_non_staticcall!(context.interpreter);
    popn!([index, value], context.interpreter);

    let target = context.interpreter.input.target_address();
    let spec_id = context.interpreter.runtime_flag.spec_id();

    if spec_id.is_enabled_in(ISTANBUL)
        &amp;amp;&amp;amp; context.interpreter.gas.remaining() &amp;amp;lt;= context.interpreter.gas_params.call_stipend()
    {
        context
            .interpreter
            .halt(InstructionResult::ReentrancySentryOOG);
        return;
    }

    gas!(
        context.interpreter,
        context.interpreter.gas_params.sstore_static_gas()
    );

    let state_load = if spec_id.is_enabled_in(BERLIN) {
        let additional_cold_cost = context
            .interpreter
            .gas_params
            .cold_storage_additional_cost();
        let skip_cold = context.interpreter.gas.remaining() &amp;amp;lt; additional_cold_cost;
        let res = context
            .host
            .sstore_skip_cold_load(target, index, value, skip_cold);
        match res {
            Ok(load) =&amp;gt; load,
            Err(LoadError::ColdLoadSkipped) =&amp;gt; return context.interpreter.halt_oog(),
            Err(LoadError::DBError) =&amp;gt; return context.interpreter.halt_fatal(),
        }
    } else {
        let Some(load) = context.host.sstore(target, index, value) else {
            return context.interpreter.halt_fatal();
        };
        load
    };

    let is_istanbul = spec_id.is_enabled_in(ISTANBUL);

    // dynamic gas
    gas!(
        context.interpreter,
        context.interpreter.gas_params.sstore_dynamic_gas(
            is_istanbul,
            &amp;amp;state_load.data,
            state_load.is_cold
        )
    );

    // refund
    context.interpreter.gas.record_refund(
        context
            .interpreter
            .gas_params
            .sstore_refund(is_istanbul, &amp;amp;state_load.data),
    );
}&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;后面调用了 &lt;code&gt;sstore_dynamic_gas&lt;/code&gt; 进行 &lt;code&gt;动态Gas&lt;/code&gt; 计算,这里深入进去看下具体逻辑.&lt;br&gt; 解释在注释里面.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;pub fn sstore_dynamic_gas(&amp;amp;self, is_istanbul: bool, vals: &amp;amp;SStoreResult, is_cold: bool) -&amp;gt; u64 {
        // istanbul 升级前的 Gas 计算,历史兼容性,老链就是兼容多.
        if !is_istanbul {
            // 当前值是0,但新值不是0,相当于新建.
            // sstore_set_without_load_cost = SSTORE_SET - SSTORE_RESET = 20000-500
            if vals.is_present_zero() &amp;amp;&amp;amp; !vals.is_new_zero() {
                return self.sstore_set_without_load_cost();
            // 其他的情况,包括非0写非0,非0写0
            // sstore_reset_without_cold_load_cost = SSTORE_RESET - ISTANBUL_SLOAD_GAS = 5000 - 800
            } else {
                return self.sstore_reset_without_cold_load_cost();
            }
        }

        let mut gas = 0;

        // 冷访问,第一次访问要额外 + cold_storage_cost(2100)
        if is_cold {
            gas += self.cold_storage_cost();
        }

        // 这里蛮绕的,storage 分三个状态:Original(原始值)、Present(当前值)、New(新值)
        // is_original_eq_present 判断当前值是否等于原始值,判断是否第一次修改
        // new_values_changes_present 判断新写入的值是否真的修改了现值
        // 所以这段就是本次交易中第一次修改槽位的逻辑
        if vals.new_values_changes_present() &amp;amp;&amp;amp; vals.is_original_eq_present() {
            // 初始值为0,相当于新写入
            // SSTORE_SET - SSTORE_RESET = 20000 - 5000
            gas += if vals.is_original_zero() {
                self.sstore_set_without_load_cost()
            // 覆盖写入
            // SSTORE_RESET - ISTANBUL_SLOAD_GAS = 5000 - 800
            } else {
                self.sstore_reset_without_cold_load_cost()
            };
        }
        gas
    }&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;&lt;code&gt;SSTORE&lt;/code&gt; 的逻辑继续往下, 到 &lt;code&gt;sstore_refund&lt;/code&gt;,进去看下具体逻辑&lt;br&gt; 这是关于 &lt;code&gt;Storage&lt;/code&gt; 的退款部分.解释写在注释里.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;pub fn sstore_refund(&amp;amp;self, is_istanbul: bool, vals: &amp;amp;SStoreResult) -&amp;gt; i64 {
        // sstore_clearing_slot_refund = 15000 
        let sstore_clearing_slot_refund = self.sstore_clearing_slot_refund() as i64;
        // istanbul 升级前
        if !is_istanbul {
            // 之前非0,新值0,相当于清空槽位,直接返现
            if !vals.is_present_zero() &amp;amp;&amp;amp; vals.is_new_zero() {
                return sstore_clearing_slot_refund;
            }
            return 0;
        }
        // 新值和现值相等
        if vals.is_new_eq_present() {
            return 0;
        }
        // istanbul 升级后清空槽位返现要判断本次交易中初始值没有修改过
        // 防止黑客攻击,反复退款
        if vals.is_original_eq_present() &amp;amp;&amp;amp; vals.is_new_zero() {
            return sstore_clearing_slot_refund;
        }
        let mut refund = 0;
        // 初始值非0
        if !vals.is_original_zero() {
            // 之前判断过现值(present)和新值(new)是否相等,所以这里新值(new)不为0
            // 也就是这里针对的是original修改为0,然后给你补偿了,但是你又重新写入不为0.
            // 我收回补偿
            if vals.is_present_zero() {
                refund -= sstore_clearing_slot_refund;
            // 就是上面说的你修改new为0,我给你补偿
            } else if vals.is_new_zero() {
                refund += sstore_clearing_slot_refund;
            }
        }
        // 原始值(original)和新值(new)相等的情况
        // 这里有个前提,无论你这次交易中,修改了多少次storage,只要你最后原始值和新值相等.
        // 就认为你没有对状态树造成影响.会给你退还set和reset费用.
        // 这两者的费用在上面的sstore_dynamic_gas最后部分.
        if vals.is_original_eq_new() {
            // 两者为0的情况
            if vals.is_original_zero() {
                refund += self.sstore_set_without_load_cost() as i64;
            // 两者不为0的情况.
            } else {
                refund += self.sstore_reset_without_cold_load_cost() as i64;
            }
        }
        refund
    }&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;&lt;code&gt;SSTORE&lt;/code&gt; 的部分就讲到这里吧,后面再进行总结.&lt;/p&gt; 
&lt;p&gt;在网站中接着往下运行,碰到了 &lt;code&gt;MSTORE&lt;/code&gt; 和 &lt;code&gt;MSTORE8&lt;/code&gt;.&lt;br&gt; &lt;code&gt;MSTORE&lt;/code&gt; 我们在前面讲过了, &lt;code&gt;MSTORE8&lt;/code&gt; 和 &lt;code&gt;MSTORE&lt;/code&gt; 逻辑差不多.&lt;br&gt; 不同的是 &lt;code&gt;MSTORE&lt;/code&gt; 写入的是 &lt;code&gt;32&lt;/code&gt; 个字节,&lt;code&gt;MSTORE8&lt;/code&gt; 写入的是&lt;code&gt;单个&lt;/code&gt;字节.&lt;br&gt; 可以自己去看下,这里就不展开了.&lt;/p&gt; 
&lt;h4&gt;SLOAD&lt;/h4&gt; 
&lt;p&gt;从&lt;code&gt;Storage&lt;/code&gt; 中加载数据.&lt;/p&gt; 
&lt;p&gt;虽然功能不同,但实现和SSTORE差不多.就不讲解了.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;pub fn sload&amp;amp;lt;WIRE: InterpreterTypes, H: Host + ?Sized&amp;gt;(context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;) {
    popn_top!([], index, context.interpreter);
    let spec_id = context.interpreter.runtime_flag.spec_id();
    let target = context.interpreter.input.target_address();

    if spec_id.is_enabled_in(BERLIN) {
        let additional_cold_cost = context
            .interpreter
            .gas_params
            .cold_storage_additional_cost();
        let skip_cold = context.interpreter.gas.remaining() &amp;amp;lt; additional_cold_cost;
        let res = context.host.sload_skip_cold_load(target, *index, skip_cold);
        match res {
            Ok(storage) =&amp;gt; {
                if storage.is_cold {
                    gas!(context.interpreter, additional_cold_cost);
                }

                *index = storage.data;
            }
            Err(LoadError::ColdLoadSkipped) =&amp;gt; context.interpreter.halt_oog(),
            Err(LoadError::DBError) =&amp;gt; context.interpreter.halt_fatal(),
        }
    } else {
        let Some(storage) = context.host.sload(target, *index) else {
            return context.interpreter.halt_fatal();
        };
        *index = storage.data;
    };
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h4&gt;KECCAK256&lt;/h4&gt; 
&lt;p&gt;对内存中的数据进行哈希.&lt;br&gt; ETH 中的哈希算法,计算地址、哈希校验、Merkle 证明都会使用到它.&lt;/p&gt; 
&lt;p&gt;网站中执行到 &lt;code&gt;461&lt;/code&gt; 步,会碰到它.&lt;br&gt; 看下指令的源码.&lt;/p&gt; 
&lt;p&gt;要注意,&lt;code&gt;popn_top!([offset], top, context.interpreter)&lt;/code&gt;这里 &lt;code&gt;top&lt;/code&gt; 是要 &lt;code&gt;hash&lt;/code&gt; 数据的长度,不是具体数据.&lt;/p&gt; 
&lt;p&gt;这里根据要 hash 的数据长度来计算 Gas 消耗.&lt;br&gt; REVM 中 &lt;code&gt;word&lt;/code&gt; 指的是 32 个字节.keccak256_per_word = 6.&lt;br&gt; 这里的计算公式是$$Gas = 6 \times \lceil \frac{len}{32} \rceil$$&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;gas!(
    context.interpreter,
    context.interpreter.gas_params.keccak256_cost(len)
);
// crates/interpreter/src/gas/params.rs
pub fn keccak256_cost(&amp;amp;self, len: usize) -&amp;gt; u64 {
        self.get(GasId::keccak256_per_word())
            .saturating_mul(num_words(len) as u64)
    }&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;&lt;code&gt;resize_memory!(context.interpreter, from, len)&lt;/code&gt; 避免数据长度不足报错.&lt;br&gt; &lt;code&gt;keccak256&lt;/code&gt; 内部就不深入了.最终调用的 C 写的库.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/instructions/system.rs
pub fn keccak256&amp;amp;lt;WIRE: InterpreterTypes, H: ?Sized&amp;gt;(context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;) {
    popn_top!([offset], top, context.interpreter);
    let len = as_usize_or_fail!(context.interpreter, top);
    gas!(
        context.interpreter,
        context.interpreter.gas_params.keccak256_cost(len)
    );
    let hash = if len == 0 {
        KECCAK_EMPTY
    } else {
        let from = as_usize_or_fail!(context.interpreter, offset);
        resize_memory!(context.interpreter, from, len);
        primitives::keccak256(context.interpreter.memory.slice_len(from, len).as_ref())
    };
    *top = hash.into();
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h4&gt;CALL&lt;/h4&gt; 
&lt;p&gt;用于合约间的调用.&lt;/p&gt; 
&lt;p&gt;CALL 的时候会传递 7 个参数.从栈顶自上而下 &lt;/p&gt; 
&lt;ol start=&quot;0&quot;&gt; 
 &lt;li&gt;&lt;code&gt;gas&lt;/code&gt;, 传递给被调用合约的 &lt;code&gt;GasLimit&lt;/code&gt;,最大数值是当前 &lt;code&gt;Frame剩余Gas&lt;/code&gt; 的 63 / 64.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;addr&lt;/code&gt;, 被调用合约的地址.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;value&lt;/code&gt; 发送给被调用合约的 ETH&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;argsOffset&lt;/code&gt; 传递给被调用函数的参数在内存中的 &lt;code&gt;offset&lt;/code&gt;.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;argsSize&lt;/code&gt; 传递给被调用函数的参数的 &lt;code&gt;size&lt;/code&gt;.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;retOffset&lt;/code&gt; 调用函数返回结果写入内存的 &lt;code&gt;offset&lt;/code&gt;.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;retSize&lt;/code&gt; 调用函数返回结果写入内存的 &lt;code&gt;size&lt;/code&gt;&lt;/li&gt; 
&lt;/ol&gt; 
&lt;p&gt;网站中跳到700步.这里对应的源码是&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Python&quot;&gt;(bool s1, bytes memory r1) = target.call{value: 1 wei}(
    abi.encodeWithSignature(&quot;mockFunction(uint256)&quot;, 123)
);&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;栈中自上而下的数据是.&lt;/p&gt; 
&lt;pre&gt;&lt;code&gt;0x0000000000000000000000000000000000000000000000000000000000045417(gas)
0x0000000000000000000000000fb8f3bae4005f8ee539c20e6273b2aac25e5ad2(to)
0x0000000000000000000000000000000000000000000000000000000000000001(value)
0x0000000000000000000000000000000000000000000000000000000000000138(argsOffset)
0x0000000000000000000000000000000000000000000000000000000000000024(argsSize)
0x0000000000000000000000000000000000000000000000000000000000000138(retOffset)
0x0000000000000000000000000000000000000000000000000000000000000000(retSize)&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;前三个比较明显直接跳过.&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;argsOffset&lt;/code&gt; 数值 &lt;code&gt;138&lt;/code&gt;, argsSize 数值0x24(36), 从内存 &lt;code&gt;0x138&lt;/code&gt; 的位置 读取36个字节.&lt;br&gt; &lt;code&gt;7b23bd32bf000000000000000000000000000000000000000000000000000000000000007b&lt;/code&gt;&lt;br&gt; &lt;code&gt;7b23bd32bf&lt;/code&gt; 是 &lt;code&gt;mockFunction(uint256)&lt;/code&gt; 的 &lt;code&gt;abiencode&lt;/code&gt;.&lt;br&gt; 后面的 &lt;code&gt;7b&lt;/code&gt; 对应的是 123.&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;retOffset&lt;/code&gt; 的数值和 &lt;code&gt;argsOffset&lt;/code&gt; 的数值是相同的,这是为了节省 &lt;code&gt;内存&lt;/code&gt; 和 &lt;code&gt;Gas&lt;/code&gt;.&lt;br&gt; 且两者阶段不一样,不会发生冲突.&lt;br&gt; &lt;code&gt;retSize&lt;/code&gt; 的数值为 &lt;code&gt;0&lt;/code&gt; 因为不确定返回的类型,这里直接返回的 &lt;code&gt;bytes&lt;/code&gt;,并不能确定长度.&lt;/p&gt; 
&lt;p&gt;看下 &lt;code&gt;Call&lt;/code&gt; 的源码.&lt;/p&gt; 
&lt;p&gt;在函数开始部分,你会看到这里也会判断 &lt;code&gt;is_static&lt;/code&gt;. 正常来说只需要在 &lt;code&gt;STATICCALL&lt;/code&gt; 中判断.&lt;br&gt; 为什么在 Call 中也判断 &lt;code&gt;static&lt;/code&gt; 的原因是 STATICCALL 的传染性.&lt;br&gt; 如果&lt;code&gt;A合约&lt;/code&gt; &lt;code&gt;STATICCALL&lt;/code&gt; &lt;code&gt;B合约&lt;/code&gt;, &lt;code&gt;B 合约&lt;/code&gt; &lt;code&gt;CALL&lt;/code&gt; &lt;code&gt;C合约&lt;/code&gt;, 在整条调用链中执行环境都是 &lt;code&gt;static&lt;/code&gt; .即使是 &lt;code&gt;CALL&lt;/code&gt;.&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;get_memory_input_and_out_ranges&lt;/code&gt; 获取函数参数和返回值在 &lt;code&gt;memory&lt;/code&gt; 中的范围,如果内存不足还会顺便对内存进行扩容,扩容的部分和前面说的 MSTORE 一样.&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new()))&lt;/code&gt; 涉及到 &lt;code&gt;CALL&lt;/code&gt; 和 &lt;code&gt;CREATE&lt;/code&gt; 都要返回一个新建 &lt;code&gt;Frame&lt;/code&gt; 的动作给上层进行新的调用.&lt;/p&gt; 
&lt;p&gt;处理函数在 &lt;code&gt;crates/handler/src/evm.rs -&amp;gt; frame_run&lt;/code&gt;&lt;br&gt; 和 &lt;code&gt;crates/handler/src/frame.rs -&amp;gt; process_next_action&lt;/code&gt; 中.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/instructions/contract.rs
pub fn call&amp;amp;lt;WIRE: InterpreterTypes, H: Host + ?Sized&amp;gt;(
    mut context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;,
) {
    popn!([local_gas_limit, to, value], context.interpreter);
    let to = to.into_address();
    let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX);
    let has_transfer = !value.is_zero();

    if context.interpreter.runtime_flag.is_static() &amp;amp;&amp;amp; has_transfer {
        context
            .interpreter
            .halt(InstructionResult::CallNotAllowedInsideStatic);
        return;
    }

    let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(context.interpreter)
    else {
        return;
    };

    let Some((gas_limit, bytecode, bytecode_hash)) =
        load_acc_and_calc_gas(&amp;amp;mut context, to, has_transfer, true, local_gas_limit)
    else {
        return;
    };

    // Call host to interact with target contract
    context
        .interpreter
        .bytecode
        .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(
            CallInputs {
                input: CallInput::SharedBuffer(input),
                gas_limit,
                target_address: to,
                caller: context.interpreter.input.target_address(),
                bytecode_address: to,
                known_bytecode: Some((bytecode_hash, bytecode)),
                value: CallValue::Transfer(value),
                scheme: CallScheme::Call,
                is_static: context.interpreter.runtime_flag.is_static(),
                return_memory_offset,
            },
        ))));
}&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;&lt;code&gt;load_acc_and_calc_gas&lt;/code&gt; 加载 &lt;code&gt;bytecode&lt;/code&gt; 并计算需要本次 CALL 需要的 Limit.&lt;br&gt; &lt;code&gt;load_account_delegated_handle_error&lt;/code&gt; 这里不深入,是加载账户和它的委托账户,不过要处理冷、热账户加载的不同 &lt;code&gt;Gas&lt;/code&gt; 消耗&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;pub fn load_acc_and_calc_gas&amp;amp;lt;H: Host + ?Sized&amp;gt;(
    context: &amp;amp;mut InstructionContext&amp;amp;lt;'_, H, impl InterpreterTypes&amp;gt;,
    to: Address,
    transfers_value: bool,
    create_empty_account: bool,
    stack_gas_limit: u64,
) -&amp;gt; Option&amp;amp;lt;(u64, Bytecode, B256)&amp;gt; {
    // 如果有涉及到转账.A转账给B X ETH
    if transfers_value {
        gas!(
            context.interpreter,
            // 9000
            context.interpreter.gas_params.transfer_value_cost(),
            None
        );
    }

    // 加载账户和它的委托账户.
    let (gas, bytecode, code_hash) =
        load_account_delegated_handle_error(context, to, transfers_value, create_empty_account)?;
    let interpreter = &amp;amp;mut context.interpreter;

    // 记录加载账户和它的委托账户消耗的Gas
    gas!(interpreter, gas, None);

    let interpreter = &amp;amp;mut context.interpreter;

    // EIP-150 升级后CALL 的 GAS 处理
    let mut gas_limit = if interpreter.runtime_flag.spec_id().is_enabled_in(TANGERINE) {
        // 这段相当于 gaslimit - gaslimit /64
        // 就是前面说的将 63/64 的 Gas 传递给CALL
        // 每次CALL之前剩下1/64,是为了避免CALL用完所有的GAS
        // 导致CALLER没有GAS处理剩下的逻辑
        let reduced_gas_limit = interpreter
            .gas_params
            .call_stipend_reduction(interpreter.gas.remaining());
        min(reduced_gas_limit, stack_gas_limit)
    } else {
        stack_gas_limit
    };
    gas!(interpreter, gas_limit, None);

    // 系统对于转账的补贴.赠送给子合约让子合约可以打个Log或者啥.
    if transfers_value {
        gas_limit = gas_limit.saturating_add(interpreter.gas_params.call_stipend());
    }

    Some((gas_limit, bytecode, code_hash))
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h4&gt;RETURN&lt;/h4&gt; 
&lt;p&gt;函数结束的指令.还会涉及将当前执行帧（Frame）在内存中的返回结果，返回给父调用者.&lt;/p&gt; 
&lt;p&gt;源码的实现比较简单.&lt;br&gt; &lt;code&gt;output&lt;/code&gt; 从内存中获取要返回的数据.&lt;br&gt; &lt;code&gt;set_action(InterpreterAction::new_return())&lt;/code&gt; 返回 &lt;code&gt;new_return&lt;/code&gt; action.&lt;/p&gt; 
&lt;p&gt;和其他的 Opcode 不一样,其他大部分的 Opcode 逻辑都直接在对应的函数中写完.&lt;br&gt; &lt;code&gt;Return&lt;/code&gt; 因为涉及到 Frame 之间的调用.所以处理逻辑分开的.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;//crates/interpreter/src/instructions/control.rs
pub fn ret&amp;amp;lt;WIRE: InterpreterTypes, H: ?Sized&amp;gt;(context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;) {
    return_inner(context.interpreter, InstructionResult::Return);
}

fn return_inner(
    interpreter: &amp;amp;mut Interpreter&amp;amp;lt;impl InterpreterTypes&amp;gt;,
    instruction_result: InstructionResult,
) {
    popn!([offset, len], interpreter);
    let len = as_usize_or_fail!(interpreter, len);
    // Important: Offset must be ignored if len is zeros
    let mut output = Bytes::default();
    if len != 0 {
        let offset = as_usize_or_fail!(interpreter, offset);
        if !interpreter.resize_memory(offset, len) {
            return;
        }
        output = interpreter.memory.slice_len(offset, len).to_vec().into()
    }

    interpreter
        .bytecode
        .set_action(InterpreterAction::new_return(
            instruction_result,
            output,
            interpreter.gas,
        ));
}&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;&lt;code&gt;Return&lt;/code&gt; 在返回后具体的处理逻辑在 &lt;code&gt;crates/handler/src/evm.rs-&amp;gt;frame_return_result&lt;/code&gt;.&lt;br&gt; 主要调用的 &lt;code&gt;return_result&lt;/code&gt;,直接跳转进去查看具体逻辑.&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;free_child_context&lt;/code&gt; SharedMemory部分讲过.每个 &lt;code&gt;frame&lt;/code&gt; 被创建的时候都会创建 &lt;code&gt;checkpoint&lt;/code&gt;,指示 &lt;code&gt;子Frame&lt;/code&gt; 的内存从这里开始.这里是将已使用内存的长度设置为 &lt;code&gt;checkpoint&lt;/code&gt;.相当于释放了之前 &lt;code&gt;子Frame&lt;/code&gt; 占用的内存.&lt;br&gt; 这里只是告诉 SharedMemory 我不再需要那块内存,但是数据还没清理.&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;match core::mem::replace(ctx.error(), Ok(()))&lt;/code&gt; 这段是判断在处理交易过程中,有没有发生错误.&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;interpreter.return_data.set_buffer(outcome.result.output)&lt;/code&gt; 这里就是将 return 返回的数据保存到 &lt;code&gt;interpreter.return_data&lt;/code&gt; 中.这里的 &lt;code&gt;interpreter.return_data&lt;/code&gt; 是 &lt;code&gt;父frame&lt;/code&gt; 的.&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;let target_len = min(mem_length, returned_len);&lt;/code&gt; 这里有个注意的点,记得前面讲 &lt;code&gt;CALL&lt;/code&gt; 指令的时候,&lt;code&gt;retSize&lt;/code&gt;为 &lt;code&gt;0&lt;/code&gt; 吗? 如果 &lt;code&gt;retSize&lt;/code&gt; 为 0 , 即&lt;code&gt;mem_length&lt;/code&gt;, &lt;code&gt;target_len&lt;/code&gt; 也就为0.&lt;br&gt; &lt;code&gt;retSize&lt;/code&gt;为 &lt;code&gt;0&lt;/code&gt; 的情况一般是返回类型是返回动态类型,例如bytes之类的.&lt;br&gt; 这些类型并不能提前知道返回的长度,所以直接设置为 &lt;code&gt;0&lt;/code&gt;.&lt;br&gt; 如果 &lt;code&gt;retSize = 0&lt;/code&gt; 会导致这段中的 &lt;code&gt;[..target_len]&lt;/code&gt; 为 &lt;code&gt;[..0]&lt;/code&gt;,实际并没有拷贝数据到 &lt;code&gt;Frame&lt;/code&gt;中专门保存返回数据的内存中.&lt;br&gt; 至于什么时候才会拷贝动态数据的返回类型,后面再说.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;if ins_result.is_ok_or_revert() {
    interpreter.gas.erase_cost(out_gas.remaining());
    interpreter
        .memory
        .set(mem_start, &amp;amp;interpreter.return_data.buffer()[..target_len]);
                }
// crates/interpreter/src/gas.rs
pub fn erase_cost(&amp;amp;mut self, returned: u64) {
    self.remaining += returned;
}
&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;&lt;code&gt;interpreter.gas.erase_cost(out_gas.remaining())&lt;/code&gt; 这里是返还未使用的&lt;code&gt;Gas&lt;/code&gt;.虽然用的 &lt;code&gt;erase&lt;/code&gt;,但是实际是加.看起来误导人所以说下.&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;create&lt;/code&gt; 的部分和 &lt;code&gt;call&lt;/code&gt; 差不多,感兴趣的自己看下&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/handler/src/evm.rs
fn frame_return_result(
        &amp;amp;mut self,
        result: &amp;amp;lt;Self::Frame as FrameTr&amp;gt;::FrameResult,
    ) -&amp;gt; Result&amp;amp;lt;Option&amp;amp;lt;&amp;amp;lt;Self::Frame as FrameTr&amp;gt;::FrameResult&amp;gt;, ContextDbError&amp;amp;lt;Self::Context&amp;gt;&amp;gt; {
        if self.frame_stack.get().is_finished() {
            self.frame_stack.pop();
        }
        if self.frame_stack.index().is_none() {
            return Ok(Some(result));
        }
        self.frame_stack
            .get()
            .return_result::&amp;amp;lt;_, ContextDbError&amp;amp;lt;Self::Context&amp;gt;&amp;gt;(&amp;amp;mut self.ctx, result)?;
        Ok(None)
    }
// crates/handler/src/frame.rs
pub fn return_result&amp;amp;lt;CTX: ContextTr, ERROR: From&amp;amp;lt;ContextTrDbError&amp;amp;lt;CTX&amp;gt;&amp;gt; + FromStringError&amp;gt;(
        &amp;amp;mut self,
        ctx: &amp;amp;mut CTX,
        result: FrameResult,
    ) -&amp;gt; Result&amp;amp;lt;(), ERROR&amp;gt; {
        self.interpreter.memory.free_child_context();
        match core::mem::replace(ctx.error(), Ok(())) {
            Err(ContextError::Db(e)) =&amp;gt; return Err(e.into()),
            Err(ContextError::Custom(e)) =&amp;gt; return Err(ERROR::from_string(e)),
            Ok(_) =&amp;gt; (),
        }

        // Insert result to the top frame.
        match result {
            FrameResult::Call(outcome) =&amp;gt; {
                let out_gas = outcome.gas();
                let ins_result = *outcome.instruction_result();
                let returned_len = outcome.result.output.len();

                let interpreter = &amp;amp;mut self.interpreter;
                let mem_length = outcome.memory_length();
                let mem_start = outcome.memory_start();
                interpreter.return_data.set_buffer(outcome.result.output);

                let target_len = min(mem_length, returned_len);

                if ins_result == InstructionResult::FatalExternalError {
                    panic!(&quot;Fatal external error in insert_call_outcome&quot;);
                }

                let item = if ins_result.is_ok() {
                    U256::from(1)
                } else {
                    U256::ZERO
                };
                let _ = interpreter.stack.push(item);

                if ins_result.is_ok_or_revert() {
                    interpreter.gas.erase_cost(out_gas.remaining());
                    interpreter
                        .memory
                        .set(mem_start, &amp;amp;interpreter.return_data.buffer()[..target_len]);
                }

                if ins_result.is_ok() {
                    interpreter.gas.record_refund(out_gas.refunded());
                }
            }
            FrameResult::Create(outcome) =&amp;gt; {
                let instruction_result = *outcome.instruction_result();
                let interpreter = &amp;amp;mut self.interpreter;

                if instruction_result == InstructionResult::Revert {
                    // Save data to return data buffer if the create reverted
                    interpreter
                        .return_data
                        .set_buffer(outcome.output().to_owned());
                } else {
                    // Otherwise clear it. Note that RETURN opcode should abort.
                    interpreter.return_data.clear();
                };

                assert_ne!(
                    instruction_result,
                    InstructionResult::FatalExternalError,
                    &quot;Fatal external error in insert_eofcreate_outcome&quot;
                );

                let this_gas = &amp;amp;mut interpreter.gas;
                if instruction_result.is_ok_or_revert() {
                    this_gas.erase_cost(outcome.gas().remaining());
                }

                let stack_item = if instruction_result.is_ok() {
                    this_gas.record_refund(outcome.gas().refunded());
                    outcome.address.unwrap_or_default().into_word().into()
                } else {
                    U256::ZERO
                };

                // Safe to push without stack limit check
                let _ = interpreter.stack.push(stack_item);
            }
        }

        Ok(())
    }&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;在网站中第一个 &lt;code&gt;RETURN&lt;/code&gt; 指令在 &lt;code&gt;934&lt;/code&gt; 步.&lt;br&gt; 两个参数,分别是返回数据在内存中的 &lt;code&gt;offse&lt;/code&gt; t和 &lt;code&gt;size&lt;/code&gt;.组合起来对应的数据是 &lt;code&gt;7c&lt;/code&gt;.&lt;/p&gt; 
&lt;h4&gt;RETURNDATASIZE&lt;/h4&gt; 
&lt;p&gt;将调用合约后 &lt;code&gt;返回数据&lt;/code&gt; 的长度压入栈.&lt;/p&gt; 
&lt;p&gt;在上面我们说了,返回数据会被拷贝到 &lt;code&gt;context.interpreter.return_data&lt;/code&gt; 中.&lt;br&gt; 这里直接返回的这部分的数据长度,没从栈中获取.&lt;br&gt; 因为 &lt;code&gt;子Frame&lt;/code&gt; 在 &lt;code&gt;return&lt;/code&gt; 的时候已经被释放,数据不存在了,这就是为什么拷贝到 &lt;code&gt;return_data&lt;/code&gt; 的原因.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/instructions/system.rs
pub fn returndatasize&amp;amp;lt;WIRE: InterpreterTypes, H: ?Sized&amp;gt;(context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;) {
    check!(context.interpreter, BYZANTIUM);
    push!(
        context.interpreter,
        U256::from(context.interpreter.return_data.buffer().len())
    );
}&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;在网站中执行 &lt;code&gt;939&lt;/code&gt; 步就是对应的指令.&lt;/p&gt; 
&lt;h4&gt;RETURNDATACOPY&lt;/h4&gt; 
&lt;p&gt;拷贝合约调用返回数据到指定内存位置.&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;as_usize_saturated!&lt;/code&gt; 宏,将 U256 类型的值转换为 usize 类型，如果数值过大设置为最大值.&lt;br&gt; 其他的源码比较明显,就不讲解了.&lt;/p&gt; 
&lt;p&gt;看下源码的功能,会感觉 &lt;code&gt;RETURNDATACOPY&lt;/code&gt; 和 &lt;code&gt;RETURNDATASIZE&lt;/code&gt; 写到 &lt;code&gt;return_result&lt;/code&gt; 里面也行.没必要加个 &lt;code&gt;interpreter.return_data&lt;/code&gt; 再拷贝.&lt;br&gt; 那为什么还要分出两个指令呢.&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;对于返回的动态数据,我并不一定需要所有的数据,我可能只需要部分.分成两部分我可以按需拷贝数据.&lt;/li&gt; 
 &lt;li&gt;数据写入内存可能会涉及内存扩容费用,调用部分合约,返回的数据过长时,我可以丢弃,避免内存扩容消耗大量Gas&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;网站中执行到 &lt;code&gt;968&lt;/code&gt; 步,就是对应的指令了.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;// crates/interpreter/src/instructions/system.rs
pub fn returndatacopy&amp;amp;lt;WIRE: InterpreterTypes, H: ?Sized&amp;gt;(context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;) {
    check!(context.interpreter, BYZANTIUM);
    popn!([memory_offset, offset, len], context.interpreter);

    let len = as_usize_or_fail!(context.interpreter, len);
    let data_offset = as_usize_saturated!(offset);

    let data_end = data_offset.saturating_add(len);
    if data_end &amp;gt; context.interpreter.return_data.buffer().len() {
        context.interpreter.halt(InstructionResult::OutOfOffset);
        return;
    }

    let Some(memory_offset) = copy_cost_and_memory_resize(context.interpreter, memory_offset, len)
    else {
        return;
    };

    context.interpreter.memory.set_data(
        memory_offset,
        data_offset,
        len,
        context.interpreter.return_data.buffer(),
    );
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h4&gt;STATICCALL&lt;/h4&gt; 
&lt;p&gt;合约间调用,和 &lt;code&gt;CALL&lt;/code&gt; 功能差不多,但是它不能修改数据.&lt;/p&gt; 
&lt;p&gt;讲下和 &lt;code&gt;CALL&lt;/code&gt; 不同的地方.&lt;br&gt; &lt;code&gt;STATICCALL&lt;/code&gt; 不能传递发送 &lt;code&gt;Value&lt;/code&gt;,参数只有相比 &lt;code&gt;CALL&lt;/code&gt; 少了 &lt;code&gt;Value&lt;/code&gt;.&lt;/p&gt; 
&lt;p&gt;因为没有 &lt;code&gt;Value&lt;/code&gt;,也不能修改数据.&lt;br&gt; &lt;code&gt;load_acc_and_calc_gas&lt;/code&gt;调用时, &lt;code&gt;transfers_value&lt;/code&gt; 和 &lt;code&gt;create_empty_account&lt;/code&gt; 直接传递 &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;CallInputs&lt;/code&gt; 中 &lt;code&gt;is_static&lt;/code&gt; 直接传递 &lt;code&gt;true&lt;/code&gt;. &lt;code&gt;Value&lt;/code&gt; 传递 &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt; 
&lt;p&gt;其他的和 &lt;code&gt;CALL&lt;/code&gt; 差不多,就不解释了.&lt;/p&gt; 
&lt;p&gt;在网站中对应的步数是 &lt;code&gt;1123&lt;/code&gt;.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;pub fn static_call&amp;amp;lt;WIRE: InterpreterTypes, H: Host + ?Sized&amp;gt;(
    mut context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;,
) {
    check!(context.interpreter, BYZANTIUM);
    popn!([local_gas_limit, to], context.interpreter);
    let to = Address::from_word(B256::from(to));
    // Max gas limit is not possible in real ethereum situation.
    let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX);

    let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(context.interpreter)
    else {
        return;
    };

    let Some((gas_limit, bytecode, bytecode_hash)) =
        load_acc_and_calc_gas(&amp;amp;mut context, to, false, false, local_gas_limit)
    else {
        return;
    };

    // Call host to interact with target contract
    context
        .interpreter
        .bytecode
        .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(
            CallInputs {
                input: CallInput::SharedBuffer(input),
                gas_limit,
                target_address: to,
                caller: context.interpreter.input.target_address(),
                bytecode_address: to,
                known_bytecode: Some((bytecode_hash, bytecode)),
                value: CallValue::Transfer(U256::ZERO),
                scheme: CallScheme::StaticCall,
                is_static: true,
                return_memory_offset,
            },
        ))));
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h4&gt;DELEGATECALL&lt;/h4&gt; 
&lt;p&gt;合约间调用,和 &lt;code&gt;CALL&lt;/code&gt; 功能差不多,但是它的上下文是 &lt;code&gt;CALLER&lt;/code&gt;.&lt;br&gt; 它读取修改的是调用发起者的&lt;code&gt;Storage&lt;/code&gt;,而不是被调用者的&lt;code&gt;Storage&lt;/code&gt;.&lt;/p&gt; 
&lt;p&gt;讲下和 &lt;code&gt;CALL&lt;/code&gt; 不同的地方.&lt;br&gt; 因为上下文是 &lt;code&gt;CALLER&lt;/code&gt; 的,所以也不能发送 &lt;code&gt;Value&lt;/code&gt;.&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;load_acc_and_calc_gas&lt;/code&gt;调用时, 传入的&lt;code&gt;to&lt;/code&gt; 是被调用者的合约地址,而不是&lt;code&gt;CALLER&lt;/code&gt;.&lt;br&gt; 所以&lt;code&gt;transfers_value&lt;/code&gt; 和 &lt;code&gt;create_empty_account&lt;/code&gt; 直接传递 &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt; 
&lt;p&gt;最关键的部分就是 &lt;code&gt;CallInputs&lt;/code&gt; 中 &lt;code&gt;target_address&lt;/code&gt;, &lt;code&gt;caller&lt;/code&gt;, &lt;code&gt;value&lt;/code&gt;.&lt;/p&gt; 
&lt;p&gt;&lt;strong&gt;target_address&lt;/strong&gt; 指示要读取修改&lt;code&gt;Storage&lt;/code&gt; 的合约.&lt;br&gt; 在 &lt;code&gt;CALL&lt;/code&gt; 中, &lt;code&gt;target_address&lt;/code&gt; 是 &lt;code&gt;to&lt;/code&gt;, 比较容易理解.&lt;/p&gt; 
&lt;p&gt;在这里是 &lt;code&gt;context.interpreter.input.target_address()&lt;/code&gt;.&lt;br&gt; &lt;code&gt;interpreter.input.target_address&lt;/code&gt;指的是 &lt;code&gt;当前Frame&lt;/code&gt; 的 &lt;code&gt;target_address&lt;/code&gt;,也就是 &lt;code&gt;当前Frame&lt;/code&gt; 的要&lt;code&gt;读取修改Storage&lt;/code&gt;的合约地址.&lt;br&gt; 相当于保持不变,你在 &lt;code&gt;当前Frame&lt;/code&gt; 读取修改的谁,在 &lt;code&gt;子Frame&lt;/code&gt; 中也读取修改的谁.&lt;/p&gt; 
&lt;p&gt;&lt;strong&gt;caller&lt;/strong&gt; 指示调用发起者.&lt;br&gt; 在 &lt;code&gt;CALL&lt;/code&gt; 中, caller 是 &lt;code&gt;context.interpreter.input.target_address()&lt;/code&gt;.&lt;br&gt; 要注意这里用的是&lt;code&gt;target_address&lt;/code&gt;.&lt;br&gt; 如果是 &lt;code&gt;当前Frame&lt;/code&gt; 是由 &lt;code&gt;CALL&lt;/code&gt; 生成的容易理解,如果是 &lt;code&gt;DELEGATECALL&lt;/code&gt; 生成的就不同了.&lt;br&gt; 例如 A &lt;code&gt;DELEGATECALL&lt;/code&gt; B, B &lt;code&gt;CALL&lt;/code&gt; C, B 的 &lt;code&gt;target_address&lt;/code&gt; 是 &lt;code&gt;A&lt;/code&gt;, 那 &lt;code&gt;C&lt;/code&gt; 的 &lt;code&gt;caller&lt;/code&gt; 实际是 &lt;code&gt;A&lt;/code&gt;, 而不是 &lt;code&gt;B&lt;/code&gt;.&lt;/p&gt; 
&lt;p&gt;在 &lt;code&gt;DELEGATECALL&lt;/code&gt; 中, &lt;code&gt;caller&lt;/code&gt; 是 &lt;code&gt;context.interpreter.input.caller_address()&lt;/code&gt;, 也就是它直接传递 &lt;code&gt;当前Frame&lt;/code&gt; 的 &lt;code&gt;CALLER&lt;/code&gt;,没进行修改.&lt;br&gt; 例如 &lt;code&gt;EOA&lt;/code&gt; 通过 &lt;code&gt;RPC&lt;/code&gt; 调用 &lt;code&gt;合约A&lt;/code&gt;,&lt;code&gt;A&lt;/code&gt; &lt;code&gt;DELEGATE&lt;/code&gt; &lt;code&gt;B&lt;/code&gt;,&lt;code&gt;B&lt;/code&gt; 的 &lt;code&gt;caller&lt;/code&gt; 应该是 &lt;code&gt;EOA&lt;/code&gt;,而不是 &lt;code&gt;A&lt;/code&gt;.&lt;br&gt; &lt;code&gt;B&lt;/code&gt; &lt;code&gt;CALL&lt;/code&gt; &lt;code&gt;C&lt;/code&gt;, 因为 caller 是 &lt;code&gt;context.interpreter.input.target_address()&lt;/code&gt;,也就是A,&lt;code&gt;所以&lt;/code&gt;C&lt;code&gt;的caller&lt;/code&gt; 是 &lt;code&gt;A&lt;/code&gt;&lt;/p&gt; 
&lt;p&gt;这段很绕,建议你们多看几遍,我自己看了几遍还是觉得绕.&lt;br&gt; 最好画个图.我在纸上画了懒得在这里画.&lt;br&gt; 分清楚 当前Frame 的 &lt;code&gt;caller&lt;/code&gt;、&lt;code&gt;target_address&lt;/code&gt;, 传递给 &lt;code&gt;子Frame&lt;/code&gt; 时的 &lt;code&gt;caller&lt;/code&gt;、&lt;code&gt;target_address&lt;/code&gt;.&lt;br&gt; 是 &lt;code&gt;DELEGATECALL&lt;/code&gt; 还是 &lt;code&gt;CALL&lt;/code&gt;.&lt;/p&gt; 
&lt;p&gt;在网站上对应的步数是 &lt;code&gt;1490&lt;/code&gt; 步.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;pub fn delegate_call&amp;amp;lt;WIRE: InterpreterTypes, H: Host + ?Sized&amp;gt;(
    mut context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;,
) {
    check!(context.interpreter, HOMESTEAD);
    popn!([local_gas_limit, to], context.interpreter);
    let to = Address::from_word(B256::from(to));
    // Max gas limit is not possible in real ethereum situation.
    let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX);

    let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(context.interpreter)
    else {
        return;
    };

    let Some((gas_limit, bytecode, bytecode_hash)) =
        load_acc_and_calc_gas(&amp;amp;mut context, to, false, false, local_gas_limit)
    else {
        return;
    };

    // Call host to interact with target contract
    context
        .interpreter
        .bytecode
        .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(
            CallInputs {
                input: CallInput::SharedBuffer(input),
                gas_limit,
                target_address: context.interpreter.input.target_address(),
                caller: context.interpreter.input.caller_address(),
                bytecode_address: to,
                known_bytecode: Some((bytecode_hash, bytecode)),
                value: CallValue::Apparent(context.interpreter.input.call_value()),
                scheme: CallScheme::DelegateCall,
                is_static: context.interpreter.runtime_flag.is_static(),
                return_memory_offset,
            },
        ))));
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h4&gt;CREATE / CREATE2&lt;/h4&gt; 
&lt;p&gt;CREATE 和 CREATE2 都是使用给定的代码创建合约.&lt;br&gt; 两个的区别主要是地址的计算不同.&lt;br&gt; CREATE2 生成的地址是确定的,可以提前预测.&lt;/p&gt; 
&lt;p&gt;地址计算:&lt;br&gt; &lt;code&gt;CREATE2: keccak256(0xff + sender_address + salt + keccak256(init_code))[12:]&lt;/code&gt;&lt;br&gt; &lt;code&gt;CREATE: keccak256(rlp([sender_address, sender_nonce]))[12:]&lt;/code&gt;&lt;/p&gt; 
&lt;p&gt;两者调用的同一个函数,用 &lt;code&gt;IS_CREATE2&lt;/code&gt; 参数区别是否 CREATE2.&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;require_non_staticcall!(context.interpreter)&lt;/code&gt; 判断是否 &lt;code&gt;STITICCALL&lt;/code&gt;.&lt;br&gt; &lt;code&gt;STITICCALL&lt;/code&gt; 不允许修改状态,自然也不允许创建合约.&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;CREATE&lt;/code&gt; 指令接受 3个函数,&lt;code&gt;CREATE2&lt;/code&gt; 接受4个参数.&lt;br&gt; 尾部多了一个 &lt;code&gt;salt&lt;/code&gt;,用于生成地址.&lt;/p&gt; 
&lt;p&gt;前3个参数是 &lt;code&gt;value&lt;/code&gt;, &lt;code&gt;offset&lt;/code&gt;, &lt;code&gt;len&lt;/code&gt;&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;&lt;code&gt;value&lt;/code&gt; 发送给创建后的合约的 &lt;code&gt;ETH&lt;/code&gt; &lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;offset&lt;/code&gt; 合约的 &lt;code&gt;initcode&lt;/code&gt; 在内存中的 &lt;code&gt;offset&lt;/code&gt;.&lt;/li&gt; 
 &lt;li&gt;&lt;code&gt;len&lt;/code&gt; 合约的 &lt;code&gt;initcode&lt;/code&gt; 在内存中的 &lt;code&gt;len&lt;/code&gt;.&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;&lt;code&gt;if len != 0 {xxx}&lt;/code&gt; 这段是读取&amp;nbsp;&lt;code&gt;init code&lt;/code&gt; 到内存并计算要消耗的Gas.&lt;br&gt; Gas 按每 32字节 进行计算. 每 32字节 消耗 2 GasLimit.&lt;/p&gt; 
&lt;p&gt;刚才扣除的是 &lt;code&gt;inticode&lt;/code&gt; 的Gas.&lt;/p&gt; 
&lt;p&gt;接下来还要扣除 Create 这个操作的基础Gas.&lt;br&gt; 在上面的公式可以看到,&lt;code&gt;CREATE&lt;/code&gt; 的地址计算参数类型都是静态参数.&lt;br&gt; 所以 &lt;code&gt;Gas&lt;/code&gt; 消耗也是固定消耗,create_cost = 32000.&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;CREATE2&lt;/code&gt; 的计算公式需要 &lt;code&gt;salt&lt;/code&gt; 和 &lt;code&gt;init_code&lt;/code&gt;. &lt;code&gt;salt&lt;/code&gt; 是静态类型, &lt;code&gt;init_code&lt;/code&gt; 是动态类型.&lt;br&gt; 所以 &lt;code&gt;Gas&lt;/code&gt; 消耗是计算出来的.在 32000 的基础上再加上 keccak256(init_code.len)的消耗.&lt;br&gt; &lt;code&gt;32000 + 6 * ( len / 32)&lt;/code&gt;&lt;/p&gt; 
&lt;p&gt;&lt;code&gt;call_stipend_reduction&lt;/code&gt; 之前说过, &lt;code&gt;CALL&lt;/code&gt; 和 &lt;code&gt;CREATE&lt;/code&gt; 的时候必须要留 1/64 给 &lt;code&gt;当前Frame&lt;/code&gt;处理后续的操作.&lt;/p&gt; 
&lt;p&gt;你会发现这里并没有计算地址和其他的合约创建操作.&lt;br&gt; 和 &lt;code&gt;CALL&lt;/code&gt; 一样,需要返回上层创建新的 &lt;code&gt;Frame&lt;/code&gt; 进行操作.&lt;/p&gt; 
&lt;p&gt;在网站中对应的步数是 1811 步.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;pub fn create&amp;amp;lt;WIRE: InterpreterTypes, const IS_CREATE2: bool, H: Host + ?Sized&amp;gt;(
    context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;,
) {
    require_non_staticcall!(context.interpreter);

    // EIP-1014: Skinny CREATE2
    if IS_CREATE2 {
        check!(context.interpreter, PETERSBURG);
    }

    popn!([value, code_offset, len], context.interpreter);
    let len = as_usize_or_fail!(context.interpreter, len);

    let mut code = Bytes::new();
    if len != 0 {
        if context
            .interpreter
            .runtime_flag
            .spec_id()
            .is_enabled_in(SpecId::SHANGHAI)
        {
            if len &amp;gt; context.host.max_initcode_size() {
                context
                    .interpreter
                    .halt(InstructionResult::CreateInitCodeSizeLimit);
                return;
            }
            gas!(
                context.interpreter,
                context.interpreter.gas_params.initcode_cost(len)
            );
        }

        let code_offset = as_usize_or_fail!(context.interpreter, code_offset);
        resize_memory!(context.interpreter, code_offset, len);

        code = Bytes::copy_from_slice(
            context
                .interpreter
                .memory
                .slice_len(code_offset, len)
                .as_ref(),
        );
    }

    let scheme = if IS_CREATE2 {
        popn!([salt], context.interpreter);
        gas!(
            context.interpreter,
            context.interpreter.gas_params.create2_cost(len)
        );
        CreateScheme::Create2 { salt }
    } else {
        gas!(
            context.interpreter,
            context.interpreter.gas_params.create_cost()
        );
        CreateScheme::Create
    };

    let mut gas_limit = context.interpreter.gas.remaining();

    if context
        .interpreter
        .runtime_flag
        .spec_id()
        .is_enabled_in(SpecId::TANGERINE)
    {
        gas_limit = context
            .interpreter
            .gas_params
            .call_stipend_reduction(gas_limit);
    }
    gas!(context.interpreter, gas_limit);

    context
        .interpreter
        .bytecode
        .set_action(InterpreterAction::NewFrame(FrameInput::Create(Box::new(
            CreateInputs::new(
                context.interpreter.input.target_address(),
                scheme,
                value,
                code,
                gas_limit,
            ),
        ))));
}&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;下一步在&lt;code&gt;crates/handler/src/frame.rs - &amp;gt; process_next_action&lt;/code&gt;中.&lt;br&gt; 然后 match 这里匹配了 NewFrame, 再执行一遍新建 &lt;code&gt;Frame&lt;/code&gt;, 走的 &lt;code&gt;make_create_frame&lt;/code&gt;.&lt;br&gt; 这部分在前面的流程讲过了,不讲了.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;let mut interpreter_result = match next_action {
    InterpreterAction::NewFrame(frame_input) =&amp;gt; {
        let depth = self.depth + 1;
        return Ok(ItemOrResult::Item(FrameInit {
            frame_input,
            depth,
            memory: self.interpreter.memory.new_child_context(),
        }));
    }
    InterpreterAction::Return(result) =&amp;gt; result,
};&lt;/code&gt;&lt;/pre&gt; 
&lt;p&gt;在上面新建的 &lt;code&gt;Frame&lt;/code&gt; 执行完成后,又回到 &lt;code&gt;process_next_action&lt;/code&gt;.&lt;br&gt; 这次执行到后面的 &lt;code&gt;return_create&lt;/code&gt;.&lt;br&gt; 这部分不讲了,感兴趣自己看下.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;pub fn return_create&amp;amp;lt;JOURNAL: JournalTr&amp;gt;(
    journal: &amp;amp;mut JOURNAL,
    checkpoint: JournalCheckpoint,
    interpreter_result: &amp;amp;mut InterpreterResult,
    address: Address,
    max_code_size: usize,
    is_eip3541_disabled: bool,
    spec_id: SpecId,
) {
    if !interpreter_result.result.is_ok() {
        journal.checkpoint_revert(checkpoint);
        return;
    }
    if !is_eip3541_disabled
        &amp;amp;&amp;amp; spec_id.is_enabled_in(LONDON)
        &amp;amp;&amp;amp; interpreter_result.output.first() == Some(&amp;amp;0xEF)
    {
        journal.checkpoint_revert(checkpoint);
        interpreter_result.result = InstructionResult::CreateContractStartingWithEF;
        return;
    }

    if spec_id.is_enabled_in(SPURIOUS_DRAGON) &amp;amp;&amp;amp; interpreter_result.output.len() &amp;gt; max_code_size {
        journal.checkpoint_revert(checkpoint);
        interpreter_result.result = InstructionResult::CreateContractSizeLimit;
        return;
    }
    let gas_for_code = interpreter_result.output.len() as u64 * gas::CODEDEPOSIT;
    if !interpreter_result.gas.record_cost(gas_for_code) {
        if spec_id.is_enabled_in(HOMESTEAD) {
            journal.checkpoint_revert(checkpoint);
            interpreter_result.result = InstructionResult::OutOfGas;
            return;
        } else {
            interpreter_result.output = Bytes::new();
        }
    }
    journal.checkpoint_commit();
    let bytecode = Bytecode::new_legacy(interpreter_result.output.clone());
    journal.set_code(address, bytecode);
    interpreter_result.result = InstructionResult::Return;
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h4&gt;LOG&lt;/h4&gt; 
&lt;p&gt;用于广播链上事件.&lt;/p&gt; 
&lt;p&gt;逻辑还是比较清晰的,没啥要特别注意的.不讲解了,自己看看.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;pub fn log&amp;amp;lt;const N: usize, H: Host + ?Sized&amp;gt;(
    context: InstructionContext&amp;amp;lt;'_, H, impl InterpreterTypes&amp;gt;,
) {
    require_non_staticcall!(context.interpreter);

    popn!([offset, len], context.interpreter);
    let len = as_usize_or_fail!(context.interpreter, len);
    gas!(
        context.interpreter,
        context.interpreter.gas_params.log_cost(N as u8, len as u64)
    );
    let data = if len == 0 {
        Bytes::new()
    } else {
        let offset = as_usize_or_fail!(context.interpreter, offset);
        // Resize memory to fit the data
        resize_memory!(context.interpreter, offset, len);
        Bytes::copy_from_slice(context.interpreter.memory.slice_len(offset, len).as_ref())
    };
    let Some(topics) = context.interpreter.stack.popn::&amp;amp;lt;N&amp;gt;() else {
        context.interpreter.halt_underflow();
        return;
    };

    let log = Log {
        address: context.interpreter.input.target_address(),
        data: LogData::new(topics.into_iter().map(B256::from).collect(), data)
            .expect(&quot;LogData should have &amp;amp;lt;=4 topics&quot;),
    };

    context.host.log(log);
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h4&gt;TSTORE / TLOAD&lt;/h4&gt; 
&lt;p&gt;用于保存和读取瞬态存储.&lt;/p&gt; 
&lt;p&gt;都是直接调用的 &lt;code&gt;context.host.tstore&lt;/code&gt;,这部分内容在前面都讲过,这里也略过.&lt;/p&gt; 
&lt;pre&gt;&lt;code class=&quot;language-Rust&quot;&gt;pub fn tstore&amp;amp;lt;WIRE: InterpreterTypes, H: Host + ?Sized&amp;gt;(context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;) {
    check!(context.interpreter, CANCUN);
    require_non_staticcall!(context.interpreter);
    popn!([index, value], context.interpreter);

    context
        .host
        .tstore(context.interpreter.input.target_address(), index, value);
}
pub fn tload&amp;amp;lt;WIRE: InterpreterTypes, H: Host + ?Sized&amp;gt;(context: InstructionContext&amp;amp;lt;'_, H, WIRE&amp;gt;) {
    check!(context.interpreter, CANCUN);
    popn_top!([], index, context.interpreter);

    *index = context
        .host
        .tload(context.interpreter.input.target_address(), *index);
}&lt;/code&gt;&lt;/pre&gt; 
&lt;h2&gt;结束&lt;/h2&gt; 
&lt;p&gt;磨磨蹭蹭终于把文章写完了,写得不是很详细将就着看吧.&lt;br&gt; 写这几篇文章的时候是一边看一边学习理解,看的是源码,还没从整体架构来了解.&lt;br&gt; 讲的也都是正常流程,没深入讲异常处理的部分.&lt;br&gt; 都有点像是在写文档和做翻译了.&lt;br&gt; 后面闲下来继续从稍高点的维度来写写.&lt;/p&gt;</description><pubDate>Fri, 20 Mar 2026 01:40:32 +0800</pubDate></item><item><title>加密货币是AI代理的银行</title><link>http://htzkw.com/post/9716.html</link><description>&lt;p&gt;&lt;img src=&quot;https://img.learnblockchain.cn/2026/03/11/65584321_image.jpg&quot; alt=&quot;Image&quot; class=&quot;aligncenter&quot;&gt;&lt;/p&gt; 
&lt;p&gt;2026年将是AI代理开始成为经济参与者的一年。它们将自主调用SaaS API、执行交易、购买云计算资源并串联工作流。正如人类需要信用卡作为“银行轨道”在现实世界中进行交易一样，代理也需要一个银行——我主张这个银行将基于稳定币。&lt;/p&gt; 
&lt;p&gt;这个论点分为两部分。首先是“为什么”——为什么加密货币（而不是信用卡）特别适合成为代理的银行层？第二部分是“如何”——假设我们接受加密货币将成为代理的银行层；为了实现这一点，我们究竟需要构建什么？&lt;/p&gt; 
&lt;h2&gt;为什么是加密货币，而不是信用卡？&lt;/h2&gt; 
&lt;p&gt;加密货币社区经常嘲笑信用卡，认为它们根本不适用于代理。这是一种肤浅的看法，可能不准确。Visa等公司在代理商务方面取得了巨大进步——例如，Visa智能商务为代理创建了一个支付网关，类似于Apple Pay的工作方式：&lt;/p&gt; 
&lt;ol&gt; 
 &lt;li&gt;就像Apple Pay一样，这些“代理卡”首先假设你作为人类拥有一张信用卡。&lt;/li&gt; 
 &lt;li&gt;Visa随后会发行一个带有设定限额、授权和有效期条件的“代币化凭证”。就像Apple Pay一样，这些凭证有一个独立的虚拟卡号，你可以安全地提供给你的代理。&lt;/li&gt; 
 &lt;li&gt;当你的代理（例如OpenClaw）使用代币化凭证进行交易时，它会在Visa服务器上解密，链接到你的实际卡片，然后Visa运行支付流程。在整个过程中，加密货币不必参与。Lobster.cash对这种流程有很好的演示。简而言之，代理卡是可行的，有时甚至比加密货币更受欢迎。那么，为什么还要使用加密货币呢？原因有三：(1) 扩展的信任结构，(2) 面向全球受众的互联网原生货币，(3) 新的支付原语。&lt;/li&gt; 
&lt;/ol&gt; 
&lt;h3&gt;扩展的信任结构&lt;/h3&gt; 
&lt;p&gt;信用卡——以及由此衍生的代理虚拟卡——具有僵化且固定的“信任结构”。它假设为了进行支付，你始终需要一个经过KYC认证的人类银行账户作为信任的后盾。然后，人类会以父母为孩子开立附属卡的方式，将信任和授权委托给代理。&lt;/p&gt; 
&lt;p&gt;另一方面，加密货币和稳定币支付不受此类信任假设的限制。虽然你可以（在许多情况下也应该）将稳定币钱包绑定到经过KYC认证的银行账户（如中心化交易所的情况），但你并非必须这样做才能获得支付权限。你可以将稳定币钱包绑定到几乎任何东西——无论是政府ID、社交媒体账户（Google、TikTok、Instagram OAuth）、域名服务器，还是无头智能合约。&lt;/p&gt; 
&lt;p&gt;许多代理可能会从Lobster.cash和其他与法币关联的信任结构中涌现。但更多代理——例如Conway——将从互联网上法币信任结构之外的其他角落涌现，而稳定币本质上是（可以说是唯一）大规模交易资金的最佳方式。&lt;/p&gt; 
&lt;p&gt;有句老话——“在互联网上，没人知道你是一条狗”。那么，在加密货币轨道上，没人知道你是一个机器人。&lt;/p&gt; 
&lt;h3&gt;面向全球受R众的互联网原生货币&lt;/h3&gt; 
&lt;p&gt;稳定币是一种面向全球受众的互联网原生货币。支付宝与Qwen的整合，提供奶茶券，展示了代理商务的未来可能是什么样子。如果你在过去十年中在中国生活或访问过，你就会体验到将一种“互联网原生货币”串联到日常应用生态系统（外卖、打车、工资）中的便利。但该系统受地理限制，在一个通过法币权威强制执行的封闭技术生态系统中运行。&lt;/p&gt; 
&lt;p&gt;另一方面，稳定币从第一天起就是全球性的，可以为世界其他地区提供这种互联网原生货币体验。这对代理来说至关重要，因为它们的第一个自动化工作流将是跨多个司法管辖区和服务提供商的串联SaaS和API调用。一个协调工作流的代理，如果它需要调用美国的LLM端点、欧洲的数据提供商和东南亚的计算集群，它不应该需要三种不同的支付轨道。它只需要一种。&lt;/p&gt; 
&lt;h3&gt;新的支付原语&lt;/h3&gt; 
&lt;p&gt;在这个稳定币支付的世界里，一切都变成了可支付的端点。实际发生的是互联网经济的&lt;strong&gt;双重杰文斯悖论&lt;/strong&gt;——你既增加了可以交易的用户数量（通过给任何人任何东西一个钱包），也增加了每个用户入驻后的交易量（你可以在更多地方结账）。&lt;/p&gt; 
&lt;p&gt;因为你可以拥有 (1) 扩展的支付方式信任结构，以及 (2) 互联网原生货币，它通过SaaS工作流和全球供应商链连接起来，你很可能会看到新的支付原语的激增。例如，任何创建Dune仪表板的人都可以使用稳定币收取查看请求的费用。&lt;/p&gt; 
&lt;p&gt;例如，几周前，我在Mistral黑客马拉松上“即兴编写”了Tokker——一个为TikTok创作者服务的品牌管理AI代理。我们通过Privy给它一个稳定币钱包，用于收取它联系的品牌的付款，使付款与TikTok创作者使用的银行方式无关。一个简单的扩展可以是使用同一个稳定币钱包来支付各种计算服务、在线广告服务等等，从而创建一个经济产出的飞轮，你既增加了获得银行服务的人数，也增加了他们在互联网上的消费金额。&lt;/p&gt; 
&lt;h2&gt;如何构建AI银行堆栈？&lt;/h2&gt; 
&lt;p&gt;现在我们对加密货币“为什么”有意义有了基本的理由，我们需要弄清楚“如何”在加密货币轨道上构建这个AI银行堆栈。&lt;/p&gt; 
&lt;p&gt;在人类世界中，银行扮演着多种角色。它是我们存储、消费、增长和借贷资金的地方。但它也是我们注册身份的地方，是争议解决的中立仲裁者，是防范恶意活动（通过AML）的安全保障。为了为AI代理构建一个银行，我们需要的不仅仅是一个钱包——我们需要一整套保障措施来调解它们与金钱交易的方式。&lt;/p&gt; 
&lt;p&gt;我相信会有几个相互关联的组件：(1) 身份和授权，(2) 购买流动性，(3) 安全保障，(4) AI代理购买的应用程序“店面”。区块链在所有这四个方面都优于传统轨道。&lt;/p&gt; 
&lt;p&gt;&lt;img src=&quot;https://img.learnblockchain.cn/2026/03/11/47958626_image.jpg&quot; alt=&quot;Image&quot; class=&quot;aligncenter&quot;&gt;&lt;/p&gt; 
&lt;h3&gt;身份和授权&lt;/h3&gt; 
&lt;p&gt;第一个方面将是身份和授权——谁是正在交易的代理，代理代表谁。这个层面有多种设计。你可以复制Visa的堆栈，创建一个链接到你的法币卡的ID。你也可以将钱包链接到电子邮件或社交媒体。例如，我创建了一个黑客马拉松原型，展示了如何使用电子邮件域名为代理支付创建ZKID。你也可以简单地将代理身份刻录在公共区块链上，如以太坊。这就是ERC 8004等标准在创建“代理注册表”方面的根本作用。&lt;/p&gt; 
&lt;h3&gt;购买流动性&lt;/h3&gt; 
&lt;p&gt;第二个方面是确保代理能够实际支付。钱不会因为你创建了一个稳定币钱包就凭空出现。今天大多数代理平台都“赞助”积分，但这无法大规模持续。法币到加密货币的入口、预融资、先买后付（BNPL）以及其他授权方案将是代理经济中极其重要的一部分。&lt;/p&gt; 
&lt;p&gt;此外，我们需要确保区块链基础设施实际运行。目前，代理主要在链上进行分级微交易（平均规模为0.09美元）。随着这些交易的规模和交易量的增加，我们需要设计——例如批处理、支付通道、预授权——以确保这些微支付不会堵塞公共区块链。&lt;/p&gt; 
&lt;h3&gt;安全保障&lt;/h3&gt; 
&lt;p&gt;正如银行需要防止恶意行为者洗钱一样，代理银行也需要防止恶意活动，如提示注入、失控的API成本和凭证泄露。巧合的是，区块链领域多年来一直在处理私钥盗窃问题，并开发了一套强大的加密免疫系统——包括可信执行环境（TEE）、多方计算（MPC）和多重签名、零知识证明（ZKP）以及其他保障措施。&lt;/p&gt; 
&lt;p&gt;我们应该将这些保障措施直接应用于代理支付系统和凭证存储——在许多方面，钱包私钥只是一个更敏感的API密钥，因此整个“私钥”保护堆栈应该被采纳用于代理技能和API凭证，以确保AI代理能够安全地与在线经济互动。&lt;/p&gt; 
&lt;h3&gt;应用程序店面&lt;/h3&gt; 
&lt;p&gt;最后，在应用层，我们正处于代理商务的“应用商店战争”时代。从Merit Systems到ATXP，再到Sponge和Sapiom等提供商，都开发了类似“应用商店”的技能策展机制——允许代理执行从抓取LinkedIn到发送电子邮件和在Hyperliquid上交易等任务。无论是通过电子商务支付现实世界服务，调用按请求付费的SaaS端点，还是自动交易加密代币，代理都需要一个“发现工具”来决定调用哪些端点、使用哪些钱包以及为每项服务支付多少费用。像Coinbase的x402这样的底层协议为代理提供了一种通用的、无需许可的方式来访问现实世界服务，最终使代理能够作为独立的金融参与者积极参与经济。&lt;/p&gt; 
&lt;h2&gt;结论&lt;/h2&gt; 
&lt;p&gt;互联网经济的代理时代才刚刚开始——Claude Code和OpenClaw在不到6个月前才开始流行。在过去十年中，区块链轨道已证明能够支撑一个数十亿美元的链上经济。我相信这两个因素将迅速融合，区块链和稳定币将成为代理经济的银行基石。&lt;/p&gt; 
&lt;p&gt;AI代理的银行根本不会像银行。它会像一个&lt;strong&gt;区块链&lt;/strong&gt;。&lt;/p&gt; 
&lt;blockquote&gt; 
 &lt;ul&gt; 
  &lt;li&gt;原文链接： x.com/0xfishylosopher/st...&lt;/li&gt; 
  &lt;li&gt;登链社区 AI 助手，为大家转译优秀英文文章，如有翻译不通的地方，还请包涵～&lt;/li&gt; 
 &lt;/ul&gt; 
&lt;/blockquote&gt;</description><pubDate>Fri, 20 Mar 2026 01:40:31 +0800</pubDate></item><item><title>代理赋能框架的解剖</title><link>http://htzkw.com/post/9715.html</link><description>&lt;p&gt;&lt;img src=&quot;https://img.learnblockchain.cn/2026/03/17/38140541_image.jpg&quot; alt=&quot;Image&quot; class=&quot;aligncenter&quot;&gt;&lt;/p&gt; 
&lt;p&gt;TLDR: Agent = Model + Harness。Harness 工程是我们围绕模型构建系统以将其转化为工作引擎的方式。模型包含智能，而 Harness 使这种智能变得有用。我们定义了什么是 Harness，并推导出了当今和未来 Agent 所需的核心组件。&lt;/p&gt; 
&lt;h2&gt;何为“Harness”？&lt;/h2&gt; 
&lt;p&gt;Agent = Model + Harness&lt;/p&gt; 
&lt;p&gt;如果你不是模型，你就是 Harness。&lt;/p&gt; 
&lt;p&gt;Harness 是除了模型本身之外的所有代码、配置和执行逻辑。一个原始模型并非一个 Agent。但当 Harness 赋予它状态、工具执行、反馈循环和可强制执行的约束时，它就成为了一个 Agent。&lt;/p&gt; 
&lt;p&gt;具体来说，Harness 包括：&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;系统提示词（System Prompts）&lt;/li&gt; 
 &lt;li&gt;工具、技能、MCPs 及其描述&lt;/li&gt; 
 &lt;li&gt;捆绑基础设施（文件系统、沙盒、浏览器）&lt;/li&gt; 
 &lt;li&gt;编排逻辑（子 Agent 生成、交接、模型路由）&lt;/li&gt; 
 &lt;li&gt;用于确定性执行的Hook/中间件（压缩、续写、代码检查）&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;在模型和 Harness 之间划分 Agent 系统的边界有许多复杂的方式。但在我看来，这是最清晰的定义，因为它迫使我们思考如何围绕模型智能设计系统。&lt;/p&gt; 
&lt;p&gt;本文的其余部分将探讨核心 Harness 组件，并从模型这一核心原语出发，推导出每个组件存在的理由。&lt;/p&gt; 
&lt;p&gt;&lt;img src=&quot;https://img.learnblockchain.cn/2026/03/17/10833868_image.jpg&quot; alt=&quot;Image&quot; class=&quot;aligncenter&quot;&gt;&lt;/p&gt; 
&lt;h2&gt;从模型的角度看，为什么我们需要 Harness？&lt;/h2&gt; 
&lt;p&gt;有些我们希望 Agent 完成的任务是模型本身无法直接做到的。这就是 Harness 发挥作用的地方。&lt;/p&gt; 
&lt;p&gt;模型（大多）接收文本、图像、音频、视频等数据，并输出文本。仅此而已。开箱即用，它们无法：&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;在交互过程中保持持久状态&lt;/li&gt; 
 &lt;li&gt;执行代码&lt;/li&gt; 
 &lt;li&gt;访问实时知识&lt;/li&gt; 
 &lt;li&gt;设置环境并安装包以完成工作&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;这些都是 Harness 层面的功能。LLM 的结构需要某种机制来封装它们，以完成有用的工作。&lt;/p&gt; 
&lt;p&gt;例如，为了实现“聊天”这样的产品用户体验，我们将模型封装在一个 &lt;code&gt;while&lt;/code&gt; 循环中，以跟踪之前的消息并追加新的用户消息。每个阅读本文的人都使用过这种 Harness。核心思想是，我们希望将期望的 Agent 行为转化为 Harness 中的实际功能。&lt;/p&gt; 
&lt;h2&gt;从期望的 Agent 行为反推 Harness 工程&lt;/h2&gt; 
&lt;p&gt;Harness 工程帮助人类注入有用的先验知识来指导 Agent 行为。随着模型能力越来越强，Harness 被用来精确地扩展和修正模型，以完成以前不可能完成的任务。&lt;/p&gt; 
&lt;p&gt;我们不会列出所有 Harness 功能的详尽清单。目标是从帮助模型完成有用工作的起点，推导出一组功能。我们将遵循以下模式：&lt;/p&gt; 
&lt;p&gt;我们想要的行为（或想要修正的行为）→ 帮助模型实现此行为的 Harness 设计。&lt;/p&gt; 
&lt;p&gt;&lt;img src=&quot;https://img.learnblockchain.cn/2026/03/17/17162859_image.jpg&quot; alt=&quot;Image&quot; class=&quot;aligncenter&quot;&gt;&lt;/p&gt; 
&lt;h2&gt;文件系统：实现持久存储和上下文管理&lt;/h2&gt; 
&lt;p&gt;我们希望 Agent 拥有持久存储，以便与真实数据交互、卸载不适合上下文的信息，并在会话之间持久化工作。&lt;/p&gt; 
&lt;p&gt;模型只能直接操作其上下文窗口内的知识。在文件系统出现之前，用户必须将内容直接复制/粘贴到模型中，这是一种笨拙的用户体验，并且不适用于自主 Agent。世界已经在使用文件系统来工作，因此模型自然而然地在数十亿个关于如何使用文件系统的 Token 上进行了训练。自然的解决方案是：&lt;/p&gt; 
&lt;p&gt;Harness 附带文件系统抽象和文件系统操作工具。&lt;/p&gt; 
&lt;p&gt;文件系统可以说是最基础的 Harness 原语，因为它解锁了以下功能：&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;Agent 获得一个工作区来读取数据、代码和文档。&lt;/li&gt; 
 &lt;li&gt;工作可以增量添加和卸载，而不是将所有内容保存在上下文中。Agent 可以存储中间输出并维护超出单个会话的状态。&lt;/li&gt; 
 &lt;li&gt;文件系统是一个自然的协作界面。多个 Agent 和人类可以通过共享文件进行协调。Agent 团队等架构依赖于此。&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;Git 为文件系统添加了版本控制，因此 Agent 可以跟踪工作、回滚错误和分支实验。我们将在下文再次讨论文件系统，因为它被证明是我们需要其他功能的关键 Harness 原语。&lt;/p&gt; 
&lt;h2&gt;Bash + 代码：通用工具&lt;/h2&gt; 
&lt;p&gt;我们希望 Agent 能够自主解决问题，而无需人类预先设计每个工具。&lt;/p&gt; 
&lt;p&gt;当前主要的 Agent 执行模式是 ReAct 循环，其中模型进行推理，通过工具调用执行一个动作，观察结果，并在一个 &lt;code&gt;while&lt;/code&gt; 循环中重复。但 Harness 只能执行它有逻辑的工具。与其强迫用户为每个可能的动作构建工具，更好的解决方案是给 Agent 一个像 Bash 这样的通用工具。&lt;/p&gt; 
&lt;p&gt;Harness 附带 Bash 工具，因此模型可以通过编写和执行代码自主解决问题。&lt;/p&gt; 
&lt;p&gt;Bash + 代码执行是让模型拥有计算机并让它们自主解决其余问题迈出的一大步。模型可以即时设计自己的工具，而不是受限于一组固定的预配置工具。&lt;/p&gt; 
&lt;p&gt;Harness 仍然附带其他工具，但代码执行已成为自主解决问题的默认通用策略。&lt;/p&gt; 
&lt;h2&gt;沙盒和工具：执行与验证工作&lt;/h2&gt; 
&lt;p&gt;Agent 需要一个具有正确默认值的环境，以便它们能够安全地行动、观察结果并取得进展。&lt;/p&gt; 
&lt;p&gt;我们已经赋予模型存储和执行代码的能力，但所有这些都需要在某个地方发生。在本地运行 Agent 生成的代码是有风险的，单个本地环境无法扩展到大型 Agent 工作负载。&lt;/p&gt; 
&lt;p&gt;沙盒为 Agent 提供了安全的操作环境。Harness 可以连接到沙盒来运行代码、检查文件、安装依赖项和完成任务，而不是在本地执行。这创建了安全、隔离的代码执行。为了更高的安全性，Harness 可以允许列表命令并强制执行网络隔离。沙盒还解锁了规模化，因为环境可以按需创建、分散到许多任务中，并在工作完成后销毁。&lt;/p&gt; 
&lt;p&gt;好的环境伴随着好的默认工具。Harness 负责配置工具，以便 Agent 能够完成有用的工作。这包括预安装语言运行时和包、用于 Git 和测试的 CLI、用于 Web 交互和验证的浏览器。&lt;/p&gt; 
&lt;p&gt;浏览器、日志、截图和测试运行器等工具为 Agent 提供了一种观察和分析其工作的方式。这有助于它们创建自我验证循环，在其中它们可以编写应用程序代码、运行测试、检查日志并修复错误。&lt;/p&gt; 
&lt;p&gt;模型本身并不会配置自己的执行环境。决定 Agent 在哪里运行、有哪些工具可用、它可以访问什么以及如何验证其工作，这些都是 Harness 层面的设计决策。&lt;/p&gt; 
&lt;h2&gt;记忆与搜索：持续学习&lt;/h2&gt; 
&lt;p&gt;Agent 应该记住它们所看到的内容，并访问在它们训练时不存在的信息。&lt;/p&gt; 
&lt;p&gt;模型除了其权重和当前上下文中的内容之外，没有额外的知识。在无法编辑模型权重的情况下，“添加知识”的唯一方法是通过上下文注入。&lt;/p&gt; 
&lt;p&gt;对于记忆，文件系统再次成为核心原语。Harness 支持像 AGENTS.md 这样的记忆文件标准，这些文件在 Agent 启动时被注入到上下文中。当 Agent 添加和编辑此文件时，Harness 会将更新后的文件加载到上下文中。这是一种持续学习的形式，Agent 将一个会话中的知识持久存储并注入到未来的会话中。&lt;/p&gt; 
&lt;p&gt;知识截止日期意味着模型无法直接访问新数据，例如更新的库版本，除非用户直接提供。对于最新知识，Web 搜索和像 Context7 这样的 MCP 工具帮助 Agent 访问超出知识截止日期之外的信息，例如新的库版本或在训练停止时不存在的当前数据。&lt;/p&gt; 
&lt;p&gt;Web 搜索和查询最新上下文的工具是嵌入到 Harness 中的有用原语。&lt;/p&gt; 
&lt;h2&gt;应对上下文腐烂（Context Rot）&lt;/h2&gt; 
&lt;p&gt;Agent 的性能不应在工作过程中下降。上下文腐烂（Context Rot）描述了模型随着上下文窗口的填满，在推理和完成任务方面的能力下降。上下文是一种宝贵且稀缺的资源，因此 Harness 需要管理它的策略。&lt;/p&gt; 
&lt;p&gt;今天的 Harness 在很大程度上是良好上下文工程的交付机制。&lt;/p&gt; 
&lt;h3&gt;压缩（Compaction）&lt;/h3&gt; 
&lt;p&gt;压缩解决了当上下文窗口即将填满时该怎么办的问题。如果没有压缩，当对话超出上下文窗口时会发生什么？一种选择是 API 报错，这不好。Harness 必须为此情况使用某种策略。因此，压缩会智能地卸载和总结现有的上下文窗口，以便 Agent 可以继续工作。&lt;/p&gt; 
&lt;h3&gt;工具调用卸载（Tool Call Offloading）&lt;/h3&gt; 
&lt;p&gt;工具调用卸载有助于减少大型工具输出的影响，这些输出可能会嘈杂地占用上下文窗口，而没有提供有用的信息。Harness 会保留超过阈值 Token 数量的工具输出的开头和结尾 Token，并将完整输出卸载到文件系统，以便模型在需要时可以访问它。&lt;/p&gt; 
&lt;h3&gt;技能（Skills）&lt;/h3&gt; 
&lt;p&gt;技能解决了在 Agent 启动时加载过多工具或 MCP 服务器到上下文中的问题，这会在 Agent 开始工作之前降低性能。技能是一种 Harness 级别的原语，它通过渐进式披露来解决这个问题。模型没有选择在启动时将技能前置信息加载到上下文中，但 Harness 可以支持这一点，以保护模型免受上下文腐烂的影响。&lt;/p&gt; 
&lt;h2&gt;长周期自主执行&lt;/h2&gt; 
&lt;p&gt;我们希望 Agent 能够自主、正确地在长周期内完成复杂的工作。&lt;/p&gt; 
&lt;p&gt;自主软件创建是编码 Agent 的圣杯。但今天的模型存在过早停止、分解复杂问题困难以及在跨多个上下文窗口工作时缺乏连贯性等问题。一个好的 Harness 必须围绕所有这些问题进行设计。&lt;/p&gt; 
&lt;p&gt;这就是早期 Harness 原语开始复合的地方。长周期工作需要持久状态、规划、观察和验证，以在多个上下文窗口中持续工作。&lt;/p&gt; 
&lt;h3&gt;文件系统和 Git 用于跟踪工作&lt;/h3&gt; 
&lt;p&gt;文件系统和 Git 用于跟踪跨会话的工作。Agent 在长时间任务中会产生数百万个 Token，因此文件系统持久地捕获工作以跟踪随时间推移的进度。添加 Git 允许新的 Agent 快速了解项目的最新工作和历史记录。对于多个 Agent 协同工作，文件系统也充当共享的工作账本，Agent 可以在其中协作。&lt;/p&gt; 
&lt;h3&gt;Ralph 循环用于持续工作&lt;/h3&gt; 
&lt;p&gt;Ralph 循环是一种 Harness 模式，它通过Hook拦截模型的退出尝试，并在一个干净的上下文窗口中重新注入原始提示，强制 Agent 根据完成目标继续其工作。文件系统使这成为可能，因为每次迭代都以新鲜的上下文开始，但从前一次迭代中读取状态。&lt;/p&gt; 
&lt;h3&gt;规划和自我验证&lt;/h3&gt; 
&lt;p&gt;规划和自我验证以保持正轨。规划是模型将目标分解为一系列步骤的过程。Harness 通过良好的提示和注入提醒如何在文件系统中使用计划文件来支持这一点。在完成每个步骤后，Agent 通过自我验证来检查其工作的正确性。Harness 中的Hook可以运行预定义的测试套件，并在失败时将错误消息反馈给模型，或者可以提示模型独立地自我评估其代码。验证将解决方案建立在测试中，并为自我改进创建反馈信号。&lt;/p&gt; 
&lt;h2&gt;Harness 的未来&lt;/h2&gt; 
&lt;h2&gt;模型训练与 Harness 设计的耦合&lt;/h2&gt; 
&lt;p&gt;今天的 Agent 产品，如 Claude Code 和 Codex，都是在模型和 Harness 循环中进行后期训练的。这有助于模型改进 Harness 设计者认为它们应该天生擅长的操作，例如文件系统操作、Bash 执行、规划或使用子 Agent 并行化工作。&lt;/p&gt; 
&lt;p&gt;这形成了一个反馈循环。有用的原语被发现，添加到 Harness 中，然后用于训练下一代模型。随着这个循环的重复，模型在它们所训练的 Harness 中变得更加强大。&lt;/p&gt; 
&lt;p&gt;但这种共同演化对泛化能力产生了有趣的副作用。它体现在诸如更改工具逻辑导致模型性能下降等方面。一个很好的例子在 Codex-5.3 提示指南中描述了 &lt;code&gt;apply_patch&lt;/code&gt; 工具逻辑用于编辑文件。一个真正智能的模型在不同补丁方法之间切换应该没有太大问题，但在循环中进行 Harness 训练会造成这种过拟合。&lt;/p&gt; 
&lt;p&gt;但这并不意味着最适合你任务的 Harness 就是模型经过后期训练的那个。Terminal Bench 2.0 排行榜就是一个很好的例子。Claude Code 中的 Opus 4.6 在其他 Harness 中的得分远低于 Opus 4.6。在之前的一篇博客中，我们展示了如何通过仅更改 Harness，将我们的编码 Agent Top 30 提升到 Terminal Bench 2.0 的 Top 5。通过优化 Harness 来完成你的任务，还有很大的提升空间。&lt;/p&gt; 
&lt;p&gt;&lt;img src=&quot;https://img.learnblockchain.cn/2026/03/17/32162951_image.jpg&quot; alt=&quot;Image&quot; class=&quot;aligncenter&quot;&gt;&lt;/p&gt; 
&lt;h2&gt;Harness 工程的发展方向&lt;/h2&gt; 
&lt;p&gt;随着模型能力越来越强，今天 Harness 中的一些功能将被模型吸收。模型将更好地进行规划、自我验证和长周期连贯性，从而减少上下文注入的需求。&lt;/p&gt; 
&lt;p&gt;这表明 Harness 的重要性会随着时间的推移而降低。但就像提示工程今天仍然有价值一样，Harness 工程很可能仍然对构建好的 Agent 有用。&lt;/p&gt; 
&lt;p&gt;诚然，今天的 Harness 弥补了模型的不足，但它们也围绕模型智能设计系统，使其更有效。一个配置良好的环境、正确的工具、持久状态和验证循环，无论其基础智能如何，都能使任何模型更高效。&lt;/p&gt; 
&lt;p&gt;Harness 工程是一个非常活跃的研究领域，我们用它来改进我们在 LangChain 的 Harness 构建库 deepagents。以下是我们今天正在探索的一些开放且有趣的问题：&lt;/p&gt; 
&lt;ul&gt; 
 &lt;li&gt;协调数百个 Agent 在共享代码库上并行工作&lt;/li&gt; 
 &lt;li&gt;Agent 分析自己的轨迹以识别和修复 Harness 级别的故障模式&lt;/li&gt; 
 &lt;li&gt;Harness 动态地为给定任务即时组装正确的工具和上下文，而不是预先配置&lt;/li&gt; 
&lt;/ul&gt; 
&lt;p&gt;这篇博客旨在定义 Harness 是什么，以及它如何受我们希望模型完成的工作的影响。&lt;/p&gt; 
&lt;p&gt;模型包含智能，而 Harness 是使这种智能有用的系统。&lt;/p&gt; 
&lt;p&gt;致敬更多的 Harness 构建，更好的系统，以及更好的 Agent。&lt;/p&gt; 
&lt;blockquote&gt; 
 &lt;ul&gt; 
  &lt;li&gt;原文链接： x.com/Vtrivedy10/status/...&lt;/li&gt; 
  &lt;li&gt;登链社区 AI 助手，为大家转译优秀英文文章，如有翻译不通的地方，还请包涵～&lt;/li&gt; 
 &lt;/ul&gt; 
&lt;/blockquote&gt;</description><pubDate>Fri, 20 Mar 2026 01:40:30 +0800</pubDate></item></channel></rss>