This document outlines the guidelines and best practices for using and creating interfaces in the
contracts-bedrock
package.
Interfaces are valuable for developers because:
- They allow interaction with OP Stack contracts without importing the source code.
- They provide compatibility across different compiler versions.
- They can reduce contract compilation time.
Instead of importing the full contract:
import "./ComplexContract.sol";
contract MyContract {
ComplexContract public complexContract;
constructor(address _complexContractAddress) {
complexContract = ComplexContract(_complexContractAddress);
}
function doSomething() external {
complexContract.someFunction();
}
}
You can use an interface:
import "./interfaces/IComplexContract.sol";`
contract MyContract {
IComplexContract public complexContract;
constructor(address _complexContractAddress) {
complexContract = IComplexContract(_complexContractAddress);
}
function doSomething() external {
complexContract.someFunction();
}
}
This approach allows for interaction without being tied to the specific implementation or compiler
version of ComplexContract
.
Contrary to common practice, source contracts for which an interface is defined SHOULD NOT use the interface contract. This means:
contract Whatever is IWhatever
is NOT allowed.- Source contracts should not use types or other definitions from their interfaces.
- Contracts that build on base contracts (e.g.,
contract OtherWhatever is Whatever
) should not importIWhatever
orIOtherWhatever
.
Instead of:
import "./IWhatever.sol";
contract Whatever is IWhatever {
// Implementation
}
Do this:
contract Whatever {
// Direct implementation without importing interface
}
-
Automation Potential: We aim to auto-generate interfaces in the future. Importing interfaces into source contracts would prevent this automation by creating a circular dependency.
-
ABI Compatibility: Achieving 1:1 compatibility between interface and source contract ABI becomes problematic when the source contract imports other contracts along with the interface. This is due to Solidity's handling of function redefinitions. See Example of ABI Compatibility Issue below for more context.
contract SomeBaseContract {
event SomeEvent();
}
interface IWhatever {
event SomeEvent();
function someOtherFunction() external;
}
contract Whatever is IWhatever, SomeBaseContract {
function someOtherFunction() external {}
}
In this case, Solidity will return the following compilation error:
DeclarationError: Event with same name and parameter types defined twice.
Contracts CAN import interfaces for OTHER contracts. This practice helps mitigate compilation time issues in older Solidity versions. As Solidity improves, we plan to phase out this exception.
import "./IOtherContract.sol";
contract MyContract {
IOtherContract public otherContract;
constructor(address _otherContractAddress) {
otherContract = IOtherContract(_otherContractAddress);
}
// Rest of the contract
}
You have several options for creating interfaces:
-
Use
cast interface
:cast interface ./path/to/contract/artifact.json
-
Use
forge inspect
:forge inspect <ContractName> abi --pretty
-
Create the interface manually:
interface IMyContract { function someFunction() external; function anotherFunction(uint256 _param) external returns (bool); // ... other functions and events }
Regardless of the method chosen, ensure that your ABIs are a 1:1 match with their source contracts.
NOTE: Using cast interface
or forge inspect
can fail to preserve certain types like enum
values. You may need to manually fix these issues or CI will complain.
To check if all interfaces match their source contracts:
- Run
just interface-check
orjust interface-check-no-build
These commands will compare the ABIs of your interfaces with their corresponding source contracts and report any discrepancies.
Our long-term objectives for interfaces include:
- Automating interface generation
- Using interfaces only for external users, not internally
- Eliminating the need for interface imports in source contracts
Until we achieve these goals, we maintain the current policy to balance development efficiency and compilation time improvements.