接口和分发器

本节介绍 OpenZeppelin Contracts for Cairo 提供的接口,并解释其背后的设计选择。

接口可以在 interface 子模块下的模块树中找到,例如 token::erc20::interface。例如:

use openzeppelin_token::erc20::interface::IERC20;

或者

use openzeppelin_token::erc20::interface::ERC20ABI;
为了简单起见,我们将使用 ERC20 作为示例,但相同的概念适用于其他模块。

接口 traits

该库提供了三种类型的 traits 来实现或与合约交互:

标准 traits

这些 traits 与预定义的接口(例如标准)相关联。 它仅包括接口中定义的函数,并且是与兼容合约交互的标准方式。

#[starknet::interface]
pub trait IERC20<TState> {
    fn total_supply(self: @TState) -> u256;
    fn balance_of(self: @TState, account: ContractAddress) -> u256;
    fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256;
    fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool;
    fn transfer_from(
        ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256
    ) -> bool;
    fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool;
}

ABI traits

它们描述了合约的完整接口。这对于与此库提供的预设合约交互非常有用,例如 ERC20 预设,它包括来自不同 traits 的函数,例如 IERC20IERC20Camel

该库为大多数组件提供了一个 ABI trait,它提供了所有外部函数签名,即使大多数时候不需要同时实现所有这些签名。当与实现该组件的合约交互时,这可能会有所帮助,而不是定义新的 dispatcher。
#[starknet::interface]
pub trait ERC20ABI<TState> {
    // IERC20
    fn total_supply(self: @TState) -> u256;
    fn balance_of(self: @TState, account: ContractAddress) -> u256;
    fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256;
    fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool;
    fn transfer_from(
        ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256
    ) -> bool;
    fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool;

    // IERC20Metadata
    fn name(self: @TState) -> ByteArray;
    fn symbol(self: @TState) -> ByteArray;
    fn decimals(self: @TState) -> u8;

    // IERC20CamelOnly
    fn totalSupply(self: @TState) -> u256;
    fn balanceOf(self: @TState, account: ContractAddress) -> u256;
    fn transferFrom(
        ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256
    ) -> bool;
}

Dispatcher traits

使用 #[starknet::interface] 注释的 traits 会自动生成一个 dispatcher,该 dispatcher 可用于与实现给定接口的合约进行交互。可以通过将 DispatcherDispatcherTrait 后缀附加到 trait 名称来导入它们,如下所示:

use openzeppelin_token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};

其他类型的 dispatchers 也是从带注释的 trait 自动生成的。有关更多信息,请参见 Cairo 书籍的 与另一个合约交互 部分。

在示例中,IERC20Dispatcher 用于与合约交互,但 IERC20DispatcherTrait 需要在作用域内才能使函数可用。

双重接口

camelCase 函数已弃用,仅为了向后兼容而维护。 建议仅将 snake_case 接口与合约和组件一起使用。camelCase 函数将在 未来的版本中删除。

按照 伟大接口迁移 计划,我们向所有预先存在的 camelCase 合约添加了 snake_case 函数,目的是最终放弃对后者的支持。

简而言之,该库提供了两种类型的接口和实用程序来处理它们:

  1. camelCase 接口,这是我们到目前为止一直在使用的接口。

  2. snake_case 接口,这是我们正在迁移到的接口。

这意味着目前我们的大多数合约都实现了 双重接口。例如,ERC20 预设合约公开了 transferFromtransfer_frombalanceOfbalance_of 等。

双重接口适用于 OpenZeppelin Contracts for Cairo 早期版本 (v0.6.1 及以下) 中存在的所有外部函数。

IERC20

ERC20 接口 trait 的默认版本公开了 snake_case 函数:

#[starknet::interface]
pub trait IERC20<TState> {
    fn name(self: @TState) -> ByteArray;
    fn symbol(self: @TState) -> ByteArray;
    fn decimals(self: @TState) -> u8;
    fn total_supply(self: @TState) -> u256;
    fn balance_of(self: @TState, account: ContractAddress) -> u256;
    fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256;
    fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool;
    fn transfer_from(
        ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256
    ) -> bool;
    fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool;
}

IERC20Camel

最重要的是,该库还提供了相同接口的 camelCase 版本:

#[starknet::interface]
pub trait IERC20Camel<TState> {
    fn name(self: @TState) -> ByteArray;
    fn symbol(self: @TState) -> ByteArray;
    fn decimals(self: @TState) -> u8;
    fn totalSupply(self: @TState) -> u256;
    fn balanceOf(self: @TState, account: ContractAddress) -> u256;
    fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256;
    fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool;
    fn transferFrom(
        ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256
    ) -> bool;
    fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool;
}