继承多态与库合约

继承多态与库合约

学习目标

  • 掌握 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)

算法描述

  1. 从当前合约开始找父合约,从右向左遍历
  2. 如果某合约的所有子合约都已遍历过,该合约入列;否则回退找兄弟
  3. 到根合约结束遍历,输出并倒置

构造函数执行顺序 = 线性化顺序

// 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
}

多重继承的应用建议

  1. 少用继承,多用组合(composition)+ 接口(interface)
  2. 但 Solidity 中组合受技术条件制约
  3. 不要滥用继承

应用案例

  • 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);
    }
}

库的内部机制

  1. internal 函数:inline 到调用者代码中(Embedded Library),不需要单独部署
  2. public 函数:库单独部署为合约,通过 delegatecall 调用(Linked Library)
  3. 与代理模式的对比
  4. 代理模式:通过兼容存储布局,delegatecall 借用代码
  5. 库合约:通过传递 storage 参数,delegatecall 借用代码
  6. 库的链接是编译时完成,不能运行时动态切换(不支持升级)

库中的 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

(0)
Walker的头像Walker
上一篇 5天前
下一篇 5天前

相关推荐

  • 合约交互与 ABI

    合约交互与 ABI 学习目标 掌握合约间调用方式、接口定义、ABI 数据结构、Web3.js 访问合约的方法。 合约间调用基础 EOA(外部账号)发起调用,可能触发合约间的调用链 调用者必须持有被调用合约的地址 方式一:同文件内直接调用 当两个合约在同一个文件中时,可以直接通过合约类型和地址进行调用: // SPDX-License-Identifier: …

    1天前
    400
  • 引用类型详解:数组、结构体、映射、字符串

    引用类型详解:数组、结构体、映射、字符串 学习目标 掌握 Solidity 中四种引用类型(数组、结构体、映射、字符串/变长字节数组)的定义和使用方法。 前置知识 已学习值类型(整型、布尔、地址、定长字节数组等)。 数组(Array) storage 中的数组 Solidity 中数组分为两种: 静态数组 T[K]:长度固定,编译时确定 动态数组 T[]:长…

  • Gas机制与转账设计

    Gas机制与转账设计 学习目标 理解区块链的经济模型与激励机制 掌握 Gas、Gas Price、Gas Fee 的概念与关系 理解 Ether 单位与转换 掌握合约转账设计(receive / fallback / payable) 区块链的经济系统 为什么需要经济模型? 计算与存储资源是稀缺的:每个节点都要执行和存储所有交易 共识和 trustless …

    22小时前
    300
  • Solidity 值类型详解

    Solidity 值类型详解 学习目标 掌握 Solidity 的整型、布尔、地址、定长字节数组、枚举等值类型 理解 EVM 256 位机器架构对类型设计的影响 掌握类型转换规则(隐式 vs 显式) 了解溢出问题的历史与解决方案 理解 Solidity 中所有类型的默认值机制 一、整型(int / uint) 1.1 基本概念 Solidity 提供有符号整…

  • 存储位置与拷贝机制:storage、memory、calldata

    存储位置与拷贝机制:storage、memory、calldata 学习目标 理解 EVM 中三种数据存储位置的特点,以及引用类型在不同存储位置之间赋值时的拷贝规则。 前置知识 已学习值类型和引用类型(数组、结构体、映射、字符串)。 三种存储位置 storage —— 持久化存储 类似数据库,数据永久保存在区块链上 成员变量(状态变量)默认存储在 stora…

简体中文 繁体中文 English