Syscalls
At the protocol level, the Starknet Operating System (OS) is the program that manages the whole Starknet network.
Some of the OS functionalities are exposed to smart contracts through the use of syscalls (system calls). Syscalls can be used to get information about the state of the Starknet network, to interact with/deploy contracts, emit events, send messages, and perform other low-level operations.
Syscalls return a SyscallResult
which is either Sucess
of Failure
, allowing the contract to handle errors.
Here's the available syscalls:
- get_block_hash
- get_execution_info
- call_contract
- deploy
- emit_event
- library_call
- send_message_to_L1
- replace_class
- storage_read
- storage_write
get_block_hash
fn get_block_hash_syscall(block_number: u64) -> SyscallResult<felt252>
Get the hash of the block number block_number
.
Only within the range [first_v0_12_0_block, current_block - 10]
.
get_execution_info
fn get_execution_info_syscall() -> SyscallResult<Box<starknet::info::ExecutionInfo>>
Get information about the current execution context.
The returned ExecutionInfo
is defined as :
#[derive(Copy, Drop, Debug)]
pub struct ExecutionInfo {
pub block_info: Box<BlockInfo>,
pub tx_info: Box<TxInfo>,
pub caller_address: ContractAddress,
pub contract_address: ContractAddress,
pub entry_point_selector: felt252,
}
#[derive(Copy, Drop, Debug, Serde)]
pub struct BlockInfo {
pub block_number: u64,
pub block_timestamp: u64,
pub sequencer_address: ContractAddress,
}
#[derive(Copy, Drop, Debug, Serde)]
pub struct TxInfo {
// The version of the transaction. Always fixed (1)
pub version: felt252,
// The account contract from which this transaction originates.
pub account_contract_address: ContractAddress,
// The max_fee field of the transaction.
pub max_fee: u128,
// The signature of the transaction.
pub signature: Span<felt252>,
// The hash of the transaction.
pub transaction_hash: felt252,
// The identifier of the chain.
// This field can be used to prevent replay of testnet transactions on mainnet.
pub chain_id: felt252,
// The transaction's nonce.
pub nonce: felt252,
// A span of ResourceBounds structs.
pub resource_bounds: Span<ResourceBounds>,
// The tip.
pub tip: u128,
// If specified, the paymaster should pay for the execution of the tx.
// The data includes the address of the paymaster sponsoring the transaction, followed by
// extra data to send to the paymaster.
pub paymaster_data: Span<felt252>,
// The data availability mode for the nonce.
pub nonce_data_availability_mode: u32,
// The data availability mode for the account balance from which fee will be taken.
pub fee_data_availability_mode: u32,
// If nonempty, will contain the required data for deploying and initializing an account
// contract: its class hash, address salt and constructor calldata.
pub account_deployment_data: Span<felt252>,
}
starknet::info
provides helper functions to access the ExecutionInfo
fields in a more convenient way:
get_execution_info() -> Box<ExecutionInfo>
get_caller_address() -> ContractAddress
get_contract_address() -> ContractAddress
get_block_info() -> Box<BlockInfo>
get_tx_info() -> Box<TxInfo>
get_block_timestamp() -> u64
get_block_number() -> u64
call_contract
fn call_contract_syscall(
address: ContractAddress, entry_point_selector: felt252, calldata: Span<felt252>
) -> SyscallResult<Span<felt252>>
Call a contract at address
with the given entry_point_selector
and calldata
.
Failure can't be caught for this syscall, and if the call fails, the whole transaction will revert.
This is not the recommended way to call a contract. Instead, use the dispatcher generated from the contract interface as shown in the Calling other contracts chapter.
deploy
fn deploy_syscall(
class_hash: ClassHash,
contract_address_salt: felt252,
calldata: Span<felt252>,
deploy_from_zero: bool,
) -> SyscallResult<(ContractAddress, Span::<felt252>)>
Deploy a new contract of the predeclared class class_hash
with calldata
.
The success result is a tuple containing the deployed contract address and the return value of the constructor.
contract_address_salt
and deploy_from_zero
are used to compute the contract address.
Example of the usage of the deploy
syscall from the Factory pattern:
pub use starknet::{ContractAddress, ClassHash};
#[starknet::interface]
pub trait ICounterFactory<TContractState> {
/// Create a new counter contract from stored arguments
fn create_counter(ref self: TContractState) -> ContractAddress;
/// Create a new counter contract from the given arguments
fn create_counter_at(ref self: TContractState, init_value: u128) -> ContractAddress;
/// Update the argument
fn update_init_value(ref self: TContractState, init_value: u128);
/// Update the class hash of the Counter contract to deploy when creating a new counter
fn update_counter_class_hash(ref self: TContractState, counter_class_hash: ClassHash);
}
#[starknet::contract]
pub mod CounterFactory {
use starknet::{ContractAddress, ClassHash, SyscallResultTrait, syscalls::deploy_syscall};
#[storage]
struct Storage {
/// Store the constructor arguments of the contract to deploy
init_value: u128,
/// Store the class hash of the contract to deploy
counter_class_hash: ClassHash,
}
#[constructor]
fn constructor(ref self: ContractState, init_value: u128, class_hash: ClassHash) {
self.init_value.write(init_value);
self.counter_class_hash.write(class_hash);
}
#[abi(embed_v0)]
impl Factory of super::ICounterFactory<ContractState> {
fn create_counter_at(ref self: ContractState, init_value: u128) -> ContractAddress {
// Contructor arguments
let mut constructor_calldata: Array::<felt252> = array![init_value.into()];
// Contract deployment
let (deployed_address, _) = deploy_syscall(
self.counter_class_hash.read(), 0, constructor_calldata.span(), false
)
.unwrap_syscall();
deployed_address
}
fn create_counter(ref self: ContractState) -> ContractAddress {
self.create_counter_at(self.init_value.read())
}
fn update_init_value(ref self: ContractState, init_value: u128) {
self.init_value.write(init_value);
}
fn update_counter_class_hash(ref self: ContractState, counter_class_hash: ClassHash) {
self.counter_class_hash.write(counter_class_hash);
}
}
}
#[cfg(test)]
mod tests {
use super::{CounterFactory, ICounterFactoryDispatcher, ICounterFactoryDispatcherTrait};
use starknet::{
SyscallResultTrait, ContractAddress, ClassHash, contract_address_const,
syscalls::deploy_syscall
};
// Define a target contract to deploy
mod target {
#[starknet::interface]
pub trait ISimpleCounter<TContractState> {
fn get_current_count(self: @TContractState) -> u128;
fn increment(ref self: TContractState);
fn decrement(ref self: TContractState);
}
#[starknet::contract]
pub mod SimpleCounter {
#[storage]
struct Storage {
// Counter variable
counter: u128,
}
#[constructor]
fn constructor(ref self: ContractState, init_value: u128) {
// Store initial value
self.counter.write(init_value);
}
#[abi(embed_v0)]
impl SimpleCounter of super::ISimpleCounter<ContractState> {
fn get_current_count(self: @ContractState) -> u128 {
self.counter.read()
}
fn increment(ref self: ContractState) {
// Store counter value + 1
let mut counter: u128 = self.counter.read() + 1;
self.counter.write(counter);
}
fn decrement(ref self: ContractState) {
// Store counter value - 1
let mut counter: u128 = self.counter.read() - 1;
self.counter.write(counter);
}
}
}
}
use target::{ISimpleCounterDispatcher, ISimpleCounterDispatcherTrait};
/// Deploy a counter factory contract
fn deploy_factory(
counter_class_hash: ClassHash, init_value: u128
) -> ICounterFactoryDispatcher {
let mut constructor_calldata: Array::<felt252> = array![
init_value.into(), counter_class_hash.into()
];
let (contract_address, _) = deploy_syscall(
CounterFactory::TEST_CLASS_HASH.try_into().unwrap(),
0,
constructor_calldata.span(),
false
)
.unwrap_syscall();
ICounterFactoryDispatcher { contract_address }
}
#[test]
fn test_deploy_counter_constructor() {
let init_value = 10;
let counter_class_hash: ClassHash = target::SimpleCounter::TEST_CLASS_HASH
.try_into()
.unwrap();
let factory = deploy_factory(counter_class_hash, init_value);
let counter_address = factory.create_counter();
let counter = target::ISimpleCounterDispatcher { contract_address: counter_address };
assert_eq!(counter.get_current_count(), init_value);
}
#[test]
fn test_deploy_counter_argument() {
let init_value = 10;
let argument_value = 20;
let counter_class_hash: ClassHash = target::SimpleCounter::TEST_CLASS_HASH
.try_into()
.unwrap();
let factory = deploy_factory(counter_class_hash, init_value);
let counter_address = factory.create_counter_at(argument_value);
let counter = target::ISimpleCounterDispatcher { contract_address: counter_address };
assert_eq!(counter.get_current_count(), argument_value);
}
#[test]
fn test_deploy_multiple() {
let init_value = 10;
let argument_value = 20;
let counter_class_hash: ClassHash = target::SimpleCounter::TEST_CLASS_HASH
.try_into()
.unwrap();
let factory = deploy_factory(counter_class_hash, init_value);
let mut counter_address = factory.create_counter();
let counter_1 = target::ISimpleCounterDispatcher { contract_address: counter_address };
counter_address = factory.create_counter_at(argument_value);
let counter_2 = target::ISimpleCounterDispatcher { contract_address: counter_address };
assert_eq!(counter_1.get_current_count(), init_value);
assert_eq!(counter_2.get_current_count(), argument_value);
}
}
emit_event
fn emit_event_syscall(
keys: Span<felt252>, data: Span<felt252>
) -> SyscallResult<()>
Emit an event with the given keys
and data
.
Example of the usage of the emit_event
syscall from the Events chapter:
#[starknet::interface]
pub trait IEventCounter<TContractState> {
fn increment(ref self: TContractState, amount: u128);
}
#[starknet::contract]
pub mod EventCounter {
use starknet::{get_caller_address, ContractAddress};
#[storage]
struct Storage {
// Counter value
counter: u128,
}
#[event]
#[derive(Copy, Drop, Debug, PartialEq, starknet::Event)]
// The event enum must be annotated with the `#[event]` attribute.
// It must also derive at least the `Drop` and `starknet::Event` traits.
pub enum Event {
CounterIncreased: CounterIncreased,
UserIncreaseCounter: UserIncreaseCounter
}
// By deriving the `starknet::Event` trait, we indicate to the compiler that
// this struct will be used when emitting events.
#[derive(Copy, Drop, Debug, PartialEq, starknet::Event)]
pub struct CounterIncreased {
pub amount: u128
}
#[derive(Copy, Drop, Debug, PartialEq, starknet::Event)]
pub struct UserIncreaseCounter {
// The `#[key]` attribute indicates that this event will be indexed.
// You can also use `#[flat]` for nested structs.
#[key]
pub user: ContractAddress,
pub new_value: u128,
}
#[abi(embed_v0)]
impl EventCounter of super::IEventCounter<ContractState> {
fn increment(ref self: ContractState, amount: u128) {
self.counter.write(self.counter.read() + amount);
// Emit event
self.emit(Event::CounterIncreased(CounterIncreased { amount }));
self
.emit(
Event::UserIncreaseCounter(
UserIncreaseCounter {
user: get_caller_address(), new_value: self.counter.read()
}
)
);
}
}
}
#[cfg(test)]
mod tests {
use super::{
EventCounter,
EventCounter::{
counterContractMemberStateTrait, Event, CounterIncreased, UserIncreaseCounter
},
IEventCounterDispatcherTrait, IEventCounterDispatcher
};
use starknet::{
ContractAddress, contract_address_const, SyscallResultTrait, syscalls::deploy_syscall
};
use starknet::testing::{set_contract_address, set_account_contract_address};
#[test]
fn test_increment_events() {
let (contract_address, _) = deploy_syscall(
EventCounter::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false
)
.unwrap_syscall();
let mut contract = IEventCounterDispatcher { contract_address };
let state = EventCounter::contract_state_for_testing();
let amount = 10;
let caller = contract_address_const::<'caller'>();
// fake caller
set_contract_address(caller);
contract.increment(amount);
// set back to the contract for reading state
set_contract_address(contract_address);
assert_eq!(state.counter.read(), amount);
// Notice the order: the first event emitted is the first to be popped.
assert_eq!(
starknet::testing::pop_log(contract_address),
Option::Some(Event::CounterIncreased(CounterIncreased { amount }))
);
assert_eq!(
starknet::testing::pop_log(contract_address),
Option::Some(
Event::UserIncreaseCounter(UserIncreaseCounter { user: caller, new_value: amount })
)
);
}
}
library_call
fn library_call_syscall(
class_hash: ClassHash, function_selector: felt252, calldata: Span<felt252>
) -> SyscallResult<Span<felt252>>
Call the function function_selector
of the class class_hash
with calldata
.
This is analogous to a delegate call in Ethereum, but only a single class is called.
send_message_to_L1
fn send_message_to_l1_syscall(
to_address: felt252, payload: Span<felt252>
) -> SyscallResult<()>
Send a message to the L1 contract at to_address
with the given payload
.
replace_class
fn replace_class_syscall(
class_hash: ClassHash
) -> SyscallResult<()>
Replace the class of the calling contract with the class class_hash
.
This is used for contract upgrades. Here's an example from the Upgradeable Contract:
use starknet::class_hash::ClassHash;
#[starknet::interface]
pub trait IUpgradeableContract<TContractState> {
fn upgrade(ref self: TContractState, impl_hash: ClassHash);
fn version(self: @TContractState) -> u8;
}
#[starknet::contract]
pub mod UpgradeableContract_V0 {
use starknet::class_hash::ClassHash;
use starknet::SyscallResultTrait;
use core::num::traits::Zero;
#[storage]
struct Storage {}
#[event]
#[derive(Copy, Drop, Debug, PartialEq, starknet::Event)]
pub enum Event {
Upgraded: Upgraded,
}
#[derive(Copy, Drop, Debug, PartialEq, starknet::Event)]
pub struct Upgraded {
pub implementation: ClassHash
}
#[abi(embed_v0)]
impl UpgradeableContract of super::IUpgradeableContract<ContractState> {
fn upgrade(ref self: ContractState, impl_hash: ClassHash) {
assert(impl_hash.is_non_zero(), 'Class hash cannot be zero');
starknet::syscalls::replace_class_syscall(impl_hash).unwrap_syscall();
self.emit(Event::Upgraded(Upgraded { implementation: impl_hash }))
}
fn version(self: @ContractState) -> u8 {
0
}
}
}
The new class code will only be used for future calls to the contract.
The current transaction containing the replace_class
syscall will continue to use the old class code. Note that you can explicitly use the new class code in the same transaction by calling call_contract
after the replace_class
syscall.
storage_read
fn storage_read_syscall(
address_domain: u32, address: StorageAddress,
) -> SyscallResult<felt252>
This low-level syscall is used to get the value in the storage of a specific key at address
in the address_domain
.
address_domain
is used to distinguish between data availability modes.
Currently, only mode ONCHAIN
(0
) is supported.
storage_write
fn storage_write_syscall(
address_domain: u32, address: StorageAddress, value: felt252
) -> SyscallResult<()>
Similar to storage_read
, this low-level syscall is used to write the value value
in the storage of a specific key at address
in the address_domain
.
Documentation
Syscalls are defined in starknet::syscall
.
You can also read the official documentation page for more details.