Visibility and Mutability

Visibility

Hay dos tipos de funciones en los contratos Starknet:

  • Functions that are accessible externally and can be called by anyone.
  • Functions that are only accessible internally and can only be called by other functions in the contract.

Estas funciones también suelen dividirse en dos bloques de implementación diferentes. El primer bloque impl para funciones accesibles externamente está anotado explícitamente con un atributo #[abi(embed_v0)]. Esto indica que todas las funciones dentro de este bloque se pueden llamar como una transacción o como una función de view. El segundo bloque impl para funciones accesibles internamente no está anotado con ningún atributo, lo que significa que todas las funciones dentro de este bloque son privadas de forma predeterminada.

State Mutability

Regardless of whether a function is internal or external, it can either modify the contract's state or not. When we declare functions that interact with storage variables inside a smart contract, we need to explicitly state that we are accessing the ContractState by adding it as the first parameter of the function. This can be done in two different ways:

  • If we want our function to be able to mutate the state of the contract, we pass it by reference like this: ref self: ContractState.
  • If we want our function to be read-only and not mutate the state of the contract, we pass it by snapshot like this: self: @ContractState.

Read-only functions, also called view functions, can be directly called without making a transaction. You can interact with them directly through a RPC node to read the contract's state, and they're free to call! External functions, that modify the contract's state, on the other side can only be called by making a transaction.

Las funciones internas no se pueden llamar externamente, pero se aplica el mismo principio con respecto a la mutabilidad de estado.

Echemos un vistazo a un contrato de ejemplo simple para verlos en acción:

#[starknet::interface] trait IExampleContract<TContractState> { fn set(ref self: TContractState, value: u32); fn get(self: @TContractState) -> u32; } #[starknet::contract] mod ExampleContract { #[storage] struct Storage { value: u32 } // The `abi(embed_v0)` attribute indicates that all the functions in this implementation can be called externally. // Omitting this attribute would make all the functions in this implementation internal. #[abi(embed_v0)] impl ExampleContract of super::IExampleContract<ContractState> { // The `set` function can be called externally because it is written inside an implementation marked as `#[external]`. // It can modify the contract's state as it is passed as a reference. fn set(ref self: ContractState, value: u32) { self.value.write(value); } // The `get` function can be called externally because it is written inside an implementation marked as `#[external]`. // However, it can't modify the contract's state is passed as a snapshot: it is only a "view" function. fn get(self: @ContractState) -> u32 { // We can call an internal function from any functions within the contract PrivateFunctionsTrait::_read_value(self) } } // The lack of the `external` attribute indicates that all the functions in this implementation can only be called internally. // We name the trait `PrivateFunctionsTrait` to indicate that it is an internal trait allowing us to call internal functions. #[generate_trait] impl PrivateFunctions of PrivateFunctionsTrait { // The `_read_value` function is outside the implementation that is marked as `#[abi(embed_v0)]`, so it's an _internal_ function // and can only be called from within the contract. // However, it can't modify the contract's state is passed as a snapshot: it is only a "view" function. fn _read_value(self: @ContractState) -> u32 { self.value.read() } } }

Visita el contrato en Voyager o juega con él en Remix.

Last change: 2023-10-19, commit: 3e4c697