返回 SC 笔记
SC Day 14

复习 - Week 2 总结 + ERC 标准速查卡

### 1. Week 2 知识回顾图谱

2026-04-14
第一阶段:基础构建
reviewerc-standardsrust-reviewweek-summary

日期: 2026-04-14 方向: Solidity / Rust 阶段: 第一阶段:基础构建 标签: #review #erc-standards #rust-review #week-summary


今日目标

类型内容
学习系统回顾 Week 2 核心知识点,查漏补缺
实操完成自检练习题,验证掌握程度
产出ERC 标准速查卡 + Week 2 知识图谱 + 自检结果

核心概念

1. Week 2 知识回顾图谱

Week 2 知识体系
├── Solidity
│   ├── Day 8: mapping + struct + 事件
│   │   ├── mapping(address => uint256) 存储模式
│   │   ├── struct 自定义类型
│   │   ├── event + indexed 参数
│   │   └── 完整 ERC20 实现
│   ├── Day 10: ERC20 标准
│   │   ├── 核心函数: transfer/approve/transferFrom
│   │   ├── approve + transferFrom 两步授权模式
│   │   ├── decimals 精度处理
│   │   └── 常见漏洞: 无限授权/精度损失
│   ├── Day 12: ERC721 标准
│   │   ├── 非同质化: 每个 tokenId 唯一
│   │   ├── safeTransferFrom vs transferFrom
│   │   ├── IERC721Receiver 接口
│   │   └── _mint vs _safeMint 安全考量
│   └── ERC 生态总览
│       ├── ERC1155: 多代币标准
│       ├── ERC4626: 代币化金库
│       └── ERC2612: permit 签名授权
│
└── Rust
    ├── Day 9: 所有权 + 借用 + 生命周期
    │   ├── 所有权三规则
    │   ├── &T (不可变借用) vs &mut T (可变借用)
    │   ├── 借用检查器规则
    │   └── 基础生命周期概念
    ├── Day 11: enum + 模式匹配 + Option/Result
    │   ├── 代数数据类型 (ADT)
    │   ├── match 穷尽性检查
    │   ├── Option<T> 替代 null
    │   ├── Result<T, E> 替代异常
    │   └── ? 运算符错误传播
    └── Day 13: trait + 泛型 + trait bound
        ├── trait 定义共享行为
        ├── 泛型 <T> 编写通用代码
        ├── trait bound: T: Display + Clone
        ├── where 子句
        └── 静态分发 vs 动态分发 (dyn)

2. ERC 标准速查卡

ERC20 vs ERC721 核心对比

维度ERC20 (同质化)ERC721 (非同质化)
代币性质可互换,每个单位相同不可互换,每个 token 唯一
余额表示mapping(address => uint256)mapping(uint256 => address)
转账参数transfer(to, amount)transferFrom(from, to, tokenId)
授权approve(spender, amount)approve(to, tokenId)
批量授权无标准方法setApprovalForAll(operator, bool)
安全转账无(ERC20 没有)safeTransferFrom + 接收者检查
元数据name/symbol/decimalsname/symbol/tokenURI
典型用例货币/治理代币/稳定币头像 NFT/游戏道具/证书/地产

ERC 标准完整速查

ERC20  - 同质化代币标准
├── 核心: balanceOf, transfer, approve, transferFrom, allowance
├── 元数据: name(), symbol(), decimals()
├── 事件: Transfer, Approval
└── 陷阱: approve 前竞态条件, decimals 精度

ERC721 - 非同质化代币标准
├── 核心: ownerOf, balanceOf, transferFrom, safeTransferFrom
├── 授权: approve, setApprovalForAll, getApproved, isApprovedForAll
├── 元数据: name(), symbol(), tokenURI()
├── 接口: IERC721Receiver.onERC721Received
└── 陷阱: _mint vs _safeMint 重入风险

ERC1155 - 多代币标准(ERC20 + ERC721 的超集)
├── 核心: balanceOf(account, id), balanceOfBatch
├── 转账: safeTransferFrom(from, to, id, amount, data)
├── 批量: safeBatchTransferFrom(from, to, ids, amounts, data)
├── 授权: setApprovalForAll (没有单 token approve)
├── 接口: IERC1155Receiver.onERC1155Received + onERC1155BatchReceived
├── 元数据: uri(id) 返回 JSON 元数据 URL
└── 优势: Gas 效率高,一个合约管理多种代币

ERC4626 - 代币化金库标准
├── 继承: ERC20
├── 存入: deposit(assets, receiver) → 返回 shares
├── 取出: withdraw(assets, receiver, owner) → 返回 shares
├── 赎回: redeem(shares, receiver, owner) → 返回 assets
├── 预览: previewDeposit, previewMint, previewWithdraw, previewRedeem
├── 汇率: convertToShares(assets), convertToAssets(shares)
└── 用途: 收益聚合器, 借贷池, 质押合约

ERC2612 - Permit(签名授权,无需链上 approve 交易)
├── 函数: permit(owner, spender, value, deadline, v, r, s)
├── 原理: 用 EIP-712 签名代替 approve 交易
├── 优势: 节省一笔 Gas,改善 UX
└── 风险: 签名可被重放(需 nonce 保护)

ERC165 - 接口检测
├── 函数: supportsInterface(bytes4 interfaceId)
├── 用途: 查询合约是否支持某个标准
└── 计算: interfaceId = 所有函数 selector 的 XOR

ERC1155 代码速览

// ERC1155 关键接口
interface IERC1155 {
    event TransferSingle(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256 id,
        uint256 value
    );
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] values
    );

    function balanceOf(address account, uint256 id) external view returns (uint256);
    function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
        external view returns (uint256[] memory);
    function safeTransferFrom(
        address from, address to, uint256 id, uint256 amount, bytes calldata data
    ) external;
    function safeBatchTransferFrom(
        address from, address to,
        uint256[] calldata ids, uint256[] calldata amounts,
        bytes calldata data
    ) external;
    function setApprovalForAll(address operator, bool approved) external;
    function isApprovedForAll(address account, address operator) external view returns (bool);
}

3. Rust 核心概念回顾

所有权规则速查

Rust 所有权三大规则:
1. 每个值有且只有一个 owner
2. 当 owner 离开作用域,值被 drop
3. 赋值/传参 = 移动(move),原变量失效

借用规则:
- 同一时刻,要么 N 个 &T,要么 1 个 &mut T
- 引用的生命周期不能超过被引用值
- 编译器通过借用检查器 (borrow checker) 在编译期保证

enum + Option + Result 速查

// Option: 表示"有或没有"
let found: Option<u64> = map.get("key").copied();
// 使用:match / if let / unwrap_or / map / and_then / ?

// Result: 表示"成功或失败"
let result: Result<String, Error> = parse_address(input);
// 使用:match / ? / unwrap_or_else / map_err

// 从 Option 转 Result
let val = found.ok_or(Error::NotFound)?;
// 从 Result 取 Option
let opt = result.ok();

Trait + 泛型速查

// 定义 trait
trait Hashable {
    fn hash(&self) -> Vec<u8>;
    fn hash_hex(&self) -> String {  // 默认实现
        hex::encode(self.hash())
    }
}

// 泛型 + trait bound
fn verify<T: Hashable + Eq>(a: &T, b: &T) -> bool {
    a.hash() == b.hash()
}

// where 子句(复杂场景)
fn complex<T, U>(a: T, b: U) -> bool
where
    T: Hashable + Clone + Send,
    U: Hashable + Sync,
{
    a.hash() == b.hash()
}

// impl Trait(简写)
fn simple(item: &impl Hashable) -> String {
    item.hash_hex()
}

代码实战:综合练习

练习 1:Solidity - 简易 NFT 市场

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IERC721 {
    function ownerOf(uint256 tokenId) external view returns (address);
    function transferFrom(address from, address to, uint256 tokenId) external;
    function getApproved(uint256 tokenId) external view returns (address);
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

/// @title 简易 NFT 市场
/// @notice 综合运用 mapping/struct/event/ERC721 知识
contract SimpleNFTMarket {
    struct Listing {
        address seller;
        address nftContract;
        uint256 tokenId;
        uint256 price;
        bool active;
    }

    // listingId => Listing
    mapping(uint256 => Listing) public listings;
    uint256 public nextListingId;

    // 平台手续费 (2.5%)
    uint256 public constant FEE_BPS = 250; // basis points
    address public immutable feeRecipient;

    event Listed(
        uint256 indexed listingId,
        address indexed seller,
        address indexed nftContract,
        uint256 tokenId,
        uint256 price
    );

    event Sold(
        uint256 indexed listingId,
        address indexed buyer,
        uint256 price
    );

    event Cancelled(uint256 indexed listingId);

    constructor() {
        feeRecipient = msg.sender;
    }

    /// @notice 上架 NFT
    function list(address nftContract, uint256 tokenId, uint256 price) external {
        require(price > 0, "Price must be > 0");

        IERC721 nft = IERC721(nftContract);
        require(nft.ownerOf(tokenId) == msg.sender, "Not the owner");
        require(
            nft.getApproved(tokenId) == address(this) ||
            nft.isApprovedForAll(msg.sender, address(this)),
            "Market not approved"
        );

        uint256 listingId = nextListingId++;
        listings[listingId] = Listing({
            seller: msg.sender,
            nftContract: nftContract,
            tokenId: tokenId,
            price: price,
            active: true
        });

        emit Listed(listingId, msg.sender, nftContract, tokenId, price);
    }

    /// @notice 购买 NFT
    function buy(uint256 listingId) external payable {
        Listing storage listing = listings[listingId];
        require(listing.active, "Listing not active");
        require(msg.value >= listing.price, "Insufficient payment");

        listing.active = false;

        // 计算手续费
        uint256 fee = (listing.price * FEE_BPS) / 10000;
        uint256 sellerProceeds = listing.price - fee;

        // 转移 NFT
        IERC721(listing.nftContract).transferFrom(
            listing.seller,
            msg.sender,
            listing.tokenId
        );

        // 支付
        (bool sent1, ) = listing.seller.call{value: sellerProceeds}("");
        require(sent1, "Payment to seller failed");

        (bool sent2, ) = feeRecipient.call{value: fee}("");
        require(sent2, "Fee payment failed");

        // 退还多余 ETH
        if (msg.value > listing.price) {
            (bool refunded, ) = msg.sender.call{value: msg.value - listing.price}("");
            require(refunded, "Refund failed");
        }

        emit Sold(listingId, msg.sender, listing.price);
    }

    /// @notice 取消上架
    function cancel(uint256 listingId) external {
        Listing storage listing = listings[listingId];
        require(listing.active, "Listing not active");
        require(listing.seller == msg.sender, "Not the seller");

        listing.active = false;
        emit Cancelled(listingId);
    }
}

练习 2:Rust - 泛型代币注册表

use std::collections::HashMap;
use std::fmt;

// ========== Trait 定义 ==========

trait TokenInfo: fmt::Display {
    fn address(&self) -> &str;
    fn name(&self) -> &str;
    fn standard(&self) -> &str;
}

// ========== 代币类型 ==========

struct ERC20Info {
    address: String,
    name: String,
    symbol: String,
    decimals: u8,
    total_supply: u128,
}

impl fmt::Display for ERC20Info {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} ({}) - Supply: {}", self.name, self.symbol, self.total_supply)
    }
}

impl TokenInfo for ERC20Info {
    fn address(&self) -> &str { &self.address }
    fn name(&self) -> &str { &self.name }
    fn standard(&self) -> &str { "ERC20" }
}

struct ERC721Info {
    address: String,
    name: String,
    symbol: String,
    max_supply: Option<u64>,
}

impl fmt::Display for ERC721Info {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self.max_supply {
            Some(max) => write!(f, "{} ({}) - Max: {}", self.name, self.symbol, max),
            None => write!(f, "{} ({}) - 无上限", self.name, self.symbol),
        }
    }
}

impl TokenInfo for ERC721Info {
    fn address(&self) -> &str { &self.address }
    fn name(&self) -> &str { &self.name }
    fn standard(&self) -> &str { "ERC721" }
}

// ========== 泛型注册表 ==========

struct TokenRegistry<T: TokenInfo> {
    tokens: HashMap<String, T>,
}

impl<T: TokenInfo> TokenRegistry<T> {
    fn new() -> Self {
        TokenRegistry { tokens: HashMap::new() }
    }

    fn register(&mut self, token: T) {
        let addr = token.address().to_string();
        println!("注册 {} 代币: {}", token.standard(), token);
        self.tokens.insert(addr, token);
    }

    fn lookup(&self, address: &str) -> Option<&T> {
        self.tokens.get(address)
    }

    fn count(&self) -> usize {
        self.tokens.len()
    }

    fn list_all(&self) {
        for (addr, token) in &self.tokens {
            println!("  [{}] {}", &addr[..10], token);
        }
    }
}

// ========== 泛型搜索函数 ==========

fn find_by_name<'a, T: TokenInfo>(registry: &'a TokenRegistry<T>, name: &str) -> Vec<&'a T> {
    registry.tokens.values()
        .filter(|t| t.name().to_lowercase().contains(&name.to_lowercase()))
        .collect()
}

fn main() {
    // ERC20 注册表
    let mut erc20_registry = TokenRegistry::new();
    erc20_registry.register(ERC20Info {
        address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".into(),
        name: "USD Coin".into(),
        symbol: "USDC".into(),
        decimals: 6,
        total_supply: 26_000_000_000_000_000,
    });
    erc20_registry.register(ERC20Info {
        address: "0xdAC17F958D2ee523a2206206994597C13D831ec7".into(),
        name: "Tether USD".into(),
        symbol: "USDT".into(),
        decimals: 6,
        total_supply: 40_000_000_000_000_000,
    });

    println!("--- ERC20 代币列表 ({}) ---", erc20_registry.count());
    erc20_registry.list_all();

    // 搜索
    let usd_tokens = find_by_name(&erc20_registry, "usd");
    println!("搜索 'usd': 找到 {} 个", usd_tokens.len());

    // ERC721 注册表
    let mut nft_registry = TokenRegistry::new();
    nft_registry.register(ERC721Info {
        address: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D".into(),
        name: "Bored Ape Yacht Club".into(),
        symbol: "BAYC".into(),
        max_supply: Some(10000),
    });

    println!("\n--- NFT 列表 ({}) ---", nft_registry.count());
    nft_registry.list_all();
}

自检练习

Solidity 自检

#问题你的答案正确答案
1ERC20 的 approve + transferFrom 为什么需要两步?___________允许第三方(如 DEX)代你转账
2ERC721 的 safeTransferFrom 多做了什么?___________检查接收方是否实现 IERC721Receiver
3_mint_safeMint 的区别?____________safeMint 会调用 onERC721Received
4mapping 的默认值是什么?___________所有类型的零值(uint=0, bool=false, address=0x0)
5eventindexed 的作用?___________允许按该字段过滤日志
6ERC20 decimals=6 时,1 USDC 存多少?___________1_000_000 (10^6)
7ERC1155 和 ERC721 的主要区别?___________ERC1155 支持同一合约管理多种代币,包括同质和非同质

Rust 自检

#问题你的答案正确答案
1let s2 = s1; 之后 s1 还能用吗?(String 类型)___________不能,所有权已移动
2Option<T> 的两个变体是?___________Some(T) 和 None
3Result<T, E>? 运算符做了什么?___________Ok → 取出值;Err → 立即 return 错误
4trait bound T: Display + Clone 是什么意思?___________T 必须同时实现 Display 和 Clone
5&T&mut T 能同时存在吗?___________不能,Rust 借用规则禁止
6Vec<Box<dyn Trait>> 是什么?___________存放实现了 Trait 的不同类型的动态分发集合
7match 不覆盖所有分支会怎样?___________编译错误

编码练习

练习 1 (Solidity):
编写一个 ERC20 合约,增加 burn() 和 burnFrom() 函数。
burn() 让持有者销毁自己的代币。
burnFrom() 让已授权的第三方帮你销毁。

练习 2 (Rust):
实现一个 Block 结构体,包含 number, hash, parent_hash, transactions (Vec<String>)。
为它实现 Display trait,打印格式化的区块信息。
用 Option<Block> 实现一个 find_block(number: u64) 函数。

练习 3 (综合):
设计一个 Rust enum 来表示 ERC 标准的所有类型:
enum ERCStandard { ERC20 { ... }, ERC721 { ... }, ERC1155 { ... } }
为每个变体携带相关数据,并实现一个 describe() 方法。

关键要点总结

要点说明
ERC20 = 同质化每个 token 相同,适合货币
ERC721 = 非同质化每个 token 唯一,适合收藏品
ERC1155 = 多代币一个合约管理多种代币类型
Rust 所有权编译期内存安全,零运行时开销
Rust enum = ADT比 Solidity enum 强大得多
Rust trait = 接口 + 默认实现比 Solidity interface 更灵活
泛型 + trait bound类型安全的多态

常见误区

误区 1:ERC20 和 ERC721 的 balanceOf 语义不同

  • ERC20: balanceOf(address) → 返回代币数量 (uint256)
  • ERC721: balanceOf(address) → 返回持有的 NFT 总数 (uint256)
  • 注意:ERC721 没有 "余额" 的概念,只有 "持有数量"

误区 2:认为 Rust 的所有权系统增加运行时开销

所有权检查完全在编译期完成。编译后的代码和 C/C++ 一样高效,没有任何运行时垃圾回收或引用计数(除非你显式使用 Rc/Arc)。

误区 3:混淆泛型和 trait object 的使用场景

  • 泛型 fn foo<T: Trait>(x: T) → 编译期单态化,为每种类型生成独立代码
  • trait object fn foo(x: &dyn Trait) → 运行时虚表查找,支持异构集合

面试关联

Q: 请比较 ERC20, ERC721, ERC1155 三个标准

结构化回答

  1. 同质性: ERC20 完全同质 → ERC721 完全非同质 → ERC1155 兼容两者
  2. Gas 效率: ERC1155 > ERC721 > ERC20(对于多代币场景)
  3. 授权模型: ERC20 只有 approve(amount) → ERC721 有单个 + 全部 → ERC1155 只有全部
  4. 安全转账: ERC20 无 → ERC721/1155 有 safe* 系列
  5. 典型场景: ERC20=货币 → ERC721=艺术品 → ERC1155=游戏道具

Q: Rust 的类型系统如何帮助区块链开发?

  • 所有权防止 double-spend 类型的逻辑错误(编译期保证值不被复制)
  • Option/Result 强制处理所有错误路径(区块链中错误处理极其关键)
  • match 穷尽性确保所有状态都被处理(状态机不会遗漏分支)
  • 泛型 + trait 让代码可复用且类型安全(跨链适配器的统一接口)

参考资源

资源说明
EIP-20ERC20 标准
EIP-721ERC721 标准
EIP-1155ERC1155 标准
EIP-4626代币化金库标准
OpenZeppelin Contracts业界标准实现
Rust Book 全文Rust 官方教程
Rust Cheat SheetRust 语法速查