Polkadot Solidity开发-课程笔记13

第四课 Task4 任务:
1⃣️选择题:https://wj.qq.com/s2/25539056/yfun/
2⃣️实践题:https://github.com/papermoonio/2026-h1-building-production-grade-dApps/blob/main/homework/lesson-4/README.md

关于内存的排列,以下哪一项描述是正确的?

A. Slot的大小只需要是8的倍数
B. 不同的存储单元可以放在同一个Slot里面
C. 动态大小的类型不需要额外的信息
D. 当前可分配内存指针存放在寄存器中

B. 不同的存储单元可以放在同一个Slot里面
(正确答案)

豆包AI的错误核心解析

这道题聚焦Polkadot/Substrate 合约开发(如ink!)的底层内存模型,并非此前的Cairo虚拟机,核心围绕区块链通用内存设计+Polkadot专属内存规则展开,当前可分配内存指针存放在寄存器中是唯一正确描述,也是各类虚拟机/执行环境的通用底层设计。

Polkadot的合约执行基于Substrate的Wasm虚拟机(合约最终编译为Wasm运行),而Wasm虚拟机对内存的动态分配、寻址完全依赖寄存器——专门的寄存器会存储当前可分配内存指针(也叫内存游标/分配指针),标记下一个可分配的内存起始地址,内存分配、数据写入都会基于该寄存器的指针值执行,分配后指针自动偏移更新,这是Polkadot合约底层内存操作的核心逻辑。

选项排除逻辑(Polkadot/Substrate 内存规则专属解析)

A错误:Slot的大小只需要是8的倍数

Polkadot/Substrate中,内存Slot(内存槽,最小寻址/分配单元)的大小是固定按64位(8字节)对齐,而非「只需要是8的倍数」(比如16、32字节并非合法Slot大小)。
这里的关键是强制8字节对齐(Slot固定8字节),是Substrate为适配Wasm虚拟机的64位指令集、保证内存访问效率的硬性规范,而非灵活的倍数要求。

B错误:不同的存储单元可以放在同一个Slot里面

Polkadot的内存是线性连续的寻址结构每个存储单元对应唯一的Slot/内存地址,Slot是内存的最小操作单元,不同存储单元会被分配到不同的Slot地址中。
若多个存储单元共享一个Slot,会直接导致数据覆盖、寻址混乱,Substrate的Wasm虚拟机也做了严格的内存隔离,从底层杜绝了这种情况。

C错误:动态大小的类型不需要额外的信息

Polkadot的ink!合约中,动态大小类型(如Vec<T>String、动态字节数组)必须携带额外元信息,核心是长度信息(部分还会携带容量信息)。
因为Wasm虚拟机无法预知动态类型的实际内存占用大小,必须通过首地址存储的长度信息确定数据的内存寻址边界,否则会触发内存越界、数据解析失败,这也是所有区块链合约开发中动态类型的通用规则。

补充:Polkadot/ink! 内存模型核心考点(适配本题)

  1. 寄存器与内存指针:Wasm虚拟机的通用寄存器负责存储内存分配指针、栈指针等核心地址,所有内存操作均基于寄存器中的指针值,是底层执行的基础;
  2. 内存Slot规则:固定64位(8字节)且强制对齐,是Substrate为Wasm虚拟机做的专属优化,提升内存访问和指令执行效率;
  3. 动态类型内存设计:ink!中的动态类型(如Vec)遵循「长度+实际数据」的存储结构,长度作为额外元信息存在首地址,是解析动态数据的必要条件。

总结

  1. 本题针对Polkadot/Substrate内存模型,唯一正确描述为D(当前可分配内存指针存放在寄存器中),这也是Wasm虚拟机的通用底层设计;
  2. 避坑核心:Polkadot的Slot是固定8字节(64位)对齐而非8的倍数、存储单元与Slot一对一寻址、动态类型必须携带长度等额外信息
  3. 该考点是ink!合约开发的底层基础,也是Polkadot生态面试/考题的高频知识点。

在Solidity中,call和delegatecall的区别是什么?, 单选

A. call在调用合约时保留了上下文的状态,而delegatecall则更改了上下文
B. delegatecall会创建新的合约实例
C. call保持合约上下文,而delegatecall在调用合约时使用调用者上下文
D. call总是返回布尔值,而delegatecall不返回值

正确答案:C

核心解析

这是Solidity中合约间调用的核心考点,calldelegatecall 最本质的区别在于执行目标合约代码时,使用的「上下文(合约地址/状态存储)」不同,选项C精准概括了这一核心差异:
call普通的合约外部调用,执行目标合约代码时,全程使用目标合约自身的上下文(操作的是目标合约的状态变量、以目标合约地址为msg.sender);
delegatecall委托调用,执行目标合约代码时,会借用调用者(发起委托的合约)的上下文(操作的是调用者的状态变量、以调用者的调用方为msg.sender),目标合约仅作为「代码逻辑的提供者」。

简单理解:

  • A.call(B):让B的代码在B的地盘执行,改B的状态;
  • A.delegatecall(B):让B的代码在A的地盘执行,改A的状态。

选项排除逻辑

A错误

描述完全颠倒——call 不会保留调用者上下文,而是使用目标合约上下文;delegatecall 并非更改上下文,而是直接使用调用者的上下文执行目标合约代码。

B错误

delegatecall 只是委托执行目标合约的现有代码,不会创建任何新的合约实例,也不会部署新合约。合约实例的创建只有new 合约名()create2两种方式,与委托调用无关。

D错误

calldelegatecall 是Solidity中同类型的底层调用方法,返回值规则完全一致:均返回**(bool, bytes)** 元组——第一个布尔值表示调用是否成功,第二个字节数组是调用的返回数据(可解析为具体结果)。二者都能返回布尔值,也都能获取返回数据。

补充:Solidity中call/delegatecall核心对比(开发必记)

特性 call(普通调用) delegatecall(委托调用)
执行上下文 目标合约的上下文(改目标状态) 调用者的上下文(改调用者状态)
核心用途 调用其他合约的普通方法、转账ETH 实现库合约(Library) 调用、代理合约(Proxy)逻辑
msg.sender/msg.value 指向发起call的合约/账户 继承原调用链的msg.sender/msg.value
返回值 (bool, bytes) (成功标识+返回数据) (bool, bytes) (与call完全一致)

典型应用:OpenZeppelin的代理合约(如TransparentProxy)就是基于delegatecall实现——代理合约作为调用者,委托逻辑合约的代码在自身上下文中执行,实现「逻辑与存储分离」的可升级合约。

总结

  1. calldelegatecall的核心区别是执行代码时的上下文不同call用目标合约上下文,delegatecall用调用者上下文(对应选项C);
  2. 二者均返回(bool, bytes),都不会创建新合约实例;
  3. delegatecall的核心价值是实现库合约、可升级代理合约,使用时需严格校验目标合约地址,避免重入/状态篡改漏洞。

Solidity中的assembly code主要用于什么目的?

A. 提高合约的可读性
B. 对智能合约进行高级功能的编码并优化字节码
C. 增加编程的复杂度
D. 使合约更容易审查

正确答案:B

核心解析

Solidity中的汇编代码(assembly code) 是直接操作EVM(以太坊虚拟机)底层的低级代码,它绕开了Solidity高级语言的部分封装,核心目的是为智能合约实现高级/底层功能,同时手动优化编译后的字节码(减小合约体积、降低Gas消耗、提升执行效率),对应选项B。

简单来说,Solidity是面向开发者的高级封装语言,但它的部分语法会对EVM能力做限制,且自动编译的字节码可能存在冗余;而汇编可以直接操作EVM的栈、内存、存储、寄存器,既能实现Solidity原生语法无法完成的底层高级功能(比如精准的内存操作、自定义的Gas控制、特殊的指令调用),又能手动精简字节码,优化合约的Gas消耗和执行效率(这是区块链合约开发的核心需求,Gas越低越友好)。

选项排除逻辑

A错误

汇编是低级底层代码,完全基于EVM指令集编写(如MLOAD/SSTORE/CALL等),语法简洁且高度依赖EVM底层知识,可读性远低于结构化的Solidity代码,反而会降低合约可读性。

C错误

增加编程复杂度是使用汇编的副作用,而非目的。开发者写汇编需要掌握EVM底层原理,编程难度和出错概率都会提升,没人会为了增加复杂度而使用汇编,复杂度只是实现功能/优化的附带结果。

D错误

汇编代码的审查难度远高于Solidity:一方面其可读性差,审计人员需要精通EVM才能理解逻辑;另一方面汇编直接操作底层存储/内存,一旦出现逻辑错误(如内存越界、存储写错槽位),更难被发现。因此汇编会让合约的审查难度大幅提升,而非更容易。

补充:Solidity中汇编的使用场景(开发高频)

Solidity支持内联汇编assembly { ... }),开发者通常会在局部关键逻辑中使用,而非整合约编写,典型场景包括:

  1. 实现Solidity原生不支持的底层功能:比如精准计算合约地址、自定义的跨合约调用逻辑、直接操作EVM的返回数据;
  2. 极致优化Gas消耗:比如精简存储操作、减少栈操作的冗余指令,适合DeFi、NFT等对Gas敏感的高频合约;
  3. 实现高级合约特性:比如可升级代理合约的底层逻辑、重入保护的精准控制、自定义的事件触发规则。

典型示例:OpenZeppelin等主流合约库中,会在核心工具函数(如地址计算、Gas优化)中嵌入内联汇编,既保证功能实现,又优化性能。

总结

  1. Solidity汇编的核心目的是实现高级/底层功能+优化字节码(减体积、降Gas)(对应选项B);
  2. 汇编的副作用是降低可读性、增加编程复杂度、提升审查难度,因此开发中遵循「能不用就不用,局部使用而非全局」的原则,仅在Solidity无法实现或需要极致优化时使用;
  3. 汇编是区块链高级开发的必备技能,主要用于合约核心逻辑的底层优化和功能拓展。

在设计可升级合约时,下述哪种模式通常被使用?

A. 静态合约
B. 单一合约模式
C. 代理合约模式
D. 自动优化合约


正确答案:C

核心解析

Solidity开发可升级合约的核心主流模式就是代理合约模式(Proxy Pattern),这也是行业通用的最佳实践,对应选项C。

可升级合约的核心需求是将合约的「状态存储」和「业务逻辑」分离,而代理合约模式正是实现这一需求的核心设计:该模式下会拆分出代理合约逻辑合约两个核心部分

  • 代理合约:作为用户唯一交互的入口,仅负责存储所有合约状态(用户余额、NFT所有权、业务配置等),且代理合约本身永不升级、代码固定
  • 逻辑合约:专门承载所有的业务逻辑代码(转账、铸造、交易规则等),支持随时替换/升级

用户所有的操作都会发送到代理合约,代理合约会通过Solidity中的delegatecall(委托调用),让新的逻辑合约代码在代理合约的上下文中执行(操作的还是代理合约里的状态),从而实现「逻辑升级但状态不丢失」的核心目标,这也是可升级合约的关键诉求。

选项排除逻辑

A错误

静态合约指的是部署后代码、逻辑完全固定,无法进行任何修改/升级的合约,这与「可升级合约」的设计需求完全相反,是不可升级合约的基础形式。

B错误

单一合约模式是将「状态存储」和「业务逻辑」写在同一个合约中,部署后合约字节码永久上链且无法修改,一旦发现漏洞或需要迭代功能,完全无法升级,只能重新部署新合约(原合约的所有状态也会丢失),不具备可升级能力。

D错误

自动优化合约只是对合约字节码、代码逻辑做Gas消耗、执行效率的优化(比如用汇编精简代码、优化存储结构),核心是「提升合约性能」,而非「实现合约可升级」,优化和升级是两个完全不同的合约开发需求,该选项与问题无关。

补充:代理合约模式的主流细分类型(行业常用)

代理合约模式是可升级合约的基础框架,业内基于此衍生出了3种成熟的细分模式,均被OpenZeppelin等主流合约库封装,开箱即用:

  1. 透明代理(Transparent Proxy):最经典的模式,区分管理员普通用户操作,避免管理员操作与用户操作的逻辑冲突,适合大部分常规DApp;
  2. UUPS代理(Universal Upgradeable Proxy Standard):目前的主流优选模式,将升级逻辑直接写在逻辑合约中,代理合约代码更精简、Gas消耗更低,适合对合约体积和Gas敏感的场景(如DeFi);
  3. 可升级的Beacon代理(Beacon Proxy):适合多个代理合约共享同一个可升级逻辑的场景(比如批量发行的NFT合约),通过Beacon合约统一管理逻辑合约地址,实现多代理的一键升级。

这三种细分模式的核心底层逻辑一致:均基于delegatecall实现「代理存状态,逻辑承业务」的分离设计。

总结

  1. 设计可升级合约的核心模式是代理合约模式(选项C),也是行业通用最佳实践;
  2. 该模式通过拆分「代理合约(存状态,不升级)」和「逻辑合约(承业务,可升级)」,结合delegatecall实现「逻辑升级、状态不丢」;
  3. 静态合约、单一合约模式均为不可升级的合约设计,自动优化合约仅针对性能,与可升级需求无关。

Diamonds在可升级合约结构中是什么?

A. 一种用于存储大数据的专用合约
B. 一种通过允许模块动态添加的代理合约模式
C. 用于加密交易的算法
D. 一种确保交易隐私的协议

正确答案:B

核心解析

Diamonds(钻石模式,也叫钻石代理模式 Diamond Proxy Pattern)是Solidity可升级合约领域进阶的代理合约模式,是对传统单逻辑合约代理模式的扩展,核心定义是允许动态添加、替换、移除模块化合约(钻石的「切面/Facet」)的代理合约模式,对应选项B。

简单来说,传统代理合约模式(如UUPS、透明代理)是一个代理合约对应一个逻辑合约,升级仅能替换整个逻辑合约;而钻石模式将合约逻辑做了极致的模块化拆分——把不同业务逻辑拆分为独立的Facet(切面合约)(比如转账Facet、铸造Facet、权限Facet),核心的钻石代理合约负责将不同的函数调用路由到对应的Facet合约,同时支持动态新增Facet、替换已有Facet、删除无用Facet,实现「按需升级、模块化拓展」的可升级能力,这也是它被称为「钻石」的原因:核心代理是「钻石主体」,各类模块化逻辑是「钻石的切面」。

钻石模式完全遵循EIP-2535 钻石标准,是目前可升级合约中扩展性最强的模式,适合大型复杂DApp(如多业务模块的DeFi协议、综合型NFT平台)。

选项排除逻辑

A错误

钻石模式的核心是模块化可升级的代理设计,并非用于存储大数据的专用合约。区块链中存储大数据通常会采用「链下存储+链上存哈希」的方式,与钻石模式无关。

C错误

钻石模式是合约架构设计模式,属于智能合约的开发规范,并非加密交易的算法。加密交易的算法通常指非对称加密(如ECDSA)、哈希算法(如Keccak256)等密码学内容,二者分属不同领域。

D错误

钻石模式不涉及交易隐私保护,它的核心是合约的可升级和模块化,而确保交易隐私的协议/模式主要有零知识证明(ZKP)、混币协议、隐私合约(如zkSync合约)等,与钻石模式无关联。

补充:钻石模式(EIP-2535)核心组成(开发必知)

钻石模式的架构由4个核心部分组成,各司其职且遵循EIP-2535标准,保证兼容性和可升级性:

  1. Diamond Proxy(钻石代理合约):核心入口,存储所有合约状态,负责将函数调用路由到对应的Facet合约,是用户唯一交互的合约;
  2. Facet(切面合约):模块化的逻辑合约,每个Facet实现一个独立的业务模块(如转账、权限、铸造),可动态新增/替换/删除;
  3. Diamond Loupe(钻石放大镜合约):提供标准化的查询接口,用于查询「哪个函数对应哪个Facet」「当前所有Facet列表」等路由信息,方便开发和审计;
  4. Diamond Cut(钻石切割合约):提供标准化的升级接口,用于执行「新增/替换/删除Facet」的操作,由合约管理员调用,实现模块化升级。

总结

  1. Diamonds(钻石模式)是遵循EIP-2535的、支持模块化逻辑动态添加的代理合约模式(对应选项B),是传统代理模式的进阶扩展;
  2. 核心优势是模块化拆分、按需升级、无限拓展,适合大型复杂DApp的可升级设计;
  3. 它是合约架构模式,并非存储方案、加密算法或隐私协议,这是本题的核心避坑点。

合约部署

/home/kali/Desktop/2026-h1-building-production-grade-dApps/homework/lesson-4/2070

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
└─# forge script script/FullWorkflow.s.sol --rpc-url https://rpc.api.moonbase.moonbeam.network --broadcast --legacy --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 33 files with Solc 0.8.30
[⠢] Solc 0.8.30 finished in 1.99s
Compiler run successful!
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.

== Logs ==
Proxy Address: 0x5D7FaF885525Cb948f320FdA9E9c19Be0a3FCc1e
ProxyAdmin Address: 0x2E3c3E56e1c7549fE274AFf0D4b48770763b35f0
V1 Implementation: 0x14B86F42Aed2ed44578014Bf839599fCE0807bFb

## Setting up 1 EVM.

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

Chain 1287

Estimated gas price: 0.03125 gwei

Estimated total gas used for script: 3091112

Estimated amount required: 0.00009659725 ETH

==========================
Enter private key:

##### moonbase
✅ [Success] Hash: 0xc2316107ca3e35e937fbd9c0a1e6138e9ed22ed56d879c7560ed2604863b749d
Contract Address: 0x14B86F42Aed2ed44578014Bf839599fCE0807bFb
Block: 14944184
Paid: 0.0000134161875 ETH (429318 gas * 0.03125 gwei)


##### moonbase
✅ [Success] Hash: 0x90f95b6ffe42c594bd3f70fb7d862ba8626123ababc376895af2531061170ea0
Contract Address: 0x2E3c3E56e1c7549fE274AFf0D4b48770763b35f0
Block: 14944185
Paid: 0.0000247393125 ETH (791658 gas * 0.03125 gwei)


##### moonbase
✅ [Success] Hash: 0xc2250f3ed6111e00865e3c13e3d04fcbdea8a476bcc0cd15b5ec029c760500fc
Contract Address: 0x5D7FaF885525Cb948f320FdA9E9c19Be0a3FCc1e
Block: 14944187
Paid: 0.0000541108125 ETH (1731546 gas * 0.03125 gwei)

✅ Sequence #1 on moonbase | Total Paid: 0.0000922663125 ETH (2952522 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/FullWorkflow.s.sol/1287/run-latest.json

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

部署版本2

forge create src/VaultV2.sol:VaultV2
–rpc-url https://rpc.api.moonbase.moonbeam.network
–private-key <你的私钥>
–legacy
–broadcast

[⠒] Compiling…
No files changed, compilation skipped
Deployer: 0x9F75b6674128CADA1C2bDc9a0a3B72ec9E4625a0
Deployed to: 0xDC6B6549F512A1e79715aB4c8C28366132Df8C4B
Transaction hash: 0x369d609374463d165709d77633e941d030eb83d880dd6d21202b9097246e395a

交易升级

Proxy 部署交易

└─# forge script script/FinalSuccess.s.sol –rpc-url https://rpc.api.moonbase.moonbeam.network –broadcast –legacy –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 1 files with Solc 0.8.30
[⠃] Solc 0.8.30 finished in 1.60s
Compiler run successful!
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.

== Logs ==

Proxy Address: 0xe80B5ec8276d3f0443106a3AFf81cF9e46E22674
V1 Implementation: 0x6FCD3f636A04057ff0985C2871DC67597658A2EA
V2 Implementation: 0x4437591162B0400a8C8FD7c977D43083D4409c3B

Setting up 1 EVM.

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

Chain 1287

Estimated gas price: 0.03125 gwei

Estimated total gas used for script: 2670117

Estimated amount required: 0.00008344115625 ETH

==========================
Enter private key:

moonbase

✅ [Success] Hash: 0xcd3a21d0d096b51977995a11a75652e22a73b1ffaa019f7bfe72045148082381
Contract Address: 0x6FCD3f636A04057ff0985C2871DC67597658A2EA
Block: 14944413
Paid: 0.0000134161875 ETH (429318 gas * 0.03125 gwei)

moonbase

✅ [Success] Hash: 0x2bd145baf42c534ea938d44f604459c32ad4ed108d64786923ffcc934a4c1268
Contract Address: 0xe80B5ec8276d3f0443106a3AFf81cF9e46E22674
Block: 14944417
Paid: 0.0000541108125 ETH (1731546 gas * 0.03125 gwei)

moonbase

✅ [Success] Hash: 0xd6d5eddc8f5585df6120e4aa6ac3c49c1a1d052d7a6a288ad19ffae20930e939
Contract Address: 0x4437591162B0400a8C8FD7c977D43083D4409c3B
Block: 14944420
Paid: 0.0000149259375 ETH (477630 gas * 0.03125 gwei)

✅ Sequence #1 on moonbase | Total Paid: 0.0000824529375 ETH (2638494 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/FinalSuccess.s.sol/1287/run-latest.json

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

金库协议 V2 已经正式通过透明代理逻辑部署并升级成功。

└─# cast send 0xf1D8e4a8Be1923ca91b882CC61F0704fc5287BFc
“upgradeAndCall(address,address,bytes)”
0x81f846C663Dc94cA5E6e4b57Cf5A9178a00ADF5B
0xa43ACce286bcc09C45F426Cd154A37f2daf0ff7e
0x
–rpc-url https://rpc.api.moonbase.moonbeam.network
–private-key 0x610bf33b53b737fe92cb01112342176e79b9988b51a3c42d89eff83cadd96ab4
–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.

blockHash 0x59aa7b416422555fe0b549f9666021180aaf33b277c45cc502fee0aef15bbdac
blockNumber 14944495
contractAddress
cumulativeGasUsed 23670
effectiveGasPrice 31250000
from 0x9F75b6674128CADA1C2bDc9a0a3B72ec9E4625a0
gasUsed 23670
logs []
logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
root
status 1 (success)
transactionHash 0x639149232819de7da2e933e8310bdf83cfb398042e9db77c818f5cac110a8c8e
transactionIndex 0
type 0
blobGasPrice
blobGasUsed
to 0xf1D8e4a8Be1923ca91b882CC61F0704fc5287BFc

Checklist

你的进度非常快!你已经完成了合约编写初步部署。从你之前的日志看,你已经在 Moonbase 上部署了三个合约地址(推测分别是 ProxyAdminVaultV1 逻辑合约和 TransparentUpgradeableProxy 代理合约)。

要完成这份作业,你接下来需要攻克 UUPs/Transparent 代理模式的交互 以及 合约升级逻辑

以下是你完成任务的三个关键步骤:


第一步:弄清楚你的“代理合约”地址

在可升级架构中,你永远不直接和 VaultV1 地址交互。你需要找到那个 Proxy(代理合约) 的地址,因为它是存储(Storage)所在的地方。

  1. **检查 run-latest.json**:查看日志,确认哪个地址是 TransparentUpgradeableProxy
  2. 测试脚本验证:编写一个简单的脚本(可以用 Foundry 的 cast 命令或你要求的 TypeScript),调用 version()
  • 预期结果: 如果你初始化成功,应该返回 1

第二步:执行“升级”操作(核心知识点)

你需要将代理合约指向的“逻辑实现”从 VaultV1 更改为 VaultV2

你需要学习的知识:

  • ProxyAdmin 的权限管理: 通常 TransparentUpgradeableProxy 的升级权限由一个 ProxyAdmin 合约控制。你不能直接调用代理合约来升级,必须通过管理员。
  • Foundry 升级脚本: 编写一个新的脚本 Upgrade.s.sol

脚本逻辑:

  1. 部署 VaultV2.sol(得到新逻辑地址)。
  2. 调用 ProxyAdmin.upgrade(proxy, v2_implementation)
  3. 调用 VaultV2(proxy).updateVersion() 来修改存储中的版本号。

第三步:编写 TypeScript 验证代码(作业要求)

这是你作业中明确要求的。你需要使用 ethers.tsviem 来演示“存储在升级后被保留”以及“新变量生效”。

代码逻辑示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function verify() {
const proxyAddress = "你的代理合约地址";
const vault = new ethers.Contract(proxyAddress, ABI, wallet);

// 1. 显示升级后的版本(证明 version 变量在 V2 中被成功修改)
const version = await vault.version();
console.log("Current Version:", version.toString()); // 应该是 2

// 2. 显示 V1 留下的数据(证明 totalSales 没有丢失)
const sales = await vault.totalSales();
console.log("Total Sales (Retained):", sales.toString());

// 3. 调用 V2 特有的新功能
await vault.setBonusPool(100);
const bonus = await vault.bonusPool();
console.log("New V2 Variable (bonusPool):", bonus.toString());
}

总结:你现在的 Checklist

  1. 确认代理地址:从部署日志中提取 ProxyProxyAdmin 的地址。
  2. 部署 V2:运行 forge create 或脚本部署 VaultV2
  3. 执行升级交易:通过 ProxyAdmin 完成指向切换(这是你作业要求的“升级交易 Hash”来源)。
  4. 完成 README
  • 部署 Hash:已有的日志里那几个。
  • 升级 Hash:执行 upgrade 操作的交易。
  • TS 调用结果:运行你的 TS 脚本并截图/复制日志。

第一步 - 确认代理地址

寻找run-latest.json

run-latest.json 的具体路径
根据你之前的执行日志,它存放在这里: /home/kali/Desktop/2026-h1-building-production-grade-dApps/homework/lesson-4/2070/broadcast/Deploy.s.sol/1287/run-latest.json

jq查看run-latest.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──(root㉿kali)-[/home/…/2026-h1-building-production-grade-dApps/homework/lesson-4/2070]
└─# cat broadcast/Deploy.s.sol/1287/run-latest.json | jq '.transactions[] | select(.contractAddress != null) | {name: .contractName, address: .contractAddress}'
{
"name": "VaultV1",
"address": "0x82c349e21761f044147b12bbd9e64513bb46d974"
}
{
"name": "ProxyAdmin",
"address": "0x2e7e268ef5a4181692c10f39e4e7d0f0e6828e8d"
}
{
"name": "TransparentUpgradeableProxy",
"address": "0x88fe9f8d8660626c9f894920a60ef9e47a1facb5"
}

是的,你已经成功完成了第一步“确认代理地址”。
根据你通过 jq 过滤出来的 JSON 日志,你的合约架构地址已经非常清晰了:
• 逻辑合约 V1 (Implementation): 0x82c349e21761f044147b12bbd9e64513bb46d974
• 管理合约 (ProxyAdmin): 0x2e7e268ef5a4181692c10f39e4e7d0f0e6828e8d
• 交互门户 (Proxy): 0x88fe9f8d8660626c9f894920a60ef9e47a1facb5(这是你之后所有操作的核心地址)

第二步 - 部署 V2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
└─# forge create src/VaultV2.sol:VaultV2 \
--rpc-url https://rpc.api.moonbase.moonbeam.network \
--private-key <私钥> \
--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.

[⠢] Compiling...
No files changed, compilation skipped
Warning: Dry run enabled, not broadcasting transaction

Contract: VaultV2
Transaction: {
"from": "0x9f75b6674128cada1c2bdc9a0a3b72ec9e4625a0",
"to": null,
"gasPrice": "0x1dcd650",
"gas": "0x867da",
"input": "0x60

第二遍运行加上广播参数

1
2
3
4
5
6
7
8
9
10
└─# forge create src/VaultV2.sol:VaultV2 \
--rpc-url https://rpc.api.moonbase.moonbeam.network \
--private-key <私钥> \
--legacy --broadcast

[⠊] Compiling...
No files changed, compilation skipped
Deployer: 0x9F75b6674128CADA1C2bDc9a0a3B72ec9E4625a0
Deployed to: 0x198a563e04B67608e1D1AF9eFeC206C6F241Fa09
Transaction hash: 0x89b84edf23ce7d213ea4306bbc908c68baf5c148dba41b3d2d64eeacacb875a5

第三步:执行升级交易

调用 ProxyAdmin 的 upgrade 方法

cast send 0x1Bb9780c8b74B84F2e3883df2eC994A8f5940F08
“upgrade(address,address)”
0x2325E5f1B997dfc6B300439D6f04Fdae4C6E46b8
0x198a563e04B67608e1D1AF9eFeC206C6F241Fa09
–rpc-url https://rpc.api.moonbase.moonbeam.network
–private-key <你的私钥>
–legacy

失败