-
Notifications
You must be signed in to change notification settings - Fork 412
refactor(test-tests): port EXTCODEHASH dynamicAccountOverwriteEmpty #2032
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
base: forks/amsterdam
Are you sure you want to change the base?
refactor(test-tests): port EXTCODEHASH dynamicAccountOverwriteEmpty #2032
Conversation
8c9db98 to
55295bf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## forks/amsterdam #2032 +/- ##
===================================================
+ Coverage 86.14% 86.18% +0.03%
===================================================
Files 599 599
Lines 39472 39472
Branches 3780 3780
===================================================
+ Hits 34002 34017 +15
+ Misses 4848 4838 -10
+ Partials 622 617 -5
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
LouisTsai-Csie
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR! I’ve left a few comments suggesting some refactoring using helper functions. Overall, the test logic looks great.
| @pytest.mark.ported_from( | ||
| [ | ||
| "https://github.com/ethereum/tests/tree/v13.3/src/GeneralStateTestsFiller/stExtCodeHash/dynamicAccountOverwriteEmpty_ParisFiller.yml", # noqa: E501 | ||
| ] | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| @pytest.mark.ported_from( | |
| [ | |
| "https://github.com/ethereum/tests/tree/v13.3/src/GeneralStateTestsFiller/stExtCodeHash/dynamicAccountOverwriteEmpty_ParisFiller.yml", # noqa: E501 | |
| ] | |
| ) | |
| @pytest.mark.ported_from( | |
| [ | |
| "https://github.com/ethereum/tests/tree/v13.3/src/GeneralStateTestsFiller/stExtCodeHash/dynamicAccountOverwriteEmpty_ParisFiller.yml", # noqa: E501 | |
| ], | |
| pr=["https://github.com/ethereum/execution-specs/pull/2032"], | |
| ) |
Adding this might be helpful, i discovered that other porting PR also have this marker.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added.
| "https://github.com/ethereum/tests/tree/v13.3/src/GeneralStateTestsFiller/stExtCodeHash/dynamicAccountOverwriteEmpty_ParisFiller.yml", # noqa: E501 | ||
| ] | ||
| ) | ||
| @pytest.mark.pre_alloc_modify |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| @pytest.mark.pre_alloc_modify |
I’m not sure if we need this for the test, maybe I’m missing something here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The caller_address is fixed, so you will not be able to deploy it in a live network. Maybe we should make it random: I don't think it will modify the main flow of the test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is resolved now by creating an account with CALL.
| caller_code = ( | ||
| # Check stats of empty account before CREATE2 | ||
| Op.SSTORE(0, Op.EXTCODEHASH(target_address)) | ||
| + Op.SSTORE(1, Op.EXTCODESIZE(target_address)) | ||
| # CREATE2 to overwrite the account | ||
| + Op.MSTORE(0, Op.PUSH32(bytes(create2_initcode).ljust(32, b"\0"))) | ||
| + Op.SSTORE( | ||
| 10, | ||
| Op.CREATE2(value=0, offset=0, size=len(create2_initcode), salt=0), | ||
| ) | ||
| # Call the deployed contract to execute its code (SSTORE 80, 11) | ||
| + Op.SSTORE(3, Op.CALL(50000, target_address, 0, 0, 0, 0, 0)) | ||
| # Check stats after CREATE2 | ||
| + Op.SSTORE(4, Op.EXTCODEHASH(target_address)) | ||
| + Op.SSTORE(5, Op.EXTCODESIZE(target_address)) | ||
| + Op.STOP | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some suggestion for this call sequence:
- We could use the
Storageobject here instead of specifying the slot forSSTORE.
Example:
from execution_testing import Storage
storage = Storage()
Op.SSTORE(storage.store_next(EMPTY_KECCAK), Op.EXTCODEHASH(target_address))store_next would handle the slot management for us. And the benefit is we could simplify the post storage by passing the entire Storage object:
caller_address: Account(storage=storage),-
In the original test, it do the
EXTCODEHASH,EXTCODESIZE,EXTCODECOPY,CALLCODEto the target address before / after the contract creation. We might need to do the same check here to avoid any coverage loss. -
In the original test, it performs a
CALLCODEto the target account. SinceCALLCODEexecutes the callee’s code in the context of the caller, any code present at the target account could potentially modify the caller’s storage. The test currently checks whether the call succeeds and whether the caller’s storage remains unchanged, but this is insufficient here because there are two possible scenarios:- There is no code at the target account, so nothing is executed.
- Code execution succeeds, but the logic does not modify storage.
To distinguish these cases, I suggest passing zero gas to the call. This ensures that if a success value is returned, no execution could have taken place at all, making the test behavior unambiguous.
This is my suggested changes, please let me know if it is unclear or does not make sense, thanks:
caller_code = (
# EXTCODEHASH Check
Op.SSTORE(
storage.store_next(EMPTY_KECCAK), Op.EXTCODEHASH(target_address)
)
# EXTCODESIZE Check
+ Op.SSTORE(storage.store_next(0), Op.EXTCODESIZE(target_address))
# EXTCODECOPY Check
+ Op.EXTCODECOPY(target_address, 0, 0, 32)
+ Op.SSTORE(storage.store_next(0), Op.MLOAD(0))
# CALL* Ops Check
+ Op.SSTORE(
storage.store_next(1),
Op.CALLCODE(
gas=0, # Passing zero gas to ensure no execution
address=target_address,
),
)
# Create the contract via CREATE2
+ Op.MSTORE(0, bytes(create2_initcode).ljust(32, b"\0"))
+ Op.SSTORE(
storage.store_next(target_address),
Op.CREATE2(size=len(create2_initcode)),
)
# EXTCODEHASH Check
+ Op.SSTORE(
storage.store_next(deploy_code.keccak256()),
Op.EXTCODEHASH(target_address),
)
# EXTCODESIZE Check
+ Op.SSTORE(
storage.store_next(len(deploy_code)),
Op.EXTCODESIZE(target_address),
)
# EXTCODECOPY Check
+ Op.EXTCODECOPY(target_address, 0, 0, 32)
+ Op.SSTORE(
storage.store_next(bytes(deploy_code).ljust(32, b"\0")),
Op.MLOAD(0),
)
# CALL* Ops Check
+ Op.SSTORE(
storage.store_next(1),
Op.CALL(gas=gas_cost, address=target_address),
)
)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I applied suggestions and even extended the workflow. In the end the check hash, size, and code itself in 3 states: non-existient, no-code and CREATE2-deployed. The no-code account is created via CALL so we can avoid fixed-address pre-state.
We also call the target account code via CALL and DELEGATECALL.
| storage={ | ||
| # Before CREATE2: empty account has keccak256("") hash | ||
| 0: EMPTY_KECCAK, | ||
| 1: 0, # EXTCODESIZE = 0 | ||
| # CALL to deployed contract succeeded | ||
| 3: 1, | ||
| # CREATE2 returned the new address | ||
| 10: target_address, | ||
| # After CREATE2: account has code | ||
| 4: deploy_code.keccak256(), | ||
| 5: len(deploy_code), | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Continue the last comment, we could simplify this storage comparison to
| storage={ | |
| # Before CREATE2: empty account has keccak256("") hash | |
| 0: EMPTY_KECCAK, | |
| 1: 0, # EXTCODESIZE = 0 | |
| # CALL to deployed contract succeeded | |
| 3: 1, | |
| # CREATE2 returned the new address | |
| 10: target_address, | |
| # After CREATE2: account has code | |
| 4: deploy_code.keccak256(), | |
| 5: len(deploy_code), | |
| }, | |
| storage=storage |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
| pre[caller_address] = Account( | ||
| balance=10**18, | ||
| code=caller_code, | ||
| nonce=1, | ||
| ) | ||
|
|
||
| # Pre-existing empty account at CREATE2 target. | ||
| pre[target_address] = Account( | ||
| balance=10, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works under the fill mode, but i would suggest using a more dynamic approach, so that we could run the test under execute remote in a live network.
We could wait until PR #1996 merged to do the refactor again, and we would no longer need the hardcoded caller_address:
caller_address = Address(0x095E7BAEA6A6C7C4C2DFEB977EFAC326AF552D87)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved. See above.
…ing pre-alloc groups (ethereum#2032) * feat(FixtureCollector): Add flush interval to the fixture collector * fix: typing * fix: typing * fix: unit test
55295bf to
ef406d9
Compare
Port the legacy static test: stExtCodeHash/dynamicAccountOverwriteEmpty_Paris for EIP-1052 EXTCODEHASH testing. The test verifies EXTCODEHASH correctly updates when an empty account is overwritten by CREATE2. Modified from original: target account has no storage to avoid EIP-7610 collision behavior.
ef406d9 to
9eaaf43
Compare
9eaaf43 to
84a2f78
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
🗒️ Description
Port the legacy static test:
stExtCodeHash/dynamicAccountOverwriteEmpty_Paris
for EIP-1052 EXTCODEHASH testing.
The test verifies EXTCODEHASH correctly updates when an empty account is overwritten by CREATE2. Modified from original: target account has no storage to avoid EIP-7610 collision behavior.
🔗 Related Issues or PRs
N/A.
✅ Checklist
toxchecks to avoid unnecessary CI fails, see also Code Standards and Enabling Pre-commit Checks:uvx tox -e statictype(scope):.mkdocs servelocally and verified the auto-generated docs for new tests in the Test Case Reference are correctly formatted.@ported_frommarker.Cute Animal Picture