Contract interfaces and Traits generation
Contract interfaces define the structure and behavior of a contract, serving as the contract's public ABI. They list all the function signatures that a contract exposes. For a detailed explanation of interfaces, you can refer to the Cairo Book.
In Cairo, to specify the interface you need to define a trait annotated with #[starknet::interface]
and then implement that trait in the contract.
When a function needs to access the contract state, it must have a self
parameter of type ContractState
. This implies that the corresponding function signature in the interface trait must also take a TContractState
type as a parameter. It's important to note that every function in the contract interface must have this self
parameter of type TContractState
.
You can use the #[generate_trait]
attribute to implicitly generate the trait for a specific implementation block. This attribute automatically generates a trait with the same functions as the ones in the implemented block, replacing the self
parameter with a generic TContractState
parameter. However, you will need to annotate the block with the #[abi(per_item)]
attribute, and each function with the appropriate attribute depending on whether it's an external function, a constructor or an L1 handler.
In summary, there's two ways to handle interfaces:
- Explicitly, by defining a trait annotated with
#[starknet::interface]
- Implicitly, by using
#[generate_trait]
combined with the#[abi(per_item)]
attributes, and annotating each function inside the implementation block with the appropriate attribute.
Explicit interface
#[starknet::interface]
pub trait IExplicitInterfaceContract<TContractState> {
fn get_value(self: @TContractState) -> u32;
fn set_value(ref self: TContractState, value: u32);
}
#[starknet::contract]
pub mod ExplicitInterfaceContract {
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
#[storage]
struct Storage {
value: u32
}
#[abi(embed_v0)]
impl ExplicitInterfaceContract of super::IExplicitInterfaceContract<ContractState> {
fn get_value(self: @ContractState) -> u32 {
self.value.read()
}
fn set_value(ref self: ContractState, value: u32) {
self.value.write(value);
}
}
}
Implicit interface
#[starknet::contract]
pub mod ImplicitInterfaceContract {
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
#[storage]
struct Storage {
value: u32
}
#[abi(per_item)]
#[generate_trait]
pub impl ImplicitInterfaceContract of IImplicitInterfaceContract {
#[external(v0)]
fn get_value(self: @ContractState) -> u32 {
self.value.read()
}
#[external(v0)]
fn set_value(ref self: ContractState, value: u32) {
self.value.write(value);
}
}
}
Internal functions
You can also use #[generate_trait]
for your internal functions.
Since this trait is generated in the context of the contract, you can define pure functions as well (functions without the self
parameter).
#[starknet::interface]
pub trait IImplicitInternalContract<TContractState> {
fn add(ref self: TContractState, nb: u32);
fn get_value(self: @TContractState) -> u32;
fn get_const(self: @TContractState) -> u32;
}
#[starknet::contract]
pub mod ImplicitInternalContract {
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
#[storage]
struct Storage {
value: u32
}
#[generate_trait]
impl InternalFunctions of InternalFunctionsTrait {
fn set_value(ref self: ContractState, value: u32) {
self.value.write(value);
}
fn get_const() -> u32 {
42
}
}
#[constructor]
fn constructor(ref self: ContractState) {
self.set_value(0);
}
#[abi(embed_v0)]
impl ImplicitInternalContract of super::IImplicitInternalContract<ContractState> {
fn add(ref self: ContractState, nb: u32) {
self.set_value(self.value.read() + nb);
}
fn get_value(self: @ContractState) -> u32 {
self.value.read()
}
fn get_const(self: @ContractState) -> u32 {
self.get_const()
}
}
}