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

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, 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.

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 atleast `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(Drop, starknet::Event)]
    enum Event {
        Upgraded: Upgraded
    }

    #[derive(Drop, starknet::Event)]
    struct Upgraded {
        implementation: ClassHash
    }

    #[abi(embed_v0)]
    impl UpgradeableContract of super::IUpgradeableContract<ContractState> {
        fn upgrade(ref self: ContractState, impl_hash: ClassHash) {
            assert(!impl_hash.is_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. (You can explicitly use the new class code by calling call_contract after the replace_class syscall in the same transaction)

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.

Last change: 2024-02-17, commit: 32b8abc