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 #[external(v0)] attribute. This attribute 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.

    #[external(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 #[external(v0)] attribute.

3. Private functions

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

    #[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 });

        }
    }

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.

At this point, you might still be wondering if all of this is really necessary if you don't need to access the contract's state in your function (for example, a helper/library function). As a matter of fact, you can also define internal functions outside of implementation blocks. The only reason why we need to define functions inside impl blocks is if we want to access the contract's state.

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

    fn get_owner_storage_address(self: @ContractState) -> starknet::StorageBaseAddress {
        self.owner.address()
    }
Last change: 2023-08-10, commit: a3bc10b