Having the ability to check if a smart contract implements a specific interface is a very important feature that supports interoperability patterns (ERC721 transferFrom, etc…). In the Ethereum ecosystem, the ERC165 specifies the “how to”, and is widely used as one of the oldest standards. In Starknet, some implementations have somehow used this Ethereum Standard for defining interface ids (like Account implementors), for being able to implement ERCs that depend on it, but there’s a lack of a formal definition, and Cairo types have been mapped to Solidity types on the fly. In the beginning, with Cairo 0 this formalization was unpractical because the language itself was likely to change a lot, but now we have achieved certain maturity with Cairo 1, which I think allows us to define in the right way this process of interface ids generation.
I propose defining an SNIP from the ERC-165 idea, but using Cairo types instead. While in the past we’ve used these Solidity types for the interface generation (Account example):
interface IAccount {
struct Call {
address to;
bytes4 selector;
bytes data;
}
function supports_interface(bytes4) external;
function is_valid_signature(bytes32, bytes32[] calldata) external;
function __execute__(Call[] calldata) external;
function __validate__(Call[] calldata) external;
function __validate_declare__(bytes32) external;
}
One of the problems with this is that for example, we would obtain the same interface for a param being Span<T>
or Array<T>
, and we would need to define if we use bytes32
or uint256
from Solidity when we have the u256
type in Cairo.
I propose having something like this:
struct Call {
to: ContractAddress,
selector: u32,
calldata: Array<felt252>
}
trait IAccount {
fn supports_interface(u32);
fn is_valid_signature(felt252, Array<felt252>);
fn __execute__(Array<Call>);
fn __validate__(Array<Call>);
fn __validate_declare__(felt252);
}
Translated into this signatures:
functionSignatures = [
'supports_interface(u32)',
'is_valid_signature(felt252,Array<felt252>)',
'__execute__(Array<(ContractAddress,u32,Array<felt252>)>)',
'__validate__(Array<(ContractAddress,u32,Array<felt252>)>)',
'__validate_declare__(felt252)'
]
Being the interface id the XOR of them (similar to Solidity), that can be computed with this Python script as an example:
# pip install pysha3
import sha3
keccak_256 = sha3.keccak_256
functions = [
'supports_interface(u32)',
'is_valid_signature(felt252,Array<felt252>)',
'__execute__(Array<(ContractAddress,u32,Array<felt252>)>)',
'__validate__(Array<(ContractAddress,u32,Array<felt252>)>)',
'__validate_declare__(felt252)'
]
def get_id(fn_signature):
id = keccak_256(fn_signature.encode()).hexdigest()[0:8]
print(fn_signature, id)
return id
def main():
interface_id = 0x0
for fn in functions:
interface_id ^= int(get_id(fn), 16)
print(hex(interface_id))
if __name__ == '__main__':
main()
With this SNIP we would have a settled definition of how to compute interface ids for Starknet, and this will support interoperability as ERC-165 does for Ethereum.