forked from ethereum/execution-spec-tests
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Tests the assertsions in EIP-6206. Both container validation and runtime execution are validated. Signed-off-by: Danno Ferrin <danno@numisight.com>
- Loading branch information
Showing
7 changed files
with
381 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
""" | ||
Cross-client EVM Object Format Tests | ||
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,244 @@ | ||
""" | ||
EOF V1 Code Validation tests | ||
""" | ||
|
||
from typing import List | ||
|
||
from ethereum_test_tools import EOFException | ||
from ethereum_test_tools.eof.v1 import Container, Section | ||
from ethereum_test_tools.eof.v1.constants import NON_RETURNING_SECTION | ||
from ethereum_test_tools.vm.opcode import Opcodes as Op | ||
|
||
|
||
def quick_code(code, inputs=0, outputs=NON_RETURNING_SECTION, height=0): | ||
"""A sorter way to write code with section 0 defaults""" | ||
return Section.Code( | ||
code=code, code_inputs=inputs, code_outputs=outputs, max_stack_height=height | ||
) | ||
|
||
|
||
def container_name(c: Container): | ||
""" | ||
Return the name of the container for use in pytest ids. | ||
""" | ||
if hasattr(c, "name"): | ||
return c.name | ||
else: | ||
return c.__class__.__name__ | ||
|
||
|
||
def generate_jumpf_target_rules(): | ||
""" | ||
Generate tests for JUMPF where we are testing the validity of the JUNMPF target. | ||
We are not testing stack so a lot of the logic is to get correct stack values. | ||
""" | ||
valid = [] | ||
invalid = [] | ||
for current_outputs in [NON_RETURNING_SECTION, 0, 2, 4]: | ||
current_non_returning = current_outputs == NON_RETURNING_SECTION | ||
current_height = 0 if current_non_returning else current_outputs | ||
for target_outputs in [NON_RETURNING_SECTION, 0, 2, 4]: | ||
target_non_returning = target_outputs == NON_RETURNING_SECTION | ||
target_height = 0 if target_non_returning else target_outputs | ||
delta = ( | ||
0 | ||
if target_non_returning or current_non_returning | ||
else target_outputs - current_height | ||
) | ||
current_extra_push = max(0, current_height - target_height) | ||
current_section = Section.Code( | ||
code=Op.PUSH0 * (current_height) | ||
+ Op.CALLDATALOAD(0) | ||
+ Op.RJUMPI[1] | ||
+ (Op.STOP if current_non_returning else Op.RETF) | ||
+ Op.PUSH0 * current_extra_push | ||
+ Op.JUMPF[2], | ||
code_inputs=0, | ||
code_outputs=current_outputs, | ||
max_stack_height=current_height + max(1, current_extra_push), | ||
) | ||
target_section = Section.Code( | ||
code=((Op.PUSH0 * delta) if delta >= 0 else (Op.POP * -delta)) | ||
+ Op.CALLF[3] | ||
+ (Op.STOP if target_non_returning else Op.RETF), | ||
code_inputs=current_height, | ||
code_outputs=target_outputs, | ||
max_stack_height=max(current_height, current_height + delta), | ||
) | ||
|
||
container = Container( | ||
name="target_co-%s_to-%s" | ||
% ( | ||
"N" if current_non_returning else current_outputs, | ||
"N" if target_non_returning else target_outputs, | ||
), | ||
sections=[ | ||
quick_code(Op.JUMPF[1], height=0 if current_non_returning else current_height) | ||
if current_non_returning | ||
else quick_code( | ||
Op.CALLF[1](0, 0) + Op.STOP, | ||
height=0 if current_non_returning else 2 + current_outputs, | ||
), | ||
current_section, | ||
target_section, | ||
quick_code(Op.SSTORE(0, 1) + Op.RETF, outputs=0, height=2), | ||
], | ||
) | ||
|
||
# now sort validity... | ||
if target_non_returning: | ||
valid.append(container) | ||
elif current_non_returning or current_outputs < target_outputs: | ||
# both as non-returning handled above | ||
container.validity_error = EOFException.UNDEFINED_EXCEPTION | ||
invalid.append(container) | ||
else: | ||
# both are returning, and current >= target | ||
valid.append(container) | ||
return (valid, invalid) | ||
|
||
|
||
def generate_jumpf_stack_returning_rules(): | ||
""" | ||
Generate tests for JUMPF where we are testing the stack rules. Returning section cases | ||
""" | ||
valid = [] | ||
invalid = [] | ||
for current_outputs in [0, 2, 4]: | ||
for target_outputs in [x for x in [0, 2, 4] if x <= current_outputs]: | ||
for target_inputs in [0, 2, 4]: | ||
for stack_diff in [-1, 0, 1] if target_inputs > 0 else [0, 1]: | ||
target_delta = target_outputs - target_inputs | ||
container = Container( | ||
name="stack-retuning_co-%d_to-%d_ti-%d_diff-%d" | ||
% (current_outputs, target_outputs, target_inputs, stack_diff), | ||
sections=[ | ||
quick_code( | ||
Op.CALLF[1] + Op.SSTORE(0, 1) + Op.STOP, height=2 + current_outputs | ||
), | ||
quick_code( | ||
Op.PUSH0 * max(0, target_inputs + stack_diff) + Op.JUMPF[2], | ||
outputs=current_outputs, | ||
height=target_inputs, | ||
), | ||
quick_code( | ||
( | ||
Op.POP * -target_delta | ||
if target_delta < 0 | ||
else Op.PUSH0 * target_delta | ||
) | ||
+ Op.RETF, | ||
inputs=target_inputs, | ||
outputs=target_outputs, | ||
height=max(target_inputs, target_outputs), | ||
), | ||
], | ||
) | ||
|
||
if stack_diff == current_outputs - target_outputs: | ||
valid.append(container) | ||
else: | ||
container.validity_error = EOFException.UNDEFINED_EXCEPTION | ||
invalid.append(container) | ||
|
||
return (valid, invalid) | ||
|
||
|
||
def generate_jumpf_stack_non_returning_rules(): | ||
""" | ||
Generate tests for JUMPF where we are testing the stack rules. Non-returning section cases. | ||
""" | ||
valid = [] | ||
invalid = [] | ||
for stack_height in [0, 2, 4]: | ||
for target_inputs in [0, 2, 4]: | ||
container = Container( | ||
name="stack-non-retuning_h-%d_ti-%d" % (stack_height, target_inputs), | ||
sections=[ | ||
quick_code(Op.JUMPF[1]), | ||
quick_code( | ||
Op.PUSH0 * stack_height + Op.JUMPF[2], | ||
height=stack_height, | ||
), | ||
quick_code( | ||
Op.POP * target_inputs + Op.SSTORE(0, 1) + Op.STOP, | ||
inputs=target_inputs, | ||
height=target_inputs, | ||
), | ||
], | ||
) | ||
|
||
if stack_height >= target_inputs: | ||
valid.append(container) | ||
else: | ||
container.validity_error = EOFException.UNDEFINED_EXCEPTION | ||
invalid.append(container) | ||
|
||
return (valid, invalid) | ||
|
||
|
||
jump_forward = Container( | ||
name="jump_forward", | ||
sections=[quick_code(Op.JUMPF[1]), quick_code(Op.SSTORE(0, 1) + Op.STOP, height=2)], | ||
) | ||
jump_backward = Container( | ||
name="jump_backward", | ||
sections=[ | ||
quick_code(Op.CALLF[2] + Op.SSTORE(0, 1) + Op.STOP, height=2), | ||
quick_code(Op.RETF, outputs=0), | ||
quick_code(Op.JUMPF[1], outputs=0), | ||
], | ||
) | ||
jump_to_self = Container( | ||
name="jump_to_self", | ||
sections=[ | ||
quick_code( | ||
Op.SLOAD(0) + Op.ISZERO + Op.RJUMPI[1] + Op.STOP + Op.SSTORE(0, 1) + Op.JUMPF[0], | ||
height=2, | ||
) | ||
], | ||
) | ||
jump_too_large = Container( | ||
name="jump_too_large", | ||
sections=[quick_code(Op.JUMPF[1025])], | ||
validity_error=EOFException.UNDEFINED_EXCEPTION, | ||
) | ||
jump_way_too_large = Container( | ||
name="jump_way_too_large", | ||
sections=[quick_code(Op.JUMPF[0xFFFF])], | ||
validity_error=EOFException.UNDEFINED_EXCEPTION, | ||
) | ||
jump_non_existent_section = Container( | ||
name="jump_non_existent_section", | ||
sections=[quick_code(Op.JUMPF[5])], | ||
validity_error=EOFException.UNDEFINED_EXCEPTION, | ||
) | ||
callf_non_returning = Container( | ||
name="callf_non_returning", | ||
sections=[quick_code(Op.CALLF[1]), quick_code(Op.STOP, outputs=NON_RETURNING_SECTION)], | ||
validity_error=EOFException.UNDEFINED_EXCEPTION, | ||
) | ||
|
||
|
||
jumpf_targets = generate_jumpf_target_rules() | ||
jumpf_stack_returning = generate_jumpf_stack_returning_rules() | ||
jumpf_stack_non_returning = generate_jumpf_target_rules() | ||
|
||
VALID: List[Container] = [ | ||
jump_forward, | ||
jump_backward, | ||
jump_to_self, | ||
*jumpf_targets[0], | ||
*jumpf_stack_returning[0], | ||
*jumpf_stack_non_returning[0], | ||
] | ||
|
||
INVALID: List[Container] = [ | ||
jump_too_large, | ||
jump_too_large, | ||
jump_non_existent_section, | ||
callf_non_returning, | ||
*jumpf_targets[1], | ||
*jumpf_stack_returning[1], | ||
*jumpf_stack_non_returning[1], | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
""" | ||
EOF V1 Constants used throughout all tests | ||
""" | ||
|
||
EOF_FORK_NAME = "Prague" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
""" | ||
EOF V1 Code Validation tests | ||
""" | ||
import pytest | ||
|
||
from ethereum_test_tools import EOFTestFiller | ||
from ethereum_test_tools.eof.v1 import Container | ||
|
||
from .contracts import INVALID, VALID, container_name | ||
from .spec import EOF_FORK_NAME | ||
|
||
REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6206.md" | ||
REFERENCE_SPEC_VERSION = "a1775816657df4093787fb9fe83c2f7cc17ecf47" | ||
|
||
pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"container", | ||
VALID, | ||
ids=container_name, | ||
) | ||
def test_jumpf_code_validation_valid( | ||
eof_test: EOFTestFiller, | ||
container: Container, | ||
): | ||
""" | ||
Test creating various types of valid EOF V1 contracts using legacy | ||
initcode and a contract creating transaction. | ||
""" | ||
assert ( | ||
container.validity_error is None | ||
), f"Valid container with validity error: {container.validity_error}" | ||
eof_test( | ||
data=container, | ||
) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"container", | ||
INVALID, | ||
ids=container_name, | ||
) | ||
def test_jumpf_code_validation_invalid( | ||
eof_test: EOFTestFiller, | ||
container: Container, | ||
): | ||
""" | ||
Test creating various types of valid EOF V1 contracts using legacy | ||
initcode and a contract creating transaction. | ||
""" | ||
assert container.validity_error is not None, "Invalid container without validity error" | ||
eof_test( | ||
data=container, | ||
expect_exception=container.validity_error, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
""" | ||
Execution of CALLF, RETF opcodes within EOF V1 containers tests | ||
""" | ||
|
||
import pytest | ||
|
||
from ethereum_test_tools import ( | ||
Account, | ||
Address, | ||
Environment, | ||
StateTestFiller, | ||
TestAddress, | ||
Transaction, | ||
) | ||
from ethereum_test_tools.eof.v1 import Container | ||
|
||
from .contracts import VALID, container_name | ||
from .spec import EOF_FORK_NAME | ||
|
||
REFERENCE_SPEC_GIT_PATH = "EIPS/eip-6206.md" | ||
REFERENCE_SPEC_VERSION = "a1775816657df4093787fb9fe83c2f7cc17ecf47" | ||
|
||
pytestmark = pytest.mark.valid_from(EOF_FORK_NAME) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"container", | ||
VALID, | ||
ids=container_name, | ||
) | ||
def test_jumpf_execution( | ||
state_test: StateTestFiller, | ||
container: Container, | ||
): | ||
""" | ||
Test JUMPF valid contracts. All should end with the same canary | ||
""" | ||
assert ( | ||
container.validity_error is None | ||
), f"Valid container with validity error: {container.validity_error}" | ||
|
||
env = Environment() | ||
|
||
pre = { | ||
TestAddress: Account( | ||
balance=1000000000000000000000, | ||
nonce=1, | ||
), | ||
Address(0x100): Account( | ||
code=container, | ||
nonce=1, | ||
), | ||
} | ||
|
||
tx = Transaction( | ||
nonce=1, | ||
to=Address(0x100), | ||
gas_limit=44_000, | ||
gas_price=10, | ||
protected=False, | ||
data="1", | ||
) | ||
|
||
post = {Address(0x100): Account(storage={0: 1})} | ||
|
||
state_test( | ||
env=env, | ||
pre=pre, | ||
post=post, | ||
tx=tx, | ||
) |
Oops, something went wrong.