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(test): test opcode programs in different scenarios #808

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

winsvega
Copy link
Contributor

@winsvega winsvega commented Sep 16, 2024

🗒️ Description

Conversion of opcode diff places tests by ori.
A test defines series of test scenarios that are run on each parametrized opcode sequence.
Then we check if the sequence worked as expected in a given scenario.

I think this is a powerful method to template test any new given opcode.
we already have pre defined scenarios. then we just add one more parameter with what we want to test, and it will be covered on all the cases automatically.

Cases can be like:
callcode->staticcall-> [opcode]
create2-> [opcode]
[opcode] -> revert

check it out. so we can define opcode programs and scenarios. then the test will put each opcode program in each scenario and verify that it's result is the same (perhaps result will be complex depending on context and fork)

the idea is so far to have a template test and then we can easily just add opcode programms and it will be run in all crazy combinations.
likce call delegate call suicide revert and so on

This is still WIP.

🔗 Related Issues

#184

✅ Checklist

  • All: Set appropriate labels for the changes.
  • All: Considered squashing commits to improve commit history.
  • All: Added an entry to CHANGELOG.md.
  • All: Considered updating the online docs in the ./docs/ directory.
  • Tests: All converted JSON/YML tests from ethereum/tests have been added to converted-ethereum-tests.txt.
  • Tests: A PR with removal of converted JSON/YML tests from ethereum/tests have been opened.
  • Tests: Included the type and version of evm t8n tool used to locally execute test cases: e.g., ref with commit hash or geth 1.13.1-stable-3f40e65.
  • Tests: Ran mkdocs serve locally and verified the auto-generated docs for new tests in the Test Case Reference are correctly formatted.

@winsvega winsvega marked this pull request as draft September 16, 2024 10:28
Copy link
Member

@chfast chfast left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you give it any better name? The "opcode diff places" has no meaning.

@winsvega
Copy link
Contributor Author

it translates as opcode in different (logical) places

@winsvega winsvega closed this Sep 20, 2024
@winsvega winsvega reopened this Sep 20, 2024
@winsvega winsvega changed the title feat(test): opcode diff places test feat(test): test opcode programs in different scenarios Sep 20, 2024
@winsvega winsvega requested a review from marioevz September 20, 2024 13:37
Copy link
Member

@marioevz marioevz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few suggestions after a quick review. I haven't checked files in the ./scenarios/ scenarios/ folder, but will do on the re-review.

tests/frontier/scenarios/test_scenarios.py Outdated Show resolved Hide resolved
tests/frontier/scenarios/common.py Outdated Show resolved Hide resolved
tests/frontier/scenarios/common.py Show resolved Hide resolved
tests/frontier/scenarios/common.py Outdated Show resolved Hide resolved
@winsvega winsvega force-pushed the dailytest branch 2 times, most recently from b1af590 to ff2eeff Compare September 24, 2024 13:13
@winsvega winsvega force-pushed the dailytest branch 2 times, most recently from b28f2ef to f7de36c Compare October 17, 2024 10:02
@winsvega winsvega added scope:pytest Scope: Pytest plugins type:feat type: Feature labels Oct 28, 2024
@winsvega winsvega force-pushed the dailytest branch 11 times, most recently from 5d3dd84 to 940cb3e Compare October 31, 2024 14:12
@winsvega winsvega force-pushed the dailytest branch 3 times, most recently from 57c4bd1 to 7b44f08 Compare November 12, 2024 11:03
@danceratopz danceratopz self-requested a review November 19, 2024 13:38
@winsvega winsvega marked this pull request as ready for review December 12, 2024 09:59
@winsvega winsvega mentioned this pull request Dec 17, 2024
22 tasks
Copy link
Member

@marioevz marioevz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More comments.

I feel like the programs should be dynamic based on the fork, and the approach could be improved in the sense that the program could be a pytest.fixture that takes the fork as input and then resolves to the actual bytecode.

See the comment in the invalid_opcodes.py file regarding the list of opcodes being invalid, since that is going to be changed frequently depending on the fork, it's going to be a pain to maintain, but we have the tools in pytest to do the maintenance more automatic and seamless.

src/ethereum_test_forks/forks/forks.py Outdated Show resolved Hide resolved
tests/frontier/scenarios/programs/invalid_opcodes.py Outdated Show resolved Hide resolved
Comment on lines +99 to +120
combinations_input: ScenarioGeneratorInput = ScenarioGeneratorInput(
fork=fork,
pre=pre,
operation_code=operation,
external_address=external_address,
external_balance=external_balance,
)

call_combinations = ScenariosCallCombinations(combinations_input).generate()
for combination in call_combinations:
if not debug.scenario_name or combination.name == debug.scenario_name:
scenarios_list.append(combination)

call_combinations = scenarios_create_combinations(combinations_input)
for combination in call_combinations:
if not debug.scenario_name or combination.name == debug.scenario_name:
scenarios_list.append(combination)

revert_combinations = scenarios_revert_combinations(combinations_input)
for combination in revert_combinations:
if not debug.scenario_name or combination.name == debug.scenario_name:
scenarios_list.append(combination)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having the inputs as a separate structure that gets used in three different places is not a great pattern.

It seems to me like we should have a single class that can generate all types of combinations:

    combinations_generator: ScenarioGenerator = ScenarioGenerator(
        fork=fork,
        pre=pre,
        operation_code=operation,
        external_address=external_address,
        external_balance=external_balance,
    )

    for combination in combinations_generator.generate_call_combinations():
        if not debug.scenario_name or combination.name == debug.scenario_name:
            scenarios_list.append(combination)

    for combination in combinations_generator.generate_create_combinations():
        if not debug.scenario_name or combination.name == debug.scenario_name:
            scenarios_list.append(combination)

    for combination in combinations_generator.generate_revert_combinations():
        if not debug.scenario_name or combination.name == debug.scenario_name:
            scenarios_list.append(combination)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here generate_X is dynamic.
so ideally we iterate over array of functions and call each function with environment conditions.
and when we want to add new scenario generator it adds a function into that list

tests/frontier/scenarios/scenarios/call_combinations.py Outdated Show resolved Hide resolved
tests/frontier/scenarios/scenarios/call_combinations.py Outdated Show resolved Hide resolved
root_contract_balance = 105
scenario_contract_balance = 107
sub_contract_balance = 111
program_selfbalance = 113
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This specific property, program_selfbalance, might be better off as a property of the operation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here I calculate manually how much the selfbalance is supposed to be in this context. and pass it as expected result to verifier. if program return different value while checking selfbalance there will be an error
this is equvivalent to post state defenition

tests/frontier/scenarios/scenarios/call_combinations.py Outdated Show resolved Hide resolved
program_suicide,
],
)
def test_scenarios(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def test_scenarios(
@pytest.mark.execute(pytest.mark.skip(reason="gas usage"))
def test_scenarios(

This test has to be skipped in execute mode because it uses 500 million gas.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should not be that much. I need to correct

Copy link
Contributor Author

@winsvega winsvega Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can make a test per scenario. right now all scenarios combined in one test for each program.
that would be a lot of test files though, but easier for debug

gas_limit=500_000_000,
gas_price=tx_gasprice,
to=runner_contract,
data=b"0x11223344",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is what you meant:

Suggested change
data=b"0x11223344",
data=b"\x11\x22\x33\x44",

The current one creates a byte array with the ascii equivalent of the "0x11223344" string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a handy constructor like BYTES("0x11223344") ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bytes.fromhex(), example here: #1067 (comment).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see here is confusion
bytes.fromhex("11223344"),
prefix 0x is not allowed, can mistake with dec

Comment on lines +10 to +19
invalid_opcode_ranges = [
range(0x0C, 0x10),
range(0x1E, 0x20),
range(0x21, 0x30),
range(0x4B, 0x50),
range(0xA5, 0xF0),
range(0xF6, 0xFA),
range(0xFB, 0xFD),
range(0xFE, 0xFF),
]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this should not be a static list, and rather should be derived from the fork properties.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes this can be built depending on fork

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, but the programs does not know about fork. I would have to refactor all programs to take fork as an argument.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm I can make this a predeployed contract in scenarios if you concern so much. but making programs a fixtures that accept fork is rather difficult design decision. programs are intended as byte sequences that are to be deployed in different scenarios and tested

@winsvega
Copy link
Contributor Author

pytest.fixture

can you explain this part a little.
currently the program is just a bytecode that is deployed and executed inside a dynamic address that is created by scenario. depending on scenario this address or rather execution context is prepared and executed. with this we achieve following model: define bytecodeX and execute it in different contextsY.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
scope:pytest Scope: Pytest plugins type:feat type: Feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants