Generating and understanding the HelloStarknet contract

If you encounter an issue while following this tutorial, check out "Hello, Starknet!" quickstart tutorial troubleshooting.

Introduction

Welcome to the second installment of the "Hello, Starknet!" quickstart series, the official tutorial for starting your journey as a Starknet developer! 🚀

Starknet contracts are a special superset of Cairo programs that are run by the Starknet sequencer, and as such, have access to Starknet’s state. This installment of the series will therefore walk you though generating and understanding Scarb’s default HelloStarknet contract, which will be used throughout the following installments.

To learn more about Starknet smart contracts, see the Cairo book.

Generating HelloStarknet

Scarb’s default HelloStarknet contract can be generated by simply running:

scarb new hello_starknet

and selecting to set up the Starknet Foundry (default) test runner. If successful, this should create a new hello_starknet directory with the following structure:

hello_cairo
|- Scarb.lock
|- Scarb.toml
|- snfoundry.toml
|- src
    |- lib.cairo
|- tests
    |- test_contract.cairo

Understanding HelloStarknet

For the purpose of this tutorial, you can ignore all files in the hello_starknet directory other than hello_starknet/src/lib.cairo, which holds the contract’s code:

/// Interface representing `HelloContract`.
/// This interface allows modification and retrieval of the contract balance.
#[starknet::interface](1)
pub trait IHelloStarknet<TContractState> { (2)
    /// Increase contract balance.
    fn increase_balance(ref self: TContractState, amount: felt252);
    /// Retrieve contract balance.
    fn get_balance(self: @TContractState) -> felt252;
}

/// Simple contract for managing balance.
#[starknet::contract]
mod HelloStarknet {
    use core::starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};

    #[storage] (3)
    struct Storage {
        balance: felt252,
    }

    #[abi(embed_v0)] (4)
    impl HelloStarknetImpl of super::IHelloStarknet<ContractState> {
        fn increase_balance(ref self: ContractState, amount: felt252) {
            assert(amount != 0, 'Amount cannot be 0');
            self.balance.write(self.balance.read() + amount);
        }

        fn get_balance(self: @ContractState) -> felt252 {
            self.balance.read()
        }
    }
}

As its comments read, HelloStarknet is a simple contract for managing balance. Specifically:

1 The contract is defined by encapsulating state and logic within a module annotated with the #[starknet::contract] attribute.
2 The logic that the contract exposes to the outside world is represented by its interface trait, annotated with the #[starknet::interface] attribute. Here, our contract defines and publicly exposes the functions increase_balance and get_balance.
3 The state is defined within the Storage struct, which is always initialized empty. Here, our struct contains a single field called balance of type felt252.
4 The logic itself is defined in the implementation block and annotated with the #[abi(embed_v0)] attribute to expose the implementations to the outside world. Here, increase_balance uses the write method to increase balance by amount and get_balance uses the read method to return the value of balance.

Once deployed, each value stored in the HelloStarknet contract’s storage will be recorded in Starknet’s history.

To review examples of more advanced contracts, see Starknet By Example.