Skip to content

Commit 3cef3de

Browse files
authored
Fix issue related to chain cloning (crytic#564)
* preliminary commit * optimization bug fix * add base block context * fix cheatcode bugs, add prevrandao, and deprecate difficulty * revert version number * fix optimization mode bug * update solc version in CI * update solc version again * fix context management * fix context bug and improve logging while shrinking * enable fork mode when --rpc-url is specified * update documentation * last update * i genuinely hate prettier * update docs
1 parent ea4cf89 commit 3cef3de

32 files changed

+278
-105
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ jobs:
218218
219219
- name: Install solc
220220
run: |
221-
solc-select use 0.8.17 --always-install
221+
solc-select use 0.8.28 --always-install
222222
223223
- name: Test
224224
run: go test ./...

chain/block_context.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package chain
22

33
import (
4+
"math/big"
5+
46
"github.com/ethereum/go-ethereum/common"
57
"github.com/ethereum/go-ethereum/core"
68
"github.com/ethereum/go-ethereum/core/types"
79
"github.com/ethereum/go-ethereum/core/vm"
8-
"math/big"
910
)
1011

1112
// newTestChainBlockContext obtains a new vm.BlockContext that is tailored to provide data from a TestChain.

chain/standard_cheat_code_contract.go

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -172,27 +172,35 @@ func getStandardCheatCodeContract(tracer *cheatCodeTracer) (*CheatCodeContract,
172172
},
173173
)
174174

175-
// Difficulty: Updates difficulty
176-
// TODO: Make changes to difficulty permanent and make it revert for post-Paris EVM versions
175+
// Difficulty: Updates difficulty. Since we do not allow users to choose the fork that
176+
// they are using (for now), and we are using a post-Paris fork, the difficulty cheatcode is a no-op.
177177
contract.addMethod(
178178
"difficulty", abi.Arguments{{Type: typeUint256}}, abi.Arguments{},
179179
func(tracer *cheatCodeTracer, inputs []any) ([]any, *cheatCodeRawReturnData) {
180-
// Maintain our changes until the transaction exits.
181-
spoofedDifficulty := inputs[0].(*big.Int)
182-
spoofedDifficultyHash := common.BigToHash(spoofedDifficulty)
180+
return nil, nil
181+
},
182+
)
183+
184+
// Prevrandao: Updates random.
185+
contract.addMethod(
186+
"prevrandao", abi.Arguments{{Type: typeBytes32}}, abi.Arguments{},
187+
func(tracer *cheatCodeTracer, inputs []any) ([]any, *cheatCodeRawReturnData) {
188+
// Store our original random
183189
originalRandom := tracer.chain.pendingBlockContext.Random
184190

185-
// In newer evm versions, block.difficulty uses opRandom instead of opDifficulty.
186-
tracer.chain.pendingBlockContext.Random = &spoofedDifficultyHash
191+
// Update the pending block context random
192+
newRandom := inputs[0].([32]byte)
193+
newRandomHash := common.BytesToHash(newRandom[:])
194+
tracer.chain.pendingBlockContext.Random = &newRandomHash
195+
196+
// Restore the original random when top frame exits
187197
tracer.CurrentCallFrame().onTopFrameExitRestoreHooks.Push(func() {
188198
tracer.chain.pendingBlockContext.Random = originalRandom
189199
})
190200
return nil, nil
191201
},
192202
)
193203

194-
// TODO: Add prevrandao cheatcode
195-
196204
// Coinbase: Updates the block coinbase. Note that this _permanently_ updates the coinbase for the remainder of the
197205
// chain's lifecycle
198206
contract.addMethod(

chain/test_chain.go

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ package chain
33
import (
44
"errors"
55
"fmt"
6+
"math/big"
7+
68
"github.com/crytic/medusa/chain/state"
79
"golang.org/x/net/context"
8-
"math/big"
910

1011
"github.com/crytic/medusa/chain/config"
1112
"github.com/ethereum/go-ethereum/core/rawdb"
@@ -211,7 +212,6 @@ func newTestChainWithStateFactory(
211212

212213
// Convert our genesis block (go-ethereum type) to a test chain block.
213214
testChainGenesisBlock := types.NewBlock(genesisBlock.Header())
214-
215215
// Create our state database over-top our database.
216216
stateDatabase := gethState.NewDatabaseWithConfig(db, dbConfig)
217217

@@ -288,8 +288,9 @@ func (t *TestChain) Clone(onCreateFunc func(chain *TestChain) error) (*TestChain
288288
// did originally.
289289
for i := 1; i < len(t.blocks); i++ {
290290
// First create a new pending block to commit
291-
blockHeader := t.blocks[i].Header
292-
_, err = targetChain.PendingBlockCreateWithParameters(blockHeader.Number.Uint64(), blockHeader.Time, &blockHeader.GasLimit)
291+
block := t.blocks[i]
292+
blockHeader := block.Header
293+
_, err = targetChain.PendingBlockCreateWithBaseBlockContext(block.BaseContext, &blockHeader.GasLimit)
293294
if err != nil {
294295
return nil, err
295296
}
@@ -578,11 +579,10 @@ func (t *TestChain) PendingBlockCreate() (*types.Block, error) {
578579
return t.PendingBlockCreateWithParameters(blockNumber, timestamp, nil)
579580
}
580581

581-
// PendingBlockCreateWithParameters constructs an empty block which is pending addition to the chain, using the block
582-
// properties provided. Note that there are no constraints on the next block number or timestamp. Because of cheatcode
583-
// usage, the next block can go back in time.
584-
// Returns the constructed block, or an error if one occurred.
585-
func (t *TestChain) PendingBlockCreateWithParameters(blockNumber uint64, blockTime uint64, blockGasLimit *uint64) (*types.Block, error) {
582+
// PendingBlockCreateWithBaseBlockContext constructs an empty block which is pending addition to the chain, using the
583+
// provided base block context. The base block context holds information such as the block number, timestamp, and base fee
584+
// that should be used to initialize the block.
585+
func (t *TestChain) PendingBlockCreateWithBaseBlockContext(baseBlockContext *types.BaseBlockContext, blockGasLimit *uint64) (*types.Block, error) {
586586
// If we already have a pending block, return an error.
587587
if t.pendingBlock != nil {
588588
return nil, fmt.Errorf("could not create a new pending block for chain, as a block is already pending")
@@ -593,41 +593,43 @@ func (t *TestChain) PendingBlockCreateWithParameters(blockNumber uint64, blockTi
593593
blockGasLimit = &t.BlockGasLimit
594594
}
595595

596-
// Obtain our parent block hash to reference in our new block.
597-
parentBlockHash := t.Head().Hash
598-
599596
// Note we do not perform any block number or timestamp validation since cheatcodes can permanently update the
600597
// block number or timestamp which could violate the invariants of a blockchain (e.g. block.number is strictly
601598
// increasing)
602599

600+
// Obtain our parent block hash to reference in our new block.
601+
parentBlockHash := t.Head().Hash
602+
603603
// Create a block header for this block:
604604
// - State root hash reflects the state after applying block updates (no transactions, so unchanged from last block)
605+
// - Other hashes will populate as we apply transactions
605606
// - Bloom is aggregated for each transaction in the block (for now empty).
606-
// - TODO: Difficulty should be revisited/checked.
607607
// - GasUsed is aggregated for each transaction in the block (for now zero).
608-
// - Mix digest is only useful for randomness, so we just fake randomness by using the previous block hash.
609-
// - TODO: BaseFee should be revisited/checked.
608+
// - We don't care too much about difficulty and mix digest so setting them to random things
610609
header := &gethTypes.Header{
611610
ParentHash: parentBlockHash,
612611
UncleHash: gethTypes.EmptyUncleHash,
613-
Coinbase: t.Head().Header.Coinbase,
614612
Root: t.Head().Header.Root,
615613
TxHash: gethTypes.EmptyRootHash,
616614
ReceiptHash: gethTypes.EmptyRootHash,
617615
Bloom: gethTypes.Bloom{},
618-
Difficulty: common.Big0,
619-
Number: big.NewInt(int64(blockNumber)),
620616
GasLimit: *blockGasLimit,
621617
GasUsed: 0,
622-
Time: blockTime,
623618
Extra: []byte{},
624-
MixDigest: parentBlockHash,
625619
Nonce: gethTypes.BlockNonce{},
626-
BaseFee: new(big.Int).Set(t.Head().Header.BaseFee),
620+
Coinbase: baseBlockContext.Coinbase,
621+
Difficulty: common.Big0,
622+
Number: new(big.Int).Set(baseBlockContext.Number),
623+
Time: baseBlockContext.Time,
624+
MixDigest: parentBlockHash,
625+
BaseFee: new(big.Int).Set(baseBlockContext.BaseFee),
627626
}
628627

629-
// Create a new block for our test node
628+
// Create a new block for our test chain
630629
t.pendingBlock = types.NewBlock(header)
630+
631+
// Set the block hash
632+
// Note that this block hash may change if cheatcodes that update the block header are used (e.g. warp)
631633
t.pendingBlock.Hash = t.pendingBlock.Header.Hash()
632634

633635
// Emit our event for the pending block being created
@@ -643,6 +645,21 @@ func (t *TestChain) PendingBlockCreateWithParameters(blockNumber uint64, blockTi
643645
return t.pendingBlock, nil
644646
}
645647

648+
// PendingBlockCreateWithParameters constructs an empty block which is pending addition to the chain, using the block number
649+
// and timestamp provided. Returns the constructed block, or an error if one occurred.
650+
func (t *TestChain) PendingBlockCreateWithParameters(blockNumber uint64, blockTime uint64, blockGasLimit *uint64) (*types.Block, error) {
651+
// We will create a base block context with the provided parameters in addition to using the current head block.
652+
// All values that are not the block number and timestamp are taken from the current head block.
653+
baseBlockContext := types.NewBaseBlockContext(
654+
blockNumber,
655+
blockTime,
656+
t.Head().Header.BaseFee,
657+
t.Head().Header.Coinbase,
658+
)
659+
660+
return t.PendingBlockCreateWithBaseBlockContext(baseBlockContext, blockGasLimit)
661+
}
662+
646663
// PendingBlockAddTx takes a message (internal txs) and adds it to the current pending block, updating the header
647664
// with relevant execution information. If a pending block was not created, an error is returned.
648665
// Returns an error if one occurred.

chain/types/base_block_context.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package types
2+
3+
import (
4+
"math/big"
5+
6+
"github.com/ethereum/go-ethereum/common"
7+
)
8+
9+
// BaseBlockContext stores block-level information (e.g. block.number or block.timestamp) when the block is first
10+
// created. We need to store these values because cheatcodes like warp or roll will directly modify the block header.
11+
// We use these values during the cloning process to ensure that execution semantics are maintained while still
12+
// allowing the cheatcodes to function as expected. We could expand this struct to hold additional values
13+
// (e.g. difficulty) but we will ere to add values only as necessary.
14+
type BaseBlockContext struct {
15+
// Number represents the block number of the block when it was first created.
16+
Number *big.Int
17+
// Time represents the timestamp of the block when it was first created.
18+
Time uint64
19+
// BaseFee represents the base fee of the block when it was first created.
20+
BaseFee *big.Int
21+
// Coinbase represents the coinbase of the block when it was first created.
22+
Coinbase common.Address
23+
}
24+
25+
// NewBaseBlockContext returns a new BaseBlockContext with the provided parameters.
26+
func NewBaseBlockContext(number uint64, time uint64, baseFee *big.Int, coinbase common.Address) *BaseBlockContext {
27+
return &BaseBlockContext{
28+
Number: new(big.Int).SetUint64(number),
29+
Time: time,
30+
BaseFee: new(big.Int).Set(baseFee),
31+
Coinbase: coinbase,
32+
}
33+
}

chain/types/block.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import (
88

99
// Block represents a rudimentary block structure generated by sending messages to a test chain.
1010
type Block struct {
11-
// hash represents the block hash for this block.
11+
// Hash represents the block hash for this block.
1212
Hash common.Hash
1313

14-
// header represents the block header for this current block.
14+
// Header represents the block header for this current block.
1515
Header *types.Header
1616

1717
// Messages represent internal EVM core.Message objects. Messages are derived from transactions after validation
@@ -22,6 +22,12 @@ type Block struct {
2222

2323
// MessageResults represents the results recorded while executing transactions.
2424
MessageResults []*MessageResults
25+
26+
// BaseContext stores the initial (base) block context before the execution of any transactions
27+
// within the block. Since transactions that use cheatcodes can affect the block header
28+
// permanently, we need to store the original values so that we can maintain execution
29+
// semantics and allow for the chain to be clone-able.
30+
BaseContext *BaseBlockContext
2531
}
2632

2733
// NewBlock returns a new Block with the provided parameters.
@@ -32,6 +38,12 @@ func NewBlock(header *types.Header) *Block {
3238
Header: header,
3339
Messages: make([]*core.Message, 0),
3440
MessageResults: make([]*MessageResults, 0),
41+
BaseContext: NewBaseBlockContext(
42+
header.Number.Uint64(),
43+
header.Time,
44+
header.BaseFee,
45+
header.Coinbase,
46+
),
3547
}
3648
return block
3749
}

cmd/fuzz_flags.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ func updateProjectConfigWithFuzzFlags(cmd *cobra.Command, projectConfig *config.
9494
if err != nil {
9595
return err
9696
}
97-
9897
err = projectConfig.Compilation.SetTarget(newTarget)
9998
if err != nil {
10099
return err
@@ -226,10 +225,14 @@ func updateProjectConfigWithFuzzFlags(cmd *cobra.Command, projectConfig *config.
226225

227226
// Update RPC url
228227
if cmd.Flags().Changed("rpc-url") {
229-
projectConfig.Fuzzing.TestChainConfig.ForkConfig.RpcUrl, err = cmd.Flags().GetString("rpc-url")
228+
rpcUrl, err := cmd.Flags().GetString("rpc-url")
230229
if err != nil {
231230
return err
232231
}
232+
233+
// Enable on-chain fuzzing with the given URL
234+
projectConfig.Fuzzing.TestChainConfig.ForkConfig.ForkModeEnabled = true
235+
projectConfig.Fuzzing.TestChainConfig.ForkConfig.RpcUrl = rpcUrl
233236
}
234237

235238
// Update RPC block

cmd/root.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package cmd
22

33
import (
4+
"os"
5+
46
"github.com/crytic/medusa/logging"
57
"github.com/rs/zerolog"
68
"github.com/spf13/cobra"
7-
"os"
89
)
910

1011
const version = "1.0.0"

compilation/types/slither.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func (s *SlitherConfig) validateArgs() error {
7070
// getArgs returns the arguments to be provided to slither, or an error if one occurs.
7171
// The slither target is provided as an input argument.
7272
func (s *SlitherConfig) getArgs(target string) ([]string, error) {
73-
// By default we do not re-compile, use the echidna printer, and output in json format
73+
// By default, we do not re-compile, use the echidna printer, and output in json format
7474
args := []string{target, "--ignore-compile", "--print", "echidna", "--json", "-"}
7575

7676
// Add remaining args

docs/src/SUMMARY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- [Testing Configuration](project_configuration/testing_config.md)
1515
- [Chain Configuration](project_configuration/chain_config.md)
1616
- [Compilation Configuration](project_configuration/compilation_config.md)
17+
- [Slither Configuration](project_configuration/slither_config.md)
1718
- [Logging Configuration](project_configuration/logging_config.md)
1819

1920
# Command Line Interface (CLI)
@@ -43,6 +44,7 @@
4344
- [roll](./cheatcodes/roll.md)
4445
- [fee](./cheatcodes/fee.md)
4546
- [difficulty](./cheatcodes/difficulty.md)
47+
- [prevrandao](./cheatcodes/prevrandao.md)
4648
- [chainId](./cheatcodes/chain_id.md)
4749
- [store](./cheatcodes/store.md)
4850
- [load](./cheatcodes/load.md)

0 commit comments

Comments
 (0)