Polkadot Solidity开发-课程笔记12

【4.1:本节课程简介& Assembly Code 汇编代码|Polkadot 上的 Solidity 与 EVM 开发者路径】 https://www.bilibili.com/video/BV1krkFBCEph/?share_source=copy_web&vd_source=eba7ead3baa3d262a5be9ffe7184d3b4

第四课实践题:
https://github.com/papermoonio/2026-h1-building-production-grade-dApps/tree/main/homework/lesson-4

可升级智能合约项目是什么

简单来说,可升级智能合约项目,是指那些其核心合约在设计时采用了特定技术架构,从而允许开发者在合约部署后,能够对其功能逻辑进行更新或修复,同时保留合约地址和用户数据(状态)不变的项目

它是在“区块链数据不可篡改”和“软件需要迭代升级”之间取得平衡的解决方案。

🔧 核心原理:逻辑与状态分离

传统智能合约一旦部署,其代码和数据都无法更改。可升级合约的核心思想是将容易变化的业务逻辑与需要持久化的数据状态分离到不同的合约中

用户始终与一个固定的“代理合约”地址交互。当需要升级时,只需更换代理合约背后指向的“逻辑合约”地址,即可让用户在执行旧操作时,自动运行更新后的代码,而所有用户数据和资产仍安全地存储在不变的代理合约中。

⚙️ 主要技术实现模式

以下是几种主流的技术模式对比:

模式 核心机制 主要优点 主要缺点 适用场景
合约迁移 直接部署新合约,并手动迁移所有用户状态和数据。 概念简单,无需复杂架构。 成本极高,过程繁琐,需要用户主动切换。 早期或简单的升级场景。
数据分离 一个合约存数据,另一个合约存逻辑,逻辑合约调用存储合约。 实现相对简单。 需要管理多个合约和复杂的权限控制。 逻辑与数据清晰分离的场景。
代理模式 用户调用代理合约,代理使用 delegatecall 将执行委托给逻辑合约,并在自身环境下改变状态。 最主流方案,用户无感知,升级平滑。 架构复杂,存在存储与函数选择器冲突风险。 绝大多数需要升级的复杂DApp。
透明代理 代理模式的一种。升级功能在代理合约中。管理员与普通用户的调用路径不同。 避免了函数选择器冲突。 每次调用都需检查调用者身份,消耗更多Gas。 对安全性要求高,管理员操作频繁的场景。
UUPS 代理 代理模式的一种。升级功能在逻辑合约中。所有调用均委托给逻辑合约。 代理合约更轻量,Gas效率更高;可从逻辑上永久移除可升级性。 一旦新逻辑合约忘记继承升级逻辑,将永久失去升级能力。 追求Gas优化,且开发规范的场景。
钻石模式 一个代理合约可将不同功能委托给多个逻辑合约(切面)。 突破合约24KB大小限制;支持模块化、精细化升级。 架构极为复杂,工具链和支持相对较新。 超大规模、功能复杂的“巨无霸”合约。

⚖️ 为什么需要可升级合约?

其主要价值在于为项目提供灵活性,以适应:

  • 修复漏洞:在发现安全漏洞时能及时补救。
  • 功能迭代:根据用户反馈或市场需求增加新功能。
  • 优化性能:优化代码以降低用户交互的Gas成本。

风险与挑战

  1. 中心化与信任问题:升级权限若由单一方控制,则违背了去中心化精神。用户必须信任开发者不会作恶。
  2. 复杂性带来新风险:升级机制本身可能引入新的漏洞(如存储冲突、初始化攻击等),历史上因此发生过重大安全事件。
  3. 实际使用谨慎:一项研究分析超过4400万个以太坊合约后发现,只有约3% 的合约具有可升级特性,而其中真正执行过升级的只有0.34% ,表明开发者对此态度非常审慎。

🛡️ 如何保障安全与去中心化?

负责任的智能合约升级管理通常结合以下措施:

  • 去中心化治理:将升级决定权交由去中心化自治组织投票决定。
  • 时间锁:在提案通过和执行之间设置延迟,给予用户反应时间。
  • 多签钱包:使用多重签名钱包管理升级权限,避免单点故障。

总的来说,可升级智能合约是区块链开发中一个强大但需慎用的工具。如果你对某个特定升级模式(如UUPS)的具体实现或现实案例有兴趣,我可以为你提供更深入的分析。

Polkadot课程内容

Memory Layout内存布局
Assembly code汇编代码
Delegate Call代理调用
Upgradable Contract可升级合约

这像是一个关于区块链底层开发的进阶课程大纲,这些主题层层递进,最终目标是理解并实现可升级智能合约。我们可以把它们看作一个完整知识体系的四个模块:

flowchart TD
    A[Polkadot合约开发进阶知识体系] --> B[模块一:内存布局]
    A --> C[模块二:汇编代码]
    A --> D[模块三:代理调用]
    A --> E[模块四:可升级合约]

    B --> F[基础<br>理解合约中数据的物理存储方式]
    C --> G[工具<br>使用底层工具读取/操作内存与调用]
    D --> H[核心机制<br>实现跨合约执行并保持状态的关键]
    F & G & H --> I[最终目标<br>构建可安全升级的智能合约系统]
    
    I --> E

下面我为你详细拆解每个模块的核心内容和它们之间的联系。

🧩 模块一:内存布局

这是理解后续所有概念的基础。在像Solidity这样的智能合约语言中,变量并不是随意存储在内存或存储中的,而是遵循极其严格的规则。你需要掌握:

  • 存储(Storage)布局:状态变量如何根据声明的顺序,在持久化存储中被分配到特定的256位“插槽”中。这是代理合约可升级性的基石,因为升级前后的新旧逻辑合约必须保持完全一致的存储布局,否则状态变量会发生灾难性的错乱。
  • 内存(Memory)与调用栈(Callstack)布局:理解临时变量、函数参数、返回值在易失性内存调用栈中的排布方式。这对于理解低级调用(如 delegatecall)时数据如何传递至关重要。

⚙️ 模块二:汇编代码

要深入理解合约如何与以太坊虚拟机交互,以及 delegatecall 等操作码的本质,就需要接触汇编代码(在以太坊中通常是 Yul内联汇编)。

  • 这是你直接操作内存、存储和调用栈的工具。
  • 通过汇编,你可以精确控制合约的行为,例如:在 delegatecall 执行时,手动管理调用上下文(如 msg.sendermsg.value)的保留,或优化Gas消耗。

🔗 模块三:代理调用

这是实现可升级合约的核心机制delegatecall 是一个特殊的底层调用操作码。它的关键特性是:

我借用你的代码,在我的上下文中执行。”

  • 调用者(代理合约) 提供执行环境(存储、余额、地址等)。
  • 被调用者(逻辑合约) 仅提供代码逻辑。
  • 执行结果是,逻辑合约的代码对代理合约的存储进行读写。这是实现逻辑与数据分离,从而实现无缝升级的关键。

🏗️ 模块四:可升级合约

这是前三项技术的综合应用与最终目标。你将学习如何组合这些知识,设计出安全的可升级合约架构,例如透明代理模式UUPS代理模式。核心课题包括:

  1. 如何安全地构建存储布局,避免升级时的存储冲突。
  2. 如何正确地使用 delegatecall 并通过汇编确保上下文正确
  3. 如何设计升级权限管理(如时间锁、多签、DAO治理),在灵活性与去中心化之间取得平衡。
  4. 了解并规避常见风险,如“函数选择器冲突”、“存储槽污染”等。

总而言之,这门课程是一条从理解数据存储的“地基”(内存布局),到掌握精密的“工具”(汇编代码),再到运用核心“机关”(代理调用),最终建造出可升级“大厦”(可升级合约)的完整路径。

如果你对其中某个模块的具体实现细节或实际代码示例有进一步兴趣,我可以为你提供更深入的解释。

为什么要使用Assembly code?

gas优化
low-level访问
复杂运算
自定义错误捕捉
裸合约交互


您列出的正是使用汇编代码(Assembly/Yul)在智能合约开发中的核心原因。它们可以看作是为了追求极致效率、精细控制和解决高级需求而选择的一条“专家路径”。

下面为您详细拆解每个原因背后的含义,并结合实例说明:

核心原因解析

主要原因 核心目标 典型应用场景
Gas优化 极致节省燃料成本 循环操作、批量处理、替代高阶Solidity函数
Low-Level访问 直接操作EVM 实现 delegatecall、精确内存操作、自定义存储布局
复杂运算 执行Solidity不支持的算法 高级数学运算(如指数、对数)、自定义加密算法
自定义错误捕捉 提供更优的错误信息 定义可携带动态数据的错误码,便于前端解析
裸合约交互 与任意合约交互 调用无接口的合约、处理非常规返回值

🔥 Gas优化(最普遍的原因)

Solidity编译器生成的字节码有时并非最优。手写汇编可以:

  • 移除安全检查:比如手动跳过数组边界检查(需自行确保安全)。
  • 直接操作内存和存储:减少中间变量和不必要的复制。
  • 精细控制操作码:用更廉价的操作码组合实现相同功能。

    示例:在一个循环中密集读写存储变量时,用汇编直接操作存储槽(sstore/sload)可能比Solidity循环节省大量Gas。

🛠️ Low-Level访问(实现高级模式的基础)

这是实现复杂设计模式的关键工具,与您课程中其他模块紧密相关:

  • 实现 delegatecall:这是构建可升级代理合约的核心。汇编用于确保调用上下文(如 msg.sender, msg.value)正确传递。
  • 精确内存布局:当需要与其他合约进行底层交互,或实现自定义的ABI编码/解码时,需要直接操作内存的特定区域。
  • 访问特定操作码:Solidity未暴露所有EVM操作码(如 bitwise 移位、某些密码学操作码),汇编可直接使用。

🧮 复杂运算

当合约需要执行Solidity语法或内置库不直接支持的数学运算时(例如:定点小数的高精度计算、特定的加密算法或哈希函数),汇编是唯一的实现手段。

⚠️ 自定义错误捕捉

Solidity 的 require(reason)revert(reason) 中的字符串错误信息会消耗大量Gas。使用汇编配合自定义错误类型,可以:

  • 定义高效、可携带数据的错误码
  • revert CustomError(arg1, arg2);
  • 这能显著节省Gas,并提供机器可读的错误信息,便于前端或监控工具解析。

🤝 裸合约交互

当需要与一个没有正式接口(Interface)或行为不标准的合约交互时(例如:处理非标准的ERC20代币转账返回值),汇编的 callstaticcall 允许你:

  • 绕过Solidity的类型检查,直接构造调用数据。
  • 手动解析原始返回数据,处理各种边缘情况。

⚠️ 重要权衡与警示

使用汇编是一把双刃剑,带来强大能力的同时也伴随巨大风险:

  • 安全性极易引入致命漏洞(如存储损坏、重入攻击)。一行错误的汇编代码可能摧毁整个合约。
  • 可读性与维护性:代码几乎不可读,极难调试和维护,对团队技术要求极高。
  • 兼容性:高度依赖EVM底层细节,不同EVM版本或区块链(如Polygon, Arbitrum)之间可能存在细微差异。

最佳实践建议:仅在以下情况谨慎考虑使用汇编,并务必进行严格审计和测试:

  1. 明确的、可衡量的Gas节省需求(如核心的、被频繁调用的函数)。
  2. 实现无法用高级Solidity完成的特定功能(如代理合约、特定算法)。
  3. 充分理解EVM和Solidity内存模型

总结来说,汇编是用于解决特定高性能、高定制化场景的专业工具,而非日常开发的首选。 如果您对如何用汇编实现 delegatecall 或进行具体的Gas优化有进一步兴趣,我可以提供更具体的代码示例或模式分析。

call与delegate call的区别

理解 calldelegatecall 的区别,是掌握以太坊智能合约高级编程(尤其是可升级合约)的绝对核心。它们都是底层调用操作,但执行上下文完全不同。

为了方便你快速理解,我将核心区别总结为下表:

特性 普通 Call (外部调用) Delegate Call (委托调用)
代码执行者 被调用合约(Callee) 调用者合约(Caller)
存储读写 读写被调用合约的存储 读写调用者合约的存储
上下文变量 msg.sender = 调用者合约
msg.value = 调用传入的以太币
msg.sendermsg.value 保持不变
核心比喻 “请别人来他家干活” “借别人的技能,在自己家干活”
主要用途 常规合约间交互、转账 实现可升级代理合约、库合约

🤔 一个生动的比喻

想象你(合约A)想制作一把椅子。

  • 使用 call:你请木匠(合约B)到他自己的作坊,用他作坊里的工具和木头做椅子。做完后,椅子留在了他的作坊。你只得到一个结果。
  • 使用 delegatecall:你把木匠(合约B)请到你的作坊,他使用你作坊里的工具和木头做椅子。做完后,椅子留在了你的作坊。你拥有了成品和所有生产过程的记录。

关键:在 delegatecall 中,木匠(逻辑合约)只提供了“做椅子”的技能(代码),而所有材料、工具和最终产品(状态)都属于你(代理合约)。


📝 核心概念详解

1. 普通 Call

  • 工作原理:它通知EVM:“现在跳转到另一个合约的地址,开始执行它的代码。” 这是一个标准的、上下文完全切换的调用。
  • 示例:合约A call 合约B的 setValue(5)
    • 执行者:合约B的代码。
    • 影响的存储:合约B的存储变量 value 被改为 5
    • msg.sender:是合约A的地址。
  • 这是最常用、最安全的交互方式

2. Delegate Call

  • 工作原理:它通知EVM:“去另一个合约那里取它的代码过来,但在我当前的环境中执行。” 执行上下文(包括存储、余额、地址等)完全保留给调用者
  • 示例:合约A(代理)delegatecall 合约B(逻辑)的 setValue(5)
    • 执行者:合约B的代码。
    • 影响的存储:合约A的存储槽(slot)被修改,如同合约A自己执行了 setValue(5) 一样。合约B的存储完全未被触及
    • msg.sender:是最初发起调用的用户地址,而不是合约A的地址。
  • 这是实现可升级合约的基石:代理合约的存储永远不变,但通过更换 delegatecall 的目标地址,就能改变其行为逻辑。

⚠️ 使用 Delegate Call 的关键风险

正是因为它强大的特性,也带来了独有的高风险:

  1. 存储布局必须绝对一致:逻辑合约(被调用者)的变量声明顺序和类型,必须与代理合约(调用者)的存储布局100%匹配。如果升级后的新逻辑合约改变了存储布局,将会永久性地破坏代理合约的所有数据
  2. 极高的安全复杂性:攻击者可能利用 delegatecall 的上下文特性,结合存储冲突进行攻击。著名的“TheDAO”重入攻击就与此有关。
  3. 需要汇编知识:为了正确处理 delegatecall 并避免函数选择器冲突,通常会配合使用汇编代码(如 assembly { ... })。

💡 如何选择?

  • 绝大多数情况,使用普通 call:它语义清晰,安全边界明确。
  • 仅在构建特定模式时,才使用 delegatecall
    • 构建可升级的代理合约(如Transparent Proxy, UUPS)。
    • 构建库合约(如Solidity的 library,其底层就是 delegatecall),用于在多个合约间复用无状态代码。

简单来说,call 是实现“功能组合”的工具,而 delegatecall 是实现“代码附着于状态”的工具。理解它,你就握住了理解现代复杂DApp架构的钥匙。

如果你对具体的存储布局如何保持同步,或想查看一个简单的可升级合约的代码实现,我可以为你进一步解释。

为什么用Foundry?

在黑客松这种高压环境下,Foundry 绝对比 Hardhat 更方便且更强大。

Foundry 是基于 Rust 开发的,它的编译速度极快,且允许你用 Solidity 直接写测试脚本(无需切换到 JS/TS),这能为你和 JW 节省大量调试时间。

以下是针对波卡测试 Hub(通常兼容 EVM,如 Asset Hub 或 Moonbase Alpha)设计的**可升级合约(使用透明代理模式)**实战方案。

  1. 技术栈选择:为什么选 Foundry?
    速度: 编译和测试几乎瞬发。

Cheatcodes: 可以直接在测试中模拟时间、地址和任意状态。

部署: forge script 比 Hardhat 的脚本更直观。

  1. 合约设计(透明代理模式)
    你需要安装 OpenZeppelin 的升级库: forge install OpenZeppelin/openzeppelin-contracts-upgradeable

命令

git clone https://github.com/papermoonio/2026-h1-building-production-grade-dApps.git
cd /home/kali/Desktop/2026-h1-building-production-grade-dApps/homework/lesson-4/2070
forge install OpenZeppelin/openzeppelin-contracts-upgradeable

Moonbase Alpha 测试网部署

Moonbase Alpha 测试网部署,Foundry 确实比 Hardhat 快很多,非常适合你现在的黑客松节奏。

1. Moonbase Alpha 水龙头 (Faucet)

你可以通过以下两个最可靠的渠道领取 DEV 代币(Moonbase Alpha 的测试币):

  • 官方 Discord 水龙头(推荐,额度较稳定):
  1. 加入 Moonbeam Discord
  2. 找到 #moonbase-alpha-faucet 频道。
  3. 输入指令:!faucet send 你的钱包地址
  • Mission Control 网页水龙头:
    访问 Moonbase Alpha Faucet。输入地址即可领取,通常每 24 小时可以领一次。

2. Foundry 快速配置建议

既然你已经在 Kali 的目录下准备好了合约文件,为了让 forge 正常工作,你需要确保项目初始化并安装了 OpenZeppelin 库:

1
2
3
4
5
6
7
# 在当前目录下初始化(如果还没做的话)
forge init --force

# 安装 OpenZeppelin 升级库
forge install OpenZeppelin/openzeppelin-contracts-upgradeable --no-commit
forge install OpenZeppelin/openzeppelin-contracts --no-commit

3. Moonbase Alpha 的网络参数 (RPC)

在你的 .env 文件或部署脚本中,使用以下参数:

  • Network Name: Moonbase Alpha
  • RPC URL: https://rpc.api.moonbase.moonbeam.network
  • Chain ID: 1287
  • Symbol: DEV
  • Block Explorer: Moonscan

4. 部署命令参考

当你领到水后,可以使用以下命令部署:

1
2
forge script script/Deploy.s.sol:DeployScript --rpc-url https://rpc.api.moonbase.moonbeam.network --broadcast --legacy

(注意:波卡生态的一些 EVM 链有时对 EIP-1559 支持不稳定,加上 --legacy 参数可以确保交易通过。)


给你的黑客松小贴士:

如果你和 JW 想要在演示时显得项目非常“成熟”,部署完后可以在 Moonscan 上验证合约代码。Foundry 支持一键验证:
--verify --verifier etherscan --verifier-url https://api-moonbase.moonscan.io/api --etherscan-api-key 你的APIKey

水领到了吗? 如果在 Moonbase 部署遇到逻辑执行错误(比如 initialize 函数报错),那通常是代理合约的构造逻辑问题,随时发给我看报错日志。

生成钱包地址,查询余额

查询钱包地址
cast wallet new


查询余额
cast balance 0x9F75b6674128CADA1C2bDc9a0a3B72ec9E4625a0 –rpc-url https://rpc.api.moonbase.moonbeam.network

Warning: This is a nightly build of Foundry. It is recommended to use the latest stable version. To mute this warning set FOUNDRY_DISABLE_NIGHTLY_WARNING in your environment.

1100000000000000000

1.1个DEV

问题一

forge install OpenZeppelin/openzeppelin-contracts –no-commit 请问这个–no-commit是什么意思?


在 Foundry 中使用 forge install 时,添加 --no-commit 标志的意思是:在安装外部库后,不要自动执行 git commit

这对于你目前的黑客松项目管理非常重要,原因如下:

1. 为什么会有这个标志?

默认情况下,forge install 的行为是:

  1. 检测你是否在 Git 仓库中。
  2. 将库作为 git submodule(子模块)添加。
  3. 自动创建一个提交,备注类似于 “Installed OpenZeppelin/…”。

如果你在一个尚未提交过任何代码的新仓库,或者你想要手动管理提交记录(比如你之前提到的删库重建,需要精细控制时间线),自动提交可能会打乱你的节奏。

2. --no-commit 的实际作用

  • 不产生历史: 它只会把代码下载到 lib/ 目录下,并修改 .gitmodules 文件,但不会在你的 Git 记录里留下那一行自动生成的提交。
  • 方便重构: 你可以在安装完所有库(OpenZeppelin 等)后,一次性把它们和你的核心代码(VaultV1.sol)打包在一个 initial commit 里。

3. 给你的黑客松操作建议

既然你在 Kali 下操作,且可能需要快速迭代:

  • 如果你希望手动控制进度:
    使用 --no-commit。这样你可以先安装完所有依赖,然后运行:
1
2
3
git add .
git commit -m "feat: initial project setup with OpenZeppelin libraries"

  • 如果你遇到了 Git 报错:
    有时候在 Kali 环境下,如果没有配置 git config user.email,不加 --no-commit 会导致安装失败。

4. 补充一个重要步骤:映射 (Mappings)

安装完后,为了让你的合约(VaultV1.sol)能找到这些库,建议检查一下项目根目录下的 remappings.txt。如果没有,可以运行:

1
2
forge remappings > remappings.txt

这样你的 import "@openzeppelin/contracts-upgradeable/..." 才能在编译时正确跳转。


顺便问一下,JW 那边开始写前端对接了吗? 如果需要 check.ts 运行,别忘了先 npm install ethers。如果你在 Moonbase Alpha 领水遇到了困难(比如 Discord 机器人没反应),告诉我,我帮你看看有没有备用的 RPC 节点。

问题二

使用–no-commit参数报错

1
2
3
4
5
6
7
8
9
10
11
12
└─# forge install OpenZeppelin/openzeppelin-contracts --no-commit
error: unexpected argument '--no-commit' found

tip: a similar argument exists: '--commit'

Usage: forge install [OPTIONS] [DEPENDENCIES]...
forge install [OPTIONS] <github username>/<github project>@<tag>...
forge install [OPTIONS] <alias>=<github username>/<github project>@<tag>...
forge install [OPTIONS] <https://<github token>@git url>...)]
forge install [OPTIONS] <https:// git url>...

For more information, try '--help'.

看来你的 Foundry 版本更新了逻辑:现在的版本默认就是“不自动提交”的,或者该参数名发生了变动。 这在黑客松里是个好消息,说明你少了一个需要担心的参数。根据你收到的 Usage 提示,直接去掉那个标志即可。

1. 修正后的安装命令

在你的项目根目录下直接运行:

1
2
3
4
5
6
# 安装 OpenZeppelin 合约库
forge install OpenZeppelin/openzeppelin-contracts

# 安装 OpenZeppelin 升级版合约库 (你做可升级合约必装这个)
forge install OpenZeppelin/openzeppelin-contracts-upgradeable

2. 为什么会报错?

在较新版本的 Foundry 中,它不再强制要求进行 git commit。如果你想让它自动提交,反而要加 --commit。现在的默认行为更符合开发者的直觉:只下载文件,把提交权交给你。

3. 安装后的关键一步:配置重映射 (Remappings)

安装完成后,你的 VaultV1.sol 可能会因为找不到路径而报错。请务必执行以下操作来生成路径映射文件:

1
2
forge remappings > remappings.txt

执行完后,打开 remappings.txt 检查一下,确保里面包含类似:
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/

4. 编译测试

配置好后,运行一下:

1
2
forge build

如果编译通过,说明你的依赖已经安装并映射成功。


黑客松进度预判

你现在已经到了环境搭建的最后一步。一旦 forge build 成功,你就可以:

  1. 填入 Moonbase Alpha 的 RPC 开始部署 V1。
  2. **让 JW 开始跑 check.ts** 监控状态。
  3. 执行 upgrade 逻辑 完成波卡课程的编程题。

水领到了吗? 如果 forge build 报错说找不到库,通常是因为 lib 目录下的文件夹名称和 import 语句里的不一致,这时候告诉我,我帮你改 remappings.txt

问题三

1
2
3
4
5
6
7
8
9
10
11
└─# source .env
forge script script/Deploy.s.sol:DeployScript --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast --legacy
Warning: This is a nightly build of Foundry. It is recommended to use the latest stable version. To mute this warning set `FOUNDRY_DISABLE_NIGHTLY_WARNING` in your environment.

Error: error sending request for url (https://rpc.api.moonbase.moonbeam.network/)

Context:
- Error #0: client error (SendRequest)
- Error #1: connection error
- Error #2: peer closed connection without sending TLS close_notify: https://docs.rs/rustls/latest/rustls/manual/_03_howto/index.html#unexpected-eof

网络不稳定导致部署失败

1
2
3
┌──(root㉿kali)-[/home/…/2026-h1-building-production-grade-dApps/homework/lesson-4/2070]
└─# curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' $RPC_URL
{"jsonrpc":"2.0","id":1,"result":"0xe39b36"}

测试后发现rpc节点是稳定的。

问题四

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌──(root㉿kali)-[/home/…/2026-h1-building-production-grade-dApps/homework/lesson-4/2070]
└─# forge script script/Deploy.s.sol:DeployScript \
--rpc-url $RPC_URL \
--private-key $PRIVATE_KEY \
--broadcast \
--legacy \
--slow
Warning: This is a nightly build of Foundry. It is recommended to use the latest stable version. To mute this warning set `FOUNDRY_DISABLE_NIGHTLY_WARNING` in your environment.

[⠊] Compiling...
[⠒] Unable to resolve imports:
"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol" in "/home/kali/Desktop/2026-h1-building-production-grade-dApps/homework/lesson-4/2070/src/VaultV1.sol"
with remappings:
@openzeppelin/contracts/=/home/kali/Desktop/2026-h1-building-production-grade-dApps/homework/lesson-4/2070/lib/openzeppelin-contracts/contracts/
erc4626-tests/=/home/kali/Desktop/2026-h1-building-production-grade-dApps/homework/lesson-4/2070/lib/openzeppelin-contracts/lib/erc4626-tests/
forge-std/=/home/kali/Desktop/2026-h1-building-production-grade-dApps/homework/lesson-4/2070/lib/forge-std/src/
halmos-cheatcodes/=/home/kali/Desktop/2026-h1-building-production-grade-dApps/homework/lesson-4/2070/lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/
openzeppelin-contracts/=/home/kali/Desktop/2026-h1-building-production-grade-dApps/homework/lesson-4/2070/lib/openzeppelin-contracts/
[⠔] Compiling 32 files with Solc 0.8.30
[⠒] Solc 0.8.30 finished in 266.18ms
Error: Compiler run failed:
Error (6275): Source "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol" not found: File not found. Searched the following locations: "/home/kali/Desktop/2026-h1-building-production-grade-dApps/homework/lesson-4/2070".
ParserError: Source "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol" not found: File not found. Searched the following locations: "/home/kali/Desktop/2026-h1-building-production-grade-dApps/homework/lesson-4/2070".
--> src/VaultV1.sol:4:1:
|
4 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


这个错误是因为你的 remappings.txt 中缺少了对 OpenZeppelin 升级库 (contracts-upgradeable) 的显式定义。

虽然你安装了库,但编译器在看到 import "@openzeppelin/contracts-upgradeable/..." 时,不知道应该去 lib/openzeppelin-contracts-upgradeable 文件夹里找。

1. 快速修复:手动添加重映射

请直接运行以下命令,强行将路径写入 remappings.txt

1
2
3
echo "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/" >> remappings.txt
echo "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/" >> remappings.txt

2. 检查 remappings.txt

运行完后,请确保你的 remappings.txt 内容看起来像这样:

1
2
3
4
@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
forge-std/=lib/forge-std/src/

3. 如果还是报错(黑客松暴力解法)

如果上面的映射无效,可能是因为文件夹名称略有不同(比如多了一个 v 或者版本号)。你可以列出 lib 目录查看:

1
ls lib

确保文件夹名称确实是 openzeppelin-contracts-upgradeable

4. 重新编译并部署

一旦映射正确,运行 forge build 应该就不会报错了。然后再次执行部署命令:

1
2
3
4
5
6
forge script script/Deploy.s.sol:DeployScript \
--rpc-url $RPC_URL \
--private-key $PRIVATE_KEY \
--broadcast \
--legacy \
--slow

为什么会出现这个问题?

当你使用 forge install 安装普通版和升级版两个库时,Foundry 有时只会自动生成其中一个的映射。由于你写的是“可升级合约”,必须明确指向 contracts-upgradeable 目录。

等你的 V1 部署成功,拿到 Proxy 地址后,告诉我一声。 我们要赶在黑客松结束前,让 JW 运行 check.ts 看到 Version 1,然后迅速执行 V2 的升级。

另外,你刚才领到水(DEV 代币)了吗? 部署代理合约组(实现合约 + Admin + Proxy)需要一定的余额,如果余额不足,部署到一半会断掉。

问题五

1
2
3
4
5
6
7
8
9
└─# forge script script/Deploy.s.sol:DeployScript \
  --rpc-url $RPC_URL \
  --private-key $PRIVATE_KEY \
  --broadcast \
  --legacy \
  --slow
error: a value is required for '--fork-url <URL>' but none was supplied

For more information, try '--help'.

这是因为 Foundry 的脚本执行环境(Forge Script) 默认需要在本地模拟一遍执行过程,而你使用的是 vm.startBroadcast() 模式,它需要通过 --fork-url(或者早期的 --rpc-url)来获取链上的状态。

在最新的 Foundry 版本中,如果你使用了脚本模式且包含 vm 相关操作,它强制要求指定 fork 环境

1. 修正命令(添加 --fork-url

直接把你的 $RPC_URL 同时也赋值给 --fork-url。这样 Forge 会先在本地分叉(Fork)一份 Moonbase Alpha 的状态,模拟运行成功后,再通过 --broadcast 发送到真实网络。

1
2
3
4
5
6
7
forge script script/Deploy.s.sol:DeployScript \
--rpc-url $RPC_URL \
--fork-url $RPC_URL \
--private-key $PRIVATE_KEY \
--broadcast \
--legacy \
--slow

2. 为什么需要两个 URL?

  • --fork-url: 用于 本地模拟。Forge 会根据这个 URL 拉取链上数据(比如 ProxyAdmin 是否已存在),确保你的脚本逻辑在本地能跑通。
  • --rpc-url: 用于 真实广播。模拟通过后,Forge 会用这个 URL 把交易发出去。
    (注:通常这两个填同一个地址即可。)

3. 如果还是报错 Unable to find implementation

这通常是因为 Deploy.s.sol 里的 import 路径在文件移动后失效了。既然你已经把合约移到了 src,请检查 script/Deploy.s.sol 的开头:

1
2
import "../src/VaultV1.sol";
import "../src/VaultV2.sol";

4. 你的 1.1 DEV 足够了

只要模拟通过,Forge 就会开始正式向 Moonbase Alpha 发送交易。你会看到类似下面的输出:

  • ## Sending transactions...
  • Transaction: 0x...
  • Contract Address: 0x...

等部署完成,拿到那个 TransparentUpgradeableProxy 的地址,立刻丢给 JW。 只要 check.ts 跑通显示 Version: 1,你这个波卡开发课的编程题就完成了一大半,剩下的升级操作只是几行命令的事!

黑客松现场环境嘈杂,别被这些小报错搞心态,这都是 Foundry 的常规脾气,解决掉这个参数问题就起飞了。

部署成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
┌──(root㉿kali)-[/home//2026-h1-building-production-grade-dApps/homework/lesson-4/2070]
└─# forge script script/Deploy.s.sol:DeployScript \
--rpc-url https://rpc.api.moonbase.moonbeam.network \
--broadcast \
--legacy \
--slow \
--interactive
Warning: This is a nightly build of Foundry. It is recommended to use the latest stable version. To mute this warning set `FOUNDRY_DISABLE_NIGHTLY_WARNING`
in your environment.

[⠢] Compiling...
[⠒] Compiling 18 files with Solc 0.8.30
[⠢] Solc 0.8.30 finished in 1.80s
Compiler run successful with warnings:
Warning (2072): Unused local variable.
--> script/Deploy.s.sol:22:9:
|
22 | TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Warning: EIP-3855 is not supported in one or more of the RPCs used.
Unsupported Chain IDs: 1287.
Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly.
For more information, please see https://eips.ethereum.org/EIPS/eip-3855
Script ran successfully.

## Setting up 1 EVM.

==========================

Chain 1287

Estimated gas price: 0.03125 gwei

Estimated total gas used for script: 3115922
==========================
Enter private key:

##### moonbase
✅ [Success] Hash: 0x983548211732205e604787b7aa046b7b3774a0ceb597b6596279a4cc1142d045
Contract Address: 0x82C349e21761f044147b12BbD9E64513Bb46D974
Block: 14917329
Paid: 0.0000146514375 ETH (468846 gas * 0.03125 gwei)


##### moonbase
✅ [Success] Hash: 0x27c8f7bfc8247be012f468b639c37d91f63af6c09e93a0195e53bd80bfe99f6e
Contract Address: 0x2e7e268eF5A4181692c10f39E4e7d0F0E6828E8d
Block: 14917330
Paid: 0.0000247393125 ETH (791658 gas * 0.03125 gwei)


##### moonbase
✅ [Success] Hash: 0x68f245b40f1cd62de640838693f7cf9dbebb26d5a4a74a0d748f56c5a85c2186
Contract Address: 0x88fe9f8D8660626c9F894920A60ef9E47A1FACB5
Block: 14917331
Paid: 0.0000527840625 ETH (1689090 gas * 0.03125 gwei)

✅ Sequence #1 on moonbase | Total Paid: 0.0000921748125 ETH (2949594 gas * avg 0.03125 gwei)


==========================

ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.

Transactions saved to: /home/kali/Desktop/2026-h1-building-production-grade-dApps/homework/lesson-4/2070/broadcast/Deploy.s.sol/1287/run-latest.json

Sensitive values saved to: /home/kali/Desktop/2026-h1-building-production-grade-dApps/homework/lesson-4/2070/cache/Deploy.s.sol/1287/run-latest.json

太好了!🎉 更换为官方的 https://rpc.api.moonbase.moonbeam.network 端点后,你的部署脚本已成功执行。这表明之前的问题确实是由原公共RPC端点临时不可用导致的。

✅ 部署结果摘要

你的智能合约已成功部署到 Moonbase Alpha测试网(Chain ID: 1287)。关键信息如下:

项目 详情
部署网络 Moonbase Alpha (测试网)
交易哈希1 0x983548...2d045 → 合约地址: 0x82C349...46D974
交易哈希2 0x27c8f7...99f6e → 合约地址: 0x2e7e26...82E8d
交易哈希3 0x68f245...c2186 → 合约地址: 0x88fe9f...1FACB5
总费用 约 0.000092 ETH (测试网代币,无实际价值)
交易记录 已保存至 /home/.../broadcast/Deploy.s.sol/1287/run-latest.json

📝 输出信息解读

你看到的几个警告是提示信息,不影响本次部署:

  • Unused local variable:这是Solidity编译器的常规代码优化提示,表示脚本中有一个变量未使用,可以忽略或后期优化代码。
  • EIP-3855 is not supported:这是因为Moonbase网络尚未支持该EIP,但你的合约版本(0.8.30)兼容性很好,当前部署完全正常,不影响功能。未来若部署到生产环境(如Moonbeam主网),可再确认兼容性。

🚀 后续建议操作

部署成功后,建议你按以下步骤验证和跟进:

  1. 立即验证交易

    • 打开 Moonbase Alpha区块浏览器 (Moonscan)
    • 将任意一个交易哈希(如 0x983548...2d045)或合约地址粘贴搜索。
    • 确认交易状态为 “Success”,并查看合约的创建详情。
  2. 检查部署详情

    • 打开生成的文件 run-latest.json,其中包含了所有构造函数参数、发送地址等更详细的交易数据,对后续验证很有用。
  3. 与合约进行交互

    • 你可以使用 Foundry 的 cast 命令、在 Remix 中连接已部署的合约地址,或编写前端脚本,来调用合约的函数,测试其功能是否正常。
  4. 安全提醒

    • 部署时使用的私钥(你交互输入的)信息已保存在 cache/ 目录下的文件中。请务必妥善保管该文件,不要将其提交到公开的代码仓库

💎 总结

核心问题已解决。你已成功将合约部署至测试网,可以开始下一步的测试和验证工作了。

如果需要在其他网络(如以太坊主网、Sepolia测试网等)部署,或者想了解如何验证合约源代码,我可以为你提供进一步的指导。