@@ -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