Expect this module to evolve.
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
.
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.
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
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.erc165.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
Please note that
supportsInterface
is camelCased because it is an exposed contract method as part of ERC165's interface. This differs from library methods (such assupports_interface
from the ERC165 library) which are snake_cased and not exposed. See the Function names and coding style for more details.
@contract_interface
namespace IERC165:
func supportsInterface(interfaceId: felt) -> (success: felt):
end
end
func supportsInterface(interfaceId: felt) -> (success: felt):
end
Returns true if this contract implements the interface defined by interfaceId
.
Parameters:
interfaceId: felt
Returns:
success: felt
func supports_interface(interface_id: felt) -> (success: felt):
end
func register_interface(interface_id: felt):
end
Returns true if this contract implements the interface defined by interface_id
.
Parameters:
interface_id: felt
Returns:
success: felt
Calling contract declares support for a specific interface defined by interface_id
.
Parameters:
interface_id: felt
Returns:
None.