继承

分析

在 Solidity 中,使用继承通常比组合更简单,也更节省 Gas。通过继承扩展合约时,子合约可以高效地将其变量与父合约的变量打包在一起。

注意顺序:变量的顺序由C3 线性化确定。您只需知道子变量位于父变量之后即可。这可以提高存储打包效率,而这正是优化 Gas 使用的关键。

  1. 继承 vs 组合的基本概念

继承(Inheritance)

contract Parent {
    uint128 parentVar1;
    uint128 parentVar2;
}

contract Child is Parent {
    uint128 childVar1;
    uint128 childVar2;
}

组合(Composition)

contract Parent {
    uint128 parentVar1;
    uint128 parentVar2;
}

contract Child {
    Parent public parent;
    uint128 childVar1;
    uint128 childVar2;
}
  1. 存储布局的差异

继承的存储布局

存储槽 0: [parentVar1(128位)] [parentVar2(128位)]
存储槽 1: [childVar1(128位)]  [childVar2(128位)]
  • 所有变量在同一个合约的存储空间中

  • 可以实现跨父子类的变量打包

  • 总共使用 2 个存储槽

组合的存储布局

Child 合约:

存储槽 0: [parent合约地址(160位)] [未使用(96位)]
存储槽 1: [childVar1(128位)] [childVar2(128位)]

Parent 合约(独立存储):

存储槽 0: [parentVar1(128位)] [parentVar2(128位)]
  • 需要额外存储父合约的地址

  • 无法跨合约进行变量打包

  • 访问父合约变量需要额外的外部调用

测试

文件位置:

src/contract/Inheritance.sol
test/contract/Inheritance.t.sol

测试结果:

forge test --gas-report --mt test_inheritance_ -vvv --optimize

继承图示

查看存储结构:

forge inspect InheritanceChild  storageLayout
forge inspect CompositionChild  storageLayout

继承图示 2

继承图示 3

继承组合
存储成本变量共享存储空间,可以打包需要额外存储父合约地址,无法打包
访问成本直接内存/存储访问需要外部合约调用
部署成本单个合约,代码可能更大但只部署一次需要部署多个合约

总结

  • 优先使用继承的场景

    • 需要频繁访问父合约的状态变量

    • 希望最大化存储打包效率

    • 父子关系紧密,逻辑高度相关

  • 考虑使用组合的场景

    • 需要运行时更换依赖合约

    • 父合约可能被多个子合约共享

    • 需要更灵活的架构设计

对应源码

本文配套代码固定到提交 8960383;Gas 数据会随编译器、优化器参数和 EVM 版本变化。