From 5a113a216c08bae5b3f39dca03476199a10e28e5 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Tue, 20 Feb 2024 17:58:28 +0100 Subject: [PATCH 01/10] eof: Stack validation with sequential pass without worklist --- lib/evmone/eof.cpp | 32 ++++++++++---------------- test/unittests/eof_validation_test.cpp | 2 +- test/unittests/evm_eof_rjump_test.cpp | 19 +++++++-------- 3 files changed, 23 insertions(+), 30 deletions(-) diff --git a/lib/evmone/eof.cpp b/lib/evmone/eof.cpp index 75f1cdc539..db7645572a 100644 --- a/lib/evmone/eof.cpp +++ b/lib/evmone/eof.cpp @@ -360,21 +360,16 @@ std::variant validate_max_stack_height( std::vector stack_heights(code.size(), LOC_UNVISITED); stack_heights[0] = code_types[func_index].inputs; - std::stack worklist; - worklist.push(0); - - while (!worklist.empty()) + for (size_t i = 0; i < code.size();) { - const auto i = worklist.top(); - worklist.pop(); - const auto opcode = static_cast(code[i]); int stack_height_required = instr::traits[opcode].stack_height_required; auto stack_height_change = instr::traits[opcode].stack_height_change; - auto stack_height = stack_heights[i]; - assert(stack_height != LOC_UNVISITED); + const auto stack_height = stack_heights[i]; + if (stack_height == LOC_UNVISITED) + return EOFValidationError::unreachable_instructions; if (opcode == OP_CALLF) { @@ -428,7 +423,7 @@ std::variant validate_max_stack_height( if (stack_height < stack_height_required) return EOFValidationError::stack_underflow; - stack_height += stack_height_change; + const auto next_stack_height = stack_height + stack_height_change; // Determine size of immediate, including the special case of RJUMPV. const size_t imm_size = (opcode == OP_RJUMPV) ? @@ -439,13 +434,12 @@ std::variant validate_max_stack_height( std::fill_n(&stack_heights[i + 1], imm_size, LOC_IMMEDIATE); // Validates the successor instruction and updates its stack height. - const auto validate_successor = [&stack_heights, &worklist](size_t successor_offset, - int32_t expected_stack_height) { + const auto visit_successor = [&stack_heights]( + size_t successor_offset, int32_t expected_stack_height) { auto& successor_stack_height = stack_heights[successor_offset]; if (successor_stack_height == LOC_UNVISITED) { successor_stack_height = expected_stack_height; - worklist.push(successor_offset); return true; } else @@ -459,7 +453,7 @@ std::variant validate_max_stack_height( { if (next >= code.size()) return EOFValidationError::no_terminating_instruction; - if (!validate_successor(next, stack_height)) + if (!visit_successor(next, next_stack_height)) return EOFValidationError::stack_height_mismatch; } @@ -468,7 +462,7 @@ std::variant validate_max_stack_height( { const auto target_rel_offset = read_int16_be(&code[i + 1]); const auto target = static_cast(i) + target_rel_offset + 3; - if (!validate_successor(static_cast(target), stack_height)) + if (!visit_successor(static_cast(target), next_stack_height)) return EOFValidationError::stack_height_mismatch; } else if (opcode == OP_RJUMPV) @@ -480,17 +474,15 @@ std::variant validate_max_stack_height( { const auto target_rel_offset = read_int16_be(&code[i + k * REL_OFFSET_SIZE + 2]); const auto target = static_cast(next) + target_rel_offset; - if (!validate_successor(static_cast(target), stack_height)) + if (!visit_successor(static_cast(target), next_stack_height)) return EOFValidationError::stack_height_mismatch; } } + + i = next; } const auto max_stack_height = *std::max_element(stack_heights.begin(), stack_heights.end()); - - if (std::find(stack_heights.begin(), stack_heights.end(), LOC_UNVISITED) != stack_heights.end()) - return EOFValidationError::unreachable_instructions; - return max_stack_height; } diff --git a/test/unittests/eof_validation_test.cpp b/test/unittests/eof_validation_test.cpp index afa917f216..04e8ab1dc8 100644 --- a/test/unittests/eof_validation_test.cpp +++ b/test/unittests/eof_validation_test.cpp @@ -368,7 +368,7 @@ TEST_F(eof_validation, EOF1_valid_rjump) EOFValidationError::success, "offset_zero"); // offset = 3 - add_test_case("EF0001 010004 0200010009 040000 00 00800001 E00003600100E0FFFA", + add_test_case("EF0001 010004 020001000D 040000 00 00800002 5FE100055F5FE000035F600100", EOFValidationError::success, "offset_positive"); // offset = -4 diff --git a/test/unittests/evm_eof_rjump_test.cpp b/test/unittests/evm_eof_rjump_test.cpp index 15fdeb260e..7b142fcb38 100644 --- a/test/unittests/evm_eof_rjump_test.cpp +++ b/test/unittests/evm_eof_rjump_test.cpp @@ -38,14 +38,14 @@ TEST_P(evm, eof1_rjump_backward) return; rev = EVMC_PRAGUE; - auto code = eof_bytecode(rjump(10) + mstore8(0, 1) + ret(0, 1) + rjump(-13), 2); + auto code = eof_bytecode(rjumpi(10, 1) + mstore8(0, 1) + ret(0, 1) + rjump(-13), 2); execute(code); EXPECT_STATUS(EVMC_SUCCESS); ASSERT_EQ(result.output_size, 1); EXPECT_EQ(result.output_data[0], 1); - code = eof_bytecode(rjump(10) + mstore8(0, 1) + ret(0, 1) + rjump(-13), 2).data("deadbeef"); + code = eof_bytecode(rjumpi(10, 1) + mstore8(0, 1) + ret(0, 1) + rjump(-13), 2).data("deadbeef"); execute(code); EXPECT_STATUS(EVMC_SUCCESS); @@ -98,8 +98,8 @@ TEST_P(evm, eof1_rjumpi_backwards) return; rev = EVMC_PRAGUE; - auto code = eof_bytecode(rjump(10) + mstore8(0, 1) + ret(0, 1) + rjumpi(-16, calldataload(0)) + - mstore8(0, 2) + ret(0, 1), + auto code = eof_bytecode(rjumpi(10, 1) + mstore8(0, 1) + ret(0, 1) + + rjumpi(-16, calldataload(0)) + mstore8(0, 2) + ret(0, 1), 2); // RJUMPI condition is true @@ -163,11 +163,12 @@ TEST_P(evm, eof1_rjumpv_multiple_offsets) return; rev = EVMC_PRAGUE; - const auto code = eof_bytecode( - rjump(12) + 10 + 0 + 0 + OP_DATACOPY + ret(0, 10) + rjumpv({12, -23, 0}, calldataload(0)) + - 10 + 10 + 0 + OP_DATACOPY + ret(0, 10) + 20 + 0 + 0 + OP_DATACOPY + ret(0, 20), - 3) - .data("ef000101000402000100010300000000000000fe"); + const auto code = + eof_bytecode(rjumpi(12, 1) + 10 + 0 + 0 + OP_DATACOPY + ret(0, 10) + + rjumpv({12, -23, 0}, calldataload(0)) + 10 + 10 + 0 + OP_DATACOPY + + ret(0, 10) + 20 + 0 + 0 + OP_DATACOPY + ret(0, 20), + 3) + .data("ef000101000402000100010300000000000000fe"); execute(code, bytes(31, 0) + "01"_hex); EXPECT_STATUS(EVMC_SUCCESS); From 2ac05ac5845e55d815809c90314ebaef96bc3fb3 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Tue, 20 Feb 2024 21:17:20 +0100 Subject: [PATCH 02/10] eof: Loosen constant stack height requirement --- lib/evmone/eof.cpp | 86 +++++++++++++++++--------- test/unittests/eof_validation_test.cpp | 3 +- 2 files changed, 59 insertions(+), 30 deletions(-) diff --git a/lib/evmone/eof.cpp b/lib/evmone/eof.cpp index db7645572a..3d4990a3d5 100644 --- a/lib/evmone/eof.cpp +++ b/lib/evmone/eof.cpp @@ -349,16 +349,23 @@ bool validate_rjump_destinations(bytes_view code) noexcept std::variant validate_max_stack_height( bytes_view code, size_t func_index, const std::vector& code_types) { - assert(!code.empty()); - - // Special values used for detecting errors. + // Special value used for detecting errors. static constexpr int32_t LOC_UNVISITED = -1; // Unvisited byte. - static constexpr int32_t LOC_IMMEDIATE = -2; // Immediate byte. // Stack height in the header is limited to uint16_t, // but keeping larger size for ease of calculation. - std::vector stack_heights(code.size(), LOC_UNVISITED); - stack_heights[0] = code_types[func_index].inputs; + struct StackHeightRange + { + int32_t min = LOC_UNVISITED; + int32_t max = LOC_UNVISITED; + + [[nodiscard]] bool visited() const noexcept { return min != LOC_UNVISITED; } + }; + + assert(!code.empty()); + + std::vector stack_heights(code.size()); + stack_heights[0] = {code_types[func_index].inputs, code_types[func_index].inputs}; for (size_t i = 0; i < code.size();) { @@ -368,8 +375,12 @@ std::variant validate_max_stack_height( auto stack_height_change = instr::traits[opcode].stack_height_change; const auto stack_height = stack_heights[i]; - if (stack_height == LOC_UNVISITED) + if (!stack_height.visited()) + { + // We reached the code that was neither referenced by previous forward jump, + // nor is part of sequential instruction flow. This is not allowed. return EOFValidationError::unreachable_instructions; + } if (opcode == OP_CALLF) { @@ -377,7 +388,7 @@ std::variant validate_max_stack_height( stack_height_required = code_types[fid].inputs; - if (stack_height + code_types[fid].max_stack_height - stack_height_required > + if (stack_height.max + code_types[fid].max_stack_height - stack_height_required > STACK_SIZE_LIMIT) return EOFValidationError::stack_overflow; @@ -390,7 +401,7 @@ std::variant validate_max_stack_height( { const auto fid = read_uint16_be(&code[i + 1]); - if (stack_height + code_types[fid].max_stack_height - code_types[fid].inputs > + if (stack_height.max + code_types[fid].max_stack_height - code_types[fid].inputs > STACK_SIZE_LIMIT) return EOFValidationError::stack_overflow; @@ -405,14 +416,18 @@ std::variant validate_max_stack_height( stack_height_required = code_types[func_index].outputs + code_types[fid].inputs - code_types[fid].outputs; - if (stack_heights[i] > stack_height_required) + + // JUMPF to returning function requires exact number of stack items + // and is allowed only in constant stack segment. + if (stack_height.max > stack_height_required) return EOFValidationError::stack_higher_than_outputs_required; } } else if (opcode == OP_RETF) { stack_height_required = code_types[func_index].outputs; - if (stack_height > code_types[func_index].outputs) + // RETF allowed only in constant stack segment + if (stack_height.max > stack_height_required) return EOFValidationError::stack_higher_than_outputs_required; } else if (opcode == OP_DUPN) @@ -420,30 +435,44 @@ std::variant validate_max_stack_height( else if (opcode == OP_SWAPN) stack_height_required = code[i + 1] + 2; - if (stack_height < stack_height_required) + if (stack_height.min < stack_height_required) return EOFValidationError::stack_underflow; - const auto next_stack_height = stack_height + stack_height_change; + const StackHeightRange next_stack_height{ + stack_height.min + stack_height_change, stack_height.max + stack_height_change}; // Determine size of immediate, including the special case of RJUMPV. const size_t imm_size = (opcode == OP_RJUMPV) ? (1 + /*count*/ (size_t{code[i + 1]} + 1) * REL_OFFSET_SIZE) : instr::traits[opcode].immediate_size; - // Mark immediate locations. - std::fill_n(&stack_heights[i + 1], imm_size, LOC_IMMEDIATE); - // Validates the successor instruction and updates its stack height. - const auto visit_successor = [&stack_heights]( - size_t successor_offset, int32_t expected_stack_height) { + const auto visit_successor = [&stack_heights](size_t current_offset, + size_t successor_offset, + StackHeightRange required_stack_height) { auto& successor_stack_height = stack_heights[successor_offset]; - if (successor_stack_height == LOC_UNVISITED) + if (successor_offset <= current_offset) // backwards jump { - successor_stack_height = expected_stack_height; - return true; + // successor_offset == current_offset case is possible only with jump into the same + // jump instruction, e.g. RJUMP(-3), so it is technically a backwards jump, too. + assert(successor_stack_height.visited()); + // The spec could have been relaxed to + // return successor_stack_height.min >= required_stack_height.min && + // successor_stack_height.max <= required_stack_height.max; + // but it was decided to have strict equality for simplicity. + return successor_stack_height.min == required_stack_height.min && + successor_stack_height.max == required_stack_height.max; } - else - return successor_stack_height == expected_stack_height; + else if (!successor_stack_height.visited()) // forwards jump, new target + successor_stack_height = required_stack_height; + else // forwards jump, target known + { + successor_stack_height.min = + std::min(required_stack_height.min, successor_stack_height.min); + successor_stack_height.max = + std::max(required_stack_height.max, successor_stack_height.max); + } + return true; }; const auto next = i + imm_size + 1; // Offset of the next instruction (may be invalid). @@ -453,7 +482,7 @@ std::variant validate_max_stack_height( { if (next >= code.size()) return EOFValidationError::no_terminating_instruction; - if (!visit_successor(next, next_stack_height)) + if (!visit_successor(i, next, next_stack_height)) return EOFValidationError::stack_height_mismatch; } @@ -462,7 +491,7 @@ std::variant validate_max_stack_height( { const auto target_rel_offset = read_int16_be(&code[i + 1]); const auto target = static_cast(i) + target_rel_offset + 3; - if (!visit_successor(static_cast(target), next_stack_height)) + if (!visit_successor(i, static_cast(target), next_stack_height)) return EOFValidationError::stack_height_mismatch; } else if (opcode == OP_RJUMPV) @@ -474,7 +503,7 @@ std::variant validate_max_stack_height( { const auto target_rel_offset = read_int16_be(&code[i + k * REL_OFFSET_SIZE + 2]); const auto target = static_cast(next) + target_rel_offset; - if (!visit_successor(static_cast(target), next_stack_height)) + if (!visit_successor(i, static_cast(target), next_stack_height)) return EOFValidationError::stack_height_mismatch; } } @@ -482,8 +511,9 @@ std::variant validate_max_stack_height( i = next; } - const auto max_stack_height = *std::max_element(stack_heights.begin(), stack_heights.end()); - return max_stack_height; + const auto max_stack_height_it = std::max_element(stack_heights.begin(), stack_heights.end(), + [](StackHeightRange lhs, StackHeightRange rhs) noexcept { return lhs.max < rhs.max; }); + return max_stack_height_it->max; } std::variant validate_eof1( diff --git a/test/unittests/eof_validation_test.cpp b/test/unittests/eof_validation_test.cpp index 04e8ab1dc8..077aee9e84 100644 --- a/test/unittests/eof_validation_test.cpp +++ b/test/unittests/eof_validation_test.cpp @@ -631,8 +631,7 @@ TEST_F(eof_validation, max_stack_height) 0x400 * OP_POP + OP_STOP + OP_RETF, EOFValidationError::invalid_max_stack_height); - add_test_case( - eof_bytecode(rjumpi(2, 0) + 1 + OP_STOP, 1), EOFValidationError::stack_height_mismatch); + add_test_case(eof_bytecode(rjumpi(2, 0) + 1 + OP_STOP, 1), EOFValidationError::success); add_test_case( eof_bytecode(rjumpi(-3, 0) + OP_STOP, 1), EOFValidationError::stack_height_mismatch); From 6c0a06d0dca5a60d48c940f74956f21a2eb00d8f Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Tue, 20 Feb 2024 17:37:26 +0100 Subject: [PATCH 03/10] Add RJUMP*, RETF, JUMPF stack validation tests and underflow tests --- test/unittests/eof_stack_validation_test.cpp | 1014 +++++++++++++++++- 1 file changed, 957 insertions(+), 57 deletions(-) diff --git a/test/unittests/eof_stack_validation_test.cpp b/test/unittests/eof_stack_validation_test.cpp index 2fa3c85d3b..4161d61c1d 100644 --- a/test/unittests/eof_stack_validation_test.cpp +++ b/test/unittests/eof_stack_validation_test.cpp @@ -12,76 +12,911 @@ using namespace evmone; using namespace evmone::test; +namespace +{ +// code prologue that creates a segment starting with possible stack heights 1 and 3 +const auto varstack = push0() + rjumpi(2, 0) + push0() + push0(); +} // namespace + +TEST_F(eof_validation, non_constant_stack_height) +{ + // Final "OP_PUSH0 + OP_PUSH0 + OP_REVERT" can be reached with stack heights: 0, 2 or 1 + add_test_case(eof_bytecode(rjumpi(7, OP_PUSH0) + OP_PUSH0 + OP_PUSH0 + rjumpi(1, OP_PUSH0) + + OP_POP + OP_PUSH0 + OP_PUSH0 + OP_REVERT, + 4), + EOFValidationError::success); + + // Final "OP_POP + OP_PUSH0 + OP_PUSH0 + OP_REVERT" can be reached with stack heights: 1, 3 or 2 + add_test_case(eof_bytecode(push0() + rjumpi(7, OP_PUSH0) + OP_PUSH0 + OP_PUSH0 + + rjumpi(1, OP_PUSH0) + OP_POP + OP_PUSH0 + OP_PUSH0 + OP_REVERT, + 5), + EOFValidationError::success); + + // Final "OP_POP + OP_POP + OP_PUSH0 + OP_PUSH0 + OP_REVERT" can be reached with stack heights: + // 0, 2 or 1. Stack underflow when height is 0. + add_test_case(eof_bytecode(rjumpi(7, OP_PUSH0) + OP_PUSH0 + OP_PUSH0 + rjumpi(1, OP_PUSH0) + + OP_POP + OP_POP + OP_PUSH0 + OP_PUSH0 + OP_REVERT, + 4), + EOFValidationError::stack_underflow); +} + +TEST_F(eof_validation, backwards_rjump) +{ + add_test_case(eof_bytecode(rjump(-3)), EOFValidationError::success); + + add_test_case(eof_bytecode(push0() + OP_POP + rjump(-5), 1), EOFValidationError::success); + + // rjump backwards from different locations to the same target + add_test_case(eof_bytecode(push0() + OP_POP + rjumpi(3, 1) + rjump(-8) + rjump(-11), 1), + EOFValidationError::success); + + // rjump backwards from different locations to the same target - stack height mismatch + add_test_case( + eof_bytecode(push0() + OP_POP + rjumpi(3, 1) + rjump(-8) + OP_PUSH0 + rjump(-12), 1), + EOFValidationError::stack_height_mismatch); + + // infinite pushing loop + add_test_case(eof_bytecode(push0() + rjump(-4), 1), EOFValidationError::stack_height_mismatch); + + // infinite popping loop + add_test_case( + eof_bytecode(push0() + OP_POP + rjump(-4), 1), EOFValidationError::stack_height_mismatch); +} + +TEST_F(eof_validation, backwards_rjump_variable_stack) +{ + add_test_case(eof_bytecode(varstack + rjump(-3), 3), EOFValidationError::success); + + add_test_case( + eof_bytecode(varstack + OP_PUSH0 + OP_POP + rjump(-5), 4), EOFValidationError::success); + + // rjump backwards from different locations to the same target + add_test_case( + eof_bytecode(varstack + OP_PUSH0 + OP_POP + rjumpi(3, 1) + rjump(-8) + rjump(-11), 4), + EOFValidationError::success); + + // rjump backwards from different locations to the same target - stack height mismatch + // 1st rjump: stack [1, 3] + // 2nd rjump: stack [2, 4] + // Jumping to [1, 3] + add_test_case( + eof_bytecode( + varstack + OP_PUSH0 + OP_POP + rjumpi(3, 1) + rjump(-8) + OP_PUSH0 + rjump(-12), 4), + EOFValidationError::stack_height_mismatch); + + // rjump backwards - max stack height mismatch + // rjumpi: stack [1, 3] + // push0: stack [2, 4] + // rjump: stack [1, 4] + // Jumping from [1, 4] to [1, 3] + add_test_case(eof_bytecode(varstack + rjumpi(1, 0) + OP_PUSH0 + rjump(-7), 4), + EOFValidationError::stack_height_mismatch); + + // rjump backwards - min stack height mismatch + // rjumpi: stack [1, 3] + // pop : stack [0, 2] + // rjump: stack [0, 3] + // Jumping from [0, 3] to [1, 3] + add_test_case(eof_bytecode(varstack + rjumpi(1, 0) + OP_POP + rjump(-7), 4), + EOFValidationError::stack_height_mismatch); + + // infinite pushing loop + add_test_case( + eof_bytecode(varstack + push0() + rjump(-4), 4), EOFValidationError::stack_height_mismatch); + + // infinite popping loop + add_test_case(eof_bytecode(varstack + push0() + OP_POP + rjump(-4), 3), + EOFValidationError::stack_height_mismatch); +} + +TEST_F(eof_validation, forwards_rjump) +{ + add_test_case(eof_bytecode(rjump(0) + OP_STOP), EOFValidationError::success); + + // forwards rjump + fallthrough - equal stack + add_test_case(eof_bytecode(push0() + rjumpi(3, 0) + rjump(1) + OP_NOT + OP_STOP, 2), + EOFValidationError::success); + + // forwards rjump + forwards rjump + fallthrough - equal stack + add_test_case( + eof_bytecode( + push0() + rjumpi(8, 0) + rjumpi(6, 0) + rjump(4) + rjump(1) + OP_NOT + OP_STOP, 2), + EOFValidationError::success); + + // forwards rjump + fallthrough - different stack + // rjump: [1, 1] + // push0: [2, 2] + add_test_case(eof_bytecode(push0() + rjumpi(3, 0) + rjump(1) + OP_PUSH0 + OP_STOP, 2), + EOFValidationError::success); + + // forwards rjump + forwards rjump + fallthrough - different stack + add_test_case(eof_bytecode(push0() + rjumpi(8, 0) + // [1, 1] + rjumpi(7, 0) + // [1, 1] + rjump(5) + // [1, 1] + OP_PUSH0 + // [2, 2] + rjump(1) + // [2, 2] + OP_NOT + // [1, 1] + OP_STOP, // [1, 2] + 2), + EOFValidationError::success); +} + +TEST_F(eof_validation, forwards_rjump_variable_stack) +{ + add_test_case(eof_bytecode(varstack + rjump(0) + OP_STOP, 3), EOFValidationError::success); + + // forwards rjump + fallthrough - equal stack + add_test_case(eof_bytecode(varstack + OP_PUSH0 + rjumpi(3, 0) + rjump(1) + OP_NOT + OP_STOP, 5), + EOFValidationError::success); + + // forwards rjump + forwards rjump + fallthrough - equal stack + add_test_case(eof_bytecode(varstack + OP_PUSH0 + rjumpi(8, 0) + rjumpi(6, 0) + rjump(4) + + rjump(1) + OP_NOT + OP_STOP, + 5), + EOFValidationError::success); + + // forwards rjump + fallthrough - different stack + add_test_case(eof_bytecode(varstack + // [1, 3] + OP_PUSH0 + // [2, 4] + rjumpi(3, 0) + // [2, 4] + rjump(1) + // [2, 4] + OP_PUSH0 + // [3, 5] + OP_STOP, // [2, 5] + 5), + EOFValidationError::success); + + // forwards rjump + forwards rjump + fallthrough - different stack + add_test_case(eof_bytecode(varstack + rjumpi(8, 0) + // [1, 3] + rjumpi(7, 0) + // [1, 3] + rjump(5) + // [1, 3] + OP_PUSH0 + // [2, 4] + rjump(1) + // [2, 4] + OP_NOT + // [1, 3] + OP_STOP, // [1, 4] + 4), + EOFValidationError::success); +} + +TEST_F(eof_validation, backwards_rjumpi) +{ + add_test_case(eof_bytecode(rjumpi(-5, 0) + OP_STOP, 1), EOFValidationError::success); + + add_test_case( + eof_bytecode(push0() + OP_POP + rjumpi(-7, 0) + OP_STOP, 1), EOFValidationError::success); + + // rjumpi backwards from different locations to the same target + add_test_case(eof_bytecode(push0() + OP_POP + rjumpi(-7, 0) + rjumpi(-12, 0) + OP_STOP, 1), + EOFValidationError::success); + + // rjumpi backwards from different locations to the same target - stack height mismatch + add_test_case( + eof_bytecode(push0() + OP_POP + rjumpi(-7, 0) + OP_PUSH0 + rjumpi(-13, 0) + OP_STOP, 2), + EOFValidationError::stack_height_mismatch); + + // valid loop + add_test_case(eof_bytecode(push0() + push(1) + OP_ADD + rjumpi(-7, OP_DUP1) + OP_STOP, 2), + EOFValidationError::success); + + // pushing loop + add_test_case( + eof_bytecode(push0() + push(1) + OP_ADD + OP_DUP1 + rjumpi(-8, OP_DUP1) + OP_STOP, 2), + EOFValidationError::stack_height_mismatch); + + // popping loop + add_test_case(eof_bytecode(push0() + OP_PUSH0 + OP_PUSH0 + rjumpi(-4, OP_POP) + OP_STOP, 2), + EOFValidationError::stack_height_mismatch); + + // rjump and rjumpi with the same target - equal stack + add_test_case(eof_bytecode(push0() + // [1, 1] + OP_POP + // [0, 0] + rjumpi(-7, 0) + // [1, 1] + rjump(-10), // [1, 1] + 1), + EOFValidationError::success); + + // rjump and rjumpi with the same target - different stack + add_test_case(eof_bytecode(push0() + // [1, 1] + OP_POP + // [0, 0] + rjumpi(-7, 0) + // [0, 0] + OP_PUSH0 + // [1, 1] + rjump(-11), // [1, 1] + 1), + EOFValidationError::stack_height_mismatch); + + // rjumpi backwards - only max stack height mismatch + add_test_case(eof_bytecode(OP_PUSH0 + // [1, 1] + rjumpi(1, 0) + // [1, 1] + OP_PUSH0 + // [2, 2] + rjumpi(-11, 0) + // [1, 2] + OP_STOP, // [1, 2] + 3), + EOFValidationError::stack_height_mismatch); + + // rjumpi backwards - only min stack height mismatch + add_test_case(eof_bytecode(OP_PUSH0 + OP_PUSH0 + // [2, 2] + rjumpi(1, 0) + // [2, 2] + OP_POP + // [1, 1] + rjumpi(-11, 0) + // [1, 2] + OP_STOP, // [1, 2] + 3), + EOFValidationError::stack_height_mismatch); +} + +TEST_F(eof_validation, backwards_rjumpi_variable_stack) +{ + add_test_case(eof_bytecode(varstack + rjumpi(-5, 0) + OP_STOP, 4), EOFValidationError::success); + + add_test_case(eof_bytecode(varstack + OP_PUSH0 + OP_POP + rjumpi(-7, 0) + OP_STOP, 4), + EOFValidationError::success); + + // rjumpi backwards from different locations to the same target + add_test_case( + eof_bytecode(varstack + OP_PUSH0 + OP_POP + rjumpi(-7, 0) + rjumpi(-12, 0) + OP_STOP, 4), + EOFValidationError::success); + + // rjumpi backwards from different locations to the same target - stack height mismatch + add_test_case( + eof_bytecode( + varstack + OP_PUSH0 + OP_POP + rjumpi(-7, 0) + OP_PUSH0 + rjumpi(-13, 0) + OP_STOP, 5), + EOFValidationError::stack_height_mismatch); + + // valid loop + add_test_case( + eof_bytecode(varstack + OP_PUSH0 + push(1) + OP_ADD + rjumpi(-7, OP_DUP1) + OP_STOP, 5), + EOFValidationError::success); + + // pushing loop + add_test_case( + eof_bytecode( + varstack + OP_PUSH0 + push(1) + OP_ADD + OP_DUP1 + rjumpi(-8, OP_DUP1) + OP_STOP, 5), + EOFValidationError::stack_height_mismatch); + + // popping loop + add_test_case( + eof_bytecode(varstack + OP_PUSH0 + OP_PUSH0 + OP_PUSH0 + rjumpi(-4, OP_POP) + OP_STOP, 5), + EOFValidationError::stack_height_mismatch); + + // rjump and rjumpi with the same target - equal stack + add_test_case(eof_bytecode(varstack + OP_PUSH0 + // [2, 4] + OP_POP + // [1, 3] + rjumpi(-7, 0) + // [1, 3] + rjump(-10), // [1, 3] + 4), + EOFValidationError::success); + + // rjump and rjumpi with the same target - different stack + add_test_case(eof_bytecode(varstack + OP_PUSH0 + // [2, 4] + OP_POP + // [1, 3] + rjumpi(-7, 0) + // [1, 3] + OP_PUSH0 + // [2, 4] + rjump(-11), // [2, 4] + 4), + EOFValidationError::stack_height_mismatch); +} + +TEST_F(eof_validation, forwards_rjumpi) +{ + add_test_case(eof_bytecode(rjumpi(0, 1) + OP_STOP, 1), EOFValidationError::success); + + // forwards rjump + fallthrough - equal stack + add_test_case( + eof_bytecode(push0() + rjumpi(1, 0) + OP_NOT + OP_STOP, 2), EOFValidationError::success); + + // forwards rjumpi + forwards rjumpi + fallthrough - equal stack + add_test_case(eof_bytecode(push0() + rjumpi(6, 0) + rjumpi(1, 0) + OP_NOT + OP_STOP, 2), + EOFValidationError::success); + + // forwards rjumpi + fallthrough - different stack + // rjumpi: [1, 1] + // push0: [2, 2] + add_test_case( + eof_bytecode(push0() + rjumpi(1, 0) + OP_PUSH0 + OP_STOP, 2), EOFValidationError::success); + + // forwards rjumpi + forwards rjumpi + fallthrough - different stack + add_test_case(eof_bytecode(push0() + // [1, 1] + rjumpi(7, 0) + // [1, 1] + OP_PUSH0 + // [2, 2] + rjumpi(1, 0) + // [2, 2] + OP_NOT + // [2, 2] + OP_STOP, // [1, 2] + 3), + EOFValidationError::success); + + // valid loop with a break + add_test_case(eof_bytecode(push0() + // [1, 1] + push(1) + // [2, 2] + OP_ADD + // [1, 1] + OP_DUP1 + // [2, 2] + push(10) + // [3, 3] + rjumpi(4, OP_GT) + // [1, 1] + rjumpi(-14, OP_DUP1) + // [1, 1] + OP_STOP, // [1, 1] + 3), + EOFValidationError::success); + + // valid loop with a break - different stack + add_test_case(eof_bytecode(push0() + // [1, 1] + push(1) + // [2, 2] + OP_ADD + // [1, 1] + OP_DUP1 + // [2, 2] + push(10) + // [3, 3] + rjumpi(5, OP_GT) + // [1, 1] + OP_PUSH0 + // [2, 2] + rjumpi(-13, OP_DUP1) + // [2, 2] + OP_STOP, // [1, 2] + 3), + EOFValidationError::success); + + // if-then-else with push - equal stack + add_test_case(eof_bytecode(push0() + // [1, 1] + rjumpi(4, 0) + // [1, 1] + OP_PUSH0 + // [2, 2] + rjump(1) + // [2, 2] + OP_PUSH0 + // [2, 2] + OP_STOP, // [2, 2] + 2), + EOFValidationError::success); + + // if-then-else with push - different stack + add_test_case(eof_bytecode(push0() + // [1, 1] + rjumpi(4, 0) + // [1, 1] + OP_PUSH0 + // [2, 2] + rjump(1) + // [2, 2] + OP_NOT + // [1, 1] + OP_STOP, // [1, 2] + 2), + EOFValidationError::success); + + // if-then-else with pop - equal stack + add_test_case(eof_bytecode(push0() + // [1, 1] + rjumpi(4, 0) + // [1, 1] + OP_POP + // [0, 0] + rjump(1) + // [0, 0] + OP_POP + // [0, 0] + OP_STOP, // [0, 0] + 2), + EOFValidationError::success); + + // if-then-else with pop - different stack + add_test_case(eof_bytecode(push0() + // [1, 1] + rjumpi(4, 0) + // [1, 1] + OP_POP + // [0, 0] + rjump(1) + // [0, 0] + OP_NOT + // [1, 1] + OP_STOP, // [0, 1] + 2), + EOFValidationError::success); + + // rjump and rjumpi with the same target - equal stack + add_test_case(eof_bytecode(push0() + // [1, 1] + rjumpi(3, 0) + // [1, 1] + rjump(0) + // [1, 1] + OP_STOP, // [1, 1] + 2), + EOFValidationError::success); + + // rjump and rjumpi with the same target - different stack + add_test_case(eof_bytecode(push0() + // [1, 1] + rjumpi(4, 0) + // [1, 1] + OP_PUSH0 + // [2, 2] + rjump(0) + // [2, 2] + OP_STOP, // [1, 2] + 2), + EOFValidationError::success); +} + +TEST_F(eof_validation, forwards_rjumpi_variable_stack) +{ + add_test_case(eof_bytecode(varstack + rjumpi(0, 1) + OP_STOP, 4), EOFValidationError::success); + + // forwards rjump + fallthrough - equal stack + add_test_case(eof_bytecode(varstack + OP_PUSH0 + rjumpi(1, 0) + OP_NOT + OP_STOP, 5), + EOFValidationError::success); + + // forwards rjumpi + forwards rjumpi + fallthrough - equal stack + add_test_case( + eof_bytecode(varstack + OP_PUSH0 + rjumpi(6, 0) + rjumpi(1, 0) + OP_NOT + OP_STOP, 5), + EOFValidationError::success); + + // forwards rjumpi + fallthrough - different stack + // rjumpi: [4, 4] + // push0: [5, 5] + add_test_case(eof_bytecode(varstack + OP_PUSH0 + rjumpi(1, 0) + OP_PUSH0 + OP_STOP, 5), + EOFValidationError::success); + + // forwards rjumpi + forwards rjumpi + fallthrough - different stack + add_test_case(eof_bytecode(varstack + OP_PUSH0 + // [2, 4] + rjumpi(7, 0) + // [2, 4] + OP_PUSH0 + // [3, 5] + rjumpi(1, 0) + // [3, 5] + OP_NOT + // [3, 5] + OP_STOP, // [2, 5] + 6), + EOFValidationError::success); + + // valid loop with a break + add_test_case(eof_bytecode(varstack + OP_PUSH0 + // [2, 4] + push(1) + // [3, 5] + OP_ADD + // [2, 4] + OP_DUP1 + // [3, 5] + push(10) + // [4, 6] + rjumpi(4, OP_GT) + // [2, 4] + rjumpi(-14, OP_DUP1) + // [2, 4] + OP_STOP, // [2, 4] + 6), + EOFValidationError::success); + + // valid loop with a break - different stack + add_test_case(eof_bytecode(varstack + OP_PUSH0 + // [2, 4] + push(1) + // [3, 5] + OP_ADD + // [2, 4] + OP_DUP1 + // [3, 5] + push(10) + // [4, 6] + rjumpi(5, OP_GT) + // [2, 4] + OP_PUSH0 + // [3, 5] + rjumpi(-13, OP_DUP1) + // [3, 5] + OP_STOP, // [2, 5] + 6), + EOFValidationError::success); + + // if-then-else with push - equal stack + add_test_case(eof_bytecode(varstack + OP_PUSH0 + // [2, 4] + rjumpi(4, 0) + // [2, 4] + OP_PUSH0 + // [3, 5] + rjump(1) + // [3, 5] + OP_PUSH0 + // [3, 5] + OP_STOP, // [3, 5] + 5), + EOFValidationError::success); + + // if-then-else with push - different stack + add_test_case(eof_bytecode(varstack + OP_PUSH0 + // [2, 4] + rjumpi(4, 0) + // [2, 4] + OP_PUSH0 + // [3, 5] + rjump(1) + // [3, 5] + OP_NOT + // [2, 4] + OP_STOP, // [2, 5] + 5), + EOFValidationError::success); + + // if-then-else with pop - equal stack + add_test_case(eof_bytecode(varstack + OP_PUSH0 + // [2, 4] + rjumpi(4, 0) + // [2, 4] + OP_POP + // [1, 3] + rjump(1) + // [1, 3] + OP_POP + // [1, 3] + OP_STOP, // [1, 3] + 5), + EOFValidationError::success); + + // if-then-else with pop - different stack + add_test_case(eof_bytecode(varstack + OP_PUSH0 + // [2, 4] + rjumpi(4, 0) + // [2, 4] + OP_POP + // [1, 3] + rjump(1) + // [1, 3] + OP_NOT + // [2, 4] + OP_STOP, // [1, 4] + 5), + EOFValidationError::success); + + // rjump and rjumpi with the same target - equal stack + add_test_case(eof_bytecode(varstack + OP_PUSH0 + // [2, 4] + rjumpi(3, 0) + // [2, 4] + rjump(0) + // [2, 4] + OP_STOP, // [2, 4] + 5), + EOFValidationError::success); + + // rjump and rjumpi with the same target - different stack + add_test_case(eof_bytecode(varstack + OP_PUSH0 + // [2, 4] + rjumpi(4, 0) + // [2, 4] + OP_PUSH0 + // [3, 5] + rjump(0) + // [3, 5] + OP_STOP, // [2, 5] + 5), + EOFValidationError::success); +} + +TEST_F(eof_validation, backwards_rjumpv) +{ + add_test_case(eof_bytecode(rjumpv({-6}, 0) + OP_STOP, 1), EOFValidationError::success); + + add_test_case( + eof_bytecode(push0() + OP_POP + rjumpv({-8}, 0) + OP_STOP, 1), EOFValidationError::success); + + // rjumpv backwards from different locations to the same target + add_test_case(eof_bytecode(push0() + OP_POP + rjumpv({-8}, 0) + rjumpv({-14}, 0) + OP_STOP, 1), + EOFValidationError::success); + + // rjumpv backwards from different locations to the same target - stack height mismatch + add_test_case( + eof_bytecode(push0() + OP_POP + rjumpv({-8}, 0) + push0() + rjumpv({-15}, 0) + OP_STOP, 2), + EOFValidationError::stack_height_mismatch); + + // rjump and rjumpv with the same target - equal stack + add_test_case(eof_bytecode(push0() + // [1, 1] + OP_POP + // [0, 0] + rjumpv({-8}, 0) + // [0, 0] + rjump(-11), // [0, 0] + 1), + EOFValidationError::success); + + // rjump and rjumpv with the same target - different stack + add_test_case(eof_bytecode(push0() + // [1, 1] + OP_POP + // [0, 0] + rjumpv({-8}, 0) + // [0, 0] + OP_PUSH0 + // [1, 1] + rjump(-12), // [1, 1] + 1), + EOFValidationError::stack_height_mismatch); + + // rjumpv backwards - only max stack height mismatch + add_test_case(eof_bytecode(push0() + // [1, 1] + rjumpi(1, 0) + // [1, 1] + OP_PUSH0 + // [2, 2] + rjumpv({-12}, 0) + // [1, 2] + OP_STOP, // [1, 2] + 3), + EOFValidationError::stack_height_mismatch); + + // rjumpv backwards - only min stack height mismatch + add_test_case(eof_bytecode(OP_PUSH0 + OP_PUSH0 + // [2, 2] + rjumpi(1, 0) + // [2, 2] + OP_POP + // [1, 1] + rjumpv({-12}, 0) + // [1, 2] + OP_STOP, // [1, 2] + 3), + EOFValidationError::stack_height_mismatch); +} + +TEST_F(eof_validation, backwards_rjumpv_variable_stack) +{ + add_test_case( + eof_bytecode(varstack + rjumpv({-6}, 0) + OP_STOP, 4), EOFValidationError::success); + + add_test_case(eof_bytecode(varstack + push0() + OP_POP + rjumpv({-8}, 0) + OP_STOP, 4), + EOFValidationError::success); + + // rjumpv backwards from different locations to the same target + add_test_case( + eof_bytecode(varstack + push0() + OP_POP + rjumpv({-8}, 0) + rjumpv({-14}, 0) + OP_STOP, 4), + EOFValidationError::success); + + // rjumpv backwards from different locations to the same target - stack height mismatch + add_test_case(eof_bytecode(varstack + push0() + OP_POP + rjumpv({-8}, 0) + push0() + + rjumpv({-15}, 0) + OP_STOP, + 5), + EOFValidationError::stack_height_mismatch); + + // rjump and rjumpv with the same target - equal stack + add_test_case(eof_bytecode(varstack + push0() + // [2, 4] + OP_POP + // [1, 3] + rjumpv({-8}, 0) + // [1, 3] + rjump(-11), // [1, 3] + 4), + EOFValidationError::success); + + // rjump and rjumpv with the same target - different stack + add_test_case(eof_bytecode(varstack + push0() + // [2, 4] + OP_POP + // [1, 3] + rjumpv({-8}, 0) + // [1, 3] + OP_PUSH0 + // [2, 4] + rjump(-12), // [2, 4] + 4), + EOFValidationError::stack_height_mismatch); + + // rjumpv backwards - only max stack height mismatch + add_test_case(eof_bytecode(varstack + push0() + // [2, 4] + rjumpi(1, 0) + // [2, 4] + OP_PUSH0 + // [3, 5] + rjumpv({-12}, 0) + // [2, 5] + OP_STOP, // [2, 5] + 5), + EOFValidationError::stack_height_mismatch); + + // rjumpv backwards - only min stack height mismatch + add_test_case(eof_bytecode(varstack + OP_PUSH0 + OP_PUSH0 + // [3, 5] + rjumpi(1, 0) + // [3, 5] + OP_POP + // [2, 4] + rjumpv({-12}, 0) + // [2, 5] + OP_STOP, // [2, 5] + 5), + EOFValidationError::stack_height_mismatch); +} + +TEST_F(eof_validation, forwards_rjumpv) +{ + add_test_case(eof_bytecode(rjumpv({0}, 1) + OP_STOP, 1), EOFValidationError::success); + + // forwards rjumpv + fallthrough - equal stack + add_test_case( + eof_bytecode(push0() + rjumpv({1}, 0) + OP_NOT + OP_STOP, 2), EOFValidationError::success); + + // forwards rjumpv 2 cases + fallthrough - equal stack + add_test_case( + eof_bytecode(push0() + rjumpv({2, 3}, 0) + OP_PUSH0 + OP_POP + OP_NOT + OP_STOP, 2), + EOFValidationError::success); + + // forwards rjumpv + fallthrough - different stack + add_test_case(eof_bytecode(push0() + // [1, 1] + rjumpv({1}, 0) + // [1, 1] + OP_PUSH0 + // [2, 2] + OP_STOP, // [1, 2] + 2), + EOFValidationError::success); + + // forwards rjumpv 2 cases + fallthrough - different stack + add_test_case(eof_bytecode(push0() + // [1, 1] + rjumpv({1, 2}, 0) + // [1, 1] + OP_PUSH0 + // [2, 2] + OP_PUSH0 + // [2, 3] + OP_NOT + // [1, 3] + OP_STOP, // [1, 2] + 3), + EOFValidationError::success); + + // switch - equal stack + add_test_case(eof_bytecode(push0() + rjumpv({5, 10}, 0) + // [1, 1] + push(1) + rjump(7) + // [2, 2] + push(2) + rjump(2) + // [2, 2] + push(3) + // [2, 2] + OP_STOP, // [2, 2] + 2), + EOFValidationError::success); + + // switch with pushes - different stack + add_test_case(eof_bytecode(push0() + rjumpv({4, 9}, 0) + // [1, 1] + push0() + rjump(8) + // [2, 2] + push0() + push0() + rjump(3) + // [3, 3] + push0() + push0() + push0() + // [4, 4] + OP_STOP, // [1, 4] + 4), + EOFValidationError::success); + + + // switch with pops - different stack + add_test_case(eof_bytecode(4 * push0() + rjumpv({4, 9}, 0) + // [4, 4] + OP_POP + rjump(8) + // [3, 3] + OP_POP + OP_POP + rjump(3) + // [2, 2] + OP_POP + OP_POP + OP_POP + // [1, 1] + OP_STOP, // [1, 4] + 5), + EOFValidationError::success); + + // rjump and rjumpv with the same target - equal stack + add_test_case(eof_bytecode(push0() + // [1, 1] + rjumpv({3}, 0) + // [1, 1] + rjump(0) + // [1, 1] + OP_STOP, // [1, 1] + 2), + EOFValidationError::success); + + // rjump and rjumpv with the same target - different stack + add_test_case(eof_bytecode(push0() + // [1, 1] + rjumpv({4}, 0) + // [1, 1] + OP_PUSH0 + // [2, 2] + rjump(0) + // [2, 2] + OP_STOP, // [1, 2] + 2), + EOFValidationError::success); +} + +TEST_F(eof_validation, forwards_rjumpv_variable_stack) +{ + add_test_case( + eof_bytecode(varstack + rjumpv({0}, 1) + OP_STOP, 4), EOFValidationError::success); + + // forwards rjumpv + fallthrough - equal stack + add_test_case(eof_bytecode(varstack + push0() + rjumpv({1}, 0) + OP_NOT + OP_STOP, 5), + EOFValidationError::success); + + // forwards rjumpv 2 cases + fallthrough - equal stack + add_test_case( + eof_bytecode( + varstack + push0() + rjumpv({2, 3}, 0) + OP_PUSH0 + OP_POP + OP_NOT + OP_STOP, 5), + EOFValidationError::success); + + // forwards rjumpv + fallthrough - different stack + add_test_case(eof_bytecode(varstack + push0() + // [2, 4] + rjumpv({1}, 0) + // [2, 4] + OP_PUSH0 + // [3, 5] + OP_STOP, // [2, 5] + 5), + EOFValidationError::success); + + // forwards rjumpv 2 cases + fallthrough - different stack + add_test_case(eof_bytecode(varstack + push0() + // [2, 4] + rjumpv({1, 2}, 0) + // [2, 4] + OP_PUSH0 + // [3, 5] + OP_PUSH0 + // [2, 6] + OP_NOT + // [2, 6] + OP_STOP, // [2, 6] + 6), + EOFValidationError::success); + + // switch - equal stack + add_test_case(eof_bytecode(varstack + push0() + rjumpv({5, 10}, 0) + // [2, 4] + push(1) + rjump(7) + // [3, 5] + push(2) + rjump(2) + // [3, 5] + push(3) + // [3, 5] + OP_STOP, // [3, 5] + 5), + EOFValidationError::success); + + // switch with pushes - different stack + add_test_case(eof_bytecode(varstack + push0() + rjumpv({4, 9}, 0) + // [2, 4] + push0() + rjump(8) + // [3, 5] + push0() + push0() + rjump(3) + // [4, 6] + push0() + push0() + push0() + // [5, 7] + OP_STOP, // [3, 7] + 7), + EOFValidationError::success); + + + // switch with pops - different stack + add_test_case(eof_bytecode(varstack + 4 * push0() + rjumpv({4, 9}, 0) + // [5, 7] + OP_POP + rjump(8) + // [4, 6] + OP_POP + OP_POP + rjump(3) + // [3, 5] + OP_POP + OP_POP + OP_POP + // [2, 4] + OP_STOP, // [2, 6] + 8), + EOFValidationError::success); + + // rjump and rjumpv with the same target - equal stack + add_test_case(eof_bytecode(varstack + push0() + // [2, 4] + rjumpv({3}, 0) + // [2, 4] + rjump(0) + // [2, 4] + OP_STOP, // [2, 4] + 5), + EOFValidationError::success); + + // rjump and rjumpv with the same target - different stack + add_test_case(eof_bytecode(varstack + push0() + // [2, 4] + rjumpv({4}, 0) + // [2, 4] + OP_PUSH0 + // [3, 5] + rjump(0) + // [3, 5] + OP_STOP, // [2, 5] + 5), + EOFValidationError::success); +} + +TEST_F(eof_validation, underflow) +{ + add_test_case(eof_bytecode(bytecode{OP_ADD} + OP_STOP, 0), EOFValidationError::stack_underflow); + + // CALLF underflow + add_test_case(eof_bytecode(callf(1) + OP_STOP, 1).code(push0() + OP_RETF, 1, 2, 2), + EOFValidationError::stack_underflow); + + // JUMPF to returning function + add_test_case(eof_bytecode(callf(1) + OP_STOP, 2) + .code(jumpf(2), 0, 2, 0) + .code(push0() + OP_RETF, 1, 2, 2), + EOFValidationError::stack_underflow); + + // JUMPF to non-returning function + add_test_case(eof_bytecode(jumpf(1), 0).code(revert(0, 0), 1, 0x80, 3), + EOFValidationError::stack_underflow); + + // RETF underflow + add_test_case(eof_bytecode(callf(1) + OP_STOP, 2).code(push0() + OP_RETF, 0, 2, 1), + EOFValidationError::stack_underflow); +} + +TEST_F(eof_validation, underflow_variable_stack) +{ + // LOG2 underflow - [1, 3] stack - neither min nor max enough for 4 inputs + add_test_case( + eof_bytecode(varstack + OP_LOG2 + OP_STOP, 3), EOFValidationError::stack_underflow); + + // ADD underflow - [1, 3] stack - only max enough for 5 inputs + add_test_case( + eof_bytecode(varstack + OP_ADD + OP_STOP, 3), EOFValidationError::stack_underflow); + + // CALLF underflow - [1, 3] stack - neither min nor max enough for 5 inputs + add_test_case(eof_bytecode(varstack + callf(1) + OP_STOP, 4).code(push0() + OP_RETF, 4, 5, 5), + EOFValidationError::stack_underflow); + + // CALLF underflow - [1, 3] stack - only max enough for 3 inputs + add_test_case(eof_bytecode(varstack + callf(1) + OP_STOP, 4).code(push0() + OP_RETF, 3, 4, 4), + EOFValidationError::stack_underflow); + + // JUMPF to returning function - neither min nor max enough for 5 inputs + add_test_case(eof_bytecode(callf(1) + OP_STOP, 3) + .code(varstack + jumpf(2), 0, 3, 3) + .code(bytecode{OP_POP} + OP_POP + OP_RETF, 5, 3, 3), + EOFValidationError::stack_underflow); + + // JUMPF to returning function - only max enough for 3 inputs + add_test_case(eof_bytecode(callf(1) + OP_STOP, 3) + .code(varstack + jumpf(2), 0, 3, 3) + .code(bytecode{OP_RETF}, 3, 3, 3), + EOFValidationError::stack_underflow); + + // JUMPF to non-returning function - [1, 3] stack - neither min nor max enough for 5 inputs + add_test_case(eof_bytecode(varstack + jumpf(1), 0).code(revert(0, 0), 5, 0x80, 7), + EOFValidationError::stack_underflow); + + // JUMPF to non-returning function - [1, 3] stack - only max enough for 3 inputs + add_test_case(eof_bytecode(varstack + jumpf(1), 0).code(revert(0, 0), 3, 0x80, 5), + EOFValidationError::stack_underflow); + + // RETF from [1, 3] stack - neither min nor max enough for 5 outputs + add_test_case(eof_bytecode(callf(1) + OP_STOP, 5).code(varstack + OP_RETF, 0, 5, 3), + EOFValidationError::stack_underflow); + + // RETF from [1, 3] stack - only max enough for 3 outputs + add_test_case(eof_bytecode(callf(1) + OP_STOP, 3).code(varstack + OP_RETF, 0, 3, 3), + EOFValidationError::stack_underflow); +} + TEST_F(eof_validation, callf_stack_validation) { - add_test_case(eof_bytecode(bytecode{OP_CALLF} + "0001" + OP_STOP, 1) - .code(push0() + push0() + OP_CALLF + "0002" + OP_RETF, 0, 1, 2) + add_test_case(eof_bytecode(callf(1) + OP_STOP, 1) + .code(push0() + push0() + callf(2) + OP_RETF, 0, 1, 2) .code(bytecode(OP_POP) + OP_RETF, 2, 1, 2), EOFValidationError::success); - add_test_case(eof_bytecode(bytecode{OP_CALLF} + "0001" + OP_STOP, 1) - .code(push0() + push0() + push0() + OP_CALLF + "0002" + OP_RETF, 0, 1, 3) + add_test_case(eof_bytecode(callf(1) + OP_STOP, 1) + .code(push0() + push0() + push0() + callf(2) + OP_RETF, 0, 1, 3) .code(bytecode(OP_POP) + OP_RETF, 2, 1, 2), EOFValidationError::stack_higher_than_outputs_required); - add_test_case(eof_bytecode(bytecode{OP_CALLF} + "0001" + OP_STOP, 1) - .code(push0() + OP_CALLF + "0002" + OP_RETF, 0, 1, 1) + add_test_case(eof_bytecode(callf(1) + OP_STOP, 1) + .code(push0() + callf(2) + OP_RETF, 0, 1, 1) .code(bytecode(OP_POP) + OP_RETF, 2, 1, 2), EOFValidationError::stack_underflow); } TEST_F(eof_validation, callf_stack_overflow) { - add_test_case(eof_bytecode(bytecode{OP_CALLF} + "0001" + OP_STOP) - .code(512 * push(1) + OP_CALLF + "0001" + 512 * OP_POP + OP_RETF, 0, 0, 512), + add_test_case(eof_bytecode(callf(1) + OP_STOP) + .code(512 * push(1) + callf(1) + 512 * OP_POP + OP_RETF, 0, 0, 512), EOFValidationError::success); - add_test_case(eof_bytecode(bytecode{OP_CALLF} + "0001" + OP_STOP) - .code(513 * push(1) + OP_CALLF + "0001" + 513 * OP_POP + OP_RETF, 0, 0, 513), + add_test_case(eof_bytecode(callf(1) + OP_STOP) + .code(513 * push(1) + callf(1) + 513 * OP_POP + OP_RETF, 0, 0, 513), EOFValidationError::stack_overflow); - add_test_case( - eof_bytecode(bytecode{OP_CALLF} + "0001" + OP_STOP) - .code(1023 * push(1) + OP_CALLF + "0001" + 1023 * OP_POP + OP_RETF, 0, 0, 1023), + add_test_case(eof_bytecode(callf(1) + OP_STOP) + .code(1023 * push(1) + callf(1) + 1023 * OP_POP + OP_RETF, 0, 0, 1023), EOFValidationError::stack_overflow); - add_test_case( - eof_bytecode(bytecode{OP_CALLF} + "0001" + OP_STOP) - .code(1023 * push(1) + OP_CALLF + "0002" + 1023 * OP_POP + OP_RETF, 0, 0, 1023) - .code(push0() + OP_POP + OP_RETF, 0, 0, 1), + add_test_case(eof_bytecode(callf(1) + OP_STOP) + .code(1023 * push(1) + callf(2) + 1023 * OP_POP + OP_RETF, 0, 0, 1023) + .code(push0() + OP_POP + OP_RETF, 0, 0, 1), EOFValidationError::success); - add_test_case( - eof_bytecode(bytecode{OP_CALLF} + "0001" + OP_STOP) - .code(1023 * push(1) + OP_CALLF + "0002" + 1023 * OP_POP + OP_RETF, 0, 0, 1023) - .code(push0() + push0() + OP_POP + OP_POP + OP_RETF, 0, 0, 2), + add_test_case(eof_bytecode(callf(1) + OP_STOP) + .code(1023 * push(1) + callf(2) + 1023 * OP_POP + OP_RETF, 0, 0, 1023) + .code(push0() + push0() + OP_POP + OP_POP + OP_RETF, 0, 0, 2), EOFValidationError::stack_overflow); } TEST_F(eof_validation, callf_with_inputs_stack_overflow) { - add_test_case(eof_bytecode(1023 * push(1) + OP_CALLF + "0001" + 1019 * OP_POP + OP_RETURN, 1023) + add_test_case(eof_bytecode(1023 * push(1) + callf(1) + 1019 * OP_POP + OP_RETURN, 1023) .code(bytecode{OP_POP} + OP_POP + OP_RETF, 2, 0, 2), EOFValidationError::success); - add_test_case(eof_bytecode(1023 * push(1) + OP_CALLF + "0001" + 1021 * OP_POP + OP_RETURN, 1023) + add_test_case(eof_bytecode(1023 * push(1) + callf(1) + 1021 * OP_POP + OP_RETURN, 1023) .code(push(1) + OP_POP + OP_RETF, 3, 3, 4), EOFValidationError::success); - add_test_case(eof_bytecode(1023 * push(1) + OP_CALLF + "0001" + 1021 * OP_POP + OP_RETURN, 1023) + add_test_case(eof_bytecode(1023 * push(1) + callf(1) + 1021 * OP_POP + OP_RETURN, 1023) .code(push0() + push0() + OP_RETF, 3, 5, 5), EOFValidationError::stack_overflow); - add_test_case(eof_bytecode(1023 * push(1) + OP_CALLF + "0001" + 1021 * OP_POP + OP_RETURN, 1023) + add_test_case(eof_bytecode(1023 * push(1) + callf(1) + 1021 * OP_POP + OP_RETURN, 1023) .code(push0() + push0() + OP_POP + OP_POP + OP_RETF, 3, 3, 5), EOFValidationError::stack_overflow); - add_test_case(eof_bytecode(1024 * push(1) + OP_CALLF + "0001" + 1020 * OP_POP + OP_RETURN, 1023) + add_test_case(eof_bytecode(1024 * push(1) + callf(1) + 1020 * OP_POP + OP_RETURN, 1023) .code(push0() + OP_POP + OP_POP + OP_POP + OP_RETF, 2, 0, 3), EOFValidationError::stack_overflow); add_test_case( - eof_bytecode(1023 * push(1) + OP_CALLF + "0001" + 1020 * OP_POP + OP_RETURN, 1023) + eof_bytecode(1023 * push(1) + callf(1) + 1020 * OP_POP + OP_RETURN, 1023) .code(push0() + push0() + OP_POP + OP_POP + OP_POP + OP_POP + OP_RETF, 2, 0, 4), EOFValidationError::stack_overflow); } @@ -89,18 +924,37 @@ TEST_F(eof_validation, callf_with_inputs_stack_overflow) TEST_F(eof_validation, retf_stack_validation) { // 2 outputs, RETF has 2 values on stack - add_test_case(eof_bytecode(bytecode{OP_CALLF} + "0001" + OP_STOP, 2) - .code(push0() + push0() + OP_RETF, 0, 2, 2), + add_test_case(eof_bytecode(callf(1) + OP_STOP, 2).code(push0() + push0() + OP_RETF, 0, 2, 2), EOFValidationError::success); // 2 outputs, RETF has 1 value on stack - add_test_case( - eof_bytecode(bytecode{OP_CALLF} + "0001" + OP_STOP, 2).code(push0() + OP_RETF, 0, 2, 1), + add_test_case(eof_bytecode(callf(1) + OP_STOP, 2).code(push0() + OP_RETF, 0, 2, 1), EOFValidationError::stack_underflow); // 2 outputs, RETF has 3 values on stack - add_test_case(eof_bytecode(bytecode{OP_CALLF} + "0001" + OP_STOP, 2) - .code(push0() + push0() + push0() + OP_RETF, 0, 2, 3), + add_test_case( + eof_bytecode(callf(1) + OP_STOP, 2).code(push0() + push0() + push0() + OP_RETF, 0, 2, 3), + EOFValidationError::stack_higher_than_outputs_required); +} + +TEST_F(eof_validation, retf_variable_stack) +{ + // RETF in variable stack segment is not allowed and always fails with one of two errors + + // RETF from [1, 3] stack returning 5 outputs + auto code = eof_bytecode(callf(1) + OP_STOP, 5).code(varstack + OP_RETF, 0, 5, 3); + add_test_case(code, EOFValidationError::stack_underflow); + + // RETF from [1, 3] stack returning 3 outputs + add_test_case(eof_bytecode(callf(1) + OP_STOP, 3).code(varstack + OP_RETF, 0, 3, 3), + EOFValidationError::stack_underflow); + + // RETF from [1, 3] stack returning 1 output + add_test_case(eof_bytecode(callf(1) + OP_STOP, 1).code(varstack + OP_RETF, 0, 1, 3), + EOFValidationError::stack_higher_than_outputs_required); + + // RETF from [1, 3] returning 0 outputs + add_test_case(eof_bytecode(callf(1) + OP_STOP, 0).code(varstack + OP_RETF, 0, 0, 3), EOFValidationError::stack_higher_than_outputs_required); } @@ -109,20 +963,20 @@ TEST_F(eof_validation, jumpf_to_returning) // JUMPF into a function with the same number of outputs as current one // Exactly required inputs on stack at JUMPF - add_test_case(eof_bytecode(bytecode{OP_CALLF} + "0001" + OP_STOP, 2) - .code(push0() + push0() + push0() + OP_JUMPF + "0002", 0, 2, 3) + add_test_case(eof_bytecode(callf(1) + OP_STOP, 2) + .code(push0() + push0() + push0() + jumpf(2), 0, 2, 3) .code(bytecode(OP_POP) + OP_RETF, 3, 2, 3), EOFValidationError::success); // Extra items on stack at JUMPF add_test_case(eof_bytecode(bytecode{OP_STOP}) - .code(push0() + push0() + push0() + push0() + OP_JUMPF + "0002", 0, 2, 4) + .code(push0() + push0() + push0() + push0() + jumpf(2), 0, 2, 4) .code(bytecode(OP_POP) + OP_RETF, 3, 2, 3), EOFValidationError::stack_higher_than_outputs_required); // Not enough inputs on stack at JUMPF add_test_case(eof_bytecode(bytecode{OP_STOP}) - .code(push0() + push0() + OP_JUMPF + "0002", 0, 2, 2) + .code(push0() + push0() + jumpf(2), 0, 2, 2) .code(bytecode(OP_POP) + OP_RETF, 3, 2, 3), EOFValidationError::stack_underflow); @@ -130,25 +984,55 @@ TEST_F(eof_validation, jumpf_to_returning) // (0, 2) --JUMPF--> (3, 1): 3 inputs + 1 output = 4 items required // Exactly required inputs on stack at JUMPF - add_test_case(eof_bytecode(bytecode{OP_CALLF} + "0001" + OP_STOP, 2) - .code(push0() + push0() + push0() + push0() + OP_JUMPF + "0002", 0, 2, 4) + add_test_case(eof_bytecode(callf(1) + OP_STOP, 2) + .code(push0() + push0() + push0() + push0() + jumpf(2), 0, 2, 4) .code(bytecode(OP_POP) + OP_POP + OP_RETF, 3, 1, 3), EOFValidationError::success); // Extra items on stack at JUMPF add_test_case( eof_bytecode(bytecode{OP_STOP}) - .code(push0() + push0() + push0() + push0() + push0() + OP_JUMPF + "0002", 0, 2, 5) - .code(bytecode(OP_POP) + OP_POP + OP_RETF, 3, 1, 3), + .code(push0() + push0() + push0() + push0() + push0() + jumpf(2), 0, 2, 5) + .code(bytecode(OP_POP) + OP_POP + OP_RETF, 3, 1, 3), EOFValidationError::stack_higher_than_outputs_required); // Not enough inputs on stack at JUMPF add_test_case(eof_bytecode(bytecode{OP_STOP}) - .code(push0() + push0() + push0() + OP_JUMPF + "0002", 0, 2, 3) + .code(push0() + push0() + push0() + jumpf(2), 0, 2, 3) .code(bytecode(OP_POP) + OP_POP + OP_RETF, 3, 1, 3), EOFValidationError::stack_underflow); } +TEST_F(eof_validation, jumpf_to_returning_variable_stack) +{ + // JUMPF to returning function in variable stack segment is not allowed and always fails with + // one of two errors + + // JUMPF from [1, 3] stack to function with 5 inputs + add_test_case(eof_bytecode(callf(1) + OP_STOP, 3) + .code(varstack + jumpf(2), 0, 3, 3) + .code(push0() + OP_RETF, 5, 3, 3), + EOFValidationError::stack_underflow); + + // JUMPF from [1, 3] stack to function with 3 inputs + add_test_case(eof_bytecode(callf(1) + OP_STOP, 3) + .code(varstack + jumpf(2), 0, 3, 3) + .code(bytecode{OP_RETF}, 3, 3, 3), + EOFValidationError::stack_underflow); + + // JUMPF from [1, 3] stack to function with 1 inputs + add_test_case(eof_bytecode(callf(1) + OP_STOP, 3) + .code(varstack + jumpf(2), 0, 3, 3) + .code(push0() + push0() + OP_RETF, 1, 3, 5), + EOFValidationError::stack_higher_than_outputs_required); + + // JUMPF from [1, 3] stack to function with 0 inputs + add_test_case(eof_bytecode(callf(1) + OP_STOP, 3) + .code(varstack + jumpf(2), 0, 3, 3) + .code(push0() + push0() + push0() + OP_RETF, 0, 3, 3), + EOFValidationError::stack_higher_than_outputs_required); +} + TEST_F(eof_validation, jumpf_to_nonreturning) { // Exactly required inputs on stack at JUMPF @@ -164,38 +1048,54 @@ TEST_F(eof_validation, jumpf_to_nonreturning) EOFValidationError::stack_underflow); } -TEST_F(eof_validation, jumpf_stack_overflow) +TEST_F(eof_validation, jumpf_to_nonreturning_variable_stack) { - add_test_case(eof_bytecode(512 * push(1) + OP_JUMPF + bytecode{"0x0000"_hex}, 512), + // JUMPF from [1, 3] stack to non-returning function with 5 inputs + add_test_case(eof_bytecode(varstack + jumpf(1), 3).code(Opcode{OP_INVALID}, 5, 0x80, 5), + EOFValidationError::stack_underflow); + + // JUMPF from [1, 3] stack to non-returning function with 3 inputs + add_test_case(eof_bytecode(varstack + jumpf(1), 3).code(Opcode{OP_INVALID}, 3, 0x80, 3), + EOFValidationError::stack_underflow); + + // Extra items on stack are allowed for JUMPF to non-returning + + // JUMPF from [1, 3] stack to non-returning function with 1 inputs + add_test_case(eof_bytecode(varstack + jumpf(1), 3).code(Opcode{OP_INVALID}, 1, 0x80, 1), EOFValidationError::success); - add_test_case(eof_bytecode(513 * push(1) + OP_JUMPF + bytecode{"0x0000"_hex}, 513), - EOFValidationError::stack_overflow); + // JUMPF from [1, 3] stack to non-returning function with 0 inputs + add_test_case(eof_bytecode(varstack + jumpf(1), 3).code(Opcode{OP_INVALID}, 0, 0x80, 0), + EOFValidationError::success); +} - add_test_case(eof_bytecode(1023 * push(1) + OP_JUMPF + bytecode{"0x0000"_hex}, 1023), - EOFValidationError::stack_overflow); +TEST_F(eof_validation, jumpf_stack_overflow) +{ + add_test_case(eof_bytecode(512 * push(1) + jumpf(0), 512), EOFValidationError::success); + + add_test_case(eof_bytecode(513 * push(1) + jumpf(0), 513), EOFValidationError::stack_overflow); add_test_case( - eof_bytecode(1023 * push(1) + OP_JUMPF + "0001", 1023).code(push0() + OP_STOP, 0, 0x80, 1), + eof_bytecode(1023 * push(1) + jumpf(0), 1023), EOFValidationError::stack_overflow); + + add_test_case(eof_bytecode(1023 * push(1) + jumpf(1), 1023).code(push0() + OP_STOP, 0, 0x80, 1), EOFValidationError::success); - add_test_case(eof_bytecode(1023 * push(1) + OP_JUMPF + "0001", 1023) - .code(push0() + push0() + OP_STOP, 0, 0x80, 2), + add_test_case( + eof_bytecode(1023 * push(1) + jumpf(1), 1023).code(push0() + push0() + OP_STOP, 0, 0x80, 2), EOFValidationError::stack_overflow); } TEST_F(eof_validation, jumpf_with_inputs_stack_overflow) { - add_test_case( - eof_bytecode(1023 * push0() + OP_JUMPF + "0001", 1023).code(push0() + OP_STOP, 2, 0x80, 3), + add_test_case(eof_bytecode(1023 * push0() + jumpf(1), 1023).code(push0() + OP_STOP, 2, 0x80, 3), EOFValidationError::success); - add_test_case(eof_bytecode(1023 * push0() + OP_JUMPF + "0001", 1023) - .code(push0() + push0() + OP_STOP, 2, 0x80, 4), + add_test_case( + eof_bytecode(1023 * push0() + jumpf(1), 1023).code(push0() + push0() + OP_STOP, 2, 0x80, 4), EOFValidationError::stack_overflow); - add_test_case( - eof_bytecode(1024 * push0() + OP_JUMPF + "0001", 1023).code(push0() + OP_STOP, 2, 0x80, 3), + add_test_case(eof_bytecode(1024 * push0() + jumpf(1), 1023).code(push0() + OP_STOP, 2, 0x80, 3), EOFValidationError::stack_overflow); } From ea268954ddd90c5acadb1401cc55950c25e9b7ff Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Fri, 16 Feb 2024 17:57:33 +0100 Subject: [PATCH 04/10] Add more stack overflow validation tests --- test/unittests/eof_stack_validation_test.cpp | 145 +++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/test/unittests/eof_stack_validation_test.cpp b/test/unittests/eof_stack_validation_test.cpp index 4161d61c1d..7cebdd9771 100644 --- a/test/unittests/eof_stack_validation_test.cpp +++ b/test/unittests/eof_stack_validation_test.cpp @@ -893,6 +893,38 @@ TEST_F(eof_validation, callf_stack_overflow) EOFValidationError::stack_overflow); } +TEST_F(eof_validation, callf_stack_overflow_variable_stack) +{ + add_test_case(eof_bytecode(varstack + 509 * push(1) + callf(1) + OP_STOP, 512) + .code(512 * push(1) + 512 * OP_POP + OP_RETF, 0, 0, 512), + EOFValidationError::success); + + // CALLF from [510, 512] stack to function with 515 max stack - both min and max stack overflow + add_test_case(eof_bytecode(varstack + 509 * push(1) + callf(1) + OP_STOP, 512) + .code(515 * push(1) + 515 * OP_POP + OP_RETF, 0, 0, 515), + EOFValidationError::stack_overflow); + + // CALLF from [510, 512] stack to function with 514 max stack - only max stack overflow + add_test_case(eof_bytecode(varstack + 509 * push(1) + callf(1) + OP_STOP, 512) + .code(514 * push(1) + 514 * OP_POP + OP_RETF, 0, 0, 514), + EOFValidationError::stack_overflow); + + // CALLF from [1021, 1023] stack to function with 1 max stack + add_test_case(eof_bytecode(varstack + 1020 * push(1) + callf(1) + OP_STOP, 1023) + .code(push0() + OP_POP + OP_RETF, 0, 0, 1), + EOFValidationError::success); + + // CALLF from [1021, 1023] stack to function with 5 max stack - both min and max stack overflow + add_test_case(eof_bytecode(varstack + 1020 * push(1) + callf(1) + OP_STOP, 1023) + .code(5 * push0() + 5 * OP_POP + OP_RETF, 0, 0, 5), + EOFValidationError::stack_overflow); + + // CALLF from [1021, 1023] stack to function with 2 max stack - only max stack overflow + add_test_case(eof_bytecode(varstack + 1020 * push(1) + callf(1) + OP_STOP, 1023) + .code(push0() + push0() + OP_POP + OP_POP + OP_RETF, 0, 0, 2), + EOFValidationError::stack_overflow); +} + TEST_F(eof_validation, callf_with_inputs_stack_overflow) { add_test_case(eof_bytecode(1023 * push(1) + callf(1) + 1019 * OP_POP + OP_RETURN, 1023) @@ -921,6 +953,59 @@ TEST_F(eof_validation, callf_with_inputs_stack_overflow) EOFValidationError::stack_overflow); } +TEST_F(eof_validation, callf_with_inputs_stack_overflow_variable_stack) +{ + // CALLF from [1021, 1023] stack to function with 2 inputs and 2 max stack + add_test_case(eof_bytecode(varstack + 1020 * push(1) + callf(1) + OP_STOP, 1023) + .code(bytecode{OP_POP} + OP_POP + OP_RETF, 2, 0, 2), + EOFValidationError::success); + + // CALLF from [1021, 1023] stack to function with 3 inputs and 4 max stack + add_test_case(eof_bytecode(varstack + 1020 * push(1) + callf(1) + OP_STOP, 1023) + .code(push(1) + OP_POP + OP_RETF, 3, 3, 4), + EOFValidationError::success); + + // CALLF from [1021, 1023] stack to 3 inputs and 7 outputs - min and max stack overflow + add_test_case(eof_bytecode(varstack + 1020 * push(1) + callf(1) + OP_STOP, 1023) + .code(4 * push0() + OP_RETF, 3, 7, 7), + EOFValidationError::stack_overflow); + + // CALLF from [1021, 1023] stack to 3 inputs and 5 outputs - only max stack overflow + add_test_case(eof_bytecode(varstack + 1020 * push(1) + callf(1) + OP_STOP, 1023) + .code(push0() + push0() + OP_RETF, 3, 5, 5), + EOFValidationError::stack_overflow); + + // CALLF from [1021, 1023] stack to 3 inputs and 7 max stack - min and max stack overflow + add_test_case(eof_bytecode(varstack + 1020 * push(1) + callf(1) + OP_STOP, 1023) + .code(4 * push0() + OP_POP + OP_POP + OP_RETF, 3, 3, 7), + EOFValidationError::stack_overflow); + + // CALLF from [1021, 1023] stack to 3 inputs and 5 max stack - only max stack overflow + add_test_case(eof_bytecode(varstack + 1020 * push(1) + callf(1) + OP_STOP, 1023) + .code(push0() + push0() + OP_POP + OP_POP + OP_RETF, 3, 3, 5), + EOFValidationError::stack_overflow); + + // CALLF from [1022, 1024] stack to 2 inputs and 5 max stack - min and max stack overflow + add_test_case(eof_bytecode(varstack + 1021 * push(1) + callf(1) + OP_STOP, 1023) + .code(3 * push0() + 5 * OP_POP + OP_RETF, 2, 0, 5), + EOFValidationError::stack_overflow); + + // CALLF from [1022, 1024] stack to 2 inputs and 3 max stack - only max stack overflow + add_test_case(eof_bytecode(varstack + 1021 * push(1) + callf(1) + OP_STOP, 1023) + .code(push0() + OP_POP + OP_POP + OP_POP + OP_RETF, 2, 0, 3), + EOFValidationError::stack_overflow); + + // CALLF from [1021, 1023] stack to 2 inputs and 6 max stack - min and max stack overflow + add_test_case(eof_bytecode(varstack + 1020 * push(1) + callf(1) + OP_STOP, 1023) + .code(4 * push0() + 6 * OP_POP + OP_RETF, 2, 0, 6), + EOFValidationError::stack_overflow); + + // CALLF from [1021, 1023] stack to 2 inputs and 4 max stack - only max stack overflow + add_test_case(eof_bytecode(varstack + 1020 * push(1) + callf(1) + OP_STOP, 1023) + .code(push0() + push0() + 4 * OP_POP + OP_RETF, 2, 0, 4), + EOFValidationError::stack_overflow); +} + TEST_F(eof_validation, retf_stack_validation) { // 2 outputs, RETF has 2 values on stack @@ -1086,6 +1171,37 @@ TEST_F(eof_validation, jumpf_stack_overflow) EOFValidationError::stack_overflow); } +TEST_F(eof_validation, jumpf_stack_overflow_variable_stack) +{ + add_test_case( + eof_bytecode(varstack + 509 * OP_PUSH0 + jumpf(0), 512), EOFValidationError::success); + + // JUMPF from [510, 512] stack to function with 515 max stack - both min and max stack overflow + add_test_case(eof_bytecode(varstack + 509 * OP_PUSH0 + jumpf(1), 512) + .code(515 * OP_PUSH0 + OP_STOP, 0, 0x80, 515), + EOFValidationError::stack_overflow); + + // JUMPF from [510, 512] stack to function with 514 max stack - only max stack overflow + add_test_case(eof_bytecode(varstack + 509 * OP_PUSH0 + jumpf(1), 512) + .code(514 * OP_PUSH0 + OP_STOP, 0, 0x80, 514), + EOFValidationError::stack_overflow); + + // JUMPF from [1021, 1023] stack to function with 1 max stack + add_test_case(eof_bytecode(varstack + 1020 * OP_PUSH0 + jumpf(1), 1023) + .code(push0() + OP_STOP, 0, 0x80, 1), + EOFValidationError::success); + + // JUMPF from [1021, 1023] stack to function with 5 max stack - both min and max stack overflow + add_test_case(eof_bytecode(varstack + 1020 * OP_PUSH0 + jumpf(1), 1023) + .code(5 * push0() + OP_STOP, 0, 0x80, 5), + EOFValidationError::stack_overflow); + + // JUMPF from [1021, 1023] stack to function with 2 max stack - only max stack overflow + add_test_case(eof_bytecode(varstack + 1020 * OP_PUSH0 + jumpf(1), 1023) + .code(push0() + push0() + OP_STOP, 0, 0x80, 2), + EOFValidationError::stack_overflow); +} + TEST_F(eof_validation, jumpf_with_inputs_stack_overflow) { add_test_case(eof_bytecode(1023 * push0() + jumpf(1), 1023).code(push0() + OP_STOP, 2, 0x80, 3), @@ -1099,6 +1215,35 @@ TEST_F(eof_validation, jumpf_with_inputs_stack_overflow) EOFValidationError::stack_overflow); } + +TEST_F(eof_validation, jumpf_with_inputs_stack_overflow_variable_stack) +{ + // JUMPF from [1021, 1023] stack to 2 inputs and 3 max stack + add_test_case(eof_bytecode(varstack + 1020 * push0() + jumpf(1), 1023) + .code(push0() + OP_STOP, 2, 0x80, 3), + EOFValidationError::success); + + // JUMPF from [1021, 1023] stack to 2 inputs and 6 max stack - min and max stack overflow + add_test_case(eof_bytecode(varstack + 1020 * push0() + jumpf(1), 1023) + .code(4 * push0() + OP_STOP, 2, 0x80, 6), + EOFValidationError::stack_overflow); + + // JUMPF from [1021, 1023] stack to 2 inputs and 4 max stack - only max stack overflow + add_test_case(eof_bytecode(varstack + 1020 * push0() + jumpf(1), 1023) + .code(push0() + push0() + OP_STOP, 2, 0x80, 4), + EOFValidationError::stack_overflow); + + // JUMPF from [1022, 1024] stack to 2 inputs and 5 max stack - min and max stack overflow + add_test_case(eof_bytecode(varstack + 1021 * push0() + jumpf(1), 1023) + .code(3 * push0() + OP_STOP, 2, 0x80, 5), + EOFValidationError::stack_overflow); + + // JUMPF from [1022, 1024] stack to 2 inputs and 3 max stack - only max stack overflow + add_test_case(eof_bytecode(varstack + 1021 * push0() + jumpf(1), 1023) + .code(push0() + OP_STOP, 2, 0x80, 3), + EOFValidationError::stack_overflow); +} + TEST_F(eof_validation, dupn_stack_validation) { const auto pushes = 20 * push(1); From fa3b4a642a66d3c7063d21bab729544b2009abf4 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Tue, 13 Feb 2024 12:34:12 +0100 Subject: [PATCH 05/10] Add tests for unreachable code and no terminating instruction --- test/unittests/eof_stack_validation_test.cpp | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/unittests/eof_stack_validation_test.cpp b/test/unittests/eof_stack_validation_test.cpp index 7cebdd9771..b7253568ed 100644 --- a/test/unittests/eof_stack_validation_test.cpp +++ b/test/unittests/eof_stack_validation_test.cpp @@ -18,6 +18,28 @@ namespace const auto varstack = push0() + rjumpi(2, 0) + push0() + push0(); } // namespace +TEST_F(eof_validation, unreachable_instructions) +{ + add_test_case( + eof_bytecode(bytecode{OP_STOP} + OP_STOP), EOFValidationError::unreachable_instructions); + + add_test_case( + eof_bytecode(rjump(1) + OP_STOP + OP_STOP), EOFValidationError::unreachable_instructions); + + // STOP reachable only via backwards jump - invalid + add_test_case( + eof_bytecode(rjump(1) + OP_STOP + rjump(-4)), EOFValidationError::unreachable_instructions); +} + +TEST_F(eof_validation, no_terminating_instruction) +{ + add_test_case(eof_bytecode(push0()), EOFValidationError::no_terminating_instruction); + + add_test_case(eof_bytecode(add(1, 2)), EOFValidationError::no_terminating_instruction); + + add_test_case(eof_bytecode(rjumpi(-5, 1)), EOFValidationError::no_terminating_instruction); +} + TEST_F(eof_validation, non_constant_stack_height) { // Final "OP_PUSH0 + OP_PUSH0 + OP_REVERT" can be reached with stack heights: 0, 2 or 1 From 25de43ba7f3f95a8175f99eabdcd22ce57dd1f03 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Wed, 7 Feb 2024 12:44:37 +0100 Subject: [PATCH 06/10] Fix tests cases with unreachable sections --- test/unittests/eof_stack_validation_test.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/unittests/eof_stack_validation_test.cpp b/test/unittests/eof_stack_validation_test.cpp index b7253568ed..bb67bf61ca 100644 --- a/test/unittests/eof_stack_validation_test.cpp +++ b/test/unittests/eof_stack_validation_test.cpp @@ -1076,13 +1076,13 @@ TEST_F(eof_validation, jumpf_to_returning) EOFValidationError::success); // Extra items on stack at JUMPF - add_test_case(eof_bytecode(bytecode{OP_STOP}) + add_test_case(eof_bytecode(callf(1) + OP_STOP, 2) .code(push0() + push0() + push0() + push0() + jumpf(2), 0, 2, 4) .code(bytecode(OP_POP) + OP_RETF, 3, 2, 3), EOFValidationError::stack_higher_than_outputs_required); // Not enough inputs on stack at JUMPF - add_test_case(eof_bytecode(bytecode{OP_STOP}) + add_test_case(eof_bytecode(callf(1) + OP_STOP, 2) .code(push0() + push0() + jumpf(2), 0, 2, 2) .code(bytecode(OP_POP) + OP_RETF, 3, 2, 3), EOFValidationError::stack_underflow); @@ -1098,13 +1098,13 @@ TEST_F(eof_validation, jumpf_to_returning) // Extra items on stack at JUMPF add_test_case( - eof_bytecode(bytecode{OP_STOP}) - .code(push0() + push0() + push0() + push0() + push0() + jumpf(2), 0, 2, 5) - .code(bytecode(OP_POP) + OP_POP + OP_RETF, 3, 1, 3), + eof_bytecode(callf(1) + OP_STOP, 2) + .code(push0() + push0() + push0() + push0() + push0() + jumpf(2), 0, 2, 5) + .code(bytecode(OP_POP) + OP_POP + OP_RETF, 3, 1, 3), EOFValidationError::stack_higher_than_outputs_required); // Not enough inputs on stack at JUMPF - add_test_case(eof_bytecode(bytecode{OP_STOP}) + add_test_case(eof_bytecode(callf(1) + OP_STOP, 2) .code(push0() + push0() + push0() + jumpf(2), 0, 2, 3) .code(bytecode(OP_POP) + OP_POP + OP_RETF, 3, 1, 3), EOFValidationError::stack_underflow); From 202e9a89d5ec5fe155a21de5d41ff1ecdc95c57c Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Fri, 16 Feb 2024 17:44:33 +0100 Subject: [PATCH 07/10] Add more JUMPF stack validation test cases --- test/unittests/eof_stack_validation_test.cpp | 72 ++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/test/unittests/eof_stack_validation_test.cpp b/test/unittests/eof_stack_validation_test.cpp index bb67bf61ca..4e9c4509f4 100644 --- a/test/unittests/eof_stack_validation_test.cpp +++ b/test/unittests/eof_stack_validation_test.cpp @@ -1069,6 +1069,18 @@ TEST_F(eof_validation, jumpf_to_returning) { // JUMPF into a function with the same number of outputs as current one + // 0 inputs target + add_test_case(eof_bytecode(callf(1) + OP_STOP, 2) + .code(jumpf(2), 0, 2, 0) + .code(push0() + push0() + OP_RETF, 0, 2, 2), + EOFValidationError::success); + + // 0 inputs target - extra items on stack at JUMPF + add_test_case(eof_bytecode(callf(1) + OP_STOP, 2) + .code(push0() + push0() + jumpf(2), 0, 2, 2) + .code(push0() + push0() + OP_RETF, 0, 2, 2), + EOFValidationError::stack_higher_than_outputs_required); + // Exactly required inputs on stack at JUMPF add_test_case(eof_bytecode(callf(1) + OP_STOP, 2) .code(push0() + push0() + push0() + jumpf(2), 0, 2, 3) @@ -1088,6 +1100,27 @@ TEST_F(eof_validation, jumpf_to_returning) EOFValidationError::stack_underflow); // JUMPF into a function with fewer outputs than current one + + // (0, 2) --JUMPF--> (0, 1): 0 inputs + 1 output = 1 item required + + // Exactly required inputs on stack at JUMPF + add_test_case(eof_bytecode(callf(1) + OP_STOP, 2) + .code(push0() + jumpf(2), 0, 2, 1) + .code(push0() + OP_RETF, 0, 1, 1), + EOFValidationError::success); + + // Extra items on stack at JUMPF + add_test_case(eof_bytecode(callf(1) + OP_STOP, 2) + .code(push0() + push0() + push0() + jumpf(2), 0, 2, 3) + .code(push0() + OP_RETF, 0, 1, 1), + EOFValidationError::stack_higher_than_outputs_required); + + // Not enough inputs on stack at JUMPF + add_test_case(eof_bytecode(callf(1) + OP_STOP, 2) + .code(jumpf(2), 0, 2, 0) + .code(push0() + OP_RETF, 0, 1, 1), + EOFValidationError::stack_underflow); + // (0, 2) --JUMPF--> (3, 1): 3 inputs + 1 output = 4 items required // Exactly required inputs on stack at JUMPF @@ -1115,6 +1148,8 @@ TEST_F(eof_validation, jumpf_to_returning_variable_stack) // JUMPF to returning function in variable stack segment is not allowed and always fails with // one of two errors + // JUMPF into a function with the same number of outputs as current one + // JUMPF from [1, 3] stack to function with 5 inputs add_test_case(eof_bytecode(callf(1) + OP_STOP, 3) .code(varstack + jumpf(2), 0, 3, 3) @@ -1138,10 +1173,47 @@ TEST_F(eof_validation, jumpf_to_returning_variable_stack) .code(varstack + jumpf(2), 0, 3, 3) .code(push0() + push0() + push0() + OP_RETF, 0, 3, 3), EOFValidationError::stack_higher_than_outputs_required); + + // JUMPF into a function with fewer outputs than current one + + // JUMPF from [1, 3] stack to function with 5 inputs + add_test_case(eof_bytecode(callf(1) + OP_STOP, 2) + .code(varstack + jumpf(2), 0, 2, 3) + .code(4 * OP_POP + OP_RETF, 5, 1, 5), + EOFValidationError::stack_underflow); + + // JUMPF from [1, 3] stack to function with 3 inputs + add_test_case(eof_bytecode(callf(1) + OP_STOP, 2) + .code(varstack + jumpf(2), 0, 2, 3) + .code(bytecode{OP_POP} + OP_POP + bytecode{OP_RETF}, 3, 1, 3), + EOFValidationError::stack_underflow); + + // JUMPF from [1, 3] stack to function with 1 inputs + add_test_case(eof_bytecode(callf(1) + OP_STOP, 2) + .code(varstack + jumpf(2), 0, 2, 3) + .code(OP_RETF, 1, 1, 1), + EOFValidationError::stack_higher_than_outputs_required); + + // JUMPF from [1, 3] stack to function with 0 inputs + add_test_case(eof_bytecode(callf(1) + OP_STOP, 2) + .code(varstack + jumpf(2), 0, 2, 3) + .code(push0() + OP_RETF, 0, 1, 1), + EOFValidationError::stack_higher_than_outputs_required); } TEST_F(eof_validation, jumpf_to_nonreturning) { + // Target has 0 inputs + + // 0 inputs on stack at JUMPF + add_test_case(eof_bytecode(jumpf(1)).code(OP_STOP, 0, 0x80, 0), EOFValidationError::success); + + // Extra items on stack at JUMPF + add_test_case(eof_bytecode(push0() + push0() + jumpf(1), 2).code(OP_STOP, 0, 0x80, 0), + EOFValidationError::success); + + // Target has 3 inputs + // Exactly required inputs on stack at JUMPF add_test_case("EF0001 010008 02000200060001 040000 00 0080000303800003 5F5F5FE50001 00", EOFValidationError::success); From ab19da50f76ef1834354caf9467ad079c1585ec3 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Fri, 16 Feb 2024 17:59:27 +0100 Subject: [PATCH 08/10] Add test case for RETF reached via different paths --- test/unittests/eof_stack_validation_test.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/unittests/eof_stack_validation_test.cpp b/test/unittests/eof_stack_validation_test.cpp index 4e9c4509f4..33c6447a94 100644 --- a/test/unittests/eof_stack_validation_test.cpp +++ b/test/unittests/eof_stack_validation_test.cpp @@ -1042,6 +1042,13 @@ TEST_F(eof_validation, retf_stack_validation) add_test_case( eof_bytecode(callf(1) + OP_STOP, 2).code(push0() + push0() + push0() + OP_RETF, 0, 2, 3), EOFValidationError::stack_higher_than_outputs_required); + + // RETF in a helper (reached via different paths) + add_test_case( + eof_bytecode(push0() + callf(1) + OP_STOP, 2) + .code(rjumpi(7, {}) + push(1) + push(1) + rjump(2) + push0() + push0() + OP_RETF, 1, 2, + 2), + EOFValidationError::success); } TEST_F(eof_validation, retf_variable_stack) @@ -1130,10 +1137,9 @@ TEST_F(eof_validation, jumpf_to_returning) EOFValidationError::success); // Extra items on stack at JUMPF - add_test_case( - eof_bytecode(callf(1) + OP_STOP, 2) - .code(push0() + push0() + push0() + push0() + push0() + jumpf(2), 0, 2, 5) - .code(bytecode(OP_POP) + OP_POP + OP_RETF, 3, 1, 3), + add_test_case(eof_bytecode(callf(1) + OP_STOP, 2) + .code(push0() + push0() + push0() + push0() + push0() + jumpf(2), 0, 2, 5) + .code(bytecode(OP_POP) + OP_POP + OP_RETF, 3, 1, 3), EOFValidationError::stack_higher_than_outputs_required); // Not enough inputs on stack at JUMPF From 5887f0235bd062e420a406efca6f674a1e7e2e60 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Wed, 21 Feb 2024 11:05:05 +0100 Subject: [PATCH 09/10] Add test cases for maximally broad stack range and jumps to self --- test/unittests/eof_stack_validation_test.cpp | 47 ++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/test/unittests/eof_stack_validation_test.cpp b/test/unittests/eof_stack_validation_test.cpp index 33c6447a94..370d705832 100644 --- a/test/unittests/eof_stack_validation_test.cpp +++ b/test/unittests/eof_stack_validation_test.cpp @@ -62,6 +62,25 @@ TEST_F(eof_validation, non_constant_stack_height) EOFValidationError::stack_underflow); } +TEST_F(eof_validation, stack_range_maximally_broad) +{ + // Construct series of RJUMPIs all targeting final STOP. + // Stack range at STOP is [0, 1023] + bytecode code = OP_STOP; + int16_t offset = 1; + for (auto i = 0; i < 1023; ++i) + { + code = rjumpi(offset, OP_PUSH0) + OP_PUSH0 + code; + offset += 5; + } + + add_test_case(eof_bytecode(code, 1023), EOFValidationError::success, "valid_1023_rjumpis"); + + code = rjumpi(offset, OP_PUSH0) + OP_PUSH0 + code; + add_test_case(eof_bytecode(code, 1023), EOFValidationError::invalid_max_stack_height, + "invalid_1024_rjumpis"); +} + TEST_F(eof_validation, backwards_rjump) { add_test_case(eof_bytecode(rjump(-3)), EOFValidationError::success); @@ -802,6 +821,34 @@ TEST_F(eof_validation, forwards_rjumpv_variable_stack) EOFValidationError::success); } +TEST_F(eof_validation, self_referencing_jumps) +{ + // rjumpf from stack 0 to stack 0 + add_test_case(eof_bytecode(rjump(-3)), EOFValidationError::success, "rjump"); + + // rjumpi from stack 0 to stack 1 + add_test_case( + eof_bytecode(rjumpi(-3, 0) + OP_STOP), EOFValidationError::stack_height_mismatch, "rjumpi"); + + // rjumpv from stack 0 to stack 1 + add_test_case(eof_bytecode(rjumpv({-4}, 0) + OP_STOP), + EOFValidationError::stack_height_mismatch, "rjumpv"); +} + +TEST_F(eof_validation, self_referencing_jumps_variable_stack) +{ + // rjumpf from stack [1, 3] to stack [1, 3] + add_test_case(eof_bytecode(varstack + rjump(-3), 3), EOFValidationError::success, "rjump"); + + // rjumpi from stack [1, 3] to stack [2, 4] + add_test_case(eof_bytecode(varstack + rjumpi(-3, 0) + OP_STOP, 4), + EOFValidationError::stack_height_mismatch, "rjumpi"); + + // rjumpv from stack [1, 3] to stack [2, 4] + add_test_case(eof_bytecode(varstack + rjumpv({-4}, 0) + OP_STOP, 4), + EOFValidationError::stack_height_mismatch, "rjumpv"); +} + TEST_F(eof_validation, underflow) { add_test_case(eof_bytecode(bytecode{OP_ADD} + OP_STOP, 0), EOFValidationError::stack_underflow); From 9a8e160fd03214fcbafb6b33d1533389deb08fcf Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Wed, 21 Feb 2024 11:20:38 +0100 Subject: [PATCH 10/10] Update state tests --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index ff25db5b3a..67f17b5e73 100644 --- a/circle.yml +++ b/circle.yml @@ -535,7 +535,7 @@ jobs: ~/tests/EIPTests/BlockchainTests/ - download_execution_tests: repo: ipsilon/tests - rev: eof-unreachable-cs-20240122 + rev: eof-relaxed-stack-validation-20240221 legacy: false - run: name: "State tests (EOF)"