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 a l1 handler.

In summary, there's two ways to handle interfaces:

  • Explicitly, by defining a trait annoted 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 {
    #[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 {
    #[storage]
    struct Storage {
        value: u32
    }

    #[abi(per_item)]
    #[generate_trait]
    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);
        }
    }
}

Note: You can import an implicitly generated contract interface with use contract::{GeneratedContractInterface}. However, the Dispatcher will not be generated automatically.

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 {
    #[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()
        }
    }
}
Last change: 2024-02-15, commit: 89037ca