Skip to content

Commit ffc5558

Browse files
authored
new(tests): EOF validation CALLF stack overflowt (#1329)
1 parent 36c5f13 commit ffc5558

File tree

3 files changed

+367
-10
lines changed

3 files changed

+367
-10
lines changed

converted-ethereum-tests.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ EOFTests/efStack/backwards_rjumpi_.json
2222
EOFTests/efStack/backwards_rjumpi_variable_stack_.json
2323
EOFTests/efStack/backwards_rjumpv_.json
2424
EOFTests/efStack/backwards_rjumpv_variable_stack_.json
25+
EOFTests/efStack_callf_stack_overflow_.json
26+
EOFTests/efStack_callf_stack_overflow_variable_stack_.json
27+
EOFTests/efStack_callf_stack_validation_.json
28+
EOFTests/efStack_callf_with_inputs_stack_overflow_.json
29+
EOFTests/efStack_callf_with_inputs_stack_overflow_variable_stack_.json
2530
EOFTests/efStack/jumpf_stack_overflow_.json
2631
EOFTests/efStack/jumpf_stack_overflow_variable_stack_.json
2732
EOFTests/efStack/jumpf_to_nonreturning_.json

tests/osaka/eip7692_eof_v1/eip4750_functions/test_code_validation.py

Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,356 @@ def test_callf_stack_height_limit_exceeded(eof_test, callee_outputs):
669669
eof_test(container=container, expect_exception=EOFException.MAX_STACK_HEIGHT_ABOVE_LIMIT)
670670

671671

672+
@pytest.mark.parametrize("stack_height", [512, 513, 1023])
673+
def test_callf_stack_overflow(eof_test: EOFTestFiller, stack_height: int):
674+
"""Test CALLF instruction recursively calling itself causing stack overflow."""
675+
container = Container(
676+
sections=[
677+
Section.Code(code=Op.CALLF[1] + Op.STOP),
678+
Section.Code(
679+
code=Op.PUSH1[1] * stack_height + Op.CALLF[1] + Op.POP * stack_height + Op.RETF,
680+
code_outputs=0,
681+
max_stack_height=stack_height,
682+
),
683+
],
684+
)
685+
stack_overflow = stack_height > MAX_RUNTIME_OPERAND_STACK_HEIGHT // 2
686+
eof_test(
687+
container=container,
688+
expect_exception=EOFException.STACK_OVERFLOW if stack_overflow else None,
689+
)
690+
691+
692+
@pytest.mark.parametrize("stack_height", [1, 2])
693+
def test_callf_stack_overflow_after_callf(eof_test: EOFTestFiller, stack_height: int):
694+
"""Test CALLF instruction calling next function causing stack overflow at validation time."""
695+
container = Container(
696+
sections=[
697+
Section.Code(code=Op.CALLF[1] + Op.STOP),
698+
Section.Code(
699+
code=Op.PUSH1[1] * 1023 + Op.CALLF[2] + Op.POP * 1023 + Op.RETF,
700+
code_outputs=0,
701+
max_stack_height=1023,
702+
),
703+
Section.Code(
704+
code=Op.PUSH0 * stack_height + Op.POP * stack_height + Op.RETF,
705+
code_outputs=0,
706+
max_stack_height=stack_height,
707+
),
708+
],
709+
)
710+
stack_overflow = 1023 + stack_height > MAX_RUNTIME_OPERAND_STACK_HEIGHT
711+
eof_test(
712+
container=container,
713+
expect_exception=EOFException.STACK_OVERFLOW if stack_overflow else None,
714+
)
715+
716+
717+
@pytest.mark.parametrize("stack_height", [512, 514, 515])
718+
def test_callf_stack_overflow_variable_stack(eof_test: EOFTestFiller, stack_height: int):
719+
"""Test CALLF instruction causing stack overflow."""
720+
container = Container(
721+
sections=[
722+
Section.Code(
723+
code=Op.RJUMPI[2](0)
724+
+ Op.PUSH0 * (MAX_RUNTIME_OPERAND_STACK_HEIGHT // 2)
725+
+ Op.CALLF[1]
726+
+ Op.STOP,
727+
max_stack_height=512,
728+
),
729+
Section.Code(
730+
code=Op.PUSH1[1] * stack_height + Op.POP * stack_height + Op.RETF,
731+
code_outputs=0,
732+
max_stack_height=stack_height,
733+
),
734+
],
735+
)
736+
stack_overflow = stack_height > MAX_RUNTIME_OPERAND_STACK_HEIGHT // 2
737+
eof_test(
738+
container=container,
739+
expect_exception=EOFException.STACK_OVERFLOW if stack_overflow else None,
740+
)
741+
742+
743+
@pytest.mark.parametrize("stack_height", [509, 510, 512])
744+
def test_callf_stack_overflow_variable_stack_2(eof_test: EOFTestFiller, stack_height: int):
745+
"""Test CALLF instruction causing stack overflow."""
746+
container = Container(
747+
sections=[
748+
Section.Code(
749+
code=Op.PUSH0 * 2
750+
+ Op.RJUMPI[2](0)
751+
+ Op.POP * 2
752+
+ Op.PUSH0 * (MAX_RUNTIME_OPERAND_STACK_HEIGHT // 2)
753+
+ Op.CALLF[1]
754+
+ Op.STOP,
755+
max_stack_height=514,
756+
),
757+
Section.Code(
758+
code=Op.PUSH1[1] * stack_height + Op.POP * stack_height + Op.RETF,
759+
code_outputs=0,
760+
max_stack_height=stack_height,
761+
),
762+
],
763+
)
764+
stack_overflow = stack_height > (MAX_RUNTIME_OPERAND_STACK_HEIGHT // 2) - 2
765+
eof_test(
766+
container=container,
767+
expect_exception=EOFException.STACK_OVERFLOW if stack_overflow else None,
768+
)
769+
770+
771+
@pytest.mark.parametrize("stack_height", [1, 2, 5])
772+
def test_callf_stack_overflow_variable_stack_3(eof_test: EOFTestFiller, stack_height: int):
773+
"""Test CALLF instruction causing stack overflow."""
774+
container = Container(
775+
sections=[
776+
Section.Code(
777+
code=Op.RJUMPI[2](0)
778+
+ Op.PUSH0 * (MAX_RUNTIME_OPERAND_STACK_HEIGHT - 1)
779+
+ Op.CALLF[1]
780+
+ Op.STOP,
781+
max_stack_height=1023,
782+
),
783+
Section.Code(
784+
code=Op.PUSH0 * stack_height + Op.POP * stack_height + Op.RETF,
785+
code_outputs=0,
786+
max_stack_height=stack_height,
787+
),
788+
],
789+
)
790+
stack_overflow = (
791+
container.sections[0].max_stack_height + stack_height > MAX_RUNTIME_OPERAND_STACK_HEIGHT
792+
)
793+
eof_test(
794+
container=container,
795+
expect_exception=EOFException.STACK_OVERFLOW if stack_overflow else None,
796+
)
797+
798+
799+
def test_callf_stack_overflow_variable_stack_4(eof_test: EOFTestFiller):
800+
"""Test reaching stack overflow before CALLF instruction."""
801+
container = Container(
802+
sections=[
803+
Section.Code(
804+
code=Op.PUSH0 * 2
805+
+ Op.RJUMPI[2](0)
806+
+ Op.POP * 2
807+
+ Op.PUSH0 * (MAX_RUNTIME_OPERAND_STACK_HEIGHT - 1)
808+
+ Op.CALLF[1]
809+
+ Op.STOP,
810+
max_stack_height=1023,
811+
),
812+
Section.Code(
813+
code=Op.RETF,
814+
code_outputs=0,
815+
max_stack_height=0,
816+
),
817+
],
818+
)
819+
eof_test(
820+
container=container,
821+
expect_exception=EOFException.STACK_OVERFLOW,
822+
)
823+
824+
825+
@pytest.mark.parametrize("stack_height", [2, 3])
826+
def test_callf_validate_outputs(eof_test: EOFTestFiller, stack_height: int):
827+
"""Test CALLF instruction when calling a function returning more outputs than expected."""
828+
container = Container(
829+
sections=[
830+
Section.Code(code=Op.CALLF[1] + Op.STOP, max_stack_height=1),
831+
Section.Code(
832+
code=Op.PUSH0 * stack_height + Op.CALLF[2] + Op.RETF,
833+
code_outputs=1,
834+
max_stack_height=stack_height,
835+
),
836+
Section.Code(
837+
code=Op.POP + Op.RETF,
838+
code_inputs=2,
839+
code_outputs=1,
840+
max_stack_height=2,
841+
),
842+
],
843+
)
844+
# Only 1 item consumed by function 2, if stack height > 2
845+
# there will be more than 1 item as outputs in function 1
846+
outputs_error = stack_height > 2
847+
eof_test(
848+
container=container,
849+
expect_exception=EOFException.STACK_HIGHER_THAN_OUTPUTS if outputs_error else None,
850+
)
851+
852+
853+
@pytest.mark.parametrize("push_stack", [1023, 1024])
854+
@pytest.mark.parametrize("pop_stack", [1019, 1020, 1021])
855+
@pytest.mark.parametrize(
856+
"code_section",
857+
[
858+
Section.Code(
859+
code=Op.POP * 2 + Op.RETF,
860+
code_inputs=2,
861+
code_outputs=0,
862+
max_stack_height=2,
863+
),
864+
Section.Code(
865+
code=Op.PUSH1[1] + Op.POP + Op.RETF,
866+
code_inputs=3,
867+
code_outputs=3,
868+
max_stack_height=4,
869+
),
870+
Section.Code(
871+
code=Op.PUSH0 * 2 + Op.RETF,
872+
code_inputs=3,
873+
code_outputs=5,
874+
max_stack_height=5,
875+
),
876+
Section.Code(
877+
code=Op.PUSH0 * 2 + Op.POP * 2 + Op.RETF,
878+
code_inputs=3,
879+
code_outputs=3,
880+
max_stack_height=5,
881+
),
882+
Section.Code(
883+
code=Op.PUSH0 + Op.POP * 3 + Op.RETF,
884+
code_inputs=2,
885+
code_outputs=0,
886+
max_stack_height=3,
887+
),
888+
Section.Code(
889+
code=Op.PUSH0 * 2 + Op.POP * 4 + Op.RETF,
890+
code_inputs=2,
891+
code_outputs=0,
892+
max_stack_height=4,
893+
),
894+
],
895+
)
896+
def test_callf_with_inputs_stack_overflow(
897+
eof_test: EOFTestFiller, code_section: Section, push_stack: int, pop_stack: int
898+
):
899+
"""Test CALLF to code section with inputs."""
900+
container = Container(
901+
name="callf_with_inputs_stack_overflow_0",
902+
sections=[
903+
Section.Code(
904+
code=Op.PUSH1[1] * push_stack + Op.CALLF[1] + Op.POP * pop_stack + Op.RETURN,
905+
max_stack_height=1023,
906+
),
907+
code_section,
908+
],
909+
)
910+
exception = None
911+
if (
912+
push_stack + code_section.max_stack_height - code_section.code_inputs
913+
> MAX_RUNTIME_OPERAND_STACK_HEIGHT
914+
):
915+
exception = EOFException.STACK_OVERFLOW
916+
elif push_stack - code_section.code_inputs + code_section.code_outputs - pop_stack < 2:
917+
exception = EOFException.STACK_UNDERFLOW
918+
elif push_stack != container.sections[0].max_stack_height:
919+
exception = EOFException.INVALID_MAX_STACK_HEIGHT
920+
921+
eof_test(container=container, expect_exception=exception)
922+
923+
924+
@pytest.mark.parametrize(
925+
"code_section",
926+
[
927+
Section.Code(
928+
code=Op.POP * 2 + Op.RETF,
929+
code_inputs=2,
930+
code_outputs=0,
931+
max_stack_height=2,
932+
),
933+
Section.Code(
934+
code=Op.PUSH1[1] + Op.POP + Op.RETF,
935+
code_inputs=3,
936+
code_outputs=3,
937+
max_stack_height=4,
938+
),
939+
Section.Code(
940+
code=Op.PUSH0 * 4 + Op.RETF,
941+
code_inputs=3,
942+
code_outputs=7,
943+
max_stack_height=7,
944+
),
945+
Section.Code(
946+
code=Op.PUSH0 * 2 + Op.RETF,
947+
code_inputs=3,
948+
code_outputs=5,
949+
max_stack_height=5,
950+
),
951+
Section.Code(
952+
code=Op.PUSH0 * 4 + Op.POP * 2 + Op.RETF,
953+
code_inputs=3,
954+
code_outputs=3,
955+
max_stack_height=7,
956+
),
957+
Section.Code(
958+
code=Op.PUSH0 * 2 + Op.POP * 2 + Op.RETF,
959+
code_inputs=3,
960+
code_outputs=3,
961+
max_stack_height=5,
962+
),
963+
Section.Code(
964+
code=Op.PUSH0 * 3 + Op.POP * 5 + Op.RETF,
965+
code_inputs=2,
966+
code_outputs=0,
967+
max_stack_height=5,
968+
),
969+
Section.Code(
970+
code=Op.PUSH0 + Op.POP * 3 + Op.RETF,
971+
code_inputs=2,
972+
code_outputs=0,
973+
max_stack_height=3,
974+
),
975+
Section.Code(
976+
code=Op.PUSH0 * 4 + Op.POP * 6 + Op.RETF,
977+
code_inputs=2,
978+
code_outputs=0,
979+
max_stack_height=6,
980+
),
981+
Section.Code(
982+
code=Op.PUSH0 * 2 + Op.POP * 4 + Op.RETF,
983+
code_inputs=2,
984+
code_outputs=0,
985+
max_stack_height=4,
986+
),
987+
],
988+
)
989+
@pytest.mark.parametrize("push_stack", [1020, 1021])
990+
def test_callf_with_inputs_stack_overflow_variable_stack(
991+
eof_test: EOFTestFiller, code_section: Section, push_stack: int
992+
):
993+
"""Test CALLF to code section with inputs (variable stack)."""
994+
container = Container(
995+
sections=[
996+
Section.Code(
997+
code=Op.PUSH0
998+
+ Op.PUSH1[0]
999+
+ Op.RJUMPI[2]
1000+
+ Op.PUSH0 * 2
1001+
+ Op.PUSH1[1] * push_stack
1002+
+ Op.CALLF[1]
1003+
+ Op.STOP,
1004+
max_stack_height=1023,
1005+
),
1006+
code_section,
1007+
],
1008+
)
1009+
initial_stack = 3 # Initial items in the scak
1010+
exception = None
1011+
if (
1012+
push_stack + initial_stack + code_section.max_stack_height - code_section.code_inputs
1013+
> MAX_RUNTIME_OPERAND_STACK_HEIGHT
1014+
):
1015+
exception = EOFException.STACK_OVERFLOW
1016+
elif push_stack + initial_stack > 1023:
1017+
exception = EOFException.INVALID_MAX_STACK_HEIGHT
1018+
1019+
eof_test(container=container, expect_exception=exception)
1020+
1021+
6721022
@pytest.mark.parametrize("callee_outputs", [1, 2, MAX_CODE_OUTPUTS - 1, MAX_CODE_OUTPUTS])
6731023
@pytest.mark.parametrize(
6741024
"max_stack_height", [0, 1, MAX_OPERAND_STACK_HEIGHT - 1, MAX_OPERAND_STACK_HEIGHT]

0 commit comments

Comments
 (0)