Inheritance, Polymorphism, and Library Contracts

Inheritance, Polymorphism, and Library Contracts

Learning Objectives

  • Master Solidity's inheritance mechanism and polymorphism
  • Understand the C3 linearization algorithm
  • Master the definition and usage of library contracts

Inheritance Basics

Inheritance Definition

  • Use the is keyword
  • Inheritance is implemented by code copying: it becomes a single contract after deployment

Visibility and Inheritance

  • private: not visible to child contracts, but cannot define members with the same name
  • internal: visible to child contracts
  • public: fully visible
  • Event and modifier name clashes also cause conflicts
// 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 can have the same name (not visible but conflicts exist)

    // function foo() private {} // even if private, cannot have the same name
}

Constructor and Inheritance

// 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 and override

  • Parent contract functions marked virtual can be overridden
  • Child contract functions use override to override
  • Abstract contracts can have unimplemented functions

Override Rules

  • If different versions of the same function are inherited from the inheritance path, it must be overridden
  • override needs to list all overridden parent contracts

C3 Linearization

Why Linearization is Needed

  • In multiple inheritance, the inheritance relationships between contracts form a graph structure
  • Need to determine: storage layout stacking order, constructor execution order, and what super points to

Linearization Rules

  • Parent before child
  • Sibling before sibling (traverse from right to left)
  • Deterministic result

Algorithm Description

  1. Start from the current contract, find parent contracts, traversing from right to left
  2. If all child contracts of a certain contract have been traversed, that contract is enqueued; otherwise, backtrack to find siblings
  3. End traversal at the root contract, output and reverse

Constructor Execution Order = Linearization Order

// 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 {
    // Linearization: D → B → C → A
    // Constructor order: A → C → B → D
    // Calculation process: x=5 → x*=10=50 → x+=10=60
    // Final x = 60
}

super Keyword

  • super points to the predecessor contract in the linearization sequence (not the direct parent contract)
// 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(); // Calls C.bar() (not A.bar!)
    }
}

contract C is A {
    function bar() public virtual override {
        emit Log("C.bar called");
        super.bar(); // Calls A.bar()
    }
}

contract D is B, C {
    // Linearization: D → C → B → A
    function bar() public override(B, C) {
        super.bar();
    }
    // Call order: D.bar → C.bar → B.bar → A.bar
}

Recommendations for Multiple Inheritance

  1. Use inheritance sparingly, prefer composition + interfaces
  2. However, composition in Solidity is constrained by technical limitations
  3. Do not abuse inheritance

Application Cases

  • ERC721: NFT standard, basic functionalities
  • ERC721URIStorage: Binding mapping between tokens and external resources
  • ERC721Enumerable: Traversal capability

Library Contracts

Definition and Constraints

  • Defined using the library keyword
  • Cannot have member variables, cannot hold Ether, cannot inherit, cannot selfdestruct
  • Cannot have a constructor
library LibraryName {
    // Can define struct, enum, constant
    function f() internal pure {
        // Function implementation
    }
}

Calling Methods

Direct Call

import "LibraryName.sol";
LibraryName.functionName(args);

using Syntax Sugar

using LibraryName for Type;
// Afterwards, it can be called in the form of value.functionName()
// Equivalent to LibraryName.functionName(value)

Example: SafeMath and Math Libraries

// 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); // Direct call
    }

    function testSquareRoot(uint x) public pure returns (uint) {
        return x.sqrt(); // using syntax sugar
    }
}

Example: Array Library

// 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 Mechanisms of Libraries

  1. internal functions: inlined into the caller's code (Embedded Library), no separate deployment needed
  2. public functions: the library is deployed as a separate contract and called via delegatecall (Linked Library)
  3. Comparison with Proxy Patterns:
  4. Proxy pattern: delegatecall borrows code by ensuring compatible storage layout
  5. Library contract: delegatecall borrows code by passing storage parameters
  6. Library linking is done at compile time, cannot be dynamically switched at runtime (does not support upgrades)

Structs and Mappings in Libraries

A special capability of Libraries: public/external functions can accept storage parameters (which regular contracts cannot). This allows mappings to be passed as parameters.

// 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;
    }
}
// Define and operate on struct in library
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 syntax sugar
    }
}

Comprehensive Example: IterableMapping

Solves the problem of non-iterable mappings by implementing an iterable mapping through a library.

Key design points:

  1. Data is stored in the array starting from index 1 (because the index of an unassigned value defaults to 0)
  2. Array data is not deleted (no pop), but marked as deleted
  3. When deleting a value in a mapping, delete must be used (all bits become 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; // Start from 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 days ago
下一篇 5 days ago

Related Posts

EN
简体中文 繁體中文 English