Introspection
To smooth interoperability, often standards require smart contracts to implement introspection mechanisms.
In Ethereum, the EIP165 standard defines how contracts should declare their support for a given interface, and how other contracts may query this support.
Starknet offers a similar mechanism for interface introspection defined by the SRC5 standard.
SRC5
Similar to its Ethereum counterpart, the SRC5 standard requires contracts to implement the supports_interface
function,
which can be used by others to query if a given interface is supported.
Usage
To expose this functionality, the contract must implement the SRC5Component, which defines the supports_interface
function.
Here is an example contract:
#[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"));
}
}
Computing the interface ID
The interface ID, as specified in the standard, is the XOR of all the Extended Function Selectors of the interface. We strongly advise reading the SNIP to understand the specifics of computing these extended function selectors. There are tools such as src5-rs that can help with this process.
Registering interfaces
For a contract to declare its support for a given interface, we recommend using the SRC5 component to register support upon contract deployment through a constructor either directly or indirectly (as an initializer) like this:
#[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) {
// Register the contract's support for the ISRC6 interface
self.src5.register_interface(interface::ISRC6_ID);
}
(...)
}
Querying interfaces
Use the supports_interface
function to query a contract’s support for a given 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)
}
}
If you are unsure whether a contract implements SRC5 or not, you can follow the process described in here. |