Introspection

Expect this module to evolve.

ERC165

The ERC165 standard allows smart contracts to exercise type introspection on other contracts, that is, examining which functions can be called on them. This is usually referred to as a contract’s interface.

Cairo contracts, like Ethereum contracts, have no native concept of an interface, so applications must usually simply trust they are not making an incorrect call. For trusted setups this is a non-issue, but often unknown and untrusted third-party addresses need to be interacted with. There may even not be any direct calls to them! (e.g. ERC20 tokens may be sent to a contract that lacks a way to transfer them out of it, locking them forever). In these cases, a contract declaring its interface can be very helpful in preventing errors.

It should be noted that the constants library includes constant variables referencing all of the interface ids used in these contracts. This allows for more legible code i.e. using IERC165_ID instead of 0x01ffc9a7.

Interface calculations

In order to ensure EVM/StarkNet compatibility, interface identifiers are not calculated on StarkNet. Instead, the interface IDs are hardcoded from their EVM calculations. On the EVM, the interface ID is calculated from the selector’s first four bytes of the hash of the function’s signature while Cairo selectors are 252 bytes long. Due to this difference, hardcoding EVM’s already-calculated interface IDs is the most consistent approach to both follow the EIP165 standard and EVM compatibility.

Registering interfaces

For a contract to declare its support for a given interface, the contract should import the ERC165 library and register its support. It’s recommended to register interface support upon contract deployment through a constructor either directly or indirectly (as an initializer) like this:

from openzeppelin.introspection.erc165.library import ERC165

INTERFACE_ID = 0x12345678

@constructor
func constructor{
        syscall_ptr: felt*,
        pedersen_ptr: HashBuiltin*,
        range_check_ptr
    }():
    ERC165.register_interface(INTERFACE_ID)
    return ()
end

Querying interfaces

To query a target contract’s support for an interface, the querying contract should call supportsInterface through IERC165 with the target contract’s address and the queried interface id. Here’s an example:

from openzeppelin.introspection.erc265.IERC165 import IERC165

INTERFACE_ID = 0x12345678

func check_support{
        syscall_ptr: felt*,
        pedersen_ptr: HashBuiltin*,
        range_check_ptr
    }(
        target_contract: felt,
    ) -> (success: felt):
    let (is_supported) = IERC165.supportsInterface(target_contract, INTERFACE_ID)
    return (is_supported)
end
supportsInterface is camelCased because it is an exposed contract method as part of ERC165’s interface. This differs from library methods (such as supports_interface from the ERC165 library) which are snake_cased and not exposed. See the Function names and coding style for more details.

IERC165

@contract_interface
namespace IERC165:
    func supportsInterface(interfaceId: felt) -> (success: felt):
    end
end

IERC165 API Specification

func supportsInterface(interfaceId: felt) -> (success: felt):
end

supportsInterface

Returns true if this contract implements the interface defined by interfaceId.

Parameters:

interfaceId: felt

Returns:

success: felt

ERC165 Library Functions

func supports_interface(interface_id: felt) -> (success: felt):
end

func register_interface(interface_id: felt):
end

supports_interface

Returns true if this contract implements the interface defined by interface_id.

Parameters:

interface_id: felt

Returns:

success: felt

register_interface

Calling contract declares support for a specific interface defined by interface_id.

Parameters:

interface_id: felt

Returns: None.