diff --git a/listings/advanced-concepts/sierra_ir/simple_program.cairo b/listings/advanced-concepts/sierra_ir/simple_program.cairo new file mode 100644 index 00000000..6707f0f5 --- /dev/null +++ b/listings/advanced-concepts/sierra_ir/simple_program.cairo @@ -0,0 +1,6 @@ +// [!region contract] +#[starknet::contract] +fn add_numbers(a: felt252, b: felt252) -> felt252 { + a + b; +} +// [!endregion contract] diff --git a/listings/advanced-concepts/sierra_ir/simple_program.sierra b/listings/advanced-concepts/sierra_ir/simple_program.sierra new file mode 100644 index 00000000..b050b10e --- /dev/null +++ b/listings/advanced-concepts/sierra_ir/simple_program.sierra @@ -0,0 +1,10 @@ +type felt252 = felt252 [storable: true, drop: true, dup: true, zero_sized: false]; + +libfunc felt252_add = felt252_add; +libfunc store_temp = store_temp; + +felt252_add([0], [1]) -> ([2]); // 0 +store_temp([2]) -> ([2]); // 1 +return([2]); // 2 + +simple::simple::add_numbers@0([0]: felt252, [1]: felt252) -> (felt252); diff --git a/listings/advanced-concepts/sierra_ir/storage_variable.sierra b/listings/advanced-concepts/sierra_ir/storage_variable.sierra new file mode 100644 index 00000000..6d1f6833 --- /dev/null +++ b/listings/advanced-concepts/sierra_ir/storage_variable.sierra @@ -0,0 +1,323 @@ +type RangeCheck = RangeCheck [storable: true, drop: false, dup: false, zero_sized: false]; +type Const = Const [storable: false, drop: false, dup: false, zero_sized: false]; +type StorageBaseAddress = StorageBaseAddress [storable: true, drop: true, dup: true, zero_sized: false]; +type core::starknet::storage::StoragePointer0Offset:: = Struct [storable: true, drop: true, dup: true, zero_sized: false]; +type Const = Const [storable: false, drop: false, dup: false, zero_sized: false]; +type Const = Const [storable: false, drop: false, dup: false, zero_sized: false]; +type Array = Array [storable: true, drop: true, dup: false, zero_sized: false]; +type Snapshot> = Snapshot> [storable: true, drop: true, dup: true, zero_sized: false]; +type core::array::Span:: = Struct>> [storable: true, drop: true, dup: true, zero_sized: false]; +type Tuple> = Struct> [storable: true, drop: true, dup: true, zero_sized: false]; +type Const = Const [storable: false, drop: false, dup: false, zero_sized: false]; +type StorageAddress = StorageAddress [storable: true, drop: true, dup: true, zero_sized: false]; +type BuiltinCosts = BuiltinCosts [storable: true, drop: true, dup: true, zero_sized: false]; +type System = System [storable: true, drop: false, dup: false, zero_sized: false]; +type core::panics::Panic = Struct [storable: true, drop: true, dup: true, zero_sized: true]; +type Tuple> = Struct> [storable: true, drop: true, dup: false, zero_sized: false]; +type core::panics::PanicResult::<(core::array::Span::,)> = Enum>, Tuple>> [storable: true, drop: true, dup: false, zero_sized: false]; +type Const = Const [storable: false, drop: false, dup: false, zero_sized: false]; +type u32 = u32 [storable: true, drop: true, dup: true, zero_sized: false]; +type Unit = Struct [storable: true, drop: true, dup: true, zero_sized: true]; +type Box = Box [storable: true, drop: true, dup: true, zero_sized: false]; +type core::option::Option::> = Enum, Unit> [storable: true, drop: true, dup: true, zero_sized: false]; +type felt252 = felt252 [storable: true, drop: true, dup: true, zero_sized: false]; +type GasBuiltin = GasBuiltin [storable: true, drop: false, dup: false, zero_sized: false]; + +libfunc revoke_ap_tracking = revoke_ap_tracking; +libfunc withdraw_gas = withdraw_gas; +libfunc branch_align = branch_align; +libfunc struct_deconstruct> = struct_deconstruct>; +libfunc enable_ap_tracking = enable_ap_tracking; +libfunc store_temp = store_temp; +libfunc array_snapshot_pop_front = array_snapshot_pop_front; +libfunc enum_init>, 0> = enum_init>, 0>; +libfunc store_temp>> = store_temp>>; +libfunc store_temp>> = store_temp>>; +libfunc jump = jump; +libfunc struct_construct = struct_construct; +libfunc enum_init>, 1> = enum_init>, 1>; +libfunc enum_match>> = enum_match>>; +libfunc unbox = unbox; +libfunc rename = rename; +libfunc store_temp = store_temp; +libfunc u32_try_from_felt252 = u32_try_from_felt252; +libfunc disable_ap_tracking = disable_ap_tracking; +libfunc drop>> = drop>>; +libfunc drop> = drop>; +libfunc drop = drop; +libfunc array_new = array_new; +libfunc const_as_immediate> = const_as_immediate>; +libfunc array_append = array_append; +libfunc struct_construct = struct_construct; +libfunc struct_construct>> = struct_construct>>; +libfunc enum_init,)>, 1> = enum_init,)>, 1>; +libfunc store_temp = store_temp; +libfunc store_temp = store_temp; +libfunc store_temp,)>> = store_temp,)>>; +libfunc get_builtin_costs = get_builtin_costs; +libfunc store_temp = store_temp; +libfunc withdraw_gas_all = withdraw_gas_all; +libfunc storage_base_address_const<763158443913282032384596498131031794477249071826004797576159089783775391621> = storage_base_address_const<763158443913282032384596498131031794477249071826004797576159089783775391621>; +libfunc u32_to_felt252 = u32_to_felt252; +libfunc storage_address_from_base = storage_address_from_base; +libfunc const_as_immediate> = const_as_immediate>; +libfunc store_temp = store_temp; +libfunc store_temp = store_temp; +libfunc storage_write_syscall = storage_write_syscall; +libfunc snapshot_take> = snapshot_take>; +libfunc drop> = drop>; +libfunc struct_construct> = struct_construct>; +libfunc struct_construct>> = struct_construct>>; +libfunc enum_init,)>, 0> = enum_init,)>, 0>; +libfunc const_as_immediate> = const_as_immediate>; +libfunc drop = drop; +libfunc const_as_immediate> = const_as_immediate>; +libfunc drop> = drop>; +libfunc struct_construct> = struct_construct>; +libfunc snapshot_take> = snapshot_take>; +libfunc drop> = drop>; +libfunc struct_deconstruct> = struct_deconstruct>; +libfunc rename = rename; +libfunc storage_read_syscall = storage_read_syscall; +libfunc const_as_immediate> = const_as_immediate>; +libfunc store_temp> = store_temp>; + +revoke_ap_tracking() -> (); // 0 +withdraw_gas([0], [1]) { fallthrough([4], [5]) 114([6], [7]) }; // 1 +branch_align() -> (); // 2 +struct_deconstruct>([3]) -> ([8]); // 3 +enable_ap_tracking() -> (); // 4 +store_temp([4]) -> ([4]); // 5 +array_snapshot_pop_front([8]) { fallthrough([9], [10]) 12([11]) }; // 6 +branch_align() -> (); // 7 +enum_init>, 0>([10]) -> ([12]); // 8 +store_temp>>([9]) -> ([13]); // 9 +store_temp>>([12]) -> ([14]); // 10 +jump() { 17() }; // 11 +branch_align() -> (); // 12 +struct_construct() -> ([15]); // 13 +enum_init>, 1>([15]) -> ([16]); // 14 +store_temp>>([11]) -> ([13]); // 15 +store_temp>>([16]) -> ([14]); // 16 +enum_match>>([14]) { fallthrough([17]) 97([18]) }; // 17 +branch_align() -> (); // 18 +unbox([17]) -> ([19]); // 19 +rename([19]) -> ([20]); // 20 +store_temp([20]) -> ([20]); // 21 +u32_try_from_felt252([4], [20]) { fallthrough([21], [22]) 93([23]) }; // 22 +branch_align() -> (); // 23 +store_temp([21]) -> ([21]); // 24 +array_snapshot_pop_front([13]) { fallthrough([24], [25]) 43([26]) }; // 25 +branch_align() -> (); // 26 +disable_ap_tracking() -> (); // 27 +drop>>([24]) -> (); // 28 +drop>([25]) -> (); // 29 +drop([22]) -> (); // 30 +array_new() -> ([27]); // 31 +const_as_immediate>() -> ([28]); // 32 +store_temp([28]) -> ([28]); // 33 +array_append([27], [28]) -> ([29]); // 34 +struct_construct() -> ([30]); // 35 +struct_construct>>([30], [29]) -> ([31]); // 36 +enum_init,)>, 1>([31]) -> ([32]); // 37 +store_temp([21]) -> ([21]); // 38 +store_temp([5]) -> ([5]); // 39 +store_temp([2]) -> ([2]); // 40 +store_temp,)>>([32]) -> ([32]); // 41 +return([21], [5], [2], [32]); // 42 +branch_align() -> (); // 43 +disable_ap_tracking() -> (); // 44 +drop>>([26]) -> (); // 45 +get_builtin_costs() -> ([33]); // 46 +store_temp([33]) -> ([33]); // 47 +withdraw_gas_all([21], [5], [33]) { fallthrough([34], [35]) 79([36], [37]) }; // 48 +branch_align() -> (); // 49 +storage_base_address_const<763158443913282032384596498131031794477249071826004797576159089783775391621>() -> ([38]); // 50 +u32_to_felt252([22]) -> ([39]); // 51 +storage_address_from_base([38]) -> ([40]); // 52 +const_as_immediate>() -> ([41]); // 53 +store_temp([41]) -> ([41]); // 54 +store_temp([40]) -> ([40]); // 55 +store_temp([34]) -> ([34]); // 56 +storage_write_syscall([35], [2], [41], [40], [39]) { fallthrough([42], [43]) 70([44], [45], [46]) }; // 57 +branch_align() -> (); // 58 +array_new() -> ([47]); // 59 +snapshot_take>([47]) -> ([48], [49]); // 60 +drop>([48]) -> (); // 61 +struct_construct>([49]) -> ([50]); // 62 +struct_construct>>([50]) -> ([51]); // 63 +enum_init,)>, 0>([51]) -> ([52]); // 64 +store_temp([34]) -> ([34]); // 65 +store_temp([42]) -> ([42]); // 66 +store_temp([43]) -> ([43]); // 67 +store_temp,)>>([52]) -> ([52]); // 68 +return([34], [42], [43], [52]); // 69 +branch_align() -> (); // 70 +struct_construct() -> ([53]); // 71 +struct_construct>>([53], [46]) -> ([54]); // 72 +enum_init,)>, 1>([54]) -> ([55]); // 73 +store_temp([34]) -> ([34]); // 74 +store_temp([44]) -> ([44]); // 75 +store_temp([45]) -> ([45]); // 76 +store_temp,)>>([55]) -> ([55]); // 77 +return([34], [44], [45], [55]); // 78 +branch_align() -> (); // 79 +drop([22]) -> (); // 80 +array_new() -> ([56]); // 81 +const_as_immediate>() -> ([57]); // 82 +store_temp([57]) -> ([57]); // 83 +array_append([56], [57]) -> ([58]); // 84 +struct_construct() -> ([59]); // 85 +struct_construct>>([59], [58]) -> ([60]); // 86 +enum_init,)>, 1>([60]) -> ([61]); // 87 +store_temp([36]) -> ([36]); // 88 +store_temp([37]) -> ([37]); // 89 +store_temp([2]) -> ([2]); // 90 +store_temp,)>>([61]) -> ([61]); // 91 +return([36], [37], [2], [61]); // 92 +branch_align() -> (); // 93 +drop>>([13]) -> (); // 94 +store_temp([23]) -> ([62]); // 95 +jump() { 101() }; // 96 +branch_align() -> (); // 97 +drop([18]) -> (); // 98 +drop>>([13]) -> (); // 99 +store_temp([4]) -> ([62]); // 100 +disable_ap_tracking() -> (); // 101 +array_new() -> ([63]); // 102 +const_as_immediate>() -> ([64]); // 103 +store_temp([64]) -> ([64]); // 104 +array_append([63], [64]) -> ([65]); // 105 +struct_construct() -> ([66]); // 106 +struct_construct>>([66], [65]) -> ([67]); // 107 +enum_init,)>, 1>([67]) -> ([68]); // 108 +store_temp([62]) -> ([62]); // 109 +store_temp([5]) -> ([5]); // 110 +store_temp([2]) -> ([2]); // 111 +store_temp,)>>([68]) -> ([68]); // 112 +return([62], [5], [2], [68]); // 113 +branch_align() -> (); // 114 +drop>([3]) -> (); // 115 +array_new() -> ([69]); // 116 +const_as_immediate>() -> ([70]); // 117 +store_temp([70]) -> ([70]); // 118 +array_append([69], [70]) -> ([71]); // 119 +struct_construct() -> ([72]); // 120 +struct_construct>>([72], [71]) -> ([73]); // 121 +enum_init,)>, 1>([73]) -> ([74]); // 122 +store_temp([6]) -> ([6]); // 123 +store_temp([7]) -> ([7]); // 124 +store_temp([2]) -> ([2]); // 125 +store_temp,)>>([74]) -> ([74]); // 126 +return([6], [7], [2], [74]); // 127 +revoke_ap_tracking() -> (); // 128 +withdraw_gas([0], [1]) { fallthrough([4], [5]) 222([6], [7]) }; // 129 +branch_align() -> (); // 130 +struct_deconstruct>([3]) -> ([8]); // 131 +store_temp([4]) -> ([4]); // 132 +array_snapshot_pop_front([8]) { fallthrough([9], [10]) 149([11]) }; // 133 +branch_align() -> (); // 134 +drop>>([9]) -> (); // 135 +drop>([10]) -> (); // 136 +array_new() -> ([12]); // 137 +const_as_immediate>() -> ([13]); // 138 +store_temp([13]) -> ([13]); // 139 +array_append([12], [13]) -> ([14]); // 140 +struct_construct() -> ([15]); // 141 +struct_construct>>([15], [14]) -> ([16]); // 142 +enum_init,)>, 1>([16]) -> ([17]); // 143 +store_temp([4]) -> ([4]); // 144 +store_temp([5]) -> ([5]); // 145 +store_temp([2]) -> ([2]); // 146 +store_temp,)>>([17]) -> ([17]); // 147 +return([4], [5], [2], [17]); // 148 +branch_align() -> (); // 149 +drop>>([11]) -> (); // 150 +get_builtin_costs() -> ([18]); // 151 +store_temp([18]) -> ([18]); // 152 +withdraw_gas_all([4], [5], [18]) { fallthrough([19], [20]) 209([21], [22]) }; // 153 +branch_align() -> (); // 154 +storage_base_address_const<763158443913282032384596498131031794477249071826004797576159089783775391621>() -> ([23]); // 155 +struct_construct>([23]) -> ([24]); // 156 +snapshot_take>([24]) -> ([25], [26]); // 157 +drop>([25]) -> (); // 158 +struct_deconstruct>([26]) -> ([27]); // 159 +rename([27]) -> ([28]); // 160 +storage_address_from_base([28]) -> ([29]); // 161 +const_as_immediate>() -> ([30]); // 162 +store_temp([30]) -> ([30]); // 163 +store_temp([29]) -> ([29]); // 164 +store_temp([19]) -> ([19]); // 165 +storage_read_syscall([20], [2], [30], [29]) { fallthrough([31], [32], [33]) 196([34], [35], [36]) }; // 166 +branch_align() -> (); // 167 +store_temp([33]) -> ([33]); // 168 +store_temp([31]) -> ([31]); // 169 +store_temp([32]) -> ([32]); // 170 +u32_try_from_felt252([19], [33]) { fallthrough([37], [38]) 186([39]) }; // 171 +branch_align() -> (); // 172 +array_new() -> ([40]); // 173 +u32_to_felt252([38]) -> ([41]); // 174 +array_append([40], [41]) -> ([42]); // 175 +snapshot_take>([42]) -> ([43], [44]); // 176 +drop>([43]) -> (); // 177 +struct_construct>([44]) -> ([45]); // 178 +struct_construct>>([45]) -> ([46]); // 179 +enum_init,)>, 0>([46]) -> ([47]); // 180 +store_temp([37]) -> ([37]); // 181 +store_temp([31]) -> ([31]); // 182 +store_temp([32]) -> ([32]); // 183 +store_temp,)>>([47]) -> ([47]); // 184 +return([37], [31], [32], [47]); // 185 +branch_align() -> (); // 186 +array_new() -> ([48]); // 187 +const_as_immediate>() -> ([49]); // 188 +store_temp([49]) -> ([49]); // 189 +array_append([48], [49]) -> ([50]); // 190 +store_temp([39]) -> ([51]); // 191 +store_temp([31]) -> ([52]); // 192 +store_temp([32]) -> ([53]); // 193 +store_temp>([50]) -> ([54]); // 194 +jump() { 201() }; // 195 +branch_align() -> (); // 196 +store_temp([19]) -> ([51]); // 197 +store_temp([34]) -> ([52]); // 198 +store_temp([35]) -> ([53]); // 199 +store_temp>([36]) -> ([54]); // 200 +struct_construct() -> ([55]); // 201 +struct_construct>>([55], [54]) -> ([56]); // 202 +enum_init,)>, 1>([56]) -> ([57]); // 203 +store_temp([51]) -> ([51]); // 204 +store_temp([52]) -> ([52]); // 205 +store_temp([53]) -> ([53]); // 206 +store_temp,)>>([57]) -> ([57]); // 207 +return([51], [52], [53], [57]); // 208 +branch_align() -> (); // 209 +array_new() -> ([58]); // 210 +const_as_immediate>() -> ([59]); // 211 +store_temp([59]) -> ([59]); // 212 +array_append([58], [59]) -> ([60]); // 213 +struct_construct() -> ([61]); // 214 +struct_construct>>([61], [60]) -> ([62]); // 215 +enum_init,)>, 1>([62]) -> ([63]); // 216 +store_temp([21]) -> ([21]); // 217 +store_temp([22]) -> ([22]); // 218 +store_temp([2]) -> ([2]); // 219 +store_temp,)>>([63]) -> ([63]); // 220 +return([21], [22], [2], [63]); // 221 +branch_align() -> (); // 222 +drop>([3]) -> (); // 223 +array_new() -> ([64]); // 224 +const_as_immediate>() -> ([65]); // 225 +store_temp([65]) -> ([65]); // 226 +array_append([64], [65]) -> ([66]); // 227 +struct_construct() -> ([67]); // 228 +struct_construct>>([67], [66]) -> ([68]); // 229 +enum_init,)>, 1>([68]) -> ([69]); // 230 +store_temp([6]) -> ([6]); // 231 +store_temp([7]) -> ([7]); // 232 +store_temp([2]) -> ([2]); // 233 +store_temp,)>>([69]) -> ([69]); // 234 +return([6], [7], [2], [69]); // 235 + +storage_variables::storage_variables::StorageVariablesExample::__wrapper__StorageVariablesExample__set@0([0]: RangeCheck, [1]: GasBuiltin, [2]: System, [3]: core::array::Span::) -> (RangeCheck, GasBuiltin, System, core::panics::PanicResult::<(core::array::Span::,)>); +storage_variables::storage_variables::StorageVariablesExample::__wrapper__StorageVariablesExample__get@128([0]: RangeCheck, [1]: GasBuiltin, [2]: System, [3]: core::array::Span::) -> (RangeCheck, GasBuiltin, System, core::panics::PanicResult::<(core::array::Span::,)>); diff --git a/listings/advanced-concepts/sierra_ir/storage_variables.cairo b/listings/advanced-concepts/sierra_ir/storage_variables.cairo new file mode 100644 index 00000000..66f1d52b --- /dev/null +++ b/listings/advanced-concepts/sierra_ir/storage_variables.cairo @@ -0,0 +1,59 @@ +#[starknet::interface] +pub trait IStorageVariableExample { + fn set(ref self: TContractState, value: u32); + fn get(self: @TContractState) -> u32; +} + +// [!region contract] +#[starknet::contract] +pub mod StorageVariablesExample { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + + #[storage] + struct Storage { + pub value: u32 + } + + #[abi(embed_v0)] + impl StorageVariablesExample of super::IStorageVariableExample { + fn set(ref self: ContractState, value: u32) { + self.value.write(value); + } + + fn get(self: @ContractState) -> u32 { + self.value.read() + } + } +} +// [!endregion contract] + +#[cfg(test)] +mod test { + use super::{ + StorageVariablesExample, IStorageVariableExampleDispatcher, + IStorageVariableExampleDispatcherTrait + }; + use starknet::{SyscallResultTrait, syscalls::deploy_syscall}; + use starknet::testing::set_contract_address; + use starknet::storage::StoragePointerReadAccess; + + #[test] + fn test_can_deploy_and_mutate_storage() { + let (contract_address, _) = deploy_syscall( + StorageVariablesExample::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false + ) + .unwrap_syscall(); + + let contract = IStorageVariableExampleDispatcher { contract_address }; + + let initial_value = 10; + + contract.set(initial_value); + assert_eq!(contract.get(), initial_value); + + // With contract state directly + let state = @StorageVariablesExample::contract_state_for_testing(); + set_contract_address(contract_address); + assert_eq!(state.value.read(), initial_value); + } +} diff --git a/pages/advanced-concepts/sierra_ir.md b/pages/advanced-concepts/sierra_ir.md new file mode 100644 index 00000000..e9a4f293 --- /dev/null +++ b/pages/advanced-concepts/sierra_ir.md @@ -0,0 +1,315 @@ +# Understanding Cairo's Sierra IR: From High-Level Cairo to Safe IR + +## Introduction + +Sierra (Safe Intermediate RepresentAtion) is a critical intermediate language in the Cairo compilation pipeline, designed to bridge the gap between high-level Cairo and low-level CASM (Cairo Assembly). Its primary purpose is mostly to be able to compile to a subset of casm that we call safe casm that ensure that any transaction sent is provable, while being able to have a high-level language (cairo1) with high memory safety. + +## From Cairo 1(High-level) to Sierra: Evolution and Problem Solving in Starknet + +Before Starknet Alpha v0.11.0, developers wrote contracts in Cairo 0 which compiled directly to Cairo assembly (CASM), and the contract class was submitted to the Starknet sequencer via `DECLARE` transaction. This approach had risks of unprovable transactions and potential sequencer attacks, with limited safety guarantees at compile time. + +Cairo 1.0 transformed this landscape by introducing Sierra as an intermediate representation, where contract classes no longer include CASM directly. Instead, the sequencer performs Sierra → CASM compilation before the CASM code is executed by the Starknet OS. This shift ensures every transaction (including failed ones) must be provable, sequencers can charge fees for all transactions, and the system enforces strict memory safety and ownership rules. + +This evolution solved critical issues through: +- Guaranteed transaction provability for both successful and failed executions +- Protected sequencers through guaranteed compensation and DoS attack prevention +- Enhanced safety through compile-time checks and strong type system enforcement +- Robust memory safety with strict ownership rules and reliable resource cleanup + +This comprehensive transition maintains backward compatibility while significantly improving both security and the development experience in the Starknet ecosystem. + +## The Compilation Pipeline +``` + Cairo 1.0 Source (High-level) + ↓ + ↓ with Cairo-to-Sierra Compiler + ↓ + Sierra IR (Safe, Provable Code) + ↓ + ↓ with Sierra-to-CASM Compiler (Run by Sequencer) + ↓ + CASM (Cairo Assembly) + ↓ + ↓ with CairoVM Execution + ↓ + STARK Proofs (Proof Generation) +``` +At its core, Sierra's compilation process is all about safety and efficiency. It carefully checks types at every step of the transformation, makes sure memory is handled safely in the final code, and keeps track of gas usage with precision. The compiler also focuses on generating optimized code while ensuring that every operation will run the same way each time - something crucial for smart contracts. If you're interested in really understanding how the compilation works under the hood, check out [cairo-compiler-workshop](https://github.com/software-mansion-labs/cairo-compiler-workshop). + +## Key Concepts in Sierra + +### 1. Type System + +Sierra implements a powerful linear type system, where each value must be used exactly once. This "use-once" principle is fundamental to Sierra's safety guarantees, preventing both resource leaks and double-use errors. The system includes concrete types like felt252 and u256, which form the foundation of Sierra's type hierarchy. These are complemented by generic types with constraints, enabling flexible but safe abstractions. User-defined types extend the system's capabilities while maintaining safety guarantees. The type system employs references and boxing mechanisms to manage memory safely, ensuring all memory access is valid, variables are properly initialized before use, and type constraints are consistently enforced throughout the program's execution. + +### 2. Memory Model + +Sierra's memory model prioritizes safety through deliberate design choices. It implements linear memory management, requiring explicit tracking of resource ownership and lifecycle. Memory operations are handled through explicit deallocation, preventing memory leaks and use-after-free errors. The model prohibits implicit copies, making data movement explicit and traceable. For complex data structures, Sierra employs reference counting to manage shared resources safely, ensuring proper cleanup when resources are no longer needed. This careful approach to memory management forms a cornerstone of Sierra's safety guarantees. + +### 3. Control Flow + +Control flow in Sierra is structured through a clear, explicit model that maintains safety while enabling complex program logic. Basic blocks serve as the fundamental units of execution, with explicit branch instructions managing program flow between them. Function calls are handled with explicit stack management, ensuring resource safety across function boundaries. Loop structures incorporate invariant checking, maintaining safety properties throughout iterations. This explicit control flow model enables comprehensive static analysis while preventing common control flow errors. + + +## Sierra Code Explanation for a simple cairo program +### original cairo program +```cairo +// [!include ~/listings/advanced-concepts/sierra_ir/simple_cairo.cairo:contract] +``` + +### compiled sierra +The above program compiled [sierra code](~/listings/advanced-concepts/sierra_ir/simple_program.sierra) + +Sierra (Safe Intermediate Representation and Execution) is a critical intermediate representation in Starknet's Cairo contract compilation process. For the simple add_numbers function, here's a detailed breakdown: + +1. Type Declarations defines the fundamental data types and their attributes used in the Sierra code. +- `felt252`: Represents the field element type +- Attributes: + - `storable: true`: this attributecan be stored + - `drop: true`: this attribute can be discarded + - `dup: true`: this attributes can be duplicated + - `zero_sized: false`: this attributes has a non-zero memory footprint + +2. Libfunc Declarations specify the library functions available for performing operations on these types. + - `felt252_add`: Performs addition on field elements + - `store_temp`: Temporarily stores the result + +3. Compilation Steps show the actual sequence of operations that happen during code execution. + - Line 0: `felt252_add([0], [1]) -> ([2]): Add input parameters + - Line 1: `store_temp([2]) -> ([2]): Store the temporary result + - Line 2: `return([2]): Return the computed value + +4. Libfunc Restrictions: +- Starknet uses an allowed [list](https://github.com/starkware-libs/cairo/tree/main/crates/cairo-lang-starknet-classes/src/allowed_libfuncs_lists) of libfuncs to ensure: + - Security: + Sierra enforces security by allowing only whitelisted operations with predefined behaviors, while maintaining strict control over memory operations and eliminating arbitrary pointer arithmetic to prevent vulnerabilities. + - Predictability: + The restricted libfunc approach ensures deterministic execution across all operations, enabling precise gas cost calculations and well-defined state transitions, while keeping all side effects explicitly managed and traceable. + - Verifiability: + By limiting library functions, Sierra simplifies the proof generation process and makes constraint system generation more efficient, which reduces verification complexity and ensures consistent behavior across different implementations. + - Error Prevention + The restricted library functions eliminate undefined behavior by catching potential runtime errors at compile time, while enforcing explicit resource management and maintaining type safety throughout the entire execution process. +The restricted libfunc approach helps maintain the safety and efficiency of smart contracts on the Starknet platform. + + +## Sierra Code Explanation for Storage Smart Contract + +```cairo +// [!include ~/listings/advanced-concepts/sierra_ir/storage_variable.cairo:contract] +``` + +### compiled sierra +The Storage smart contract compiled [sierra code](~/listings/advanced-concepts/sierra_ir/storage_variable.sierra) + +### 1. Type Decalarations: +This section defines the fundamental types that Sierra uses to ensure type safety and memory management. + +- System and Base Types + ```cairo + type RangeCheck = RangeCheck [storable: true, drop: false, dup: false, zero_sized: false]; + type felt252 = felt252 [storable: true, drop: true, dup: true, zero_sized: false]; + type u32 = u32 [storable: true, drop: true, dup: true, zero_sized: false]; + type GasBuiltin = GasBuiltin [storable: true, drop: false, dup: false, zero_sized: false]; + ``` + What This Means: + + - `RangeCheck`: A type used for validating numeric ranges. It can't be dropped or duplicated, ensuring that range checks are always properly handled. + - `felt252`: Field element type, the basic numeric type in Cairo. It's flexible - can be stored, dropped, and duplicated. + - `u32`: 32-bit unsigned integer, with similar properties to felt252. + - `GasBuiltin`: Tracks gas consumption. Can't be dropped or duplicated to prevent gas accounting errors. + +- Storage-Related Types + ```cairo + type StorageBaseAddress = StorageBaseAddress [storable: true, drop: true, dup: true, zero_sized: false]; + type core::starknet::storage::StoragePointer0Offset:: = + Struct; + type StorageAddress = StorageAddress [storable: true, drop: true, dup: true, zero_sized: false]; + ``` + What This Means: + + - `StorageBaseAddress`: Represents the base location in contract storage. + - `StoragePointer0Offset`: A structured type that points to u32 values in storage. + - `StorageAddress`: The actual address used for storage operations. + +### 2. Library Functions(libfunc) +Sierra provides a set of built-in functions (libfuncs) for handling memory, storage, and gas operations. +- Memory Management + ```cairo + libfunc revoke_ap_tracking = revoke_ap_tracking; + libfunc enable_ap_tracking = enable_ap_tracking; + libfunc disable_ap_tracking = disable_ap_tracking; + + + libfunc store_temp = store_temp; + libfunc drop = drop; + ``` + #### Purpose: + These functions control the allocation pointer (ap) tracking system, which is crucial for memory safety: + + - Enable/disable tracking when needed + - Prevent memory leaks + - Ensure proper memory allocation + + + +- Storage Operations + ```cairo + libfunc storage_base_address_const<...> = storage_base_address_const<...>; + libfunc storage_address_from_base = storage_address_from_base; + libfunc storage_write_syscall = storage_write_syscall; + libfunc storage_read_syscall = storage_read_syscall; + ``` + #### Purpose: + These functions handle all storage interactions: + + - Computing storage addresses + - Reading from storage + - Writing to storage + - Managing storage layout + + +- Gas Management + ```cairo + libfunc withdraw_gas = withdraw_gas; + libfunc withdraw_gas_all = withdraw_gas_all; + libfunc get_builtin_costs = get_builtin_costs; + ``` + #### Purpose: + These functions handle all aspects of gas accounting: + + - Track gas consumption + - Withdraw gas for operations + - Calculate operation costs + - Prevent out-of-gas scenarios + +### 3. Execution Flow Analysis + +- Initialization and Gas Management Phase +This phase marks the beginning of contract execution where Sierra prepares the execution environment and handles initial gas costs. First, it disables the allocation pointer tracking, which is a safety measure to prevent memory tracking overhead during gas calculations. Then, it performs a critical gas withdrawal check - this ensures the contract has sufficient gas to execute, with fallback paths for both successful and failed gas checks. After gas validation, it aligns the execution branch for consistent memory layout and finally deconstructs the input parameters into their base components for further processing. +The phase is crucial for: + + - Setting up memory safety mechanisms + - Ensuring gas availability + - Preparing input parameters + - Establishing execution paths + ```cairo + // The actual code implementation + revoke_ap_tracking() -> (); // 0 + withdraw_gas([0], [1]) { fallthrough([4], [5]) 114([6], [7]) }; // 1 + branch_align() -> (); // 2 + struct_deconstruct>([3]) -> ([8]); // 3 + ``` + +- Parameter Processing Phase +In this phase, Sierra handles the processing and validation of input parameters. It reactivates the allocation pointer tracking, which was disabled during gas calculations, to ensure memory safety during parameter processing. The range check pointer is stored for numeric validations, crucial for ensuring values are within acceptable ranges. The system then processes array inputs through snapshots - a mechanism that provides safe, immutable views of array data. Finally, it handles option types, which are essential for representing potentially null or invalid values. This phase is fundamental for type safety and memory integrity. + +```cairo +enable_ap_tracking() -> (); // 4 +store_temp([4]) -> ([4]); // 5 +array_snapshot_pop_front([8]) { fallthrough([9], [10]) 12([11]) }; // 6 +enum_init>, 0>([10]) -> ([12]); // 8 +``` + +- Storage Operation Phase +The storage operation phase is where Sierra manages contract storage interactions. It begins by computing the storage base address - a unique identifier for the storage variable. This address is derived from contract-specific constants and variable positions. The system then converts this base address into a concrete storage address that can be used for actual storage operations. During write operations, the system includes multiple checks and fallback paths to handle potential failures, ensuring storage consistency even in error cases. + +```cairo +storage_base_address_const<...>() -> ([38]); // 50 +storage_address_from_base([38]) -> ([40]); // 52 +storage_write_syscall([35], [2], [41], [40], [39]) { + fallthrough([42], [43]) 70([44], [45], [46]) +}; // 57 +``` + +- Value Processing Phase +During value processing, Sierra handles type conversions and validations of data. This phase is critical for ensuring type safety and data integrity. The system attempts to convert between different numeric types (like felt252 to u32) with built-in overflow checks. Temporary storage is used to maintain values during processing, with careful management of memory resources. Each conversion includes fallback paths for handling potential failures. + +```cairo +u32_try_from_felt252([4], [20]) { fallthrough([21], [22]) 93([23]) }; // 22 +store_temp([20]) -> ([20]); // 21 +``` + +- Error Handling Phase +The error handling phase implements Sierra's robust error management system. It constructs panic results for error cases, ensuring that failures are handled gracefully and securely. The system creates structured error information including panic details and relevant error data. This phase is crucial for maintaining contract reliability and providing meaningful error feedback. All error states are carefully packaged into result types that can be safely returned and handled by the calling context. + +```cairo +struct_construct() -> ([30]); // 35 +struct_construct>>([30], [29]) -> ([31]); // 36 +enum_init,)>, 1>([31]) -> ([32]); // 37 +``` + +- Return Phase +The return phase is responsible for finalizing the execution and preparing the return values. It ensures all resources are properly accounted for by storing final values for range checking, gas tracking, and system state. The phase carefully packages all return values, maintaining type safety and memory integrity. This phase is critical for ensuring the contract's state is consistent after execution. + +```cairo +store_temp([21]) -> ([21]); // 38 +store_temp([5]) -> ([5]); // 39 +store_temp([2]) -> ([2]); // 40 +return([21], [5], [2], [32]); // 42 +``` + +- Storage Read Flow +The storage read operation is a specialized flow that handles retrieving values from contract storage. It begins with a storage address computation, followed by a syscall to read the storage value. The read value is then processed and converted to the appropriate type with necessary validations. This operation includes comprehensive error handling for cases like invalid storage addresses or type conversion failures. + +```cairo +storage_read_syscall([20], [2], [30], [29]) { + fallthrough([31], [32], [33]) 196([34], [35], [36]) +}; // 166 +store_temp([33]) -> ([33]); // 168 +u32_try_from_felt252([19], [33]); // 171 +``` + +- Storage Write Flow +The storage write operation manages a secure and atomic process for updating contract state in Sierra. It begins with precise gas calculations and withdrawals to ensure sufficient resources for the complete operation. The system then computes a unique storage address through a two-step process: generating a base address from contract-specific constants and transforming it into a concrete storage location. Finally, it executes the atomic write operation through a system call, with comprehensive error handling to maintain storage integrity. The entire process ensures consistent state updates, prevents data corruption, and handles failures gracefully. + + ```cairo + withdraw_gas([0], [1]) { fallthrough([4], [5]) 114([6], [7]) }; + + storage_base_address_const<...>() -> ([38]); + storage_address_from_base([38]) -> ([40]); + + storage_write_syscall([35], [2], [41], [40], [39]); + ``` + +### 4. Function Implementation Analysis +- Set Function Implementation + ```cairo + // Function wrapper + storage_variables::storage_variables::StorageVariablesExample::__wrapper__StorageVariablesExample__set@0( + [0]: RangeCheck, // For numeric checks + [1]: GasBuiltin, // For gas tracking + [2]: System, // For system calls + [3]: core::array::Span:: // Input parameters + ) + ``` + Key Operations: + 1. `Gas` withdrawal and checks + 2. `Parameter` validation + 3. `Storage` address computation + 4. `Value` writing to storage + 5. `Return` handling with panic checks + +- Get Function Implementation + storage_variables::storage_variables::StorageVariablesExample::__wrapper__StorageVariablesExample__get@128( + // Similar parameters as set + ) + + Key Operations: + 1. `Gas` checks + 2. `Storage` address computation + 3. `Value` reading from storage + 4. `Type` conversion and validation + 5. `Result` packaging and return + +## Further Reading + +- [Under the hood of Cairo 1.0: Exploring Sierra](https://www.nethermind.io/blog/under-the-hood-of-cairo-1-0-exploring-sierra-part-1) + +- [Under the hood of Cairo 2.0: Exploring Sierra](https://www.nethermind.io/blog/under-the-hood-of-cairo-1-0-exploring-sierra-part-2) + +- [Under the hood of Cairo 1.0: Exploring Sierra](https://www.nethermind.io/blog/under-the-hood-of-cairo-1-0-exploring-sierra-part-3) + +- [Cairo and Sierra](https://docs.starknet.io/architecture-and-concepts/smart-contracts/cairo-and-sierra/) + +- [Sierra - Deep Dive](https://www.starknet.io/blog/sierra-deep-dive-video/) +