Interface Identification

The Extendable pattern makes use of two different interface identification methods. These identification methods help track what interfaces are implemented by an Extendable contract and which Extensions contain the implementation.

The two methods are:

Interfaces and function selectors both make use of the bytes4 type.

Full Interface Identification

When implementing a contract, it is common to implement certain known standards and interfaces to enable compatibility and composability. This means that being able to identify a contract as an implementer of certain interface will help both a developer understand what is being implemented and a user understand how to interact with it.

The most common example of this is the IERC20 interface that are used by most protocols to interact with ERC20 tokens. The interface identifier for IERC20 is 0x36372b07.

In Solidity, an interface identifier can be retrieved through:

type(IYourInterface).interfaceId

Extensions can implement one or multiple interfaces and each interface must be defined by the implementation. Then Extension can then be queried using its introspection functions to give a detailed view of which interfaces it implements.

Extension.getSolidityInterface() returns:

function yourFunction() external returns(bool);
function anotherFunction() external;
// more...

This allows the Extendable contract to be able to report a full interface view of which functions it implements by querying each Extension it uses.

Extendable.getCurrentInterface() returns:

interface IExtended {
    // fully compiled list of all Extensions' functions
}

Developers can use this interface identification method to detect which Extensions implement which interfaces, extend, remove or replace them accordingly as whole units and manage their contracts with modular control.

Function Selector Identification

Functions are identified using their bytes4 selector, which is calculated as the first 4 bytes of: keccak256(function_signature)where function_signature is a string representation of the function in the form: name(argType,arg2Type,...).

For example, the ERC20 function totalSupply has a full signature of: function balanceOf(address account) external view returns (uint256);

which first reduces to: balanceOf(address)

before applying the hash and truncation to 4 bytes: 0x70a08231

Here is an online tool that can take any function signature and calculate its 4-byte function selector. In Solidity, function selectors can be retrieved through:

IYourInterface.yourFunction.selector

The function selectors are used by the EVM to detect, for a transaction, which function of a contract should be called. These same function selectors are what is used by Extendable to detect which Extension implements each function and thus which Extension should be called.

When the Extendable contract is called with a function, we first check the incoming transaction's function selector, we match the selector with an Extension, and call the Extension if it exists.

An Extension must define the functions under each interface that it implements. When it gets extended, these functions are recorded by the Extendable and point to the Extension as the implementer. During a call, the record is looked up and the correct Extension is found.

Extension.getInterface() returns:

[
    {
        interfaceId: "0x...",
        functions: [
            "0x...",
            "0x...",
            ...
        ]
    }
]

A list of interfaces with the functions that comprise that interface are returned by the Extension.

This allows a clear introspection endpoint for Extensions to display what it implements and allow Extendable (and other contracts) to be able to interact with it to make such introspection checks.

Last updated