-
Notifications
You must be signed in to change notification settings - Fork 66
feat: add eip8037 fuzzing scenarios #235
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: master
Are you sure you want to change the base?
Changes from all commits
9a9450d
2055525
fa8e3d9
5858497
f4f09b7
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,93 @@ | ||
| // Copyright 2026 Spencer Taylor-Brown (terminus-31) | ||
| // This file is part of the goevmlab library. | ||
| // | ||
| // The library is free software: you can redistribute it and/or modify | ||
| // it under the terms of the GNU Lesser General Public License as published by | ||
| // the Free Software Foundation, either version 3 of the License, or | ||
| // (at your option) any later version. | ||
|
|
||
| package fuzzing | ||
|
|
||
| import ( | ||
| "math/big" | ||
| "math/rand" | ||
|
|
||
| "github.com/ethereum/go-ethereum/common" | ||
| "github.com/ethereum/go-ethereum/common/hexutil" | ||
| "github.com/ethereum/go-ethereum/core/vm" | ||
| "github.com/ethereum/go-ethereum/core/vm/program" | ||
| program2 "github.com/holiman/goevmlab/program" | ||
| ) | ||
|
|
||
| // fillCreateSdSameTx generates a state test where a factory contract | ||
| // CREATEs (or CREATE2s) a contract whose initcode immediately calls | ||
| // SELFDESTRUCT. Tests the EIP-6780 × EIP-8037 same-tx CREATE+SD path: | ||
| // state gas is charged for the new account but no refund is issued | ||
| // because the account is removed in-tx and never persists. See | ||
| // test_state_gas_selfdestruct.py::test_create_selfdestruct_no_refund_*. | ||
| // | ||
| // Beneficiary is varied across four scenarios that interact with the | ||
| // SELFDESTRUCT state-gas table: | ||
| // - ZERO address | ||
| // - factory (self) | ||
|
Owner
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. The initcode executes in a new address, doesn't it ? SO the |
||
| // - pre-existing EOA (no new-account state gas) | ||
| // - nonexistent (charges 183,600 if SD originator has nonzero balance) | ||
| func fillCreateSdSameTx(gst *GstMaker, fork string) { | ||
| factory := common.HexToAddress("0xF1") | ||
|
|
||
| var beneficiary common.Address | ||
| switch rand.Intn(4) { | ||
| case 0: | ||
| beneficiary = common.Address{} | ||
| case 1: | ||
| beneficiary = factory | ||
| case 2: | ||
| beneficiary = common.HexToAddress("0xBE") | ||
| default: | ||
| beneficiary = common.HexToAddress("0xDEAD") | ||
| } | ||
|
Comment on lines
+38
to
+48
Owner
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. IMO maybe let's add a bit more options here. One should be |
||
|
|
||
| // Init code: push beneficiary, SELFDESTRUCT. | ||
| ctor := program.New() | ||
| ctor.Push(beneficiary) | ||
| ctor.Op(vm.SELFDESTRUCT) | ||
|
|
||
| // Factory: CREATE/CREATE2 the SD-only contract, then no-op call to | ||
| // the resulting (now-destroyed) address. Coin-flip CREATE vs CREATE2. | ||
| p := program.New() | ||
| useCreate2 := rand.Intn(2) == 0 | ||
| program2.CreateAndCall(p, ctor.Bytes(), useCreate2, vm.CALL) | ||
| p.Op(vm.STOP) | ||
|
|
||
| gst.AddAccount(factory, GenesisAccount{ | ||
| Code: p.Bytes(), | ||
| Balance: big.NewInt(1), // SD with nonzero balance triggers new-account state gas | ||
| Storage: make(map[common.Hash]common.Hash), | ||
| }) | ||
| // Pre-existing beneficiary (the "0xBE" branch) gets a genesis entry | ||
| // half the time, so the SD-to-existing-vs-new branches both surface. | ||
| // Balance is nonzero so the account isn't "empty" (EELS rejects | ||
| // empty accounts in pre-state fixtures). | ||
| if beneficiary == common.HexToAddress("0xBE") && rand.Intn(2) == 0 { | ||
| gst.AddAccount(beneficiary, GenesisAccount{ | ||
| Balance: big.NewInt(1), | ||
| Storage: make(map[common.Hash]common.Hash), | ||
| }) | ||
| } | ||
|
|
||
| gasLimit := gasLimitOptions8037[rand.Intn(len(gasLimitOptions8037))] | ||
| tx := &StTransaction{ | ||
| GasLimit: []uint64{gasLimit}, | ||
| Nonce: 0, | ||
| Value: []string{"0x0"}, | ||
| Data: []string{"0x"}, | ||
| MaxFeePerGas: big.NewInt(0x10), | ||
| MaxPriorityFeePerGas: big.NewInt(0x10), | ||
| To: factory.Hex(), | ||
| Sender: sender, | ||
| PrivateKey: hexutil.MustDecode( | ||
| "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", | ||
| ), | ||
| } | ||
| gst.SetTx(tx) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| // Copyright 2026 Spencer Taylor-Brown (terminus-31) | ||
| // This file is part of the goevmlab library. | ||
| // | ||
| // The library is free software: you can redistribute it and/or modify | ||
| // it under the terms of the GNU Lesser General Public License as published by | ||
| // the Free Software Foundation, either version 3 of the License, or | ||
| // (at your option) any later version. | ||
|
|
||
| package fuzzing | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "math/big" | ||
| "math/rand" | ||
|
|
||
| "github.com/ethereum/go-ethereum/common" | ||
| "github.com/ethereum/go-ethereum/common/hexutil" | ||
| "github.com/ethereum/go-ethereum/core/vm" | ||
| "github.com/ethereum/go-ethereum/core/vm/program" | ||
| ) | ||
|
|
||
| // fillDeepCalls generates a state test with a deep CALL chain (5-50 | ||
| // frames) where each contract calls the next and the leaf does SSTORE. | ||
| // Exercises the EIP-8037 reservoir-passing-through-frames pathway at | ||
| // depths well beyond what RandCall2200's compile-time-recursion cap of | ||
| // 10 reaches. See test_state_gas_call.py::test_nested_calls_*. | ||
| func fillDeepCalls(gst *GstMaker, fork string) { | ||
| depth := 5 + rand.Intn(46) // 5..50 | ||
|
Owner
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. Is But maybe I'm missing something |
||
| contracts := make([]common.Address, depth) | ||
| for i := 0; i < depth; i++ { | ||
| contracts[i] = common.HexToAddress(fmt.Sprintf("0xc%03x", i)) | ||
| } | ||
|
|
||
| for i := 0; i < depth; i++ { | ||
| p := program.New() | ||
| if i == depth-1 { | ||
| // Leaf: SSTORE so the deepest frame draws state gas | ||
| // from (the parent chain's) reservoir. | ||
| p.Sstore(0, 1) | ||
| p.Op(vm.STOP) | ||
| } else { | ||
| // Intermediate: CALL the next contract, forwarding all | ||
| // gas. CALL stack (top → bottom): gas, to, value, argOff, | ||
| // argSize, retOff, retSize. | ||
| p.Push(0) // retSize | ||
| p.Push(0) // retOff | ||
| p.Push(0) // argSize | ||
| p.Push(0) // argOff | ||
| p.Push(0) // value | ||
| p.Push(contracts[i+1]) | ||
| p.Op(vm.GAS) | ||
| p.Op(vm.CALL) | ||
| p.Op(vm.POP) | ||
| p.Op(vm.STOP) | ||
| } | ||
| gst.AddAccount(contracts[i], GenesisAccount{ | ||
| Code: p.Bytes(), | ||
| Balance: new(big.Int), | ||
| Storage: make(map[common.Hash]common.Hash), | ||
| }) | ||
| } | ||
|
|
||
| gasLimit := gasLimitOptions8037[rand.Intn(len(gasLimitOptions8037))] | ||
| tx := &StTransaction{ | ||
| GasLimit: []uint64{gasLimit}, | ||
| Nonce: 0, | ||
| Value: []string{"0x0"}, | ||
| Data: []string{"0x"}, | ||
| MaxFeePerGas: big.NewInt(0x10), | ||
| MaxPriorityFeePerGas: big.NewInt(0x10), | ||
| To: contracts[0].Hex(), | ||
| Sender: sender, | ||
| PrivateKey: hexutil.MustDecode( | ||
| "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", | ||
| ), | ||
| } | ||
| gst.SetTx(tx) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| // Copyright 2026 Spencer Taylor-Brown (terminus-31) | ||
| // This file is part of the goevmlab library. | ||
| // | ||
| // The library is free software: you can redistribute it and/or modify | ||
| // it under the terms of the GNU Lesser General Public License as published by | ||
| // the Free Software Foundation, either version 3 of the License, or | ||
| // (at your option) any later version. | ||
|
|
||
| package fuzzing | ||
|
|
||
| import ( | ||
| "math/big" | ||
| "math/rand" | ||
|
|
||
| "github.com/ethereum/go-ethereum/common" | ||
| "github.com/ethereum/go-ethereum/common/hexutil" | ||
| "github.com/ethereum/go-ethereum/core/vm" | ||
| "github.com/ethereum/go-ethereum/core/vm/program" | ||
| ) | ||
|
|
||
| // fillSstoreRestoration generates a state test that exercises the | ||
| // EIP-8037 SSTORE restoration refund pathway: a slot transitions | ||
| // 0 → x → 0 within a single transaction, refunding the | ||
| // (STATE_BYTES_PER_STORAGE_SET × CPSB = 97,920) state-gas charge on | ||
| // the initial write back to the reservoir. | ||
| // | ||
| // The contract runs N cycles (1-20) of (write nonzero, clear) across | ||
| // distinct slots, randomising the intermediate value. Inline reservoir | ||
| // replenishment is exercised when N is large enough that the refund | ||
| // from cycle i seeds the budget for cycle i+1. See | ||
| // test_state_gas_sstore.py::test_sstore_restoration_*. | ||
| func fillSstoreRestoration(gst *GstMaker, fork string) { | ||
| target := common.HexToAddress("0xF1") | ||
|
|
||
| p := program.New() | ||
| cycles := 1 + rand.Intn(20) | ||
| for i := 0; i < cycles; i++ { | ||
| slot := i | ||
| val := 1 + rand.Intn(100) | ||
| p.Sstore(slot, val) // 0 → nonzero (charges 97,920 state gas) | ||
| p.Sstore(slot, 0) // nonzero → 0 (refunds 97,920 to reservoir) | ||
| } | ||
| p.Op(vm.STOP) | ||
|
|
||
| gst.AddAccount(target, GenesisAccount{ | ||
| Code: p.Bytes(), | ||
| Balance: new(big.Int), | ||
| Storage: make(map[common.Hash]common.Hash), | ||
| }) | ||
|
|
||
| gasLimit := gasLimitOptions8037[rand.Intn(len(gasLimitOptions8037))] | ||
| tx := &StTransaction{ | ||
| GasLimit: []uint64{gasLimit}, | ||
| Nonce: 0, | ||
| Value: []string{"0x0"}, | ||
| Data: []string{"0x"}, | ||
| MaxFeePerGas: big.NewInt(0x10), | ||
| MaxPriorityFeePerGas: big.NewInt(0x10), | ||
| To: target.Hex(), | ||
| Sender: sender, | ||
| PrivateKey: hexutil.MustDecode( | ||
| "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", | ||
| ), | ||
| } | ||
| gst.SetTx(tx) | ||
| } |
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.
Why did you feel the need for this? IMO the drain-period is usually pretty fast, unless one of the clients hang. But if they do, I don't think this will help.
Or is it because you don't want a second bug to obscure the first one?