11// Reference implementation of call tracer.
22
3- use std:: mem;
4-
53use evm_interpreter:: ERGS_PER_GAS ;
64use ruint:: aliases:: { B160 , U256 } ;
75use zk_ee:: system:: {
@@ -17,7 +15,8 @@ use zk_ee::utils::Bytes32;
1715pub enum CallType {
1816 #[ default]
1917 Call ,
20- Constructor ,
18+ Create ,
19+ Create2 ,
2120 Delegate ,
2221 Static ,
2322 DelegateStatic ,
@@ -28,19 +27,22 @@ pub enum CallType {
2827 Selfdestruct ,
2928}
3029
31- impl From < CallModifier > for CallType {
32- fn from ( value : CallModifier ) -> Self {
30+ impl CallType {
31+ fn from ( value : CallModifier , is_create : & Option < CreateType > ) -> Self {
3332 // Note: in our implementation Selfdestruct isn't actually implemented as a "call". But in traces it should be treated like one
3433 match value {
35- CallModifier :: Constructor => CallType :: Constructor ,
3634 CallModifier :: NoModifier => CallType :: Call ,
3735 CallModifier :: Delegate => CallType :: Delegate ,
3836 CallModifier :: Static => CallType :: Static ,
3937 CallModifier :: DelegateStatic => CallType :: DelegateStatic ,
4038 CallModifier :: EVMCallcode => CallType :: EVMCallcode ,
4139 CallModifier :: EVMCallcodeStatic => CallType :: EVMCallcodeStatic ,
42- CallModifier :: ZKVMSystem => CallType :: ZKVMSystem , // Not used
43- CallModifier :: ZKVMSystemStatic => CallType :: ZKVMSystemStatic , // Not used
40+ CallModifier :: ZKVMSystem => CallType :: ZKVMSystem ,
41+ CallModifier :: ZKVMSystemStatic => CallType :: ZKVMSystemStatic ,
42+ CallModifier :: Constructor => match is_create. as_ref ( ) . expect ( "Should exist" ) {
43+ CreateType :: Create => CallType :: Create ,
44+ CreateType :: Create2 => CallType :: Create2 ,
45+ } ,
4446 }
4547 }
4648}
@@ -74,6 +76,12 @@ pub enum CallError {
7476 FatalError ( String ) , // Some fatal internal error outside of EVM specification (ZKsync OS specific)
7577}
7678
79+ #[ derive( Debug ) ]
80+ pub enum CreateType {
81+ Create ,
82+ Create2 ,
83+ }
84+
7785#[ derive( Default ) ]
7886pub struct CallTracer {
7987 pub transactions : Vec < Call > ,
@@ -82,6 +90,8 @@ pub struct CallTracer {
8290 pub current_call_depth : usize ,
8391 pub collect_logs : bool ,
8492 pub only_top_call : bool ,
93+
94+ create_operation_requested : Option < CreateType > ,
8595}
8696
8797impl CallTracer {
@@ -93,6 +103,7 @@ impl CallTracer {
93103 current_call_depth : 0 ,
94104 collect_logs,
95105 only_top_call,
106+ create_operation_requested : None ,
96107 }
97108 }
98109}
@@ -102,8 +113,21 @@ impl<S: EthereumLikeTypes> Tracer<S> for CallTracer {
102113 self . current_call_depth += 1 ;
103114
104115 if !self . only_top_call || self . current_call_depth == 1 {
116+ // Top-level deployment (initiated by EOA) won't trigger `on_create_request` hook
117+ // This is always a CREATE
118+ if self . current_call_depth == 1
119+ && initial_state. external_call . modifier == CallModifier :: Constructor
120+ {
121+ self . create_operation_requested = Some ( CreateType :: Create ) ;
122+ }
123+
124+ let call_type = CallType :: from (
125+ initial_state. external_call . modifier ,
126+ & self . create_operation_requested ,
127+ ) ;
128+
105129 self . unfinished_calls . push ( Call {
106- call_type : CallType :: from ( initial_state . external_call . modifier ) ,
130+ call_type,
107131 from : initial_state. external_call . caller ,
108132 to : initial_state. external_call . callee ,
109133 value : initial_state. external_call . nominal_token_value ,
@@ -117,6 +141,11 @@ impl<S: EthereumLikeTypes> Tracer<S> for CallTracer {
117141 logs : vec ! [ ] , // will be populated later
118142 } )
119143 }
144+
145+ // Reset flag, required data is consumed
146+ if self . create_operation_requested . is_some ( ) {
147+ self . create_operation_requested = None ;
148+ }
120149 }
121150
122151 fn after_execution_frame_completed ( & mut self , result : Option < ( & S :: Resources , & CallResult < S > ) > ) {
@@ -140,7 +169,14 @@ impl<S: EthereumLikeTypes> Tracer<S> for CallTracer {
140169 finished_call. output = return_values. returndata . to_vec ( ) ;
141170 }
142171 zk_ee:: system:: CallResult :: Successful { return_values } => {
143- finished_call. output = return_values. returndata . to_vec ( ) ;
172+ match finished_call. call_type {
173+ CallType :: Create | CallType :: Create2 => {
174+ // output should be already populated in `on_bytecode_change` hook
175+ }
176+ _ => {
177+ finished_call. output = return_values. returndata . to_vec ( ) ;
178+ }
179+ }
144180 }
145181 } ;
146182 }
@@ -153,23 +189,36 @@ impl<S: EthereumLikeTypes> Tracer<S> for CallTracer {
153189 }
154190 }
155191
156- finished_call. calls = mem:: take ( & mut self . finished_calls ) ;
157-
158- self . finished_calls . push ( finished_call) ;
192+ if let Some ( parent_call) = self . unfinished_calls . last_mut ( ) {
193+ parent_call. calls . push ( finished_call) ;
194+ } else {
195+ self . finished_calls . push ( finished_call) ;
196+ }
159197 }
160198
161199 self . current_call_depth -= 1 ;
200+
201+ // Reset flag in case if frame terminated due to out-of-native / other internal ZKsync OS error
202+ if self . create_operation_requested . is_some ( ) {
203+ self . create_operation_requested = None ;
204+ }
162205 }
163206
164207 fn begin_tx ( & mut self , _calldata : & [ u8 ] ) {
165208 self . current_call_depth = 0 ;
209+
210+ // Sanity check
211+ assert ! ( self . create_operation_requested. is_none( ) ) ;
166212 }
167213
168214 fn finish_tx ( & mut self ) {
169215 assert_eq ! ( self . current_call_depth, 0 ) ;
170216 assert ! ( self . unfinished_calls. is_empty( ) ) ;
171217 assert_eq ! ( self . finished_calls. len( ) , 1 ) ;
172218
219+ // Sanity check
220+ assert ! ( self . create_operation_requested. is_none( ) ) ;
221+
173222 self . transactions
174223 . push ( self . finished_calls . pop ( ) . expect ( "Should exist" ) ) ;
175224 }
@@ -196,7 +245,6 @@ impl<S: EthereumLikeTypes> Tracer<S> for CallTracer {
196245 ) {
197246 }
198247
199- #[ inline( always) ]
200248 fn on_event (
201249 & mut self ,
202250 _ee_type : zk_ee:: execution_environment_type:: ExecutionEnvironmentType ,
@@ -214,6 +262,35 @@ impl<S: EthereumLikeTypes> Tracer<S> for CallTracer {
214262 }
215263 }
216264
265+ /// Is called on a change of bytecode for some account.
266+ /// `new_bytecode` can be None if bytecode is unknown at the moment of change (e.g. force deploy by hash in system hook)
267+ fn on_bytecode_change (
268+ & mut self ,
269+ _ee_type : zk_ee:: execution_environment_type:: ExecutionEnvironmentType ,
270+ address : <S :: IOTypes as SystemIOTypesConfig >:: Address ,
271+ new_bytecode : Option < & [ u8 ] > ,
272+ _new_bytecode_hash : <S :: IOTypes as SystemIOTypesConfig >:: BytecodeHashValue ,
273+ new_observable_bytecode_length : u32 ,
274+ ) {
275+ let call = self . unfinished_calls . last_mut ( ) . expect ( "Should exist" ) ;
276+
277+ match call. call_type {
278+ CallType :: Create | CallType :: Create2 => {
279+ assert_eq ! ( address, call. to) ;
280+ let deployed_raw_bytecode = new_bytecode. expect ( "Should be present" ) ;
281+
282+ assert ! ( deployed_raw_bytecode. len( ) >= new_observable_bytecode_length as usize ) ;
283+
284+ // raw bytecode may include internal artifacts (jumptable), so we need to trim it
285+ call. output =
286+ deployed_raw_bytecode[ ..new_observable_bytecode_length as usize ] . to_vec ( ) ;
287+ }
288+ _ => {
289+ // should not happen now (system hooks currently do not trigger this hook)
290+ }
291+ }
292+ }
293+
217294 #[ inline( always) ]
218295 fn evm_tracer ( & mut self ) -> & mut impl EvmTracer < S > {
219296 self
@@ -242,6 +319,11 @@ impl<S: EthereumLikeTypes> EvmTracer<S> for CallTracer {
242319 let current_call = self . unfinished_calls . last_mut ( ) . expect ( "Should exist" ) ;
243320 current_call. error = Some ( CallError :: EvmError ( error. clone ( ) ) ) ;
244321 current_call. reverted = true ;
322+
323+ // In case we fail after `on_create_request` hook, but before `on_new_execution_frame` hook
324+ if self . create_operation_requested . is_some ( ) {
325+ self . create_operation_requested = None ;
326+ }
245327 }
246328
247329 /// Special cases, when error happens in frame before any opcode is executed (unfortunately we can't provide access to state)
@@ -250,6 +332,8 @@ impl<S: EthereumLikeTypes> EvmTracer<S> for CallTracer {
250332 let current_call = self . unfinished_calls . last_mut ( ) . expect ( "Should exist" ) ;
251333 current_call. error = Some ( CallError :: EvmError ( error. clone ( ) ) ) ;
252334 current_call. reverted = true ;
335+
336+ assert ! ( self . create_operation_requested. is_none( ) ) ;
253337 }
254338
255339 /// We should treat selfdestruct as a special kind of a call
@@ -260,7 +344,7 @@ impl<S: EthereumLikeTypes> EvmTracer<S> for CallTracer {
260344 frame_state : & impl EvmFrameInterface < S > ,
261345 ) {
262346 // Following Geth implementation: https://github.com/ethereum/go-ethereum/blob/2dbb580f51b61d7ff78fceb44b06835827704110/core/vm/instructions.go#L894
263- self . finished_calls . push ( Call {
347+ let call_frame = Call {
264348 call_type : CallType :: Selfdestruct ,
265349 from : frame_state. address ( ) ,
266350 to : beneficiary,
@@ -273,6 +357,27 @@ impl<S: EthereumLikeTypes> EvmTracer<S> for CallTracer {
273357 reverted : false ,
274358 calls : vec ! [ ] ,
275359 logs : vec ! [ ] ,
276- } )
360+ } ;
361+
362+ if let Some ( parent_call) = self . unfinished_calls . last_mut ( ) {
363+ parent_call. calls . push ( call_frame) ;
364+ } else {
365+ self . finished_calls . push ( call_frame) ;
366+ }
367+ }
368+
369+ /// Called on CREATE/CREATE2 system request.
370+ /// Hook is called *before* new execution frame is created.
371+ /// Note: CREATE/CREATE2 opcode execution can fail after this hook (and call on_opcode_error correspondingly)
372+ /// Note: top-level deployment won't trigger this hook
373+ fn on_create_request ( & mut self , is_create2 : bool ) {
374+ // Can't be some - `on_new_execution_frame` or `on_opcode_error` should reset flag
375+ assert ! ( self . create_operation_requested. is_none( ) ) ;
376+
377+ self . create_operation_requested = if is_create2 {
378+ Some ( CreateType :: Create )
379+ } else {
380+ Some ( CreateType :: Create2 )
381+ } ;
277382 }
278383}
0 commit comments