1212from ethereum_test_tools import EOFTestFiller
1313from ethereum_test_tools .eof .v1 import Container , Section
1414from ethereum_test_tools .vm .opcode import Opcodes as Op
15+ from ethereum_test_types .eof .v1 .constants import NON_RETURNING_SECTION
1516from ethereum_test_vm .bytecode import Bytecode
1617
1718from .. import EOF_FORK_NAME
@@ -40,6 +41,7 @@ class RjumpKind(Enum):
4041 RJUMPI_TO_START = auto ()
4142 RJUMPV_EMPTY_AND_OVER_NEXT = auto ()
4243 RJUMPV_OVER_PUSH_AND_TO_START = auto ()
44+ RJUMPI_OVER_RETF = auto ()
4345
4446 def __str__ (self ) -> str :
4547 """
@@ -119,6 +121,8 @@ def rjump_code_with(
119121 body = Op .RJUMPV [[1 , - code_so_far_len - rjumpv_two_destinations_len ]](0 ) + Op .PUSH0
120122 is_backwards = True
121123 pushes = True
124+ elif rjump_kind == RjumpKind .RJUMPI_OVER_RETF :
125+ body = Op .RJUMPI [1 ](0 ) + Op .RETF
122126 elif not rjump_kind :
123127 pass
124128 else :
@@ -172,7 +176,8 @@ def section_code_with(
172176 Also returns some traits of the section: `has_invalid_back_jump`, `rjump_snippet_pops`,
173177 `rjump_snippet_pushes`, `rjump_falls_off_code`
174178 """
175- code = Bytecode (min_stack_height = inputs , max_stack_height = inputs )
179+ code = Bytecode ()
180+ code .pushed_stack_items , code .max_stack_height = (inputs , inputs )
176181
177182 if call :
178183 body = call_code_with (inputs , outputs , call )
@@ -188,6 +193,11 @@ def section_code_with(
188193 rjump , is_backwards , rjump_snippet_pops , rjump_snippet_pushes = rjump_code_with (
189194 rjump_kind , 0 , body
190195 )
196+ if rjump_kind == RjumpKind .RJUMPI_OVER_RETF :
197+ if inputs > outputs :
198+ rjump_snippet_pushes = True
199+ elif outputs > inputs :
200+ rjump_snippet_pops = True
191201 code += rjump
192202
193203 code += body
@@ -200,11 +210,16 @@ def section_code_with(
200210
201211 if is_backwards and inputs != outputs :
202212 has_invalid_back_jump = True
213+
214+ if rjump_spot == RjumpSpot .BEFORE_TERMINATION or (
215+ rjump_spot == RjumpSpot .BEGINNING and len (termination ) == 0
216+ ):
203217 if rjump_kind in [
204218 RjumpKind .RJUMPI_OVER_NEXT ,
205219 RjumpKind .RJUMPI_OVER_NEXT_NESTED ,
206220 RjumpKind .RJUMPV_EMPTY_AND_OVER_NEXT ,
207221 ]:
222+ # Jump over termination or jump over body, but there is nothing after the body.
208223 rjump_falls_off_code = True
209224
210225 code += termination
@@ -231,15 +246,15 @@ def section_code_with(
231246)
232247@pytest .mark .parametrize (
233248 "rjump_kind" ,
234- RjumpKind . __members__ . values () ,
249+ RjumpKind ,
235250)
236251# Parameter value fixed for first iteration, to cover the most important case.
237252@pytest .mark .parametrize ("rjump_section_idx" , [0 , 1 ])
238253@pytest .mark .parametrize (
239254 "rjump_spot" ,
240- RjumpSpot . __members__ . values () ,
255+ RjumpSpot ,
241256)
242- def test_eof_validity (
257+ def test_rjumps_callf_retf (
243258 eof_test : EOFTestFiller ,
244259 inputs : Tuple [int , ...],
245260 outputs : Tuple [int , ...],
@@ -264,6 +279,10 @@ def test_eof_validity(
264279 container_has_rjump_pops = False
265280 container_has_rjump_pushes = False
266281 container_has_rjump_off_code = False
282+ container_has_section_0_retf = (
283+ rjump_section_idx == 0 and rjump_kind == RjumpKind .RJUMPI_OVER_RETF
284+ )
285+
267286 for section_idx in range (num_sections ):
268287 if section_idx == 0 :
269288 call = Op .CALLF [section_idx + 1 ]
@@ -298,15 +317,15 @@ def test_eof_validity(
298317 termination ,
299318 )
300319
301- container_has_invalid_back_jump = (
302- container_has_invalid_back_jump or section_has_invalid_back_jump
303- )
304- container_has_rjump_pops = container_has_rjump_pops or rjump_snippet_pops
320+ if section_has_invalid_back_jump :
321+ container_has_invalid_back_jump = True
322+ if rjump_snippet_pops :
323+ container_has_rjump_pops = True
305324 # Pushes to the stack never affect the zeroth section, because it `STOP`s and not `RETF`s.
306- container_has_rjump_pushes = container_has_rjump_pushes or (
307- rjump_snippet_pushes and section_idx != 0
308- )
309- container_has_rjump_off_code = container_has_rjump_off_code or rjump_falls_off_code
325+ if rjump_snippet_pushes and section_idx != 0 :
326+ container_has_rjump_pushes = True
327+ if rjump_falls_off_code :
328+ container_has_rjump_off_code = True
310329
311330 if section_idx > 0 :
312331 sections .append (
@@ -328,7 +347,99 @@ def test_eof_validity(
328347 possible_exceptions .append (EOFException .STACK_HIGHER_THAN_OUTPUTS )
329348 if container_has_rjump_off_code :
330349 possible_exceptions .append (EOFException .INVALID_RJUMP_DESTINATION )
350+ if container_has_section_0_retf :
351+ possible_exceptions .append (EOFException .INVALID_NON_RETURNING_FLAG )
331352
332- eof_test (
333- data = bytes (Container (sections = sections )), expect_exception = possible_exceptions or None
334- )
353+ eof_test (data = Container (sections = sections ), expect_exception = possible_exceptions or None )
354+
355+
356+ @pytest .mark .parametrize (
357+ "inputs" , itertools .product (* ([possible_inputs_outputs ] * (num_sections - 1 )))
358+ )
359+ @pytest .mark .parametrize (
360+ "rjump_kind" ,
361+ RjumpKind ,
362+ )
363+ # Parameter value fixed for first iteration, to cover the most important case.
364+ @pytest .mark .parametrize ("rjump_section_idx" , [0 , 1 ])
365+ @pytest .mark .parametrize (
366+ "rjump_spot" ,
367+ # `termination` is empty for JUMPF codes, because JUMPF serves as one. Spot
368+ # `BEFORE_TERMINATION` is unreachable code.
369+ [k for k in RjumpSpot if k not in [RjumpSpot .BEFORE_TERMINATION ]],
370+ )
371+ def test_rjumps_jumpf_nonreturning (
372+ eof_test : EOFTestFiller ,
373+ inputs : Tuple [int , ...],
374+ rjump_kind : RjumpKind ,
375+ rjump_section_idx : int ,
376+ rjump_spot : RjumpSpot ,
377+ ):
378+ """
379+ Test EOF container validaiton for EIP-4200 vs EIP-6206 interactions on non-returning
380+ functions.
381+ """
382+ # Zeroth section has always 0 inputs and 0 outputs, so is excluded from param
383+ inputs = (0 ,) + inputs
384+
385+ sections = []
386+ container_has_rjump_pops = False
387+ container_has_rjump_off_code = False
388+ container_has_non_returning_retf = False
389+
390+ for section_idx in range (num_sections ):
391+ if section_idx < num_sections - 1 :
392+ call = Op .JUMPF [section_idx + 1 ]
393+ call .popped_stack_items = inputs [section_idx + 1 ]
394+ call .pushed_stack_items = 0
395+ call .min_stack_height = call .popped_stack_items
396+ call .max_stack_height = max (call .popped_stack_items , call .pushed_stack_items )
397+ termination = Bytecode ()
398+ else :
399+ call = None
400+ termination = Op .STOP
401+
402+ # `section_has_invalid_back_jump` - never happens: we excluded RJUMP from the end
403+ # `rjump_snippet_pushes` - never happens: we never RETF where too large stack would fail
404+ (
405+ code ,
406+ _section_has_invalid_back_jump ,
407+ rjump_snippet_pops ,
408+ _rjump_snippet_pushes ,
409+ rjump_falls_off_code ,
410+ ) = section_code_with (
411+ inputs [section_idx ],
412+ 0 ,
413+ rjump_kind if rjump_section_idx == section_idx else None ,
414+ rjump_spot ,
415+ call ,
416+ termination ,
417+ )
418+
419+ if rjump_snippet_pops :
420+ container_has_rjump_pops = True
421+ if rjump_falls_off_code :
422+ container_has_rjump_off_code = True
423+ if rjump_kind == RjumpKind .RJUMPI_OVER_RETF :
424+ container_has_non_returning_retf = True
425+
426+ if section_idx > 0 :
427+ sections .append (
428+ Section .Code (
429+ code ,
430+ code_inputs = inputs [section_idx ],
431+ code_outputs = NON_RETURNING_SECTION ,
432+ )
433+ )
434+ else :
435+ sections .append (Section .Code (code ))
436+
437+ possible_exceptions = []
438+ if container_has_rjump_pops :
439+ possible_exceptions .append (EOFException .STACK_UNDERFLOW )
440+ if container_has_rjump_off_code :
441+ possible_exceptions .append (EOFException .INVALID_RJUMP_DESTINATION )
442+ if container_has_non_returning_retf :
443+ possible_exceptions .append (EOFException .INVALID_NON_RETURNING_FLAG )
444+
445+ eof_test (data = Container (sections = sections ), expect_exception = possible_exceptions or None )
0 commit comments