💙 Gate Square #Gate Blue Challenge# 💙
Show your limitless creativity with Gate Blue!
📅 Event Period
August 11 – 20, 2025
🎯 How to Participate
1. Post your original creation (image / video / hand-drawn art / digital work, etc.) on Gate Square, incorporating Gate’s brand blue or the Gate logo.
2. Include the hashtag #Gate Blue Challenge# in your post title or content.
3. Add a short blessing or message for Gate in your content (e.g., “Wishing Gate Exchange continued success — may the blue shine forever!”).
4. Submissions must be original and comply with community guidelines. Plagiarism or re
Full Interpretation of Starknet Improved Grammar
Version 2 of the Cairo compiler introduces changes to the Starknet syntax to make the code more explicit and safe. The smart contract public interface is defined using traits, and access to storage is done through the ContractState trait. Private methods must be defined with a different implementation than the public interface. Events are now defined as enums, where each variant is a struct of the same name.
overview
Version 2 of the Cairo compiler introduces changes to the Starknet syntax to make the code more explicit and safe. The smart contract public interface is defined using traits, and access to storage is done through the ContractState trait. Private methods must be defined with a different implementation than the public interface. Events are now defined as enums, where each variant is a struct of the same name.
Disclaimer: The terms used here refer to different versions of the Cairo compiler, and the syntax is provisional as the Starknet community is still debating which is the best term to use. Once determined, this article will be updated accordingly.
The Compiler v2
Just last week, a new major version 2.0.0-rc0 of the Cairo compiler was released on Github. The new compiler provides significant improvements to the Starknet plugin, making our code safer, more explicit, and more reusable. Note that this new version of the compiler is not yet supported on Starknet testnet or mainnet as it is still being worked on in the integrated environment.
The goal of this article is to show you how to rewrite a Starknet smart contract created for Cairo compiler version 1.x into a smart contract compatible with compiler version 2.x. Our starting point is the Ownable smart contract created in the previous article, which is compatible with Cario compiler version 1.x.
[contract] mod Ownable {use starknet::ContractAddress;use starknet::get_caller_address;
[event] fn OwnershipTransferred(previous_owner: ContractAddress, new_owner: ContractAddress) {}
struct Storage {owner: ContractAddress,}
[constructor] fn constructor() {let deployer = get_caller_address();owner::write(deployer);}
[view] fn get_owner() -> ContractAddress {owner::read()}
[external] fn transfer_ownership(new_owner: ContractAddress) {only_owner();let previous_owner = owner::read();owner::write(new_owner);OwnershipTransferred(previous_owner, new_owner);}
fn only_owner() {let caller = get_caller_address();assert(caller == owner::read(), 'Caller is not the owner');
Project Settings
Since Protostar does not yet support compiler v2, this article will rely on the Scarb pre-release (version 0.5.0-alpha.1) that supports it. To install that specific version of Scarb, you can use the following command.
$ --proto '=https' --tlsv1.2 -sSf | bash -s -- -v 0.5.0-alpha.1
After the installation is complete, verify that you have the correct version.
$ scarb --version>>>scarb 0.5.0-alpha.1 (546dad33d 2023-06-19)cairo:2.0.0-rc3()
A Scarb project can now be created.
$ scarb new cairo1_v2$cdcairo1_v2
You should get a folder structure like below.
$ tree .>>>.├── Scarb.toml└── src└──lib.cairo
In order for Scarb to compile Starknet smart contracts, the Starknet plugin needs to be enabled as a dependency.
// Scarb.toml... [dependencies] starknet="2.0.0-rc3"
After the setup is complete, we can head to src/lib.cairo and start writing smart contracts.
Storage and constructor
In version 2 of the Cairo compiler, smart contracts are still defined by modules annotated with a contract attribute, only this time the attribute is named after the plugin that defines it, in this case starknet.
#[starknet::contract]mod Ownable {}
Internal storage is still defined as a structure that must be called Storage, only this time it must be annotated with a storage attribute.
#[starknet::contract]mod Ownable {use super::ContractAddress; # [storage] struct Storage {owner: ContractAddress,
To define the constructor, we annotate the function with the constructor attribute, like we did in v1, the advantage is that now the function can have any name, it doesn't need to be called "constructor" like in v1. Although it's not required, I'll still refer to the function as a "constructor" out of convention, but you can call it differently.
Another important change is that now the constructor automatically passes a reference to ContractState, which acts as an intermediary for storing variables, in this case "owner".
#[starknet::contract]mod Ownable {use super::ContractAddress; # [storage] struct Storage {owner: ContractAddress,} # [constructor] fn constructor(ref self: ContractState) {let deployer = get_caller_address();self.owner.write(deployer);
Note that the syntax for writing and reading storage has changed since v1. Before we did owner::write(), now we do self.owner.write(). The same applies to reading from storage.
By the way, the ContractState type doesn't need to be brought into scope manually, it's included in the prelude.
Public Methods
An important difference from version 1 of the Cairo compiler is that now we need to explicitly define the public interface of a smart contract using traits annotated with the starknet::interface attribute.
use starknet::ContractAddress;
#[starknet::interface]trait OwnableTrait { fn transfer_ownership(ref self: T, new_owner: ContractAddress); fn get_owner(self: @T) -> ContractAddress;}
#[starknet::contract]mod Ownable { ...}
If you remember the original code from v1, our smart contract has two "public" methods (get_owner and transfer_ownership) and one "private" method (only_owner). This feature only deals with public methods, and does not rely on "external" or "view" attributes to indicate which methods can modify the state of the contract and which methods cannot. Instead, this is now made explicit by the type of the parameter self.
If a method requires a reference to ContractStorage (which, once implemented, the generic T does), that method is able to modify the internal state of the smart contract. This is what we used to call an "external" method. On the other hand, if a method requires a snapshot of ContractStorage, it can only read it, not modify it. This is what we used to call the "view" method.
We can now create an implementation for the trait we just defined using the keyword impl. Remember, Cairo differs from Rust in that implementations have names.
use starknet::ContractAddress;
#[starknet::interface]trait OwnableTrait { fn transfer_ownership(ref self: T, new_owner: ContractAddress); fn get_owner(self: @T) -> ContractAddress;}
#[starknet::contract]mod Ownable { ... #[external(v0)] impl OwnableImpl of super::OwnableTrait { fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress) { let prev_owner = self.owner.read(); self.owner.write(new_owner); }
fn get_owner(self: @ContractState) -> ContractAddress { self.owner.read() }
We create an implementation for our trait inside the module defining the smart contract, passing the type ContractState as a generic type T so that the storage can be accessed like a constructor.
Our implementation is annotated with the attribute external(v0). Version 0 in the attribute means that the selector is only derived from the method name, as was the case in the past. The downside is that if you define another implementation of a different trait for your smart contract, and two traits happen to use the same name for one of their methods, the compiler will throw an error because of the duplicate selector.
A future version of this property may add a new way to evaluate selectors to prevent conflicts, but that doesn't work yet. Currently, we can only use version 0 of external properties.
Private Methods
We also need to define another method for the smart contract, only_owner. This method checks if the person calling it should be the owner of the smart contract.
Because this is a private method that is not allowed to be called from the outside, it cannot be defined as part of the OwnableTrait (the public interface of the smart contract). Instead, we'll use the generate_trait attribute to create a new implementation of the autogenerated trait.
...#[starknet::contract]mod Ownable { ... #[generate_trait] impl PrivateMethods of PrivateMethodsTrait { fn only_owner(self: @ContractState) { let caller = get_caller_address(); assert(caller == self.owner.read(), 'Caller is not the owner'); }
The only_owner method can now be used by calling self.only_owner() where needed.
#[starknet::contract]mod Ownable { ... #[external(v0)] impl OwnableImpl of super::OwnableTrait { fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress) { self.only_owner(); ... } ... }
#[generate_trait] impl PrivateMethods of PrivateMethodsTrait { fn only_owner(self: @ContractState) { ... }
event
In Cairo v1 an event was just a function without a body and annotated with the event attribute, while in v2 an event is an enum annotated with the same attribute, but now implemented using derive some additional features.
...#[starknet::contract]mod Ownable { ... # [event] #[derive(Drop, starknet::Event)] enum Event { OwnershipTransferred: OwnershipTransferred, }
#[derive(Drop, starknet::Event)] struct OwnershipTransferred { # [key] prev_owner: ContractAddress, # [key] new_owner: ContractAddress,
Each variant of the event enum must be a struct with the same name. In this structure, we use the optional key attribute to define all the values we want to emit, to inform the system which values we want Starknet to index, so that the indexer can search and retrieve faster. In this case, we want to index two values (prev_owner and new_owner).
The ContractState trait defines an emit method that can be used to emit events.
...#[starknet::contract]mod Ownable { ... #[external(v0)] impl OwnableImpl of super::OwnableTrait { fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress) { ... self.emit(Event::OwnershipTransferred(OwnershipTransferred { prev_owner: prev_owner, new_owner: new_owner, })); } ... } ...}
With this final feature, we have completed the migration of the Ownable smart contract from v1 to v2. The full code is shown below.
use starknet::ContractAddress;
#[starknet::interface]trait OwnableTrait { fn transfer_ownership(ref self: T, new_owner: ContractAddress); fn get_owner(self: @T) -> ContractAddress;}
#[starknet::contract]mod Ownable { use super::ContractAddress; use starknet::get_caller_address;
[event] #[derive(Drop, starknet::Event)] enum Event { OwnershipTransferred: OwnershipTransferred, }
#[derive(Drop, starknet::Event)] struct OwnershipTransferred { # [key] prev_owner: ContractAddress, # [key] new_owner: ContractAddress, }
[storage] struct Storage { owner: ContractAddress, }
[constructor] fn constructor(ref self: ContractState) { let deployer = get_caller_address(); self.owner.write(deployer); }
#[external(v0)] impl OwnableImpl of super::OwnableTrait { fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress) { self.only_owner(); let prev_owner = self.owner.read(); self.owner.write(new_owner); self.emit(Event::OwnershipTransferred(OwnershipTransferred { prev_owner: prev_owner, new_owner: new_owner, })); }
fn get_owner(self: @ContractState) -> ContractAddress { self.owner.read() } }
#[generate_trait] impl PrivateMethods of PrivateMethodsTrait { fn only_owner(self: @ContractState) { let caller = get_caller_address(); assert(caller == self.owner.read(), 'Caller is not the owner'); }
You can also find this code on Github.
in conclusion
Version 2 of the Cairo compiler brings a new syntax to Starknet that makes smart contract code look more consistent with Cairo itself and, by extension, more Rust-like. Even at the expense of more cumbersome code, the security benefits are worth the tradeoff.
In this article, we haven't touched everything about the new syntax, especially how it interacts with other smart contracts, but you can read the compiler's changelog, read this post on the forum, or watch a video on StarkWare's YouTube channel to learn more about it. to know more information.
This new version of the compiler will be available on Starknet's testnet in a few weeks and mainnet in a few weeks, so don't try to deploy this code just yet, it won't work yet.
Cairo just keeps getting better.
resource