接口及合约交互
在上一节继承中,我们已经理解了一些抽象的概念:把各个合约都拥有的功能,作为统一接口在父合约里提供,让所有的子合约都可以继承。
接口(Interface)则更进一步,是一种定义了一组抽象方法的规范,接口描述了一组兑现应该具有哪些方法,但并不提供这些方法的具体实现。
接口只用来定义方法,而没有实现的方法体。抽象合约则可以有方法的实现,抽象合约可以实现一个或多个接口,以满足接口定义的方法要求。
接口的作用主要体现在以下几个方面:
规范行为:接口定义了一组方法,要求实现这个接口的合约必须提供这些方法的具体实现。通过实现接口,可以确保一组合约拥有相同的方法,并且这些方法的功能和行为是一致的,从而增强了代码的一致性和可预测性。
解耦合:接口可以将定义方法的部分与具体实现合约分离,从而实现了解耦合。因此我们可以基于接口来进行合约间的相互调用, 而不是基于实现。
接口也是合约设计中的方法规范,用于定义合约之间的协作方式,提高代码的可维护性和可读性。
使用接口
Solidity 用Interface
关键字定义接口,以下是一段示例代码定义了一个名为ICounter
的接口:
pragma solidity ^0.8.10;
interface ICounter {
function increment() external;
}
由于接口是一组方法规范的,因此接口:
- 无任何实现的函数
- 不能继承自其他接口
- 没有构造方法
- 没有状态变量
合约可以实现一个接口:
contract Counter is ICounter {
uint public count;
function increment() external override {
count += 1;
}
}
接口中的所有方法都是隐含的 virtual
方法,因此即便没有 virtual
,也可以被重写。
利用接口调用合约
软件设计中,有一个很重要的原则:依赖接口,而不是依赖实现。
假设我们链上已经部署了一个Counter
合约, 合约地址为0xabcd....
, 源代码文件:Counter.sol
,代码如下:
pragma solidity ^0.8.0;
contract Counter is ICounter {
uint public count;
function increment() external override {
count += 1;
}
}
如何在我们的合约里调用链上Counter
合约的increment()
方法呢?
import "./ICounter.sol";
contract MyContract {
function incrementCounter(address _counter) external {
ICounter(_counter).increment();
}
}
高亮代码ICounter(_counter).increment();
的含义是:把合约地址 _counter
类型转化为接口ICounter
类型(接口类型与合约类型一样,也是自定义类型),再调用接口内的increment()
方法。
还有一个方法是基于具体的实现合约,例如:
import "./Counter.sol";
contract MyContract {
function incrementCounter(address _counter) external {
Counter(_counter).increment();
}
}
代码Counter(_counter).increment();
的含义是:把合约地址 _counter
类型转化为合约Counter
类似,再调用合约里的increment()
方法。
依赖接口与依赖实现两个方法在EVM层面没有区别,最终都是通过合约地址找到对应的函数来执行。
但是用接口来进行合约交互时,会更明确得传递一个含义:我在调用该接口,而不管他的实现,可以有任意的合约来进行实现。另外在接口文件里,由于没有实现细节,代码会更清晰,因此我会更推荐是用接口,
调用 ERC20 合约进行转账
合约间的交互,使用非常广泛,因此,这里再举一个示例:实现一个奖励合约,给用户发放 ERC20 代币奖励。
ERC20 代币如下:
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor() ERC20("MyToken", "MTK") {}
}
先在脑海里想一想如何实现?
我们可以通过 IERC20 接口调用MyToken
的 transfer
方法,把奖励合约中的MyToken
发送给用户。
代码可以这样写:
contract Award {
IERC20 immutable token;
// 部署时传入 MyToken 合约地址
constructor(address t) {
token = IERC20(t);
}
function sendAward(address user) public {
token.transfer(user, 100);
}
}
sendAward
函数用来发送奖金,当然需要需要在 Award
合约创建好之后,向 Award
转入一些MyToken
。
如果文章不好理解,区块链技术集训营 视频课程可以让大家学的更轻松。
小结
本文,我们学习了接口的概念:接口是一组抽象方法的规范,在合约间相互调用时,应该依赖接口,而不是依赖实现。
接口使用 interface
来定义,接口也是一个类型,在合约间相互调用时,我们把地址(合约实例)转化为接口,再调用相应的函数。
来 DeCert.me 码一个未来,DeCert 让每一位开发者轻松构建自己的可信履历。 前往挑战 Solidity 101:接口,完成挑战并获得技能认证 NFT。
DeCert.me 由登链社区 @UpchainDAO 孵化,欢迎 Discord 频道 一起交流。
本教程来自贡献者 @Tiny熊。