首页
学习
活动
专区
圈层
工具
发布
49 篇文章
1
《纸上谈兵·solidity》第 0 课:搭建 Solidity 开发环境(三种方式)
2
《纸上谈兵·solidity》第 1 课:部署你的第一个 Solidity 合约
3
《纸上谈兵·solidity》第 2 课:调用、修改、读取,Solidity 合约不是 REST API
4
《纸上谈兵·solidity》第 3 课:事件(Event)机制与链上日志——不是 print,是广播!
5
《纸上谈兵·solidity》第 4 课:Solidity 合约中的错误处理机制(`require`、`revert`、`assert`)和自定义错误
6
《纸上谈兵·solidity》第 5 课:依赖与外部调用 —— 合约交互的风险与防护
7
《纸上谈兵·solidity》第 6 课:Solidity 数据存储布局 —— memory、storage、calldata 傻傻分不清?
8
《纸上谈兵·solidity》第 7 课:Solidity 函数可见性和修饰器 —— public 和 private 不只是权限标签
9
《纸上谈兵·solidity》第 8 课:Solidity 中的继承与接口 —— 模块化不是“复制粘贴”的借口
10
《纸上谈兵·solidity》第 9 课:Solidity 事件与日志机制 —— 合约世界的“printf”工具
11
《纸上谈兵·solidity》第 10 课:Solidity `fallback` / `receive` 函数 —— 合约如何收 ETH 和响应未知调用?
12
《纸上谈兵·solidity》第 11 课:Solidity 错误处理与异常机制 —— 让合约优雅地失败
13
《纸上谈兵·solidity》第 12 课:Solidity 函数选择器与 ABI 编码原理
14
《纸上谈兵·solidity》第 13 课:Solidity 低级调用 call/delegatecall/staticcall —— 直接和 EVM“对话”
15
《纸上谈兵·solidity》第 14 课:Solidity 中的可升级合约模式 —— 从代理合约到透明代理、UUPS 与安全陷阱
16
《纸上谈兵·solidity》第 15 课:Solidity 库与可重用代码
17
《纸上谈兵·solidity》第 16 课:Pull over Push 支付模式与 Check-Effects-Interactions 原则
18
《纸上谈兵·solidity》第 17 课:合约设计模式实战(二)—— Access Control 与权限管理
19
《纸上谈兵·solidity》第 18 课:合约设计模式实战(三)—— 代理 + 插件化架构(Diamond Standard / EIP-2535)
20
《纸上谈兵·solidity》第 19 课:安全专题(一)—— 常见攻击手法与防御
21
《纸上谈兵·solidity》第 20 课:Solidity 安全专题(二)—— 编译器特性与低级漏洞
22
《纸上谈兵·solidity》第 21 课:Gas 优化与成本分析 —— 写出便宜的智能合约
23
《纸上谈兵·solidity》第 22 课:代币合约(ERC20)从零实现与扩展
24
《纸上谈兵·solidity》第 23 课:NFT 合约(ERC721 / ERC1155)实战
25
《纸上谈兵·solidity》第 24 课:去中心化众筹合约(Crowdfunding)实战
26
《纸上谈兵·solidity》第 25 课:简化版的去中心化交易所(DEX)简化版
27
《纸上谈兵·solidity》第 26 课:借贷合约简化实现
28
《纸上谈兵·solidity》第 27 课:DAO 治理合约(去中心化自治组织)
29
《纸上谈兵·solidity》第 28 课:智能合约安全审计案例复盘 -- The DAO Hack(2016)
30
《纸上谈兵·solidity》第 29 课:智能合约安全审计案例复盘 -- Parity Wallet Hack(2017)
31
《纸上谈兵·solidity》第 30 课:智能合约安全审计案例复盘 -- Nomad Bridge(2022)
32
《纸上谈兵·solidity》第 31 课:多签钱包在跨链桥中的应用 —— Nomad 事件复盘
33
《纸上谈兵·solidity》第 32 课:DeFi 基础合约
34
《纸上谈兵·solidity》第 33 课:多签钱包(Multisig Wallet)-- 合约设计与实现
35
《纸上谈兵·solidity》第 34 课:多签钱包(Multisig Wallet)-- 上线
36
《纸上谈兵·solidity》第 35 课:去中心化交易所(DEX)实战 — 合约设计
37
《纸上谈兵·solidity》第 36 课:去中心化交易所(DEX)实战 — 上线
38
《纸上谈兵·solidity》第 37 课:DeFi 实战 -- 资金池与利率模型
39
《纸上谈兵·solidity》第 38 课:DeFi 实战(2) -- 清算机制与价格预言机
40
《纸上谈兵·solidity》第 39 课:DeFi 实战(3) -- 利息累积与 aToken 设计
41
《纸上谈兵·solidity》第 40 课:DeFi 实战(4) -- 风险控制与防护
42
《纸上谈兵·solidity》第 41 课:DeFi 实战(5) -- 协议费与治理
43
《纸上谈兵·solidity》第 42 课:DeFi 实战(6) -- 跨资产借贷与多市场支持
44
《纸上谈兵·solidity》第 43 课:DeFi 实战(7) -- 清算机制进阶(多资产抵押清算路径、拍卖机制)
45
《纸上谈兵·solidity》第 44 课:DeFi 实战(8) -- 利率曲线与资金池优化(动态利用率模型)
46
《纸上谈兵·solidity》第 45 课:DeFi 实战(9) -- 利息累积与结算机制(可复利)
47
《纸上谈兵·solidity》第 46 课:DeFi 实战(10) -- 跨链借贷与流动性桥接
48
《纸上谈兵·solidity》第 47 课:DeFi 实战(11) -- 治理代币 & 激励机制(Tokenomics & Governance)
49
《纸上谈兵·solidity》第 48 课:DeFi 实战(12) -- 前端 DApp 集成与用户交互(React + ethers.js 实战)

《纸上谈兵·solidity》第 8 课:Solidity 中的继承与接口 —— 模块化不是“复制粘贴”的借口

模块化开发是大型合约系统不可或缺的组成部分。本课简单剖析 Solidity 中的继承(Inheritance)、接口(Interface)、抽象合约(Abstract Contract)等关键机制,帮你在合约系统中正确地拆分职责、重用逻辑、规范合约交互,而不是简单复制粘贴。


一、继承(Inheritance)

继承是 Solidity 的核心语言特性之一。它允许你将多个合约组织在一起形成层级结构,子合约可以继承父合约的状态变量、函数、事件和修饰器(modifier)

继承语法:

代码语言:txt
复制
contract Parent {
    string public name = "parent";
}

contract Child is Parent {
    function getName() public view returns (string memory) {
        return name;
    }
}

这里 Child 自动继承了 Parent 中的 name 状态变量。


二、构造函数的继承与初始化顺序

Solidity 支持显式传参给父合约的构造函数:

代码语言:txt
复制
contract A {
    uint public x;
    constructor(uint _x) {
        x = _x;
    }
}

contract B is A {
    constructor() A(42) {}
}

多重继承时的初始化顺序:

代码语言:txt
复制
contract A {
    constructor() { /* A init */ }
}
contract B is A {
    constructor() A() { /* B init */ }
}
contract C is A, B {
    constructor() A() B() {}
}

初始化顺序遵循继承声明顺序,不是构造函数中的调用顺序。


三、函数重写:virtualoverride

如果你希望某个函数可以被子合约覆盖(重写),你必须在父合约中标记为 virtual。子合约中实现该函数时必须使用 override

代码语言:txt
复制
contract Base {
    function foo() public pure virtual returns (string memory) {
        return "Base";
    }
}

contract Derived is Base {
    function foo() public pure override returns (string memory) {
        return "Derived";
    }
}

多重继承时需指定 override 的所有来源:

代码语言:txt
复制
contract A {
    function bar() public pure virtual returns (string memory) {
        return "A";
    }
}

contract B {
    function bar() public pure virtual returns (string memory) {
        return "B";
    }
}

contract C is A, B {
    function bar() public pure override(A, B) returns (string memory) {
        return "C";
    }
}

四、抽象合约(Abstract Contract)

当合约包含至少一个未实现的函数(即不提供函数体),它就变成了抽象合约。

代码语言:txt
复制
abstract contract Animal {
    function speak() public view virtual returns (string memory);
}

抽象合约不能被部署,必须由继承它的合约来实现所有未实现函数

代码语言:txt
复制
contract Dog is Animal {
    function speak() public pure override returns (string memory) {
        return "Woof!";
    }
}

抽象合约非常适合做模板、接口的默认实现等用途。


五、接口(Interface)

接口定义了一组标准的函数签名,用于合约之间通信时的约定。与抽象合约不同的是:

  • 所有函数必须是 external
  • 不允许有状态变量和实现逻辑
  • 不允许构造函数
  • 可用于调用链上已部署合约的 ABI
代码语言:txt
复制
interface IERC20 {
    function transfer(address to, uint256 value) external returns (bool);
    function balanceOf(address owner) external view returns (uint256);
}

使用示例:

代码语言:txt
复制
IERC20 token = IERC20(tokenAddress);
token.transfer(msg.sender, 1000);

六、组合 vs 继承 vs 接口:模块化选择建议

场景

使用模式

理由与优势

重用通用逻辑或变量

继承

降低代码重复率,可共用函数、状态等

动态组合、插拔式模块(如插件)

组合

解耦更彻底,适合通过部署地址传入外部依赖

合约之间通信或依赖

接口

减少依赖方对实现方的了解程度,便于升级和替换

多模块逻辑共用一份代码

抽象合约

提供默认行为或统一接口逻辑的模板式架构


七、Foundry 实战测试:继承逻辑

src/Base.sol

代码语言:txt
复制
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Base {
    function value() public pure virtual returns (uint256) {
        return 1;
    }
}

src/Child.sol

代码语言:txt
复制
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./Base.sol";

contract Child is Base {
    function value() public pure override returns (uint256) {
        return 2;
    }
}

test/Inherit.t.sol:

代码语言:txt
复制
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import "../src/Child.sol";

contract InheritTest is Test {
    function testOverrideValue() public {
        Child child = new Child();
        assertEq(child.value(), 2);
    }
}

执行测试:

代码语言:bash
复制
➜  counter git:(main) ✗ forge test --match-path test/Inherit.t.sol -vvv
[⠊] Compiling...
[⠒] Compiling 3 files with Solc 0.8.30
[⠑] Solc 0.8.30 finished in 506.50ms
Compiler run successful!

Ran 1 test for test/Inherit.t.sol:InheritTest
[PASS] testOverrideValue() (gas: 71422)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.70ms (458.38µs CPU time)

Ran 1 test suite in 215.73ms (1.70ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

八、多重继承冲突与线性化(C3 Linearization)

Solidity 使用 C3 线性化规则解决多重继承路径冲突,声明顺序决定调用顺序

src/Inheritance.sol:

代码语言:txt
复制
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract A {
    event Log(string msg);

    constructor() {
        emit Log("A initialized");
    }
}

contract B is A {
    constructor() {
        emit Log("B initialized");
    }
}

contract C is A {
    constructor() {
        emit Log("C initialized");
    }
}

contract D is B, C {
    constructor() {
        emit Log("D initialized");
    }
}

test/InheritanceTest.t.sol:

代码语言:txt
复制
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "forge-std/Test.sol";
import "../src/Inheritance.sol";

contract InheritanceTest is Test {
    function testInitOrder() public {
        vm.recordLogs();
        D d = new D();

        Vm.Log[] memory entries = vm.getRecordedLogs();
        for (uint i = 0; i < entries.length; i++) {
            emit log(string(entries[i].data));
        }
    }
}

执行结果:

代码语言:bash
复制
➜  counter git:(main) ✗ forge test --match-path test/InheritanceTest.t.sol -vvv
[⠊] Compiling...
[⠒] Compiling 5 files with Solc 0.8.30
[⠑] Solc 0.8.30 finished in 520.22ms
Compiler run successful with warnings:
Warning (2072): Unused local variable.
  --> test/InheritanceTest.t.sol:10:9:
   |
10 |         D d = new D();
   |         ^^^


Ran 1 test for test/InheritanceTest.t.sol:InheritanceTest
[PASS] testInitOrder() (gas: 75328)
Logs:
A initialized
B initialized
C initialized
D initialized

Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.21ms (760.88µs CPU time)

Ran 1 test suite in 200.50ms (4.21ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

九、总结

  • 继承是一种结构化组织代码的方式,适用于复用状态和逻辑
  • 抽象合约与接口有助于构建灵活可扩展的架构
  • 接口是与外部世界交互的标准桥梁
  • 多重继承需小心管理重写顺序和构造顺序

十、扩展:什么是 C3 Linearization?

C3 Linearization 是一种算法,用于解决多重继承中的方法解析顺序(MRO,Method Resolution Order),即当一个合约继承了多个父合约时,编译器如何确定哪个父合约的函数/构造函数先执行或使用。

它的目标是生成一个线性、有序、无重复的父类列表,用于:

  • 决定构造函数的执行顺序;
  • 决定函数调用的搜索顺序(比如 super.foo() 调用哪一个版本);
  • 解决菱形继承(diamond inheritance)冲突。

C3 Linearization 规则

给定一个继承链,比如:

代码语言:txt
复制
contract A {}
contract B is A {}
contract C is A {}
contract D is B, C {}

计算 D 的线性化顺序 L(D) 的规则如下:

代码语言:txt
复制
L(D) = [D] + merge(L(B), L(C), [B, C])

其中 + 表示连接,merge 是核心算法,它按如下方式合并多个列表(线性化链 + 当前继承顺序):

  1. 每次从各个列表的头部选出第一个类;
  2. 选择那个不在任何其他列表的尾部(即后续)中出现的类;
  3. 将它添加到结果中并从所有列表中移除;
  4. 重复,直到所有列表为空。

如果不能找到这样的类,说明继承存在冲突(比如循环继承),编译报错。

例子:解释上面的 D is B, C

代码语言:txt
复制
contract A {}
contract B is A {}
contract C is A {}
contract D is B, C {}

计算线性化顺序如下:

Step 1: 单独计算每个类的线性化结果

代码语言:txt
复制
L(A) = [A]
L(B) = [B] + merge(L(A), [A]) = [B, A]
L(C) = [C] + merge(L(A), [A]) = [C, A]

Step 2: 计算 D

代码语言:txt
复制
L(D) = [D] + merge(L(B), L(C), [B, C])
     = [D] + merge([B, A], [C, A], [B, C])

使用 merge:

  • B 是第一个候选项,不出现在任何其他列表的尾部(C 和 A),所以 B 合法。
  • 移除 B:→ [A], [C, A], [C]
  • C 是合法的头(不出现在其他尾部)→ 添加 C
  • 移除 C:→ [A], [A], []
  • A 是合法的头 → 添加 A
  • 所有列表为空

最终:L(D) = [D, B, C, A]


下一课预告

第 9 课:Solidity 事件与日志机制 —— 合约世界的 printf 与事件监听基础

下一篇
举报
领券