-
Notifications
You must be signed in to change notification settings - Fork 182
feat(bloatnet): Add first multi-opcode benchmarks for Bloatnet #2090
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
Changes from 27 commits
a1f2153
02d65b4
e721cc6
d1cad25
16f6d30
374e08a
333c876
79a95b8
8131e98
5f805fd
090a400
cd02a02
1f3c381
f6def7e
7e20a50
c24ad35
fc27e53
7d87262
8556014
326915e
1f8e62a
8babb13
e70132b
0e889d7
e4583b6
06f9a63
49c1343
2875cf4
b634ca3
774c56c
6e6863a
f2cd5f9
cf2c7c6
a862f76
55396fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
#!/usr/bin/env python3 | ||
"""Test CREATE2 factory deployment.""" | ||
|
||
from web3 import Web3 | ||
from eth_utils import keccak | ||
|
||
# Connect to Geth | ||
w3 = Web3(Web3.HTTPProvider("http://127.0.0.1:8545")) | ||
if not w3.is_connected(): | ||
print("Failed to connect to Geth") | ||
exit(1) | ||
|
||
test_account = w3.eth.accounts[0] | ||
print(f"Using test account: {test_account}") | ||
print(f"Balance: {w3.eth.get_balance(test_account) / 10**18:.4f} ETH") | ||
|
||
# Simple CREATE2 factory that returns the deployed address | ||
factory_bytecode = ( | ||
# Constructor: return runtime code | ||
"60" + "1f" # PUSH1 0x1F (runtime size = 31) | ||
"80" # DUP1 | ||
"60" + "0a" # PUSH1 0x0A (runtime offset) | ||
"60" + "00" # PUSH1 0x00 (memory dest) | ||
"39" # CODECOPY | ||
"60" + "00" # PUSH1 0x00 (return offset) | ||
"f3" # RETURN | ||
|
||
# Runtime: minimal CREATE2 | ||
"36" # CALLDATASIZE | ||
"60" + "00" # PUSH1 0x00 | ||
"60" + "00" # PUSH1 0x00 | ||
"37" # CALLDATACOPY (copy all calldata to memory) | ||
|
||
"60" + "00" # PUSH1 0x00 (salt - using 0 for simplicity) | ||
|
||
"36" # CALLDATASIZE (size of init code) | ||
"60" + "00" # PUSH1 0x00 (offset in memory) | ||
"60" + "00" # PUSH1 0x00 (value) | ||
"f5" # CREATE2 | ||
|
||
"60" + "00" # PUSH1 0x00 | ||
"52" # MSTORE (store address at 0) | ||
"60" + "20" # PUSH1 0x20 | ||
"60" + "00" # PUSH1 0x00 | ||
|
||
"f3" # RETURN (return address) | ||
) | ||
|
||
# Deploy factory | ||
factory_tx = w3.eth.send_transaction({ | ||
'from': test_account, | ||
'data': '0x' + factory_bytecode, | ||
'gas': 3000000 | ||
}) | ||
|
||
factory_receipt = w3.eth.wait_for_transaction_receipt(factory_tx) | ||
if factory_receipt.status != 1: | ||
print("Failed to deploy factory") | ||
exit(1) | ||
|
||
factory_address = factory_receipt.contractAddress | ||
print(f"\nFactory deployed at: {factory_address}") | ||
|
||
# Create simple contract bytecode (just returns 42) | ||
simple_bytecode = "602a60005260206000f3" # PUSH1 42, PUSH1 0, MSTORE, PUSH1 32, PUSH1 0, RETURN | ||
|
||
# Deploy using factory | ||
print("\nDeploying contract via CREATE2...") | ||
deploy_tx = w3.eth.send_transaction({ | ||
'from': test_account, | ||
'to': factory_address, | ||
'data': '0x' + simple_bytecode, | ||
'gas': 1000000 | ||
}) | ||
|
||
deploy_receipt = w3.eth.wait_for_transaction_receipt(deploy_tx) | ||
print(f"Transaction status: {deploy_receipt.status}") | ||
|
||
# Get return value (the deployed address) | ||
result = w3.eth.call({ | ||
'to': factory_address, | ||
'data': '0x' + simple_bytecode | ||
}) | ||
|
||
if result: | ||
deployed_addr = '0x' + result[-20:].hex() | ||
print(f"Contract deployed at: {deployed_addr}") | ||
|
||
# Verify by checking code | ||
code = w3.eth.get_code(deployed_addr) | ||
print(f"Deployed code length: {len(code)} bytes") | ||
|
||
else: | ||
print("No return value from factory") |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,133 @@ | ||||||
# BloatNet Benchmark Tests setup guide | ||||||
|
||||||
## Overview | ||||||
|
||||||
The Bloatnet benchmarks work on the following fashion: | ||||||
1. They usually require a previously-deployed state (usually quite large) which the benchmarks | ||||||
will interact with. | ||||||
2. The deployment script helpers help deploying the required bytecode for the specific tests. | ||||||
3. The outputs of the deployment scripts get hardcoded into the codebase such that the benchmarks can interact with them. | ||||||
|
||||||
## Gas Cost Constants | ||||||
|
||||||
### BALANCE + EXTCODESIZE Pattern | ||||||
**Gas per contract: 2,707** | ||||||
- `PUSH20` (address): 3 gas | ||||||
- `BALANCE` (cold access): 2,600 gas | ||||||
- `POP`: 2 gas | ||||||
- `EXTCODESIZE` (warm): 100 gas | ||||||
- `POP`: 2 gas | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This tests is basically two scenarios in one. It tests both BALANCE (which marks it warm) and EXTCODESIZE. Note that for accounts in the Merkle Patricia Trie in the state, account are stored as:
Thus reading balance from MPT will "just" require reading the account. So, I believe we need scenarios for BALANCE/EXTCODESIZE. For EXTCODESIZE, I think this benchmark test is what you want:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For BALANCE (cold) this test:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I follow you here. If these standalone scenarios already exist as you correctly pointed out, and my PR adds the combination of them into a single test, what is actually needed further from what this PR adds? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you claiming we need to implement something? What I want is to test the combination of the 2 together. And observe if any client has optimizations that can be applied. This is all part of the following scenarios I want to implement for bloatnet: https://hackmd.io/9icZeLN7R0Sk5mIjKlZAHQ#Opcode-State-Access-Combination-Tests There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry! You are right, I was thinking of this from a different perspective (opcodes in isolation). The combined test is indeed not written. |
||||||
|
||||||
### BALANCE + EXTCODECOPY Pattern | ||||||
**Gas per contract: ~5,007** | ||||||
- `PUSH20` (address): 3 gas | ||||||
- `BALANCE` (cold access): 2,600 gas | ||||||
- `POP`: 2 gas | ||||||
- `EXTCODECOPY` setup: ~100 gas | ||||||
- `EXTCODECOPY` (24KB): ~2,300 gas | ||||||
|
attack_call = Op.EXTCODECOPY(address=Op.SHA3(32 - 20 - 1, 85), dest_offset=96, size=1000) |
I'm not sure why it copies 1000 bytes, but this should be edited to read 1 byte so we can target more accounts. (1000 byte copy likely from the original idea of these benchmarks (zkEVM) because we want to measure the worst case zk cycles there, not the worst state attack, so slightly different performance perspective regarding worst case scenarios there)
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.
Note: in linked tests there is also a calculation in the pre-setup phase to calculate how much contracts are necessary. This uses an upper bound, it should be slightly less in practice, so attack block will always read non-empty accounts
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 part of reading a single byte is pure gold! Thanks so much for this trick! I did not consider it but makes all the sense!
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 believe this file got accidentally committed but will still leave some comments.
Nit: this is the factory initcode as this bytecode will deploy the CREATE2 factory contract in case of a create transaction.