Skip to content

Commit

Permalink
feat: syscalls (#157)
Browse files Browse the repository at this point in the history
  • Loading branch information
julio4 authored Dec 21, 2023
1 parent 51a794a commit 07023b0
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// ANCHOR: contract
use starknet::class_hash::ClassHash;

#[starknet::interface]
Expand Down Expand Up @@ -28,14 +29,19 @@ mod UpgradeableContract_V0 {

#[abi(embed_v0)]
impl UpgradeableContract of super::IUpgradeableContract<ContractState> {
// ANCHOR: upgrade
fn upgrade(ref self: ContractState, impl_hash: ClassHash) {
assert(!impl_hash.is_zero(), 'Class hash cannot be zero');
starknet::replace_class_syscall(impl_hash).unwrap_syscall();
self.emit(Event::Upgraded(Upgraded { implementation: impl_hash }))
}
// ANCHOR_END: upgrade

fn version(self: @ContractState) -> u8 {
0
}
}
}
// ANCHOR_END: contract


6 changes: 6 additions & 0 deletions listings/getting-started/events/src/counter.cairo
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// ANCHOR: contract
#[starknet::interface]
trait IEventCounter<TContractState> {
fn increment(ref self: TContractState);
Expand Down Expand Up @@ -42,6 +43,7 @@ mod EventCounter {
counter += 1;
self.counter.write(counter);
// Emit event
// ANCHOR: emit
self.emit(Event::CounterIncreased(CounterIncreased { amount: 1 }));
self
.emit(
Expand All @@ -51,6 +53,10 @@ mod EventCounter {
}
)
);
// ANCHOR_END: emit
}
}
}
// ANCHOR_END: contract


6 changes: 6 additions & 0 deletions listings/getting-started/factory/src/simple_factory.cairo
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// ANCHOR: contract
use starknet::{ContractAddress, ClassHash};

#[starknet::interface]
Expand Down Expand Up @@ -36,6 +37,7 @@ mod CounterFactory {

#[abi(embed_v0)]
impl Factory of super::ICounterFactory<ContractState> {
// ANCHOR: deploy
fn create_counter_at(ref self: ContractState, init_value: u128) -> ContractAddress {
// Contructor arguments
let mut constructor_calldata: Array::<felt252> = array![init_value.into()];
Expand All @@ -48,6 +50,7 @@ mod CounterFactory {

deployed_address
}
// ANCHOR_END: deploy

fn create_counter(ref self: ContractState) -> ContractAddress {
self.create_counter_at(self.init_value.read())
Expand All @@ -62,3 +65,6 @@ mod CounterFactory {
}
}
}
// ANCHOR_END: contract


1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Summary
- [Mappings](./ch00/basics/mappings.md)
- [Errors](./ch00/basics/errors.md)
- [Events](./ch00/basics/events.md)
- [Syscalls](./ch00/basics/syscalls.md)
- [Storing Custom Types](./ch00/basics/storing-custom-types.md)
- [Custom types in entrypoints](./ch00/basics/custom-types-in-entrypoints.md)
- [Documentation](./ch00/basics/documentation.md)
Expand Down
2 changes: 1 addition & 1 deletion src/ch00/basics/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ An event is defined as struct that derives the `#[starknet::Event]` trait. The f
Here's a simple example of a contract using events that emit an event each time a counter is incremented by the "increment" function:

```rust
{{#include ../../../listings/getting-started/events/src/counter.cairo}}
{{#include ../../../listings/getting-started/events/src/counter.cairo:contract}}
```
Visit contract on [Voyager](https://goerli.voyager.online/contract/0x022e3B59518EA04aBb5da671ea04ecC3a154400f226d2Df38eFE146741b9E2F6) or play with it in [Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/getting-started/events/src/counter.cairo).
271 changes: 271 additions & 0 deletions src/ch00/basics/syscalls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
# 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_block_hash)
- [get_execution_info](#get_execution_info)
- [call_contract](#call_contract)
- [deploy](#deploy)
- [emit_event](#emit_event)
- [library_call](#library_call)
- [send_message_to_L1](#send_message_to_L1)
- [replace_class](#replace_class)
- [storage_read](#storage_read)
- [storage_write](#storage_write)
<!-- - keccak_syscall ? -->

#### get_block_hash

```rust
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

```rust
fn get_execution_info_syscall() -> SyscallResult<Box<starknet::info::ExecutionInfo>>
```

Get information about the current execution context.
The returned `ExecutionInfo` is defined as :

```rust
#[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

```rust
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](../interacting/calling_other_contracts.md).

<!-- TODO Add example ? / with interact chapter -->

#### deploy

```rust
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](../interacting/factory.md):

```rust
{{#rustdoc_include ../../../listings/getting-started/factory/src/simple_factory.cairo:deploy}}
```

#### emit_event

```rust
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](../basics/events.md) chapter:

```rust
{{#rustdoc_include ../../../listings/getting-started/events/src/counter.cairo:emit}}
```

<!-- TODO Add a more low-level example ? -->
<!-- ```
let keys = ArrayTrait::new();
keys.append('key');
keys.append('deposit');
let values = ArrayTrait::new();
values.append(1);
values.append(2);
values.append(3);
emit_event_syscall(keys, values).unwrap_syscall();
``` -->

#### library_call

```rust
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.

<!-- TODO Add example: issue #41 -->

#### send_message_to_L1

```rust
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`.

<!-- TODO - Link to message example page: issue #9 -->

#### replace_class

```rust
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](../../ch01/upgradeable_contract.md):

```rust
{{#rustdoc_include ../../../listings/applications/upgradeable_contract/src/upgradeable_contract_v0.cairo:upgrade}}
```

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

```rust
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

```rust
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`](https://github.com/starkware-libs/cairo/blob/ec14a5e2c484190ff40811c973a72a53739cedb7/corelib/src/starknet/syscalls.cairo)

You can also read the [official documentation page](https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/system-calls-cairo1/) for more details.

<!--

#### Gas cost

```rust
mod gas_costs {
const STEP: usize = 100;
const RANGE_CHECK: usize = 70;

/// Entry point initial gas cost enforced by the compiler.
const ENTRY_POINT_INITIAL_BUDGET: usize = 100 * STEP;

/// OS gas costs.
const ENTRY_POINT: usize = ENTRY_POINT_INITIAL_BUDGET + 500 * STEP;
// The required gas for each syscall minus the base amount that was pre-charged (by the
// compiler).
pub const CALL_CONTRACT: usize = 10 * STEP + ENTRY_POINT;
pub const DEPLOY: usize = 200 * STEP + ENTRY_POINT;
pub const EMIT_EVENT: usize = 10 * STEP;
pub const GET_BLOCK_HASH: usize = 50 * STEP;
pub const GET_EXECUTION_INFO: usize = 10 * STEP;
pub const LIBRARY_CALL: usize = CALL_CONTRACT;
pub const REPLACE_CLASS: usize = 50 * STEP;
pub const SEND_MESSAGE_TO_L1: usize = 50 * STEP;
pub const STORAGE_READ: usize = 50 * STEP;
pub const STORAGE_WRITE: usize = 50 * STEP;
/// ...
}
```

Specific gas cost are defined in this [file](https://github.com/starkware-libs/cairo/blob/ec14a5e2c484190ff40811c973a72a53739cedb7/crates/cairo-lang-runner/src/casm_run/mod.rs#L333)
-->
2 changes: 1 addition & 1 deletion src/ch00/interacting/factory.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Using the factory pattern, we can deploy multiple instances of the same contract
Here's a minimal example of a factory contract that deploy the `SimpleCounter` contract:

```rust
{{#include ../../../listings/getting-started/factory/src/simple_factory.cairo}}
{{#include ../../../listings/getting-started/factory/src/simple_factory.cairo:contract}}
```

<!-- This is not ready for "Open in remix" because we need multiple files -->
Expand Down
2 changes: 1 addition & 1 deletion src/ch00/testing/contract-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ To make testing more convenient, the `testing` module of the corelib provides so
- `set_account_contract_address(address: ContractAddress)`
- `set_max_fee(fee: u128)`

You may also need the `info` module from the corelib, which allows you to access information about the current transaction context:
You may also need the `info` module from the corelib, which allows you to access information about the current execution context (see [syscalls](../basics/syscalls.md)):
- `get_caller_address() -> ContractAddress`
- `get_contract_address() -> ContractAddress`
- `get_block_info() -> Box<BlockInfo>`
Expand Down
2 changes: 1 addition & 1 deletion src/ch01/upgradeable_contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ To illustrate this concept, let's consider an example with two contracts: `Upgra
Start by deploying `UpgradeableContract_V0` as the initial version. Next, send a transaction that invokes the `upgrade` function, with the class hash of `UpgradeableContract_V1` as parameter to upgrade the class hash of the deployed contract to the `UpgradeableContract_V1` one. Then, call the `version` method on the contract to see that the contract was upgraded to the V1 version.

```rust
{{#include ../../listings/applications/upgradeable_contract/src/upgradeable_contract_v0.cairo}}
{{#include ../../listings/applications/upgradeable_contract/src/upgradeable_contract_v0.cairo:contract}}
```
Visit contract on [Voyager](https://goerli.voyager.online/contract/0x005300003ade5d10447d941a42d48b7141074cd8bade2b16520684896a5090ea) or play with it in [Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/applications/upgradeable_contract/src/upgradeable_contract_v0.cairo).

Expand Down

0 comments on commit 07023b0

Please sign in to comment.