Cairo is known as an alt VM which is zk-friendly and allows developers to enjoy super efficient STARK proofs for the underlying computation while writing in a high level language. In this post, we’ll convince you that Cairo is also a great language to write in, with exciting features and tooling that will make your life as a smart contract developer much easier.
Friendly smart contract syntax
Cairo is heavily inspired by Rust. Cairo compiles to a custom architecture for proving-efficiency. In that, Cairo and Starknet are different from chains that offer “Rust smart contract” like Arbitrum’s Stylus or Solana. However, note that since there are no out of the box “smart contracts in Rust”, such solutions usually offer a system of macros that allows devs to define the contract interface and perform blockchain-related operations.
In Cairo, such definitions are more native to the language, as demonstrated by the following trait definition that represents an ERC20:
#[starknet::interface]
pub trait IERC20<TState> {
fn total_supply(self: @TState) -> u256;
fn balance_of(self: @TState, account: ContractAddress) -> u256;
fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256;
fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool;
fn transfer_from(
ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256
) -> bool;
fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool;
}
Any contract can implement this generic trait, thus “becoming” an ERC20.
Similarly, the contract’s storage and event are defined as regular Cairo types:
#[storage]
struct Storage {
ERC20_name: ByteArray,
ERC20_symbol: ByteArray,
ERC20_total_supply: u256,
ERC20_balances: Map<ContractAddress, u256>,
ERC20_allowances: Map<(ContractAddress, ContractAddress), u256>
}
#[event]
#[derive(Copy, Drop, PartialEq, starknet::Event)]
enum Event {
Transfer: Transfer,
Approval: Approval,
}
#[derive(Copy, Drop, PartialEq, starknet::Event)]
struct Transfer {
#[key]
from: ContractAddress,
#[key]
to: ContractAddress,
value: u256
}
#[derive(Copy, Drop, PartialEq, starknet::Event)]
struct Approval {
#[key]
owner: ContractAddress,
#[key]
spender: ContractAddress,
value: u256
}
The fact that it’s all simply standard Cairo code, rather than something implemented behind the scenes, makes the basic contract syntax more straightforward and easy to understand.
Upgradability
Upgradability in Cairo is much friendlier, compared to EVM chains, without requiring any additional contracts to serve as a proxy. Thanks to the separation between contract definitions (classes) and instances, a contract may call the replace_class syscall, which completely replaces its underlying code at the end of the execution of the current entry point, with code (class) that was previously declared on the network.
Making your contract upgradable becomes a simple matter of embedding OpenZeppelin’s upgradable component (see more on composability below) in your contract, without making your deployment process more cumbersome. You can check out the upgradability component in OZ’s repository, or play with it directly in the cairo contract wizard.
Fancy accounts
A lot has been said on Starknet’s native account abstraction, and indeed it has given rise to interesting developments in accounts code:
- The standard account interface supports multicalls. You no longer need to split your intentions, allow and transfer may be part of the same transaction!
- ETH account that supports Secp256k1 based signatures is already part of OZ’s standard contracts package
- More exotic accounts, like Braavos or Argent, which are the most commonly used wallets on Starknet, offer advanced features like authentication via the WebAuthn protocol or multisigs.
Such advanced features are simply part of the account’s Cairo code, without requiring a complex system of contracts or off-chain architecture.
Composition over inheritance
Unlike solidity, Cairo does not have a notion of inheritance, but champions composability instead. The notion of components allows one to write logical units that:
- Have their own storage and events
- Have their own ABIs that can extend contracts
- Can be embedded in any contract that wants to use them
- Allow the contract to inject dependencies, thus affecting the component behavior
The last point is of particular interest, as it allowed for interesting fresh design in both the standard ERC20 OpenZeppelin implementation, via their notion of hooks. A similar direction gave rise to extensibility in Ekubo, Starknet’s next-gen AMM, which now allows adding custom logic to a new pool behavior.
Hints and private inputs
Outside the context of Starknet, one can pass private inputs to a Cairo program, and more generally offload computation to an arbitrary backend. Such computation is not part of the proven Cairo program, but it allows one to write nondeterministic code conveniently. This extension to the language was developed by Reilabs and can be found in the cairo-hints repository.
Suppose for example that you want to write a nondeterministic program for computing the square root of an integer. The program will “guess” the square root (this is the unproven part), and in Cairo code we’ll verify that squaring the root results in the input value. To do this, we simply have to specify our hint interface in a proto file:
syntax = "proto3";
package oracle;
message Request {
uint64 n = 1;
}
message Response {
uint64 n = 1;
}
service SqrtOracle {
rpc Sqrt(Request) returns (Response);
}
Then, running scarb hints-generate, generates the following Cairo module:
use starknet::testing::cheatcode;
#[derive(Drop, Serde)]
struct Request {
n: u64,
}
#[derive(Drop, Serde)]
struct Response {
n: u64,
}
#[generate_trait]
impl SqrtOracle of SqrtOracleTrait {
fn sqrt(arg: super::oracle::Request) -> super::oracle::Response {
let mut serialized = ArrayTrait::new();
arg.serialize(ref serialized);
let mut result = cheatcode::<'sqrt'>(serialized.span());
Serde::deserialize(ref result).unwrap()
}
}
Thanks to the above, you can easily call sqrt from the SqrtOracle module in your program’s code, making the program that uses the hint very simple:
mod oracle;
use oracle::{Request, SqrtOracle};
fn main() -> bool {
let num = 1764;
let request = Request { n: num };
let result = SqrtOracle::sqrt(request);
result.n * result.n == num
}
It now remains to implement the hint’s body, i.e. the unproven part. Luckily, the scarb hints-generate command also generated a Rust/JS backend, and all that remains is to implement the body of the sqrt function.
The hints extension to the Cairo language allows the development of privacy-focused applications, in a way that is much more general than just private inputs. Offloading the hint execution to a backend of your choice means that hints can do things that are impossible in Cairo, such as accessing the file system or making HTTP requests. To dive further into Cairo hints and feel their power, see the tutorial.
Mature tooling
Experienced smart contract developers know that you need a lot more than a good language to be able to smoothly execute your plans. A successful smart contract language needs a good testing framework, the ability to profile and debug on-chain transactions, good integrations with code editors, and this is only a very partial list.
Over the last year, the Cairo ecosystem has developed significantly in terms of capabilities, performance, and DevX. Among the “must know” of the Cairo world are:
- Scarb — a package manager for Cairo (think Cargo for Rust)
- scarbs.xyz — a Cairo package registry (still in its infancy, but will one day be what crates.io is for Rust)
- Starknet foundry: with the recently released v0.30.0, it’s the primary framework for smart contract testing
- Cairo profiler: a tool for profiling your code, also integrated with foundry
- Starknet devnet: simulate Starknet locally and interact with smart contracts
- cairovm.codes: a web playground for those who want to play around with Cairo/Sierra without having to install anything, which can also be found on the Cairo website
These tools are in active development, with dedicated teams who are in touch with the developer community in order to fine-tune the developer experience. This list is only very partial, for a more comprehensive coverage you can visit the Starknet documentation.
Home to on-chain gaming
With the fundamental promise of allowing much richer on-chain computation, Starknet attracted many game developers and can now boast of Realms, Influence, forceprime and many others.
Thanks to the dojo framework, you don’t have to develop all the game’s infrastructure from scratch, but rather use their toolchain and system of Cairo plugins that make game development easier. For those who want to jump in immediately, see Dojo’s starter’s guide.
What’s next
Cairo and Starknet both keep evolving, don’t miss out on the action! Start learning with the Cairo book and join us on discord.