@@ -196,3 +196,203 @@ jq -r '.opName' debug_output/**/*.jsonl
196
196
5 . Commit with semantic format
197
197
198
198
** Critical** : Always run linting and type checking. Use ` --clean ` when filling. Never use hardcoded addresses.
199
+
200
+ ## 🎯 BloatNet Multi-Opcode Benchmarks
201
+
202
+ ### Overview
203
+
204
+ BloatNet benchmarks stress-test Ethereum clients by rapidly accessing many large (24KB) contracts to measure state-handling performance. The multi-opcode variants test specific EVM opcodes (BALANCE, EXTCODESIZE, EXTCODECOPY) against pre-deployed contracts.
205
+
206
+ ### Architecture
207
+
208
+ ``` text
209
+ ┌─────────────────────┐ ┌──────────────────┐ ┌──────────────────┐
210
+ │ Initcode Contract │ │ Factory Contract │ │ 24KB Contracts │
211
+ │ (~9.5KB) │ │ (116B) │ │ (N × 24KB each) │
212
+ └──────────┬──────────┘ └────────┬─────────┘ └──────────────────┘
213
+ │ │ │
214
+ │ EXTCODECOPY │ CREATE2(salt++) │
215
+ └─────────────────────────►├─────────────────────► Contract_0
216
+ ├─────────────────────► Contract_1
217
+ ├─────────────────────► Contract_2
218
+ └─────────────────────► Contract_N
219
+
220
+ ┌──────────────────┐
221
+ │ Attack Contract │──STATICCALL──► Factory.getConfig()
222
+ │ │ returns: (N, hash)
223
+ └────────┬─────────┘
224
+ │
225
+ └─► Loop(i=0 to N):
226
+ 1. Generate CREATE2 addr: keccak256(0xFF|factory|i|hash)[12:]
227
+ 2. BALANCE(addr) → 2600 gas (cold access)
228
+ 3. EXTCODESIZE(addr) → 100 gas (warm access)
229
+ ```
230
+
231
+ ### Key Components
232
+
233
+ #### 1. Initcode Contract (` deploy_initcode.py ` )
234
+
235
+ - ** Purpose** : Stores the template initcode that generates unique 24KB contracts
236
+ - ** Size** : ~ 9.5KB (uses ` While ` loop expansion)
237
+ - ** Uniqueness** : Uses ` ADDRESS ` opcode as seed, making each deployed contract unique
238
+ - ** Algorithm** :
239
+ - Store ` ADDRESS ` as initial seed (different per CREATE2 deployment)
240
+ - Loop: Expand to 24KB using SHA3 + XOR operations with 256-entry XOR table
241
+ - Set first byte to ` 0x00 ` (STOP) for efficient CALL handling
242
+ - Return 24KB bytecode
243
+
244
+ #### 2. Factory Contract (` deploy_create2_factory.py ` )
245
+
246
+ - ** Purpose** : Deploys contracts via CREATE2 and provides config info to tests
247
+ - ** Storage Layout** :
248
+ - Slot 0: Counter (number of deployed contracts)
249
+ - Slot 1: Init code hash (for CREATE2 address calculation)
250
+ - Slot 2: Initcode contract address
251
+ - ** Interface** :
252
+ - ` CALLDATASIZE == 0 ` : Returns ` (num_deployed_contracts, init_code_hash) ` (64 bytes)
253
+ - ` CALLDATASIZE > 0 ` : Deploys new contract via CREATE2 with counter as salt
254
+ - ** Deployment Flow** :
255
+ 1 . Load initcode from stored address via EXTCODECOPY
256
+ 2 . CREATE2 with current counter as salt
257
+ 3 . Increment counter
258
+ 4 . Return deployed address
259
+
260
+ #### 3. Attack Contract (in test files)
261
+
262
+ - ** Purpose** : Rapidly accesses all deployed contracts to stress state handling
263
+ - ** Flow** :
264
+ 1 . STATICCALL factory.getConfig() to get N and hash
265
+ 2 . Setup memory for CREATE2 address generation: ` 0xFF + factory_addr + salt + hash `
266
+ 3 . Loop N times:
267
+ - Generate CREATE2 address: ` keccak256(memory[11:96]) `
268
+ - Execute opcode tests (BALANCE, EXTCODESIZE, EXTCODECOPY)
269
+ - Increment salt
270
+ - ** Gas Calculation** : Each test calculates exact gas cost per contract iteration
271
+
272
+ ### Test Implementation Pattern
273
+
274
+ Tests in [ test_multi_opcode.py] ( tests/benchmark/bloatnet/test_multi_opcode.py ) :
275
+
276
+ ``` python
277
+ @pytest.mark.valid_from (" Prague" )
278
+ def test_bloatnet_balance_extcodesize (
279
+ blockchain_test : BlockchainTestFiller,
280
+ pre : Alloc,
281
+ fork : Fork,
282
+ gas_benchmark_value : int ,
283
+ ):
284
+ # 1. Calculate cost per contract access
285
+ cost_per_contract = (
286
+ gas_costs.G_KECCAK_256 + gas_costs.G_KECCAK_256_WORD * 3 # Address gen
287
+ + gas_costs.G_COLD_ACCOUNT_ACCESS # BALANCE (cold)
288
+ + gas_costs.G_WARM_ACCOUNT_ACCESS # EXTCODESIZE (warm)
289
+ + /* stack operations */
290
+ )
291
+
292
+ # 2. Deploy factory as stub (must be pre-deployed externally)
293
+ factory_address = pre.deploy_contract(
294
+ code = Bytecode(),
295
+ stub = " bloatnet_factory" ,
296
+ )
297
+
298
+ # 3. Build attack contract that:
299
+ # - Calls factory.getConfig() to get N and hash
300
+ # - Loops N times, generating CREATE2 addresses and accessing contracts
301
+ attack_code = (
302
+ Op.STATICCALL(factory_address, ret_offset = 96 , ret_size = 64 )
303
+ + /* setup CREATE2 address generation */
304
+ + While(
305
+ body = (
306
+ Op.SHA3(11 , 85 ) # Generate CREATE2 address
307
+ + Op.DUP1
308
+ + Op.POP(Op.BALANCE ) # Cold access
309
+ + Op.POP(Op.EXTCODESIZE ) # Warm access
310
+ + /* increment salt */
311
+ ),
312
+ condition = /* counter > 0 */
313
+ )
314
+ )
315
+ ```
316
+
317
+ ### Deployment Workflow
318
+
319
+ 1 . ** Deploy Initcode** (` deploy_initcode.py ` ):
320
+ ``` bash
321
+ python3 deploy_initcode.py
322
+ # Outputs: initcode_address.json with address and hash
323
+ ```
324
+
325
+ 2 . ** Deploy Factory** (` deploy_create2_factory.py ` ):
326
+ ``` bash
327
+ python3 deploy_create2_factory.py
328
+ # Outputs: stubs.json with factory address
329
+ ```
330
+
331
+ 3 . ** Deploy Contracts** (` deploy_bloatnet_contracts.py ` ):
332
+ ``` bash
333
+ python3 deploy_bloatnet_contracts.py 1000 --stubs stubs.json
334
+ # Deploys 1000 contracts via factory (each uses ~8.6M gas)
335
+ ```
336
+
337
+ 4 . ** Run Tests** :
338
+ ``` bash
339
+ uv run execute remote \
340
+ --rpc-endpoint http://127.0.0.1:8545 \
341
+ --address-stubs stubs.json \
342
+ -- --fork Prague --gas-benchmark-values 5 \
343
+ tests/benchmark/bloatnet/test_multi_opcode.py
344
+ ```
345
+
346
+ ### Critical Design Decisions
347
+
348
+ 1 . ** Stub Contracts** : Tests use ` stub="bloatnet_factory" ` to reference pre-deployed factory
349
+ - Avoids hardcoded addresses
350
+ - Factory address provided via ` --address-stubs ` flag
351
+ - Tests read config dynamically via ` getConfig() `
352
+
353
+ 2 . ** Dynamic Address Generation** : Attack contracts generate CREATE2 addresses on-the-fly
354
+ - Uses same formula: ` keccak256(0xFF | factory | salt | hash)[12:] `
355
+ - No pre-computation or storage of addresses needed
356
+ - Minimal memory footprint
357
+
358
+ 3 . ** Gas Precision** : Each test calculates exact gas cost per iteration
359
+ - Uses ` fork.gas_costs() ` for accurate opcode costs
360
+ - Includes SHA3 word costs, stack operations, loop overhead
361
+ - Ensures tests consume exactly the target gas amount
362
+
363
+ 4 . ** Unique Contracts** : Each deployed contract has unique bytecode
364
+ - Uses ` ADDRESS ` opcode in initcode (different per CREATE2 deployment)
365
+ - Forces clients to store distinct contract code
366
+ - Prevents deduplication optimizations
367
+
368
+ ### Available Tests
369
+
370
+ - ` test_bloatnet_balance_extcodesize ` : BALANCE (cold) + EXTCODESIZE (warm)
371
+ - ` test_bloatnet_balance_extcodecopy ` : BALANCE (cold) + EXTCODECOPY (warm, reads last byte)
372
+
373
+ ### Common Patterns
374
+
375
+ ** Reading factory config in attack contract** :
376
+ ``` python
377
+ Op.STATICCALL(
378
+ gas = Op.GAS ,
379
+ address = factory_address,
380
+ args_offset = 0 ,
381
+ args_size = 0 ,
382
+ ret_offset = 96 ,
383
+ ret_size = 64 , # Returns 2 × 32 bytes
384
+ )
385
+ + Op.MLOAD(96 ) # num_deployed_contracts
386
+ + Op.MLOAD(128 ) # init_code_hash
387
+ ```
388
+
389
+ ** CREATE2 address generation in memory** :
390
+ ``` python
391
+ # Memory layout: 0xFF(1) + factory(20) + salt(32) + hash(32) = 85 bytes
392
+ Op.MSTORE(0 , factory_address)
393
+ + Op.MSTORE8(11 , 0x FF ) # Prefix at position 32-20-1
394
+ + Op.MSTORE(32 , 0 ) # Salt
395
+ + Op.MSTORE(64 , hash ) # Init code hash
396
+ # Generate address:
397
+ Op.SHA3(11 , 85 ) # Hash from byte 11 for 85 bytes
398
+ ```
0 commit comments