Skip to content

Commit 1b27cf9

Browse files
anishnaikXenomega
andauthored
Corpus (crytic#11)
* Added corpus with support for storing coverage-increasing call sequences, computing coverage on startup for those sequences, etc. * Update to only commit coverage in call frames which succeeded (did not revert) in the CoverageTracer. * Updated serialization format for coverage maps and call message to use hex strings rather than base64. * Fix for TxContext during TestChain block production * Changed coverage to be collected per deployment address/code hash * Added support for init bytecode coverage in CoverageMaps * Added DeploymentOrder configuration variable which explicitly dictates the order for contracts to be deployed in (for corpus determinism). * Refactoring of Fuzzer unit test API * Improved bytecode matching * Added validation for DeploymentOrder * Added events throughout Fuzzer/FuzzerWorker, moved TestCaseProvider to be event/hook driven. * Made EventEmitter's accepted event handlers always return an error type (so errors can be passed around). Refactored all usages of it. Co-authored-by: David Pokora <[email protected]>
1 parent 94c4d23 commit 1b27cf9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2272
-831
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ An example project configuration can be observed below:
2525
"timeout": 0,
2626
"testLimit": 10000000,
2727
"maxTxSequenceLength": 100,
28+
"corpusDirectory": "corpus",
29+
"coverageEnabled": true,
30+
"deploymentOrder": ["TestXY", "TestContract2"],
2831
"deployerAddress": "0x1111111111111111111111111111111111111111",
2932
"senderAddresses": [
3033
"0x1111111111111111111111111111111111111111",
@@ -63,8 +66,14 @@ The structure is described below:
6366
- `timeout` refers to the number of seconds before the fuzzing campaign should be terminated. If a zero value is provided, the timeout will not be enforced. The timeout begins counting after compilation succeeds and the fuzzing campaign is starting.
6467
- `testLimit` refers to a threshold of the number of function calls to make before the fuzzing campaign should be terminated. Must be a non-negative number. If a zero value is provided, no call limit will be enforced.
6568
- `maxTxSequenceLength` defines the maximum number of function calls to generate in a single sequence that tries to violate property tests. For property tests which require many calls to violate, this number should be set sufficiently high.
69+
- `corpusDirectory` refers to the path where the corpus should be saved. The corpus collects artifacts during a fuzzing campaign that help drive fuzzer features (e.g. coverage-increasing call sequences which the fuzzer collects to be mutates).
70+
- `coverageEnabled` refers to whether coverage-increasing call sequences should be saved in the corpus for the fuzzer to mutate. This aims to help achieve greater coverage across contracts when testing.
71+
- `deploymentOrder` refers to the order in which compiled contracts (contracts resulting from compilation, as specified by the `compilation` config) should be deployed to the fuzzer on startup. At least one contract name must be specified here.
72+
- **Note**: Changing this order may render entries in the corpus invalid. It is recommended to clear your corpus when doing so.
6673
- `deployerAddress` defines the account address used to deploy contracts on startup, represented as a hex string.
74+
- **Note**: Changing this address may render entries in the corpus invalid. It is recommended to clear your corpus when doing so.
6775
- `senderAddresses` defines the account addresses used to send function calls to deployed contracts in the fuzzing campaign.
76+
- **Note**: Removing previously existing addresses may render entries in the corpus invalid. It is recommended to clear your corpus when doing so.
6877
- `testing` defines the configuration for built-in test case providers which can be leveraged for fuzzing campaign.
6978
- `stopOnFailedTest` defines whether the fuzzer should exit after detecting the first failed test, or continue fuzzing to find other results.
7079
- `assertionTesting` describes configuration for assertion-based testing.

chain/test_chain.go

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,16 @@ type TestChain struct {
6464
// such as whether certain fees should be charged and which execution tracer should be used (if any).
6565
vmConfig *vm.Config
6666

67-
// BlockAddedEventEmitter serves events indicating a block was added to the chain.
68-
BlockAddedEventEmitter events.EventEmitter[BlockAddedEvent]
69-
// BlockRemovedEventEmitter serves events indicating a block was removed from the chain.
67+
// BlockMiningEventEmitter emits events indicating a block was added to the chain.
68+
BlockMiningEventEmitter events.EventEmitter[BlockMiningEvent]
69+
// BlockMinedEventEmitter emits events indicating a block was added to the chain.
70+
BlockMinedEventEmitter events.EventEmitter[BlockMinedEvent]
71+
// BlockRemovedEventEmitter emits events indicating a block was removed from the chain.
7072
BlockRemovedEventEmitter events.EventEmitter[BlockRemovedEvent]
7173

72-
// ContractDeploymentAddedEventEmitter serves events indicating a new contract was deployed to the chain.
74+
// ContractDeploymentAddedEventEmitter emits events indicating a new contract was deployed to the chain.
7375
ContractDeploymentAddedEventEmitter events.EventEmitter[ContractDeploymentsAddedEvent]
74-
// ContractDeploymentAddedEventEmitter serves events indicating a previously deployed contract was removed
76+
// ContractDeploymentAddedEventEmitter emits events indicating a previously deployed contract was removed
7577
// from the chain.
7678
ContractDeploymentRemovedEventEmitter events.EventEmitter[ContractDeploymentsRemovedEvent]
7779
}
@@ -204,7 +206,7 @@ func (t *TestChain) CopyTo(targetChain *TestChain) error {
204206
for i := 1; i < len(t.blocks); i++ {
205207
blockHeader := t.blocks[i].Header()
206208
blockNumber := blockHeader.Number.Uint64()
207-
_, err := targetChain.CreateNewBlockWithParameters(blockNumber, blockHeader.Time, t.blocks[i].Messages()...)
209+
_, err := targetChain.MineBlockWithParameters(blockNumber, blockHeader.Time, t.blocks[i].Messages()...)
208210
if err != nil {
209211
return err
210212
}
@@ -227,6 +229,12 @@ func (t *TestChain) MemoryDatabaseEntryCount() int {
227229
return t.keyValueStore.Len()
228230
}
229231

232+
// CommittedBlocks returns the real blocks which were committed to the chain, where methods such as BlockFromNumber
233+
// return the simulated chain state with intermediate blocks injected for block number jumps, etc.
234+
func (t *TestChain) CommittedBlocks() []*chainTypes.Block {
235+
return t.blocks
236+
}
237+
230238
// Head returns the head of the chain (the latest block).
231239
func (t *TestChain) Head() *chainTypes.Block {
232240
return t.blocks[len(t.blocks)-1]
@@ -418,19 +426,25 @@ func (t *TestChain) RevertToBlockNumber(blockNumber uint64) error {
418426
// Emit our events for newly deployed contracts
419427
for i := len(removedBlocks) - 1; i >= 0; i-- {
420428
// Emit our event for removing a block
421-
t.BlockRemovedEventEmitter.Publish(BlockRemovedEvent{
429+
err := t.BlockRemovedEventEmitter.Publish(BlockRemovedEvent{
422430
Chain: t,
423431
Block: removedBlocks[i],
424432
})
433+
if err != nil {
434+
return err
435+
}
425436

426437
// For each call message in our block, if we had any resulting deployed smart contracts, signal that they have
427438
// now been removed.
428439
for _, messageResult := range removedBlocks[i].MessageResults() {
429440
if len(messageResult.DeployedContractBytecodes) > 0 {
430-
t.ContractDeploymentRemovedEventEmitter.Publish(ContractDeploymentsRemovedEvent{
441+
err = t.ContractDeploymentRemovedEventEmitter.Publish(ContractDeploymentsRemovedEvent{
431442
Chain: t,
432443
DeployedContractBytecodes: messageResult.DeployedContractBytecodes,
433444
})
445+
if err != nil {
446+
return err
447+
}
434448
}
435449
}
436450
}
@@ -450,6 +464,7 @@ func (t *TestChain) CreateMessage(from common.Address, to *common.Address, value
450464
gasPrice := big.NewInt(1)
451465

452466
// Setting fee and tip cap to zero alongside the NoBaseFee for the vm.Config will bypass base fee validation.
467+
// TODO: Set this appropriately for newer transaction types.
453468
gasFeeCap := big.NewInt(0)
454469
gasTipCap := big.NewInt(0)
455470

@@ -471,7 +486,7 @@ func (t *TestChain) CallContract(msg *chainTypes.CallMessage) (*core.ExecutionRe
471486
txContext := core.NewEVMTxContext(msg)
472487
blockContext := newTestChainBlockContext(t, t.Head().Header())
473488

474-
// Create our EVM instance
489+
// Create our EVM instance.
475490
evm := vm.NewEVM(blockContext, txContext, t.state, t.chainConfig, vm.Config{NoBaseFee: true})
476491

477492
// Fund the gas pool, so it can execute endlessly (no block gas limit).
@@ -486,21 +501,31 @@ func (t *TestChain) CallContract(msg *chainTypes.CallMessage) (*core.ExecutionRe
486501
return res, err
487502
}
488503

489-
// CreateNewBlock takes messages (internal txs) and constructs a block with them, similar to a real chain using
490-
// transactions to construct a block. Returns the constructed block, or an error if one occurred.
491-
func (t *TestChain) CreateNewBlock(messages ...*chainTypes.CallMessage) (*chainTypes.Block, error) {
504+
// MineBlock takes messages (internal txs), constructs a block with them, and updates the chain head, similar to a
505+
// real chain using transactions to construct a block.
506+
// Returns the constructed block, or an error if one occurred.
507+
func (t *TestChain) MineBlock(messages ...*chainTypes.CallMessage) (*chainTypes.Block, error) {
492508
// Create a block with default parameters
493509
blockNumber := t.HeadBlockNumber() + 1
494510
timestamp := t.Head().Header().Time + 1 // TODO: Find a sensible default step for timestamp.
495-
return t.CreateNewBlockWithParameters(blockNumber, timestamp, messages...)
511+
return t.MineBlockWithParameters(blockNumber, timestamp, messages...)
496512
}
497513

498-
// CreateNewBlockWithParameters takes messages (internal txs) and constructs a block with them, similar to a real chain
499-
// using transactions to construct a block. It accepts block header parameters used to produce the block. Values
500-
// should be sensibly chosen (e.g., block number and timestamps should be greater than the previous block).
501-
// Providing a block number that is greater than the previous block number plus one will simulate empty blocks between.
514+
// MineBlockWithParameters takes messages (internal txs), constructs a block with them, and updates the chain head,
515+
// similar to a real chain using transactions to construct a block. It accepts block header parameters used to produce
516+
// the block. Values should be sensibly chosen (e.g., block number and timestamps should be greater than the previous
517+
// block). Providing a block number that is greater than the previous block number plus one will simulate empty blocks
518+
// between.
502519
// Returns the constructed block, or an error if one occurred.
503-
func (t *TestChain) CreateNewBlockWithParameters(blockNumber uint64, blockTime uint64, messages ...*chainTypes.CallMessage) (*chainTypes.Block, error) {
520+
func (t *TestChain) MineBlockWithParameters(blockNumber uint64, blockTime uint64, messages ...*chainTypes.CallMessage) (*chainTypes.Block, error) {
521+
// Emit our event for mining a new block
522+
err := t.BlockMiningEventEmitter.Publish(BlockMiningEvent{
523+
Chain: t,
524+
})
525+
if err != nil {
526+
return nil, err
527+
}
528+
504529
// Validate our block number exceeds our previous head
505530
currentHeadBlockNumber := t.Head().Header().Number.Uint64()
506531
if blockNumber <= currentHeadBlockNumber {
@@ -546,7 +571,7 @@ func (t *TestChain) CreateNewBlockWithParameters(blockNumber uint64, blockTime u
546571
Bloom: types.Bloom{},
547572
Difficulty: common.Big0,
548573
Number: big.NewInt(int64(blockNumber)),
549-
GasLimit: t.GasLimit(), // re-used from previous block
574+
GasLimit: t.GasLimit(),
550575
GasUsed: 0,
551576
Time: blockTime,
552577
Extra: []byte{},
@@ -572,7 +597,7 @@ func (t *TestChain) CreateNewBlockWithParameters(blockNumber uint64, blockTime u
572597
blockContext := newTestChainBlockContext(t, header)
573598

574599
// Create our EVM instance.
575-
evm := vm.NewEVM(blockContext, vm.TxContext{}, t.state, t.chainConfig, *t.vmConfig)
600+
evm := vm.NewEVM(blockContext, core.NewEVMTxContext(messages[i]), t.state, t.chainConfig, *t.vmConfig)
576601

577602
// Apply our transaction
578603
var usedGas uint64
@@ -617,19 +642,25 @@ func (t *TestChain) CreateNewBlockWithParameters(blockNumber uint64, blockTime u
617642
// Append our new block to our chain.
618643
t.blocks = append(t.blocks, block)
619644

620-
// Emit our event for adding a new block
621-
t.BlockAddedEventEmitter.Publish(BlockAddedEvent{
645+
// Emit our event for mining a new block
646+
err = t.BlockMinedEventEmitter.Publish(BlockMinedEvent{
622647
Chain: t,
623648
Block: block,
624649
})
650+
if err != nil {
651+
return nil, err
652+
}
625653

626654
// Emit our events for newly deployed contracts
627655
for _, messageResult := range messageResults {
628656
if len(messageResult.DeployedContractBytecodes) > 0 {
629-
t.ContractDeploymentAddedEventEmitter.Publish(ContractDeploymentsAddedEvent{
657+
err = t.ContractDeploymentAddedEventEmitter.Publish(ContractDeploymentsAddedEvent{
630658
Chain: t,
631659
DeployedContractBytecodes: messageResult.DeployedContractBytecodes,
632660
})
661+
if err != nil {
662+
return nil, err
663+
}
633664
}
634665
}
635666

@@ -655,7 +686,7 @@ func (t *TestChain) DeployContract(contract *compilationTypes.CompiledContract,
655686
msg := t.CreateMessage(deployer, nil, value, b)
656687

657688
// Create a new block with our deployment message/tx.
658-
block, err := t.CreateNewBlock(msg)
689+
block, err := t.MineBlock(msg)
659690
if err != nil {
660691
return common.Address{}, nil, err
661692
}

chain/test_chain_events.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@ package chain
22

33
import "github.com/trailofbits/medusa/chain/types"
44

5-
// BlockAddedEvent describes an event where a new block is added to the TestChain. This only considers internally
6-
// committed blocks, not ones spoofed in between block number jumps.
7-
type BlockAddedEvent struct {
5+
// BlockMiningEvent describes an event where a new block has been requested to be mined in the TestChain. This only
6+
// considers committed blocks.
7+
type BlockMiningEvent struct {
8+
// Chain refers to the TestChain which emitted the event.
9+
Chain *TestChain
10+
}
11+
12+
// BlockMinedEvent describes an event where a new block is added to the TestChain. This only considers committed blocks.
13+
type BlockMinedEvent struct {
814
// Chain refers to the TestChain which emitted the event.
915
Chain *TestChain
1016

chain/test_chain_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func TestChainReverting(t *testing.T) {
9898
// Determine if this will jump a certain number of blocks.
9999
if rand.Float32() >= blockNumberJumpProbability {
100100
// We decided not to jump, so we commit a block with a normal consecutive block number.
101-
_, err := chain.CreateNewBlock()
101+
_, err := chain.MineBlock()
102102
assert.NoError(t, err)
103103
} else {
104104
// We decided to jump, so we commit a block with a random jump amount.
@@ -110,7 +110,7 @@ func TestChainReverting(t *testing.T) {
110110
// the diff.
111111

112112
// Create a block with our parameters
113-
_, err := chain.CreateNewBlockWithParameters(newBlockNumber, chain.Head().Header().Time+jumpDistance)
113+
_, err := chain.MineBlockWithParameters(newBlockNumber, chain.Head().Header().Time+jumpDistance)
114114
assert.NoError(t, err)
115115
}
116116

@@ -158,7 +158,7 @@ func TestChainBlockNumberJumping(t *testing.T) {
158158
// Determine if this will jump a certain number of blocks.
159159
if rand.Float32() >= blockNumberJumpProbability {
160160
// We decided not to jump, so we commit a block with a normal consecutive block number.
161-
_, err := chain.CreateNewBlock()
161+
_, err := chain.MineBlock()
162162
assert.NoError(t, err)
163163
} else {
164164
// We decided to jump, so we commit a block with a random jump amount.
@@ -170,7 +170,7 @@ func TestChainBlockNumberJumping(t *testing.T) {
170170
// the diff.
171171

172172
// Create a block with our parameters
173-
_, err := chain.CreateNewBlockWithParameters(newBlockNumber, chain.Head().Header().Time+jumpDistance)
173+
_, err := chain.MineBlockWithParameters(newBlockNumber, chain.Head().Header().Time+jumpDistance)
174174
assert.NoError(t, err)
175175
}
176176
}
@@ -233,7 +233,7 @@ func TestChainDynamicDeployments(t *testing.T) {
233233

234234
// Create some empty blocks and ensure we can get our state for this block number.
235235
for x := 0; x < 5; x++ {
236-
block, err = chain.CreateNewBlock()
236+
block, err = chain.MineBlock()
237237
assert.NoError(t, err)
238238

239239
// Empty blocks should not record message results or dynamic deployments.
@@ -298,7 +298,7 @@ func TestChainCloning(t *testing.T) {
298298

299299
// Create some empty blocks and ensure we can get our state for this block number.
300300
for x := 0; x < i; x++ {
301-
_, err = chain.CreateNewBlock()
301+
_, err = chain.MineBlock()
302302
assert.NoError(t, err)
303303

304304
_, err = chain.StateAfterBlockNumber(chain.HeadBlockNumber())
@@ -364,7 +364,7 @@ func TestChainCallSequenceReplayMatchSimple(t *testing.T) {
364364

365365
// Create some empty blocks and ensure we can get our state for this block number.
366366
for x := 0; x < i; x++ {
367-
_, err = chain.CreateNewBlock()
367+
_, err = chain.MineBlock()
368368
assert.NoError(t, err)
369369

370370
_, err = chain.StateAfterBlockNumber(chain.HeadBlockNumber())
@@ -382,7 +382,7 @@ func TestChainCallSequenceReplayMatchSimple(t *testing.T) {
382382

383383
// Replay all messages after genesis
384384
for i := 1; i < len(chain.blocks); i++ {
385-
_, err := recreatedChain.CreateNewBlock(chain.blocks[i].Messages()...)
385+
_, err := recreatedChain.MineBlock(chain.blocks[i].Messages()...)
386386
assert.NoError(t, err)
387387
}
388388

chain/test_chain_tracer.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ type testChainTracer struct {
1414
// callDepth refers to the current EVM depth during tracing.
1515
callDepth uint64
1616

17+
// evm refers to the EVM instance last captured.
18+
evm *vm.EVM
19+
1720
// deployedContractBytecode is a list of all bytecode deployments found from the current/last transaction
1821
// executed.
1922
deployedContractBytecode []*types.DeployedContractBytecode
@@ -44,6 +47,9 @@ func (t *testChainTracer) CaptureTxEnd(restGas uint64) {
4447

4548
// CaptureStart initializes the tracing operation for the top of a call frame, as defined by vm.EVMLogger.
4649
func (t *testChainTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
50+
// Store our evm reference
51+
t.evm = env
52+
4753
// Create our stack item for pending deployments discovered at this call frame depth.
4854
t.pendingDeployedContractBytecode = append(t.pendingDeployedContractBytecode, make([]*types.DeployedContractBytecode, 0))
4955

@@ -64,6 +70,11 @@ func (t *testChainTracer) CaptureEnd(output []byte, gasUsed uint64, d time.Durat
6470
// If we encountered an error, we reverted, so we don't consider the deployments.
6571
if err == nil {
6672
t.deployedContractBytecode = append(t.deployedContractBytecode, t.pendingDeployedContractBytecode[t.callDepth]...)
73+
74+
// Fetch runtime bytecode for all verified deployments.
75+
for _, singleDeployment := range t.deployedContractBytecode {
76+
singleDeployment.RuntimeBytecode = t.evm.StateDB.GetCode(singleDeployment.Address)
77+
}
6778
}
6879

6980
// Pop the pending contracts for this frame off the stack.

0 commit comments

Comments
 (0)