Contract Functions

In this section, we are going to be looking at the different types of functions you could encounter in contracts:

1. Constructors

Constructors are a special type of function that only runs once when deploying a contract, and can be used to initialize the state of a contract.

    #[constructor]
    fn constructor(ref self: ContractState, owner: Person) {
        self.names.write(owner.address, owner.name);
        self.total_names.write(1);
        self.owner.write(owner);
    }

Some important rules to note:

  1. Your contract can't have more than one constructor.
  2. Your constructor function must be named constructor.
  3. It must be annotated with the #[constructor] attribute.

2. Public functions

As stated previously, public functions are accessible from outside of the contract. They must be defined inside an implementation block annotated with the #[abi(embed_v0)] attribute. This attribute means that all functions embedded inside it are implementations of the Starknet interface, and therefore entry points of the contract. It only affects the visibility (public vs private/internal), but it doesn't inform us on the ability of these functions to modify the state of the contract.

    #[abi(embed_v0)]
    impl NameRegistry of super::INameRegistry<ContractState> {
        fn store_name(ref self: ContractState, name: felt252, registration_type: RegistrationType) {
            let caller = get_caller_address();
            self._store_name(caller, name, registration_type);
        }

        fn get_name(self: @ContractState, address: ContractAddress) -> felt252 {
            let name = self.names.read(address);
            name
        }
        fn get_owner(self: @ContractState) -> Person {
            let owner = self.owner.read();
            owner
        }
    }

External functions

External functions are functions that can modify the state of a contract. They are public and can be called by any other contract or externally. External functions are public functions where the self: ContractState is passed as reference with the ref keyword, allowing you to modify the state of the contract.

        fn store_name(ref self: ContractState, name: felt252, registration_type: RegistrationType) {
            let caller = get_caller_address();
            self._store_name(caller, name, registration_type);
        }

View functions

View functions are read-only functions allowing you to access data from the contract while ensuring that the state of the contract is not modified. They can be called by other contracts or externally. View functions are public functions where the self: ContractState is passed as snapshot, preventing you from modifying the state of the contract.

        fn get_name(self: @ContractState, address: ContractAddress) -> felt252 {
            let name = self.names.read(address);
            name
        }

Note: It's important to note that both external and view functions are public. To create an internal function in a contract, you will need to define it outside of the implementation block annotated with the #[abi(embed_v0)] attribute.

3. Private functions

Functions that are not defined in a block annotated with the #[abi(embed_v0)] attribute are private functions (also called internal functions). They can only be called from within the contract.

They can be grouped in a dedicated impl block (e.g in components, to easily import internal functions all at once in the embedding contracts) or just be added as free functions inside the contract module. Note that these 2 methods are equivalent. Just choose the one that makes your code more readable and easy to use.


    // Could be a group of functions about a same topic
    #[generate_trait]
    impl InternalFunctions of InternalFunctionsTrait {
        fn _store_name(
            ref self: ContractState,
            user: ContractAddress,
            name: felt252,
            registration_type: RegistrationType
        ) {
            let mut total_names = self.total_names.read();
            self.names.write(user, name);
            self.registration_type.write(user, registration_type);
            self.total_names.write(total_names + 1);
            self.emit(StoredName { user: user, name: name });

        }
    }

    // Free functions

    fn get_contract_name() -> felt252 {
        'Name Registry'
    }

    fn get_owner_storage_address(self: @ContractState) -> starknet::StorageBaseAddress {
        self.owner.address()
    }

Wait, what is this #[generate_trait] attribute? Where is the trait definition for this implementation? Well, the #[generate_trait] attribute is a special attribute that tells the compiler to generate a trait definition for the implementation block. This allows you to get rid of the boilerplate code of defining a trait and implementing it for the implementation block. We will see more about this in the next section.

4. [abi(per_item)] attribute

You can also define the entrypoint type of a function individually inside an impl using the#[abi(per_item)] attribute on top of your impl. It is often used with the #[generate_trait] attribute, as it allows you to define entrypoints without an explicit interface. In this case, the functions will not be grouped under an impl in the abi. Note that when using #[abi(per_item)] attribute, public functions need to be annotated with #[external(v0)] attribute - otherwise, they will not be exposed.

In the case of #[abi(per_item)] attribute usage without #[generate_trait], it will only be possible to include constructor, l1-handler and internal functions in the trait implementation. Indeed, #[abi(per_item)] only works with a trait that is not defined as a Starknet interface. Hence, it will be mandatory to create another trait defined as interface to implement public functions.

Here is a short example:

#[starknet::contract]
mod AbiAttribute {
    #[storage]
    struct Storage {}

    #[abi(per_item)]
    #[generate_trait]
    impl SomeImpl of SomeTrait {
        #[constructor]
        // this is a constructor function
        fn constructor(ref self: ContractState) {}

        #[external(v0)]
        // this is a public function
        fn external_function(ref self: ContractState, arg1: felt252) {}

        #[l1_handler]
        // this is a l1_handler function
        fn handle_message(ref self: ContractState, from_address: felt252, arg: felt252) {}

        // this is an internal function
        fn internal_function(self: @ContractState) {}
    }
}