复习 - Week 3 总结
### 一、Solidity Week 3 知识图谱
日期: 2026-04-21 方向: Solidity + Rust 阶段: 第一阶段:基础构建 (Day 21-24) 标签: #复习 #总结 #Solidity #Rust #自测
今日目标
Week 3 是整个基础构建阶段的关键转折点。前两周我们分别建立了 Solidity 和 Rust 的语言基础,第三周则深入到了两门语言的高级特性。今天的目标是:
- 系统回顾 Solidity 高级特性:继承、库、内联汇编、底层调用、Crowdfunding 项目
- 系统回顾 Rust 高级特性:闭包、生命周期、错误处理
- 对比总结 两门语言在设计理念上的异同
- 自测检验 通过 Quiz 验证知识掌握程度
核心概念
一、Solidity Week 3 知识图谱
1.1 继承体系 (Inheritance)
Solidity 支持多重继承,采用 C3 线性化 算法解决菱形继承问题。这是理解 OpenZeppelin 合约库的基础。
继承知识树:
├── 单继承: contract Child is Parent
├── 多重继承: contract Child is A, B (顺序很重要!)
├── C3线性化: 从右到左、深度优先
├── virtual / override 关键字
├── super 关键字 (调用继承链的下一个)
├── 抽象合约: abstract contract (含未实现函数)
└── 接口: interface (只有函数签名)
关键回顾 — 继承顺序决定行为:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract A {
function foo() public pure virtual returns (string memory) {
return "A";
}
}
contract B is A {
function foo() public pure virtual override returns (string memory) {
return "B";
}
}
contract C is A {
function foo() public pure virtual override returns (string memory) {
return "C";
}
}
// 多重继承: 最右边的基类优先级最高
contract D is B, C {
// 必须 override 所有冲突的父合约
function foo() public pure override(B, C) returns (string memory) {
return super.foo(); // 返回 "C" (C3线性化: D -> C -> B -> A)
}
}
contract E is C, B {
function foo() public pure override(C, B) returns (string memory) {
return super.foo(); // 返回 "B" (C3线性化: E -> B -> C -> A)
}
}
1.2 库 (Library)
库是无状态的代码复用单元,不能持有以太币,不能有状态变量(只能有 constant)。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// 数学安全库 — 虽然0.8+自带溢出检查,但演示库的用法
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
function percentage(uint256 amount, uint256 bps) internal pure returns (uint256) {
require(bps <= 10000, "BPS > 10000");
return (amount * bps) / 10000;
}
}
// 地址工具库
library AddressUtils {
function isContract(address account) internal view returns (bool) {
uint256 size;
assembly {
size := extcodesize(account)
}
return size > 0;
}
}
contract Vault {
using SafeMath for uint256; // using...for 语法糖
using AddressUtils for address;
uint256 public totalDeposits;
uint256 public feeBps = 30; // 0.3%
function deposit() external payable {
require(!msg.sender.isContract(), "No contracts"); // 库函数当成员方法调用
uint256 fee = msg.value.percentage(feeBps);
totalDeposits = totalDeposits.add(msg.value - fee);
}
}
1.3 内联汇编 (Inline Assembly / Yul)
Yul 是 Solidity 的低级语言,用 assembly { } 块嵌入,可以直接操作 EVM 栈和内存。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract AssemblyReview {
// 1. 高效存储读写
function getStorageAt(uint256 slot) external view returns (bytes32 value) {
assembly {
value := sload(slot)
}
}
function setStorageAt(uint256 slot, bytes32 value) external {
assembly {
sstore(slot, value)
}
}
// 2. 高效内存操作
function efficientKeccak(uint256 a, uint256 b) external pure returns (bytes32 hash) {
assembly {
// 在 free memory pointer 位置写入数据
let ptr := mload(0x40)
mstore(ptr, a)
mstore(add(ptr, 0x20), b)
hash := keccak256(ptr, 0x40)
}
}
// 3. 获取合约代码大小
function codeSize(address target) external view returns (uint256 size) {
assembly {
size := extcodesize(target)
}
}
// 4. 高效地址转换
function toUint160(address addr) external pure returns (uint160 result) {
assembly {
result := addr
}
}
}
1.4 底层调用 (Low-level Calls)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract LowLevelCallReview {
// call — 最常用,可发送ETH + 调用函数
function safeCall(address target, bytes calldata data)
external
payable
returns (bool success, bytes memory returnData)
{
(success, returnData) = target.call{value: msg.value, gas: 50000}(data);
// 注意: call 失败不会 revert,需要手动检查
}
// staticcall — 只读调用,不能修改状态
function safeStaticCall(address target, bytes calldata data)
external
view
returns (bytes memory returnData)
{
bool success;
(success, returnData) = target.staticcall(data);
require(success, "Static call failed");
}
// delegatecall — 借用别人的代码,在自己的存储上执行
// 这是代理模式的基础!
function safeDelegateCall(address implementation, bytes calldata data)
external
returns (bytes memory returnData)
{
bool success;
(success, returnData) = implementation.delegatecall(data);
require(success, "Delegatecall failed");
}
// 编码调用数据
function encodeTransfer(address to, uint256 amount)
external
pure
returns (bytes memory)
{
return abi.encodeWithSignature("transfer(address,uint256)", to, amount);
}
}
1.5 Crowdfunding 项目回顾
Week 3 综合项目,用到了所有学过的知识点:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Crowdfunding {
enum State { Active, Successful, Failed, Closed }
struct Campaign {
address creator;
uint256 goal;
uint256 deadline;
uint256 totalFunded;
State state;
mapping(address => uint256) contributions;
}
uint256 public campaignCount;
mapping(uint256 => Campaign) public campaigns;
event CampaignCreated(uint256 indexed id, address creator, uint256 goal);
event Contributed(uint256 indexed id, address contributor, uint256 amount);
event Withdrawn(uint256 indexed id, address contributor, uint256 amount);
event CampaignFinalized(uint256 indexed id, State state);
modifier onlyState(uint256 id, State expected) {
require(campaigns[id].state == expected, "Invalid state");
_;
}
function createCampaign(uint256 goal, uint256 durationDays) external returns (uint256) {
require(goal > 0, "Goal must be > 0");
uint256 id = campaignCount++;
Campaign storage c = campaigns[id];
c.creator = msg.sender;
c.goal = goal;
c.deadline = block.timestamp + durationDays * 1 days;
c.state = State.Active;
emit CampaignCreated(id, msg.sender, goal);
return id;
}
function contribute(uint256 id) external payable onlyState(id, State.Active) {
Campaign storage c = campaigns[id];
require(block.timestamp < c.deadline, "Campaign ended");
require(msg.value > 0, "Must send ETH");
c.contributions[msg.sender] += msg.value;
c.totalFunded += msg.value;
emit Contributed(id, msg.sender, msg.value);
}
function finalize(uint256 id) external onlyState(id, State.Active) {
Campaign storage c = campaigns[id];
require(block.timestamp >= c.deadline, "Not ended yet");
if (c.totalFunded >= c.goal) {
c.state = State.Successful;
(bool ok, ) = c.creator.call{value: c.totalFunded}("");
require(ok, "Transfer failed");
c.state = State.Closed;
} else {
c.state = State.Failed;
}
emit CampaignFinalized(id, c.state);
}
function refund(uint256 id) external onlyState(id, State.Failed) {
Campaign storage c = campaigns[id];
uint256 amount = c.contributions[msg.sender];
require(amount > 0, "No contribution");
c.contributions[msg.sender] = 0; // CEI pattern
(bool ok, ) = msg.sender.call{value: amount}("");
require(ok, "Refund failed");
emit Withdrawn(id, msg.sender, amount);
}
}
二、Rust Week 3 知识图谱
2.1 闭包 (Closures)
闭包是可以捕获环境变量的匿名函数。Rust 闭包有三种 trait:
| Trait | 捕获方式 | 说明 | 示例 |
|---|---|---|---|
Fn | &self (不可变借用) | 可多次调用,不修改环境 | |x| x + captured_val |
FnMut | &mut self (可变借用) | 可多次调用,可修改环境 | |x| { counter += x; } |
FnOnce | self (所有权转移) | 只能调用一次 | move || drop(value) |
fn closure_review() {
// Fn — 不可变捕获
let multiplier = 3;
let multiply = |x: i32| x * multiplier;
println!("{}", multiply(5)); // 15
println!("{}", multiply(10)); // 30 — 可重复调用
// FnMut — 可变捕获
let mut total = 0;
let mut accumulate = |x: i32| {
total += x;
total
};
println!("{}", accumulate(5)); // 5
println!("{}", accumulate(10)); // 15
// FnOnce — 所有权转移
let name = String::from("Ethereum");
let consume = move || {
println!("Consumed: {}", name);
drop(name); // name 被 move 进闭包
};
consume();
// consume(); // 编译错误! 已经被消耗
// 闭包作为参数
let numbers = vec![1, 2, 3, 4, 5];
let evens: Vec<_> = numbers.iter().filter(|&&x| x % 2 == 0).collect();
let doubled: Vec<_> = numbers.iter().map(|&x| x * 2).collect();
println!("Evens: {:?}, Doubled: {:?}", evens, doubled);
}
// 闭包作为返回值 — 需要 Box<dyn Fn>
fn make_greeter(greeting: String) -> Box<dyn Fn(&str) -> String> {
Box::new(move |name| format!("{}, {}!", greeting, name))
}
2.2 生命周期 (Lifetimes)
生命周期是 Rust 最独特的特性,保证引用永远有效。
// 基础: 生命周期标注
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
// 结构体中的生命周期
struct TokenInfo<'a> {
name: &'a str,
symbol: &'a str,
chain: &'a str,
}
impl<'a> TokenInfo<'a> {
fn display(&self) -> String {
format!("{} ({}) on {}", self.name, self.symbol, self.chain)
}
// 返回引用的生命周期与self相同
fn symbol(&self) -> &str {
self.symbol
}
}
// 多个生命周期参数
struct Pair<'a, 'b> {
first: &'a str,
second: &'b str,
}
// 生命周期省略规则 (Elision Rules):
// 1. 每个引用参数获得独立的生命周期
// 2. 如果只有一个输入生命周期,它被赋给所有输出
// 3. 如果有 &self 参数,self 的生命周期被赋给所有输出
// 规则应用示例 — 以下两个签名等价:
// fn first_word(s: &str) -> &str
// fn first_word<'a>(s: &'a str) -> &'a str
// 'static 生命周期 — 整个程序运行期间有效
fn get_chain_name() -> &'static str {
"Ethereum Mainnet"
}
2.3 错误处理 (Error Handling)
use std::fmt;
use std::num::ParseIntError;
// 自定义错误类型
#[derive(Debug)]
enum Web3Error {
NetworkError(String),
ParseError(ParseIntError),
InvalidAddress(String),
InsufficientBalance { required: u64, available: u64 },
}
impl fmt::Display for Web3Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Web3Error::NetworkError(msg) => write!(f, "Network error: {}", msg),
Web3Error::ParseError(e) => write!(f, "Parse error: {}", e),
Web3Error::InvalidAddress(addr) => write!(f, "Invalid address: {}", addr),
Web3Error::InsufficientBalance { required, available } => {
write!(f, "Insufficient balance: need {}, have {}", required, available)
}
}
}
}
impl std::error::Error for Web3Error {}
// From trait 实现 — 支持 ? 操作符自动转换
impl From<ParseIntError> for Web3Error {
fn from(e: ParseIntError) -> Self {
Web3Error::ParseError(e)
}
}
// 使用 Result 和 ? 操作符
fn parse_balance(s: &str) -> Result<u64, Web3Error> {
let balance: u64 = s.parse()?; // ParseIntError 自动转为 Web3Error
Ok(balance)
}
fn transfer(from_balance: u64, amount: u64) -> Result<u64, Web3Error> {
if amount > from_balance {
return Err(Web3Error::InsufficientBalance {
required: amount,
available: from_balance,
});
}
Ok(from_balance - amount)
}
// 链式错误处理
fn process_transaction(balance_str: &str, amount_str: &str) -> Result<u64, Web3Error> {
let balance = parse_balance(balance_str)?;
let amount = parse_balance(amount_str)?;
transfer(balance, amount)
}
三、Solidity vs Rust 对比总结
| 维度 | Solidity | Rust |
|---|---|---|
| 类型系统 | 静态+弱类型(隐式转换有限) | 静态+强类型(零隐式转换) |
| 内存管理 | EVM 自动管理(storage/memory/calldata) | 所有权系统,编译期保证 |
| 错误处理 | require/revert/assert | Result<T, E> + ? 操作符 |
| 代码复用 | 继承 + 库 | Trait + 泛型 |
| 并发 | 无(EVM 单线程) | async/await + 所有权保证线程安全 |
| 可变性 | 默认可变 | 默认不可变(let vs let mut) |
| 空值 | address(0), 0 作为默认值 | Option<T>,无 null |
| 执行环境 | EVM 沙箱 | 原生二进制或 WASM |
| Gas/性能 | 每个操作都有 Gas 成本 | 零成本抽象 |
代码实战
综合自测 Quiz
Solidity 部分
Q1: 以下代码的 foo() 返回什么?
contract A {
function foo() public pure virtual returns (uint) { return 1; }
}
contract B is A {
function foo() public pure virtual override returns (uint) { return 2; }
}
contract C is A {
function foo() public pure virtual override returns (uint) { return 3; }
}
contract D is C, B {
function foo() public pure override(C, B) returns (uint) {
return super.foo();
}
}
A1: 返回 2。C3 线性化顺序: D -> B -> C -> A,super.foo() 调用链中第一个 B 的实现。
Q2: call 和 delegatecall 的核心区别?
A2: call 在目标合约的上下文执行(使用目标的 storage 和 msg.sender = 调用者);delegatecall 在调用者的上下文执行(使用调用者的 storage,msg.sender 不变)。这是代理模式的基础。
Q3: 为什么 Crowdfunding 的 refund 先把 contributions 置零再转账?
A3: 防止重入攻击。这是 CEI (Checks-Effects-Interactions) 模式 — 先检查条件、再修改状态、最后外部交互。
Q4: library 的 internal 函数和 public 函数有什么区别?
A4: internal 函数在编译时内联到调用合约(不产生 DELEGATECALL),更省 Gas。public 函数会独立部署,调用时通过 DELEGATECALL,有额外开销。
Rust 部分
Q5: 为什么这段代码编译不过?
fn main() {
let s1 = String::from("hello");
let closure = || println!("{}", s1);
drop(s1);
closure();
}
A5: 闭包捕获了 s1 的不可变引用(&s1),但 drop(s1) 转移了 s1 的所有权,导致闭包持有的引用悬空。编译器报错 "cannot move out of s1 because it is borrowed"。
Q6: 'static 生命周期意味着什么?
A6: 引用在整个程序运行期间有效。字符串字面量(&str)自动是 'static 的。'static 并不意味着必须是全局变量——Box::leak 也能创建 'static 引用。
Q7: ? 操作符的本质是什么?
A7: 语法糖。expr? 等价于 match expr { Ok(v) => v, Err(e) => return Err(From::from(e)) }。需要当前函数返回 Result,且错误类型实现了对应的 From trait。
Q8: Fn, FnMut, FnOnce 的关系?
A8: FnOnce 是超集,所有闭包都实现了它。FnMut: FnOnce 是子 trait。Fn: FnMut 是最严格的。当函数参数要求 FnOnce 时,传入 Fn 闭包也可以。
关键要点总结
Solidity 三大必掌握
- 继承 + C3 线性化: 理解 OpenZeppelin 合约库的基础,面试必问
- 低级调用三兄弟:
call(转账+调用)、staticcall(只读)、delegatecall(代理模式核心) - 内联汇编: 理解
sload/sstore/mload/mstore,为理解代理模式存储槽做准备
Rust 三大必掌握
- 闭包 + Trait:
Fn/FnMut/FnOnce决定闭包如何捕获环境 - 生命周期: 核心理念是"引用不能比被引用的数据活得更久"
- 错误处理:
Result<T, E>+?+ 自定义错误类型 = Rust 惯用模式
设计理念对比
- Solidity: 防御性编程 — require/revert 在运行时保护
- Rust: 编译期保证 — 所有权/生命周期在编译期就排除大部分bug
常见误区
- Solidity 继承顺序写反:
contract D is B, C和contract D is C, B的行为不同 - Library 中用 storage 变量: Library 不能有状态变量,只能有 constant
- delegatecall 存储冲突: 代理合约和实现合约的存储布局必须兼容
- Rust 闭包 move:
move关键字强制闭包获取所有权,不是"移动闭包" - 混淆
'static和 static:'static是生命周期,static是全局变量关键字
面试关联
| 面试题 | 涉及知识点 |
|---|---|
| "解释 delegatecall 和代理模式的关系" | 底层调用 + 存储布局 |
| "Solidity 的继承机制和 C3 线性化" | 继承 + super |
| "如何防止智能合约的重入攻击" | CEI 模式 + Crowdfunding |
| "Rust 的所有权系统如何保证内存安全" | 所有权 + 生命周期 |
| "解释 Rust 的错误处理最佳实践" | Result + ? + 自定义 Error |
| "Fn/FnMut/FnOnce 的区别" | 闭包 trait |