Skip to content

Commit

Permalink
feat: Add evm bytes to python converter (#357)
Browse files Browse the repository at this point in the history
* feat: add evm bytes to python converter

* entry_points/tests: add tests for evm bytes parser.

* evm_bytes_to_python: Add help

* changelog

---------

Co-authored-by: spencer-tb <[email protected]>
  • Loading branch information
marioevz and spencer-tb authored Jan 9, 2024
1 parent 7306261 commit e07bfbe
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Test fixtures for use by clients are available for each release on the [Github r
- ✨ Add a `--single-fixture-per-file` flag to generate one fixture JSON file per test case ([#331](https://github.com/ethereum/execution-spec-tests/pull/331)).
- 🔀 Rename test fixtures names to match the corresponding pytest node ID as generated using `fill` ([#342](https://github.com/ethereum/execution-spec-tests/pull/342)).
- 💥 Replace "=" with "_" in pytest node ids and test fixture names ([#342](https://github.com/ethereum/execution-spec-tests/pull/342)).
- ✨ Add `evm_bytes_to_python` command which converts EVM bytes to Python Opcodes ([#357](https://github.com/ethereum/execution-spec-tests/pull/357))
- 🔀 Locally calculate the transactions list's root instead of using the one returned by t8n when producing BlockchainTests ([#353](https://github.com/ethereum/execution-spec-tests/pull/353))
- ✨ Fork objects used to write tests can now be compared using the `>`, `>=`, `<`, `<=` operators, to check for a fork being newer than, newer than or equal, older than, older than or equal, respectively when compared against other fork ([#367](https://github.com/ethereum/execution-spec-tests/pull/367))
- 🐞 Storage type iterator is now fixed ([#369](https://github.com/ethereum/execution-spec-tests/pull/369))
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ console_scripts =
pyspelling_soft_fail = entry_points.pyspelling_soft_fail:main
markdownlintcli2_soft_fail = entry_points.markdownlintcli2_soft_fail:main
create_whitelist_for_flake8_spelling = entry_points.create_whitelist_for_flake8_spelling:main
evm_bytes_to_python = entry_points.evm_bytes_to_python:main

[options.extras_require]
test =
Expand Down
57 changes: 57 additions & 0 deletions src/entry_points/evm_bytes_to_python.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""
Define an entry point wrapper for pytest.
"""

import sys
from typing import Any, List, Optional

from ethereum_test_tools import Opcodes as Op


def process_evm_bytes(evm_bytes_hex_string: Any) -> str: # noqa: D103
if evm_bytes_hex_string.startswith("0x"):
evm_bytes_hex_string = evm_bytes_hex_string[2:]

evm_bytes = bytearray(bytes.fromhex(evm_bytes_hex_string))

opcodes_strings: List[str] = []

while evm_bytes:
opcode_byte = evm_bytes.pop(0)

opcode: Optional[Op] = None
for op in Op:
if op.int() == opcode_byte:
opcode = op
break

if opcode is None:
raise ValueError(f"Unknown opcode: {opcode_byte}")

if opcode.data_portion_length > 0:
data_portion = evm_bytes[: opcode.data_portion_length]
evm_bytes = evm_bytes[opcode.data_portion_length :]
opcodes_strings.append(f'Op.{opcode._name_}("0x{data_portion.hex()}")')
else:
opcodes_strings.append(f"Op.{opcode._name_}")

return " + ".join(opcodes_strings)


def print_help(): # noqa: D103
print("Usage: evm_bytes_to_python <EVM bytes hex string>")


def main(): # noqa: D103
if len(sys.argv) != 2:
print_help()
sys.exit(1)
if sys.argv[1] in ["-h", "--help"]:
print_help()
sys.exit(0)
evm_bytes_hex_string = sys.argv[1]
print(process_evm_bytes(evm_bytes_hex_string))


if __name__ == "__main__":
main()
3 changes: 3 additions & 0 deletions src/entry_points/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
Basic pytest applications `entry_points` unit tests.
"""
55 changes: 55 additions & 0 deletions src/entry_points/tests/test_evm_bytes_to_python.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""
Test suite for `entry_points.evm_bytes_to_python` module.
"""

import pytest
from evm_bytes_to_python import process_evm_bytes

from ethereum_test_tools import Opcodes as Op

basic_vector = [
"0x60008080808061AAAA612d5ff1600055",
'Op.PUSH1("0x00") + Op.DUP1 + Op.DUP1 + Op.DUP1 + Op.DUP1 + Op.PUSH2("0xaaaa") + Op.PUSH2("0x2d5f") + Op.CALL + Op.PUSH1("0x00") + Op.SSTORE', # noqa: E501
]
complex_vector = [
"0x7fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf5f527fc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedf6020527fe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff60405260786040356020355f35608a565b5f515f55602051600155604051600255005b5e56", # noqa: E501
'Op.PUSH32("0xa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf") + Op.PUSH0 + Op.MSTORE + Op.PUSH32("0xc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedf") + Op.PUSH1("0x20") + Op.MSTORE + Op.PUSH32("0xe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff") + Op.PUSH1("0x40") + Op.MSTORE + Op.PUSH1("0x78") + Op.PUSH1("0x40") + Op.CALLDATALOAD + Op.PUSH1("0x20") + Op.CALLDATALOAD + Op.PUSH0 + Op.CALLDATALOAD + Op.PUSH1("0x8a") + Op.JUMP + Op.JUMPDEST + Op.PUSH0 + Op.MLOAD + Op.PUSH0 + Op.SSTORE + Op.PUSH1("0x20") + Op.MLOAD + Op.PUSH1("0x01") + Op.SSTORE + Op.PUSH1("0x40") + Op.MLOAD + Op.PUSH1("0x02") + Op.SSTORE + Op.STOP + Op.JUMPDEST + Op.MCOPY + Op.JUMP', # noqa: E501
]


@pytest.mark.parametrize(
"evm_bytes, python_opcodes",
[
(basic_vector[0], basic_vector[1]),
(basic_vector[0][2:], basic_vector[1]), # no "0x" prefix
(complex_vector[0], complex_vector[1]),
(complex_vector[0][2:], complex_vector[1]), # no "0x" prefix
],
)
def test_evm_bytes_to_python(evm_bytes, python_opcodes):
"""Test evm_bytes_to_python using the basic and complex vectors"""
assert process_evm_bytes(evm_bytes) == python_opcodes


@pytest.mark.parametrize("opcode", list(Op))
def test_individual_opcodes(opcode):
"""Test each opcode individually"""
if opcode.data_portion_length > 0:
expected_output = f'Op.{opcode._name_}("0x")'
else:
expected_output = f"Op.{opcode._name_}"

bytecode = opcode.int().to_bytes(1, byteorder="big").hex()
assert process_evm_bytes("0x" + bytecode) == expected_output


def test_invalid_opcode():
"""Invalid hex string"""
with pytest.raises(ValueError):
process_evm_bytes("0xZZ")


def test_unknown_opcode():
"""Opcode not defined in Op"""
with pytest.raises(ValueError):
process_evm_bytes("0x0F")

0 comments on commit e07bfbe

Please sign in to comment.