继承多态与库合约
学习目标
- 掌握 Solidity 继承机制与多态
- 理解 C3 线性化算法
- 掌握库合约(library)的定义和使用
继承基础
继承定义
- 使用
is关键字 - 继承的实现方式是代码拷贝:部署后变成一个合约
可见性与继承
- private:子合约不可见,但不能定义同名成员
- internal:子合约可见
- public:完全可见
- event 和 modifier 重名也会冲突
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
contract Base {
string private name;
event MyEvent();
modifier myMod() {
_;
}
function foo() private {}
}
contract ContractA is Base {
string private name; // private 可以重名(不可见但存在冲突)
// function foo() private {} // 即使 private 也不能同名
}
构造函数与继承
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
abstract contract Base {
constructor(string memory _name) {}
}
contract ContractA is Base {
string name;
constructor() Base(name) {}
}
多态(Polymorphism)
virtual 与 override
- 父合约函数标记
virtual表示可被覆盖 - 子合约函数用
override进行覆盖 - abstract 合约可以有未实现的函数
覆盖规则
- 如果从继承路径中继承了同一函数的不同版本,必须覆盖
- override 需要列出所有被覆盖的父合约
C3 线性化
为什么需要线性化
- 多重继承中,合约间的继承关系构成图结构
- 需要确定:存储布局堆叠顺序、构造函数执行顺序、super 指向谁
线性化规则
- 父在子前
- 兄在弟前(从右到左遍历)
- 结果确定(deterministic)
算法描述
- 从当前合约开始找父合约,从右向左遍历
- 如果某合约的所有子合约都已遍历过,该合约入列;否则回退找兄弟
- 到根合约结束遍历,输出并倒置
构造函数执行顺序 = 线性化顺序
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
contract A {
uint public x = 5;
constructor() {}
}
contract B is A {
constructor() {
x += 10;
}
}
contract C is A {
constructor() {
x *= 10;
}
}
contract D is C, B {
// 线性化:D → B → C → A
// 构造顺序:A → C → B → D
// 计算过程:x=5 → x*=10=50 → x+=10=60
// 最终 x = 60
}
super 关键字
- super 指向线性化序列中的前驱合约(不是直接父合约)
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
contract A {
event Log(string message);
function bar() public virtual {
emit Log("A.bar called");
}
}
contract B is A {
function bar() public virtual override {
emit Log("B.bar called");
super.bar(); // 调用 C.bar()(不是 A.bar!)
}
}
contract C is A {
function bar() public virtual override {
emit Log("C.bar called");
super.bar(); // 调用 A.bar()
}
}
contract D is B, C {
// 线性化:D → C → B → A
function bar() public override(B, C) {
super.bar();
}
// 调用顺序:D.bar → C.bar → B.bar → A.bar
}
多重继承的应用建议
- 少用继承,多用组合(composition)+ 接口(interface)
- 但 Solidity 中组合受技术条件制约
- 不要滥用继承
应用案例
- ERC721:NFT 标准,基础功能
- ERC721URIStorage:Token 与外部资源的绑定映射
- ERC721Enumerable:遍历能力
库合约(Library)
定义与约束
- 使用
library关键字定义 - 不能有成员变量、不能有 Ether、不能继承、不能 selfdestruct
- 不能有构造函数
library LibraryName {
// 可以定义 struct、enum、constant
function f() internal pure {
// 函数实现
}
}
调用方式
直接调用
import "LibraryName.sol";
LibraryName.functionName(args);
using 语法糖
using LibraryName for Type;
// 之后可以用 value.functionName() 的形式调用
// 等价于 LibraryName.functionName(value)
示例:SafeMath 和 Math 库
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
library SafeMath {
function add(uint x, uint y) internal pure returns (uint) {
uint z = x + y;
require(z >= x, "uint overflow");
return z;
}
}
library Math {
function sqrt(uint y) internal pure returns (uint z) {
if (y > 3) {
z = y;
uint x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
}
}
contract TestSafeMath {
using Math for uint;
uint public MAX_UINT = 2 ** 256 - 1;
function testAdd(uint x, uint y) public pure returns (uint) {
return SafeMath.add(x, y); // 直接调用
}
function testSquareRoot(uint x) public pure returns (uint) {
return x.sqrt(); // using 语法糖
}
}
示例:Array 库
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
library Array {
function remove(uint[] storage arr, uint index) public {
require(arr.length > 0, "Can't remove from empty array");
arr[index] = arr[arr.length - 1];
arr.pop();
}
}
contract TestArray {
uint[] public arr;
function testArrayRemove() public {
for (uint i = 0; i < 3; i++) {
arr.push(i);
}
Array.remove(arr, 1);
assert(arr.length == 2);
assert(arr[0] == 0);
assert(arr[1] == 2);
}
}
库的内部机制
- internal 函数:inline 到调用者代码中(Embedded Library),不需要单独部署
- public 函数:库单独部署为合约,通过 delegatecall 调用(Linked Library)
- 与代理模式的对比:
- 代理模式:通过兼容存储布局,delegatecall 借用代码
- 库合约:通过传递 storage 参数,delegatecall 借用代码
- 库的链接是编译时完成,不能运行时动态切换(不支持升级)
库中的 struct 和 mapping
Library 的一个特殊能力:public/external 函数可以接受 storage 参数(普通合约不行)。这使得 mapping 可以作为参数传递。
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
library TransferOperation {
function transfer(
address from,
address to,
uint amount,
mapping(address => uint) storage balanceOf
) public {
uint current = balanceOf[from];
require(current >= amount, "not enough balance!");
uint toc = balanceOf[to];
current -= amount;
toc += amount;
balanceOf[from] = current;
balanceOf[to] = toc;
}
}
// 库中定义 struct 并操作
pragma solidity >=0.7.0 <0.9.0;
library LibraryShape {
struct Rectangle {
uint width;
uint height;
}
function area(Rectangle storage s) public view returns (uint) {
return s.width * s.height;
}
}
contract Draw {
using LibraryShape for LibraryShape.Rectangle;
LibraryShape.Rectangle shape;
function getArea() public view returns (uint) {
return shape.area(); // using 语法糖
}
}
综合示例:IterableMapping
解决 mapping 无法遍历的问题,通过库实现可遍历的 mapping。
设计要点:
1. 数据在数组中从索引 1 开始存放(因为未赋值的 value 的 index 默认为 0)
2. 数组数据不删除(不用 pop),而是标记 deleted
3. mapping 中的 value 删除时必须用 delete(所有位变为 0)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
library IterableMapping {
struct itmap {
mapping(uint => IndexValue) data;
KeyFlag[] keys;
uint size;
}
struct IndexValue {
uint keyIndex;
uint value;
}
struct KeyFlag {
uint key;
bool deleted;
}
function insert(itmap storage self, uint key, uint value) public returns (bool replaced) {
uint keyIndex = self.data[key].keyIndex;
self.data[key].value = value;
if (keyIndex > 0) return true;
else {
uint index = self.keys.length;
self.keys.push(KeyFlag(key, false));
self.data[key].keyIndex = index + 1; // 从 1 开始
self.keys[index].key = key;
self.size++;
return false;
}
}
function remove(itmap storage self, uint key) internal returns (bool success) {
uint keyIndex = self.data[key].keyIndex;
if (keyIndex == 0) return false;
delete self.data[key];
self.keys[keyIndex - 1].deleted = true;
self.size--;
return true;
}
function contains(itmap storage self, uint key) internal view returns (bool) {
return self.data[key].keyIndex > 0;
}
function iterate_start(itmap storage self) internal view returns (uint) {
uint keyIndex = 0;
while (keyIndex < self.keys.length && self.keys[keyIndex].deleted)
keyIndex++;
return keyIndex;
}
function iterate_valid(itmap storage self, uint keyIndex) internal view returns (bool) {
return keyIndex < self.keys.length;
}
function iterate_next(itmap storage self, uint keyIndex) internal view returns (uint) {
keyIndex++;
while (keyIndex < self.keys.length && self.keys[keyIndex].deleted)
keyIndex++;
return keyIndex;
}
function iterate_get(itmap storage self, uint keyIndex) internal view returns (uint key, uint value) {
key = self.keys[keyIndex].key;
value = self.data[key].value;
}
}
主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/7500