返回 SC 笔记
SC Day 21

复习 - Week 3 总结

### 一、Solidity Week 3 知识图谱

2026-04-21
第一阶段:基础构建 (Day 21-24)
复习总结SolidityRust自测

日期: 2026-04-21 方向: Solidity + Rust 阶段: 第一阶段:基础构建 (Day 21-24) 标签: #复习 #总结 #Solidity #Rust #自测


今日目标

Week 3 是整个基础构建阶段的关键转折点。前两周我们分别建立了 Solidity 和 Rust 的语言基础,第三周则深入到了两门语言的高级特性。今天的目标是:

  1. 系统回顾 Solidity 高级特性:继承、库、内联汇编、底层调用、Crowdfunding 项目
  2. 系统回顾 Rust 高级特性:闭包、生命周期、错误处理
  3. 对比总结 两门语言在设计理念上的异同
  4. 自测检验 通过 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; }
FnOnceself (所有权转移)只能调用一次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 对比总结

维度SolidityRust
类型系统静态+弱类型(隐式转换有限)静态+强类型(零隐式转换)
内存管理EVM 自动管理(storage/memory/calldata)所有权系统,编译期保证
错误处理require/revert/assertResult<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: calldelegatecall 的核心区别? A2: call 在目标合约的上下文执行(使用目标的 storage 和 msg.sender = 调用者);delegatecall 在调用者的上下文执行(使用调用者的 storage,msg.sender 不变)。这是代理模式的基础。

Q3: 为什么 Crowdfunding 的 refund 先把 contributions 置零再转账? A3: 防止重入攻击。这是 CEI (Checks-Effects-Interactions) 模式 — 先检查条件、再修改状态、最后外部交互。

Q4: libraryinternal 函数和 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 三大必掌握

  1. 继承 + C3 线性化: 理解 OpenZeppelin 合约库的基础,面试必问
  2. 低级调用三兄弟: call(转账+调用)、staticcall(只读)、delegatecall(代理模式核心)
  3. 内联汇编: 理解 sload/sstore/mload/mstore,为理解代理模式存储槽做准备

Rust 三大必掌握

  1. 闭包 + Trait: Fn/FnMut/FnOnce 决定闭包如何捕获环境
  2. 生命周期: 核心理念是"引用不能比被引用的数据活得更久"
  3. 错误处理: Result<T, E> + ? + 自定义错误类型 = Rust 惯用模式

设计理念对比

  • Solidity: 防御性编程 — require/revert 在运行时保护
  • Rust: 编译期保证 — 所有权/生命周期在编译期就排除大部分bug

常见误区

  1. Solidity 继承顺序写反: contract D is B, Ccontract D is C, B 的行为不同
  2. Library 中用 storage 变量: Library 不能有状态变量,只能有 constant
  3. delegatecall 存储冲突: 代理合约和实现合约的存储布局必须兼容
  4. Rust 闭包 move: move 关键字强制闭包获取所有权,不是"移动闭包"
  5. 混淆 'static 和 static: 'static 是生命周期,static 是全局变量关键字

面试关联

面试题涉及知识点
"解释 delegatecall 和代理模式的关系"底层调用 + 存储布局
"Solidity 的继承机制和 C3 线性化"继承 + super
"如何防止智能合约的重入攻击"CEI 模式 + Crowdfunding
"Rust 的所有权系统如何保证内存安全"所有权 + 生命周期
"解释 Rust 的错误处理最佳实践"Result + ? + 自定义 Error
"Fn/FnMut/FnOnce 的区别"闭包 trait

参考资源