Contract migration guide

With the v2.0.0 release of the Cairo compiler, the Starknet contract syntax has evolved. This affects how external functions, storage, and events are organized inside the contract.

This page highlights the technical steps required to migrate from the old Starknet contract syntax to the new.

For a comprehensive breakdown of the changes, see the community forum post.

New contract syntax - concrete steps for migrating

Given a contract written with the previous compiler version (v1.1.0), you can follow the steps below in order to make it compatible with the new syntax.

Contract anotation

Outside the contract module, Starknet related attributes are expected to have the starknet:: prefix.

  • old

  • new

#[contract]
mod CounterContract {
   ...
}
#[starknet::contract]
mod CounterContract {
   ...
}

Storage anotation

Anotate the Storage struct with the #[storage] attribute

  • old

  • new

struct Storage {
    counter: u128,
    other_contract: IOtherContractDispatcher
}
#[storage]
struct Storage {
    counter: u128,
    other_contract: IOtherContractDispatcher
}

Contract interface

Gather your contract’s external and view function signatures under a trait annotated with #[starknet::interface]:

  • Add a generic parameter to the trait, here we use the name TContractState as it stands for the state of your contract

  • For view functions, add the self: @TContractState argument

  • For external functions, add the ref self: TContractState argument

  • Static functions that do not touch storage or emit events do not require an addition argument

  • old

  • new

#[contract]
mod CounterContract {
   #[external]
   fn increase_counter(amount: u128) { ... }
   #[external]
   fn decrease_counter(amount: u128) { ... }
   #[view]
   fn get_counter() -> u128 { ... }
}
#[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 {
   ...
}

Add interface Impl

Add the external and view function bodies under an impl of the interface trait, and mark the impl with the [external(v0)] attribute

  • old

  • new

#[contract]
mod CounterContract {
   #[external]
   fn increase_counter(amount: u128) { ... }
   #[external]
   fn decrease_counter(amount: u128) { ... }
   #[view]
   fn get_counter() -> u128 { ... }
}
#[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 {
   #[external(v0)]
   impl CounterContract of super::ICounterContract<ContractState> {
      fn increase_counter(ref self: ContractState, amount: u128) { ... }
      fn decrease_counter(ref self: ContractState, amount: u128) { ... }
      fn get_counter(self: @ContractState) -> u128 { ... }
   }
}

Replace the abi attribute with starknet::interface

These attributes are responsible for generating the dispatcher type, used to call the contract. Replace the [abi] attribute with [starknet::interface]. While it doesn’t affect the generated code, we recommended adding to the trait a generic parameter T representing the contract’s state, and adding the ref self: T argument to external functions and self: @T argument for view functions.

  • old

  • new

#[abi]
trait IOtherContract {
    fn decrease_allowed() -> bool;
}
#[starknet::interface]
trait IOtherContract<TContractState> {
    fn decrease_allowed(self: @TContractState) -> bool;
}

Storage access

Modify storage access to happen through ContractState or @ContractState (none external functions in the contract that access storage also need to get it as an argument).

  • old

  • new

let current = counter::read();
let current = self.counter.read();

Events definition

Unify all the contract’s events under the Event enum, and add a corresponding struct for every variant (all the structs must derive the Event trait, and each member type has to implement the Serde trait)

  • old

  • new

#[event]
fn counter_increased(amount: u128) {}
#[event]
fn counter_decreased(amount: u128) {}
#[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
}

Events emition

Emit events via the ContractState type

  • old

  • new

fn increase_counter(amount: u128) {
    ...
    counter_increased(amount);
}
fn increase_counter(ref self: ContractState, amount: u128) {
    ...
    self.emit(CounterIncreased { amount });
}