Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add evm bytes to python converter #357

Merged
merged 5 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
47 changes: 47 additions & 0 deletions src/entry_points/evm_bytes_to_python.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""
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 main(): # noqa: D103
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")