教程区块链区块链技术ch1212.1 Solidity进阶:继承、接口、库与内联汇编

本页目录

第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"] — 优先 B

12.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 复用

库有两种调用方式:

  1. internal 调用:代码内联,无外部调用开销(using MyLib for type
  2. 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)+ 运行时库的独立地址
delegatecall2600 + 运行时库的地址,执行上下文为调用者
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));         // 1500n

12.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

评论加载中…

发表评论

0/2000