Skip to content
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

feat: Reversion metrics #466

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/src/project_configuration/fuzzing_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ The fuzzing configuration defines the parameters for the fuzzing campaign.
> 🚩 It is advised not to change this naively, as a minimum must be set for the chain to operate.
- **Default**: `12_500_000`

### `reversionReporterEnabled`

- **Type**: Boolean
- **Description**: Enable or disables the reversion reporting plugin
- **Default**: `false`

## Using `constructorArgs`

There might be use cases where contracts in `targetContracts` have constructors that accept arguments. The `constructorArgs`
Expand Down
2 changes: 1 addition & 1 deletion fuzzing/calls/call_sequence_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func ExecuteCallSequenceIteratively(chain *chain.TestChain, fetchElementFunc Exe
}

// Try to add our transaction to this block.
err = chain.PendingBlockAddTx(callSequenceElement.Call.ToCoreMessage(), additionalTracers...)
err := chain.PendingBlockAddTx(callSequenceElement.Call.ToCoreMessage(), additionalTracers...)

if err != nil {
// If we encountered a block gas limit error, this tx is too expensive to fit in this block.
Expand Down
3 changes: 3 additions & 0 deletions fuzzing/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ type FuzzingConfig struct {
// TransactionGasLimit describes the maximum amount of gas that will be used by the fuzzer generated transactions.
TransactionGasLimit uint64 `json:"transactionGasLimit"`

// ReversionReporterEnabled determines whether reversion metrics should be collected and reported.
ReversionReporterEnabled bool `json:"reversionReporterEnabled"`

// Testing describes the configuration used for different testing strategies.
Testing TestingConfig `json:"testing"`

Expand Down
11 changes: 6 additions & 5 deletions fuzzing/config/config_defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,12 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) {
"0x20000",
"0x30000",
},
DeployerAddress: "0x30000",
MaxBlockNumberDelay: 60480,
MaxBlockTimestampDelay: 604800,
BlockGasLimit: 125_000_000,
TransactionGasLimit: 12_500_000,
DeployerAddress: "0x30000",
MaxBlockNumberDelay: 60480,
MaxBlockTimestampDelay: 604800,
BlockGasLimit: 125_000_000,
TransactionGasLimit: 12_500_000,
ReversionReporterEnabled: false,
Testing: TestingConfig{
StopOnFailedTest: true,
StopOnFailedContractMatching: false,
Expand Down
86 changes: 46 additions & 40 deletions fuzzing/config/gen_fuzzing_config.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 19 additions & 3 deletions fuzzing/fuzzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"errors"
"fmt"
"github.com/crytic/medusa/fuzzing/coverage"
"github.com/crytic/medusa/fuzzing/reversion"
"math/big"
"math/rand"
"os"
Expand All @@ -19,7 +21,6 @@ import (

"github.com/crytic/medusa/fuzzing/executiontracer"

"github.com/crytic/medusa/fuzzing/coverage"
"github.com/crytic/medusa/logging"
"github.com/crytic/medusa/logging/colors"
"github.com/rs/zerolog"
Expand Down Expand Up @@ -70,6 +71,9 @@ type Fuzzer struct {
// corpus stores a list of transaction sequences that can be used for coverage-guided fuzzing
corpus *corpus.Corpus

// ReversionReporter tracks per-function reversion metrics, if enabled
ReversionReporter *reversion.ReversionReporter

// randomProvider describes the provider used to generate random values in the Fuzzer. All other random providers
// used by the Fuzzer's subcomponents are derived from this one.
randomProvider *rand.Rand
Expand Down Expand Up @@ -153,6 +157,7 @@ func NewFuzzer(config config.ProjectConfig) (*Fuzzer, error) {
contractDefinitions: make(fuzzerTypes.Contracts, 0),
testCases: make([]TestCase, 0),
testCasesFinished: make(map[string]TestCase),
ReversionReporter: reversion.CreateReversionReporter(config),
Hooks: FuzzerHooks{
NewCallSequenceGeneratorConfigFunc: defaultCallSequenceGeneratorConfigFunc,
NewShrinkingValueMutatorFunc: defaultShrinkingValueMutatorFunc,
Expand Down Expand Up @@ -807,12 +812,14 @@ func (f *Fuzzer) Start() error {
return err
}

// Measure reversion stats across all workers
f.ReversionReporter.StartWorker(f.ctx)

// Run the main worker loop
err = f.spawnWorkersLoop(baseTestChain)
if err != nil {
f.logger.Error("Encountered an error in the main fuzzing loop", err)
}

// NOTE: After this point, we capture errors but do not return immediately, as we want to exit gracefully.

// If we have coverage enabled and a corpus directory set, write the corpus. We do this even if we had a
Expand All @@ -831,11 +838,16 @@ func (f *Fuzzer) Start() error {
err = fuzzerStoppingErr
f.logger.Error("FuzzerStopping event subscriber returned an error", err)
}

// Print our results on exit.
f.printExitingResults()

err = f.ReversionReporter.BuildArtifact(f.logger, f.ContractDefinitions(), f.config.Fuzzing.CorpusDirectory)
if err != nil {
f.logger.Error("Failed to generate reversion stats report", err)
}

// Finally, generate our coverage report if we have set a valid corpus directory.

if err == nil && f.config.Fuzzing.CorpusDirectory != "" {
coverageReportPath := filepath.Join(f.config.Fuzzing.CorpusDirectory, "coverage_report.html")
err = coverage.GenerateReport(f.compilations, f.corpus.CoverageMaps(), coverageReportPath)
Expand All @@ -845,6 +857,10 @@ func (f *Fuzzer) Start() error {
f.logger.Info("Coverage report saved to file: ", colors.Bold, coverageReportPath, colors.Reset)
}
}
err = f.ReversionReporter.WriteReport(f.config.Fuzzing.CorpusDirectory, f.logger)
if err != nil {
f.logger.Error("Failed to write reversion metrics to disk", err)
}

// Return any encountered error.
return err
Expand Down
1 change: 1 addition & 0 deletions fuzzing/fuzzer_test_methods_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func getFuzzerTestingProjectConfig(t *testing.T, compilationConfig *compilation.
assert.NoError(t, err)
projectConfig.Compilation = compilationConfig
projectConfig.Fuzzing.Workers = 3
projectConfig.Fuzzing.ReversionReporterEnabled = true
projectConfig.Fuzzing.WorkerResetLimit = 50
projectConfig.Fuzzing.Timeout = 0
projectConfig.Fuzzing.TestLimit = 1_500_000
Expand Down
6 changes: 4 additions & 2 deletions fuzzing/fuzzer_worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,7 @@ func (fw *FuzzerWorker) run(baseTestChain *chain.TestChain) (bool, error) {
// Subscribe our chain event handlers
initializedChain.Events.ContractDeploymentAddedEventEmitter.Subscribe(fw.onChainContractDeploymentAddedEvent)
initializedChain.Events.ContractDeploymentRemovedEventEmitter.Subscribe(fw.onChainContractDeploymentRemovedEvent)
initializedChain.Events.PendingBlockCommitted.Subscribe(fw.Fuzzer().ReversionReporter.OnPendingBlockCommittedEvent)

// Emit an event indicating the worker has created its chain.
err = fw.Events.FuzzerWorkerChainCreated.Publish(FuzzerWorkerChainCreatedEvent{
Expand Down Expand Up @@ -615,9 +616,10 @@ func (fw *FuzzerWorker) run(baseTestChain *chain.TestChain) (bool, error) {
}
}

// Emit an event indicating the worker is about to test a new call sequence.
// Emit an event indicating the worker has completed testing a call sequence.
err = fw.Events.CallSequenceTested.Publish(FuzzerWorkerCallSequenceTestedEvent{
Worker: fw,
Worker: fw,
Sequence: callSequence,
})
if err != nil {
return false, fmt.Errorf("error returned by an event handler when a worker emitted an event indicating testing of a new call sequence has concluded: %v", err)
Expand Down
3 changes: 3 additions & 0 deletions fuzzing/fuzzer_worker_events.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package fuzzing
import (
"github.com/crytic/medusa/chain"
"github.com/crytic/medusa/events"
"github.com/crytic/medusa/fuzzing/calls"
"github.com/crytic/medusa/fuzzing/contracts"
"github.com/ethereum/go-ethereum/common"
)
Expand Down Expand Up @@ -93,4 +94,6 @@ type FuzzerWorkerCallSequenceTestingEvent struct {
type FuzzerWorkerCallSequenceTestedEvent struct {
// Worker represents the instance of the fuzzing.FuzzerWorker for which the event occurred.
Worker *FuzzerWorker
// Sequence represents the call sequence that was tested
Sequence calls.CallSequence
}
Loading