第4章(4.1-4.4)已构建了 Solidity 基础认知。这节开始进入进阶阶段:当智能合约从"教学示例"演变为"生产系统",模块化、复用性和极致的 gas 效率成为核心设计目标。
12.1.1 继承:钻石问题与 C3 线性化
Solidity 支持多重继承,但采用 C3 线性化解决同名函数的冲突:
solidity
// C3 线性化示例
// 继承链: A -> B -> C -> D
// 方法查找顺序按 "最左优先、深度优先" 确定
contract A { function f() public pure returns (string memory) { return "A"; } }
contract B is A { function f() public pure returns (string memory) { return "B"; } }
contract C is A { function f() public pure returns (string memory) { return "C"; } }
contract D is B, C {} // f() 返回 "B"(B 在左)
// 顺序反过来:
// contract D2 is C, B {} // f() 返回 "C"typescript
/**
* TypeScript 模拟 C3 线性化的拓扑排序
*/
function linearizeC3(
bases: Map<string, string[]>
): Map<string, string[]> {
const result = new Map<string, string[]>();
function merge(className: string, parents: string[]): string[] {
if (result.has(className)) return result.get(className)!;
const merged: string[] = [className];
// 合并父类线性化结果
for (const parent of parents) {
const parentLinear = merge(parent, bases.get(parent) || []);
for (const cls of parentLinear) {
if (!merged.includes(cls)) merged.push(cls);
}
}
result.set(className, merged);
return merged;
}
for (const [name, parents] of bases) {
merge(name, parents);
}
return result;
}
// 示例
const inheritanceDAG = new Map([
["A", []],
["B", ["A"]],
["C", ["A"]],
["D", ["B", "C"]],
]);
const mro = linearizeC3(inheritanceDAG);
console.log("D 的 MRO:", mro.get("D")); // ["D", "B", "A", "C"] — 优先 B12.1.2 接口与抽象合约
solidity
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
}
abstract contract BasePausable {
bool internal _paused;
modifier whenNotPaused() {
require(!_paused, "paused");
_;
}
function _pause() internal { _paused = true; }
}
// 组合使用:接口来定义标准,抽象合约来复用逻辑
contract MyToken is BasePausable, IERC20 {
// 实现具体逻辑
}12.1.3 库(Library):Stateless 复用
库有两种调用方式:
- internal 调用:代码内联,无外部调用开销(
using MyLib for type) - delegatecall:在调用者上下文中执行(用于代理模式,12.5 节)
solidity
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "overflow");
return c;
}
}
using SafeMath for uint256;
uint256 result = a.add(b); // 编译器内联为 SafeMath.add(a, b)库作为代码复用的 Gas 优势
| 方式 | 每次调用 Gas | 代码存放位置 |
|---|---|---|
| 内部函数(internal) | 0 外部调用 | 调用者合约中 |
| 继承(is) | 0 外部调用 | 调用者合约中 |
| 外部库(external) | 2600(cold)+ 运行时 | 库的独立地址 |
delegatecall | 2600 + 运行时 | 库的地址,执行上下文为调用者 |
typescript
/**
* 模拟 SafeMath 溢出检查的纯 TS 实现
* 核心:a + b < a 当且仅当发生了 unsigned 溢出
*/
function safeAdd(a: bigint, b: bigint): { result: bigint; overflow: boolean } {
const sum = a + b;
// 在 true 256-bit 无符号域中:如果 a + b < a,则发生了溢出
const MAX_UINT256 = (1n << 256n) - 1n;
const overflow = sum > MAX_UINT256; // 注意:BigInt 本身不限制位宽,但 EVM 会 wrap
// 这里用范围检查模拟 EVM 语义
return {
result: overflow ? sum & MAX_UINT256 : sum,
overflow,
};
}
// --- 模拟 EVM 回滚行为 ---
function checkedAdd(a: bigint, b: bigint): bigint {
const { result, overflow } = safeAdd(a, b);
if (overflow) throw new Error("SafeMath: overflow");
return result;
}
// Solidity 0.8.0+ 已经内置 checked arithmetic,0.7.x 仍需要手动用库
console.log(checkedAdd(2n**255n, 2n**255n)); // Error: overflow
console.log(checkedAdd(1000n, 500n)); // 1500n12.1.4 内联汇编:极致的 Gas 控制
Solidity assembly { ... } 直接操作 EVM 操作码,以删除冗余边界检查(当编译器无法证明安全时):
solidity
// 标准 Solidity: 每次访问都有边界检查
function sumArray(uint256[] memory arr) public pure returns (uint256) {
uint256 sum;
for (uint i; i < arr.length; ) { // unchecked 增量
sum += arr[i++];
}
return sum;
}
// 内联汇编版本:消除所有边界检查(假设编译器无法证明)
function sumArrayAssembly(uint256[] memory arr) public pure returns (uint256 s) {
assembly {
let len := mload(arr) // arr 在 memory 中的第一个 word 是长度
let ptr := add(arr, 0x20) // 数据从第 32 字节开始
let end := add(ptr, mul(len, 0x20))
for {} lt(ptr, end) { ptr := add(ptr, 0x20) } {
s := add(s, mload(ptr))
}
}
}内存布局的精确理解
typescript
/**
* 模拟 EVM 内存数组布局
* 内存布局: [length: 32 bytes][item0: 32][item1: 32]...
* 地址从 0x80 开始
*/
function evmMemoryArray(arr: bigint[]): { mem: Uint8Array; lengthSlot: number; dataStart: number } {
// 简化:32 字节对齐
const totalSize = 32 + arr.length * 32;
const mem = new Uint8Array(totalSize);
// 写入长度
const lenView = new DataView(mem.buffer, 0, 8);
lenView.setBigUint64(24, BigInt(arr.length), false); // 写入最后 8 字节,零填充前 24 字节
// 写入数据
for (let i = 0; i < arr.length; i++) {
const offset = 32 + i * 32;
const view = new DataView(mem.buffer, offset + 24, 8);
view.setBigUint64(0, arr[i], false);
}
return { mem, lengthSlot: 0, dataStart: 32 };
}
// 演示
const sample = evmMemoryArray([1n, 2n, 3n, 4n]);
console.log("Length slot:", sample.mem.slice(0, 32));
console.log("Data starts at offset 32:", sample.dataStart);
console.log("Item[0]:", new DataView(sample.mem.buffer, 32 + 24, 8).getBigUint64(0, false));12.1.5 知识地图
mindmap
root((Solidity 进阶))
继承
C3 线性化<br/>MRO
多继承冲突解决
虚函数覆盖
接口
IERC20 / ERC721 标准
function selector
强制契约
库
internal 内联<br/>零外部调用开销
delegatecall<br/>代理模式关键
using ... for ... 语法糖
内联汇编
直接 EVM 操作码
gas 效率极致
memory 布局精确控制
> ← 上一章 ch11 总结 | 前往 → 12.2 事件、日志与链下监听 |*
评论
0评论加载中…