抽象合约

Solidity 中,接口、抽象合约、与合约的抽象程度大致如下:

抽象合约介于接口与具体合约之间。抽象合约与具体的合约最本质的区别在于抽象合约不能够被实例化。

抽象合约中往往存在未被具体实现的虚(virtual)函数。

抽象合约声明

抽象合约通过 abstract contract 声明:

AbstractContract.sol
运行
复制
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;

abstract contract Greeting {
    function sayHi() external virtual returns (string memory);

    function sayBye() external virtual returns (string memory);
}

抽象合约继承抽象合约

一个抽象合约可以继承另一个抽象合约:

AbstractContract.sol
运行
复制
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;

abstract contract Greeting {
    function sayHi() external virtual returns (string memory);

    function sayBye() external virtual returns (string memory);
}

// 继承另一个抽象合约,只实现其中的 sayHi 虚函数
abstract contract SubGreeting is Greeting {
    function sayHi() external pure override returns (string memory) {
        return "Hi";
    }
}

一个合约如果继承于另一个抽象合约,且未完全实现抽象合约中未实现的虚函数,则该合约也必须标志为抽象合约,如上例的 SubGreeting 未实现 sayBye 函数,因此该合约也必须声明为抽象合约。

具体合约继承抽象合约

继承于抽象合约的具体合约,必须实现抽象合约中的所有虚函数。

AbstractContract.sol
运行
复制
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;

abstract contract Greeting {
    function sayHi() external virtual returns (string memory);

    function sayBye() external virtual returns (string memory);
}

// 继承另一个抽象合约,只实现其中的 sayHi 虚函数
abstract contract SubGreeting is Greeting {
    function sayHi() external pure override returns (string memory) {
        return "Hi";
    }
}

contract GreetingImpl is SubGreeting {
    function sayBye() external pure override returns (string memory) {
        return "Bye";
    }
}

抽象合约与虚函数的关系

需要注意,抽象合约与虚函数之间并无必要联系:

  • 存在虚函数不一定就是抽象合约,这是因为 虚函数 本身是可以带有一个默认实现的,因此一个具体合约也可以带有一个虚函数,只要该虚函数有一个默认实现
  • 抽象合约中也不一定有虚函数,即使所有函数都已经被具体实现,该合约仍可以被标志为抽象合约。此举旨在告知使用者不应直接实例化此合约。例如,该合约可能是一个模板合约,需要进一步继承并扩展

假设我们需要实现一批多语言的打招呼和告别的合约,合约有两个函数,sayHi sayBye,默认的实现是使用英文打招呼和告别:

Greeting.sol
运行
复制
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;

abstract contract Greeting {
    // 默认实现为英文打招呼
    function sayHi() external virtual returns (string memory) {
        return "Hi";
    }

    // 默认实现为英文告别
    function sayBye() external virtual returns (string memory) {
        return "Bye";
    }
}

该实例是虚拟合约,不能被初始化,它应该被其他具体合约继承,根据不同语言的具体合约具体地实现 sayHi sayBye 函数。一个英文版的具体合约:

Greeting.sol
运行
复制
// 英文版具体合约,可以被实例化
contract EnglishGreeting is Greeting {
    // 内部未重新实现 sayHi, sayBye 两个虚函数
    // 但仍然是合法的,此时将使用虚函数的默认实现
}

此时我们再新增一个中文版的合约:

Greeting.sol
运行
复制
contract ChineseGreeting is Greeting {
    function sayHi() external pure override returns (string memory) {
        return unicode"你好";
    }

    function sayBye() external pure override returns (string memory) {
        return unicode"拜拜";
    }
}