contracts
Contracts
Contracts can be deployed to Starknet.
The contract modules are defined by adding a #[starknet::contract]
attribute above the module definition:
#[starknet::contract]
mod Contract {
}
Storage
Inside a contract definition, you can define a storage, using the #[storage]
attribute:
#[storage]
struct Storage {
token_supply: felt252,
decimals: u8
}
Interfaces
In order to interact with your contract, you’ll want to define an interface, which gathers external (functions that mutate the storage) and view (functions that don’t mutate the storage) function signatures under a trait. Traits use a generic argument, generally referring to the contract state. Static functions that do not use the storage or emit events do not require an additional argument. Interfaces are defined by adding a #[starknet::interface]
attribute:
#[starknet::interface]
trait IContract<TContractState> {
fn increase_token_supply(ref self: TContractState, amount: felt252);
fn increase_decimals(ref self: TContractState, amount: u8);
fn get_token_supply(self: @TContractState) -> felt252;
}
Implementations
Functions that affect the contract state use a mutable reference to the ContractState
: ref self: TContractState
; while the functions that don’t affect the state use: self: @TContractState
.
And to implement this trait inside the contract, we use the impl
keyword, with a #[external(v0)]
attribute:
mod Contract {
#[storage]
struct Storage {
token_supply: felt252,
decimals: u8
}
#[external(v0)]
impl Contract of super::IContract<ContractState> {
fn increase_token_supply(ref self: ContractState, amount: felt252) { ... }
fn increase_decimals(ref self: ContractState, amount: u8) { ... }
fn get_token_supply(self: @ContractState) -> felt252 { ... }
}
}
Storage Access
In order to interact with the contract state, you can use read
and write
functions:
let current_balance = self.balance.read();
self.balance.write(current_balance + amount)
That can be used inside functions:
fn increase_token_supply(ref self: ContractState, amount: felt252) {
let current_token_supply = self.token_supply.read();
self.token_supply.write(current_token_supply + amount)
}
Constructors
When you deploy a contract, you can set its initial values using a constructor:
mod Contract {
#[constructor]
fn constructor(ref self: ContractState, initial_token_supply: felt252, initial_decimals: u8) {
self.token_supply.write(initial_token_supply);
self.decimals.write(initial_decimals);
}
}
Events definition
In Cairo2 all the contract events are unified under the Event
enum:
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
TokenSupplyIncreased: TokenSupplyIncreased,
DecimalsIncreased: DecimalsIncreased
}
#[derive(Drop, starknet::Event)]
struct TokenSupplyIncreased {
amount: felt252
}
#[derive(Drop, starknet::Event)]
struct DecimalsIncreased {
amount: u8
}
Emitting Events
In order to emit events, you can do the following:
fn increase_token_supply(ref self: ContractState, amount: felt252) {
...
self.emit(TokenSupplyIncreased { amount });
}
Try it out!
- Install the toolchain:
- For macOS and Linux, run our script:
curl -sL https://raw.githubusercontent.com/lambdaclass/cairo-by-example/main/build/installer.sh | bash -s 2.2.0
- For Windows and others, please see the official guide
- Run the example:
- Copy the example into a contracts.cairo file and run with:
%!s(<nil>) contracts.cairo