Syntax changes in the recently released version 2.0.0-rc0 of Cairo#
Channels for relevant information#
GitHub Cairo project task progress board: https://github.com/orgs/starkware-libs/projects/1/views/1
GitHub release information list: https://github.com/starkware-libs/cairo/releases
General changes#
(1). Integer literals no longer need to specify the variable type
// Before
let n:u8 = 8_u8;
// Now the suffix can be omitted, but it can still be added without error
let n:u8 = 8;
(2). Creating dictionaries
Before
use dict::Felt252DictTrait;
fn main(){
let mut dict = Felt252DictTrait::new();
}
Now using the Default trait
use dict::Felt252DictTrait;
use traits::Default;
fn main(){
let mut map : Felt252Dict<felt252> = Default::default();
}
Syntax changes for writing contracts#
First, the contract code for the old version is given:
#[abi]
trait IOtherContract {
fn decrease_allowed() -> bool;
}
#[contract]
mod CounterContract {
use starknet::ContractAddress;
use super::{
IOtherContractDispatcher,
IOtherContractDispatcherTrait,
IOtherContractLibraryDispatcher
};
struct Storage {
counter: u128,
other_contract: IOtherContractDispatcher
}
#[event]
fn counter_increased(amount: u128) {}
#[event]
fn counter_decreased(amount: u128) {}
#[constructor]
fn constructor(initial_counter: u128, other_contract_addr: ContractAddress) {
counter::write(initial_counter);
other_contract::write(IOtherContractDispatcher { contract_address: other_contract_addr });
}
#[external]
fn increase_counter(amount: u128) {
let current = counter::read();
counter::write(current + amount);
counter_increased(amount);
}
#[external]
fn decrease_counter(amount: u128) {
let allowed = other_contract::read().decrease_allowed();
if allowed {
let current = counter::read();
counter::write(current - amount);
counter_decreased(amount);
}
}
#[view]
fn get_counter() -> u128 {
counter::read()
}
}
The following code is for contracts with the new syntax#
(1). External functions are centralized in a specific trait and its corresponding impl
The above contract has three public functions: increase_counter
, decrease_counter
, and get_counter
.
- First, these public functions will be defined in a trait marked with
#[starknet::interface]
, which defines the function signatures (or function selectors). - This trait includes a generic variable
TContractState
, which represents the contract's storage struct. - These public functions are methods of
TContractState
. - In the methods, the first parameter is
self
; if it is a view method,self
is the snapshot ofTContractState
(self: @TContractState
); if it is a state-changing method,self
is the reference ofTContractState
(ref self: TContractState
). The other parameters follow. - The logic of the public functions is written in an impl marked with
#[external(v0)]
.
The syntax rules for public functions have changed significantly. The comments in the code will provide more details:
/// @notice Defines the external interface of the current contract, all external functions will be defined in the impl of this trait
#[starknet::interface]
trait ICounterContract<TContractState> {
fn increase_counter(ref self: TContractState, amount: u128);
fn decrease_counter(ref self: TContractState, amount: u128);
fn get_counter(self: @TContractState) -> u128;
}
#[starknet::contract]
mod CounterContract {
...
/// @notice Here, all external functions are defined, ContractState represents the contract's storage state
/// @dev Distinguish between view functions and state-changing functions by passing a snapshot or a reference; if it is a snapshot, it is a view function
/// @dev The contract syntax is still being updated, v0 is for compatibility between the current contract and future versions of the compiler
#[external(v0)]
impl CounterContract of super::ICounterContract<ContractState> {
// Passed a snapshot, so it is a view function
fn get_counter(self: @ContractState) -> u128 {
self.counter.read()
}
// Passed a reference, so it will modify the contract's storage state
fn increase_counter(ref self: ContractState, amount: u128) {
// Read the contract's state variable
let current = self.counter.read();
// Modify the contract's state variable
self.counter.write(current + amount);
// ContractState also has the ability to emit events
self.emit(Event::CounterIncreased(CounterIncreased { amount }));
}
fn decrease_counter(ref self: ContractState, amount: u128) {
let allowed = self.other_contract.read().decrease_allowed();
if allowed {
let current = self.counter.read();
self.counter.write(current - amount);
self.emit(Event::CounterDecreased(CounterDecreased { amount }));
}
}
}
...
}
(2). External contract calls
Since the syntax for public functions has changed, external contract calls will naturally change as well.
- The part previously marked with
#[abi]
is now marked with#[starknet::interface]
. - The trait uses a generic trait, which is used in the same way as described above.
/// @notice Definition of the external contract interface
/// @dev Replace #[abi] with #[starknet::interface]
/// @dev Use a generic trait, where TContractState is the generic name representing the contract state
#[starknet::interface]
trait IOtherContract<TContractState> {
fn decrease_allowed(self: @TContractState) -> bool;
}
(3). Changes to events
There are also significant changes to events, now represented using enums and structs.
- All events are defined in an enum marked with
#[event]
and#[derive(Drop, starknet::Event)]
. - Each event is represented by a separate struct that holds the fields and types of the event, also marked with
#[derive(Drop, starknet::Event)]
. - Event calls use
ContractState
:self.emit(Event::CounterDecreased(CounterDecreased { amount }));
/// @notice The contract's events have also undergone significant changes
/// @dev Define all events in an enum named Event, marked with #[event]
/// @dev Each event is defined with a structure: event_name: event_type, where event_type holds the parameters of the event
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
CounterIncreased: CounterIncreased,
CounterDecreased: CounterDecreased
}
#[derive(Drop, starknet::Event)]
struct CounterIncreased {
amount: u128
}
#[derive(Drop, starknet::Event)]
struct CounterDecreased {
amount: u128
}