|
3 | 3 | opcode is supported by the fork supports and fails otherwise. |
4 | 4 | """ |
5 | 5 |
|
6 | | -from typing import Dict |
| 6 | +from typing import Dict, Iterator |
7 | 7 |
|
8 | 8 | import pytest |
9 | 9 | from execution_testing import ( |
|
19 | 19 | Transaction, |
20 | 20 | UndefinedOpcodes, |
21 | 21 | ) |
| 22 | +from execution_testing.base_types.base_types import ZeroPaddedHexNumber |
| 23 | +from execution_testing.forks import Byzantium |
22 | 24 |
|
23 | 25 | REFERENCE_SPEC_GIT_PATH = "N/A" |
24 | 26 | REFERENCE_SPEC_VERSION = "N/A" |
@@ -135,3 +137,64 @@ def test_cover_revert(state_test: StateTestFiller, pre: Alloc) -> None: |
135 | 137 | ) |
136 | 138 |
|
137 | 139 | state_test(env=Environment(), pre=pre, post={}, tx=tx) |
| 140 | + |
| 141 | + |
| 142 | +def fork_opcodes_increasing_stack( |
| 143 | + fork: Fork, |
| 144 | +) -> Iterator[Op]: |
| 145 | + """ |
| 146 | + Yields opcodes which are valid for `fork` and increase the operand stack. |
| 147 | + """ |
| 148 | + for opcode in fork.valid_opcodes(): |
| 149 | + if opcode.pushed_stack_items > opcode.popped_stack_items: |
| 150 | + yield opcode |
| 151 | + |
| 152 | + |
| 153 | +@pytest.mark.valid_from("Frontier") |
| 154 | +@pytest.mark.parametrize_by_fork("opcode", fork_opcodes_increasing_stack) |
| 155 | +@pytest.mark.parametrize("fails", [True, False]) |
| 156 | +def test_stack_overflow( |
| 157 | + state_test: StateTestFiller, |
| 158 | + pre: Alloc, |
| 159 | + fork: Fork, |
| 160 | + opcode: Op, |
| 161 | + fails: bool, |
| 162 | + env: Environment, |
| 163 | +) -> None: |
| 164 | + """Test that opcodes which leave new items on the stack can overflow.""" |
| 165 | + if cap := fork.transaction_gas_limit_cap(): |
| 166 | + env.gas_limit = ZeroPaddedHexNumber(cap) |
| 167 | + |
| 168 | + pre_stack_items = fork.max_stack_height() |
| 169 | + if not fails: |
| 170 | + pre_stack_items -= ( |
| 171 | + opcode.pushed_stack_items - opcode.popped_stack_items |
| 172 | + ) |
| 173 | + slot_code_worked = 1 |
| 174 | + value_code_failed = 0xDEADBEEF |
| 175 | + value_code_worked = 1 |
| 176 | + |
| 177 | + contract = pre.deploy_contract( |
| 178 | + code=Op.SSTORE(slot_code_worked, value_code_worked) |
| 179 | + + Op.PUSH1(0) * pre_stack_items |
| 180 | + + opcode |
| 181 | + + Op.STOP, |
| 182 | + storage={slot_code_worked: value_code_failed}, |
| 183 | + ) |
| 184 | + |
| 185 | + tx = Transaction( |
| 186 | + gas_limit=100_000, |
| 187 | + to=contract, |
| 188 | + sender=pre.fund_eoa(), |
| 189 | + protected=fork >= Byzantium, |
| 190 | + ) |
| 191 | + expected_storage = { |
| 192 | + slot_code_worked: value_code_failed if fails else value_code_worked |
| 193 | + } |
| 194 | + |
| 195 | + state_test( |
| 196 | + env=env, |
| 197 | + pre=pre, |
| 198 | + tx=tx, |
| 199 | + post={contract: Account(storage=expected_storage)}, |
| 200 | + ) |
0 commit comments