内省

为了平滑互操作性,标准通常要求智能合约实现 内省机制

在 Ethereum 中,https://eips.ethereum.org/EIPS/eip-165[EIP165] 标准定义了合约应该如何声明它们对给定接口的支持,以及其他合约如何查询这种支持。

Starknet 提供了类似的接口内省机制,由 SRC5 标准定义。

SRC5

与 Ethereum 对应物类似,https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md[SRC5] 标准要求合约实现 supports_interface 函数, 其他合约可以使用该函数来查询是否支持给定的接口。

用法

为了公开此功能,合约必须实现 SRC5Component,它定义了 supports_interface 函数。 这是一个合约示例:

#[starknet::contract]
mod MyContract {
    use openzeppelin_introspection::src5::SRC5Component;

    component!(path: SRC5Component, storage: src5, event: SRC5Event);

    #[abi(embed_v0)]
    impl SRC5Impl = SRC5Component::SRC5Impl<ContractState>;
    impl SRC5InternalImpl = SRC5Component::InternalImpl<ContractState>;

    #[storage]
    struct Storage {
        #[substorage(v0)]
        src5: SRC5Component::Storage
    }

    #[event]
    #[derive(Drop, starknet::Event)]
    enum Event {
        #[flat]
        SRC5Event: SRC5Component::Event
    }

    #[constructor]
    fn constructor(ref self: ContractState) {
        self.src5.register_interface(selector!("some_interface"));
    }
}

接口

#[starknet::interface]
pub trait ISRC5 {
    /// 查询合约是否实现了接口。
    /// 接收接口标识符,如 SRC-5 中所规定。
    /// 如果合约实现了 `interface_id`,则返回 `true`,否则返回 `false`。
    fn supports_interface(interface_id: felt252) -> bool;
}

计算接口 ID

如标准中所指定,接口 ID 是接口的所有 扩展函数选择器异或。我们强烈建议阅读 SNIP 以了解计算这些 扩展函数选择器的具体细节。有一些工具,例如 src5-rs 可以帮助完成此过程。

注册接口

对于合约声明其对给定接口的支持,我们建议使用 SRC5 组件在合约部署时通过构造函数直接或间接(作为初始化器)注册支持,如下所示:

#[starknet::contract]
mod MyContract {
    use openzeppelin_account::interface;
    use openzeppelin_introspection::src5::SRC5Component;

    component!(path: SRC5Component, storage: src5, event: SRC5Event);

    #[abi(embed_v0)]
    impl SRC5Impl = SRC5Component::SRC5Impl<ContractState>;
    impl InternalImpl = SRC5Component::InternalImpl<ContractState>;

    #[storage]
    struct Storage {
        #[substorage(v0)]
        src5: SRC5Component::Storage
    }

    #[event]
    #[derive(Drop, starknet::Event)]
    enum Event {
        #[flat]
        SRC5Event: SRC5Component::Event
    }

    #[constructor]
    fn constructor(ref self: ContractState) {
        // 注册合约对 ISRC6 接口的支持
        self.src5.register_interface(interface::ISRC6_ID);
    }

    (...)
}

查询接口

使用 supports_interface 函数查询合约对给定接口的支持。

#[starknet::contract]
mod MyContract {
    use openzeppelin_account::interface;
    use openzeppelin_introspection::interface::ISRC5DispatcherTrait;
    use openzeppelin_introspection::interface::ISRC5Dispatcher;
    use starknet::ContractAddress;

    #[storage]
    struct Storage {}

    #[external(v0)]
    fn query_is_account(self: @ContractState, target: ContractAddress) -> bool {
        let dispatcher = ISRC5Dispatcher { contract_address: target };
        dispatcher.supports_interface(interface::ISRC6_ID)
    }
}
如果你不确定合约是否实现了 SRC5,你可以按照 此处 描述的过程操作。