<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>智能合约 on 牛哥聊技术</title><link>https://www.lingcoder.com/tags/%E6%99%BA%E8%83%BD%E5%90%88%E7%BA%A6/</link><description>Recent content in 智能合约 on 牛哥聊技术</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Tue, 28 Oct 2025 16:00:00 +0800</lastBuildDate><atom:link href="https://www.lingcoder.com/tags/%E6%99%BA%E8%83%BD%E5%90%88%E7%BA%A6/index.xml" rel="self" type="application/rss+xml"/><item><title>Compound 协议解析：DeFi 借贷的奠基者</title><link>https://www.lingcoder.com/p/compound-protocol-explained/</link><pubDate>Tue, 28 Oct 2025 16:00:00 +0800</pubDate><guid>https://www.lingcoder.com/p/compound-protocol-explained/</guid><description>&lt;img src="https://www.lingcoder.com/p/compound-protocol-explained/cover.svg" alt="Featured image of post Compound 协议解析：DeFi 借贷的奠基者" /&gt;&lt;h2 id="写在前面"&gt;&lt;a href="#%e5%86%99%e5%9c%a8%e5%89%8d%e9%9d%a2" class="header-anchor"&gt;&lt;/a&gt;写在前面
&lt;/h2&gt;&lt;p&gt;DeFi 圈子里有几个绕不过去的奠基者协议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MakerDAO&lt;/strong&gt;：把法币概念引入链上（DAI 稳定币）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Uniswap&lt;/strong&gt;：把 AMM（自动做市商）做到极致&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compound&lt;/strong&gt;：把链上借贷做成可组合金融基础设施&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Compound&lt;/strong&gt; 由 Robert Leshner 和 Geoffrey Hayes 在 2018 年推出。它创造了几个让整个 DeFi 沿用至今的范式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;cToken 模型&lt;/strong&gt;：把&amp;quot;存入资产&amp;quot;变成可流通的代币&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;算法利率&lt;/strong&gt;：根据资金池利用率自动调整借贷利率&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;流动性挖矿（COMP 代币）&lt;/strong&gt;：开启 DeFi 真正的&amp;quot;治理代币热&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;预言机喂价&lt;/strong&gt;：所有借贷依赖外部价格&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你想理解 Aave、Venus、JustLend 等借贷协议——&lt;strong&gt;先吃透 Compound&lt;/strong&gt;。本文讲清楚它的核心机制和工程价值。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一compound-在做什么"&gt;&lt;a href="#%e4%b8%80compound-%e5%9c%a8%e5%81%9a%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;一、Compound 在做什么
&lt;/h2&gt;&lt;p&gt;简单说——&lt;strong&gt;Compound 是一个去中心化的&amp;quot;借贷池&amp;quot;&lt;/strong&gt;。任何人可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;存入资产&lt;/strong&gt;（USDC、ETH、DAI 等支持的资产）→ 赚利息&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;抵押资产借出&lt;/strong&gt;（用你的存款做抵押借出别的资产）&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 Lender[出借人] --&gt;|存入 USDC| Pool[(资金池)]
 Borrower[借款人] --&gt;|抵押 ETH| Pool
 Pool --&gt;|借出 USDC| Borrower
 Pool --&gt;|利息| Lender&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;没有撮合&lt;/strong&gt;——所有用户对的是&amp;quot;池子&amp;quot;，不是另一个用户。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二ctoken存款的代币化"&gt;&lt;a href="#%e4%ba%8cctoken%e5%ad%98%e6%ac%be%e7%9a%84%e4%bb%a3%e5%b8%81%e5%8c%96" class="header-anchor"&gt;&lt;/a&gt;二、cToken：存款的代币化
&lt;/h2&gt;&lt;p&gt;存进 Compound 的 USDC 不是普通存款——你换回了 &lt;strong&gt;cUSDC&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;存入 100 USDC → 收到约 5000 cUSDC（按当前 exchange rate ≈ 1 USDC : 50 cUSDC）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;cUSDC&lt;/code&gt; 是一种 ERC-20 代币：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数量代表你&lt;strong&gt;对池子的份额&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;池子产生利息时，&lt;strong&gt;cToken 总数不变，但 exchange rate 持续上涨&lt;/strong&gt;——也就是 1 cUSDC 能兑换的 USDC 越来越多&lt;/li&gt;
&lt;li&gt;取回时把 cUSDC 烧掉换回 USDC（按当前 exchange rate）&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 User --&gt;|deposit 100 USDC| Compound
 Compound --&gt;|mint ~5000 cUSDC| User
 Note1[初期 exchange rate ≈ 0.02 USDC/cUSDC]
 Note2[一年后 exchange rate ≈ 0.0213 USDC/cUSDC&lt;br/&gt;同样 5000 cUSDC 现在能换回 ~106 USDC]&lt;/pre&gt;&lt;p&gt;也就是说，&lt;strong&gt;同样数量的 cUSDC 能换回的 USDC 持续增加&lt;/strong&gt;——这就是利息累积的体现。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;cToken 的革命性&lt;/strong&gt;：它是&lt;strong&gt;可组合的金融乐高&lt;/strong&gt;——你可以把 cUSDC：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;存进 Yearn 等收益聚合器&lt;/li&gt;
&lt;li&gt;在 DEX 上交易&lt;/li&gt;
&lt;li&gt;作为其他 DeFi 协议的抵押品&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不像传统银行存款只是个数字——&lt;strong&gt;cToken 是真实的可流通资产&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="三算法利率模型"&gt;&lt;a href="#%e4%b8%89%e7%ae%97%e6%b3%95%e5%88%a9%e7%8e%87%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;三、算法利率模型
&lt;/h2&gt;&lt;p&gt;利率不是固定的——&lt;strong&gt;根据资金池的&amp;quot;利用率&amp;quot;动态调整&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;利用率 U = 借出量 / 总存款
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;利率公式（简化）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;borrowRate = baseRate + U × multiplier // 利用率低时
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;borrowRate = baseRate + U × multiplier + (U - kink) × jumpMultiplier // U 超过 kink 后陡升
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;直觉：&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;/ul&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 LowU[利用率低] --&gt; LowRate[低利率] --&gt; AttractBorrow[吸引借款]
 HighU[利用率高] --&gt; HighRate[高利率] --&gt; AttractDeposit[吸引存款]&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Compound 的&amp;quot;利率曲线&amp;quot;&lt;/strong&gt; 让市场自动平衡——不需要中心化机构定价。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="四抵押与清算"&gt;&lt;a href="#%e5%9b%9b%e6%8a%b5%e6%8a%bc%e4%b8%8e%e6%b8%85%e7%ae%97" class="header-anchor"&gt;&lt;/a&gt;四、抵押与清算
&lt;/h2&gt;&lt;p&gt;借款必须超额抵押——比如借 70 USDC 要抵押价值 100 ETH。&lt;/p&gt;
&lt;h3 id="collateral-factor"&gt;&lt;a href="#collateral-factor" class="header-anchor"&gt;&lt;/a&gt;Collateral Factor
&lt;/h3&gt;&lt;p&gt;每种资产有一个 Collateral Factor（抵押因子）——比如 ETH 是 75%、稳定币 90%。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;你抵押 1000 USD 的 ETH（CF=75%）→ 最多能借 750 USD 等值的资产&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="健康因子"&gt;&lt;a href="#%e5%81%a5%e5%ba%b7%e5%9b%a0%e5%ad%90" class="header-anchor"&gt;&lt;/a&gt;健康因子
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Health Factor = (抵押品价值 × CF) / 借款价值
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;HF &amp;gt; 1：安全&lt;/li&gt;
&lt;li&gt;HF &amp;lt; 1：可被清算&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="清算机制"&gt;&lt;a href="#%e6%b8%85%e7%ae%97%e6%9c%ba%e5%88%b6" class="header-anchor"&gt;&lt;/a&gt;清算机制
&lt;/h3&gt;&lt;p&gt;如果 ETH 价格暴跌让 HF 跌破 1——&lt;strong&gt;任何人都可以触发清算&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;清算者&lt;strong&gt;替借款人还一部分债&lt;/strong&gt;（最多 50%）&lt;/li&gt;
&lt;li&gt;清算者&lt;strong&gt;以折扣价&lt;/strong&gt;拿走借款人的抵押品（折扣通常 5-8%）&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 BorrowerHF[借款人 HF=0.95] --&gt; Anyone[任何人]
 Anyone --&gt;|repay 50% debt| Pool
 Pool --&gt;|reward 105% collateral| Anyone&lt;/pre&gt;&lt;p&gt;清算者赚 5% 差价——&lt;strong&gt;这是激励清算者监控市场的机制&lt;/strong&gt;。链上有专门的&amp;quot;清算机器人&amp;quot;24x7 盯着所有借款人的 HF。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="五comp-治理代币与流动性挖矿"&gt;&lt;a href="#%e4%ba%94comp-%e6%b2%bb%e7%90%86%e4%bb%a3%e5%b8%81%e4%b8%8e%e6%b5%81%e5%8a%a8%e6%80%a7%e6%8c%96%e7%9f%bf" class="header-anchor"&gt;&lt;/a&gt;五、COMP 治理代币与流动性挖矿
&lt;/h2&gt;&lt;p&gt;2020 年 6 月 Compound 上线了 &lt;strong&gt;COMP 代币&lt;/strong&gt;——治理用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;总量 1000 万&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每天向用户分发&lt;/strong&gt;——根据&amp;quot;借入 + 存款总额&amp;quot;按比例&lt;/li&gt;
&lt;li&gt;持有 COMP 可以参与协议治理（提案、投票）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这一改动&lt;strong&gt;点燃了整个 DeFi 行业的&amp;quot;流动性挖矿&amp;quot;狂潮&lt;/strong&gt;：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;用户存款不仅能赚利息，&lt;strong&gt;还能赚 COMP&lt;/strong&gt;——COMP 价格高时，挖矿收益甚至超过本金利息。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;短短几周——Compound 总锁仓量从几亿美元飙到几十亿。&lt;strong&gt;Aave、SushiSwap、Curve 等无数协议跟进发币&lt;/strong&gt;——DeFi 进入&amp;quot;DeFi Summer&amp;quot;。&lt;/p&gt;
&lt;h3 id="治理实践"&gt;&lt;a href="#%e6%b2%bb%e7%90%86%e5%ae%9e%e8%b7%b5" class="header-anchor"&gt;&lt;/a&gt;治理实践
&lt;/h3&gt;&lt;p&gt;COMP 持有者可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;提议加新借贷市场&lt;/li&gt;
&lt;li&gt;调整 Collateral Factor&lt;/li&gt;
&lt;li&gt;调整利率曲线参数&lt;/li&gt;
&lt;li&gt;升级合约&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Compound 是真正实现了&amp;quot;协议自治&amp;quot;的样板&lt;/strong&gt;——团队渐渐退出，决策由代币持有者做。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="六预言机compound-的命门"&gt;&lt;a href="#%e5%85%ad%e9%a2%84%e8%a8%80%e6%9c%bacompound-%e7%9a%84%e5%91%bd%e9%97%a8" class="header-anchor"&gt;&lt;/a&gt;六、预言机：Compound 的命门
&lt;/h2&gt;&lt;p&gt;所有借贷决策都依赖&lt;strong&gt;资产价格&lt;/strong&gt;——清算判断、抵押率计算都要价格输入。&lt;/p&gt;
&lt;p&gt;Compound 早期使用 &lt;strong&gt;Open Price Feed&lt;/strong&gt;——以 &lt;strong&gt;Coinbase Pro&lt;/strong&gt;（后改名 Coinbase Exchange）作为主要签名报价方，配合 Uniswap V2 TWAP 做防御。设计预期是接入更多 reporter，但实际上 Coinbase 长期是唯一稳定上报的数据源。&lt;/p&gt;
&lt;p&gt;但&lt;strong&gt;预言机被攻击的案例不少&lt;/strong&gt;——经典案例是 2020 年 11 月 26 日 &lt;strong&gt;DAI 在 Coinbase Pro 价格异常飙升&lt;/strong&gt;，导致 Compound 上约 &lt;strong&gt;8900 万美元&lt;/strong&gt;的资产被异常清算。这是 DeFi 借贷协议&lt;strong&gt;永远的 risk&lt;/strong&gt;——单源预言机一旦被推、被异常波动，整个清算系统就被绑架。&lt;/p&gt;
&lt;p&gt;V3 之后 Compound 全面切到 &lt;strong&gt;Chainlink&lt;/strong&gt; 等专业预言机——&lt;strong&gt;多源 + 时间加权 + 偏离阈值&lt;/strong&gt; 多重保护，把这类风险显著降低。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="七ctoken-vs-atoken-vs-dtoken"&gt;&lt;a href="#%e4%b8%83ctoken-vs-atoken-vs-dtoken" class="header-anchor"&gt;&lt;/a&gt;七、cToken vs aToken vs dToken
&lt;/h2&gt;&lt;p&gt;不同借贷协议的&amp;quot;存款代币&amp;quot;模型有差异：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: left"&gt;&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Compound (cToken)&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Aave V2 (aToken)&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Aave V3 (aToken)&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;余额变化&lt;/td&gt;
 &lt;td style="text-align: left"&gt;数量不变，价格上涨&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;strong&gt;数量上涨&lt;/strong&gt;，价格 = 1&lt;/td&gt;
 &lt;td style="text-align: left"&gt;数量上涨&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;用户体验&lt;/td&gt;
 &lt;td style="text-align: left"&gt;看着不增长（要折算）&lt;/td&gt;
 &lt;td style="text-align: left"&gt;看到余额秒级增长&lt;/td&gt;
 &lt;td style="text-align: left"&gt;同 V2&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;复合性&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✓&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✓&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✓&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;aToken 用户体验上更好——&lt;strong&gt;钱包里看到余额持续增长&lt;/strong&gt;，比 cToken 直观。Compound 后来推出的 V3 借鉴了 aToken 模型。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="八compound-v2-vs-v3"&gt;&lt;a href="#%e5%85%abcompound-v2-vs-v3" class="header-anchor"&gt;&lt;/a&gt;八、Compound V2 vs V3
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Compound V2（2019）&lt;/strong&gt;：当时被称为&amp;quot;DeFi 借贷的标准&amp;quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Compound V3（2022）&lt;/strong&gt;——重大改变：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;每个市场只有一种借出资产&lt;/strong&gt;（如 USDC 池只能借 USDC）——避免协议级流动性碎片化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;抵押品和借出资产分离&lt;/strong&gt;——你抵押 ETH 借 USDC，但 ETH 不会被借出去&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;简化治理&lt;/strong&gt;——更专注、更易管理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;V3 的设计哲学是 &lt;strong&gt;&amp;ldquo;做对一件事&amp;rdquo;&lt;/strong&gt;——让借贷更安全、更可预测，但灵活性比 V2 低一些。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="九compound-的影响和遗产"&gt;&lt;a href="#%e4%b9%9dcompound-%e7%9a%84%e5%bd%b1%e5%93%8d%e5%92%8c%e9%81%97%e4%ba%a7" class="header-anchor"&gt;&lt;/a&gt;九、Compound 的影响和遗产
&lt;/h2&gt;&lt;h3 id="直接受影响的协议"&gt;&lt;a href="#%e7%9b%b4%e6%8e%a5%e5%8f%97%e5%bd%b1%e5%93%8d%e7%9a%84%e5%8d%8f%e8%ae%ae" class="header-anchor"&gt;&lt;/a&gt;直接受影响的协议
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Aave&lt;/strong&gt; - 借贷模型几乎完全师承 Compound&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Venus（BNB 链）&lt;/strong&gt; - Compound V2 的 fork&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JustLend（Tron）&lt;/strong&gt; - Compound V2 的 fork&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Benqi（Avalanche）&lt;/strong&gt; - Compound V2 的 fork&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Compound 的开源代码被复制粘贴到了几乎所有 EVM 链&lt;/strong&gt;——是 DeFi 借贷的&amp;quot;参考实现&amp;quot;。&lt;/p&gt;
&lt;h3 id="留下的范式"&gt;&lt;a href="#%e7%95%99%e4%b8%8b%e7%9a%84%e8%8c%83%e5%bc%8f" class="header-anchor"&gt;&lt;/a&gt;留下的范式
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;池化借贷&lt;/strong&gt;（vs P2P 撮合）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;算法利率&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代币化存款&lt;/strong&gt;（cToken）&lt;/li&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;/ol&gt;
&lt;p&gt;这些都是 &lt;strong&gt;Compound 第一个推广&lt;/strong&gt;——后续协议都在它的基础上演化。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="十compound-的局限与反思"&gt;&lt;a href="#%e5%8d%81compound-%e7%9a%84%e5%b1%80%e9%99%90%e4%b8%8e%e5%8f%8d%e6%80%9d" class="header-anchor"&gt;&lt;/a&gt;十、Compound 的局限与反思
&lt;/h2&gt;&lt;h3 id="1-高-gas-费"&gt;&lt;a href="#1-%e9%ab%98-gas-%e8%b4%b9" class="header-anchor"&gt;&lt;/a&gt;1. 高 Gas 费
&lt;/h3&gt;&lt;p&gt;每次存款 / 借款 / 清算都是链上交易——以太坊高峰期单次操作 $50+。&lt;strong&gt;这是为什么 Compound 在 L2 / 侧链才能让小用户负担得起&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="2-系统性风险"&gt;&lt;a href="#2-%e7%b3%bb%e7%bb%9f%e6%80%a7%e9%a3%8e%e9%99%a9" class="header-anchor"&gt;&lt;/a&gt;2. 系统性风险
&lt;/h3&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;智能合约 bug → 资金被盗&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Compound 历史上有过几次小事故&lt;/strong&gt;——DAI 利率公式 bug 短暂导致部分清算异常，但整体记录良好。&lt;/p&gt;
&lt;h3 id="3-治理参与度低"&gt;&lt;a href="#3-%e6%b2%bb%e7%90%86%e5%8f%82%e4%b8%8e%e5%ba%a6%e4%bd%8e" class="header-anchor"&gt;&lt;/a&gt;3. 治理参与度低
&lt;/h3&gt;&lt;p&gt;虽然有 COMP 投票——&lt;strong&gt;多数提案投票率不高&lt;/strong&gt;。&amp;ldquo;代币持有者治理&amp;quot;在理论上美好，&lt;strong&gt;实践中常常是『大户投票决定一切』&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="4-复杂性对小用户不友好"&gt;&lt;a href="#4-%e5%a4%8d%e6%9d%82%e6%80%a7%e5%af%b9%e5%b0%8f%e7%94%a8%e6%88%b7%e4%b8%8d%e5%8f%8b%e5%a5%bd" class="header-anchor"&gt;&lt;/a&gt;4. 复杂性对小用户不友好
&lt;/h3&gt;&lt;p&gt;抵押率、利率曲线、清算阈值——&lt;strong&gt;普通人很难弄懂&lt;/strong&gt;。这是 DeFi 至今没能真正&amp;quot;出圈&amp;quot;的痛点之一。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="十一对开发者的启发"&gt;&lt;a href="#%e5%8d%81%e4%b8%80%e5%af%b9%e5%bc%80%e5%8f%91%e8%80%85%e7%9a%84%e5%90%af%e5%8f%91" class="header-anchor"&gt;&lt;/a&gt;十一、对开发者的启发
&lt;/h2&gt;&lt;p&gt;读 Compound 源码（&lt;a class="link" href="https://github.com/compound-finance/compound-protocol" target="_blank" rel="noopener"
 &gt;github.com/compound-finance/compound-protocol&lt;/a&gt;）能学到很多：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CToken.sol&lt;/strong&gt;：cToken 实现，&lt;strong&gt;学习&amp;quot;代币化金融头寸&amp;quot;的范式&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Comptroller.sol&lt;/strong&gt;：协议核心控制器，&lt;strong&gt;学习如何用单合约管理多市场&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;InterestRateModel.sol&lt;/strong&gt;：利率模型——&lt;strong&gt;算法定价的实战案例&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PriceOracleProxy.sol&lt;/strong&gt;：预言机抽象层&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Governance.sol&lt;/strong&gt;：治理合约&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些不是&amp;quot;教学代码&amp;rdquo;——是&lt;strong&gt;真实管理着数十亿美元资金的代码&lt;/strong&gt;，工程质量在 DeFi 中属于标杆水平。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;p&gt;把 Compound 的价值压一句：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;Compound 不只是一个借贷协议——它是 DeFi 的『结构性创新者』，定义了池化借贷、算法利率、流动性挖矿等几乎所有 DeFi 沿用至今的范式。&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;理解它的几个关键：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;cToken = 代币化的存款 + 利息&lt;/strong&gt;&lt;/li&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;COMP 让协议进入&amp;quot;代币治理&amp;quot;时代&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;V3 进一步简化和专注&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你做 DeFi 借贷类项目——&lt;strong&gt;绝不要 fork Compound 就上线&lt;/strong&gt;。要理解每一行代码、每一个参数的含义。Compound 的代码值得反复研读——
&lt;strong&gt;是 DeFi 工程师值得花时间啃的源码之一&lt;/strong&gt;。&lt;/p&gt;</description></item><item><title>OpenZeppelin Contracts 5.0 的关键变化</title><link>https://www.lingcoder.com/p/openzeppelin-5-major-changes/</link><pubDate>Thu, 07 Aug 2025 15:30:00 +0800</pubDate><guid>https://www.lingcoder.com/p/openzeppelin-5-major-changes/</guid><description>&lt;img src="https://www.lingcoder.com/p/openzeppelin-5-major-changes/cover.svg" alt="Featured image of post OpenZeppelin Contracts 5.0 的关键变化" /&gt;&lt;h2 id="写在前面"&gt;&lt;a href="#%e5%86%99%e5%9c%a8%e5%89%8d%e9%9d%a2" class="header-anchor"&gt;&lt;/a&gt;写在前面
&lt;/h2&gt;&lt;p&gt;OpenZeppelin Contracts 是 Solidity 生态最广泛使用的标准库——ERC-20、ERC-721、AccessControl、Proxy、Pausable…… &lt;strong&gt;几乎每个生产级合约项目都依赖它&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;2023 年 10 月发布的 &lt;strong&gt;5.0&lt;/strong&gt; 是它自 4.0 之后最大的一次主版本升级——&lt;strong&gt;不只是 bug 修复&lt;/strong&gt;，而是对 API、安全模型、模块组织的全面重塑。&lt;/p&gt;
&lt;p&gt;本文讲清楚 5.0 的核心变化、升级要踩的坑、新特性的工程价值。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一最大变化solidity-0820-基线"&gt;&lt;a href="#%e4%b8%80%e6%9c%80%e5%a4%a7%e5%8f%98%e5%8c%96solidity-0820-%e5%9f%ba%e7%ba%bf" class="header-anchor"&gt;&lt;/a&gt;一、最大变化：Solidity 0.8.20+ 基线
&lt;/h2&gt;&lt;p&gt;OpenZeppelin 5.0 要求 &lt;strong&gt;Solidity ^0.8.20&lt;/strong&gt;——意味着几个旧版本必须放弃：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pragma solidity ^0.8.0&lt;/code&gt; 不够了&lt;/li&gt;
&lt;li&gt;升级编译器同时要校对每个 mixin/inherit&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;升 Solidity 版本通常是个连锁反应——所有依赖的第三方库也要兼容。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二ownable-必须传-initialowner"&gt;&lt;a href="#%e4%ba%8cownable-%e5%bf%85%e9%a1%bb%e4%bc%a0-initialowner" class="header-anchor"&gt;&lt;/a&gt;二、Ownable 必须传 &lt;code&gt;initialOwner&lt;/code&gt;
&lt;/h2&gt;&lt;p&gt;最容易踩的小坑——&lt;code&gt;Ownable&lt;/code&gt; 构造函数变了：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 4.x
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;contract&lt;/span&gt; &lt;span class="nc"&gt;MyContract&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Ownable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="c1"&gt;// 自动用 msg.sender 作为 owner
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 5.x
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;contract&lt;/span&gt; &lt;span class="nc"&gt;MyContract&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Ownable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;initialOwner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Ownable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initialOwner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="c1"&gt;// 必须显式传
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;为什么改？——&lt;strong&gt;因为 &lt;code&gt;msg.sender&lt;/code&gt; 在 deployer 用 factory 模式时往往不是真实的 owner&lt;/strong&gt;。强制显式传 owner，**避免&amp;quot;工厂部署却把 owner 设成工厂&amp;quot;**这类隐蔽 bug。&lt;/p&gt;
&lt;p&gt;升级时所有 Ownable 的子合约构造函数都要改。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="三accesscontrol-的角色管理变了"&gt;&lt;a href="#%e4%b8%89accesscontrol-%e7%9a%84%e8%a7%92%e8%89%b2%e7%ae%a1%e7%90%86%e5%8f%98%e4%ba%86" class="header-anchor"&gt;&lt;/a&gt;三、AccessControl 的角色管理变了
&lt;/h2&gt;&lt;h3 id="_grantrole-移到-internal"&gt;&lt;a href="#_grantrole-%e7%a7%bb%e5%88%b0-internal" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;_grantRole&lt;/code&gt; 移到 internal
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 4.x：_setupRole 用于初始化阶段
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;_setupRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_ADMIN_ROLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 5.x：_setupRole 删除，统一用 _grantRole
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;_grantRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_ADMIN_ROLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;_setupRole&lt;/code&gt; 这个&amp;quot;专门为初始化用&amp;quot;的函数被合并——&lt;strong&gt;减少 API 表面&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="hasrole-行为不变但-getrolemembercount--getrolemember-移到了-accesscontrolenumerable"&gt;&lt;a href="#hasrole-%e8%a1%8c%e4%b8%ba%e4%b8%8d%e5%8f%98%e4%bd%86-getrolemembercount--getrolemember-%e7%a7%bb%e5%88%b0%e4%ba%86-accesscontrolenumerable" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;hasRole&lt;/code&gt; 行为不变，但 &lt;code&gt;getRoleMemberCount&lt;/code&gt; / &lt;code&gt;getRoleMember&lt;/code&gt; 移到了 &lt;code&gt;AccessControlEnumerable&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;如果你需要枚举角色成员，必须显式继承 &lt;code&gt;AccessControlEnumerable&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="四erc20-的-_beforetokentransfer--_aftertokentransfer-删除"&gt;&lt;a href="#%e5%9b%9berc20-%e7%9a%84-_beforetokentransfer--_aftertokentransfer-%e5%88%a0%e9%99%a4" class="header-anchor"&gt;&lt;/a&gt;四、ERC20 的 &lt;code&gt;_beforeTokenTransfer&lt;/code&gt; / &lt;code&gt;_afterTokenTransfer&lt;/code&gt; 删除
&lt;/h2&gt;&lt;p&gt;4.x 钩子模型：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_beforeTokenTransfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_beforeTokenTransfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;5.x 简化为单个 &lt;code&gt;_update&lt;/code&gt; 钩子：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="nb"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 在这里加自定义逻辑
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;为什么改&lt;/strong&gt;——钩子分前后两个增加了心智负担和 Gas 开销，单个 &lt;code&gt;_update&lt;/code&gt; 函数已经覆盖所有需求。&lt;/p&gt;
&lt;p&gt;升级时所有自定义 ERC20 / ERC721 都要把 hook 重写。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="五safeerc20-的简化"&gt;&lt;a href="#%e4%ba%94safeerc20-%e7%9a%84%e7%ae%80%e5%8c%96" class="header-anchor"&gt;&lt;/a&gt;五、SafeERC20 的简化
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 4.x
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;SafeERC20&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;safeTransfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;SafeERC20&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;safeApprove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ⚠️ 已弃用
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 5.x
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;safeTransfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 用 using for
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;forceApprove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 替代 safeApprove
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;safeApprove&lt;/code&gt; 因为重入 + 兼容性问题被废除——&lt;strong&gt;&lt;code&gt;forceApprove&lt;/code&gt; 通过先 approve(0) 再 approve(amount) 解决&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="六proxy--upgradeability-的整理"&gt;&lt;a href="#%e5%85%adproxy--upgradeability-%e7%9a%84%e6%95%b4%e7%90%86" class="header-anchor"&gt;&lt;/a&gt;六、Proxy / Upgradeability 的整理
&lt;/h2&gt;&lt;h3 id="升级合约用-erc-1967-原生槽"&gt;&lt;a href="#%e5%8d%87%e7%ba%a7%e5%90%88%e7%ba%a6%e7%94%a8-erc-1967-%e5%8e%9f%e7%94%9f%e6%a7%bd" class="header-anchor"&gt;&lt;/a&gt;升级合约用 ERC-1967 原生槽
&lt;/h3&gt;&lt;p&gt;5.x 完全用 EIP-1967 标准槽（不再有自定义实现）——&lt;strong&gt;所有可升级合约都用同一套 storage slot&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="initializable-的-_disableinitializers"&gt;&lt;a href="#initializable-%e7%9a%84-_disableinitializers" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;Initializable&lt;/code&gt; 的 &lt;code&gt;_disableInitializers&lt;/code&gt;
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_disableInitializers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 防止 implementation 合约被攻击者初始化
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这是必须做的——&lt;strong&gt;否则 implementation 合约本身可能被外部直接 init&lt;/strong&gt;（Parity Wallet 2017 年就因为这个被 self-destruct，14 万 ETH 永久冻结）。5.x 的 &lt;code&gt;Initializable&lt;/code&gt; 加了更严格的检查。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="七新增模块"&gt;&lt;a href="#%e4%b8%83%e6%96%b0%e5%a2%9e%e6%a8%a1%e5%9d%97" class="header-anchor"&gt;&lt;/a&gt;七、新增模块
&lt;/h2&gt;&lt;h3 id="multicall"&gt;&lt;a href="#multicall" class="header-anchor"&gt;&lt;/a&gt;Multicall
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;contract&lt;/span&gt; &lt;span class="nc"&gt;MyContract&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Multicall&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 一笔交易里调多个本合约函数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;multicall&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encodeCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyContract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;func1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(...)),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encodeCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyContract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;func2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(...))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;节省 Gas、保证原子性&lt;/strong&gt;——很多 DeFi 协议自己实现 Multicall，5.x 提供了官方版本。&lt;/p&gt;
&lt;h3 id="nonces"&gt;&lt;a href="#nonces" class="header-anchor"&gt;&lt;/a&gt;Nonces
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;contract&lt;/span&gt; &lt;span class="nc"&gt;MyContract&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Nonces&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 防 replay 的 nonce 管理标准化
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_useNonce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;ERC-2612 permit、EIP-712 签名场景的标配。&lt;/p&gt;
&lt;h3 id="erc4337账户抽象"&gt;&lt;a href="#erc4337%e8%b4%a6%e6%88%b7%e6%8a%bd%e8%b1%a1" class="header-anchor"&gt;&lt;/a&gt;ERC4337（账户抽象）
&lt;/h3&gt;&lt;p&gt;5.x 提供基础组件，&lt;strong&gt;让你能更容易地实现 ERC-4337 智能账户&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="erc2771-forwarder"&gt;&lt;a href="#erc2771-forwarder" class="header-anchor"&gt;&lt;/a&gt;ERC2771 Forwarder
&lt;/h3&gt;&lt;p&gt;支持 meta-transaction（用户不付 Gas，relayer 代发）。&lt;/p&gt;
&lt;h3 id="erc-7201-命名空间存储namespaced-storage"&gt;&lt;a href="#erc-7201-%e5%91%bd%e5%90%8d%e7%a9%ba%e9%97%b4%e5%ad%98%e5%82%a8namespaced-storage" class="header-anchor"&gt;&lt;/a&gt;ERC-7201 命名空间存储（Namespaced Storage）
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;contracts-upgradeable&lt;/code&gt; 包里全面改用 &lt;a class="link" href="https://eips.ethereum.org/EIPS/eip-7201" target="_blank" rel="noopener"
 &gt;ERC-7201&lt;/a&gt; 命名空间存储布局，&lt;strong&gt;取代原来的 &lt;code&gt;__gap&lt;/code&gt; 模式&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 5.x 风格
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;/// @custom:storage-location erc7201:openzeppelin.storage.MyContract
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;MyContractStorage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="nb"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;每个合约的 storage 字段被收进一个独立 struct，slot 由 &lt;code&gt;keccak256(namespace) - 1&lt;/code&gt; 派生——&lt;strong&gt;升级加字段不再受顺序约束&lt;/strong&gt;，老项目从 &lt;code&gt;__gap&lt;/code&gt; 模式迁移过来要小心 storage layout 兼容。这是 5.x 在升级合约存储模型上最大的改动。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="八删除的东西"&gt;&lt;a href="#%e5%85%ab%e5%88%a0%e9%99%a4%e7%9a%84%e4%b8%9c%e8%a5%bf" class="header-anchor"&gt;&lt;/a&gt;八、删除的东西
&lt;/h2&gt;&lt;h3 id="1-一些低使用率的工具"&gt;&lt;a href="#1-%e4%b8%80%e4%ba%9b%e4%bd%8e%e4%bd%bf%e7%94%a8%e7%8e%87%e7%9a%84%e5%b7%a5%e5%85%b7" class="header-anchor"&gt;&lt;/a&gt;1. 一些低使用率的工具
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;MerkleProof&lt;/code&gt; 内部实现优化，但 API 兼容；&lt;code&gt;Address.isContract&lt;/code&gt; 删除（用 &lt;code&gt;code.length &amp;gt; 0&lt;/code&gt; 替代）。&lt;/p&gt;
&lt;h3 id="2-counters-库"&gt;&lt;a href="#2-counters-%e5%ba%93" class="header-anchor"&gt;&lt;/a&gt;2. &lt;code&gt;Counters&lt;/code&gt; 库
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 4.x
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;using&lt;/span&gt; &lt;span class="n"&gt;Counters&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;Counters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Counters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Counter&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 5.x
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;随着 Solidity 0.8+ 内置溢出检查，&lt;code&gt;Counters&lt;/code&gt; 已经没意义——&lt;strong&gt;直接用 uint256 + ++&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="3-addressfunctiondelegatecall"&gt;&lt;a href="#3-addressfunctiondelegatecall" class="header-anchor"&gt;&lt;/a&gt;3. &lt;code&gt;Address.functionDelegateCall&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;整体 &lt;code&gt;Address&lt;/code&gt; 库被简化——某些 helper 函数移除或重命名。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="九governor-的全面重构"&gt;&lt;a href="#%e4%b9%9dgovernor-%e7%9a%84%e5%85%a8%e9%9d%a2%e9%87%8d%e6%9e%84" class="header-anchor"&gt;&lt;/a&gt;九、Governor 的全面重构
&lt;/h2&gt;&lt;p&gt;DAO 治理的 &lt;code&gt;Governor&lt;/code&gt; 模块——4.x 的复杂度被 5.x 大幅简化：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 5.x
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;contract&lt;/span&gt; &lt;span class="nc"&gt;MyGovernor&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Governor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;GovernorSettings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;GovernorCountingSimple&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;GovernorVotes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;GovernorVotesQuorumFraction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;GovernorTimelockControl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;mixin 数量更精简、API 更一致——&lt;strong&gt;做 DAO 的项目必看&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="十安全增强"&gt;&lt;a href="#%e5%8d%81%e5%ae%89%e5%85%a8%e5%a2%9e%e5%bc%ba" class="header-anchor"&gt;&lt;/a&gt;十、安全增强
&lt;/h2&gt;&lt;h3 id="1-更严格的-reentrancy-检查"&gt;&lt;a href="#1-%e6%9b%b4%e4%b8%a5%e6%a0%bc%e7%9a%84-reentrancy-%e6%a3%80%e6%9f%a5" class="header-anchor"&gt;&lt;/a&gt;1. 更严格的 reentrancy 检查
&lt;/h3&gt;&lt;p&gt;5.0 的 &lt;code&gt;ReentrancyGuard&lt;/code&gt; 仍是普通 storage 实现（5.0 发布时 EIP-1153 transient storage 还未上链；EIP-1153 随 Cancun 升级在 2024-03 才上线主网）。&lt;strong&gt;5.1（2024-10）之后&lt;/strong&gt;新增了独立的 &lt;code&gt;ReentrancyGuardTransient&lt;/code&gt; 合约——基于 EIP-1153 transient storage，gas 显著降低（普通版热槽约 5000 gas，transient 版约 200 gas）。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 5.0+：普通版
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;@openzeppelin/contracts/utils/ReentrancyGuard.sol&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 5.1+：transient 版（更省 gas，但需 EVM 支持 EIP-1153）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="2-permit-标准化"&gt;&lt;a href="#2-permit-%e6%a0%87%e5%87%86%e5%8c%96" class="header-anchor"&gt;&lt;/a&gt;2. Permit 标准化
&lt;/h3&gt;&lt;p&gt;ERC-2612 (permit) 在 5.x 默认开启更严格的签名校验。&lt;/p&gt;
&lt;h3 id="3-改进的-ecdsa-验证"&gt;&lt;a href="#3-%e6%94%b9%e8%bf%9b%e7%9a%84-ecdsa-%e9%aa%8c%e8%af%81" class="header-anchor"&gt;&lt;/a&gt;3. 改进的 ECDSA 验证
&lt;/h3&gt;&lt;p&gt;签名相关的工具类（&lt;code&gt;ECDSA&lt;/code&gt;、&lt;code&gt;SignatureChecker&lt;/code&gt;）的 API 更清晰，&lt;strong&gt;避免 &lt;code&gt;recover&lt;/code&gt; 返回 0 地址不被察觉的隐患&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="十一升级实战-checklist"&gt;&lt;a href="#%e5%8d%81%e4%b8%80%e5%8d%87%e7%ba%a7%e5%ae%9e%e6%88%98-checklist" class="header-anchor"&gt;&lt;/a&gt;十一、升级实战 Checklist
&lt;/h2&gt;&lt;p&gt;老项目升 5.x 的清单：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Solidity 升到 0.8.20+&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;&lt;code&gt;Ownable&lt;/code&gt; 子合约改构造函数&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;&lt;code&gt;_setupRole&lt;/code&gt; → &lt;code&gt;_grantRole&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;&lt;code&gt;_beforeTokenTransfer&lt;/code&gt; / &lt;code&gt;_afterTokenTransfer&lt;/code&gt; → &lt;code&gt;_update&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;&lt;code&gt;safeApprove&lt;/code&gt; → &lt;code&gt;forceApprove&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;删除 &lt;code&gt;Counters&lt;/code&gt;，用 uint256 +1&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;删除 &lt;code&gt;Address.isContract&lt;/code&gt;，用 &lt;code&gt;addr.code.length &amp;gt; 0&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;可升级合约的 implementation 加 &lt;code&gt;_disableInitializers()&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;Governor 子合约重写&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;完整跑 audits / tests&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;工具：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 用 OpenZeppelin 的 upgrade tool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npx @openzeppelin/upgrades migrate
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;未必能自动改全——&lt;strong&gt;手工 review 仍然必须&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="十二新项目的最佳实践"&gt;&lt;a href="#%e5%8d%81%e4%ba%8c%e6%96%b0%e9%a1%b9%e7%9b%ae%e7%9a%84%e6%9c%80%e4%bd%b3%e5%ae%9e%e8%b7%b5" class="header-anchor"&gt;&lt;/a&gt;十二、新项目的最佳实践
&lt;/h2&gt;&lt;p&gt;如果你是从 0 起步——&lt;strong&gt;直接用 5.x&lt;/strong&gt;。几条建议：&lt;/p&gt;
&lt;h3 id="1-用-erc-1967-标准的可升级合约"&gt;&lt;a href="#1-%e7%94%a8-erc-1967-%e6%a0%87%e5%87%86%e7%9a%84%e5%8f%af%e5%8d%87%e7%ba%a7%e5%90%88%e7%ba%a6" class="header-anchor"&gt;&lt;/a&gt;1. 用 ERC-1967 标准的可升级合约
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;contract&lt;/span&gt; &lt;span class="nc"&gt;MyContract&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Initializable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OwnableUpgradeable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;/// @custom:oz-upgrades-unsafe-allow constructor
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_disableInitializers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// ⚠️ 必加：防止 implementation 合约被外部 init
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;initialOwner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;initializer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;__Ownable_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initialOwner&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="2-永远-_disableinitializers"&gt;&lt;a href="#2-%e6%b0%b8%e8%bf%9c-_disableinitializers" class="header-anchor"&gt;&lt;/a&gt;2. 永远 &lt;code&gt;_disableInitializers()&lt;/code&gt;
&lt;/h3&gt;&lt;h3 id="3-用-using-safeerc20-for-ierc20"&gt;&lt;a href="#3-%e7%94%a8-using-safeerc20-for-ierc20" class="header-anchor"&gt;&lt;/a&gt;3. 用 &lt;code&gt;using SafeERC20 for IERC20&lt;/code&gt;
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;using&lt;/span&gt; &lt;span class="n"&gt;SafeERC20&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;IERC20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;safeTransfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="4-reentrancyguard-默认启用"&gt;&lt;a href="#4-reentrancyguard-%e9%bb%98%e8%ae%a4%e5%90%af%e7%94%a8" class="header-anchor"&gt;&lt;/a&gt;4. ReentrancyGuard 默认启用
&lt;/h3&gt;&lt;p&gt;任何接收/转账函数加 &lt;code&gt;nonReentrant&lt;/code&gt;——&lt;strong&gt;5.1+ 优先用 &lt;code&gt;ReentrancyGuardTransient&lt;/code&gt;（transient storage 版，每次开销约 200 gas），5.0 用普通版（gas 量级与 4.x 同）&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="5-关注-eip-712-签名"&gt;&lt;a href="#5-%e5%85%b3%e6%b3%a8-eip-712-%e7%ad%be%e5%90%8d" class="header-anchor"&gt;&lt;/a&gt;5. 关注 EIP-712 签名
&lt;/h3&gt;&lt;p&gt;权限调用 / 用户授权 / meta-tx——签名相关都用 OpenZeppelin 的 EIP-712 实现，别自己造。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="十三为什么这次升级值得"&gt;&lt;a href="#%e5%8d%81%e4%b8%89%e4%b8%ba%e4%bb%80%e4%b9%88%e8%bf%99%e6%ac%a1%e5%8d%87%e7%ba%a7%e5%80%bc%e5%be%97" class="header-anchor"&gt;&lt;/a&gt;十三、为什么这次升级值得
&lt;/h2&gt;&lt;p&gt;OpenZeppelin 4.x 系列已经是事实标准——&lt;strong&gt;5.x 升级带来的不是惊艳新功能，而是『更精简、更安全、更现代』&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;具体收益：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;代码量减少&lt;/strong&gt;：单个 &lt;code&gt;_update&lt;/code&gt; 替代两个 hook&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;更小 Gas&lt;/strong&gt;：transient storage、内置优化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;避免历史坑&lt;/strong&gt;：&lt;code&gt;forceApprove&lt;/code&gt;、&lt;code&gt;_disableInitializers&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DAO 治理更易用&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;新场景支持&lt;/strong&gt;：ERC-4337、Multicall&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;p&gt;把全文压一句：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;OpenZeppelin 5.0 不是『可选的小升级』——它是 Solidity 0.8.20+ 时代的事实标准，新项目应该直接采用，老项目应该规划迁移。&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;工程要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Ownable&lt;/code&gt; 构造函数必传 owner&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ERC20 hook 改为 &lt;code&gt;_update&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;safeApprove&lt;/code&gt; 用 &lt;code&gt;forceApprove&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Counters 删除，直接 uint256&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可升级合约必加 &lt;code&gt;_disableInitializers&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;永远去 OpenZeppelin GitHub 看 release notes&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;智能合约的&amp;quot;代码即资金&amp;quot;特性意味着——&lt;strong&gt;用过期的库 = 用过期的安全保护&lt;/strong&gt;。OpenZeppelin 5.x 是当下应该用的版本。&lt;/p&gt;</description></item><item><title>不可变的合约如何「升级」？聊聊 Solidity 合约升级模式</title><link>https://www.lingcoder.com/p/solidity-contract-upgrade-patterns/</link><pubDate>Mon, 02 Sep 2024 20:00:00 +0800</pubDate><guid>https://www.lingcoder.com/p/solidity-contract-upgrade-patterns/</guid><description>&lt;img src="https://www.lingcoder.com/p/solidity-contract-upgrade-patterns/cover.svg" alt="Featured image of post 不可变的合约如何「升级」？聊聊 Solidity 合约升级模式" /&gt;&lt;h2 id="一个让所有以太坊新手都困惑的悖论"&gt;&lt;a href="#%e4%b8%80%e4%b8%aa%e8%ae%a9%e6%89%80%e6%9c%89%e4%bb%a5%e5%a4%aa%e5%9d%8a%e6%96%b0%e6%89%8b%e9%83%bd%e5%9b%b0%e6%83%91%e7%9a%84%e6%82%96%e8%ae%ba" class="header-anchor"&gt;&lt;/a&gt;一个让所有以太坊新手都困惑的悖论
&lt;/h2&gt;&lt;p&gt;智能合约最被反复强调的特性，是 &lt;strong&gt;不可变（immutable）&lt;/strong&gt;——代码部署上链之后，一行都不能改。&lt;/p&gt;
&lt;p&gt;但你只要在生产里跑过几个月就会发现一个现实：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;代码会有 bug&lt;/li&gt;
&lt;li&gt;业务会有新需求&lt;/li&gt;
&lt;li&gt;协议会被攻击，要打补丁&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这两件事看起来根本是矛盾的——&lt;strong&gt;承诺不可变，又必须能改&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;整个&amp;quot;合约升级&amp;quot;这个话题，本质上就是在回答：&lt;strong&gt;怎么在不破坏不可变承诺的前提下，让合约的行为可以演进？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;下面我们从最朴素的方案开始，一路讲到现在主流的代理模式、UUPS、Beacon、Diamond，把这些方案的来龙去脉、踩坑点和适用边界一次性讲清楚。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一最朴素的方案合约迁移contract-migration"&gt;&lt;a href="#%e4%b8%80%e6%9c%80%e6%9c%b4%e7%b4%a0%e7%9a%84%e6%96%b9%e6%a1%88%e5%90%88%e7%ba%a6%e8%bf%81%e7%a7%bbcontract-migration" class="header-anchor"&gt;&lt;/a&gt;一、最朴素的方案：合约迁移（Contract Migration）
&lt;/h2&gt;&lt;p&gt;最直观的思路：既然合约不能改，那就&lt;strong&gt;部署一份新的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;流程大致是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;部署 &lt;code&gt;TokenV2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;写一个迁移脚本，把 &lt;code&gt;TokenV1&lt;/code&gt; 上每个用户的余额读出来，写进 &lt;code&gt;TokenV2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;通知所有依赖方（前端、其他合约、CEX、用户）：地址换了，请用新的&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这条路在小协议里偶尔还会用到，但作为通用方案，它的痛点很硬：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;地址变了&lt;/strong&gt;——所有依赖你的合约和应用都要改地址。DeFi 里这意味着流动性池要重建、用户授权要重做，几乎等于一次&amp;quot;硬分叉&amp;quot;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态迁移成本高&lt;/strong&gt;——用户多的时候，光迁移就要几十万美元 Gas，且过程中可能有人在动账。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;信任成本极高&lt;/strong&gt;——用户怎么相信新合约里的余额没被篡改？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以业界很快意识到：迁移不是&amp;quot;升级&amp;quot;，是&amp;quot;换房&amp;quot;。真正的升级，应该让&lt;strong&gt;地址不变、状态延续，只换&amp;quot;大脑&amp;quot;&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二核心技巧delegatecall-与代理模式"&gt;&lt;a href="#%e4%ba%8c%e6%a0%b8%e5%bf%83%e6%8a%80%e5%b7%a7delegatecall-%e4%b8%8e%e4%bb%a3%e7%90%86%e6%a8%a1%e5%bc%8f" class="header-anchor"&gt;&lt;/a&gt;二、核心技巧：&lt;code&gt;delegatecall&lt;/code&gt; 与代理模式
&lt;/h2&gt;&lt;p&gt;要做到&amp;quot;地址不变换大脑&amp;quot;，离不开 EVM 提供的一个特殊指令——&lt;strong&gt;&lt;code&gt;delegatecall&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="一句话理解-delegatecall"&gt;&lt;a href="#%e4%b8%80%e5%8f%a5%e8%af%9d%e7%90%86%e8%a7%a3-delegatecall" class="header-anchor"&gt;&lt;/a&gt;一句话理解 &lt;code&gt;delegatecall&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;普通调用 &lt;code&gt;call&lt;/code&gt;：&lt;strong&gt;用别人的代码、操作别人的存储&lt;/strong&gt;。
&lt;code&gt;delegatecall&lt;/code&gt;：&lt;strong&gt;用别人的代码、操作自己的存储&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;也就是说，合约 A 通过 &lt;code&gt;delegatecall&lt;/code&gt; 调合约 B 的某个函数时，执行的是 B 的字节码，但 &lt;code&gt;msg.sender&lt;/code&gt;、&lt;code&gt;msg.value&lt;/code&gt;、&lt;strong&gt;所有存储读写都发生在 A 上&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这就给了我们一个大胆的想法——&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;让一个&lt;strong&gt;只负责存数据&lt;/strong&gt;的合约（Proxy），通过 &lt;code&gt;delegatecall&lt;/code&gt; 把所有调用都转发给另一个&lt;strong&gt;只负责跑逻辑&lt;/strong&gt;的合约（Logic）。&lt;/p&gt;
&lt;p&gt;升级时只需要把 Proxy 指向的 Logic 地址换掉，&lt;strong&gt;Proxy 的地址和存储完全不变&lt;/strong&gt;。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="关系图"&gt;&lt;a href="#%e5%85%b3%e7%b3%bb%e5%9b%be" class="header-anchor"&gt;&lt;/a&gt;关系图
&lt;/h3&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 User([用户 / DApp])
 Proxy["Proxy 合约&lt;br/&gt;(地址不变 + 存储)"]
 LogicV1["Logic V1&lt;br/&gt;(纯代码)"]
 LogicV2["Logic V2&lt;br/&gt;(升级后)"]

 User -- 调用 --&gt; Proxy
 Proxy -- delegatecall --&gt; LogicV1
 Proxy -. 升级后改指向 .-&gt; LogicV2&lt;/pre&gt;&lt;h3 id="一个最小可运行的代理"&gt;&lt;a href="#%e4%b8%80%e4%b8%aa%e6%9c%80%e5%b0%8f%e5%8f%af%e8%bf%90%e8%a1%8c%e7%9a%84%e4%bb%a3%e7%90%86" class="header-anchor"&gt;&lt;/a&gt;一个最小可运行的代理
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;contract&lt;/span&gt; &lt;span class="nc"&gt;MinimalProxy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 当前指向的逻辑合约
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;_impl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_impl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;admin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;upgrade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;_newImpl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;sender&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;not admin&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_newImpl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="k"&gt;payable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;impl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assembly&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;calldatacopy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;calldatasize&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="ow"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nf"&gt;delegatecall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;gas&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;impl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;calldatasize&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;returndatacopy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;returndatasize&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;revert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;returndatasize&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;returndatasize&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;fallback&lt;/code&gt; 把任何调用都 &lt;code&gt;delegatecall&lt;/code&gt; 到 &lt;code&gt;implementation&lt;/code&gt;。换 &lt;code&gt;implementation&lt;/code&gt; 就等于升级。&lt;/p&gt;
&lt;p&gt;看起来挺优雅，但魔鬼藏在细节里——&lt;strong&gt;存储布局&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="三升级最大的坑存储布局冲突"&gt;&lt;a href="#%e4%b8%89%e5%8d%87%e7%ba%a7%e6%9c%80%e5%a4%a7%e7%9a%84%e5%9d%91%e5%ad%98%e5%82%a8%e5%b8%83%e5%b1%80%e5%86%b2%e7%aa%81" class="header-anchor"&gt;&lt;/a&gt;三、升级最大的坑：存储布局冲突
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;delegatecall&lt;/code&gt; 操作的是 &lt;strong&gt;Proxy 的存储&lt;/strong&gt;，但&lt;strong&gt;用的是 Logic 的代码&lt;/strong&gt;。这就要求两件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Logic 合约里读写哪个存储槽，必须和 Proxy 里实际的存储槽对得上&lt;/li&gt;
&lt;li&gt;升级前后两版 Logic 之间，存储槽不能错位&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;举个例子。假设 Logic V1 是这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;contract&lt;/span&gt; &lt;span class="nc"&gt;LogicV1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// slot 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;totalSupply&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// slot 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;某天我们想升级，写了个看起来合情合理的 V2：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;contract&lt;/span&gt; &lt;span class="nc"&gt;LogicV2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;totalSupply&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// slot 0 ← 和 V1 的 owner 撞了！
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// slot 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;newField&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// slot 2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;升级完，原来 &lt;code&gt;owner&lt;/code&gt; 的位置被当成了 &lt;code&gt;totalSupply&lt;/code&gt;，瞬间把一个地址解读成天文数字的代币总量——&lt;strong&gt;协议直接报废&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="三条铁律"&gt;&lt;a href="#%e4%b8%89%e6%9d%a1%e9%93%81%e5%be%8b" class="header-anchor"&gt;&lt;/a&gt;三条铁律
&lt;/h3&gt;&lt;ol&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;不能改变量类型&lt;/strong&gt;（包括 &lt;code&gt;uint256&lt;/code&gt; ↔ &lt;code&gt;int256&lt;/code&gt;、定长数组长度等）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;新版本想加字段？&lt;strong&gt;只能在末尾追加&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="storage-gap-习惯openzeppelin-4x-时代的做法"&gt;&lt;a href="#storage-gap-%e4%b9%a0%e6%83%afopenzeppelin-4x-%e6%97%b6%e4%bb%a3%e7%9a%84%e5%81%9a%e6%b3%95" class="header-anchor"&gt;&lt;/a&gt;Storage Gap 习惯（OpenZeppelin 4.x 时代的做法）
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;时效性更新&lt;/strong&gt;：本节描述的 &lt;code&gt;__gap&lt;/code&gt; 模式是 OpenZeppelin 4.x 的标准做法。&lt;strong&gt;OpenZeppelin 5.0（2023-10）起的 &lt;code&gt;contracts-upgradeable&lt;/code&gt; 包已经改用 &lt;a class="link" href="https://eips.ethereum.org/EIPS/eip-7201" target="_blank" rel="noopener"
 &gt;ERC-7201 命名空间存储&lt;/a&gt;（Namespaced Storage Layout）&lt;/strong&gt;——每个合约的 storage 字段被收进一个独立 struct，slot 从 &lt;code&gt;keccak256(namespace) - 1&lt;/code&gt; 派生，&lt;strong&gt;升级加字段不再受顺序约束，也不再需要预留 &lt;code&gt;__gap&lt;/code&gt;&lt;/strong&gt;。新项目应该直接用 ERC-7201；老项目从 &lt;code&gt;__gap&lt;/code&gt; 模式迁移到 ERC-7201 时要小心 storage layout 兼容。下面这段 &lt;code&gt;__gap&lt;/code&gt; 写法仍然适用于 4.x 的基类继承场景（与 4.x 升级链路向后兼容）。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;为了给未来&amp;quot;末尾追加&amp;quot;留余地，可继承的基类里通常会预留一段空槽：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;contract&lt;/span&gt; &lt;span class="nc"&gt;MyUpgradeable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;totalSupply&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ... 业务字段
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;__gap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 给未来升级留 50 个槽
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这是 OpenZeppelin 4.x 的标准做法。一旦未来要在基类加字段，就从 &lt;code&gt;__gap&lt;/code&gt; 里&amp;quot;借&amp;quot;——既不破坏子类的存储布局，又不需要 fork 一份父类。&lt;/p&gt;
&lt;p&gt;ERC-7201 的等价写法长这样（5.0+）：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;contract&lt;/span&gt; &lt;span class="nc"&gt;MyUpgradeable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;/// @custom:storage-location erc7201:myproject.storage.MyUpgradeable
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;MyStorage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="n"&gt;totalSupply&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ... 业务字段
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// keccak256(abi.encode(uint256(keccak256(&amp;#34;myproject.storage.MyUpgradeable&amp;#34;)) - 1)) &amp;amp; ~bytes32(uint256(0xff))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;bytes32&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;constant&lt;/span&gt; &lt;span class="n"&gt;STORAGE_SLOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;...;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_s&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;pure&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyStorage&lt;/span&gt; &lt;span class="k"&gt;storage&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;bytes32&lt;/span&gt; &lt;span class="n"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;STORAGE_SLOT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assembly&lt;/span&gt; { &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;slot&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;每个合约把自己的 storage 锁进独立命名空间——升级时加字段、调顺序、改基类继承都不会再撞槽。这是 5.x 在升级模型上最关键的一步。&lt;/p&gt;
&lt;h3 id="升级前一定要做-storage-layout-校验"&gt;&lt;a href="#%e5%8d%87%e7%ba%a7%e5%89%8d%e4%b8%80%e5%ae%9a%e8%a6%81%e5%81%9a-storage-layout-%e6%a0%a1%e9%aa%8c" class="header-anchor"&gt;&lt;/a&gt;升级前一定要做 storage layout 校验
&lt;/h3&gt;&lt;p&gt;存储布局错位的检查靠人肉是不现实的——升级链路必须有自动化校验把这个关。具体工具按你用的栈选：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hardhat 栈&lt;/strong&gt;：&lt;a class="link" href="https://github.com/OpenZeppelin/openzeppelin-upgrades" target="_blank" rel="noopener"
 &gt;OpenZeppelin Upgrades Plugin&lt;/a&gt;（&lt;code&gt;@openzeppelin/hardhat-upgrades&lt;/code&gt;）——升级前自动比对两版合约的存储布局，不兼容直接报错挡住。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Foundry 栈&lt;/strong&gt;：&lt;a class="link" href="https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades" target="_blank" rel="noopener"
 &gt;openzeppelin-foundry-upgrades&lt;/a&gt;，或者直接 &lt;code&gt;forge inspect &amp;lt;Contract&amp;gt; storageLayout&lt;/code&gt; 把新旧两版的 storage layout 输出成 JSON，在 CI 里做 diff。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;更通用&lt;/strong&gt;：&lt;a class="link" href="https://github.com/crytic/slither" target="_blank" rel="noopener"
 &gt;Slither&lt;/a&gt; 的 &lt;code&gt;slither-check-upgradeability&lt;/code&gt; 也能做静态布局比对。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;形式不重要——&lt;strong&gt;一定要有一道自动检查&lt;/strong&gt;。生产合约靠肉眼 review storage layout 早晚出事。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="四三种主流代理标准"&gt;&lt;a href="#%e5%9b%9b%e4%b8%89%e7%a7%8d%e4%b8%bb%e6%b5%81%e4%bb%a3%e7%90%86%e6%a0%87%e5%87%86" class="header-anchor"&gt;&lt;/a&gt;四、三种主流代理标准
&lt;/h2&gt;&lt;p&gt;代理模式发展到今天，演化出了三种主流形态。&lt;/p&gt;
&lt;h3 id="1-transparent-proxy透明代理"&gt;&lt;a href="#1-transparent-proxy%e9%80%8f%e6%98%8e%e4%bb%a3%e7%90%86" class="header-anchor"&gt;&lt;/a&gt;1. Transparent Proxy（透明代理）
&lt;/h3&gt;&lt;p&gt;OpenZeppelin 早期推荐。它的核心问题是解决一个微妙的冲突——&lt;/p&gt;
&lt;p&gt;如果 Proxy 自己有个函数叫 &lt;code&gt;upgrade()&lt;/code&gt;，Logic 里也有个函数叫 &lt;code&gt;upgrade()&lt;/code&gt;，用户调进来时到底是哪个执行？&lt;/p&gt;
&lt;p&gt;Transparent Proxy 的解法是&lt;strong&gt;按调用者区分&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;管理员&lt;/strong&gt;调用 → 永远走 Proxy 自己的管理函数&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;普通用户&lt;/strong&gt;调用 → 永远走 &lt;code&gt;delegatecall&lt;/code&gt; 到 Logic&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 Admin([管理员]) --&gt;|管理调用| ProxyAdmin["Proxy&lt;br/&gt;(管理逻辑)"]
 User([普通用户]) --&gt;|业务调用| ProxyDelegate["Proxy&lt;br/&gt;(delegatecall)"]
 ProxyDelegate --&gt; Logic["Logic 合约"]&lt;/pre&gt;&lt;p&gt;简单可靠，但代价是 &lt;strong&gt;Proxy 里要存管理员判断逻辑，每次调用都多一次条件判断和 SLOAD，Gas 偏贵&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="2-uupserc-1822oz-现在的默认推荐"&gt;&lt;a href="#2-uupserc-1822oz-%e7%8e%b0%e5%9c%a8%e7%9a%84%e9%bb%98%e8%ae%a4%e6%8e%a8%e8%8d%90" class="header-anchor"&gt;&lt;/a&gt;2. UUPS（ERC-1822，OZ 现在的默认推荐）
&lt;/h3&gt;&lt;p&gt;UUPS 的核心反转是——&lt;strong&gt;把升级逻辑搬进 Logic 合约&lt;/strong&gt;。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;contract&lt;/span&gt; &lt;span class="nc"&gt;LogicUUPS&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;upgradeTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;newImpl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;sender&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;not owner&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 通过 delegatecall 改的是 Proxy 自己的 implementation 槽
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assembly&lt;/span&gt; { &lt;span class="nf"&gt;sstore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_IMPL_SLOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newImpl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ... 其他业务函数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这样一来：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Proxy 极度精简&lt;/strong&gt;，只有一个 fallback，Gas 最省&lt;/li&gt;
&lt;li&gt;升级权限由 Logic 控制——这意味着 &lt;strong&gt;如果你升级到一个忘了写 &lt;code&gt;upgradeTo&lt;/code&gt; 的新 Logic，合约就永远没法再升级了&lt;/strong&gt;（一种&amp;quot;自爆开关&amp;quot;）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;OpenZeppelin 现在更推荐 UUPS，但前提是开发者必须严格遵守&amp;quot;新版本永远要继承 &lt;code&gt;UUPSUpgradeable&lt;/code&gt;&amp;ldquo;的规范，否则可能误锁死。&lt;/p&gt;
&lt;h3 id="3-beacon-proxy信标代理"&gt;&lt;a href="#3-beacon-proxy%e4%bf%a1%e6%a0%87%e4%bb%a3%e7%90%86" class="header-anchor"&gt;&lt;/a&gt;3. Beacon Proxy（信标代理）
&lt;/h3&gt;&lt;p&gt;前两种都是&amp;quot;一个 Proxy 对应一个 Logic&amp;rdquo;。如果你有 &lt;strong&gt;几百上千个同种合约&lt;/strong&gt;（比如 Uniswap V3 工厂创建的众多池子），每次升级要逐一调用，成本难以承受。&lt;/p&gt;
&lt;p&gt;Beacon Proxy 抽象出一个&lt;strong&gt;信标合约&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 Beacon["Beacon&lt;br/&gt;(只存当前 Logic 地址)"]
 Logic["Logic 合约"]

 P1["Pool Proxy 1"] -.读地址.-&gt; Beacon
 P2["Pool Proxy 2"] -.读地址.-&gt; Beacon
 P3["Pool Proxy ..."] -.读地址.-&gt; Beacon

 P1 -.delegatecall.-&gt; Logic
 P2 -.delegatecall.-&gt; Logic
 P3 -.delegatecall.-&gt; Logic

 Beacon -. 指向 .-&gt; Logic&lt;/pre&gt;&lt;p&gt;所有 Proxy 都从 Beacon 读&amp;quot;当前 Logic 地址&amp;quot;。&lt;strong&gt;升级时只改 Beacon 一处，所有 Proxy 同时生效&lt;/strong&gt;。代价是每次调用多一次跨合约读地址的 Gas。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: left"&gt;方案&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Gas 开销&lt;/th&gt;
 &lt;th style="text-align: left"&gt;升级权限位置&lt;/th&gt;
 &lt;th style="text-align: left"&gt;适用场景&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;Transparent Proxy&lt;/td&gt;
 &lt;td style="text-align: left"&gt;较高&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Proxy&lt;/td&gt;
 &lt;td style="text-align: left"&gt;单实例、追求稳定保守&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;UUPS&lt;/td&gt;
 &lt;td style="text-align: left"&gt;最低&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Logic&lt;/td&gt;
 &lt;td style="text-align: left"&gt;单实例、追求性能（OZ 默认推荐）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;Beacon Proxy&lt;/td&gt;
 &lt;td style="text-align: left"&gt;中等&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Beacon&lt;/td&gt;
 &lt;td style="text-align: left"&gt;多实例、需要批量同步升级&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="五更激进的方案diamondeip-2535"&gt;&lt;a href="#%e4%ba%94%e6%9b%b4%e6%bf%80%e8%bf%9b%e7%9a%84%e6%96%b9%e6%a1%88diamondeip-2535" class="header-anchor"&gt;&lt;/a&gt;五、更激进的方案：Diamond（EIP-2535）
&lt;/h2&gt;&lt;p&gt;EVM 对单个合约的字节码大小有 &lt;strong&gt;24KB 的硬上限&lt;/strong&gt;（EIP-170）。当协议复杂到一个 Logic 合约塞不下时，前面那些&amp;quot;单 Logic&amp;quot;的代理模式就不够用了。&lt;/p&gt;
&lt;p&gt;Diamond 模式的思路是——&lt;strong&gt;一个 Proxy（Diamond），多个 Logic（Facet）&lt;/strong&gt;，按函数选择器路由。&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TB
 User([用户调用])
 Diamond["Diamond Proxy&lt;br/&gt;(查路由表 + delegatecall)"]
 F1["Facet A&lt;br/&gt;转账逻辑"]
 F2["Facet B&lt;br/&gt;治理逻辑"]
 F3["Facet C&lt;br/&gt;奖励逻辑"]
 F4["Facet ...&lt;br/&gt;(可动态增删)"]

 User --&gt; Diamond
 Diamond -- delegatecall --&gt; F1
 Diamond -- delegatecall --&gt; F2
 Diamond -- delegatecall --&gt; F3
 Diamond -- delegatecall --&gt; F4&lt;/pre&gt;&lt;p&gt;每个 Facet 只实现一部分函数，Diamond 内部维护一张 &lt;code&gt;selector → facet address&lt;/code&gt; 的路由表，调用进来时查表然后 &lt;code&gt;delegatecall&lt;/code&gt;。升级时可以&lt;strong&gt;按 facet 增、删、换&lt;/strong&gt;，粒度比单 Logic 细得多。&lt;/p&gt;
&lt;p&gt;代价是复杂度陡升——存储布局要按&amp;quot;Diamond Storage&amp;quot;的命名空间约定写、调试链路更长、对应工具链不如 OZ 主流模式成熟。&lt;strong&gt;不是协议规模真的超过单合约能力，不要轻易上 Diamond&lt;/strong&gt;（Aavegotchi 是公认上得最成功的案例之一）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="六不能忽略的工程细节"&gt;&lt;a href="#%e5%85%ad%e4%b8%8d%e8%83%bd%e5%bf%bd%e7%95%a5%e7%9a%84%e5%b7%a5%e7%a8%8b%e7%bb%86%e8%8a%82" class="header-anchor"&gt;&lt;/a&gt;六、不能忽略的工程细节
&lt;/h2&gt;&lt;h3 id="1-没有-constructor只有-initializer"&gt;&lt;a href="#1-%e6%b2%a1%e6%9c%89-constructor%e5%8f%aa%e6%9c%89-initializer" class="header-anchor"&gt;&lt;/a&gt;1. 没有 constructor，只有 initializer
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;constructor&lt;/code&gt; 在合约部署时执行，&lt;strong&gt;只对 Logic 自己的存储生效，不会写到 Proxy 的存储&lt;/strong&gt;。所以可升级合约的初始化必须放在一个普通函数里，靠&lt;strong&gt;只能调用一次&lt;/strong&gt;的修饰符保护：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;contract&lt;/span&gt; &lt;span class="nc"&gt;MyLogic&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;initialized&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;_owner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;initialized&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;already initialized&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;initialized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_owner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;OpenZeppelin 提供了 &lt;code&gt;Initializable&lt;/code&gt; 基类，标准做法是用 &lt;code&gt;initializer&lt;/code&gt; 修饰符。&lt;/p&gt;
&lt;h3 id="2-部署后立刻调用-initializer"&gt;&lt;a href="#2-%e9%83%a8%e7%bd%b2%e5%90%8e%e7%ab%8b%e5%88%bb%e8%b0%83%e7%94%a8-initializer" class="header-anchor"&gt;&lt;/a&gt;2. 部署后立刻调用 initializer
&lt;/h3&gt;&lt;p&gt;如果部署完 Logic 不立刻初始化，&lt;strong&gt;任何人都可以抢先调一次 &lt;code&gt;initialize&lt;/code&gt; 把自己设成 owner&lt;/strong&gt;。这种漏洞历史上发生过多次。脚本部署 Proxy 时要用 &lt;code&gt;atomicallyDeployAndInitialize&lt;/code&gt; 之类的封装，把&amp;quot;部署 + 初始化&amp;quot;放进同一个交易。&lt;/p&gt;
&lt;h3 id="3-升级权限--协议生死开关"&gt;&lt;a href="#3-%e5%8d%87%e7%ba%a7%e6%9d%83%e9%99%90--%e5%8d%8f%e8%ae%ae%e7%94%9f%e6%ad%bb%e5%bc%80%e5%85%b3" class="header-anchor"&gt;&lt;/a&gt;3. 升级权限 = 协议生死开关
&lt;/h3&gt;&lt;p&gt;控制 &lt;code&gt;upgrade&lt;/code&gt; 的私钥，等于&lt;strong&gt;握有协议的全部资金权&lt;/strong&gt;。任何成熟协议都会用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;多签&lt;/strong&gt;：避免单点泄密&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Timelock&lt;/strong&gt;：提案到生效之间留出延迟（通常 24~72 小时），让用户有撤资时间&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DAO 治理投票&lt;/strong&gt;：进一步去中心化，把升级决策交给代币持有者&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你看到一个号称&amp;quot;去中心化&amp;quot;的协议，升级权在一把单签钥匙上——&lt;strong&gt;它在技术上和中心化产品没有任何区别&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="七可升级的代价安全与去中心化的张力"&gt;&lt;a href="#%e4%b8%83%e5%8f%af%e5%8d%87%e7%ba%a7%e7%9a%84%e4%bb%a3%e4%bb%b7%e5%ae%89%e5%85%a8%e4%b8%8e%e5%8e%bb%e4%b8%ad%e5%bf%83%e5%8c%96%e7%9a%84%e5%bc%a0%e5%8a%9b" class="header-anchor"&gt;&lt;/a&gt;七、可升级的代价：安全与去中心化的张力
&lt;/h2&gt;&lt;p&gt;可升级合约的存在，本身就违反了&amp;quot;不可变&amp;quot;的初心。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;管理员可以悄悄换掉 Logic，把所有用户的资金转走&lt;/strong&gt;——这是真实发生过的攻击。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;审计的难度被放大&lt;/strong&gt;：用户审计了今天的代码，明天升级后等于审了个寂寞。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;形式化验证、不可变指标都失效&lt;/strong&gt;：协议的安全保证从&amp;quot;代码&amp;quot;层退化为&amp;quot;运营方信誉 + 治理流程&amp;quot;层。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以业界有一种相反的声音：&lt;strong&gt;真正成熟的协议应当逐步放弃可升级性&lt;/strong&gt;。Uniswap V2/V3 的核心合约就是不可升级的——它的迭代靠&amp;quot;部署新协议、用户自愿迁移&amp;quot;完成，代价高，但换来了最强的信任。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;作为协议设计者，你需要问自己：&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;这个合约的升级权，是不是协议安全的天花板？&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;如果答案是&amp;quot;是&amp;quot;，那就要尽一切努力收紧它——多签、Timelock、治理、最终弃管。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;作为协议用户，你也要学会问：&lt;/strong&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;这个合约能升级吗？升级权在谁手里？有 Timelock 吗？&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;回答不出来的协议，本质上就是一个许可型应用，无论它前端多花哨。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;p&gt;把这一整圈讲下来，回到最初那个悖论——&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;智能合约的不可变是承诺，但现实的演进是必需。&lt;strong&gt;升级模式&lt;/strong&gt;就是在两者之间做工程妥协的产物。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;四种主流方案的本质：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;合约迁移&lt;/strong&gt;：放弃地址不变，老老实实换房&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transparent Proxy / UUPS&lt;/strong&gt;：地址不变，靠 &lt;code&gt;delegatecall&lt;/code&gt; 把存储和代码解耦&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Beacon Proxy&lt;/strong&gt;：多个 Proxy 共享一个 Logic 指针，批量升级&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Diamond&lt;/strong&gt;：突破合约大小上限，按 facet 灵活增删&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而无论用哪种方案，&lt;strong&gt;真正的难点从来不是合约怎么写，而是升级权怎么管&lt;/strong&gt;。技术层面可升级是简单的，治理层面让用户敢用一个可升级的合约，才是更难、也更重要的命题。&lt;/p&gt;
&lt;p&gt;一句话收尾：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;可升级是工程能力，不可升级是产品承诺，怎么在两者之间找平衡，是每一个协议设计者要回答的问题。&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;</description></item><item><title>NFT 标准选型与场景实践</title><link>https://www.lingcoder.com/p/nft-standards-comparison/</link><pubDate>Thu, 23 Jun 2022 16:00:00 +0800</pubDate><guid>https://www.lingcoder.com/p/nft-standards-comparison/</guid><description>&lt;img src="https://www.lingcoder.com/p/nft-standards-comparison/cover.svg" alt="Featured image of post NFT 标准选型与场景实践" /&gt;&lt;h2 id="两个标准都叫nft但它们解决的问题不同"&gt;&lt;a href="#%e4%b8%a4%e4%b8%aa%e6%a0%87%e5%87%86%e9%83%bd%e5%8f%abnft%e4%bd%86%e5%ae%83%e4%bb%ac%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98%e4%b8%8d%e5%90%8c" class="header-anchor"&gt;&lt;/a&gt;两个标准都叫&amp;quot;NFT&amp;quot;，但它们解决的问题不同
&lt;/h2&gt;&lt;p&gt;新人接触 NFT 时常被这两个标准的并存搞晕：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ERC-721&lt;/strong&gt;：CryptoPunks、Bored Ape、CryptoKitties 都是这个标准&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ERC-1155&lt;/strong&gt;：Decentraland 道具、Enjin 卡牌、OpenSea 创作工具&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它们都是 NFT，但&lt;strong&gt;底层模型完全不同&lt;/strong&gt;：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;ERC-721 一个合约&lt;strong&gt;管理一系列独一无二的 token&lt;/strong&gt;，每个 tokenId 唯一。&lt;/p&gt;
&lt;p&gt;ERC-1155 一个合约&lt;strong&gt;管理多种 token&lt;/strong&gt;，&lt;strong&gt;每种 token 可以有多个&lt;/strong&gt;——既可以是 NFT，也可以是 SemiFungible，甚至 Fungible。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;理解这两个标准的边界，决定了你写 NFT 项目时该选哪个、为什么选。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一erc-721经典的单一-nft"&gt;&lt;a href="#%e4%b8%80erc-721%e7%bb%8f%e5%85%b8%e7%9a%84%e5%8d%95%e4%b8%80-nft" class="header-anchor"&gt;&lt;/a&gt;一、ERC-721：经典的&amp;quot;单一 NFT&amp;quot;
&lt;/h2&gt;&lt;p&gt;ERC-721 由 Dieter Shirley、William Entriken 等人于 2018 年提出（EIP-721）。核心理念——&lt;strong&gt;每个 token 唯一&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IERC721&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ownerOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="n"&gt;tokenId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="k"&gt;view&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;balanceOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="k"&gt;view&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint256&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;transferFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="n"&gt;tokenId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;approve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="n"&gt;tokenId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ... 其他
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;每个 tokenId 对应&lt;strong&gt;一个独一无二的资产&lt;/strong&gt;。一只 BAYC 猴子、一张 CryptoKitty——对应一个 tokenId。&lt;/p&gt;
&lt;h3 id="数据结构"&gt;&lt;a href="#%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84" class="header-anchor"&gt;&lt;/a&gt;数据结构
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;_owners&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// tokenId → owner
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;_balances&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// owner → 持有数量
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="典型操作"&gt;&lt;a href="#%e5%85%b8%e5%9e%8b%e6%93%8d%e4%bd%9c" class="header-anchor"&gt;&lt;/a&gt;典型操作
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 铸造一个 token
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;_owners&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tokenId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;_balances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 转账
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;_owners&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tokenId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newOwner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;_balances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;_balances&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="erc-721-的特性"&gt;&lt;a href="#erc-721-%e7%9a%84%e7%89%b9%e6%80%a7" class="header-anchor"&gt;&lt;/a&gt;ERC-721 的特性
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;每个 tokenId 唯一&lt;/strong&gt;——一个 owner，不存在&amp;quot;持有多少&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每个 token 一段元数据&lt;/strong&gt;——通过 &lt;code&gt;tokenURI(tokenId)&lt;/code&gt; 取&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;批量操作要循环 N 次&lt;/strong&gt;——转 100 个 NFT 要 100 次 &lt;code&gt;transferFrom&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="二erc-1155多代币--半同质"&gt;&lt;a href="#%e4%ba%8cerc-1155%e5%a4%9a%e4%bb%a3%e5%b8%81--%e5%8d%8a%e5%90%8c%e8%b4%a8" class="header-anchor"&gt;&lt;/a&gt;二、ERC-1155：多代币 + 半同质
&lt;/h2&gt;&lt;p&gt;ERC-1155 由 Enjin 团队 2018 年提出（EIP-1155），针对 ERC-721 的几个痛点：&lt;/p&gt;
&lt;h3 id="痛点-1游戏里有大量同种道具"&gt;&lt;a href="#%e7%97%9b%e7%82%b9-1%e6%b8%b8%e6%88%8f%e9%87%8c%e6%9c%89%e5%a4%a7%e9%87%8f%e5%90%8c%e7%a7%8d%e9%81%93%e5%85%b7" class="header-anchor"&gt;&lt;/a&gt;痛点 1：游戏里有大量同种道具
&lt;/h3&gt;&lt;p&gt;ERC-721 模型下，&amp;ldquo;100 把同样的剑&amp;quot;要 100 个不同 tokenId、100 次铸造、100 次转账——&lt;strong&gt;Gas 成本爆炸&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="痛点-2合约碎片化"&gt;&lt;a href="#%e7%97%9b%e7%82%b9-2%e5%90%88%e7%ba%a6%e7%a2%8e%e7%89%87%e5%8c%96" class="header-anchor"&gt;&lt;/a&gt;痛点 2：合约碎片化
&lt;/h3&gt;&lt;p&gt;每个游戏出一种 NFT 都要部署一个新合约——以太坊上充斥着 1 万个仅区分一个游戏内物品的合约。&lt;/p&gt;
&lt;h3 id="痛点-3不能批量"&gt;&lt;a href="#%e7%97%9b%e7%82%b9-3%e4%b8%8d%e8%83%bd%e6%89%b9%e9%87%8f" class="header-anchor"&gt;&lt;/a&gt;痛点 3：不能批量
&lt;/h3&gt;&lt;p&gt;ERC-721 没有 &lt;code&gt;safeBatchTransferFrom&lt;/code&gt;——电商场景里&amp;quot;一键购买 5 件商品&amp;quot;做不到。&lt;/p&gt;
&lt;h3 id="erc-1155-的解法"&gt;&lt;a href="#erc-1155-%e7%9a%84%e8%a7%a3%e6%b3%95" class="header-anchor"&gt;&lt;/a&gt;ERC-1155 的解法
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;一个合约管理多种 id，每种 id 可以有多份&lt;/strong&gt;。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IERC1155&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;balanceOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="k"&gt;view&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint256&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;balanceOfBatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="k"&gt;view&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint256&lt;/span&gt;&lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;safeTransferFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bytes&lt;/span&gt; &lt;span class="nb"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;safeBatchTransferFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;amounts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bytes&lt;/span&gt; &lt;span class="nb"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;注意 &lt;code&gt;balanceOf&lt;/code&gt; 多了 &lt;code&gt;(account, id)&lt;/code&gt;——&lt;strong&gt;同一个 id 可以有多份&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="数据结构-1"&gt;&lt;a href="#%e6%95%b0%e6%8d%ae%e7%bb%93%e6%9e%84-1" class="header-anchor"&gt;&lt;/a&gt;数据结构
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;_balances&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// _balances[tokenId][owner] = 持有数量
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="三能力对比表"&gt;&lt;a href="#%e4%b8%89%e8%83%bd%e5%8a%9b%e5%af%b9%e6%af%94%e8%a1%a8" class="header-anchor"&gt;&lt;/a&gt;三、能力对比表
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: left"&gt;&lt;/th&gt;
 &lt;th style="text-align: left"&gt;ERC-721&lt;/th&gt;
 &lt;th style="text-align: left"&gt;ERC-1155&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;token 模型&lt;/td&gt;
 &lt;td style="text-align: left"&gt;每 id 唯一&lt;/td&gt;
 &lt;td style="text-align: left"&gt;每 id 可有多份&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;适合 NFT&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✓&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✓ （amount = 1 等于 NFT）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;适合 Fungible&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✗&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✓&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;批量转账&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✗（要循环）&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✓ &lt;code&gt;safeBatchTransferFrom&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;批量查询余额&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✗&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✓ &lt;code&gt;balanceOfBatch&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;Gas（批量场景）&lt;/td&gt;
 &lt;td style="text-align: left"&gt;高&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;strong&gt;显著更低&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;metadata&lt;/td&gt;
 &lt;td style="text-align: left"&gt;tokenURI(id)&lt;/td&gt;
 &lt;td style="text-align: left"&gt;uri(id)（可用 {id} 占位符）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;approve 粒度&lt;/td&gt;
 &lt;td style="text-align: left"&gt;单 token&lt;/td&gt;
 &lt;td style="text-align: left"&gt;全合约（setApprovalForAll）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;钱包/工具支持&lt;/td&gt;
 &lt;td style="text-align: left"&gt;几乎全部&lt;/td&gt;
 &lt;td style="text-align: left"&gt;主流支持，部分老钱包不支持&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;适用场景&lt;/td&gt;
 &lt;td style="text-align: left"&gt;头像、艺术、收藏品&lt;/td&gt;
 &lt;td style="text-align: left"&gt;游戏道具、批量、组合资产&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="四gas-成本erc-1155-的杀手级优势"&gt;&lt;a href="#%e5%9b%9bgas-%e6%88%90%e6%9c%acerc-1155-%e7%9a%84%e6%9d%80%e6%89%8b%e7%ba%a7%e4%bc%98%e5%8a%bf" class="header-anchor"&gt;&lt;/a&gt;四、Gas 成本：ERC-1155 的杀手级优势
&lt;/h2&gt;&lt;p&gt;铸造 100 个相同道具：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ERC-721
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_mint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nextTokenId&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 100 次 SSTORE
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ERC-1155
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;_mint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ITEM_SWORD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 1 次 SSTORE（balance = 100）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;实测对比（铸造 100 件相同道具）：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: left"&gt;方案&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Gas 消耗&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;ERC-721&lt;/td&gt;
 &lt;td style="text-align: left"&gt;~3,500,000&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;ERC-1155&lt;/td&gt;
 &lt;td style="text-align: left"&gt;~80,000&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;差距 40 倍。批量转账的差距类似。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这是为什么链游、批量市场几乎都选 ERC-1155&lt;/strong&gt;——同等业务，Gas 成本压到几十分之一。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="五适用场景"&gt;&lt;a href="#%e4%ba%94%e9%80%82%e7%94%a8%e5%9c%ba%e6%99%af" class="header-anchor"&gt;&lt;/a&gt;五、适用场景
&lt;/h2&gt;&lt;h3 id="选-erc-721-的场景"&gt;&lt;a href="#%e9%80%89-erc-721-%e7%9a%84%e5%9c%ba%e6%99%af" class="header-anchor"&gt;&lt;/a&gt;选 ERC-721 的场景
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;每个 token 独一无二&lt;/strong&gt;：头像、PFP、艺术品、稀有收藏&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每个 token 元数据完全不同&lt;/strong&gt;：CryptoPunks 每个都不一样&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;品牌效应&lt;/strong&gt;：BAYC、Doodles 这种&amp;quot;个体认知度&amp;quot;项目——单合约、单 tokenId&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="选-erc-1155-的场景"&gt;&lt;a href="#%e9%80%89-erc-1155-%e7%9a%84%e5%9c%ba%e6%99%af" class="header-anchor"&gt;&lt;/a&gt;选 ERC-1155 的场景
&lt;/h3&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;批量操作&lt;/strong&gt;：电商商品、物流凭证&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;混合资产&lt;/strong&gt;：一个合约里既有可堆叠物品，又有独一无二的稀有 NFT&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;组合金融产品&lt;/strong&gt;：一个合约里管理 N 种衍生品&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="一些灰色地带"&gt;&lt;a href="#%e4%b8%80%e4%ba%9b%e7%81%b0%e8%89%b2%e5%9c%b0%e5%b8%a6" class="header-anchor"&gt;&lt;/a&gt;一些灰色地带
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;限量版艺术品&lt;/strong&gt;（一种艺术 100 个相同复制品）：ERC-1155 更合适&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;演唱会门票（每张序列号不同）&lt;/strong&gt;：ERC-721 更合适&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Loot 这种链上随机生成&lt;/strong&gt;：ERC-721 元数据 on-chain 更合适&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;判断关键&lt;/strong&gt;：&amp;ldquo;这种 token 会有多份吗？&amp;quot;——会就 ERC-1155，不会就 ERC-721。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="六metadata-的差异"&gt;&lt;a href="#%e5%85%admetadata-%e7%9a%84%e5%b7%ae%e5%bc%82" class="header-anchor"&gt;&lt;/a&gt;六、metadata 的差异
&lt;/h2&gt;&lt;h3 id="erc-721-的-tokenuri"&gt;&lt;a href="#erc-721-%e7%9a%84-tokenuri" class="header-anchor"&gt;&lt;/a&gt;ERC-721 的 tokenURI
&lt;/h3&gt;&lt;p&gt;每个 tokenId 一个 URI：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;tokenURI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="n"&gt;tokenId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;view&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;encodePacked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseURI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokenId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;返回的 JSON：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;BAYC #4521&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;image&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ipfs://Qm.../4521.png&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;attributes&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;trait_type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Background&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Gold&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="erc-1155-的-uri"&gt;&lt;a href="#erc-1155-%e7%9a%84-uri" class="header-anchor"&gt;&lt;/a&gt;ERC-1155 的 uri
&lt;/h3&gt;&lt;p&gt;支持 &lt;code&gt;{id}&lt;/code&gt; 占位符：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;view&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;https://example.com/api/{id}.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;客户端拿到后用 64 位 hex（补全 &lt;code&gt;0x&lt;/code&gt; 前缀）替换 &lt;code&gt;{id}&lt;/code&gt;——这样&lt;strong&gt;一个 URI 模板能服务所有 id&lt;/strong&gt;，省心。&lt;/p&gt;
&lt;p&gt;但实际上很多 ERC-1155 项目仍然给每个 id 单独配 URI——尤其是游戏道具，每种道具一段元数据。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="七approve-模型差异"&gt;&lt;a href="#%e4%b8%83approve-%e6%a8%a1%e5%9e%8b%e5%b7%ae%e5%bc%82" class="header-anchor"&gt;&lt;/a&gt;七、approve 模型差异
&lt;/h2&gt;&lt;h3 id="erc-721"&gt;&lt;a href="#erc-721" class="header-anchor"&gt;&lt;/a&gt;ERC-721
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;approve(to, tokenId)&lt;/code&gt;：授权某地址转某个 token&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setApprovalForAll(operator, true)&lt;/code&gt;：授权某地址操作所有 token&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="erc-1155"&gt;&lt;a href="#erc-1155" class="header-anchor"&gt;&lt;/a&gt;ERC-1155
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;没有单 token approve&lt;/strong&gt;——只能 &lt;code&gt;setApprovalForAll&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;这意味着用户必须信任整个 operator（如交易市场）能动他全部 1155 资产&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这是 ERC-1155 在用户安全侧的劣势——授权范围更大、撤销更重要。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="八写一个-erc-1155-合约"&gt;&lt;a href="#%e5%85%ab%e5%86%99%e4%b8%80%e4%b8%aa-erc-1155-%e5%90%88%e7%ba%a6" class="header-anchor"&gt;&lt;/a&gt;八、写一个 ERC-1155 合约
&lt;/h2&gt;&lt;p&gt;OpenZeppelin 提供了一行能用的实现：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;@openzeppelin/contracts/token/ERC1155/ERC1155.sol&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;@openzeppelin/contracts/access/Ownable.sol&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;contract&lt;/span&gt; &lt;span class="nc"&gt;GameItems&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;ERC1155&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Ownable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;constant&lt;/span&gt; &lt;span class="n"&gt;SWORD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;constant&lt;/span&gt; &lt;span class="n"&gt;SHIELD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;constant&lt;/span&gt; &lt;span class="n"&gt;POTION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;constant&lt;/span&gt; &lt;span class="n"&gt;LEGENDARY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;ERC1155&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://example.com/api/{id}.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_mint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SWORD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_mint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SHIELD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_mint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;POTION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_mint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LEGENDARY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 唯一稀有 NFT
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;onlyOwner&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_mint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;mintBatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;memory&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;memory&lt;/span&gt; &lt;span class="n"&gt;amounts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;onlyOwner&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_mintBatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amounts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;注意 &lt;code&gt;LEGENDARY&lt;/code&gt; 的 amount = 1——&lt;strong&gt;这相当于在同一个合约里用 ERC-1155 表达 NFT&lt;/strong&gt;。这是 ERC-1155 的灵活性。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="九踩坑提醒"&gt;&lt;a href="#%e4%b9%9d%e8%b8%a9%e5%9d%91%e6%8f%90%e9%86%92" class="header-anchor"&gt;&lt;/a&gt;九、踩坑提醒
&lt;/h2&gt;&lt;h3 id="1-safetransferfrom-接收方要实现钩子"&gt;&lt;a href="#1-safetransferfrom-%e6%8e%a5%e6%94%b6%e6%96%b9%e8%a6%81%e5%ae%9e%e7%8e%b0%e9%92%a9%e5%ad%90" class="header-anchor"&gt;&lt;/a&gt;1. &lt;code&gt;safeTransferFrom&lt;/code&gt; 接收方要实现钩子
&lt;/h3&gt;&lt;p&gt;如果接收方是合约，必须实现 &lt;code&gt;onERC721Received&lt;/code&gt; / &lt;code&gt;onERC1155Received&lt;/code&gt;，否则转账 revert。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onERC1155Received&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bytes&lt;/span&gt; &lt;span class="n"&gt;calldata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="k"&gt;pure&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bytes4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onERC1155Received&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="2-erc-1155-的-transfer-必须带-amount"&gt;&lt;a href="#2-erc-1155-%e7%9a%84-transfer-%e5%bf%85%e9%a1%bb%e5%b8%a6-amount" class="header-anchor"&gt;&lt;/a&gt;2. ERC-1155 的 transfer 必须带 amount
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ERC-721 的语法
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transferFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokenId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ERC-1155 的语法
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;safeTransferFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;NFT 性质的 token 永远 amount = 1。&lt;/p&gt;
&lt;h3 id="3-metadata-占位符兼容"&gt;&lt;a href="#3-metadata-%e5%8d%a0%e4%bd%8d%e7%ac%a6%e5%85%bc%e5%ae%b9" class="header-anchor"&gt;&lt;/a&gt;3. metadata 占位符兼容
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;{id}&lt;/code&gt; 占位符是 ERC-1155 标准里规定的——&lt;strong&gt;实际平台支持参差不齐&lt;/strong&gt;。OpenSea / 区块链浏览器多数支持，但小钱包可能不识别——保险起见每个 id 单独配 URI。&lt;/p&gt;
&lt;h3 id="4-不要混用模型"&gt;&lt;a href="#4-%e4%b8%8d%e8%a6%81%e6%b7%b7%e7%94%a8%e6%a8%a1%e5%9e%8b" class="header-anchor"&gt;&lt;/a&gt;4. 不要混用模型
&lt;/h3&gt;&lt;p&gt;同一个项目里&lt;strong&gt;要么用 ERC-721 要么用 ERC-1155&lt;/strong&gt;，不要在一个集合里两种都发。买家会困惑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;为什么这个 NFT 在我的钱包里看不到？&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;为什么我数量是 5 但只能转 1？&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="5-老钱包可能不支持-erc-1155"&gt;&lt;a href="#5-%e8%80%81%e9%92%b1%e5%8c%85%e5%8f%af%e8%83%bd%e4%b8%8d%e6%94%af%e6%8c%81-erc-1155" class="header-anchor"&gt;&lt;/a&gt;5. 老钱包可能不支持 ERC-1155
&lt;/h3&gt;&lt;p&gt;Trust Wallet、imToken 较新版本支持，但 5 年前的老钱包可能识别不出 1155——目标用户群里要有市场调研。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="十其他相关标准"&gt;&lt;a href="#%e5%8d%81%e5%85%b6%e4%bb%96%e7%9b%b8%e5%85%b3%e6%a0%87%e5%87%86" class="header-anchor"&gt;&lt;/a&gt;十、其他相关标准
&lt;/h2&gt;&lt;p&gt;NFT 生态的相关标准还有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ERC-2981&lt;/strong&gt;：版税标准——让 NFT 在二级市场卖出时自动扣版税给原作者&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;EIP-2535&lt;/strong&gt;：Diamond 合约标准——超大合约的模块化升级&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ERC-4907&lt;/strong&gt;：可租赁 NFT&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ERC-721A&lt;/strong&gt;：Azuki 优化的批量铸造 ERC-721（Gas 比标准 721 低很多）&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
 &lt;p&gt;🔥 ERC-721A 在某种程度上是对 ERC-1155 的&amp;quot;反击&amp;rdquo;——保留 ERC-721 的语义但优化批量铸造 Gas，是大量 PFP 项目的选择。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;p&gt;把全文压一句：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;ERC-721 表达『每个唯一』，ERC-1155 表达『多种 + 多份』。一个合约一个项目就用 721；多类型批量场景必上 1155。&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;工程选型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PFP / 艺术 / 收藏&lt;/strong&gt; → ERC-721（或 ERC-721A）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;游戏 / 批量 / 多种&lt;/strong&gt; → ERC-1155&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;混合需求&lt;/strong&gt; → ERC-1155（amount=1 表 NFT，amount&amp;gt;1 表数量）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;二级市场版税&lt;/strong&gt; → 配 ERC-2981&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;NFT 生态还在演进，理解这些标准的边界比追新更重要——&lt;strong&gt;搞清&amp;quot;业务场景该用哪个&amp;rdquo;&lt;/strong&gt;，远比&amp;quot;哪个最先进&amp;quot;实用。&lt;/p&gt;</description></item><item><title>智能合约的四种调用方式：Call / CallCode / DelegateCall / StaticCall</title><link>https://www.lingcoder.com/p/solidity-call-callcode-delegatecall-staticcall/</link><pubDate>Thu, 17 Mar 2022 16:00:00 +0800</pubDate><guid>https://www.lingcoder.com/p/solidity-call-callcode-delegatecall-staticcall/</guid><description>&lt;img src="https://www.lingcoder.com/p/solidity-call-callcode-delegatecall-staticcall/cover.svg" alt="Featured image of post 智能合约的四种调用方式：Call / CallCode / DelegateCall / StaticCall" /&gt;&lt;h2 id="一段看似平平无奇的代码行为完全不同"&gt;&lt;a href="#%e4%b8%80%e6%ae%b5%e7%9c%8b%e4%bc%bc%e5%b9%b3%e5%b9%b3%e6%97%a0%e5%a5%87%e7%9a%84%e4%bb%a3%e7%a0%81%e8%a1%8c%e4%b8%ba%e5%ae%8c%e5%85%a8%e4%b8%8d%e5%90%8c" class="header-anchor"&gt;&lt;/a&gt;一段看似平平无奇的代码，行为完全不同
&lt;/h2&gt;&lt;p&gt;四个看起来很像的调用：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// call
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;callcode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// callcode（已弃用）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;delegatecall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// delegatecall
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;staticcall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// staticcall
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;它们都是&amp;quot;调另一个合约&amp;quot;——但&lt;strong&gt;存储上下文、msg.sender、能否改状态完全不同&lt;/strong&gt;。用错任何一个轻则无效，重则被攻击者利用整个协议归零。&lt;/p&gt;
&lt;p&gt;本文把这四种调用的差异系统讲清楚，并配代码例子说明各自用法和风险。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一四者本质对比"&gt;&lt;a href="#%e4%b8%80%e5%9b%9b%e8%80%85%e6%9c%ac%e8%b4%a8%e5%af%b9%e6%af%94" class="header-anchor"&gt;&lt;/a&gt;一、四者本质对比
&lt;/h2&gt;&lt;p&gt;EVM 中合约调用的&amp;quot;上下文&amp;quot;由三个核心要素组成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;代码（Code）&lt;/strong&gt;：要执行哪段字节码？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;存储（Storage）&lt;/strong&gt;：读写哪个合约的存储？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;msg.sender&lt;/code&gt; / &lt;code&gt;msg.value&lt;/code&gt;&lt;/strong&gt;：调用者身份是谁？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;四种调用对这三个要素的处理：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: left"&gt;&lt;/th&gt;
 &lt;th style="text-align: left"&gt;执行的代码&lt;/th&gt;
 &lt;th style="text-align: left"&gt;操作的存储&lt;/th&gt;
 &lt;th style="text-align: left"&gt;msg.sender&lt;/th&gt;
 &lt;th style="text-align: left"&gt;msg.value&lt;/th&gt;
 &lt;th style="text-align: left"&gt;能否改状态&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;call&lt;/code&gt;&lt;/td&gt;
 &lt;td style="text-align: left"&gt;target&lt;/td&gt;
 &lt;td style="text-align: left"&gt;target&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;strong&gt;caller&lt;/strong&gt;&lt;/td&gt;
 &lt;td style="text-align: left"&gt;可传值&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✓&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;callcode&lt;/code&gt;&lt;/td&gt;
 &lt;td style="text-align: left"&gt;target&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;strong&gt;caller&lt;/strong&gt;&lt;/td&gt;
 &lt;td style="text-align: left"&gt;caller&lt;/td&gt;
 &lt;td style="text-align: left"&gt;可传值&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✓&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;delegatecall&lt;/code&gt;&lt;/td&gt;
 &lt;td style="text-align: left"&gt;target&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;strong&gt;caller&lt;/strong&gt;&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;strong&gt;原发起者&lt;/strong&gt;&lt;/td&gt;
 &lt;td style="text-align: left"&gt;原 value&lt;/td&gt;
 &lt;td style="text-align: left"&gt;✓&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;&lt;code&gt;staticcall&lt;/code&gt;&lt;/td&gt;
 &lt;td style="text-align: left"&gt;target&lt;/td&gt;
 &lt;td style="text-align: left"&gt;target&lt;/td&gt;
 &lt;td style="text-align: left"&gt;caller&lt;/td&gt;
 &lt;td style="text-align: left"&gt;不可传值&lt;/td&gt;
 &lt;td style="text-align: left"&gt;&lt;strong&gt;✗ 只读&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;callcode&lt;/code&gt; 已经被官方标记为 deprecated（功能被 delegatecall 替代），实战中几乎不用。重点是 &lt;strong&gt;call / delegatecall / staticcall&lt;/strong&gt; 三种。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二call最朴素的远程调用"&gt;&lt;a href="#%e4%ba%8ccall%e6%9c%80%e6%9c%b4%e7%b4%a0%e7%9a%84%e8%bf%9c%e7%a8%8b%e8%b0%83%e7%94%a8" class="header-anchor"&gt;&lt;/a&gt;二、call：最朴素的&amp;quot;远程调用&amp;quot;
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;contract&lt;/span&gt; &lt;span class="nc"&gt;Caller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;callTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="k"&gt;payable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bytes&lt;/span&gt; &lt;span class="k"&gt;memory&lt;/span&gt; &lt;span class="nb"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;call&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;encodeWithSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;doSomething(uint256)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;call failed&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;call&lt;/code&gt; 的语义是——&lt;strong&gt;让对方合约执行对方的代码、操作对方的存储&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;执行 &lt;code&gt;target&lt;/code&gt; 的代码&lt;/li&gt;
&lt;li&gt;改 &lt;code&gt;target&lt;/code&gt; 的存储&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;target&lt;/code&gt; 看来，&lt;code&gt;msg.sender = Caller&lt;/code&gt; 这个调用方&lt;/li&gt;
&lt;li&gt;可以转账（&lt;code&gt;{value: x}&lt;/code&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这就是一次&amp;quot;标准的合约间调用&amp;quot;。&lt;/p&gt;
&lt;h3 id="典型场景"&gt;&lt;a href="#%e5%85%b8%e5%9e%8b%e5%9c%ba%e6%99%af" class="header-anchor"&gt;&lt;/a&gt;典型场景
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;转账给一个普通地址&lt;/strong&gt;：&lt;code&gt;recipient.call{value: amount}(&amp;quot;&amp;quot;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;调用未知接口&lt;/strong&gt;：当目标合约接口不固定时（如 ERC-721 的 &lt;code&gt;onERC721Received&lt;/code&gt; 钩子），用 call&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="注意点"&gt;&lt;a href="#%e6%b3%a8%e6%84%8f%e7%82%b9" class="header-anchor"&gt;&lt;/a&gt;注意点
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;call 失败不会自动 revert&lt;/strong&gt; ——返回 &lt;code&gt;bool ok&lt;/code&gt;，要自己 require&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重入风险&lt;/strong&gt;：call 触发外部代码执行，可能再调回来——经典 The DAO 漏洞就是这个&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="三delegatecall用别人的代码操作自己的存储"&gt;&lt;a href="#%e4%b8%89delegatecall%e7%94%a8%e5%88%ab%e4%ba%ba%e7%9a%84%e4%bb%a3%e7%a0%81%e6%93%8d%e4%bd%9c%e8%87%aa%e5%b7%b1%e7%9a%84%e5%ad%98%e5%82%a8" class="header-anchor"&gt;&lt;/a&gt;三、delegatecall：用别人的代码、操作自己的存储
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;contract&lt;/span&gt; &lt;span class="nc"&gt;Proxy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="k"&gt;payable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;impl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assembly&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;calldatacopy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;calldatasize&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="ow"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nf"&gt;delegatecall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;gas&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;impl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;calldatasize&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;returndatacopy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;returndatasize&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nf"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;revert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;returndatasize&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;return&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;returndatasize&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;delegatecall&lt;/code&gt; 的语义是——&lt;strong&gt;借用对方的字节码逻辑，但操作的是自己的存储和上下文&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;执行 &lt;code&gt;target&lt;/code&gt; 的代码&lt;/li&gt;
&lt;li&gt;改 &lt;strong&gt;caller&lt;/strong&gt; 的存储（不是 target 的！）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;msg.sender&lt;/code&gt; 是&lt;strong&gt;原始调用者&lt;/strong&gt;（不是 caller）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;msg.value&lt;/code&gt; 也是原始的&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="典型场景代理升级模式"&gt;&lt;a href="#%e5%85%b8%e5%9e%8b%e5%9c%ba%e6%99%af%e4%bb%a3%e7%90%86%e5%8d%87%e7%ba%a7%e6%a8%a1%e5%bc%8f" class="header-anchor"&gt;&lt;/a&gt;典型场景：代理升级模式
&lt;/h3&gt;&lt;p&gt;可升级合约（OpenZeppelin Proxy / UUPS）的根基就是 delegatecall：&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 User --&gt; Proxy
 Proxy -- delegatecall --&gt; Logic
 Proxy -.存储在.-&gt; Storage&lt;/pre&gt;&lt;p&gt;Proxy 把所有调用 delegatecall 给 Logic 合约——Logic 操作的存储是 Proxy 的，所以&lt;strong&gt;升级 Logic 不影响数据&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="致命陷阱存储槽冲突"&gt;&lt;a href="#%e8%87%b4%e5%91%bd%e9%99%b7%e9%98%b1%e5%ad%98%e5%82%a8%e6%a7%bd%e5%86%b2%e7%aa%81" class="header-anchor"&gt;&lt;/a&gt;致命陷阱：存储槽冲突
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;delegatecall&lt;/code&gt; 操作 caller 的存储，&lt;strong&gt;但用的是 target 的存储槽布局&lt;/strong&gt;——两边布局必须严格对齐。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;contract&lt;/span&gt; &lt;span class="nc"&gt;LogicV1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// slot 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// slot 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;contract&lt;/span&gt; &lt;span class="nc"&gt;LogicV2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;uint256&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// slot 0 ← 和 V1 的 owner 撞了
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// slot 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;升级到 V2 后，原来 owner 槽存的地址被当作 counter 解读——&lt;strong&gt;协议直接报废&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="致命陷阱二误用-delegatecall-给陌生地址"&gt;&lt;a href="#%e8%87%b4%e5%91%bd%e9%99%b7%e9%98%b1%e4%ba%8c%e8%af%af%e7%94%a8-delegatecall-%e7%bb%99%e9%99%8c%e7%94%9f%e5%9c%b0%e5%9d%80" class="header-anchor"&gt;&lt;/a&gt;致命陷阱二：误用 delegatecall 给陌生地址
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bytes&lt;/span&gt; &lt;span class="n"&gt;calldata&lt;/span&gt; &lt;span class="nb"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;onlyOwner&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;delegatecall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ❌ 极度危险
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果攻击者诱导 owner 调用此函数指向恶意 target，&lt;strong&gt;target 的代码可以任意改 caller 的存储&lt;/strong&gt;——包括把 owner 改成攻击者地址。&lt;/p&gt;
&lt;p&gt;历史上的 Parity Wallet 漏洞、Audius 攻击等，都是 delegatecall 滥用导致的灾难。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;delegatecall 必须只指向你自己控制的、白名单内的合约。&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="四staticcall只读调用"&gt;&lt;a href="#%e5%9b%9bstaticcall%e5%8f%aa%e8%af%bb%e8%b0%83%e7%94%a8" class="header-anchor"&gt;&lt;/a&gt;四、staticcall：只读调用
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;readSomething&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="k"&gt;view&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bytes&lt;/span&gt; &lt;span class="k"&gt;memory&lt;/span&gt; &lt;span class="nb"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;staticcall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;encodeWithSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;getValue()&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;staticcall failed&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint256&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;staticcall&lt;/code&gt; 是 EIP-214 引入——和 call 几乎一样，但&lt;strong&gt;强制只读&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;执行 target 的代码&lt;/li&gt;
&lt;li&gt;改 target 的存储——不行，只读&lt;/li&gt;
&lt;li&gt;不能转账&lt;/li&gt;
&lt;li&gt;target 内部任何 SSTORE / LOG / CREATE 都会让调用 revert&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="典型场景-1"&gt;&lt;a href="#%e5%85%b8%e5%9e%8b%e5%9c%ba%e6%99%af-1" class="header-anchor"&gt;&lt;/a&gt;典型场景
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;view&lt;/code&gt; / &lt;code&gt;pure&lt;/code&gt; 函数的调用&lt;/strong&gt;——Solidity 编译器会自动用 staticcall&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;集成第三方合约的查询&lt;/strong&gt;：你想查别人合约状态但不信任它会改你状态——staticcall 强制只读&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPriceFromOracle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IOracle&lt;/span&gt; &lt;span class="n"&gt;oracle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="k"&gt;view&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 编译器自动选 staticcall
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;oracle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="为什么重要"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e9%87%8d%e8%a6%81" class="header-anchor"&gt;&lt;/a&gt;为什么重要
&lt;/h3&gt;&lt;p&gt;没有 staticcall 之前，要&amp;quot;安全地查询别人的合约&amp;quot;非常麻烦——对方合约可能在 view 函数里偷偷改状态（虽然不该）。staticcall &lt;strong&gt;从 EVM 层面强制只读&lt;/strong&gt;——这是一个安全保障。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="五callcode已弃用"&gt;&lt;a href="#%e4%ba%94callcode%e5%b7%b2%e5%bc%83%e7%94%a8" class="header-anchor"&gt;&lt;/a&gt;五、callcode：已弃用
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;callcode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 已 deprecated
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;callcode 是 delegatecall 的早期版本，&lt;strong&gt;唯一区别是 msg.sender 不会保留为原始调用者&lt;/strong&gt;——这通常是个 bug 而不是 feature。所以 Solidity 0.5.0 之后 callcode 被禁用，全部用 delegatecall。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;新代码里永远不要写 callcode&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="六四种调用的决策树"&gt;&lt;a href="#%e5%85%ad%e5%9b%9b%e7%a7%8d%e8%b0%83%e7%94%a8%e7%9a%84%e5%86%b3%e7%ad%96%e6%a0%91" class="header-anchor"&gt;&lt;/a&gt;六、四种调用的&amp;quot;决策树&amp;quot;
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 Start([要调用另一个合约?])
 Start --&gt; S{需要写状态?}
 S --&gt;|不需要，只查询| Static[staticcall]
 S --&gt;|需要写| Self{改的是自己的存储 or 对方的?}
 Self --&gt;|对方的存储 - 远程调用| Call[call]
 Self --&gt;|自己的存储 - 借代码| Trust{对方合约 100% 可信?}
 Trust --&gt;|是| Delegate[delegatecall]
 Trust --&gt;|否| Avoid[别 delegatecall！]&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="七solidity-高级语法的隐式调用"&gt;&lt;a href="#%e4%b8%83solidity-%e9%ab%98%e7%ba%a7%e8%af%ad%e6%b3%95%e7%9a%84%e9%9a%90%e5%bc%8f%e8%b0%83%e7%94%a8" class="header-anchor"&gt;&lt;/a&gt;七、Solidity 高级语法的隐式调用
&lt;/h2&gt;&lt;p&gt;直接写函数调用时，Solidity 会&lt;strong&gt;根据函数修饰符自动选择&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ITarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;viewFn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// staticcall
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ITarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;pureFn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// staticcall
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ITarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;normalFn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// call
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;view&lt;/code&gt; / &lt;code&gt;pure&lt;/code&gt; → staticcall&lt;/li&gt;
&lt;li&gt;普通函数 → call&lt;/li&gt;
&lt;li&gt;delegatecall 永远要&lt;strong&gt;显式写&lt;/strong&gt;——没有&amp;quot;自动 delegatecall&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这是合理的——delegatecall 太危险，必须开发者明确知道自己在做什么。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="八几个工程实践"&gt;&lt;a href="#%e5%85%ab%e5%87%a0%e4%b8%aa%e5%b7%a5%e7%a8%8b%e5%ae%9e%e8%b7%b5" class="header-anchor"&gt;&lt;/a&gt;八、几个工程实践
&lt;/h2&gt;&lt;h3 id="1-call-一律检查返回值"&gt;&lt;a href="#1-call-%e4%b8%80%e5%be%8b%e6%a3%80%e6%9f%a5%e8%bf%94%e5%9b%9e%e5%80%bc" class="header-anchor"&gt;&lt;/a&gt;1. call 一律检查返回值
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;call failed&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;不检查就忽略错误，等于&amp;quot;我以为调用成功了，其实没有&amp;quot;——脏数据由此而生。&lt;/p&gt;
&lt;h3 id="2-转账永远用-call-不要用-transfer"&gt;&lt;a href="#2-%e8%bd%ac%e8%b4%a6%e6%b0%b8%e8%bf%9c%e7%94%a8-call-%e4%b8%8d%e8%a6%81%e7%94%a8-transfer" class="header-anchor"&gt;&lt;/a&gt;2. 转账永远用 call 不要用 transfer
&lt;/h3&gt;&lt;p&gt;老代码：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ❌ 现代不推荐
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;transfer&lt;/code&gt; 一直限制 2300 gas（这点从 Solidity 引入 transfer 时就如此）。真正让它频繁出问题的是 &lt;strong&gt;EIP-2929（Berlin 升级，2021-04）&lt;/strong&gt;——抬高了 SLOAD/CALL 等状态访问的 gas 成本，接收方 fallback 里很多原本能跑过的操作再也挤不进 2300 gas，导致 OOG。现代写法：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;call&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;transfer failed&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="3-delegatecall-配-onlyowner--白名单"&gt;&lt;a href="#3-delegatecall-%e9%85%8d-onlyowner--%e7%99%bd%e5%90%8d%e5%8d%95" class="header-anchor"&gt;&lt;/a&gt;3. delegatecall 配 onlyOwner / 白名单
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;trustedTargets&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;delegateAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;address&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bytes&lt;/span&gt; &lt;span class="n"&gt;calldata&lt;/span&gt; &lt;span class="nb"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;external&lt;/span&gt; &lt;span class="n"&gt;onlyOwner&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trustedTargets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;not trusted&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;delegatecall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;永远不要让外部参数决定 delegatecall 的目标&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="4-staticcall-保证只读"&gt;&lt;a href="#4-staticcall-%e4%bf%9d%e8%af%81%e5%8f%aa%e8%af%bb" class="header-anchor"&gt;&lt;/a&gt;4. staticcall 保证只读
&lt;/h3&gt;&lt;p&gt;集成新协议时——只查询、不修改：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ISomeProtocol&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;getInfo&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 用 view 接口，编译器自动 staticcall
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果对方接口没声明 view 但你&lt;strong&gt;确定它只读&lt;/strong&gt;——可以手动 staticcall：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bytes&lt;/span&gt; &lt;span class="k"&gt;memory&lt;/span&gt; &lt;span class="nb"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;staticcall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;encodeWithSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;getInfo()&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果对方实际改了状态，staticcall 会让交易失败——&lt;strong&gt;EVM 层面拦截，比靠开发者自觉安全&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="5-用-openzeppelin-address-库"&gt;&lt;a href="#5-%e7%94%a8-openzeppelin-address-%e5%ba%93" class="header-anchor"&gt;&lt;/a&gt;5. 用 OpenZeppelin Address 库
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-solidity" data-lang="solidity"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;using&lt;/span&gt; &lt;span class="n"&gt;Address&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="kt"&gt;address&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;functionCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 安全的 call + revert
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;functionStaticCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 安全的 staticcall
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;functionDelegateCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 安全的 delegatecall（要白名单）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;OZ 的封装多了 require、revert reason 解析等——&lt;strong&gt;比裸 call 安全得多&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="九几个真实案例"&gt;&lt;a href="#%e4%b9%9d%e5%87%a0%e4%b8%aa%e7%9c%9f%e5%ae%9e%e6%a1%88%e4%be%8b" class="header-anchor"&gt;&lt;/a&gt;九、几个真实案例
&lt;/h2&gt;&lt;h3 id="案例-1parity-wallet-的-14-万-eth2017"&gt;&lt;a href="#%e6%a1%88%e4%be%8b-1parity-wallet-%e7%9a%84-14-%e4%b8%87-eth2017" class="header-anchor"&gt;&lt;/a&gt;案例 1：Parity Wallet 的 14 万 ETH（2017）
&lt;/h3&gt;&lt;p&gt;Parity 的多签钱包用 delegatecall 委托给一个共享 Library。攻击者发现可以&lt;strong&gt;直接调用 Library 的 init 函数把自己设成 owner&lt;/strong&gt;——再调用 kill 函数，整个 Library 自毁，所有依赖它的钱包永久无法转账。&lt;/p&gt;
&lt;p&gt;教训：&lt;strong&gt;delegatecall 目标合约的所有 public/external 函数都要审慎设计&lt;/strong&gt;——public 入口被外部直接调用 + delegatecall 同时滥用，就是这种灾难。&lt;/p&gt;
&lt;h3 id="案例-2audius-治理攻击2022"&gt;&lt;a href="#%e6%a1%88%e4%be%8b-2audius-%e6%b2%bb%e7%90%86%e6%94%bb%e5%87%bb2022" class="header-anchor"&gt;&lt;/a&gt;案例 2：Audius 治理攻击（2022）
&lt;/h3&gt;&lt;p&gt;Audius 协议合约存在 &lt;strong&gt;storage collision + initialize 未保护&lt;/strong&gt;双重 bug——存储槽布局错位让攻击者可以重新调用 &lt;code&gt;initialize&lt;/code&gt; 把自己设为治理 guardian，然后&lt;strong&gt;通过提交并自我通过的恶意治理提案&lt;/strong&gt;转走社区池里约 1860 万 AUDIO（约 600 万美元）。&lt;/p&gt;
&lt;p&gt;教训：&lt;strong&gt;proxy 模式下，所有可升级合约的 initialize 必须加 &lt;code&gt;initializer&lt;/code&gt; 修饰符防止重复调用，并且基类与子类的 storage 布局必须严格对齐&lt;/strong&gt;——这两个 bug 任何一个不出，攻击都成立不了。&lt;/p&gt;
&lt;h3 id="案例-3the-dao2016"&gt;&lt;a href="#%e6%a1%88%e4%be%8b-3the-dao2016" class="header-anchor"&gt;&lt;/a&gt;案例 3：The DAO（2016）
&lt;/h3&gt;&lt;p&gt;经典重入——&lt;code&gt;call&lt;/code&gt; 触发的外部调用导致重新进入合约，余额还没扣就被反复提款。&lt;/p&gt;
&lt;p&gt;教训：&lt;strong&gt;调用外部前先改自己的状态&lt;/strong&gt;（Checks-Effects-Interactions 模式）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="十调试工具"&gt;&lt;a href="#%e5%8d%81%e8%b0%83%e8%af%95%e5%b7%a5%e5%85%b7" class="header-anchor"&gt;&lt;/a&gt;十、调试工具
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Foundry trace&lt;/strong&gt;：&lt;code&gt;forge test -vvvv&lt;/code&gt; 能看到每一层 call/delegatecall/staticcall 的细节&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tenderly&lt;/strong&gt;：可视化交易调用链路&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Etherscan trace&lt;/strong&gt;：交易详情里的&amp;quot;Internal Txns&amp;quot;标签&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;调试 delegatecall 出现&amp;quot;为什么状态没变 / 变错&amp;quot;的问题——第一步永远是看清楚到底是 call 还是 delegatecall，存储槽对齐没。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;p&gt;把全文压一句：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;call 是远程调用、delegatecall 是借代码、staticcall 是强制只读、callcode 是历史。四者用错就是漏洞。&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;工程上几条铁律：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;call 一定要 require(ok)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;delegatecall 只指向自己控制的、白名单内的合约&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;delegatecall 升级要严格保持存储布局&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;查询用 staticcall 比信任 view 更安全&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;永远不要 callcode&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;转账用 &lt;code&gt;call{value:...}&lt;/code&gt; 不用 transfer&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;把这四种调用的语义吃透——你看 OpenZeppelin / Aave / Uniswap 的代码就能瞬间看懂&amp;quot;为什么这里要这么写&amp;quot;。&lt;/p&gt;</description></item></channel></rss>