From cbde4a3f6891b08657dde531799ec80a939fffa3 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Fri, 4 Aug 2023 13:24:33 +0200 Subject: [PATCH 01/55] simple json report generation --- fuzzing/coverage/report_generation.go | 83 ++++++++++++++++++++++++- fuzzing/coverage/report_template.gojson | 15 +++++ 2 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 fuzzing/coverage/report_template.gojson diff --git a/fuzzing/coverage/report_generation.go b/fuzzing/coverage/report_generation.go index 184038f7..9caa009f 100644 --- a/fuzzing/coverage/report_generation.go +++ b/fuzzing/coverage/report_generation.go @@ -3,20 +3,26 @@ package coverage import ( _ "embed" "fmt" - "github.com/crytic/medusa/compilation/types" - "github.com/crytic/medusa/utils" "html/template" "math" "os" "path/filepath" "strconv" + "strings" "time" + + "github.com/crytic/medusa/compilation/types" + "github.com/crytic/medusa/utils" ) var ( //go:embed report_template.gohtml htmlReportTemplate []byte ) +var ( + //go:embed report_template.gojson + jsonReportTemplate []byte +) // GenerateReport takes a set of CoverageMaps and compilations, and produces a coverage report using them, detailing // all source mapped ranges of the source files which were covered or not. @@ -31,7 +37,80 @@ func GenerateReport(compilations []types.Compilation, coverageMaps *CoverageMaps // Finally, export the report data we analyzed. if htmlReportPath != "" { err = exportCoverageReport(sourceAnalysis, htmlReportPath) + err = exportCoverageReportJSON(sourceAnalysis, htmlReportPath) + } + return err +} + +// exportCoverageReportJSON takes a previously performed source analysis and generates a JSON coverage report from it. +// Returns an error if one occurs. +func exportCoverageReportJSON(sourceAnalysis *SourceAnalysis, outputPath string) error { + functionMap := template.FuncMap{ + "add": func(x int, y int) int { + return x + y + }, + "sub": func(x int, y int) int { + return x - y + }, + "relativePath": func(path string) string { + // Obtain a path relative to our current working directory. + // If we encounter an error, return the original path. + cwd, err := os.Getwd() + if err != nil { + return path + } + relativePath, err := filepath.Rel(cwd, path) + if err != nil { + return path + } + + return relativePath + }, + "lastActiveIndex": func(sourceFileAnalysis *SourceFileAnalysis) int { + // Determine the last active line index and return it + lastIndex := 0 + for lineIndex, line := range sourceFileAnalysis.Lines { + if line.IsActive { + lastIndex = lineIndex + } + } + return lastIndex + }, } + + // Parse our JSON template + tmpl, err := template.New("coverage_report.json").Funcs(functionMap).Parse(string(jsonReportTemplate)) + + // Converts the html path to the json path + // TODO: Remove this in favor of a more generic approach + parts := strings.Split(outputPath, "/") + parts = parts[:len(parts)-1] + outputPath = strings.Join(parts, "/") + outputPath = outputPath + "/coverage_report.json" + + fmt.Println("Report path:", outputPath) + + // If the parent directory doesn't exist, create it. + parentDirectory := filepath.Dir(outputPath) + err = utils.MakeDirectory(parentDirectory) + if err != nil { + return err + } + + // Create our report file + file, err := os.Create(outputPath) + if err != nil { + _ = file.Close() + return fmt.Errorf("could not export report, failed to open file for writing: %v", err) + } + + // Execute the template and write it back to file. + err = tmpl.Execute(file, sourceAnalysis) + fileCloseErr := file.Close() + if err == nil { + err = fileCloseErr + } + return err } diff --git a/fuzzing/coverage/report_template.gojson b/fuzzing/coverage/report_template.gojson new file mode 100644 index 00000000..7e841d5d --- /dev/null +++ b/fuzzing/coverage/report_template.gojson @@ -0,0 +1,15 @@ +[{{range $index, $sourceFile := .SortedFiles}}{{if $index}},{{end}} + { + "lines": { + "found": {{$sourceFile.ActiveLineCount}}, + "hit": {{$sourceFile.CoveredLineCount}}, + "details": [{{$lastActive := lastActiveIndex $sourceFile}}{{range $lineIndex, $line := $sourceFile.Lines}}{{if $line.IsActive}} + { + "line": {{add $lineIndex 1}}, + "hit": {{if or $line.IsCovered $line.IsCoveredReverted}} 1 {{else}} 0 {{end}} + }{{if ne $lineIndex $lastActive}},{{end}}{{end}}{{end}} + ], + "file": "{{relativePath $sourceFile.Path}}" + } + }{{end}} +] \ No newline at end of file From 095fb735383cd46b70d62d1bfbe54e247bd2b51e Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Fri, 4 Aug 2023 17:20:59 +0200 Subject: [PATCH 02/55] fix json template, change htmlReportPath to corpus path --- fuzzing/coverage/report_generation.go | 20 +++++++++----------- fuzzing/coverage/report_template.gojson | 6 +++--- fuzzing/fuzzer.go | 14 +++++++------- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/fuzzing/coverage/report_generation.go b/fuzzing/coverage/report_generation.go index 9caa009f..a1c07cf2 100644 --- a/fuzzing/coverage/report_generation.go +++ b/fuzzing/coverage/report_generation.go @@ -8,7 +8,6 @@ import ( "os" "path/filepath" "strconv" - "strings" "time" "github.com/crytic/medusa/compilation/types" @@ -27,7 +26,7 @@ var ( // GenerateReport takes a set of CoverageMaps and compilations, and produces a coverage report using them, detailing // all source mapped ranges of the source files which were covered or not. // Returns an error if one occurred. -func GenerateReport(compilations []types.Compilation, coverageMaps *CoverageMaps, htmlReportPath string) error { +func GenerateReport(compilations []types.Compilation, coverageMaps *CoverageMaps, reportPath string) error { // Perform source analysis. sourceAnalysis, err := AnalyzeSourceCoverage(compilations, coverageMaps) if err != nil { @@ -35,9 +34,9 @@ func GenerateReport(compilations []types.Compilation, coverageMaps *CoverageMaps } // Finally, export the report data we analyzed. - if htmlReportPath != "" { - err = exportCoverageReport(sourceAnalysis, htmlReportPath) - err = exportCoverageReportJSON(sourceAnalysis, htmlReportPath) + if reportPath != "" { + err = exportCoverageReport(sourceAnalysis, reportPath) + err = exportCoverageReportJSON(sourceAnalysis, reportPath) } return err } @@ -81,12 +80,8 @@ func exportCoverageReportJSON(sourceAnalysis *SourceAnalysis, outputPath string) // Parse our JSON template tmpl, err := template.New("coverage_report.json").Funcs(functionMap).Parse(string(jsonReportTemplate)) - // Converts the html path to the json path - // TODO: Remove this in favor of a more generic approach - parts := strings.Split(outputPath, "/") - parts = parts[:len(parts)-1] - outputPath = strings.Join(parts, "/") - outputPath = outputPath + "/coverage_report.json" + // Add the report file to the path + outputPath = filepath.Join(outputPath, "coverage_report.json") fmt.Println("Report path:", outputPath) @@ -161,6 +156,9 @@ func exportCoverageReport(sourceAnalysis *SourceAnalysis, outputPath string) err return fmt.Errorf("could not export report, failed to parse report template: %v", err) } + // Add the report file to the path + outputPath = filepath.Join(outputPath, "coverage_report.html") + // If the parent directory doesn't exist, create it. parentDirectory := filepath.Dir(outputPath) err = utils.MakeDirectory(parentDirectory) diff --git a/fuzzing/coverage/report_template.gojson b/fuzzing/coverage/report_template.gojson index 7e841d5d..4d63b264 100644 --- a/fuzzing/coverage/report_template.gojson +++ b/fuzzing/coverage/report_template.gojson @@ -8,8 +8,8 @@ "line": {{add $lineIndex 1}}, "hit": {{if or $line.IsCovered $line.IsCoveredReverted}} 1 {{else}} 0 {{end}} }{{if ne $lineIndex $lastActive}},{{end}}{{end}}{{end}} - ], - "file": "{{relativePath $sourceFile.Path}}" - } + ] + }, + "file": "{{relativePath $sourceFile.Path}}" }{{end}} ] \ No newline at end of file diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index e64a7ba2..40b362ae 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -3,21 +3,21 @@ package fuzzing import ( "context" "fmt" - "github.com/crytic/medusa/fuzzing/coverage" - "github.com/crytic/medusa/logging" - "github.com/crytic/medusa/logging/colors" - "github.com/rs/zerolog" - "github.com/rs/zerolog/pkgerrors" "io" "math/big" "math/rand" - "path/filepath" "sort" "strconv" "strings" "sync" "time" + "github.com/crytic/medusa/fuzzing/coverage" + "github.com/crytic/medusa/logging" + "github.com/crytic/medusa/logging/colors" + "github.com/rs/zerolog" + "github.com/rs/zerolog/pkgerrors" + "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/utils/randomutils" "github.com/ethereum/go-ethereum/core/types" @@ -684,7 +684,7 @@ func (f *Fuzzer) Start() error { // 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") + coverageReportPath := f.config.Fuzzing.CorpusDirectory err = coverage.GenerateReport(f.compilations, f.corpus.CoverageMaps(), coverageReportPath) f.logger.Info("Coverage report saved to file: ", colors.Bold, coverageReportPath, colors.Reset) } From 18433cc36158b8f8204aeb22402af8bed0b0b348 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Fri, 4 Aug 2023 17:50:32 +0200 Subject: [PATCH 03/55] added html and json file path configs, updated report generation --- fuzzing/config/config.go | 11 ++++++++- fuzzing/config/config_defaults.go | 2 ++ fuzzing/coverage/report_generation.go | 33 +++++++++++++++------------ fuzzing/fuzzer.go | 10 +++++--- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/fuzzing/config/config.go b/fuzzing/config/config.go index 283e5329..3e1a1560 100644 --- a/fuzzing/config/config.go +++ b/fuzzing/config/config.go @@ -3,9 +3,10 @@ package config import ( "encoding/json" "errors" + "os" + "github.com/crytic/medusa/chain/config" "github.com/rs/zerolog" - "os" "github.com/crytic/medusa/compilation" "github.com/crytic/medusa/utils" @@ -46,6 +47,14 @@ type FuzzingConfig struct { // the in-memory corpus will be used, but not flush to disk. CorpusDirectory string `json:"corpusDirectory"` + // HtmlReportFile describes the name for the html coverage file. If empty, + // the html coverage file will not be saved + HtmlReportFile string `json:"htmlReportPath"` + + // JsonReportFile describes the name for the html coverage file. If empty, + // the json coverage file will not be saved + JsonReportFile string `json:"jsonReportPath"` + // CoverageEnabled describes whether to use coverage-guided fuzzing CoverageEnabled bool `json:"coverageEnabled"` diff --git a/fuzzing/config/config_defaults.go b/fuzzing/config/config_defaults.go index 2a6e92f0..e4ef3644 100644 --- a/fuzzing/config/config_defaults.go +++ b/fuzzing/config/config_defaults.go @@ -40,6 +40,8 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) { DeploymentOrder: []string{}, ConstructorArgs: map[string]map[string]any{}, CorpusDirectory: "", + HtmlReportFile: "coverage_report.html", + JsonReportFile: "coverage_report.json", CoverageEnabled: true, SenderAddresses: []string{ "0x10000", diff --git a/fuzzing/coverage/report_generation.go b/fuzzing/coverage/report_generation.go index a1c07cf2..70411672 100644 --- a/fuzzing/coverage/report_generation.go +++ b/fuzzing/coverage/report_generation.go @@ -17,8 +17,6 @@ import ( var ( //go:embed report_template.gohtml htmlReportTemplate []byte -) -var ( //go:embed report_template.gojson jsonReportTemplate []byte ) @@ -26,24 +24,35 @@ var ( // GenerateReport takes a set of CoverageMaps and compilations, and produces a coverage report using them, detailing // all source mapped ranges of the source files which were covered or not. // Returns an error if one occurred. -func GenerateReport(compilations []types.Compilation, coverageMaps *CoverageMaps, reportPath string) error { +func GenerateReport(compilations []types.Compilation, coverageMaps *CoverageMaps, corpusPath string, htmlReportPath string, jsonReportPath string) error { // Perform source analysis. sourceAnalysis, err := AnalyzeSourceCoverage(compilations, coverageMaps) if err != nil { return err } - // Finally, export the report data we analyzed. - if reportPath != "" { - err = exportCoverageReport(sourceAnalysis, reportPath) - err = exportCoverageReportJSON(sourceAnalysis, reportPath) + // Stores the output path of the report + var outputPath string + + // Export the html report of the data we analyzed. + if htmlReportPath != "" { + outputPath = filepath.Join(corpusPath, htmlReportPath) + err = exportHtmlCoverageReport(sourceAnalysis, outputPath) + } + // Export the json report of the data we analyzed. + if jsonReportPath != "" { + outputPath = filepath.Join(corpusPath, jsonReportPath) + err2 := exportJsonCoverageReport(sourceAnalysis, outputPath) + if err == nil && err2 != nil { + err = err2 + } } return err } // exportCoverageReportJSON takes a previously performed source analysis and generates a JSON coverage report from it. // Returns an error if one occurs. -func exportCoverageReportJSON(sourceAnalysis *SourceAnalysis, outputPath string) error { +func exportJsonCoverageReport(sourceAnalysis *SourceAnalysis, outputPath string) error { functionMap := template.FuncMap{ "add": func(x int, y int) int { return x + y @@ -80,9 +89,6 @@ func exportCoverageReportJSON(sourceAnalysis *SourceAnalysis, outputPath string) // Parse our JSON template tmpl, err := template.New("coverage_report.json").Funcs(functionMap).Parse(string(jsonReportTemplate)) - // Add the report file to the path - outputPath = filepath.Join(outputPath, "coverage_report.json") - fmt.Println("Report path:", outputPath) // If the parent directory doesn't exist, create it. @@ -111,7 +117,7 @@ func exportCoverageReportJSON(sourceAnalysis *SourceAnalysis, outputPath string) // exportCoverageReport takes a previously performed source analysis and generates an HTML coverage report from it. // Returns an error if one occurs. -func exportCoverageReport(sourceAnalysis *SourceAnalysis, outputPath string) error { +func exportHtmlCoverageReport(sourceAnalysis *SourceAnalysis, outputPath string) error { // Define mappings onto some useful variables/functions. functionMap := template.FuncMap{ "timeNow": time.Now, @@ -156,9 +162,6 @@ func exportCoverageReport(sourceAnalysis *SourceAnalysis, outputPath string) err return fmt.Errorf("could not export report, failed to parse report template: %v", err) } - // Add the report file to the path - outputPath = filepath.Join(outputPath, "coverage_report.html") - // If the parent directory doesn't exist, create it. parentDirectory := filepath.Dir(outputPath) err = utils.MakeDirectory(parentDirectory) diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index 40b362ae..c7a06ad3 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -6,6 +6,7 @@ import ( "io" "math/big" "math/rand" + "path/filepath" "sort" "strconv" "strings" @@ -684,9 +685,12 @@ func (f *Fuzzer) Start() error { // Finally, generate our coverage report if we have set a valid corpus directory. if err == nil && f.config.Fuzzing.CorpusDirectory != "" { - coverageReportPath := f.config.Fuzzing.CorpusDirectory - err = coverage.GenerateReport(f.compilations, f.corpus.CoverageMaps(), coverageReportPath) - f.logger.Info("Coverage report saved to file: ", colors.Bold, coverageReportPath, colors.Reset) + htmlReportPath := f.config.Fuzzing.HtmlReportFile + jsonReportPath := f.config.Fuzzing.JsonReportFile + corpusDir := f.config.Fuzzing.CorpusDirectory + err = coverage.GenerateReport(f.compilations, f.corpus.CoverageMaps(), corpusDir, htmlReportPath, jsonReportPath) + f.logger.Info("HTML Coverage report saved to file: ", colors.Bold, filepath.Join(corpusDir, htmlReportPath), colors.Reset) + f.logger.Info("JSON Coverage report saved to file: ", colors.Bold, filepath.Join(corpusDir, jsonReportPath), colors.Reset) } // Return any encountered error. From 8ec0007e624d774eb2d2ea17a7fea1e46a1023ba Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Fri, 4 Aug 2023 18:05:47 +0200 Subject: [PATCH 04/55] fix lint error --- fuzzing/coverage/report_generation.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fuzzing/coverage/report_generation.go b/fuzzing/coverage/report_generation.go index 70411672..abe8cc7c 100644 --- a/fuzzing/coverage/report_generation.go +++ b/fuzzing/coverage/report_generation.go @@ -88,8 +88,9 @@ func exportJsonCoverageReport(sourceAnalysis *SourceAnalysis, outputPath string) // Parse our JSON template tmpl, err := template.New("coverage_report.json").Funcs(functionMap).Parse(string(jsonReportTemplate)) - - fmt.Println("Report path:", outputPath) + if err != nil { + return fmt.Errorf("could not export report, failed to parse report template: %v", err) + } // If the parent directory doesn't exist, create it. parentDirectory := filepath.Dir(outputPath) From 194a782e6d3627f3bb46b7a2802407363bbe32c2 Mon Sep 17 00:00:00 2001 From: David Pokora Date: Fri, 11 Aug 2023 10:58:38 -0400 Subject: [PATCH 05/55] Update to go-ethereum 1.12.0 (#199) * Updated CallMessage to be compliant with core.Message made in new go-ethereum --------- Co-authored-by: anishnaik --- chain/test_chain.go | 18 +- chain/test_chain_test.go | 64 ++++++- chain/types/block.go | 4 +- chain/vendored/apply_transaction.go | 4 +- compilation/types/source_maps.go | 2 +- fuzzing/calls/call_message.go | 197 +++++++++++--------- fuzzing/calls/call_sequence.go | 16 +- fuzzing/calls/call_sequence_execution.go | 2 +- fuzzing/calls/gen_call_message_json.go | 113 ++++++----- fuzzing/corpus/corpus.go | 8 +- fuzzing/corpus/corpus_test.go | 18 +- fuzzing/executiontracer/execution_tracer.go | 2 +- fuzzing/fuzzer.go | 3 +- fuzzing/fuzzer_worker.go | 23 ++- fuzzing/fuzzer_worker_sequence_generator.go | 8 +- fuzzing/test_case_optimization_provider.go | 4 +- fuzzing/test_case_property_provider.go | 4 +- go.mod | 13 +- go.sum | 56 +----- utils/message_transaction_utils.go | 14 +- 20 files changed, 313 insertions(+), 260 deletions(-) diff --git a/chain/test_chain.go b/chain/test_chain.go index ec60979c..a4466cc7 100644 --- a/chain/test_chain.go +++ b/chain/test_chain.go @@ -89,6 +89,14 @@ func NewTestChain(genesisAlloc core.GenesisAlloc, testChainConfig *config.TestCh return nil, err } + // TODO: go-ethereum doesn't set shanghai start time for THEIR test `ChainConfig` struct. + // Note: We have our own `TestChainConfig` definition that is different (second argument in this function). + // We should allow the user to provide a go-ethereum `ChainConfig` to do custom fork selection, inside of our + // `TestChainConfig` definition. Or we should wrap it in our own struct to simplify the options and not pollute + // our overall medusa project config. + shanghaiTime := uint64(0) + chainConfig.ShanghaiTime = &shanghaiTime + // Create our genesis definition with our default chain config. genesisDefinition := &core.Genesis{ Config: chainConfig, @@ -527,7 +535,7 @@ func (t *TestChain) RevertToBlockNumber(blockNumber uint64) error { // It takes an optional state argument, which is the state to execute the message over. If not provided, the // current pending state (or committed state if none is pending) will be used instead. // The state executed over may be a pending block state. -func (t *TestChain) CallContract(msg core.Message, state *state.StateDB, additionalTracers ...vm.EVMLogger) (*core.ExecutionResult, error) { +func (t *TestChain) CallContract(msg *core.Message, state *state.StateDB, additionalTracers ...vm.EVMLogger) (*core.ExecutionResult, error) { // If our provided state is nil, use our current chain state. if state == nil { state = t.state @@ -537,7 +545,7 @@ func (t *TestChain) CallContract(msg core.Message, state *state.StateDB, additio snapshot := state.Snapshot() // Set infinite balance to the fake caller account - from := state.GetOrNewStateObject(msg.From()) + from := state.GetOrNewStateObject(msg.From) from.SetBalance(math.MaxBig256) // Create our transaction and block contexts for the vm @@ -552,7 +560,7 @@ func (t *TestChain) CallContract(msg core.Message, state *state.StateDB, additio // Create our EVM instance. evm := vm.NewEVM(blockContext, txContext, state, t.chainConfig, vm.Config{ - Debug: true, + //Debug: true, Tracer: extendedTracerRouter, NoBaseFee: true, ConfigExtensions: t.vmConfigExtensions, @@ -672,7 +680,7 @@ func (t *TestChain) PendingBlockCreateWithParameters(blockNumber uint64, blockTi // PendingBlockAddTx takes a message (internal txs) and adds it to the current pending block, updating the header // with relevant execution information. If a pending block was not created, an error is returned. // Returns the constructed block, or an error if one occurred. -func (t *TestChain) PendingBlockAddTx(message core.Message) error { +func (t *TestChain) PendingBlockAddTx(message *core.Message) error { // If we don't have a pending block, return an error if t.pendingBlock == nil { return errors.New("could not add tx to the chain's pending block because no pending block was created") @@ -692,7 +700,7 @@ func (t *TestChain) PendingBlockAddTx(message core.Message) error { // Create our EVM instance. evm := vm.NewEVM(blockContext, core.NewEVMTxContext(message), t.state, t.chainConfig, vm.Config{ - Debug: true, + //Debug: true, Tracer: t.transactionTracerRouter, NoBaseFee: true, ConfigExtensions: t.vmConfigExtensions, diff --git a/chain/test_chain_test.go b/chain/test_chain_test.go index bb049d1c..5c8e6449 100644 --- a/chain/test_chain_test.go +++ b/chain/test_chain_test.go @@ -241,14 +241,26 @@ func TestChainDynamicDeployments(t *testing.T) { // Deploy the currently indexed contract next // Create a message to represent our contract deployment. - msg := types.NewMessage(senders[0], nil, chain.State().GetNonce(senders[0]), big.NewInt(0), chain.BlockGasLimit, big.NewInt(1), big.NewInt(0), big.NewInt(0), contract.InitBytecode, nil, false) + msg := core.Message{ + To: nil, + From: senders[0], + Nonce: chain.State().GetNonce(senders[0]), + Value: big.NewInt(0), + GasLimit: chain.BlockGasLimit, + GasPrice: big.NewInt(1), + GasFeeCap: big.NewInt(0), + GasTipCap: big.NewInt(0), + Data: contract.InitBytecode, + AccessList: nil, + SkipAccountChecks: false, + } // Create a new pending block we'll commit to chain block, err := chain.PendingBlockCreate() assert.NoError(t, err) // Add our transaction to the block - err = chain.PendingBlockAddTx(msg) + err = chain.PendingBlockAddTx(&msg) assert.NoError(t, err) // Commit the pending block to the chain, so it becomes the new head. @@ -354,14 +366,26 @@ func TestChainDeploymentWithArgs(t *testing.T) { assert.NoError(t, err) // Create a message to represent our contract deployment. - msg := types.NewMessage(senders[0], nil, chain.State().GetNonce(senders[0]), big.NewInt(0), chain.BlockGasLimit, big.NewInt(1), big.NewInt(0), big.NewInt(0), msgData, nil, false) + msg := core.Message{ + To: nil, + From: senders[0], + Nonce: chain.State().GetNonce(senders[0]), + Value: big.NewInt(0), + GasLimit: chain.BlockGasLimit, + GasPrice: big.NewInt(1), + GasFeeCap: big.NewInt(0), + GasTipCap: big.NewInt(0), + Data: msgData, + AccessList: nil, + SkipAccountChecks: false, + } // Create a new pending block we'll commit to chain block, err := chain.PendingBlockCreate() assert.NoError(t, err) // Add our transaction to the block - err = chain.PendingBlockAddTx(msg) + err = chain.PendingBlockAddTx(&msg) assert.NoError(t, err) // Commit the pending block to the chain, so it becomes the new head. @@ -451,14 +475,26 @@ func TestChainCloning(t *testing.T) { // Deploy the currently indexed contract next // Create a message to represent our contract deployment. - msg := types.NewMessage(senders[0], nil, chain.State().GetNonce(senders[0]), big.NewInt(0), chain.BlockGasLimit, big.NewInt(1), big.NewInt(0), big.NewInt(0), contract.InitBytecode, nil, false) + msg := core.Message{ + To: nil, + From: senders[0], + Nonce: chain.State().GetNonce(senders[0]), + Value: big.NewInt(0), + GasLimit: chain.BlockGasLimit, + GasPrice: big.NewInt(1), + GasFeeCap: big.NewInt(0), + GasTipCap: big.NewInt(0), + Data: contract.InitBytecode, + AccessList: nil, + SkipAccountChecks: false, + } // Create a new pending block we'll commit to chain block, err := chain.PendingBlockCreate() assert.NoError(t, err) // Add our transaction to the block - err = chain.PendingBlockAddTx(msg) + err = chain.PendingBlockAddTx(&msg) assert.NoError(t, err) // Commit the pending block to the chain, so it becomes the new head. @@ -533,14 +569,26 @@ func TestChainCallSequenceReplayMatchSimple(t *testing.T) { if len(contract.Abi.Constructor.Inputs) == 0 { for i := 0; i < 10; i++ { // Create a message to represent our contract deployment. - msg := types.NewMessage(senders[0], nil, chain.State().GetNonce(senders[0]), big.NewInt(0), chain.BlockGasLimit, big.NewInt(1), big.NewInt(0), big.NewInt(0), contract.InitBytecode, nil, false) + msg := core.Message{ + To: nil, + From: senders[0], + Nonce: chain.State().GetNonce(senders[0]), + Value: big.NewInt(0), + GasLimit: chain.BlockGasLimit, + GasPrice: big.NewInt(1), + GasFeeCap: big.NewInt(0), + GasTipCap: big.NewInt(0), + Data: contract.InitBytecode, + AccessList: nil, + SkipAccountChecks: false, + } // Create a new pending block we'll commit to chain block, err := chain.PendingBlockCreate() assert.NoError(t, err) // Add our transaction to the block - err = chain.PendingBlockAddTx(msg) + err = chain.PendingBlockAddTx(&msg) assert.NoError(t, err) // Commit the pending block to the chain, so it becomes the new head. diff --git a/chain/types/block.go b/chain/types/block.go index 47638e19..754f81ff 100644 --- a/chain/types/block.go +++ b/chain/types/block.go @@ -18,7 +18,7 @@ type Block struct { // of a transaction occurs and can be thought of as an internal EVM transaction. It contains typical transaction // fields plainly (e.g., no transaction signature is included, the sender is derived and simply supplied as a field // in a message). - Messages []core.Message + Messages []*core.Message // MessageResults represents the results recorded while executing transactions. MessageResults []*MessageResults @@ -30,7 +30,7 @@ func NewBlock(header *types.Header) *Block { block := &Block{ Hash: header.Hash(), Header: header, - Messages: make([]core.Message, 0), + Messages: make([]*core.Message, 0), MessageResults: make([]*MessageResults, 0), } return block diff --git a/chain/vendored/apply_transaction.go b/chain/vendored/apply_transaction.go index e9fab99e..2044bf02 100644 --- a/chain/vendored/apply_transaction.go +++ b/chain/vendored/apply_transaction.go @@ -35,7 +35,7 @@ import ( // This executes on an underlying EVM and returns a transaction receipt, or an error if one occurs. // Additional changes: // - Exposed core.ExecutionResult as a return value. -func EVMApplyTransaction(msg Message, config *params.ChainConfig, author *common.Address, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, *ExecutionResult, error) { +func EVMApplyTransaction(msg *Message, config *params.ChainConfig, author *common.Address, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, *ExecutionResult, error) { // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) evm.Reset(txContext, statedb) @@ -67,7 +67,7 @@ func EVMApplyTransaction(msg Message, config *params.ChainConfig, author *common receipt.GasUsed = result.UsedGas // If the transaction created a contract, store the creation address in the receipt. - if msg.To() == nil { + if msg.To == nil { receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) } diff --git a/compilation/types/source_maps.go b/compilation/types/source_maps.go index 9804733d..c5a92ad0 100644 --- a/compilation/types/source_maps.go +++ b/compilation/types/source_maps.go @@ -170,7 +170,7 @@ func (s SourceMap) GetInstructionIndexToOffsetLookup(bytecode []byte) ([]int, er operandCount := 0 if op.IsPush() { if op == vm.PUSH0 { - operandCount = 1 + operandCount = 0 } else { operandCount = int(op) - int(vm.PUSH1) + 1 } diff --git a/fuzzing/calls/call_message.go b/fuzzing/calls/call_message.go index de3a856f..47dc0263 100644 --- a/fuzzing/calls/call_message.go +++ b/fuzzing/calls/call_message.go @@ -5,6 +5,7 @@ import ( "github.com/crytic/medusa/logging" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core" coreTypes "github.com/ethereum/go-ethereum/core/types" "golang.org/x/exp/slices" "math/big" @@ -15,91 +16,113 @@ import ( //go:generate go get github.com/fjl/gencodec //go:generate go run github.com/fjl/gencodec -type CallMessage -field-override callMessageMarshaling -out gen_call_message_json.go -// CallMessage implements Ethereum's coreTypes.Message, used to apply EVM/state updates. +// CallMessage implements and extends Ethereum's coreTypes.Message, used to apply EVM/state updates. type CallMessage struct { - // MsgFrom represents a core.Message's from parameter (sender), indicating who sent a transaction/message to the + // From represents a core.Message's from parameter (sender), indicating who sent a transaction/message to the // Ethereum core to apply a state update. - MsgFrom common.Address `json:"from"` + From common.Address `json:"from"` - // MsgTo represents the receiving address for a given core.Message. - MsgTo *common.Address `json:"to"` + // To represents the receiving address for a given core.Message. + To *common.Address `json:"to"` - // MsgNonce represents the core.Message sender's nonce - MsgNonce uint64 `json:"nonce"` + // Nonce represents the core.Message sender's nonce + Nonce uint64 `json:"nonce"` - // MsgValue represents ETH value to be sent to the receiver of the message. - MsgValue *big.Int `json:"value"` + // Value represents ETH value to be sent to the receiver of the message. + Value *big.Int `json:"value"` - // MsgGas represents the maximum amount of gas the sender is willing to spend to cover the cost of executing the + // GasLimit represents the maximum amount of gas the sender is willing to spend to cover the cost of executing the // message or transaction. - MsgGas uint64 `json:"gas"` + GasLimit uint64 `json:"gasLimit"` - // MsgGasPrice represents the price which the sender is willing to pay for each unit of gas used during execution + // GasPrice represents the price which the sender is willing to pay for each unit of gas used during execution // of the message. - MsgGasPrice *big.Int `json:"gasPrice"` + GasPrice *big.Int `json:"gasPrice"` - // MsgGasFeeCap represents the maximum fee to enforce for gas related costs (related to 1559 transaction executed). + // GasFeeCap represents the maximum fee to enforce for gas related costs (related to 1559 transaction executed). // The use of nil here indicates that the gas price oracle should be relied on instead. - MsgGasFeeCap *big.Int `json:"gasFeeCap"` + GasFeeCap *big.Int `json:"gasFeeCap"` - // MsgGasTipCap represents the fee cap to use for 1559 transaction. The use of nil here indicates that the gas price + // GasTipCap represents the fee cap to use for 1559 transaction. The use of nil here indicates that the gas price // oracle should be relied on instead. - MsgGasTipCap *big.Int `json:"gasTipCap"` + GasTipCap *big.Int `json:"gasTipCap"` - // MsgData represents the underlying message data to be sent to the receiver. If the receiver is a smart contract, + // Data represents the underlying message data to be sent to the receiver. If the receiver is a smart contract, // this will likely house your call parameters and other serialized data. If MsgDataAbiValues is non-nil, this // value is not used. - MsgData []byte `json:"data,omitempty"` + Data []byte `json:"data,omitempty"` - // MsgDataAbiValues represents the underlying message data to be sent to the receiver. If the receiver is a smart - // contract, this will likely house your call parameters and other serialized data. This overrides MsgData if it is + // DataAbiValues represents the underlying message data to be sent to the receiver. If the receiver is a smart + // contract, this will likely house your call parameters and other serialized data. This overrides Data if it is // set, allowing Data to be sourced from method ABI input arguments instead. - MsgDataAbiValues *CallMessageDataAbiValues `json:"dataAbiValues,omitempty"` + DataAbiValues *CallMessageDataAbiValues `json:"dataAbiValues,omitempty"` + + // AccessList represents a core.Message's AccessList parameter which represents the storage slots and contracts + // that will be accessed during the execution of this message. + AccessList coreTypes.AccessList + + // SkipAccountChecks represents a core.Message's SkipAccountChecks. If it is set to true, then the message nonce + // is not checked against the account nonce in state and will not verify if the sender is an EOA. + SkipAccountChecks bool } // callMessageMarshaling is a structure that overrides field types during JSON marshaling. It allows CallMessage to // have its custom marshaling methods auto-generated and will handle type conversions for serialization purposes. // For example, this enables serialization of big.Int but specifying a different field type to control serialization. type callMessageMarshaling struct { - MsgValue *hexutil.Big - MsgGasPrice *hexutil.Big - MsgGasFeeCap *hexutil.Big - MsgGasTipCap *hexutil.Big - MsgData hexutil.Bytes + Value *hexutil.Big + GasPrice *hexutil.Big + GasFeeCap *hexutil.Big + GasTipCap *hexutil.Big + Data hexutil.Bytes } // NewCallMessage instantiates a new call message from a given set of parameters, with call data set from bytes. func NewCallMessage(from common.Address, to *common.Address, nonce uint64, value *big.Int, gasLimit uint64, gasPrice, gasFeeCap, gasTipCap *big.Int, data []byte) *CallMessage { // Construct and return a new message from our given parameters. return &CallMessage{ - MsgFrom: from, - MsgTo: to, - MsgNonce: nonce, - MsgValue: value, - MsgGas: gasLimit, - MsgGasPrice: gasPrice, - MsgGasFeeCap: gasFeeCap, - MsgGasTipCap: gasTipCap, - MsgData: data, - MsgDataAbiValues: nil, + From: from, + To: to, + Nonce: nonce, + Value: value, + GasLimit: gasLimit, + GasPrice: gasPrice, + GasFeeCap: gasFeeCap, + GasTipCap: gasTipCap, + Data: data, + DataAbiValues: nil, + AccessList: nil, + SkipAccountChecks: false, } } // NewCallMessageWithAbiValueData instantiates a new call message from a given set of parameters, with call data set // from method ABI specified inputs. -func NewCallMessageWithAbiValueData(from common.Address, to *common.Address, nonce uint64, value *big.Int, gasLimit uint64, gasPrice, gasFeeCap, gasTipCap *big.Int, data *CallMessageDataAbiValues) *CallMessage { +func NewCallMessageWithAbiValueData(from common.Address, to *common.Address, nonce uint64, value *big.Int, gasLimit uint64, gasPrice, gasFeeCap, gasTipCap *big.Int, abiData *CallMessageDataAbiValues) *CallMessage { + // Pack the ABI value data + var data []byte + var err error + if abiData != nil { + data, err = abiData.Pack() + if err != nil { + logging.GlobalLogger.Panic("Failed to pack call message ABI values", err) + } + } + // Construct and return a new message from our given parameters. return &CallMessage{ - MsgFrom: from, - MsgTo: to, - MsgNonce: nonce, - MsgValue: value, - MsgGas: gasLimit, - MsgGasPrice: gasPrice, - MsgGasFeeCap: gasFeeCap, - MsgGasTipCap: gasTipCap, - MsgData: nil, - MsgDataAbiValues: data, + From: from, + To: to, + Nonce: nonce, + Value: value, + GasLimit: gasLimit, + GasPrice: gasPrice, + GasFeeCap: gasFeeCap, + GasTipCap: gasTipCap, + Data: data, + DataAbiValues: abiData, + AccessList: nil, + SkipAccountChecks: false, } } @@ -107,68 +130,62 @@ func NewCallMessageWithAbiValueData(from common.Address, to *common.Address, non // underlying test chain properties if they are not yet set. func (m *CallMessage) FillFromTestChainProperties(chain *chain.TestChain) { // Set our nonce for this - m.MsgNonce = chain.State().GetNonce(m.MsgFrom) + m.Nonce = chain.State().GetNonce(m.From) // If a gas limit was not provided, allow the entire block gas limit to be used for this message. - if m.MsgGas == 0 { - m.MsgGas = chain.BlockGasLimit + if m.GasLimit == 0 { + m.GasLimit = chain.BlockGasLimit } // If a gas price was not provided, we use 1 as a default. - if m.MsgGasPrice == nil { - m.MsgGasPrice = big.NewInt(1) + if m.GasPrice == nil { + m.GasPrice = big.NewInt(1) } // Setting fee and tip cap to zero alongside the NoBaseFee for the vm.Config will bypass base fee validation. // TODO: Set this appropriately for newer transaction types. - m.MsgGasFeeCap = big.NewInt(0) - m.MsgGasTipCap = big.NewInt(0) + m.GasFeeCap = big.NewInt(0) + m.GasTipCap = big.NewInt(0) } -func (m *CallMessage) From() common.Address { return m.MsgFrom } -func (m *CallMessage) To() *common.Address { return m.MsgTo } -func (m *CallMessage) GasPrice() *big.Int { return m.MsgGasPrice } -func (m *CallMessage) GasFeeCap() *big.Int { return m.MsgGasFeeCap } -func (m *CallMessage) GasTipCap() *big.Int { return m.MsgGasTipCap } -func (m *CallMessage) Value() *big.Int { return m.MsgValue } -func (m *CallMessage) Gas() uint64 { return m.MsgGas } -func (m *CallMessage) Nonce() uint64 { return m.MsgNonce } -func (m *CallMessage) Data() []byte { - // If we have message data derived from ABI values, pack them and return the data. - if m.MsgDataAbiValues != nil { - data, err := m.MsgDataAbiValues.Pack() - if err != nil { - logging.GlobalLogger.Panic("Failed to pack call message ABI values", err) - } - return data - } - - // Otherwise we return our message data set from bytes. - return m.MsgData -} -func (m *CallMessage) AccessList() coreTypes.AccessList { return nil } -func (m *CallMessage) IsFake() bool { return true } - // Clone creates a copy of the given message and its underlying components, or an error if one occurs. func (m *CallMessage) Clone() (*CallMessage, error) { // Clone our underlying ABI values data if we have any. - clonedAbiValues, err := m.MsgDataAbiValues.Clone() + clonedAbiValues, err := m.DataAbiValues.Clone() if err != nil { return nil, err } // Create a message with the same data copied over. clone := &CallMessage{ - MsgFrom: m.MsgFrom, - MsgTo: m.MsgTo, // this value should be read-only, so we re-use it rather than cloning. - MsgNonce: m.MsgNonce, - MsgValue: new(big.Int).Set(m.MsgValue), - MsgGas: m.MsgGas, - MsgGasPrice: new(big.Int).Set(m.MsgGasPrice), - MsgGasFeeCap: new(big.Int).Set(m.MsgGasFeeCap), - MsgGasTipCap: new(big.Int).Set(m.MsgGasTipCap), - MsgData: slices.Clone(m.MsgData), - MsgDataAbiValues: clonedAbiValues, + From: m.From, + To: m.To, // this value should be read-only, so we re-use it rather than cloning. + Nonce: m.Nonce, + Value: new(big.Int).Set(m.Value), + GasLimit: m.GasLimit, + GasPrice: new(big.Int).Set(m.GasPrice), + GasFeeCap: new(big.Int).Set(m.GasFeeCap), + GasTipCap: new(big.Int).Set(m.GasTipCap), + Data: slices.Clone(m.Data), + DataAbiValues: clonedAbiValues, + AccessList: m.AccessList, + SkipAccountChecks: m.SkipAccountChecks, } return clone, nil } + +func (m *CallMessage) ToCoreMessage() *core.Message { + return &core.Message{ + To: m.To, + From: m.From, + Nonce: m.Nonce, + Value: new(big.Int).Set(m.Value), + GasLimit: m.GasLimit, + GasPrice: new(big.Int).Set(m.GasPrice), + GasFeeCap: new(big.Int).Set(m.GasFeeCap), + GasTipCap: new(big.Int).Set(m.GasTipCap), + Data: slices.Clone(m.Data), + AccessList: m.AccessList, + SkipAccountChecks: m.SkipAccountChecks, + } +} diff --git a/fuzzing/calls/call_sequence.go b/fuzzing/calls/call_sequence.go index 41ae2ccc..53ae874e 100644 --- a/fuzzing/calls/call_sequence.go +++ b/fuzzing/calls/call_sequence.go @@ -118,7 +118,7 @@ func (cs CallSequence) Hash() (common.Hash, error) { // Try to obtain a hash for the message/call. If this fails, we will replace it in the deferred panic // recovery. - messageHashData = utils.MessageToTransaction(cse.Call).Hash().Bytes() + messageHashData = utils.MessageToTransaction(cse.Call.ToCoreMessage()).Hash().Bytes() }() // Hash the message hash data. @@ -205,7 +205,7 @@ func (cse *CallSequenceElement) Method() (*abi.Method, error) { if cse.Contract == nil { return nil, nil } - return cse.Contract.CompiledContract().Abi.MethodById(cse.Call.Data()) + return cse.Contract.CompiledContract().Abi.MethodById(cse.Call.Data) } // String returns a displayable string representing the CallSequenceElement. @@ -224,7 +224,7 @@ func (cse *CallSequenceElement) String() string { } // Next decode our arguments (we jump four bytes to skip the function selector) - args, err := method.Inputs.Unpack(cse.Call.Data()[4:]) + args, err := method.Inputs.Unpack(cse.Call.Data[4:]) argsText := "" if err == nil { argsText, err = valuegeneration.EncodeABIArgumentsToString(method.Inputs, args) @@ -249,10 +249,10 @@ func (cse *CallSequenceElement) String() string { argsText, blockNumberStr, blockTimeStr, - cse.Call.Gas(), - cse.Call.GasPrice().String(), - cse.Call.Value().String(), - cse.Call.From(), + cse.Call.GasLimit, + cse.Call.GasPrice.String(), + cse.Call.Value.String(), + cse.Call.From, ) } @@ -273,7 +273,7 @@ func (cse *CallSequenceElement) AttachExecutionTrace(chain *chain.TestChain, con } // Perform our call with the given trace - _, cse.ExecutionTrace, err = executiontracer.CallWithExecutionTrace(chain, contractDefinitions, cse.Call, state) + _, cse.ExecutionTrace, err = executiontracer.CallWithExecutionTrace(chain, contractDefinitions, cse.Call.ToCoreMessage(), state) if err != nil { return fmt.Errorf("failed to resolve execution trace due to error replaying the call: %v", err) } diff --git a/fuzzing/calls/call_sequence_execution.go b/fuzzing/calls/call_sequence_execution.go index 8007bb6a..824c9c8e 100644 --- a/fuzzing/calls/call_sequence_execution.go +++ b/fuzzing/calls/call_sequence_execution.go @@ -84,7 +84,7 @@ func ExecuteCallSequenceIteratively(chain *chain.TestChain, fetchElementFunc Exe } // Try to add our transaction to this block. - err = chain.PendingBlockAddTx(callSequenceElement.Call) + err = chain.PendingBlockAddTx(callSequenceElement.Call.ToCoreMessage()) if err != nil { // If we encountered a block gas limit error, this tx is too expensive to fit in this block. // If there are other transactions in the block, this makes sense. The block is "full". diff --git a/fuzzing/calls/gen_call_message_json.go b/fuzzing/calls/gen_call_message_json.go index 5b3583bf..26662c91 100644 --- a/fuzzing/calls/gen_call_message_json.go +++ b/fuzzing/calls/gen_call_message_json.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" ) var _ = (*callMessageMarshaling)(nil) @@ -15,78 +16,90 @@ var _ = (*callMessageMarshaling)(nil) // MarshalJSON marshals as JSON. func (c CallMessage) MarshalJSON() ([]byte, error) { type CallMessage struct { - MsgFrom common.Address `json:"from"` - MsgTo *common.Address `json:"to"` - MsgNonce uint64 `json:"nonce"` - MsgValue *hexutil.Big `json:"value"` - MsgGas uint64 `json:"gas"` - MsgGasPrice *hexutil.Big `json:"gasPrice"` - MsgGasFeeCap *hexutil.Big `json:"gasFeeCap"` - MsgGasTipCap *hexutil.Big `json:"gasTipCap"` - MsgData hexutil.Bytes `json:"data,omitempty"` - MsgDataAbiValues *CallMessageDataAbiValues `json:"dataAbiValues,omitempty"` + From common.Address `json:"from"` + To *common.Address `json:"to"` + Nonce uint64 `json:"nonce"` + Value *hexutil.Big `json:"value"` + GasLimit uint64 `json:"gasLimit"` + GasPrice *hexutil.Big `json:"gasPrice"` + GasFeeCap *hexutil.Big `json:"gasFeeCap"` + GasTipCap *hexutil.Big `json:"gasTipCap"` + Data hexutil.Bytes `json:"data,omitempty"` + DataAbiValues *CallMessageDataAbiValues `json:"dataAbiValues,omitempty"` + AccessList types.AccessList + SkipAccountChecks bool } var enc CallMessage - enc.MsgFrom = c.MsgFrom - enc.MsgTo = c.MsgTo - enc.MsgNonce = c.MsgNonce - enc.MsgValue = (*hexutil.Big)(c.MsgValue) - enc.MsgGas = c.MsgGas - enc.MsgGasPrice = (*hexutil.Big)(c.MsgGasPrice) - enc.MsgGasFeeCap = (*hexutil.Big)(c.MsgGasFeeCap) - enc.MsgGasTipCap = (*hexutil.Big)(c.MsgGasTipCap) - enc.MsgData = c.MsgData - enc.MsgDataAbiValues = c.MsgDataAbiValues + enc.From = c.From + enc.To = c.To + enc.Nonce = c.Nonce + enc.Value = (*hexutil.Big)(c.Value) + enc.GasLimit = c.GasLimit + enc.GasPrice = (*hexutil.Big)(c.GasPrice) + enc.GasFeeCap = (*hexutil.Big)(c.GasFeeCap) + enc.GasTipCap = (*hexutil.Big)(c.GasTipCap) + enc.Data = c.Data + enc.DataAbiValues = c.DataAbiValues + enc.AccessList = c.AccessList + enc.SkipAccountChecks = c.SkipAccountChecks return json.Marshal(&enc) } // UnmarshalJSON unmarshals from JSON. func (c *CallMessage) UnmarshalJSON(input []byte) error { type CallMessage struct { - MsgFrom *common.Address `json:"from"` - MsgTo *common.Address `json:"to"` - MsgNonce *uint64 `json:"nonce"` - MsgValue *hexutil.Big `json:"value"` - MsgGas *uint64 `json:"gas"` - MsgGasPrice *hexutil.Big `json:"gasPrice"` - MsgGasFeeCap *hexutil.Big `json:"gasFeeCap"` - MsgGasTipCap *hexutil.Big `json:"gasTipCap"` - MsgData *hexutil.Bytes `json:"data,omitempty"` - MsgDataAbiValues *CallMessageDataAbiValues `json:"dataAbiValues,omitempty"` + From *common.Address `json:"from"` + To *common.Address `json:"to"` + Nonce *uint64 `json:"nonce"` + Value *hexutil.Big `json:"value"` + GasLimit *uint64 `json:"gasLimit"` + GasPrice *hexutil.Big `json:"gasPrice"` + GasFeeCap *hexutil.Big `json:"gasFeeCap"` + GasTipCap *hexutil.Big `json:"gasTipCap"` + Data *hexutil.Bytes `json:"data,omitempty"` + DataAbiValues *CallMessageDataAbiValues `json:"dataAbiValues,omitempty"` + AccessList *types.AccessList + SkipAccountChecks *bool } var dec CallMessage if err := json.Unmarshal(input, &dec); err != nil { return err } - if dec.MsgFrom != nil { - c.MsgFrom = *dec.MsgFrom + if dec.From != nil { + c.From = *dec.From } - if dec.MsgTo != nil { - c.MsgTo = dec.MsgTo + if dec.To != nil { + c.To = dec.To } - if dec.MsgNonce != nil { - c.MsgNonce = *dec.MsgNonce + if dec.Nonce != nil { + c.Nonce = *dec.Nonce } - if dec.MsgValue != nil { - c.MsgValue = (*big.Int)(dec.MsgValue) + if dec.Value != nil { + c.Value = (*big.Int)(dec.Value) } - if dec.MsgGas != nil { - c.MsgGas = *dec.MsgGas + if dec.GasLimit != nil { + c.GasLimit = *dec.GasLimit } - if dec.MsgGasPrice != nil { - c.MsgGasPrice = (*big.Int)(dec.MsgGasPrice) + if dec.GasPrice != nil { + c.GasPrice = (*big.Int)(dec.GasPrice) } - if dec.MsgGasFeeCap != nil { - c.MsgGasFeeCap = (*big.Int)(dec.MsgGasFeeCap) + if dec.GasFeeCap != nil { + c.GasFeeCap = (*big.Int)(dec.GasFeeCap) } - if dec.MsgGasTipCap != nil { - c.MsgGasTipCap = (*big.Int)(dec.MsgGasTipCap) + if dec.GasTipCap != nil { + c.GasTipCap = (*big.Int)(dec.GasTipCap) } - if dec.MsgData != nil { - c.MsgData = *dec.MsgData + if dec.Data != nil { + c.Data = *dec.Data } - if dec.MsgDataAbiValues != nil { - c.MsgDataAbiValues = dec.MsgDataAbiValues + if dec.DataAbiValues != nil { + c.DataAbiValues = dec.DataAbiValues + } + if dec.AccessList != nil { + c.AccessList = *dec.AccessList + } + if dec.SkipAccountChecks != nil { + c.SkipAccountChecks = *dec.SkipAccountChecks } return nil } diff --git a/fuzzing/corpus/corpus.go b/fuzzing/corpus/corpus.go index d8c4d479..9cc4cda6 100644 --- a/fuzzing/corpus/corpus.go +++ b/fuzzing/corpus/corpus.go @@ -171,21 +171,21 @@ func (c *Corpus) initializeSequences(sequenceFiles *corpusDirectory[calls.CallSe // If we are deploying a contract and not targeting one with this call, there should be no work to do. currentSequenceElement := sequence[currentIndex] - if currentSequenceElement.Call.MsgTo == nil { + if currentSequenceElement.Call.To == nil { return currentSequenceElement, nil } // We are calling a contract with this call, ensure we can resolve the contract call is targeting. - resolvedContract, resolvedContractExists := deployedContracts[*currentSequenceElement.Call.MsgTo] + resolvedContract, resolvedContractExists := deployedContracts[*currentSequenceElement.Call.To] if !resolvedContractExists { - sequenceInvalidError = fmt.Errorf("contract at address '%v' could not be resolved", currentSequenceElement.Call.MsgTo.String()) + sequenceInvalidError = fmt.Errorf("contract at address '%v' could not be resolved", currentSequenceElement.Call.To.String()) return nil, nil } currentSequenceElement.Contract = resolvedContract // Next, if our sequence element uses ABI values to produce call data, our deserialized data is not yet // sufficient for runtime use, until we use it to resolve runtime references. - callAbiValues := currentSequenceElement.Call.MsgDataAbiValues + callAbiValues := currentSequenceElement.Call.DataAbiValues if callAbiValues != nil { sequenceInvalidError = callAbiValues.Resolve(currentSequenceElement.Contract.CompiledContract().Abi) if sequenceInvalidError != nil { diff --git a/fuzzing/corpus/corpus_test.go b/fuzzing/corpus/corpus_test.go index 3f32eefe..c49c904e 100644 --- a/fuzzing/corpus/corpus_test.go +++ b/fuzzing/corpus/corpus_test.go @@ -55,15 +55,15 @@ func getMockCallSequenceElement() *calls.CallSequenceElement { func getMockCallSequenceElementCall() *calls.CallMessage { to := common.BigToAddress(big.NewInt(rand.Int63())) txn := calls.CallMessage{ - MsgFrom: common.BigToAddress(big.NewInt(rand.Int63())), - MsgTo: &to, - MsgNonce: rand.Uint64(), - MsgValue: big.NewInt(int64(rand.Int())), - MsgGas: rand.Uint64(), - MsgGasPrice: big.NewInt(int64(rand.Int())), - MsgGasFeeCap: big.NewInt(int64(rand.Int())), - MsgGasTipCap: big.NewInt(int64(rand.Int())), - MsgData: []byte{uint8(rand.Uint64()), uint8(rand.Uint64()), uint8(rand.Uint64()), uint8(rand.Uint64())}, + From: common.BigToAddress(big.NewInt(rand.Int63())), + To: &to, + Nonce: rand.Uint64(), + Value: big.NewInt(int64(rand.Int())), + GasLimit: rand.Uint64(), + GasPrice: big.NewInt(int64(rand.Int())), + GasFeeCap: big.NewInt(int64(rand.Int())), + GasTipCap: big.NewInt(int64(rand.Int())), + Data: []byte{uint8(rand.Uint64()), uint8(rand.Uint64()), uint8(rand.Uint64()), uint8(rand.Uint64())}, } return &txn } diff --git a/fuzzing/executiontracer/execution_tracer.go b/fuzzing/executiontracer/execution_tracer.go index fdf6c08d..ed96dbe1 100644 --- a/fuzzing/executiontracer/execution_tracer.go +++ b/fuzzing/executiontracer/execution_tracer.go @@ -14,7 +14,7 @@ import ( // CallWithExecutionTrace obtains an execution trace for a given call, on the provided chain, using the state // provided. If a nil state is provided, the current chain state will be used. // Returns the ExecutionTrace for the call or an error if one occurs. -func CallWithExecutionTrace(chain *chain.TestChain, contractDefinitions contracts.Contracts, msg core.Message, state *state.StateDB) (*core.ExecutionResult, *ExecutionTrace, error) { +func CallWithExecutionTrace(chain *chain.TestChain, contractDefinitions contracts.Contracts, msg *core.Message, state *state.StateDB) (*core.ExecutionResult, *ExecutionTrace, error) { // Create an execution tracer executionTracer := NewExecutionTracer(contractDefinitions, chain.CheatCodeContracts()) diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index c7a06ad3..8d879dee 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -377,7 +377,8 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) erro } // Add our transaction to the block - err = testChain.PendingBlockAddTx(msg) + // Add our transaction to the block + err = testChain.PendingBlockAddTx(msg.ToCoreMessage()) if err != nil { return err } diff --git a/fuzzing/fuzzer_worker.go b/fuzzing/fuzzer_worker.go index f347c682..d8f01bed 100644 --- a/fuzzing/fuzzer_worker.go +++ b/fuzzing/fuzzer_worker.go @@ -323,6 +323,14 @@ func (fw *FuzzerWorker) testNextCallSequence() (calls.CallSequence, []ShrinkCall // shrink verifier. Chain state is reverted to the testing base prior to returning. // Returns a boolean indicating if the shrunken call sequence is valid for a given shrink request, or an error if one occurred. func (fw *FuzzerWorker) testShrunkenCallSequence(possibleShrunkSequence calls.CallSequence, shrinkRequest ShrinkCallSequenceRequest) (bool, error) { + // After testing the sequence, we'll want to rollback changes to reset our testing state. + var err error + defer func() { + if err == nil { + err = fw.chain.RevertToBlockNumber(fw.testingBaseBlockNumber) + } + }() + // Our "fetch next call method" method will simply fetch and fix the call message in case any fields are not correct due to shrinking. fetchElementFunc := func(currentIndex int) (*calls.CallSequenceElement, error) { // If we are at the end of our sequence, return nil indicating we should stop executing. @@ -340,9 +348,9 @@ func (fw *FuzzerWorker) testShrunkenCallSequence(possibleShrunkSequence calls.Ca executionCheckFunc := func(currentlyExecutedSequence calls.CallSequence) (bool, error) { // Check for updates to coverage and corpus (using only the section of the sequence we tested so far). // If we detect coverage changes, add this sequence. - err := fw.fuzzer.corpus.CheckSequenceCoverageAndUpdate(currentlyExecutedSequence, fw.getNewCorpusCallSequenceWeight(), true) - if err != nil { - return true, err + seqErr := fw.fuzzer.corpus.CheckSequenceCoverageAndUpdate(currentlyExecutedSequence, fw.getNewCorpusCallSequenceWeight(), true) + if seqErr != nil { + return true, seqErr } // If our fuzzer context is done, exit out immediately without results. @@ -354,7 +362,7 @@ func (fw *FuzzerWorker) testShrunkenCallSequence(possibleShrunkSequence calls.Ca } // Execute our call sequence. - _, err := calls.ExecuteCallSequenceIteratively(fw.chain, fetchElementFunc, executionCheckFunc) + _, err = calls.ExecuteCallSequenceIteratively(fw.chain, fetchElementFunc, executionCheckFunc) if err != nil { return false, err } @@ -372,11 +380,6 @@ func (fw *FuzzerWorker) testShrunkenCallSequence(possibleShrunkSequence calls.Ca return false, err } } - - // After testing the sequence, we'll want to rollback changes to reset our testing state. - if err = fw.chain.RevertToBlockNumber(fw.testingBaseBlockNumber); err != nil { - return false, err - } return validShrunkSequence, nil } @@ -430,7 +433,7 @@ func (fw *FuzzerWorker) shrinkCallSequence(callSequence calls.CallSequence, shri possibleShrunkSequence, _ := optimizedSequence.Clone() // Loop for each argument in the currently indexed call to mutate it. - abiValuesMsgData := possibleShrunkSequence[i].Call.MsgDataAbiValues + abiValuesMsgData := possibleShrunkSequence[i].Call.DataAbiValues for j := 0; j < len(abiValuesMsgData.InputValues); j++ { mutatedInput, err := valuegeneration.MutateAbiValue(fw.sequenceGenerator.config.ValueGenerator, fw.shrinkingValueMutator, &abiValuesMsgData.Method.Inputs[j].Type, abiValuesMsgData.InputValues[j]) if err != nil { diff --git a/fuzzing/fuzzer_worker_sequence_generator.go b/fuzzing/fuzzer_worker_sequence_generator.go index 8892cd3b..2b9358a4 100644 --- a/fuzzing/fuzzer_worker_sequence_generator.go +++ b/fuzzing/fuzzer_worker_sequence_generator.go @@ -260,6 +260,9 @@ func (g *CallSequenceGenerator) PopSequenceElement() (*calls.CallSequenceElement } } + // Update the element with the current nonce for the associated chain. + element.Call.FillFromTestChainProperties(g.worker.chain) + // Update our base sequence, advance our position, and return the processed element from this round. g.baseSequence[g.fetchIndex] = element g.fetchIndex++ @@ -301,7 +304,6 @@ func (g *CallSequenceGenerator) generateNewElement() (*calls.CallSequenceElement Method: &selectedMethod.Method, InputValues: args, }) - msg.FillFromTestChainProperties(g.worker.chain) // Determine our delay values for this element blockNumberDelay := uint64(0) @@ -441,12 +443,12 @@ func callSeqGenFuncInterleaveAtRandom(sequenceGenerator *CallSequenceGenerator, // Returns an error if one occurs. func prefetchModifyCallFuncMutate(sequenceGenerator *CallSequenceGenerator, element *calls.CallSequenceElement) error { // If this element has no ABI value based call data, exit early. - if element.Call == nil || element.Call.MsgDataAbiValues == nil { + if element.Call == nil || element.Call.DataAbiValues == nil { return nil } // Loop for each input value and mutate it - abiValuesMsgData := element.Call.MsgDataAbiValues + abiValuesMsgData := element.Call.DataAbiValues for i := 0; i < len(abiValuesMsgData.InputValues); i++ { mutatedInput, err := valuegeneration.MutateAbiValue(sequenceGenerator.config.ValueGenerator, sequenceGenerator.config.ValueMutator, &abiValuesMsgData.Method.Inputs[i].Type, abiValuesMsgData.InputValues[i]) if err != nil { diff --git a/fuzzing/test_case_optimization_provider.go b/fuzzing/test_case_optimization_provider.go index ba5ab258..424db7e5 100644 --- a/fuzzing/test_case_optimization_provider.go +++ b/fuzzing/test_case_optimization_provider.go @@ -96,10 +96,10 @@ func (t *OptimizationTestCaseProvider) runOptimizationTest(worker *FuzzerWorker, var executionTrace *executiontracer.ExecutionTrace if trace { executionTracer := executiontracer.NewExecutionTracer(worker.fuzzer.contractDefinitions, worker.chain.CheatCodeContracts()) - executionResult, err = worker.Chain().CallContract(msg, nil, executionTracer) + executionResult, err = worker.Chain().CallContract(msg.ToCoreMessage(), nil, executionTracer) executionTrace = executionTracer.Trace() } else { - executionResult, err = worker.Chain().CallContract(msg, nil) + executionResult, err = worker.Chain().CallContract(msg.ToCoreMessage(), nil) } if err != nil { return nil, nil, fmt.Errorf("failed to call optimization test method: %v", err) diff --git a/fuzzing/test_case_property_provider.go b/fuzzing/test_case_property_provider.go index ca8be15c..10399cc5 100644 --- a/fuzzing/test_case_property_provider.go +++ b/fuzzing/test_case_property_provider.go @@ -99,10 +99,10 @@ func (t *PropertyTestCaseProvider) checkPropertyTestFailed(worker *FuzzerWorker, var executionTrace *executiontracer.ExecutionTrace if trace { executionTracer := executiontracer.NewExecutionTracer(worker.fuzzer.contractDefinitions, worker.chain.CheatCodeContracts()) - executionResult, err = worker.Chain().CallContract(msg, nil, executionTracer) + executionResult, err = worker.Chain().CallContract(msg.ToCoreMessage(), nil, executionTracer) executionTrace = executionTracer.Trace() } else { - executionResult, err = worker.Chain().CallContract(msg, nil) + executionResult, err = worker.Chain().CallContract(msg.ToCoreMessage(), nil) } if err != nil { return false, nil, fmt.Errorf("failed to call property test method: %v", err) diff --git a/go.mod b/go.mod index ca1b0639..81250f0e 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/Masterminds/semver v1.5.0 - github.com/ethereum/go-ethereum v1.11.1 + github.com/ethereum/go-ethereum v1.12.0 github.com/fxamacker/cbor v1.5.1 github.com/google/uuid v1.3.0 github.com/pkg/errors v0.9.1 @@ -15,6 +15,7 @@ require ( golang.org/x/crypto v0.11.0 golang.org/x/exp v0.0.0-20230206171751-46f607a40771 golang.org/x/net v0.12.0 + golang.org/x/sys v0.10.0 ) require ( @@ -33,13 +34,13 @@ require ( github.com/getsentry/sentry-go v0.18.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-stack/stack v1.8.1 // indirect + github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/golang/snappy v0.0.4 // indirect + github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/gorilla/websocket v1.5.0 // indirect - github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect - github.com/holiman/uint256 v1.2.1 // indirect + github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/klauspost/compress v1.15.15 // indirect github.com/kr/pretty v0.3.1 // indirect @@ -54,7 +55,6 @@ require ( github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.39.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect - github.com/prometheus/tsdb v0.10.0 // indirect github.com/rivo/uniseg v0.4.3 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect @@ -63,11 +63,10 @@ require ( github.com/tklauser/numcpus v0.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect - golang.org/x/sys v0.10.0 // indirect golang.org/x/text v0.11.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/ethereum/go-ethereum v1.11.1 => github.com/crytic/medusa-geth v0.0.0-20230221190257-777a77b25150 +replace github.com/ethereum/go-ethereum => github.com/crytic/medusa-geth v0.0.0-20230811005223-cee04520a2f9 diff --git a/go.sum b/go.sum index fe8142b2..1db673f4 100644 --- a/go.sum +++ b/go.sum @@ -8,26 +8,20 @@ github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwS github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/VictoriaMetrics/fastcache v1.12.0 h1:vnVi/y9yKDcD9akmc4NqAoqgQhJrOwUF+j9LTgn4QDE= github.com/VictoriaMetrics/fastcache v1.12.0/go.mod h1:tjiYeEfYXCqacuvYw/7UoDIeJaNxq6132xHICNP77w8= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -52,8 +46,8 @@ github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/crytic/medusa-geth v0.0.0-20230221190257-777a77b25150 h1:Helt4ysP5N0cJzvhBsx4JcAOte5gD4whzamaXWpg37M= -github.com/crytic/medusa-geth v0.0.0-20230221190257-777a77b25150/go.mod h1:DuefStAgaxoaYGLR0FueVcVbehmn5n9QUcVrMCuOvuc= +github.com/crytic/medusa-geth v0.0.0-20230811005223-cee04520a2f9 h1:BUuL3h23IdVSmyq3I7LVUYRInKgXShMLKElYaFgn4RM= +github.com/crytic/medusa-geth v0.0.0-20230811005223-cee04520a2f9/go.mod h1:/oo2X/dZLJjf2mJ6YT9wcWxa4nNJDBKDBU6sFIpx1Gs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -64,9 +58,7 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -89,23 +81,19 @@ github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/ github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -127,9 +115,9 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -149,12 +137,10 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e h1:pIYdhNkDh+YENVNi3gto8n9hAmRxKxoar0iE6BLucjw= -github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e/go.mod h1:j9cQbcqHQujT0oKJ38PylVfqohClLr3CvDC+Qcg+lhU= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= -github.com/holiman/uint256 v1.2.1 h1:XRtyuda/zw2l+Bq/38n5XUoEF72aSOu/77Thd9pPp2o= -github.com/holiman/uint256 v1.2.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= +github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c h1:DZfsyhDK1hnSS5lH8l+JggqzEleHteTYfutAiVlSUM8= +github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= @@ -169,7 +155,6 @@ github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrO github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= @@ -183,8 +168,6 @@ github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0 github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -193,6 +176,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -213,7 +197,6 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= @@ -225,14 +208,12 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -247,30 +228,20 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= -github.com/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic= -github.com/prometheus/tsdb v0.10.0/go.mod h1:oi49uRhEe9dPUTlS3JRZOwJuVi6tmh10QSgwXEyGCt4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -289,10 +260,8 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= @@ -304,7 +273,6 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -343,7 +311,6 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -368,7 +335,6 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -398,9 +364,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -486,7 +450,6 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -501,7 +464,6 @@ gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/utils/message_transaction_utils.go b/utils/message_transaction_utils.go index cfd54b25..57fd68ed 100644 --- a/utils/message_transaction_utils.go +++ b/utils/message_transaction_utils.go @@ -6,13 +6,13 @@ import ( ) // MessageToTransaction derives a types.Transaction from a types.Message. -func MessageToTransaction(msg core.Message) *types.Transaction { +func MessageToTransaction(msg *core.Message) *types.Transaction { return types.NewTx(&types.LegacyTx{ - Nonce: msg.Nonce(), - GasPrice: msg.GasPrice(), - Gas: msg.Gas(), - To: msg.To(), - Value: msg.Value(), - Data: msg.Data(), + Nonce: msg.Nonce, + GasPrice: msg.GasPrice, + Gas: msg.GasLimit, + To: msg.To, + Value: msg.Value, + Data: msg.Data, }) } From 2dddaf18ebde551912dfff63131ef0e979aa9702 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Aug 2023 11:28:36 -0400 Subject: [PATCH 06/55] Bump golang.org/x/net from 0.12.0 to 0.14.0 (#196) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.12.0 to 0.14.0. - [Commits](https://github.com/golang/net/compare/v0.12.0...v0.14.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 81250f0e..1aec9600 100644 --- a/go.mod +++ b/go.mod @@ -12,10 +12,10 @@ require ( github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 - golang.org/x/crypto v0.11.0 + golang.org/x/crypto v0.12.0 golang.org/x/exp v0.0.0-20230206171751-46f607a40771 - golang.org/x/net v0.12.0 - golang.org/x/sys v0.10.0 + golang.org/x/net v0.14.0 + golang.org/x/sys v0.11.0 ) require ( @@ -63,7 +63,7 @@ require ( github.com/tklauser/numcpus v0.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect - golang.org/x/text v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 1db673f4..1c621d8b 100644 --- a/go.sum +++ b/go.sum @@ -319,8 +319,8 @@ golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= @@ -351,8 +351,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -394,8 +394,8 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -403,8 +403,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From ab00c593afc46282bfd066996a254d1a2ab9de8c Mon Sep 17 00:00:00 2001 From: anishnaik Date: Tue, 15 Aug 2023 15:54:16 -0400 Subject: [PATCH 07/55] `console.log` go brrr (#193) Introduce `console.log` functionality with string formatting capabilities. --------- Co-authored-by: David Pokora --- chain/cheat_code_contract.go | 27 +- chain/console_log_cheat_code_contract.go | 124 ++ ...des.go => standard_cheat_code_contract.go} | 24 +- compilation/abiutils/solidity_errors.go | 20 +- fuzzing/executiontracer/execution_trace.go | 77 +- fuzzing/fuzzer_test.go | 56 +- .../cheat_codes/console_log/console_log.sol | 1568 +++++++++++++++++ logging/colors/constants.go | 3 + utils/combinatorial_utils.go | 46 + 9 files changed, 1898 insertions(+), 47 deletions(-) create mode 100644 chain/console_log_cheat_code_contract.go rename chain/{cheat_codes.go => standard_cheat_code_contract.go} (94%) create mode 100644 fuzzing/testdata/contracts/cheat_codes/console_log/console_log.sol create mode 100644 utils/combinatorial_utils.go diff --git a/chain/cheat_code_contract.go b/chain/cheat_code_contract.go index 0b644cfc..08ceb6ea 100644 --- a/chain/cheat_code_contract.go +++ b/chain/cheat_code_contract.go @@ -54,6 +54,30 @@ type cheatCodeRawReturnData struct { Err error } +// getCheatCodeProviders obtains a cheatCodeTracer (used to power cheat code analysis) and associated CheatCodeContract +// objects linked to the tracer (providing on-chain callable methods as an entry point). These objects are attached to +// the TestChain to enable cheat code functionality. +// Returns the tracer and associated pre-compile contracts, or an error, if one occurred. +func getCheatCodeProviders() (*cheatCodeTracer, []*CheatCodeContract, error) { + // Create a cheat code tracer and attach it to the chain. + tracer := newCheatCodeTracer() + + // Obtain our standard cheat code pre-compile + stdCheatCodeContract, err := getStandardCheatCodeContract(tracer) + if err != nil { + return nil, nil, err + } + + // Obtain the console.log pre-compile + consoleCheatCodeContract, err := getConsoleLogCheatCodeContract(tracer) + if err != nil { + return nil, nil, err + } + + // Return the tracer and precompiles + return tracer, []*CheatCodeContract{stdCheatCodeContract, consoleCheatCodeContract}, nil +} + // newCheatCodeContract returns a new precompiledContract which uses the attached cheatCodeTracer for execution // context. func newCheatCodeContract(tracer *cheatCodeTracer, address common.Address, name string) *CheatCodeContract { @@ -98,7 +122,7 @@ func (c *CheatCodeContract) Abi() *abi.ABI { } // addMethod adds a new method to the precompiled contract. -// Returns an error if one occurred. +// Throws a panic if either the name is the empty string or the handler is nil. func (c *CheatCodeContract) addMethod(name string, inputs abi.Arguments, outputs abi.Arguments, handler cheatCodeMethodHandler) { // Verify a method name was provided if name == "" { @@ -117,7 +141,6 @@ func (c *CheatCodeContract) addMethod(name string, inputs abi.Arguments, outputs method: method, handler: handler, } - // Add the method to the ABI. // Note: Normally the key here should be the method name, not sig. But cheat code contracts have duplicate // method names with different parameter types, so we use this so they don't override. diff --git a/chain/console_log_cheat_code_contract.go b/chain/console_log_cheat_code_contract.go new file mode 100644 index 00000000..1c20dedb --- /dev/null +++ b/chain/console_log_cheat_code_contract.go @@ -0,0 +1,124 @@ +package chain + +import ( + "github.com/crytic/medusa/utils" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "strconv" +) + +// ConsoleLogContractAddress is the address for the console.log precompile contract +var ConsoleLogContractAddress = common.HexToAddress("0x000000000000000000636F6e736F6c652e6c6f67") + +// getConsoleLogCheatCodeContract obtains a CheatCodeContract which implements the console.log functions. +// Returns the precompiled contract, or an error if there is one. +func getConsoleLogCheatCodeContract(tracer *cheatCodeTracer) (*CheatCodeContract, error) { + // Create a new precompile to add methods to. + contract := newCheatCodeContract(tracer, ConsoleLogContractAddress, "Console") + + // Define all the ABI types needed for console.log functions + typeUint256, err := abi.NewType("uint256", "", nil) + if err != nil { + return nil, err + } + typeInt256, err := abi.NewType("int256", "", nil) + if err != nil { + return nil, err + } + typeString, err := abi.NewType("string", "", nil) + if err != nil { + return nil, err + } + typeBool, err := abi.NewType("bool", "", nil) + if err != nil { + return nil, err + } + typeAddress, err := abi.NewType("address", "", nil) + if err != nil { + return nil, err + } + typeBytes, err := abi.NewType("bytes", "", nil) + if err != nil { + return nil, err + } + + // We will store all the fixed byte (e.g. byte1, byte2) in a mapping + const numFixedByteTypes = 32 + fixedByteTypes := make(map[int]abi.Type, numFixedByteTypes) + for i := 1; i <= numFixedByteTypes; i++ { + byteString := "bytes" + strconv.FormatInt(int64(i), 10) + fixedByteTypes[i], err = abi.NewType(byteString, "", nil) + if err != nil { + return nil, err + } + } + + // We have a few special log function signatures outside all the permutations of (string, uint256, bool, address). + // These include log(int256), log(bytes), log(bytesX), and log(string, uint256). So, we will manually create these + // signatures and then programmatically iterate through all the permutations. + + // Note that none of the functions actually do anything - they just have to be callable so that the execution + // traces can show the arguments that the user wants to log! + + // log(int256): Log an int256 + contract.addMethod("log", abi.Arguments{{Type: typeInt256}}, abi.Arguments{}, + func(tracer *cheatCodeTracer, inputs []any) ([]any, *cheatCodeRawReturnData) { + return nil, nil + }, + ) + + // log(bytes): Log bytes + contract.addMethod("log", abi.Arguments{{Type: typeBytes}}, abi.Arguments{}, + func(tracer *cheatCodeTracer, inputs []any) ([]any, *cheatCodeRawReturnData) { + return nil, nil + }, + ) + + // Now, we will add the logBytes1, logBytes2, and so on in a loop + for i := 1; i <= numFixedByteTypes; i++ { + // Create local copy of abi argument + fixedByteType := fixedByteTypes[i] + + // Add the method + contract.addMethod("log", abi.Arguments{{Type: fixedByteType}}, abi.Arguments{}, + func(tracer *cheatCodeTracer, inputs []any) ([]any, *cheatCodeRawReturnData) { + return nil, nil + }, + ) + } + + // log(string, int256): Log string with an int where the string could be formatted + contract.addMethod("log", abi.Arguments{{Type: typeString}, {Type: typeInt256}}, abi.Arguments{}, + func(tracer *cheatCodeTracer, inputs []any) ([]any, *cheatCodeRawReturnData) { + return nil, nil + }, + ) + + // These are the four parameter types that console.log() accepts + choices := abi.Arguments{{Type: typeUint256}, {Type: typeString}, {Type: typeBool}, {Type: typeAddress}} + + // Create all possible permutations (with repetition) where the number of choices increases from 1...len(choices) + permutations := make([]abi.Arguments, 0) + for n := 1; n <= len(choices); n++ { + nextSetOfPermutations := utils.PermutationsWithRepetition(choices, n) + for _, permutation := range nextSetOfPermutations { + permutations = append(permutations, permutation) + } + } + + // Iterate across each permutation to add their associated event and function handler + for i := 0; i < len(permutations); i++ { + // Make a local copy of the current permutation + permutation := permutations[i] + + // Create the function handler + contract.addMethod("log", permutation, abi.Arguments{}, + func(tracer *cheatCodeTracer, inputs []any) ([]any, *cheatCodeRawReturnData) { + return nil, nil + }, + ) + } + + // Return our precompile contract information. + return contract, nil +} diff --git a/chain/cheat_codes.go b/chain/standard_cheat_code_contract.go similarity index 94% rename from chain/cheat_codes.go rename to chain/standard_cheat_code_contract.go index 49bd4ae3..141bf204 100644 --- a/chain/cheat_codes.go +++ b/chain/standard_cheat_code_contract.go @@ -14,30 +14,14 @@ import ( "strings" ) -// getCheatCodeProviders obtains a cheatCodeTracer (used to power cheat code analysis) and associated CheatCodeContract -// objects linked to the tracer (providing on-chain callable methods as an entry point). These objects are attached to -// the TestChain to enable cheat code functionality. -// Returns the tracer and associated pre-compile contracts, or an error, if one occurred. -func getCheatCodeProviders() (*cheatCodeTracer, []*CheatCodeContract, error) { - // Create a cheat code tracer and attach it to the chain. - tracer := newCheatCodeTracer() - - // Obtain our cheat code pre-compiles - stdCheatCodeContract, err := getStandardCheatCodeContract(tracer) - if err != nil { - return nil, nil, err - } - - // Return the tracer and precompiles - return tracer, []*CheatCodeContract{stdCheatCodeContract}, nil -} +// StandardCheatcodeContractAddress is the address for the standard cheatcode contract +var StandardCheatcodeContractAddress = common.HexToAddress("0x7109709ECfa91a80626fF3989D68f67F5b1DD12D") // getStandardCheatCodeContract obtains a CheatCodeContract which implements common cheat codes. // Returns the precompiled contract, or an error if one occurs. func getStandardCheatCodeContract(tracer *cheatCodeTracer) (*CheatCodeContract, error) { - // Define our address for this precompile contract, then create a new precompile to add methods to. - contractAddress := common.HexToAddress("0x7109709ECfa91a80626fF3989D68f67F5b1DD12D") - contract := newCheatCodeContract(tracer, contractAddress, "StdCheats") + // Create a new precompile to add methods to. + contract := newCheatCodeContract(tracer, StandardCheatcodeContractAddress, "StdCheats") // Define some basic ABI argument types typeAddress, err := abi.NewType("address", "", nil) diff --git a/compilation/abiutils/solidity_errors.go b/compilation/abiutils/solidity_errors.go index 384a8fda..fd848f99 100644 --- a/compilation/abiutils/solidity_errors.go +++ b/compilation/abiutils/solidity_errors.go @@ -113,25 +113,25 @@ func GetPanicReason(panicCode uint64) string { // Switch on panic code switch panicCode { case PanicCodeCompilerInserted: - return "compiler inserted panic" + return "panic: compiler inserted panic" case PanicCodeAssertFailed: - return "assertion failed" + return "panic: assertion failed" case PanicCodeArithmeticUnderOverflow: - return "arithmetic underflow" + return "panic: arithmetic underflow" case PanicCodeDivideByZero: - return "division by zero" + return "panic: division by zero" case PanicCodeEnumTypeConversionOutOfBounds: - return "enum access out of bounds" + return "panic: enum access out of bounds" case PanicCodeIncorrectStorageAccess: - return "incorrect storage access" + return "panic: incorrect storage access" case PanicCodePopEmptyArray: - return "pop on empty array" + return "panic: pop on empty array" case PanicCodeOutOfBoundsArrayAccess: - return "out of bounds array access" + return "panic: out of bounds array access" case PanicCodeAllocateTooMuchMemory: - return "overallocation of memory" + return "panic; overallocation of memory" case PanicCodeCallUninitializedVariable: - return "call on uninitialized variable" + return "panic: call on uninitialized variable" default: return fmt.Sprintf("unknown panic code(%v)", panicCode) } diff --git a/fuzzing/executiontracer/execution_trace.go b/fuzzing/executiontracer/execution_trace.go index a021de9d..287ce52c 100644 --- a/fuzzing/executiontracer/execution_trace.go +++ b/fuzzing/executiontracer/execution_trace.go @@ -3,6 +3,7 @@ package executiontracer import ( "encoding/hex" "fmt" + "github.com/crytic/medusa/chain" "github.com/crytic/medusa/compilation/abiutils" "github.com/crytic/medusa/fuzzing/contracts" "github.com/crytic/medusa/fuzzing/valuegeneration" @@ -11,6 +12,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" coreTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "regexp" "strings" ) @@ -36,10 +38,12 @@ func newExecutionTrace(contracts contracts.Contracts) *ExecutionTrace { // generateCallFrameEnterElements generates a list of elements describing top level information about this call frame. // This list of elements will hold information about what kind of call it is, wei sent, what method is called, and more. -// Additionally, the list may also hold formatting options for console output. -func (t *ExecutionTrace) generateCallFrameEnterElements(callFrame *CallFrame) []any { - // Create list of elements +// Additionally, the list may also hold formatting options for console output. This function also returns a non-empty +// string in case this call frame represents a call to the console.log precompile contract. +func (t *ExecutionTrace) generateCallFrameEnterElements(callFrame *CallFrame) ([]any, string) { + // Create list of elements and console log string elements := make([]any, 0) + var consoleLogString string // Define some strings and objects that represent our current call frame var ( @@ -95,10 +99,30 @@ func (t *ExecutionTrace) generateCallFrameEnterElements(callFrame *CallFrame) [] // Unpack our input values and obtain a string to represent them inputValues, err := method.Inputs.Unpack(abiDataInputBuffer) if err == nil { + // Encode the ABI arguments into strings encodedInputString, err := valuegeneration.EncodeABIArgumentsToString(method.Inputs, inputValues) if err == nil { inputArgumentsDisplayText = &encodedInputString } + + // If the call was made to the console log precompile address, let's retrieve the log and format it + if callFrame.ToAddress == chain.ConsoleLogContractAddress { + // First, attempt to do string formatting if the first element is a string and has a percent sign in it + exp := regexp.MustCompile(`%`) + stringInput, isString := inputValues[0].(string) + if isString && exp.MatchString(stringInput) { + // Format the string and add it to the list of logs + consoleLogString = fmt.Sprintf(inputValues[0].(string), inputValues[1:]...) + } else { + // The string does not need to be formatted, and we can just use the encoded input string + consoleLogString = encodedInputString + } + + // Add a bullet point before the string and a new line after the string + if len(consoleLogString) > 0 { + consoleLogString = colors.BULLET_POINT + " " + consoleLogString + "\n" + } + } } } @@ -120,7 +144,11 @@ func (t *ExecutionTrace) generateCallFrameEnterElements(callFrame *CallFrame) [] } } else { if callFrame.ExecutedCode { - callInfo = fmt.Sprintf("%v.%v(%v) (addr=%v, value=%v, sender=%v)", codeContractName, methodName, *inputArgumentsDisplayText, callFrame.ToAddress.String(), callFrame.CallValue, callFrame.SenderAddress.String()) + if callFrame.ToAddress == chain.ConsoleLogContractAddress { + callInfo = fmt.Sprintf("%v.%v(%v)", codeContractName, methodName, *inputArgumentsDisplayText) + } else { + callInfo = fmt.Sprintf("%v.%v(%v) (addr=%v, value=%v, sender=%v)", codeContractName, methodName, *inputArgumentsDisplayText, callFrame.ToAddress.String(), callFrame.CallValue, callFrame.SenderAddress.String()) + } } else { callInfo = fmt.Sprintf("(addr=%v, value=%v, sender=%v)", callFrame.ToAddress.String(), callFrame.CallValue, callFrame.SenderAddress.String()) } @@ -129,7 +157,7 @@ func (t *ExecutionTrace) generateCallFrameEnterElements(callFrame *CallFrame) [] // Add call information to the elements elements = append(elements, callInfo, "\n") - return elements + return elements, consoleLogString } // generateCallFrameExitElements generates a list of elements describing the return data of the call frame (e.g. @@ -263,11 +291,13 @@ func (t *ExecutionTrace) generateEventEmittedElements(callFrame *CallFrame, even return elements } -// generateElementsForCallFrame generates a list of elements for a given call frame and its children. Additionally, -// the list may also hold formatting options for console output. -func (t *ExecutionTrace) generateElementsForCallFrame(currentDepth int, callFrame *CallFrame) []any { - // Create list of elements +// generateElementsAndLogsForCallFrame generates a list of elements and logs for a given call frame and its children. +// The list of elements may also hold formatting options for console output. The list of logs represent calls to the +// console.log precompile contract. +func (t *ExecutionTrace) generateElementsAndLogsForCallFrame(currentDepth int, callFrame *CallFrame) ([]any, []any) { + // Create list of elements and logs elements := make([]any, 0) + consoleLogs := make([]any, 0) // Create our current call line prefix (indented by call depth) prefix := strings.Repeat("\t", currentDepth) + " => " @@ -278,8 +308,14 @@ func (t *ExecutionTrace) generateElementsForCallFrame(currentDepth int, callFram } // Add the call frame enter header elements + newElements, consoleLogString := t.generateCallFrameEnterElements(callFrame) elements = append(elements, prefix) - elements = append(elements, t.generateCallFrameEnterElements(callFrame)...) + elements = append(elements, newElements...) + + // If this call frame was a console.log contract call, add the string to the list of logs + if len(consoleLogString) > 0 { + consoleLogs = append(consoleLogs, consoleLogString) + } // Now that the header has been printed, create our indent level to express everything that // happened under it. @@ -293,8 +329,9 @@ func (t *ExecutionTrace) generateElementsForCallFrame(currentDepth int, callFram for _, operation := range callFrame.Operations { if childCallFrame, ok := operation.(*CallFrame); ok { // If this is a call frame being entered, generate information recursively. - childOutputLines := t.generateElementsForCallFrame(currentDepth+1, childCallFrame) + childOutputLines, childConsoleLogStrings := t.generateElementsAndLogsForCallFrame(currentDepth+1, childCallFrame) elements = append(elements, childOutputLines...) + consoleLogs = append(consoleLogs, childConsoleLogStrings...) } else if eventLog, ok := operation.(*coreTypes.Log); ok { // If an event log was emitted, add a message for it. elements = append(elements, prefix) @@ -304,23 +341,35 @@ func (t *ExecutionTrace) generateElementsForCallFrame(currentDepth int, callFram // If we self-destructed, add a message for it before our footer. if callFrame.SelfDestructed { - elements = append(elements, prefix, colors.MagentaBold, "[selfdestruct]", colors.Reset, "\n") + elements = append(elements, prefix, colors.RedBold, "[selfdestruct]", colors.Reset, "\n") } // Add the call frame exit footer elements = append(elements, prefix) elements = append(elements, t.generateCallFrameExitElements(callFrame)...) + } // Return our elements - return elements + return elements, consoleLogs } // Log returns a logging.LogBuffer that represents this execution trace. This buffer will be passed to the underlying // logger which will format it accordingly for console or file. func (t *ExecutionTrace) Log() *logging.LogBuffer { + // Create a buffer buffer := logging.NewLogBuffer() - buffer.Append(t.generateElementsForCallFrame(0, t.TopLevelCallFrame)...) + + // First, add the elements that make up the overarching execution trace + elements, logs := t.generateElementsAndLogsForCallFrame(0, t.TopLevelCallFrame) + buffer.Append(elements...) + + // If we captured any logs during tracing, add them to the overarching execution trace + if len(logs) > 0 { + buffer.Append(colors.Bold, "[Logs]", colors.Reset, "\n") + buffer.Append(logs...) + } + return buffer } diff --git a/fuzzing/fuzzer_test.go b/fuzzing/fuzzer_test.go index 42ff13c5..978ecbc0 100644 --- a/fuzzing/fuzzer_test.go +++ b/fuzzing/fuzzer_test.go @@ -258,6 +258,60 @@ func TestCheatCodes(t *testing.T) { } } +// TestConsoleLog tests the console.log precompile contract by logging a variety of different primitive types and +// then failing. The execution trace for the failing call sequence should hold the various logs. +func TestConsoleLog(t *testing.T) { + // These are the logs that should show up in the execution trace + expectedLogs := []string{ + "2", + "hello world", + "byte", + "i is 2", + "% bool is true, addr is 0x0000000000000000000000000000000000000000, u is 100", + } + + filePaths := []string{ + "testdata/contracts/cheat_codes/console_log/console_log.sol", + } + for _, filePath := range filePaths { + runFuzzerTest(t, &fuzzerSolcFileTest{ + filePath: filePath, + configUpdates: func(config *config.ProjectConfig) { + config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TestLimit = 10000 + // enable assertion testing only + config.Fuzzing.Testing.PropertyTesting.Enabled = true + config.Fuzzing.Testing.AssertionTesting.Enabled = true + }, + method: func(f *fuzzerTestContext) { + // Start the fuzzer + err := f.fuzzer.Start() + assert.NoError(t, err) + + // Check for failed assertion tests. + failedTestCase := f.fuzzer.TestCasesWithStatus(TestCaseStatusFailed) + assert.NotEmpty(t, failedTestCase, "expected to have failed test cases") + + // Obtain our first failed test case, get the message, and verify it contains our assertion failed. + failingSequence := *failedTestCase[0].CallSequence() + assert.NotEmpty(t, failingSequence, "expected to have calls in the call sequence failing an assertion test") + + // Obtain the last call + lastCall := failingSequence[len(failingSequence)-1] + assert.NotNilf(t, lastCall.ExecutionTrace, "expected to have an execution trace attached to call sequence for this test") + + // Get the execution trace message + executionTraceMsg := lastCall.ExecutionTrace.Log().String() + + // Verify it contains all expected logs + for _, expectedLog := range expectedLogs { + assert.Contains(t, executionTraceMsg, expectedLog) + } + }, + }) + } +} + // TestDeploymentsInnerDeployments runs tests to ensure dynamically deployed contracts are detected by the Fuzzer and // their properties are tested appropriately. func TestDeploymentsInnerDeployments(t *testing.T) { @@ -379,7 +433,7 @@ func TestExecutionTraces(t *testing.T) { "testdata/contracts/execution_tracing/proxy_call.sol": {"TestContract -> InnerDeploymentContract.setXY", "Hello from proxy call args!"}, "testdata/contracts/execution_tracing/revert_custom_error.sol": {"CustomError", "Hello from a custom error!"}, "testdata/contracts/execution_tracing/revert_reasons.sol": {"RevertingContract was called and reverted."}, - "testdata/contracts/execution_tracing/self_destruct.sol": {"[selfdestruct]", "[assertion failed]"}, + "testdata/contracts/execution_tracing/self_destruct.sol": {"[selfdestruct]", "[panic: assertion failed]"}, } for filePath, expectedTraceMessages := range expectedMessagesPerTest { runFuzzerTest(t, &fuzzerSolcFileTest{ diff --git a/fuzzing/testdata/contracts/cheat_codes/console_log/console_log.sol b/fuzzing/testdata/contracts/cheat_codes/console_log/console_log.sol new file mode 100644 index 00000000..ddf5ffe4 --- /dev/null +++ b/fuzzing/testdata/contracts/cheat_codes/console_log/console_log.sol @@ -0,0 +1,1568 @@ +// Test console.log capabilities to make sure logging and string formatting are happening as expected +contract TestContract { + + function testConsoleLog() public { + // Log an int256 + int256 i = 2; + console.log(i); + + // Log bytes + bytes memory byteSlice = "hello world"; + console.logBytes(byteSlice); + + // Log fixed bytes + bytes4 fixedBytes = "byte"; + console.logBytes4(fixedBytes); + + // Log a string and int256 while testing string formatting + string memory str = "i is %d"; + console.log(str, i); + + // Test the permutation logic by logging a random permutation and also string formatting + bool b = true; + address addr = address(0); + uint256 u = 100; + str = "%% bool is %t, addr is %s, u is %d"; + console.log(str, b, addr, u); + assert(false); + } +} + +library console { + address constant CONSOLE_ADDRESS = address(0x000000000000000000636F6e736F6c652e6c6f67); + + function _sendLogPayload(bytes memory payload) private view { + uint256 payloadLength = payload.length; + address consoleAddress = CONSOLE_ADDRESS; + /// @solidity memory-safe-assembly + assembly { + let payloadStart := add(payload, 32) + let r := staticcall(gas(), consoleAddress, payloadStart, payloadLength, 0, 0) + } + } + + function log() internal view { + _sendLogPayload(abi.encodeWithSignature("log()")); + } + + function logInt(int256 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(int256)", p0)); + } + + function logUint(uint256 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256)", p0)); + } + + function logString(string memory p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string)", p0)); + } + + function logBool(bool p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool)", p0)); + } + + function logAddress(address p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address)", p0)); + } + + function logBytes(bytes memory p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes)", p0)); + } + + function logBytes1(bytes1 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes1)", p0)); + } + + function logBytes2(bytes2 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes2)", p0)); + } + + function logBytes3(bytes3 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes3)", p0)); + } + + function logBytes4(bytes4 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes4)", p0)); + } + + function logBytes5(bytes5 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes5)", p0)); + } + + function logBytes6(bytes6 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes6)", p0)); + } + + function logBytes7(bytes7 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes7)", p0)); + } + + function logBytes8(bytes8 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes8)", p0)); + } + + function logBytes9(bytes9 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes9)", p0)); + } + + function logBytes10(bytes10 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes10)", p0)); + } + + function logBytes11(bytes11 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes11)", p0)); + } + + function logBytes12(bytes12 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes12)", p0)); + } + + function logBytes13(bytes13 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes13)", p0)); + } + + function logBytes14(bytes14 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes14)", p0)); + } + + function logBytes15(bytes15 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes15)", p0)); + } + + function logBytes16(bytes16 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes16)", p0)); + } + + function logBytes17(bytes17 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes17)", p0)); + } + + function logBytes18(bytes18 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes18)", p0)); + } + + function logBytes19(bytes19 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes19)", p0)); + } + + function logBytes20(bytes20 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes20)", p0)); + } + + function logBytes21(bytes21 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes21)", p0)); + } + + function logBytes22(bytes22 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes22)", p0)); + } + + function logBytes23(bytes23 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes23)", p0)); + } + + function logBytes24(bytes24 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes24)", p0)); + } + + function logBytes25(bytes25 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes25)", p0)); + } + + function logBytes26(bytes26 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes26)", p0)); + } + + function logBytes27(bytes27 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes27)", p0)); + } + + function logBytes28(bytes28 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes28)", p0)); + } + + function logBytes29(bytes29 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes29)", p0)); + } + + function logBytes30(bytes30 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes30)", p0)); + } + + function logBytes31(bytes31 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes31)", p0)); + } + + function logBytes32(bytes32 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bytes32)", p0)); + } + + function log(uint256 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256)", p0)); + } + + function log(int256 p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(int256)", p0)); + } + + function log(string memory p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string)", p0)); + } + + function log(bool p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool)", p0)); + } + + function log(address p0) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address)", p0)); + } + + function log(uint256 p0, uint256 p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256)", p0, p1)); + } + + function log(uint256 p0, string memory p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string)", p0, p1)); + } + + function log(uint256 p0, bool p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool)", p0, p1)); + } + + function log(uint256 p0, address p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address)", p0, p1)); + } + + function log(string memory p0, uint256 p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256)", p0, p1)); + } + + function log(string memory p0, int256 p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,int256)", p0, p1)); + } + + function log(string memory p0, string memory p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string)", p0, p1)); + } + + function log(string memory p0, bool p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool)", p0, p1)); + } + + function log(string memory p0, address p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address)", p0, p1)); + } + + function log(bool p0, uint256 p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256)", p0, p1)); + } + + function log(bool p0, string memory p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string)", p0, p1)); + } + + function log(bool p0, bool p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool)", p0, p1)); + } + + function log(bool p0, address p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address)", p0, p1)); + } + + function log(address p0, uint256 p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256)", p0, p1)); + } + + function log(address p0, string memory p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string)", p0, p1)); + } + + function log(address p0, bool p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool)", p0, p1)); + } + + function log(address p0, address p1) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,address)", p0, p1)); + } + + function log(uint256 p0, uint256 p1, uint256 p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256)", p0, p1, p2)); + } + + function log(uint256 p0, uint256 p1, string memory p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string)", p0, p1, p2)); + } + + function log(uint256 p0, uint256 p1, bool p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool)", p0, p1, p2)); + } + + function log(uint256 p0, uint256 p1, address p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address)", p0, p1, p2)); + } + + function log(uint256 p0, string memory p1, uint256 p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256)", p0, p1, p2)); + } + + function log(uint256 p0, string memory p1, string memory p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string)", p0, p1, p2)); + } + + function log(uint256 p0, string memory p1, bool p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool)", p0, p1, p2)); + } + + function log(uint256 p0, string memory p1, address p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address)", p0, p1, p2)); + } + + function log(uint256 p0, bool p1, uint256 p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256)", p0, p1, p2)); + } + + function log(uint256 p0, bool p1, string memory p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string)", p0, p1, p2)); + } + + function log(uint256 p0, bool p1, bool p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool)", p0, p1, p2)); + } + + function log(uint256 p0, bool p1, address p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address)", p0, p1, p2)); + } + + function log(uint256 p0, address p1, uint256 p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256)", p0, p1, p2)); + } + + function log(uint256 p0, address p1, string memory p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string)", p0, p1, p2)); + } + + function log(uint256 p0, address p1, bool p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool)", p0, p1, p2)); + } + + function log(uint256 p0, address p1, address p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address)", p0, p1, p2)); + } + + function log(string memory p0, uint256 p1, uint256 p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256)", p0, p1, p2)); + } + + function log(string memory p0, uint256 p1, string memory p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string)", p0, p1, p2)); + } + + function log(string memory p0, uint256 p1, bool p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool)", p0, p1, p2)); + } + + function log(string memory p0, uint256 p1, address p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address)", p0, p1, p2)); + } + + function log(string memory p0, string memory p1, uint256 p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256)", p0, p1, p2)); + } + + function log(string memory p0, string memory p1, string memory p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string,string)", p0, p1, p2)); + } + + function log(string memory p0, string memory p1, bool p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string,bool)", p0, p1, p2)); + } + + function log(string memory p0, string memory p1, address p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string,address)", p0, p1, p2)); + } + + function log(string memory p0, bool p1, uint256 p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256)", p0, p1, p2)); + } + + function log(string memory p0, bool p1, string memory p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,string)", p0, p1, p2)); + } + + function log(string memory p0, bool p1, bool p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool)", p0, p1, p2)); + } + + function log(string memory p0, bool p1, address p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,address)", p0, p1, p2)); + } + + function log(string memory p0, address p1, uint256 p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256)", p0, p1, p2)); + } + + function log(string memory p0, address p1, string memory p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address,string)", p0, p1, p2)); + } + + function log(string memory p0, address p1, bool p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address,bool)", p0, p1, p2)); + } + + function log(string memory p0, address p1, address p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address,address)", p0, p1, p2)); + } + + function log(bool p0, uint256 p1, uint256 p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256)", p0, p1, p2)); + } + + function log(bool p0, uint256 p1, string memory p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string)", p0, p1, p2)); + } + + function log(bool p0, uint256 p1, bool p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool)", p0, p1, p2)); + } + + function log(bool p0, uint256 p1, address p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address)", p0, p1, p2)); + } + + function log(bool p0, string memory p1, uint256 p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256)", p0, p1, p2)); + } + + function log(bool p0, string memory p1, string memory p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,string)", p0, p1, p2)); + } + + function log(bool p0, string memory p1, bool p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool)", p0, p1, p2)); + } + + function log(bool p0, string memory p1, address p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,address)", p0, p1, p2)); + } + + function log(bool p0, bool p1, uint256 p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256)", p0, p1, p2)); + } + + function log(bool p0, bool p1, string memory p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string)", p0, p1, p2)); + } + + function log(bool p0, bool p1, bool p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool)", p0, p1, p2)); + } + + function log(bool p0, bool p1, address p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address)", p0, p1, p2)); + } + + function log(bool p0, address p1, uint256 p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256)", p0, p1, p2)); + } + + function log(bool p0, address p1, string memory p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,string)", p0, p1, p2)); + } + + function log(bool p0, address p1, bool p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool)", p0, p1, p2)); + } + + function log(bool p0, address p1, address p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,address)", p0, p1, p2)); + } + + function log(address p0, uint256 p1, uint256 p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256)", p0, p1, p2)); + } + + function log(address p0, uint256 p1, string memory p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string)", p0, p1, p2)); + } + + function log(address p0, uint256 p1, bool p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool)", p0, p1, p2)); + } + + function log(address p0, uint256 p1, address p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address)", p0, p1, p2)); + } + + function log(address p0, string memory p1, uint256 p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256)", p0, p1, p2)); + } + + function log(address p0, string memory p1, string memory p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string,string)", p0, p1, p2)); + } + + function log(address p0, string memory p1, bool p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string,bool)", p0, p1, p2)); + } + + function log(address p0, string memory p1, address p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string,address)", p0, p1, p2)); + } + + function log(address p0, bool p1, uint256 p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256)", p0, p1, p2)); + } + + function log(address p0, bool p1, string memory p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,string)", p0, p1, p2)); + } + + function log(address p0, bool p1, bool p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool)", p0, p1, p2)); + } + + function log(address p0, bool p1, address p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,address)", p0, p1, p2)); + } + + function log(address p0, address p1, uint256 p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256)", p0, p1, p2)); + } + + function log(address p0, address p1, string memory p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,address,string)", p0, p1, p2)); + } + + function log(address p0, address p1, bool p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,address,bool)", p0, p1, p2)); + } + + function log(address p0, address p1, address p2) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,address,address)", p0, p1, p2)); + } + + function log(uint256 p0, uint256 p1, uint256 p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,uint256)", p0, p1, p2, p3)); + } + + function log(uint256 p0, uint256 p1, uint256 p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,string)", p0, p1, p2, p3)); + } + + function log(uint256 p0, uint256 p1, uint256 p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,bool)", p0, p1, p2, p3)); + } + + function log(uint256 p0, uint256 p1, uint256 p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,address)", p0, p1, p2, p3)); + } + + function log(uint256 p0, uint256 p1, string memory p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,uint256)", p0, p1, p2, p3)); + } + + function log(uint256 p0, uint256 p1, string memory p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,string)", p0, p1, p2, p3)); + } + + function log(uint256 p0, uint256 p1, string memory p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,bool)", p0, p1, p2, p3)); + } + + function log(uint256 p0, uint256 p1, string memory p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,address)", p0, p1, p2, p3)); + } + + function log(uint256 p0, uint256 p1, bool p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,uint256)", p0, p1, p2, p3)); + } + + function log(uint256 p0, uint256 p1, bool p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,string)", p0, p1, p2, p3)); + } + + function log(uint256 p0, uint256 p1, bool p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,bool)", p0, p1, p2, p3)); + } + + function log(uint256 p0, uint256 p1, bool p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,address)", p0, p1, p2, p3)); + } + + function log(uint256 p0, uint256 p1, address p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,uint256)", p0, p1, p2, p3)); + } + + function log(uint256 p0, uint256 p1, address p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,string)", p0, p1, p2, p3)); + } + + function log(uint256 p0, uint256 p1, address p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,bool)", p0, p1, p2, p3)); + } + + function log(uint256 p0, uint256 p1, address p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,address)", p0, p1, p2, p3)); + } + + function log(uint256 p0, string memory p1, uint256 p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,uint256)", p0, p1, p2, p3)); + } + + function log(uint256 p0, string memory p1, uint256 p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,string)", p0, p1, p2, p3)); + } + + function log(uint256 p0, string memory p1, uint256 p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,bool)", p0, p1, p2, p3)); + } + + function log(uint256 p0, string memory p1, uint256 p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,address)", p0, p1, p2, p3)); + } + + function log(uint256 p0, string memory p1, string memory p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,uint256)", p0, p1, p2, p3)); + } + + function log(uint256 p0, string memory p1, string memory p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,string)", p0, p1, p2, p3)); + } + + function log(uint256 p0, string memory p1, string memory p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,bool)", p0, p1, p2, p3)); + } + + function log(uint256 p0, string memory p1, string memory p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,address)", p0, p1, p2, p3)); + } + + function log(uint256 p0, string memory p1, bool p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,uint256)", p0, p1, p2, p3)); + } + + function log(uint256 p0, string memory p1, bool p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,string)", p0, p1, p2, p3)); + } + + function log(uint256 p0, string memory p1, bool p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,bool)", p0, p1, p2, p3)); + } + + function log(uint256 p0, string memory p1, bool p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,address)", p0, p1, p2, p3)); + } + + function log(uint256 p0, string memory p1, address p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,uint256)", p0, p1, p2, p3)); + } + + function log(uint256 p0, string memory p1, address p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,string)", p0, p1, p2, p3)); + } + + function log(uint256 p0, string memory p1, address p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,bool)", p0, p1, p2, p3)); + } + + function log(uint256 p0, string memory p1, address p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,address)", p0, p1, p2, p3)); + } + + function log(uint256 p0, bool p1, uint256 p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,uint256)", p0, p1, p2, p3)); + } + + function log(uint256 p0, bool p1, uint256 p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,string)", p0, p1, p2, p3)); + } + + function log(uint256 p0, bool p1, uint256 p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,bool)", p0, p1, p2, p3)); + } + + function log(uint256 p0, bool p1, uint256 p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,address)", p0, p1, p2, p3)); + } + + function log(uint256 p0, bool p1, string memory p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,uint256)", p0, p1, p2, p3)); + } + + function log(uint256 p0, bool p1, string memory p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,string)", p0, p1, p2, p3)); + } + + function log(uint256 p0, bool p1, string memory p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,bool)", p0, p1, p2, p3)); + } + + function log(uint256 p0, bool p1, string memory p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,address)", p0, p1, p2, p3)); + } + + function log(uint256 p0, bool p1, bool p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,uint256)", p0, p1, p2, p3)); + } + + function log(uint256 p0, bool p1, bool p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,string)", p0, p1, p2, p3)); + } + + function log(uint256 p0, bool p1, bool p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,bool)", p0, p1, p2, p3)); + } + + function log(uint256 p0, bool p1, bool p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,address)", p0, p1, p2, p3)); + } + + function log(uint256 p0, bool p1, address p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,uint256)", p0, p1, p2, p3)); + } + + function log(uint256 p0, bool p1, address p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,string)", p0, p1, p2, p3)); + } + + function log(uint256 p0, bool p1, address p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,bool)", p0, p1, p2, p3)); + } + + function log(uint256 p0, bool p1, address p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,address)", p0, p1, p2, p3)); + } + + function log(uint256 p0, address p1, uint256 p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,uint256)", p0, p1, p2, p3)); + } + + function log(uint256 p0, address p1, uint256 p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,string)", p0, p1, p2, p3)); + } + + function log(uint256 p0, address p1, uint256 p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,bool)", p0, p1, p2, p3)); + } + + function log(uint256 p0, address p1, uint256 p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,address)", p0, p1, p2, p3)); + } + + function log(uint256 p0, address p1, string memory p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,uint256)", p0, p1, p2, p3)); + } + + function log(uint256 p0, address p1, string memory p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,string)", p0, p1, p2, p3)); + } + + function log(uint256 p0, address p1, string memory p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,bool)", p0, p1, p2, p3)); + } + + function log(uint256 p0, address p1, string memory p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,address)", p0, p1, p2, p3)); + } + + function log(uint256 p0, address p1, bool p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,uint256)", p0, p1, p2, p3)); + } + + function log(uint256 p0, address p1, bool p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,string)", p0, p1, p2, p3)); + } + + function log(uint256 p0, address p1, bool p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,bool)", p0, p1, p2, p3)); + } + + function log(uint256 p0, address p1, bool p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,address)", p0, p1, p2, p3)); + } + + function log(uint256 p0, address p1, address p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,uint256)", p0, p1, p2, p3)); + } + + function log(uint256 p0, address p1, address p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,string)", p0, p1, p2, p3)); + } + + function log(uint256 p0, address p1, address p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,bool)", p0, p1, p2, p3)); + } + + function log(uint256 p0, address p1, address p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,address)", p0, p1, p2, p3)); + } + + function log(string memory p0, uint256 p1, uint256 p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,uint256)", p0, p1, p2, p3)); + } + + function log(string memory p0, uint256 p1, uint256 p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,string)", p0, p1, p2, p3)); + } + + function log(string memory p0, uint256 p1, uint256 p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,bool)", p0, p1, p2, p3)); + } + + function log(string memory p0, uint256 p1, uint256 p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,address)", p0, p1, p2, p3)); + } + + function log(string memory p0, uint256 p1, string memory p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,uint256)", p0, p1, p2, p3)); + } + + function log(string memory p0, uint256 p1, string memory p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,string)", p0, p1, p2, p3)); + } + + function log(string memory p0, uint256 p1, string memory p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,bool)", p0, p1, p2, p3)); + } + + function log(string memory p0, uint256 p1, string memory p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,address)", p0, p1, p2, p3)); + } + + function log(string memory p0, uint256 p1, bool p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,uint256)", p0, p1, p2, p3)); + } + + function log(string memory p0, uint256 p1, bool p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,string)", p0, p1, p2, p3)); + } + + function log(string memory p0, uint256 p1, bool p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,bool)", p0, p1, p2, p3)); + } + + function log(string memory p0, uint256 p1, bool p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,address)", p0, p1, p2, p3)); + } + + function log(string memory p0, uint256 p1, address p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,uint256)", p0, p1, p2, p3)); + } + + function log(string memory p0, uint256 p1, address p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,string)", p0, p1, p2, p3)); + } + + function log(string memory p0, uint256 p1, address p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,bool)", p0, p1, p2, p3)); + } + + function log(string memory p0, uint256 p1, address p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,address)", p0, p1, p2, p3)); + } + + function log(string memory p0, string memory p1, uint256 p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,uint256)", p0, p1, p2, p3)); + } + + function log(string memory p0, string memory p1, uint256 p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,string)", p0, p1, p2, p3)); + } + + function log(string memory p0, string memory p1, uint256 p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,bool)", p0, p1, p2, p3)); + } + + function log(string memory p0, string memory p1, uint256 p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,address)", p0, p1, p2, p3)); + } + + function log(string memory p0, string memory p1, string memory p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string,string,uint256)", p0, p1, p2, p3)); + } + + function log(string memory p0, string memory p1, string memory p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string,string,string)", p0, p1, p2, p3)); + } + + function log(string memory p0, string memory p1, string memory p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string,string,bool)", p0, p1, p2, p3)); + } + + function log(string memory p0, string memory p1, string memory p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string,string,address)", p0, p1, p2, p3)); + } + + function log(string memory p0, string memory p1, bool p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,uint256)", p0, p1, p2, p3)); + } + + function log(string memory p0, string memory p1, bool p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,string)", p0, p1, p2, p3)); + } + + function log(string memory p0, string memory p1, bool p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,bool)", p0, p1, p2, p3)); + } + + function log(string memory p0, string memory p1, bool p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,address)", p0, p1, p2, p3)); + } + + function log(string memory p0, string memory p1, address p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string,address,uint256)", p0, p1, p2, p3)); + } + + function log(string memory p0, string memory p1, address p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string,address,string)", p0, p1, p2, p3)); + } + + function log(string memory p0, string memory p1, address p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string,address,bool)", p0, p1, p2, p3)); + } + + function log(string memory p0, string memory p1, address p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,string,address,address)", p0, p1, p2, p3)); + } + + function log(string memory p0, bool p1, uint256 p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,uint256)", p0, p1, p2, p3)); + } + + function log(string memory p0, bool p1, uint256 p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,string)", p0, p1, p2, p3)); + } + + function log(string memory p0, bool p1, uint256 p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,bool)", p0, p1, p2, p3)); + } + + function log(string memory p0, bool p1, uint256 p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,address)", p0, p1, p2, p3)); + } + + function log(string memory p0, bool p1, string memory p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,uint256)", p0, p1, p2, p3)); + } + + function log(string memory p0, bool p1, string memory p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,string)", p0, p1, p2, p3)); + } + + function log(string memory p0, bool p1, string memory p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,bool)", p0, p1, p2, p3)); + } + + function log(string memory p0, bool p1, string memory p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,address)", p0, p1, p2, p3)); + } + + function log(string memory p0, bool p1, bool p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,uint256)", p0, p1, p2, p3)); + } + + function log(string memory p0, bool p1, bool p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,string)", p0, p1, p2, p3)); + } + + function log(string memory p0, bool p1, bool p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,bool)", p0, p1, p2, p3)); + } + + function log(string memory p0, bool p1, bool p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,address)", p0, p1, p2, p3)); + } + + function log(string memory p0, bool p1, address p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,uint256)", p0, p1, p2, p3)); + } + + function log(string memory p0, bool p1, address p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,string)", p0, p1, p2, p3)); + } + + function log(string memory p0, bool p1, address p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,bool)", p0, p1, p2, p3)); + } + + function log(string memory p0, bool p1, address p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,address)", p0, p1, p2, p3)); + } + + function log(string memory p0, address p1, uint256 p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,uint256)", p0, p1, p2, p3)); + } + + function log(string memory p0, address p1, uint256 p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,string)", p0, p1, p2, p3)); + } + + function log(string memory p0, address p1, uint256 p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,bool)", p0, p1, p2, p3)); + } + + function log(string memory p0, address p1, uint256 p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,address)", p0, p1, p2, p3)); + } + + function log(string memory p0, address p1, string memory p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address,string,uint256)", p0, p1, p2, p3)); + } + + function log(string memory p0, address p1, string memory p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address,string,string)", p0, p1, p2, p3)); + } + + function log(string memory p0, address p1, string memory p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address,string,bool)", p0, p1, p2, p3)); + } + + function log(string memory p0, address p1, string memory p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address,string,address)", p0, p1, p2, p3)); + } + + function log(string memory p0, address p1, bool p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,uint256)", p0, p1, p2, p3)); + } + + function log(string memory p0, address p1, bool p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,string)", p0, p1, p2, p3)); + } + + function log(string memory p0, address p1, bool p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,bool)", p0, p1, p2, p3)); + } + + function log(string memory p0, address p1, bool p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,address)", p0, p1, p2, p3)); + } + + function log(string memory p0, address p1, address p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address,address,uint256)", p0, p1, p2, p3)); + } + + function log(string memory p0, address p1, address p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address,address,string)", p0, p1, p2, p3)); + } + + function log(string memory p0, address p1, address p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address,address,bool)", p0, p1, p2, p3)); + } + + function log(string memory p0, address p1, address p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(string,address,address,address)", p0, p1, p2, p3)); + } + + function log(bool p0, uint256 p1, uint256 p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,uint256)", p0, p1, p2, p3)); + } + + function log(bool p0, uint256 p1, uint256 p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,string)", p0, p1, p2, p3)); + } + + function log(bool p0, uint256 p1, uint256 p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,bool)", p0, p1, p2, p3)); + } + + function log(bool p0, uint256 p1, uint256 p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,address)", p0, p1, p2, p3)); + } + + function log(bool p0, uint256 p1, string memory p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,uint256)", p0, p1, p2, p3)); + } + + function log(bool p0, uint256 p1, string memory p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,string)", p0, p1, p2, p3)); + } + + function log(bool p0, uint256 p1, string memory p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,bool)", p0, p1, p2, p3)); + } + + function log(bool p0, uint256 p1, string memory p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,address)", p0, p1, p2, p3)); + } + + function log(bool p0, uint256 p1, bool p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,uint256)", p0, p1, p2, p3)); + } + + function log(bool p0, uint256 p1, bool p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,string)", p0, p1, p2, p3)); + } + + function log(bool p0, uint256 p1, bool p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,bool)", p0, p1, p2, p3)); + } + + function log(bool p0, uint256 p1, bool p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,address)", p0, p1, p2, p3)); + } + + function log(bool p0, uint256 p1, address p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,uint256)", p0, p1, p2, p3)); + } + + function log(bool p0, uint256 p1, address p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,string)", p0, p1, p2, p3)); + } + + function log(bool p0, uint256 p1, address p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,bool)", p0, p1, p2, p3)); + } + + function log(bool p0, uint256 p1, address p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,address)", p0, p1, p2, p3)); + } + + function log(bool p0, string memory p1, uint256 p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,uint256)", p0, p1, p2, p3)); + } + + function log(bool p0, string memory p1, uint256 p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,string)", p0, p1, p2, p3)); + } + + function log(bool p0, string memory p1, uint256 p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,bool)", p0, p1, p2, p3)); + } + + function log(bool p0, string memory p1, uint256 p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,address)", p0, p1, p2, p3)); + } + + function log(bool p0, string memory p1, string memory p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,uint256)", p0, p1, p2, p3)); + } + + function log(bool p0, string memory p1, string memory p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,string)", p0, p1, p2, p3)); + } + + function log(bool p0, string memory p1, string memory p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,bool)", p0, p1, p2, p3)); + } + + function log(bool p0, string memory p1, string memory p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,address)", p0, p1, p2, p3)); + } + + function log(bool p0, string memory p1, bool p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,uint256)", p0, p1, p2, p3)); + } + + function log(bool p0, string memory p1, bool p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,string)", p0, p1, p2, p3)); + } + + function log(bool p0, string memory p1, bool p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,bool)", p0, p1, p2, p3)); + } + + function log(bool p0, string memory p1, bool p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,address)", p0, p1, p2, p3)); + } + + function log(bool p0, string memory p1, address p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,uint256)", p0, p1, p2, p3)); + } + + function log(bool p0, string memory p1, address p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,string)", p0, p1, p2, p3)); + } + + function log(bool p0, string memory p1, address p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,bool)", p0, p1, p2, p3)); + } + + function log(bool p0, string memory p1, address p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,address)", p0, p1, p2, p3)); + } + + function log(bool p0, bool p1, uint256 p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,uint256)", p0, p1, p2, p3)); + } + + function log(bool p0, bool p1, uint256 p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,string)", p0, p1, p2, p3)); + } + + function log(bool p0, bool p1, uint256 p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,bool)", p0, p1, p2, p3)); + } + + function log(bool p0, bool p1, uint256 p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,address)", p0, p1, p2, p3)); + } + + function log(bool p0, bool p1, string memory p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,uint256)", p0, p1, p2, p3)); + } + + function log(bool p0, bool p1, string memory p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,string)", p0, p1, p2, p3)); + } + + function log(bool p0, bool p1, string memory p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,bool)", p0, p1, p2, p3)); + } + + function log(bool p0, bool p1, string memory p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,address)", p0, p1, p2, p3)); + } + + function log(bool p0, bool p1, bool p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,uint256)", p0, p1, p2, p3)); + } + + function log(bool p0, bool p1, bool p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,string)", p0, p1, p2, p3)); + } + + function log(bool p0, bool p1, bool p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,bool)", p0, p1, p2, p3)); + } + + function log(bool p0, bool p1, bool p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,address)", p0, p1, p2, p3)); + } + + function log(bool p0, bool p1, address p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,uint256)", p0, p1, p2, p3)); + } + + function log(bool p0, bool p1, address p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,string)", p0, p1, p2, p3)); + } + + function log(bool p0, bool p1, address p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,bool)", p0, p1, p2, p3)); + } + + function log(bool p0, bool p1, address p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,address)", p0, p1, p2, p3)); + } + + function log(bool p0, address p1, uint256 p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,uint256)", p0, p1, p2, p3)); + } + + function log(bool p0, address p1, uint256 p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,string)", p0, p1, p2, p3)); + } + + function log(bool p0, address p1, uint256 p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,bool)", p0, p1, p2, p3)); + } + + function log(bool p0, address p1, uint256 p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,address)", p0, p1, p2, p3)); + } + + function log(bool p0, address p1, string memory p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,uint256)", p0, p1, p2, p3)); + } + + function log(bool p0, address p1, string memory p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,string)", p0, p1, p2, p3)); + } + + function log(bool p0, address p1, string memory p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,bool)", p0, p1, p2, p3)); + } + + function log(bool p0, address p1, string memory p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,address)", p0, p1, p2, p3)); + } + + function log(bool p0, address p1, bool p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,uint256)", p0, p1, p2, p3)); + } + + function log(bool p0, address p1, bool p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,string)", p0, p1, p2, p3)); + } + + function log(bool p0, address p1, bool p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,bool)", p0, p1, p2, p3)); + } + + function log(bool p0, address p1, bool p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,address)", p0, p1, p2, p3)); + } + + function log(bool p0, address p1, address p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,uint256)", p0, p1, p2, p3)); + } + + function log(bool p0, address p1, address p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,string)", p0, p1, p2, p3)); + } + + function log(bool p0, address p1, address p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,bool)", p0, p1, p2, p3)); + } + + function log(bool p0, address p1, address p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,address)", p0, p1, p2, p3)); + } + + function log(address p0, uint256 p1, uint256 p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,uint256)", p0, p1, p2, p3)); + } + + function log(address p0, uint256 p1, uint256 p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,string)", p0, p1, p2, p3)); + } + + function log(address p0, uint256 p1, uint256 p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,bool)", p0, p1, p2, p3)); + } + + function log(address p0, uint256 p1, uint256 p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,address)", p0, p1, p2, p3)); + } + + function log(address p0, uint256 p1, string memory p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,uint256)", p0, p1, p2, p3)); + } + + function log(address p0, uint256 p1, string memory p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,string)", p0, p1, p2, p3)); + } + + function log(address p0, uint256 p1, string memory p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,bool)", p0, p1, p2, p3)); + } + + function log(address p0, uint256 p1, string memory p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,address)", p0, p1, p2, p3)); + } + + function log(address p0, uint256 p1, bool p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,uint256)", p0, p1, p2, p3)); + } + + function log(address p0, uint256 p1, bool p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,string)", p0, p1, p2, p3)); + } + + function log(address p0, uint256 p1, bool p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,bool)", p0, p1, p2, p3)); + } + + function log(address p0, uint256 p1, bool p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,address)", p0, p1, p2, p3)); + } + + function log(address p0, uint256 p1, address p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,uint256)", p0, p1, p2, p3)); + } + + function log(address p0, uint256 p1, address p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,string)", p0, p1, p2, p3)); + } + + function log(address p0, uint256 p1, address p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,bool)", p0, p1, p2, p3)); + } + + function log(address p0, uint256 p1, address p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,address)", p0, p1, p2, p3)); + } + + function log(address p0, string memory p1, uint256 p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,uint256)", p0, p1, p2, p3)); + } + + function log(address p0, string memory p1, uint256 p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,string)", p0, p1, p2, p3)); + } + + function log(address p0, string memory p1, uint256 p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,bool)", p0, p1, p2, p3)); + } + + function log(address p0, string memory p1, uint256 p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,address)", p0, p1, p2, p3)); + } + + function log(address p0, string memory p1, string memory p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string,string,uint256)", p0, p1, p2, p3)); + } + + function log(address p0, string memory p1, string memory p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string,string,string)", p0, p1, p2, p3)); + } + + function log(address p0, string memory p1, string memory p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string,string,bool)", p0, p1, p2, p3)); + } + + function log(address p0, string memory p1, string memory p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string,string,address)", p0, p1, p2, p3)); + } + + function log(address p0, string memory p1, bool p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,uint256)", p0, p1, p2, p3)); + } + + function log(address p0, string memory p1, bool p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,string)", p0, p1, p2, p3)); + } + + function log(address p0, string memory p1, bool p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,bool)", p0, p1, p2, p3)); + } + + function log(address p0, string memory p1, bool p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,address)", p0, p1, p2, p3)); + } + + function log(address p0, string memory p1, address p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string,address,uint256)", p0, p1, p2, p3)); + } + + function log(address p0, string memory p1, address p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string,address,string)", p0, p1, p2, p3)); + } + + function log(address p0, string memory p1, address p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string,address,bool)", p0, p1, p2, p3)); + } + + function log(address p0, string memory p1, address p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,string,address,address)", p0, p1, p2, p3)); + } + + function log(address p0, bool p1, uint256 p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,uint256)", p0, p1, p2, p3)); + } + + function log(address p0, bool p1, uint256 p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,string)", p0, p1, p2, p3)); + } + + function log(address p0, bool p1, uint256 p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,bool)", p0, p1, p2, p3)); + } + + function log(address p0, bool p1, uint256 p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,address)", p0, p1, p2, p3)); + } + + function log(address p0, bool p1, string memory p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,uint256)", p0, p1, p2, p3)); + } + + function log(address p0, bool p1, string memory p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,string)", p0, p1, p2, p3)); + } + + function log(address p0, bool p1, string memory p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,bool)", p0, p1, p2, p3)); + } + + function log(address p0, bool p1, string memory p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,address)", p0, p1, p2, p3)); + } + + function log(address p0, bool p1, bool p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,uint256)", p0, p1, p2, p3)); + } + + function log(address p0, bool p1, bool p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,string)", p0, p1, p2, p3)); + } + + function log(address p0, bool p1, bool p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,bool)", p0, p1, p2, p3)); + } + + function log(address p0, bool p1, bool p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,address)", p0, p1, p2, p3)); + } + + function log(address p0, bool p1, address p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,uint256)", p0, p1, p2, p3)); + } + + function log(address p0, bool p1, address p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,string)", p0, p1, p2, p3)); + } + + function log(address p0, bool p1, address p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,bool)", p0, p1, p2, p3)); + } + + function log(address p0, bool p1, address p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,address)", p0, p1, p2, p3)); + } + + function log(address p0, address p1, uint256 p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,uint256)", p0, p1, p2, p3)); + } + + function log(address p0, address p1, uint256 p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,string)", p0, p1, p2, p3)); + } + + function log(address p0, address p1, uint256 p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,bool)", p0, p1, p2, p3)); + } + + function log(address p0, address p1, uint256 p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,address)", p0, p1, p2, p3)); + } + + function log(address p0, address p1, string memory p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,address,string,uint256)", p0, p1, p2, p3)); + } + + function log(address p0, address p1, string memory p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,address,string,string)", p0, p1, p2, p3)); + } + + function log(address p0, address p1, string memory p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,address,string,bool)", p0, p1, p2, p3)); + } + + function log(address p0, address p1, string memory p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,address,string,address)", p0, p1, p2, p3)); + } + + function log(address p0, address p1, bool p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,uint256)", p0, p1, p2, p3)); + } + + function log(address p0, address p1, bool p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,string)", p0, p1, p2, p3)); + } + + function log(address p0, address p1, bool p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,bool)", p0, p1, p2, p3)); + } + + function log(address p0, address p1, bool p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,address)", p0, p1, p2, p3)); + } + + function log(address p0, address p1, address p2, uint256 p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,address,address,uint256)", p0, p1, p2, p3)); + } + + function log(address p0, address p1, address p2, string memory p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,address,address,string)", p0, p1, p2, p3)); + } + + function log(address p0, address p1, address p2, bool p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,address,address,bool)", p0, p1, p2, p3)); + } + + function log(address p0, address p1, address p2, address p3) internal view { + _sendLogPayload(abi.encodeWithSignature("log(address,address,address,address)", p0, p1, p2, p3)); + } + +} \ No newline at end of file diff --git a/logging/colors/constants.go b/logging/colors/constants.go index c1a7657a..4f3f555a 100644 --- a/logging/colors/constants.go +++ b/logging/colors/constants.go @@ -31,4 +31,7 @@ const ( const ( // LEFT_ARROW is the unicode string for a left arrow glyph LEFT_ARROW = "\u21fe" + + // BULLET_POINT is the unicode string for a triangular bullet point + BULLET_POINT = "\u2023" ) diff --git a/utils/combinatorial_utils.go b/utils/combinatorial_utils.go new file mode 100644 index 00000000..7eea59d4 --- /dev/null +++ b/utils/combinatorial_utils.go @@ -0,0 +1,46 @@ +package utils + +// PermutationsWithRepetition will take in an array and an integer, n, where n represents how many items need to +// be selected from the array. The function returns an array of all permutations of size n +func PermutationsWithRepetition[T any](choices []T, n int) [][]T { + numChoices := len(choices) + + // At each iteration of the for loop below, one of the indices in counter + // increments by one. Here is what selector looks like over a few iterations + // [0, 0, 0, 0] -> [1, 0, 0, 0] -> ... -> [2, 1, 0, 0] -> ... -> [4, 3, 1, 0] and so on until we reach back to + // [0, 0, 0, 0] which means all permutations have been enumerated. + counter := make([]int, n) + permutations := make([][]T, 0) + for { + // The counter will determine the order of the current permutation. The i-th value of the permutation is equal to + // the x-th index in the choices array. + permutation := make([]T, n) + for i, x := range counter { + permutation[i] = choices[x] + } + + // Add the permutation to the list of permutations + permutations = append(permutations, permutation) + + // This for loop will determine the next value of the counter array + for i := 0; ; { + // Increment the i-th index + counter[i]++ + // If we haven't updated the i-th index of counter up to numChoices - 1, we increment that index + if counter[i] < numChoices { + break + } + + // Once the i-th index is equal to numChoices, we reset counter[i] back to 0 and move on to the next index + // with i++ + counter[i] = 0 + i++ + + // Once we reach the length of the counter array, we are done with enumerating all permutations since all + // indices in the counter array have been reset back to 0 + if i == n { + return permutations + } + } + } +} From 3172b53bf78eb0485a478d4591f3a41b17042f63 Mon Sep 17 00:00:00 2001 From: David Pokora Date: Fri, 18 Aug 2023 14:32:47 -0400 Subject: [PATCH 08/55] Added denomination parsing to AST value extraction (#202) * Added denomination parsing to AST constant mining * Added tests for AST value extraction, removed print from experimental code --------- Co-authored-by: anishnaik --- fuzzing/fuzzer.go | 24 +++++-- fuzzing/fuzzer_test.go | 71 +++++++++++++++++++ .../value_generation/ast_value_extraction.sol | 48 +++++++++++++ fuzzing/valuegeneration/value_set.go | 30 ++++++++ fuzzing/valuegeneration/value_set_from_ast.go | 56 ++++++++++++++- go.mod | 1 + go.sum | 2 + 7 files changed, 225 insertions(+), 7 deletions(-) create mode 100644 fuzzing/testdata/contracts/value_generation/ast_value_extraction.sol diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index 8d879dee..be0fd598 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -7,6 +7,7 @@ import ( "math/big" "math/rand" "path/filepath" + "runtime" "sort" "strconv" "strings" @@ -727,13 +728,24 @@ func (f *Fuzzer) printMetricsLoop() { // Calculate time elapsed since the last update secondsSinceLastUpdate := time.Since(lastPrintedTime).Seconds() + // Obtain memory usage stats + var memStats runtime.MemStats + runtime.ReadMemStats(&memStats) + memoryUsedMB := memStats.Alloc / 1024 / 1024 + memoryTotalMB := memStats.Sys / 1024 / 1024 + // Print a metrics update - f.logger.Info(colors.Bold, "fuzz: ", colors.Reset, - "elapsed: ", colors.Bold, time.Since(startTime).Round(time.Second).String(), colors.Reset, - ", calls: ", colors.Bold, fmt.Sprintf("%d (%d/sec)", callsTested, uint64(float64(new(big.Int).Sub(callsTested, lastCallsTested).Uint64())/secondsSinceLastUpdate)), colors.Reset, - ", seq/s: ", colors.Bold, fmt.Sprintf("%d", uint64(float64(new(big.Int).Sub(sequencesTested, lastSequencesTested).Uint64())/secondsSinceLastUpdate)), colors.Reset, - ", resets/s: ", colors.Bold, fmt.Sprintf("%d", uint64(float64(new(big.Int).Sub(workerStartupCount, lastWorkerStartupCount).Uint64())/secondsSinceLastUpdate)), colors.Reset, - ", coverage: ", colors.Bold, fmt.Sprintf("%d", f.corpus.ActiveMutableSequenceCount()), colors.Reset) + logBuffer := logging.NewLogBuffer() + logBuffer.Append(colors.Bold, "fuzz: ", colors.Reset) + logBuffer.Append("elapsed: ", colors.Bold, time.Since(startTime).Round(time.Second).String(), colors.Reset) + logBuffer.Append(", calls: ", colors.Bold, fmt.Sprintf("%d (%d/sec)", callsTested, uint64(float64(new(big.Int).Sub(callsTested, lastCallsTested).Uint64())/secondsSinceLastUpdate)), colors.Reset) + logBuffer.Append(", seq/s: ", colors.Bold, fmt.Sprintf("%d", uint64(float64(new(big.Int).Sub(sequencesTested, lastSequencesTested).Uint64())/secondsSinceLastUpdate)), colors.Reset) + logBuffer.Append(", coverage: ", colors.Bold, fmt.Sprintf("%d", f.corpus.ActiveMutableSequenceCount()), colors.Reset) + if f.logger.Level() <= zerolog.DebugLevel { + logBuffer.Append(", mem: ", colors.Bold, fmt.Sprintf("%v/%v MB", memoryUsedMB, memoryTotalMB), colors.Reset) + logBuffer.Append(", resets/s: ", colors.Bold, fmt.Sprintf("%d", uint64(float64(new(big.Int).Sub(workerStartupCount, lastWorkerStartupCount).Uint64())/secondsSinceLastUpdate)), colors.Reset) + } + f.logger.Info(logBuffer.Elements()...) // Update our delta tracking metrics lastPrintedTime = time.Now() diff --git a/fuzzing/fuzzer_test.go b/fuzzing/fuzzer_test.go index 978ecbc0..55c845fd 100644 --- a/fuzzing/fuzzer_test.go +++ b/fuzzing/fuzzer_test.go @@ -1,11 +1,13 @@ package fuzzing import ( + "encoding/hex" "github.com/crytic/medusa/chain" "github.com/crytic/medusa/events" "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/fuzzing/valuegeneration" "github.com/crytic/medusa/utils" + "github.com/ethereum/go-ethereum/common" "math/big" "math/rand" "testing" @@ -595,6 +597,75 @@ func TestValueGenerationSolving(t *testing.T) { } } +// TestASTValueExtraction runs a test to ensure appropriate AST values can be mined out of a compiled source's AST. +func TestASTValueExtraction(t *testing.T) { + // Define our expected values to be mined. + expectedAddresses := []common.Address{ + common.HexToAddress("0x7109709ECfa91a80626fF3989D68f67F5b1DD12D"), + common.HexToAddress("0x1234567890123456789012345678901234567890"), + } + expectedIntegers := []string{ + // Unsigned integer tests + "111", // no denomination + "1", // 1 wei (base unit) + "2000000000", // 2 gwei + "5000000000000000000", // 5 ether + "6", // 6 seconds (base unit) + "420", // 7 minutes + "28800", // 8 hours + "777600", // 9 days + "6048000", // 10 weeks + + // Signed integer tests + "-111", // no denomination + "-1", // 1 wei (base unit) + "-2000000000", // 2 gwei + "-5000000000000000000", // 5 ether + "-6", // 6 seconds (base unit) + "-420", // 7 minutes + "-28800", // 8 hours + "-777600", // 9 days + "-6048000", // 10 weeks + } + expectedStrings := []string{ + "testString", + "testString2", + } + expectedByteSequences := make([][]byte, 0) // no tests yet + + // Run the fuzzer test + runFuzzerTest(t, &fuzzerSolcFileTest{ + filePath: "testdata/contracts/value_generation/ast_value_extraction.sol", + configUpdates: func(config *config.ProjectConfig) { + config.Fuzzing.TestLimit = 1 // stop immediately to simply see what values were mined. + config.Fuzzing.Testing.AssertionTesting.Enabled = true + config.Fuzzing.Testing.PropertyTesting.Enabled = false + }, + method: func(f *fuzzerTestContext) { + // Start the fuzzer + err := f.fuzzer.Start() + assert.NoError(t, err) + + // Verify all of our expected values exist + valueSet := f.fuzzer.BaseValueSet() + for _, expectedAddr := range expectedAddresses { + assert.True(t, valueSet.ContainsAddress(expectedAddr), "Value set did not contain expected address: %v", expectedAddr.String()) + } + for _, expectedIntegerStr := range expectedIntegers { + expectedInteger, ok := new(big.Int).SetString(expectedIntegerStr, 10) + assert.True(t, ok, "Could not parse provided expected integer string in test: \"%v\"", expectedIntegerStr) + assert.True(t, valueSet.ContainsInteger(expectedInteger), "Value set did not contain expected integer: %v", expectedInteger.String()) + } + for _, expectedString := range expectedStrings { + assert.True(t, valueSet.ContainsString(expectedString), "Value set did not contain expected string: \"%v\"", expectedString) + } + for _, expectedByteSequence := range expectedByteSequences { + assert.True(t, valueSet.ContainsBytes(expectedByteSequence), "Value set did not contain expected bytes: \"%v\"", hex.EncodeToString(expectedByteSequence)) + } + }, + }) +} + // TestVMCorrectness runs tests to ensure block properties are reported consistently within the EVM, as it's configured // by the chain.TestChain. func TestVMCorrectness(t *testing.T) { diff --git a/fuzzing/testdata/contracts/value_generation/ast_value_extraction.sol b/fuzzing/testdata/contracts/value_generation/ast_value_extraction.sol new file mode 100644 index 00000000..02f2e99c --- /dev/null +++ b/fuzzing/testdata/contracts/value_generation/ast_value_extraction.sol @@ -0,0 +1,48 @@ +// This contract verifies the fuzzer can extract AST literals of different subdenominations from the file. +contract TestContract { + function addressValues() public { + address x = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; + assert(x != address(0x1234567890123456789012345678901234567890)); + } + function uintValues() public { + // Use all integer denoms + uint x = 111; + x = 1 wei; + x = 2 gwei; + //x = 3 szabo; + //x = 4 finney; + x = 5 ether; + x = 6 seconds; + x = 7 minutes; + x = 8 hours; + x = 9 days; + x = 10 weeks; + //x = 11 years; + + // Dummy assertion that should always pass. + assert(x != 0); + } + function intValues() public { + // Use all integer denoms + int x = -111; + x = -1 wei; + x = -2 gwei; + //x = -3 szabo; + //x = -4 finney; + x = -5 ether; + x = -6 seconds; + x = -7 minutes; + x = -8 hours; + x = -9 days; + x = -10 weeks; + //x = -11 years; + + // Dummy assertion that should always pass. + assert(x != 0); + } + function stringValues() public { + string memory s = "testString"; + s = "testString2"; + assert(true); + } +} diff --git a/fuzzing/valuegeneration/value_set.go b/fuzzing/valuegeneration/value_set.go index 77e78b99..883aab73 100644 --- a/fuzzing/valuegeneration/value_set.go +++ b/fuzzing/valuegeneration/value_set.go @@ -64,6 +64,12 @@ func (vs *ValueSet) AddAddress(a common.Address) { vs.addresses[a] = nil } +// ContainsAddress checks if an address is contained in the ValueSet. +func (vs *ValueSet) ContainsAddress(a common.Address) bool { + _, contains := vs.addresses[a] + return contains +} + // RemoveAddress removes an address item from the ValueSet. func (vs *ValueSet) RemoveAddress(a common.Address) { delete(vs.addresses, a) @@ -85,6 +91,12 @@ func (vs *ValueSet) AddInteger(b *big.Int) { vs.integers[b.String()] = b } +// ContainsInteger checks if an integer is contained in the ValueSet. +func (vs *ValueSet) ContainsInteger(b *big.Int) bool { + _, contains := vs.integers[b.String()] + return contains +} + // RemoveInteger removes an integer item from the ValueSet. func (vs *ValueSet) RemoveInteger(b *big.Int) { delete(vs.integers, b.String()) @@ -106,6 +118,12 @@ func (vs *ValueSet) AddString(s string) { vs.strings[s] = nil } +// ContainsString checks if a string is contained in the ValueSet. +func (vs *ValueSet) ContainsString(s string) bool { + _, contains := vs.strings[s] + return contains +} + // RemoveString removes a string item from the ValueSet. func (vs *ValueSet) RemoveString(s string) { delete(vs.strings, s) @@ -133,6 +151,18 @@ func (vs *ValueSet) AddBytes(b []byte) { vs.bytes[hashStr] = b } +// ContainsBytes checks if a byte sequence is contained in the ValueSet. +func (vs *ValueSet) ContainsBytes(b []byte) bool { + // Calculate hash and reset our hash provider + vs.hashProvider.Write(b) + hashStr := hex.EncodeToString(vs.hashProvider.Sum(nil)) + vs.hashProvider.Reset() + + // Check if the key exists in our lookup + _, contains := vs.bytes[hashStr] + return contains +} + // RemoveBytes removes a byte sequence item from the ValueSet. func (vs *ValueSet) RemoveBytes(b []byte) { // Calculate hash and reset our hash provider diff --git a/fuzzing/valuegeneration/value_set_from_ast.go b/fuzzing/valuegeneration/value_set_from_ast.go index 11eb9c5a..0051956c 100644 --- a/fuzzing/valuegeneration/value_set_from_ast.go +++ b/fuzzing/valuegeneration/value_set_from_ast.go @@ -2,6 +2,7 @@ package valuegeneration import ( "github.com/ethereum/go-ethereum/common" + "github.com/shopspring/decimal" "math/big" "strings" ) @@ -20,8 +21,16 @@ func (vs *ValueSet) SeedFromAst(ast any) { return // fail silently to continue walking } + // Extract the subdenomination type + tempSubdenomination, obtainedSubdenomination := node["subdenomination"].(string) + var literalSubdenomination *string + if obtainedSubdenomination { + literalSubdenomination = &tempSubdenomination + } + // Seed ValueSet with literals if literalKind == "number" { + // If it has a 0x prefix, it won't have decimals if strings.HasPrefix(literalValue, "0x") { if b, ok := big.NewInt(0).SetString(literalValue[2:], 16); ok { vs.AddInteger(b) @@ -29,7 +38,8 @@ func (vs *ValueSet) SeedFromAst(ast any) { vs.AddAddress(common.BigToAddress(b)) } } else { - if b, ok := big.NewInt(0).SetString(literalValue, 10); ok { + if decValue, err := decimal.NewFromString(literalValue); err == nil { + b := getAbsoluteValueFromDenominatedValue(decValue, literalSubdenomination) vs.AddInteger(b) vs.AddInteger(new(big.Int).Neg(b)) vs.AddAddress(common.BigToAddress(b)) @@ -42,6 +52,50 @@ func (vs *ValueSet) SeedFromAst(ast any) { }) } +// getAbsoluteValueFromDenominatedValue converts a given decimal number in a provided denomination to a big.Int +// that represents its actual calculated value. +// Note: Decimals must be used as big.Float is prone to similar mantissa-related precision issues as float32/float64. +// Returns the calculated value given the floating point number in a given denomination. +func getAbsoluteValueFromDenominatedValue(number decimal.Decimal, denomination *string) *big.Int { + // If the denomination is nil, we do nothing + if denomination == nil { + return number.BigInt() + } + + // Otherwise, switch on the type and obtain a multiplier + var multiplier decimal.Decimal + switch *denomination { + case "wei": + multiplier = decimal.NewFromFloat32(1) + case "gwei": + multiplier = decimal.NewFromFloat32(1e9) + case "szabo": + multiplier = decimal.NewFromFloat32(1e12) + case "finney": + multiplier = decimal.NewFromFloat32(1e15) + case "ether": + multiplier = decimal.NewFromFloat32(1e18) + case "seconds": + multiplier = decimal.NewFromFloat32(1) + case "minutes": + multiplier = decimal.NewFromFloat32(60) + case "hours": + multiplier = decimal.NewFromFloat32(60 * 60) + case "days": + multiplier = decimal.NewFromFloat32(60 * 60 * 24) + case "weeks": + multiplier = decimal.NewFromFloat32(60 * 60 * 24 * 7) + case "years": + multiplier = decimal.NewFromFloat32(60 * 60 * 24 * 7 * 365) + default: + multiplier = decimal.NewFromFloat32(1) + } + + // Obtain the transformed number as an integer. + transformedValue := number.Mul(multiplier) + return transformedValue.BigInt() +} + // walkAstNodes walks/iterates across an AST for each node, calling the provided walk function with each discovered node // as an argument. func walkAstNodes(ast any, walkFunc func(node map[string]any)) { diff --git a/go.mod b/go.mod index 1aec9600..83b9d462 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/google/uuid v1.3.0 github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.29.0 + github.com/shopspring/decimal v1.3.1 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 diff --git a/go.sum b/go.sum index 1c621d8b..7721ec5e 100644 --- a/go.sum +++ b/go.sum @@ -259,6 +259,8 @@ github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtm github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= From b911551f1e0e088b5f2860811bba19ae71ee80d9 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Tue, 22 Aug 2023 23:09:49 +0200 Subject: [PATCH 09/55] Update README.md (#184) --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b4fc3fa..c4597293 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,10 @@ It provides parallelized fuzz testing of smart contracts through CLI, or its Go ### Precompiled binaries -To use `medusa`, first ensure you have [crytic-compile](https://github.com/crytic/crytic-compile) and a suitable compilation framework (e.g. `solc`, `hardhat`) installed on your machine. +To use `medusa`, ensure you have: + +- [crytic-compile](https://github.com/crytic/crytic-compile) (`pip3 install crytic-compile`) +- a suitable compilation framework (e.g. `solc`, `hardhat`) installed on your machine. We recommend [solc-select](https://github.com/crytic/solc-select) to quickly switch between Solidity compiler versions. You can then fetch the latest binaries for your platform from our [GitHub Releases](https://github.com/crytic/medusa/releases) page. From 931bba0c525b074f1495e7bb103550d984119c26 Mon Sep 17 00:00:00 2001 From: anishnaik Date: Tue, 22 Aug 2023 17:54:11 -0400 Subject: [PATCH 10/55] Multi-channel and multi-error log support (#180) --- cmd/root.go | 10 +- fuzzing/fuzzer.go | 14 +- logging/init.go | 16 ++ logging/logger.go | 467 ++++++++++++++++++++++++++--------------- logging/logger_test.go | 47 +++++ 5 files changed, 375 insertions(+), 179 deletions(-) create mode 100644 logging/init.go create mode 100644 logging/logger_test.go diff --git a/cmd/root.go b/cmd/root.go index 9ec81398..37b1e941 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,7 +4,7 @@ import ( "github.com/crytic/medusa/logging" "github.com/rs/zerolog" "github.com/spf13/cobra" - "io" + "os" ) const version = "0.1.1" @@ -18,11 +18,13 @@ var rootCmd = &cobra.Command{ } // cmdLogger is the logger that will be used for the cmd package -var cmdLogger = logging.NewLogger(zerolog.InfoLevel, true, make([]io.Writer, 0)...) +var cmdLogger = logging.NewLogger(zerolog.InfoLevel) -// Execute provides an exportable function to invoke the CLI. -// Returns an error if one was encountered. +// Execute provides an exportable function to invoke the CLI. Returns an error if one was encountered. func Execute() error { + // Add stdout as an unstructured, colorized output stream for the command logger + cmdLogger.AddWriter(os.Stdout, logging.UNSTRUCTURED, true) + rootCmd.CompletionOptions.DisableDefaultCmd = true return rootCmd.Execute() } diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index be0fd598..7571448a 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -3,9 +3,9 @@ package fuzzing import ( "context" "fmt" - "io" "math/big" "math/rand" + "os" "path/filepath" "runtime" "sort" @@ -18,7 +18,6 @@ import ( "github.com/crytic/medusa/logging" "github.com/crytic/medusa/logging/colors" "github.com/rs/zerolog" - "github.com/rs/zerolog/pkgerrors" "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/utils/randomutils" @@ -90,12 +89,11 @@ type Fuzzer struct { // NewFuzzer returns an instance of a new Fuzzer provided a project configuration, or an error if one is encountered // while initializing the code. func NewFuzzer(config config.ProjectConfig) (*Fuzzer, error) { - // Create the global logger, set some global logging parameters, and enable terminal coloring - logging.GlobalLogger = logging.NewLogger(config.Logging.Level, true, make([]io.Writer, 0)...) - zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack - zerolog.TimeFieldFormat = zerolog.TimeFormatUnix + // Create the global logger and add stdout as an unstructured, colored output stream + logging.GlobalLogger = logging.NewLogger(config.Logging.Level) + logging.GlobalLogger.AddWriter(os.Stdout, logging.UNSTRUCTURED, true) - // If the log directory is a non-empty string, create a file for file logging + // If the log directory is a non-empty string, create a file for unstructured, un-colorized file logging if config.Logging.LogDirectory != "" { // Filename will be the "log-current_unix_timestamp.log" filename := "log-" + strconv.FormatInt(time.Now().Unix(), 10) + ".log" @@ -105,7 +103,7 @@ func NewFuzzer(config config.ProjectConfig) (*Fuzzer, error) { logging.GlobalLogger.Error("Failed to create log file", err) return nil, err } - logging.GlobalLogger.AddWriter(file, logging.UNSTRUCTURED) + logging.GlobalLogger.AddWriter(file, logging.UNSTRUCTURED, false) } // Get the fuzzer's custom sub-logger diff --git a/logging/init.go b/logging/init.go new file mode 100644 index 00000000..0f47cf81 --- /dev/null +++ b/logging/init.go @@ -0,0 +1,16 @@ +package logging + +import ( + "github.com/rs/zerolog" + "github.com/rs/zerolog/pkgerrors" +) + +// init will instantiate the global logger and set up some global parameters from the zerolog package. +func init() { + // Instantiate the global logger + GlobalLogger = NewLogger(zerolog.Disabled) + + // Setup stack trace support and set the timestamp format to UNIX + zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack + zerolog.TimeFieldFormat = zerolog.TimeFormatUnix +} diff --git a/logging/logger.go b/logging/logger.go index 6385d182..48d5c539 100644 --- a/logging/logger.go +++ b/logging/logger.go @@ -11,25 +11,31 @@ import ( // GlobalLogger describes a Logger that is disabled by default and is instantiated when the fuzzer is created. Each module/package // should create its own sub-logger. This allows to create unique logging instances depending on the use case. -var GlobalLogger = NewLogger(zerolog.Disabled, false, nil) +var GlobalLogger *Logger -// Logger describes a custom logging object that can log events to any arbitrary channel and can handle specialized -// output to console as well +// Logger describes a custom logging object that can log events to any arbitrary channel in structured, unstructured with colors, +// and unstructured formats. type Logger struct { // level describes the log level level zerolog.Level - // multiLogger describes a logger that will be used to output logs to any arbitrary channel(s) in either structured - // or unstructured format. - multiLogger zerolog.Logger + // structuredLogger describes a logger that will be used to output structured logs to any arbitrary channel. + structuredLogger zerolog.Logger - // consoleLogger describes a logger that will be used to output unstructured output to console. - // We are creating a separate logger for console so that we can support specialized formatting / custom coloring. - consoleLogger zerolog.Logger + // structuredWriters describes the various channels that the output from the structuredLogger will go to. + structuredWriters []io.Writer - // writers describes a list of io.Writer objects where log output will go. This writers list can be appended to / - // removed from. - writers []io.Writer + // unstructuredLogger describes a logger that will be used to stream un-colorized, unstructured output to any arbitrary channel. + unstructuredLogger zerolog.Logger + + // unstructuredWriters describes the various channels that the output from the unstructuredLogger will go to. + unstructuredWriters []io.Writer + + // unstructuredColorLogger describes a logger that will be used to stream colorized, unstructured output to any arbitrary channel. + unstructuredColorLogger zerolog.Logger + + // unstructuredColorWriters describes the various channels that the output from the unstructuredColoredLogger will go to. + unstructuredColorWriters []io.Writer } // LogFormat describes what format to log in @@ -45,73 +51,144 @@ const ( // StructuredLogInfo describes a key-value mapping that can be used to log structured data type StructuredLogInfo map[string]any -// NewLogger will create a new Logger object with a specific log level. The Logger can output to console, if enabled, -// and output logs to any number of arbitrary io.Writer channels -func NewLogger(level zerolog.Level, consoleEnabled bool, writers ...io.Writer) *Logger { - // The two base loggers are effectively loggers that are disabled - // We are creating instances of them so that we do not get nil pointer dereferences down the line - baseMultiLogger := zerolog.New(os.Stdout).Level(zerolog.Disabled) - baseConsoleLogger := zerolog.New(os.Stdout).Level(zerolog.Disabled) - - // If we are provided a list of writers, update the multi logger - if len(writers) > 0 { - baseMultiLogger = zerolog.New(zerolog.MultiLevelWriter(writers...)).Level(level).With().Timestamp().Logger() - } - - // If console logging is enabled, update the console logger - if consoleEnabled { - consoleWriter := setupDefaultFormatting(zerolog.ConsoleWriter{Out: os.Stdout}, level) - baseConsoleLogger = zerolog.New(consoleWriter).Level(level) - } - +// NewLogger will create a new Logger object with a specific log level. By default, a logger that is instantiated +// with this function is not usable until a log channel is added. To add or remove channels that the logger +// streams logs to, call the Logger.AddWriter and Logger.RemoveWriter functions. +func NewLogger(level zerolog.Level) *Logger { return &Logger{ - level: level, - multiLogger: baseMultiLogger, - consoleLogger: baseConsoleLogger, - writers: writers, + level: level, + structuredLogger: zerolog.New(os.Stdout).Level(zerolog.Disabled), + structuredWriters: make([]io.Writer, 0), + unstructuredLogger: zerolog.New(os.Stdout).Level(zerolog.Disabled), + unstructuredWriters: make([]io.Writer, 0), + unstructuredColorLogger: zerolog.New(os.Stdout).Level(zerolog.Disabled), + unstructuredColorWriters: make([]io.Writer, 0), } } // NewSubLogger will create a new Logger with unique context in the form of a key-value pair. The expected use of this -// function is for each package to have their own unique logger so that parsing of logs is "grep-able" based on some key +// is for each module or component of the system to create their own contextualized logs. The key can be used to search +// for logs from a specific module or component. func (l *Logger) NewSubLogger(key string, value string) *Logger { - subFileLogger := l.multiLogger.With().Str(key, value).Logger() - subConsoleLonger := l.consoleLogger.With().Str(key, value).Logger() + // Create the sub-loggers with the new key-value context + subStructuredLogger := l.structuredLogger.With().Str(key, value).Logger() + subUnstructuredColoredLogger := l.unstructuredColorLogger.With().Str(key, value).Logger() + subUnstructuredLogger := l.unstructuredLogger.With().Str(key, value).Logger() + + // Create new slices for the writers since we want to make a deep copy for each one + subStructuredWriters := make([]io.Writer, len(l.structuredWriters)) + copy(subStructuredWriters, l.structuredWriters) + + subUnstructuredColorWriters := make([]io.Writer, len(l.unstructuredColorWriters)) + copy(subUnstructuredColorWriters, l.unstructuredColorWriters) + + subUnstructuredWriters := make([]io.Writer, len(l.unstructuredWriters)) + copy(subUnstructuredWriters, l.unstructuredWriters) + + // Return a new logger return &Logger{ - level: l.level, - multiLogger: subFileLogger, - consoleLogger: subConsoleLonger, - writers: l.writers, + level: l.level, + structuredLogger: subStructuredLogger, + structuredWriters: subStructuredWriters, + unstructuredColorLogger: subUnstructuredColoredLogger, + unstructuredColorWriters: subUnstructuredColorWriters, + unstructuredLogger: subUnstructuredLogger, + unstructuredWriters: subUnstructuredWriters, } } -// AddWriter will add a writer to the list of channels where log output will be sent. -func (l *Logger) AddWriter(writer io.Writer, format LogFormat) { - // Check to see if the writer is already in the array of writers - for _, w := range l.writers { - if writer == w { - return +// AddWriter will add a writer to which log output will go to. If the format is structured then the writer will get +// structured output. If the writer is unstructured, then the writer has the choice to either receive colored or un-colored +// output. Note that unstructured writers will be converted into a zerolog.ConsoleWriter to maintain the same format +// across all unstructured output streams. +func (l *Logger) AddWriter(writer io.Writer, format LogFormat, colored bool) { + // First, try to add the writer to the list of channels that want structured logs + if format == STRUCTURED { + for _, w := range l.structuredWriters { + if w == writer { + // Writer already exists, return + return + } } + // Add the writer and recreate the logger + l.structuredWriters = append(l.structuredWriters, writer) + l.structuredLogger = zerolog.New(zerolog.MultiLevelWriter(l.structuredWriters...)).Level(l.level).With().Timestamp().Logger() + return } - // If we want unstructured output, wrap the base writer object into a console writer so that we get unstructured output with no ANSI coloring - if format == UNSTRUCTURED { - writer = zerolog.ConsoleWriter{Out: writer, NoColor: true} + // Now that we know we are going to create an unstructured writer, we will create an unstructured writer with(out) coloring + // using zerolog's console writer object. + unstructuredWriter := formatUnstructuredWriter(writer, l.level, colored) + + // Now, try to add the writer to the list of channels that want unstructured, colored logs + if format == UNSTRUCTURED && colored { + for _, w := range l.unstructuredColorWriters { + // We must convert the writer to a console writer to correctly check for existence within the list + if w.(zerolog.ConsoleWriter).Out == writer { + // Writer already exists, return + return + } + } + // Add the unstructured writer and recreate the logger + l.unstructuredColorWriters = append(l.unstructuredColorWriters, unstructuredWriter) + l.unstructuredColorLogger = zerolog.New(zerolog.MultiLevelWriter(l.unstructuredColorWriters...)).Level(l.level).With().Timestamp().Logger() } - // Add it to the list of writers and update the multi logger - l.writers = append(l.writers, writer) - l.multiLogger = zerolog.New(zerolog.MultiLevelWriter(l.writers...)).Level(l.level).With().Timestamp().Logger() + // Otherwise, try to add the writer to the list of channels that want unstructured, un-colored logs + if format == UNSTRUCTURED && !colored { + for _, w := range l.unstructuredWriters { + // We must convert the writer to a console writer to correctly check for existence within the list + if w.(zerolog.ConsoleWriter).Out == writer { + // Writer already exists, return + return + } + } + // Add the unstructured writer and recreate the logger + l.unstructuredWriters = append(l.unstructuredWriters, unstructuredWriter) + l.unstructuredLogger = zerolog.New(zerolog.MultiLevelWriter(l.unstructuredWriters...)).Level(l.level).With().Timestamp().Logger() + } } -// RemoveWriter will remove a writer from the list of writers that the logger manages. If the writer does not exist, this -// function is a no-op -func (l *Logger) RemoveWriter(writer io.Writer) { - // Iterate through the writers - for i, w := range l.writers { - if writer == w { - // Create a new slice without the writer at index i - l.writers = append(l.writers[:i], l.writers[i+1]) +// RemoveWriter will remove a writer from the list of writers that the logger manages. The writer will be either removed +// from the list of structured, unstructured and colored, or unstructured and un-colored writers. If the same writer +// is receiving multiple types of log output (e.g. structured and unstructured with color) then this function must be called +// multiple times. If the writer does not exist in any list, then this function is a no-op. +func (l *Logger) RemoveWriter(writer io.Writer, format LogFormat, colored bool) { + // First, try to remove the writer from the list of structured writers + if format == STRUCTURED { + // Check for writer existence + for i, w := range l.structuredWriters { + if w == writer { + // Remove the writer and recreate the logger + l.structuredWriters = append(l.structuredWriters[:i], l.structuredWriters[i+1:]...) + l.structuredLogger = zerolog.New(zerolog.MultiLevelWriter(l.structuredWriters...)).Level(l.level).With().Timestamp().Logger() + } + } + } + + // Now, try to remove the writer from the list of unstructured, colored writers + if format == UNSTRUCTURED && colored { + // Check for writer existence + for i, w := range l.unstructuredColorWriters { + // We must convert the writer to a console writer to correctly check for existence within the list + if w.(zerolog.ConsoleWriter).Out == writer { + // Remove the writer and recreate the logger + l.unstructuredColorWriters = append(l.unstructuredColorWriters[:i], l.unstructuredColorWriters[i+1:]...) + l.unstructuredColorLogger = zerolog.New(zerolog.MultiLevelWriter(l.unstructuredColorWriters...)).Level(l.level).With().Timestamp().Logger() + } + } + } + + // Otherwise, try to remove the writer from the list of unstructured, un-colored writers + if format == UNSTRUCTURED && !colored { + // Check for writer existence + for i, w := range l.unstructuredWriters { + // We must convert the writer to a console writer to correctly check for existence within the list + if w.(zerolog.ConsoleWriter).Out == writer { + // Remove the writer and recreate the logger + l.unstructuredWriters = append(l.unstructuredWriters[:i], l.unstructuredWriters[i+1:]...) + l.unstructuredLogger = zerolog.New(zerolog.MultiLevelWriter(l.unstructuredWriters...)).Level(l.level).With().Timestamp().Logger() + } } } } @@ -124,111 +201,104 @@ func (l *Logger) Level() zerolog.Level { // SetLevel will update the log level of the Logger func (l *Logger) SetLevel(level zerolog.Level) { l.level = level - l.multiLogger = l.multiLogger.Level(level) - l.consoleLogger = l.consoleLogger.Level(level) + + // Update the level of each underlying logger + l.structuredLogger = l.structuredLogger.Level(level) + l.unstructuredColorLogger = l.unstructuredColorLogger.Level(level) + l.unstructuredLogger = l.unstructuredLogger.Level(level) } // Trace is a wrapper function that will log a trace event func (l *Logger) Trace(args ...any) { - // Build the messages and retrieve any error or associated structured log info - consoleMsg, multiMsg, err, info := buildMsgs(args...) + // Build the messages and retrieve any errors or associated structured log info + colorMsg, noColorMsg, errs, info := buildMsgs(args...) // Instantiate log events - consoleLog := l.consoleLogger.Trace() - multiLog := l.multiLogger.Trace() - - // Chain the error - chainError(consoleLog, multiLog, err, l.level <= zerolog.DebugLevel) + structuredLog := l.structuredLogger.Trace() + unstructuredColoredLog := l.unstructuredColorLogger.Trace() + unstructuredLog := l.unstructuredLogger.Trace() - // Chain the structured log info and messages and send off the logs - chainStructuredLogInfoAndMsgs(consoleLog, multiLog, info, consoleMsg, multiMsg) + // Chain the structured log info, errors, and messages and send off the logs + chainStructuredLogInfoErrorsAndMsgs(structuredLog, unstructuredColoredLog, unstructuredLog, info, errs, colorMsg, noColorMsg) } // Debug is a wrapper function that will log a debug event func (l *Logger) Debug(args ...any) { - // Build the messages and retrieve any error or associated structured log info - consoleMsg, multiMsg, err, info := buildMsgs(args...) + // Build the messages and retrieve any errors or associated structured log info + colorMsg, noColorMsg, errs, info := buildMsgs(args...) // Instantiate log events - consoleLog := l.consoleLogger.Debug() - multiLog := l.multiLogger.Debug() + structuredLog := l.structuredLogger.Debug() + unstructuredColoredLog := l.unstructuredColorLogger.Debug() + unstructuredLog := l.unstructuredLogger.Debug() - // Chain the error - chainError(consoleLog, multiLog, err, l.level <= zerolog.DebugLevel) - - // Chain the structured log info and messages and send off the logs - chainStructuredLogInfoAndMsgs(consoleLog, multiLog, info, consoleMsg, multiMsg) + // Chain the structured log info, errors, and messages and send off the logs + chainStructuredLogInfoErrorsAndMsgs(structuredLog, unstructuredColoredLog, unstructuredLog, info, errs, colorMsg, noColorMsg) } // Info is a wrapper function that will log an info event func (l *Logger) Info(args ...any) { - // Build the messages and retrieve any error or associated structured log info - consoleMsg, multiMsg, err, info := buildMsgs(args...) + // Build the messages and retrieve any errors or associated structured log info + colorMsg, noColorMsg, errs, info := buildMsgs(args...) // Instantiate log events - consoleLog := l.consoleLogger.Info() - multiLog := l.multiLogger.Info() - - // Chain the error - chainError(consoleLog, multiLog, err, l.level <= zerolog.DebugLevel) + structuredLog := l.structuredLogger.Info() + unstructuredColoredLog := l.unstructuredColorLogger.Info() + unstructuredLog := l.unstructuredLogger.Info() - // Chain the structured log info and messages and send off the logs - chainStructuredLogInfoAndMsgs(consoleLog, multiLog, info, consoleMsg, multiMsg) + // Chain the structured log info, errors, and messages and send off the logs + chainStructuredLogInfoErrorsAndMsgs(structuredLog, unstructuredColoredLog, unstructuredLog, info, errs, colorMsg, noColorMsg) } // Warn is a wrapper function that will log a warning event both on console func (l *Logger) Warn(args ...any) { - // Build the messages and retrieve any error or associated structured log info - consoleMsg, multiMsg, err, info := buildMsgs(args...) + // Build the messages and retrieve any errors or associated structured log info + colorMsg, noColorMsg, errs, info := buildMsgs(args...) // Instantiate log events - consoleLog := l.consoleLogger.Warn() - multiLog := l.multiLogger.Warn() - - // Chain the error - chainError(consoleLog, multiLog, err, l.level <= zerolog.DebugLevel) + structuredLog := l.structuredLogger.Warn() + unstructuredColoredLog := l.unstructuredColorLogger.Warn() + unstructuredLog := l.unstructuredLogger.Warn() - // Chain the structured log info and messages and send off the logs - chainStructuredLogInfoAndMsgs(consoleLog, multiLog, info, consoleMsg, multiMsg) + // Chain the structured log info, errors, and messages and send off the logs + chainStructuredLogInfoErrorsAndMsgs(structuredLog, unstructuredColoredLog, unstructuredLog, info, errs, colorMsg, noColorMsg) } // Error is a wrapper function that will log an error event. func (l *Logger) Error(args ...any) { - // Build the messages and retrieve any error or associated structured log info - consoleMsg, multiMsg, err, info := buildMsgs(args...) + // Build the messages and retrieve any errors or associated structured log info + colorMsg, noColorMsg, errs, info := buildMsgs(args...) // Instantiate log events - consoleLog := l.consoleLogger.Error() - multiLog := l.multiLogger.Error() + structuredLog := l.structuredLogger.Error() + unstructuredColoredLog := l.unstructuredColorLogger.Error() + unstructuredLog := l.unstructuredLogger.Error() - // Chain the error - chainError(consoleLog, multiLog, err, l.level <= zerolog.DebugLevel) - - // Chain the structured log info and messages and send off the logs - chainStructuredLogInfoAndMsgs(consoleLog, multiLog, info, consoleMsg, multiMsg) + // Chain the structured log info, errors, and messages and send off the logs + chainStructuredLogInfoErrorsAndMsgs(structuredLog, unstructuredColoredLog, unstructuredLog, info, errs, colorMsg, noColorMsg) } // Panic is a wrapper function that will log a panic event func (l *Logger) Panic(args ...any) { - // Build the messages and retrieve any error or associated structured log info - consoleMsg, multiMsg, err, info := buildMsgs(args...) + // Build the messages and retrieve any errors or associated structured log info + colorMsg, noColorMsg, errs, info := buildMsgs(args...) // Instantiate log events - consoleLog := l.consoleLogger.Panic() - multiLog := l.multiLogger.Panic() - - // Chain the error - chainError(consoleLog, multiLog, err, true) + structuredLog := l.structuredLogger.Panic() + unstructuredColoredLog := l.unstructuredColorLogger.Panic() + unstructuredLog := l.unstructuredLogger.Panic() - // Chain the structured log info and messages and send off the logs - chainStructuredLogInfoAndMsgs(consoleLog, multiLog, info, consoleMsg, multiMsg) + // Chain the structured log info, errors, and messages and send off the logs + chainStructuredLogInfoErrorsAndMsgs(structuredLog, unstructuredColoredLog, unstructuredLog, info, errs, colorMsg, noColorMsg) } // buildMsgs describes a function that takes in a variadic list of arguments of any type and returns two strings and, -// optionally, an error and a StructuredLogInfo object. The first string will be a colorized-string that can be used for -// console logging while the second string will be a non-colorized one that can be used for file/structured logging. -// The error and the StructuredLogInfo can be used to add additional context to log messages -func buildMsgs(args ...any) (string, string, error, StructuredLogInfo) { +// optionally, a list of errors and a StructuredLogInfo object. The first string will be a colorized-message while the +// second string will be a non-colorized one. Colors are applied if one or more of the input arguments are of type +// colors.ColorFunc. The colorized message can be used for channels that request unstructured, colorized log output +// while the non-colorized one can be used for structured streams and unstructured streams that don't want color. The +// errors and the StructuredLogInfo can be used to add additional context to log messages. +func buildMsgs(args ...any) (string, string, []error, StructuredLogInfo) { // Guard clause if len(args) == 0 { return "", "", nil, nil @@ -236,10 +306,10 @@ func buildMsgs(args ...any) (string, string, error, StructuredLogInfo) { // Initialize the base color context, the string buffers and the structured log info object colorCtx := colors.Reset - consoleOutput := make([]string, 0) - fileOutput := make([]string, 0) + colorMsg := make([]string, 0) + noColorMsg := make([]string, 0) + errs := make([]error, 0) var info StructuredLogInfo - var err error // Iterate through each argument in the list and switch on type for _, arg := range args { @@ -251,86 +321,154 @@ func buildMsgs(args ...any) (string, string, error, StructuredLogInfo) { // Note that only one structured log info can be provided for each log message info = t case error: - // Note that only one error can be provided for each log message - err = t + // Append error to the list of errors + errs = append(errs, t) default: - // In the base case, append the object to the two string buffers. The console string buffer will have the + // In the base case, append the object to the two string buffers. The colored string buffer will have the // current color context applied to it. - consoleOutput = append(consoleOutput, colorCtx(t)) - fileOutput = append(fileOutput, fmt.Sprintf("%v", t)) + colorMsg = append(colorMsg, colorCtx(t)) + noColorMsg = append(noColorMsg, fmt.Sprintf("%v", t)) } } - return strings.Join(consoleOutput, ""), strings.Join(fileOutput, ""), err, info + return strings.Join(colorMsg, ""), strings.Join(noColorMsg, ""), errs, info } -// chainError is a helper function that takes in a *zerolog.Event for console and multi-log output and chains an error -// to both events. If debug is true, then a stack trace is added to both events as well. -func chainError(consoleLog *zerolog.Event, multiLog *zerolog.Event, err error, debug bool) { - // First append the errors to each event. Note that even if err is nil, there will not be a panic here - consoleLog.Err(err) - multiLog.Err(err) - - // If we are in debug mode or below, then we will add the stack traces as well for debugging - if debug { - consoleLog.Stack() - multiLog.Stack() +// chainStructuredLogInfoErrorsAndMsgs describes a function that takes in a *zerolog.Event for the structured, unstructured +// with color, and unstructured without colors log streams, chains any StructuredLogInfo and errors provided to it, +// adds the associated messages, and sends out the logs to their respective channels. Note that the StructuredLogInfo object +// is only appended to the structured log event and not to the unstructured ones. Additionally, note that errors are appended as a +// formatted bulleted list for unstructured logging while for the structured logger they get appended as a key-value pair. +func chainStructuredLogInfoErrorsAndMsgs(structuredLog *zerolog.Event, unstructuredColoredLog *zerolog.Event, unstructuredLog *zerolog.Event, info StructuredLogInfo, errs []error, colorMsg string, noColorMsg string) { + // First, we need to create a formatted error string for unstructured output + var errStr string + for _, err := range errs { + // To make the formatting a little nicer, we will add a tab after each new line in the error so that + // errors can be better differentiated on unstructured channels + lines := make([]string, 0) + for i, line := range strings.Split(err.Error(), "\n") { + // Add a tab to the line only after the first new line in the error message + if i != 0 { + line = "\t" + line + } + lines = append(lines, line) + } + + // Update the error string to be based on the tabbed lines array + if len(lines) > 0 { + err = fmt.Errorf("%v", strings.Join(lines, "\n")) + } + + // Append a bullet point and the formatted error to the error string + errStr += "\n" + colors.BULLET_POINT + " " + err.Error() + } + + // Add structured error element to the multi-log output and append the error string to the console message + // TODO: Add support for stack traces in the future + if len(errs) != 0 { + structuredLog.Errs("errors", errs) + } + + // The structured message will be the one without any potential errors appended to it since the errors will be provided + // as a key-value pair + structuredMsg := noColorMsg + + // Add the colorized and non-colorized version of the error string to the colorized and non-colorized messages, respectively. + if len(errStr) > 0 { + colorMsg += colors.Red(errStr) + noColorMsg += errStr } -} -// chainStructuredLogInfoAndMsgs is a helper function that takes in a *zerolog.Event for console and multi-log output, -// chains any StructuredLogInfo provided to it, adds the associated messages, and sends out the logs to their respective -// channels. -func chainStructuredLogInfoAndMsgs(consoleLog *zerolog.Event, multiLog *zerolog.Event, info StructuredLogInfo, consoleMsg string, multiMsg string) { - // If we are provided a structured log info object, add that as a key-value pair to the events + // If we are provided a structured log info object, add that as a key-value pair to the structured log event if info != nil { - consoleLog.Any("info", info) - multiLog.Any("info", info) + structuredLog.Any("info", info) } // Append the messages to each event. This will also result in the log events being sent out to their respective - // streams. Note that we are deferring the msg to multi logger in case we are logging a panic and want to make sure that - // all channels receive the panic log - defer multiLog.Msg(multiMsg) - consoleLog.Msg(consoleMsg) + // streams. Note that we are deferring the message to two of the three loggers multi logger in case we are logging a panic + // and want to make sure that all channels receive the panic log. + defer func() { + structuredLog.Msg(structuredMsg) + unstructuredLog.Msg(noColorMsg) + }() + unstructuredColoredLog.Msg(colorMsg) } -// setupDefaultFormatting will update the console logger's formatting to the medusa standard -func setupDefaultFormatting(writer zerolog.ConsoleWriter, level zerolog.Level) zerolog.ConsoleWriter { - // Get rid of the timestamp for console output - writer.FormatTimestamp = func(i interface{}) string { +// formatUnstructuredWriter will create a custom-formatted zerolog.ConsoleWriter from an arbitrary io.Writer. A zerolog.ConsoleWriter is +// what is used under-the-hood to support unstructured log output. Custom formatting is applied to specific fields, +// timestamps, and the log level strings. If requested, coloring may be applied to the log level strings. +func formatUnstructuredWriter(writer io.Writer, level zerolog.Level, colored bool) zerolog.ConsoleWriter { + // Create the console writer + consoleWriter := zerolog.ConsoleWriter{Out: writer, NoColor: !colored} + + // Get rid of the timestamp for unstructured output + consoleWriter.FormatTimestamp = func(i interface{}) string { return "" } - // We will define a custom format for each level - writer.FormatLevel = func(i any) string { + // If we are above debug level, we want to get rid of the `module` component when logging to unstructured streams + if level > zerolog.DebugLevel { + consoleWriter.FieldsExclude = []string{"module"} + } + + // If coloring is enabled, we will return a custom, colored string for each log severity level + // Otherwise, we will just return a non-colorized string for each log severity level + consoleWriter.FormatLevel = func(i any) string { // Create a level object for better switch logic level, err := zerolog.ParseLevel(i.(string)) if err != nil { panic(fmt.Sprintf("unable to parse the log level: %v", err)) } - // Switch on the level and return a custom, colored string + // Switch on the level switch level { case zerolog.TraceLevel: + if !colored { + // No coloring for "trace" string + return zerolog.LevelTraceValue + } // Return a bold, cyan "trace" string return colors.CyanBold(zerolog.LevelTraceValue) case zerolog.DebugLevel: + if !colored { + // No coloring for "debug" string + return zerolog.LevelDebugValue + } // Return a bold, blue "debug" string return colors.BlueBold(zerolog.LevelDebugValue) case zerolog.InfoLevel: + if !colored { + // Return a left arrow without any coloring + return colors.LEFT_ARROW + } // Return a bold, green left arrow return colors.GreenBold(colors.LEFT_ARROW) case zerolog.WarnLevel: + if !colored { + // No coloring for "warn" string + return zerolog.LevelWarnValue + } // Return a bold, yellow "warn" string return colors.YellowBold(zerolog.LevelWarnValue) case zerolog.ErrorLevel: + if !colored { + // No coloring for "err" string + return zerolog.LevelErrorValue + } // Return a bold, red "err" string return colors.RedBold(zerolog.LevelErrorValue) case zerolog.FatalLevel: + if !colored { + // No coloring for "fatal" string + return zerolog.LevelFatalValue + } // Return a bold, red "fatal" string return colors.RedBold(zerolog.LevelFatalValue) case zerolog.PanicLevel: + if !colored { + // No coloring for "panic" string + return zerolog.LevelPanicValue + } // Return a bold, red "panic" string return colors.RedBold(zerolog.LevelPanicValue) default: @@ -338,10 +476,5 @@ func setupDefaultFormatting(writer zerolog.ConsoleWriter, level zerolog.Level) z } } - // If we are above debug level, we want to get rid of the `module` component when logging to console - if level > zerolog.DebugLevel { - writer.FieldsExclude = []string{"module"} - } - - return writer + return consoleWriter } diff --git a/logging/logger_test.go b/logging/logger_test.go new file mode 100644 index 00000000..15404b83 --- /dev/null +++ b/logging/logger_test.go @@ -0,0 +1,47 @@ +package logging + +import ( + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "os" + "testing" +) + +// TestAddAndRemoveWriter will test to Logger.AddWriter and Logger.RemoveWriter functions to ensure that they work as expected. +func TestAddAndRemoveWriter(t *testing.T) { + // Create a base logger + logger := NewLogger(zerolog.InfoLevel) + + // Add three types of writers + // 1. Unstructured and colorized output to stdout + logger.AddWriter(os.Stdout, UNSTRUCTURED, true) + // 2. Unstructured and non-colorized output to stderr + logger.AddWriter(os.Stderr, UNSTRUCTURED, false) + // 3. Structured output to stdin + logger.AddWriter(os.Stdin, STRUCTURED, false) + + // We should expect the underlying data structures are correctly updated + assert.Equal(t, len(logger.unstructuredWriters), 1) + assert.Equal(t, len(logger.unstructuredColorWriters), 1) + assert.Equal(t, len(logger.structuredWriters), 1) + + // Try to add duplicate writers + logger.AddWriter(os.Stdout, UNSTRUCTURED, true) + logger.AddWriter(os.Stderr, UNSTRUCTURED, false) + logger.AddWriter(os.Stdin, STRUCTURED, false) + + // Ensure that the lengths of the lists have not changed + assert.Equal(t, len(logger.unstructuredWriters), 1) + assert.Equal(t, len(logger.unstructuredColorWriters), 1) + assert.Equal(t, len(logger.structuredWriters), 1) + + // Remove each writer + logger.RemoveWriter(os.Stdout, UNSTRUCTURED, true) + logger.RemoveWriter(os.Stderr, UNSTRUCTURED, false) + logger.RemoveWriter(os.Stdin, STRUCTURED, false) + + // We should expect the underlying data structures are correctly updated + assert.Equal(t, len(logger.unstructuredWriters), 0) + assert.Equal(t, len(logger.unstructuredColorWriters), 0) + assert.Equal(t, len(logger.structuredWriters), 0) +} From aa82533a110e200c43d57054a48d286b11466b8f Mon Sep 17 00:00:00 2001 From: anishnaik Date: Wed, 23 Aug 2023 13:22:57 -0400 Subject: [PATCH 11/55] Update version (#210) --- cmd/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index 37b1e941..ae03d15d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,7 +7,7 @@ import ( "os" ) -const version = "0.1.1" +const version = "0.1.2" // rootCmd represents the root CLI command object which all other commands stem from. var rootCmd = &cobra.Command{ From 24ebcc49a2cdd9d3f93aa4c8d2d8c563ba788213 Mon Sep 17 00:00:00 2001 From: Exca-DK Date: Mon, 25 Sep 2023 16:03:25 +0200 Subject: [PATCH 12/55] Support NoColor and --no-color (#222) * Console output with optional coloring * optional Colorize * LF and CRLF line endings * update no-color flag description * code review changes --------- Authored-by: Exca-DK --- cmd/fuzz_flags.go | 12 +++++++++++ fuzzing/config/config.go | 3 +++ fuzzing/config/config_defaults.go | 1 + fuzzing/fuzzer.go | 8 ++++++-- logging/colors/colorize_unix.go | 14 +++++++++++-- logging/colors/colorize_windows.go | 6 +++++- logging/logger_test.go | 32 ++++++++++++++++++++++++++++-- 7 files changed, 69 insertions(+), 7 deletions(-) diff --git a/cmd/fuzz_flags.go b/cmd/fuzz_flags.go index 02300ecb..e1999804 100644 --- a/cmd/fuzz_flags.go +++ b/cmd/fuzz_flags.go @@ -68,6 +68,10 @@ func addFuzzFlags() error { // Trace all fuzzCmd.Flags().Bool("trace-all", false, fmt.Sprintf("print the execution trace for every element in a shrunken call sequence instead of only the last element (unless a config file is provided, default is %t)", defaultConfig.Fuzzing.Testing.TraceAll)) + + // Logging color + fuzzCmd.Flags().Bool("no-color", false, "disabled colored terminal output") + return nil } @@ -176,5 +180,13 @@ func updateProjectConfigWithFuzzFlags(cmd *cobra.Command, projectConfig *config. return err } } + + // Update logging color mode + if cmd.Flags().Changed("no-color") { + projectConfig.Logging.NoColor, err = cmd.Flags().GetBool("no-color") + if err != nil { + return err + } + } return nil } diff --git a/fuzzing/config/config.go b/fuzzing/config/config.go index 3e1a1560..561a091f 100644 --- a/fuzzing/config/config.go +++ b/fuzzing/config/config.go @@ -198,6 +198,9 @@ type LoggingConfig struct { // LogDirectory describes what directory log files should be outputted in/ LogDirectory being a non-empty string is // equivalent to enabling file logging. LogDirectory string `json:"logDirectory"` + + // NoColor indicates whether or not log messages should be displayed with colored formatting. + NoColor bool `json:"noColor"` } // ConsoleLoggingConfig describes the configuration options for logging to console. Note that this not being used right now diff --git a/fuzzing/config/config_defaults.go b/fuzzing/config/config_defaults.go index e4ef3644..ecaf2a18 100644 --- a/fuzzing/config/config_defaults.go +++ b/fuzzing/config/config_defaults.go @@ -85,6 +85,7 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) { Logging: LoggingConfig{ Level: zerolog.InfoLevel, LogDirectory: "", + NoColor: false, }, } diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index 7571448a..b792a55d 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -89,9 +89,13 @@ type Fuzzer struct { // NewFuzzer returns an instance of a new Fuzzer provided a project configuration, or an error if one is encountered // while initializing the code. func NewFuzzer(config config.ProjectConfig) (*Fuzzer, error) { - // Create the global logger and add stdout as an unstructured, colored output stream + // Disable colors if requested + if config.Logging.NoColor { + colors.DisableColor() + } + // Create the global logger and add stdout as an unstructured output stream logging.GlobalLogger = logging.NewLogger(config.Logging.Level) - logging.GlobalLogger.AddWriter(os.Stdout, logging.UNSTRUCTURED, true) + logging.GlobalLogger.AddWriter(os.Stdout, logging.UNSTRUCTURED, !config.Logging.NoColor) // If the log directory is a non-empty string, create a file for unstructured, un-colorized file logging if config.Logging.LogDirectory != "" { diff --git a/logging/colors/colorize_unix.go b/logging/colors/colorize_unix.go index 89ea510d..ba404662 100644 --- a/logging/colors/colorize_unix.go +++ b/logging/colors/colorize_unix.go @@ -5,11 +5,21 @@ package colors import "fmt" -// EnableColor is a no-op function for non-windows systems because we know that they support ANSI escape codes -func EnableColor() {} +var enabled = true + +// EnableColor enables the use of colors for non-windows systems. +func EnableColor() { enabled = true } + +// DisableColor disables the use of colors for non-windows systems. +func DisableColor() { enabled = false } // Colorize returns the string s wrapped in ANSI code c for non-windows systems // Source: https://github.com/rs/zerolog/blob/4fff5db29c3403bc26dee9895e12a108aacc0203/console.go func Colorize(s any, c Color) string { + // Return original string if explicitly disabled + if !enabled { + return fmt.Sprintf("%v", s) + } + return fmt.Sprintf("\x1b[%dm%v\x1b[0m", c, s) } diff --git a/logging/colors/colorize_windows.go b/logging/colors/colorize_windows.go index 87266688..4e57c269 100644 --- a/logging/colors/colorize_windows.go +++ b/logging/colors/colorize_windows.go @@ -5,8 +5,9 @@ package colors import ( "fmt" - "golang.org/x/sys/windows" "os" + + "golang.org/x/sys/windows" ) var enabled bool @@ -53,6 +54,9 @@ func EnableColor() { } } +// DisableColor will disable colors +func DisableColor() { enabled = false } + // Colorize returns the string s wrapped in ANSI code c assuming that ANSI is supported on the Windows version // Source: https://github.com/rs/zerolog/blob/4fff5db29c3403bc26dee9895e12a108aacc0203/console.go func Colorize(s any, c Color) string { diff --git a/logging/logger_test.go b/logging/logger_test.go index 15404b83..540e78e3 100644 --- a/logging/logger_test.go +++ b/logging/logger_test.go @@ -1,10 +1,15 @@ package logging import ( - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" + "bytes" + "fmt" "os" + "strings" "testing" + + "github.com/crytic/medusa/logging/colors" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" ) // TestAddAndRemoveWriter will test to Logger.AddWriter and Logger.RemoveWriter functions to ensure that they work as expected. @@ -45,3 +50,26 @@ func TestAddAndRemoveWriter(t *testing.T) { assert.Equal(t, len(logger.unstructuredColorWriters), 0) assert.Equal(t, len(logger.structuredWriters), 0) } + +// TestDisabledColors verifies the behavior of the unstructured colored logger when colors are disabled, +// ensuring that it does not output colors when the color feature is turned off. +func TestDisabledColors(t *testing.T) { + // Create a base logger + logger := NewLogger(zerolog.InfoLevel) + + // Add colorized logger + var buf bytes.Buffer + logger.AddWriter(&buf, UNSTRUCTURED, true) + + // We should expect the underlying data structures are correctly updated + assert.Equal(t, len(logger.unstructuredColorWriters), 1) + + // Disable colors and log msg + colors.DisableColor() + logger.Info("foo") + + // Ensure that msg doesn't include colors afterwards + prefix := fmt.Sprintf("%s %s", colors.LEFT_ARROW, "foo") + _, ok := strings.CutPrefix(buf.String(), prefix) + assert.True(t, ok) +} From e37083264c12fe0c9deace2a7c7965d4a2960e46 Mon Sep 17 00:00:00 2001 From: Feist Josselin Date: Mon, 25 Sep 2023 16:52:19 +0200 Subject: [PATCH 13/55] Update CONTRIBUTING.md (#185) Co-authored-by: anishnaik --- CONTRIBUTING.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7df2230d..deceb304 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,6 +34,47 @@ If any of these requirements are violated, you should expect your pull request t Pull request reviewers have a responsibility to uphold these standards. Even if a pull request is compliant with these requirements, a reviewer which identifies an opportunity to document some caveat (such as a `// TODO: ` comment) should request it be added prior to pull request approval. +### Linters + +Several linters and security checkers are run on the PRs. + +#### Go + +To install + +- `go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest` + +To run + +- `go fmt ./...` +- `golangci-lint run --timeout 5m0s` + +#### Markdown/Json/Yaml + +To install + +- `npm install -g prettier` +- `npm install -g markdown-link-check@3.10.3` + +To run + +- `prettier '**.json' '**/*.md' '**/*.yml' '!(pkg)'` +- `markdown-link-check --config .github/workflows/resources/markdown_link_check.json ./*.md` + +To format (overwrite files) + +- `prettier '**.json' '**/*.md' '**/*.yml' '!(pkg)' -w` + +#### Github action + +To install + +- `go install github.com/rhysd/actionlint/cmd/actionlint@latest` + +To run + +- `actionlint` + ### Cross-platform considerations - Ensure file/directory names do not exceed 32 characters in length to minimize filepath length issues on Windows. File/directory names should be shorter than this where possible. From f2c956e5eca02fa3b30cfe4f457b683747e929b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 11:09:02 -0400 Subject: [PATCH 14/55] Bump github.com/rs/zerolog from 1.29.0 to 1.30.0 (#186) Bumps [github.com/rs/zerolog](https://github.com/rs/zerolog) from 1.29.0 to 1.30.0. - [Release notes](https://github.com/rs/zerolog/releases) - [Commits](https://github.com/rs/zerolog/compare/v1.29.0...v1.30.0) --- updated-dependencies: - dependency-name: github.com/rs/zerolog dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: David Pokora Co-authored-by: anishnaik --- go.mod | 2 +- go.sum | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 83b9d462..5cac4f40 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/fxamacker/cbor v1.5.1 github.com/google/uuid v1.3.0 github.com/pkg/errors v0.9.1 - github.com/rs/zerolog v1.29.0 + github.com/rs/zerolog v1.30.0 github.com/shopspring/decimal v1.3.1 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 diff --git a/go.sum b/go.sum index 7721ec5e..439dd9db 100644 --- a/go.sum +++ b/go.sum @@ -42,7 +42,7 @@ github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcju github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -249,9 +249,9 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w= -github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c= +github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= From def06b26d560a3cbfd1c7bc1060b23d961d8d494 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 11:33:48 -0400 Subject: [PATCH 15/55] Bump github.com/google/uuid from 1.3.0 to 1.3.1 (#215) Bumps [github.com/google/uuid](https://github.com/google/uuid) from 1.3.0 to 1.3.1. - [Release notes](https://github.com/google/uuid/releases) - [Changelog](https://github.com/google/uuid/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/uuid/compare/v1.3.0...v1.3.1) --- updated-dependencies: - dependency-name: github.com/google/uuid dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: anishnaik --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5cac4f40..3c93f980 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Masterminds/semver v1.5.0 github.com/ethereum/go-ethereum v1.12.0 github.com/fxamacker/cbor v1.5.1 - github.com/google/uuid v1.3.0 + github.com/google/uuid v1.3.1 github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.30.0 github.com/shopspring/decimal v1.3.1 diff --git a/go.sum b/go.sum index 439dd9db..52bd7664 100644 --- a/go.sum +++ b/go.sum @@ -129,8 +129,8 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= From 4b2dacf786161510185de89764ecb7e1d9153358 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 12:08:10 -0400 Subject: [PATCH 16/55] Bump golang.org/x/crypto from 0.12.0 to 0.13.0 (#226) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.12.0 to 0.13.0. - [Commits](https://github.com/golang/crypto/compare/v0.12.0...v0.13.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: anishnaik --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 3c93f980..6486d2b8 100644 --- a/go.mod +++ b/go.mod @@ -13,10 +13,10 @@ require ( github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 - golang.org/x/crypto v0.12.0 + golang.org/x/crypto v0.13.0 golang.org/x/exp v0.0.0-20230206171751-46f607a40771 golang.org/x/net v0.14.0 - golang.org/x/sys v0.11.0 + golang.org/x/sys v0.12.0 ) require ( @@ -64,7 +64,7 @@ require ( github.com/tklauser/numcpus v0.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 52bd7664..7dd94a5a 100644 --- a/go.sum +++ b/go.sum @@ -321,8 +321,8 @@ golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= @@ -396,8 +396,8 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -405,8 +405,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From b4e7547bc5e7cf2fbfaac798d2ab3fc72b0eed39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Nov 2023 10:08:41 -0500 Subject: [PATCH 17/55] Bump github.com/rs/zerolog from 1.30.0 to 1.31.0 (#236) Bumps [github.com/rs/zerolog](https://github.com/rs/zerolog) from 1.30.0 to 1.31.0. - [Release notes](https://github.com/rs/zerolog/releases) - [Commits](https://github.com/rs/zerolog/compare/v1.30.0...v1.31.0) --- updated-dependencies: - dependency-name: github.com/rs/zerolog dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 6486d2b8..9246f409 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/fxamacker/cbor v1.5.1 github.com/google/uuid v1.3.1 github.com/pkg/errors v0.9.1 - github.com/rs/zerolog v1.30.0 + github.com/rs/zerolog v1.31.0 github.com/shopspring/decimal v1.3.1 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 @@ -47,7 +47,7 @@ require ( github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect diff --git a/go.sum b/go.sum index 7dd94a5a..53eff249 100644 --- a/go.sum +++ b/go.sum @@ -183,7 +183,6 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -191,8 +190,9 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -250,8 +250,8 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4 github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c= -github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w= +github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= +github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -396,6 +396,7 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= From f4f66059da62652eea12da3600a0893a38d7e29f Mon Sep 17 00:00:00 2001 From: anishnaik Date: Wed, 8 Nov 2023 11:45:18 -0500 Subject: [PATCH 18/55] add setuptools to pip install line (#252) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3722c69c..6af82f51 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -155,7 +155,7 @@ jobs: - name: Install Python dependencies run: | - pip3 install --no-cache-dir solc-select crytic-compile + pip3 install --no-cache-dir setuptools solc-select crytic-compile - name: Install solc run: | From 914a1447d58ef38f9a44048a80aa40dbd5489ce0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Nov 2023 12:24:31 -0500 Subject: [PATCH 19/55] Bump actions/checkout from 3 to 4 (#228) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: anishnaik --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6af82f51..c5c97e7d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: timeout-minutes: 10 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Speed up Go (Windows) if: runner.os == 'Windows' @@ -76,7 +76,7 @@ jobs: timeout-minutes: 10 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: @@ -116,7 +116,7 @@ jobs: timeout-minutes: 20 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Speed up Go, Python, Node (Windows) if: runner.os == 'Windows' From ae4c36e3b8d4c83952fa98b563988c0f7b72089f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Nov 2023 12:47:35 -0500 Subject: [PATCH 20/55] Bump golang.org/x/crypto from 0.13.0 to 0.14.0 (#240) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.13.0 to 0.14.0. - [Commits](https://github.com/golang/crypto/compare/v0.13.0...v0.14.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: anishnaik --- go.mod | 4 ++-- go.sum | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 9246f409..358eff17 100644 --- a/go.mod +++ b/go.mod @@ -13,10 +13,10 @@ require ( github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 - golang.org/x/crypto v0.13.0 + golang.org/x/crypto v0.14.0 golang.org/x/exp v0.0.0-20230206171751-46f607a40771 golang.org/x/net v0.14.0 - golang.org/x/sys v0.12.0 + golang.org/x/sys v0.13.0 ) require ( diff --git a/go.sum b/go.sum index 53eff249..5e083873 100644 --- a/go.sum +++ b/go.sum @@ -321,8 +321,8 @@ golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= @@ -396,9 +396,8 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= From 1f8afda4eb0dc143ee138c37c420800c46794b6f Mon Sep 17 00:00:00 2001 From: anishnaik Date: Wed, 8 Nov 2023 14:07:24 -0500 Subject: [PATCH 21/55] Fix `testAllContracts` behavior (#253) --- chain/test_chain.go | 10 ++++++---- chain/test_chain_deployments_tracer.go | 21 ++++++++++++--------- chain/test_chain_events.go | 4 ++++ chain/types/deployed_contract_bytecode.go | 4 ++++ fuzzing/config/config_defaults.go | 2 +- fuzzing/fuzzer_test.go | 1 + fuzzing/fuzzer_worker.go | 5 +++++ logging/logger_test.go | 2 +- 8 files changed, 34 insertions(+), 15 deletions(-) diff --git a/chain/test_chain.go b/chain/test_chain.go index a4466cc7..9e9ff4e2 100644 --- a/chain/test_chain.go +++ b/chain/test_chain.go @@ -856,8 +856,9 @@ func (t *TestChain) emitContractChangeEvents(reverting bool, messageResults ...* // this execution result is being committed to chain. if deploymentChange.Creation { err = t.Events.ContractDeploymentAddedEventEmitter.Publish(ContractDeploymentsAddedEvent{ - Chain: t, - Contract: deploymentChange.Contract, + Chain: t, + Contract: deploymentChange.Contract, + DynamicDeployment: deploymentChange.DynamicCreation, }) } else if deploymentChange.Destroyed { err = t.Events.ContractDeploymentRemovedEventEmitter.Publish(ContractDeploymentsRemovedEvent{ @@ -887,8 +888,9 @@ func (t *TestChain) emitContractChangeEvents(reverting bool, messageResults ...* }) } else if deploymentChange.Destroyed { err = t.Events.ContractDeploymentAddedEventEmitter.Publish(ContractDeploymentsAddedEvent{ - Chain: t, - Contract: deploymentChange.Contract, + Chain: t, + Contract: deploymentChange.Contract, + DynamicDeployment: deploymentChange.DynamicCreation, }) } if err != nil { diff --git a/chain/test_chain_deployments_tracer.go b/chain/test_chain_deployments_tracer.go index c2672aaa..d8bec11f 100644 --- a/chain/test_chain_deployments_tracer.go +++ b/chain/test_chain_deployments_tracer.go @@ -74,9 +74,10 @@ func (t *testChainDeploymentsTracer) CaptureStart(env *vm.EVM, from common.Addre InitBytecode: input, RuntimeBytecode: nil, }, - Creation: true, - SelfDestructed: false, - Destroyed: false, + Creation: true, + DynamicCreation: false, + SelfDestructed: false, + Destroyed: false, }) } } @@ -118,9 +119,10 @@ func (t *testChainDeploymentsTracer) CaptureEnter(typ vm.OpCode, from common.Add InitBytecode: input, RuntimeBytecode: nil, }, - Creation: true, - SelfDestructed: false, - Destroyed: false, + Creation: true, + DynamicCreation: true, + SelfDestructed: false, + Destroyed: false, }) } } @@ -158,9 +160,10 @@ func (t *testChainDeploymentsTracer) CaptureState(pc uint64, op vm.OpCode, gas, InitBytecode: nil, RuntimeBytecode: t.evm.StateDB.GetCode(scope.Contract.Address()), }, - Creation: false, - SelfDestructed: true, - Destroyed: t.selfDestructDestroysCode, + Creation: false, + DynamicCreation: false, + SelfDestructed: true, + Destroyed: t.selfDestructDestroysCode, }) } } diff --git a/chain/test_chain_events.go b/chain/test_chain_events.go index adc92fd4..49c4cf48 100644 --- a/chain/test_chain_events.go +++ b/chain/test_chain_events.go @@ -93,6 +93,10 @@ type ContractDeploymentsAddedEvent struct { // Contract defines information for the contract which was deployed to the Chain. Contract *types.DeployedContractBytecode + + // DynamicDeployment describes whether this contract deployment was dynamic (e.g. `c = new MyContract()`) or was + // because of a traditional transaction + DynamicDeployment bool } // ContractDeploymentsRemovedEvent describes an event where a contract has become unavailable on the TestChain, either diff --git a/chain/types/deployed_contract_bytecode.go b/chain/types/deployed_contract_bytecode.go index e32c76d1..9f49bef8 100644 --- a/chain/types/deployed_contract_bytecode.go +++ b/chain/types/deployed_contract_bytecode.go @@ -16,6 +16,10 @@ type DeployedContractBytecodeChange struct { // Destroyed are true. Creation bool + // DynamicCreation indicates whether the change made was a _dynamic_ contract creation. This cannot be true if + // Creation is false. + DynamicCreation bool + // SelfDestructed indicates whether the change made was due to a self-destruct instruction being executed. This // cannot be true if Creation is true. // Note: This may not be indicative of contract removal (as is the case with Destroyed), as proposed changes to diff --git a/fuzzing/config/config_defaults.go b/fuzzing/config/config_defaults.go index ecaf2a18..cf6cf515 100644 --- a/fuzzing/config/config_defaults.go +++ b/fuzzing/config/config_defaults.go @@ -55,7 +55,7 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) { TransactionGasLimit: 12_500_000, Testing: TestingConfig{ StopOnFailedTest: true, - StopOnFailedContractMatching: true, + StopOnFailedContractMatching: false, StopOnNoTests: true, TestAllContracts: false, TraceAll: false, diff --git a/fuzzing/fuzzer_test.go b/fuzzing/fuzzer_test.go index 55c845fd..7fdc4f47 100644 --- a/fuzzing/fuzzer_test.go +++ b/fuzzing/fuzzer_test.go @@ -398,6 +398,7 @@ func TestDeploymentsSelfDestruct(t *testing.T) { config.Fuzzing.DeploymentOrder = []string{"InnerDeploymentFactory"} config.Fuzzing.TestLimit = 500 // this test should expose a failure quickly. config.Fuzzing.Testing.StopOnNoTests = false + config.Fuzzing.Testing.TestAllContracts = true }, method: func(f *fuzzerTestContext) { // Subscribe to any mined block events globally. When receiving them, check contract changes for a diff --git a/fuzzing/fuzzer_worker.go b/fuzzing/fuzzer_worker.go index d8f01bed..27848a23 100644 --- a/fuzzing/fuzzer_worker.go +++ b/fuzzing/fuzzer_worker.go @@ -152,6 +152,11 @@ func (fw *FuzzerWorker) getNewCorpusCallSequenceWeight() *big.Int { // onChainContractDeploymentAddedEvent is the event callback used when the chain detects a new contract deployment. // It attempts bytecode matching and updates the list of deployed contracts the worker should use for fuzz testing. func (fw *FuzzerWorker) onChainContractDeploymentAddedEvent(event chain.ContractDeploymentsAddedEvent) error { + // Do not track the deployed contract if the contract deployment was a dynamic one and testAllContracts is false + if !fw.fuzzer.config.Fuzzing.Testing.TestAllContracts && event.DynamicDeployment { + return nil + } + // Add the contract address to our value set so our generator can use it in calls. fw.valueSet.AddAddress(event.Contract.Address) diff --git a/logging/logger_test.go b/logging/logger_test.go index 540e78e3..53e0d6a4 100644 --- a/logging/logger_test.go +++ b/logging/logger_test.go @@ -70,6 +70,6 @@ func TestDisabledColors(t *testing.T) { // Ensure that msg doesn't include colors afterwards prefix := fmt.Sprintf("%s %s", colors.LEFT_ARROW, "foo") - _, ok := strings.CutPrefix(buf.String(), prefix) + _, _, ok := strings.Cut(buf.String(), prefix) assert.True(t, ok) } From 8cc3957a6ebd8004a59a4a47749ea09bcbb789c9 Mon Sep 17 00:00:00 2001 From: Damilola Edwards Date: Wed, 8 Nov 2023 21:03:12 +0100 Subject: [PATCH 22/55] Open non-zero coverage reports in corpus by default (#243) Co-authored-by: anishnaik --- fuzzing/coverage/report_template.gohtml | 35 +++++++++++++++---------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/fuzzing/coverage/report_template.gohtml b/fuzzing/coverage/report_template.gohtml index cab7c07e..d7993e81 100644 --- a/fuzzing/coverage/report_template.gohtml +++ b/fuzzing/coverage/report_template.gohtml @@ -68,6 +68,9 @@ .collapsible-active:after { content: "\2212"; } + .collapsible-active + .collapsible-container { + max-height: none; + } .collapsible-container { margin-bottom: 5px; max-height: 0; @@ -150,12 +153,21 @@ {{$linesCoveredPercentInt := percentageInt $linesCovered $linesActive}} {{/* Output a collapsible header/container for each source*/}} - + {{if not $linesCoveredPercentInt}} + + {{else}} + + {{end}}

@@ -214,17 +226,12 @@ for (i = 0; i < collapsibleHeaders.length; i++) { collapsibleHeaders[i].addEventListener("click", function() { this.classList.toggle("collapsible-active"); - const collapsibleContainer = this.nextElementSibling; - if (collapsibleContainer.style.maxHeight){ - collapsibleContainer.style.maxHeight = null; - } else { - collapsibleContainer.style.maxHeight = collapsibleContainer.scrollHeight + "px"; - } + }); } - // If there's only one item, expand it by default. - if (collapsibleHeaders.length === 1) { + // If there's only one item and that item has 0% coverage, expand it by default. + if (collapsibleHeaders.length === 1 && !collapsibleHeaders.className.contains("collapsible-active")) { collapsibleHeaders[0].click(); } From 9ad9ff2fc211274d3cd0e4504439449a259c8d30 Mon Sep 17 00:00:00 2001 From: anishnaik Date: Thu, 9 Nov 2023 15:51:59 -0500 Subject: [PATCH 23/55] Fix `warp` cheatcode (#255) * fix warp cheatcode * revert if warp input value is greater than type(uint64).max * Make max uint64 a global value --- chain/standard_cheat_code_contract.go | 20 +++++++++++++++---- .../contracts/cheat_codes/vm/warp.sol | 10 +++++++++- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/chain/standard_cheat_code_contract.go b/chain/standard_cheat_code_contract.go index 141bf204..88b8f6d2 100644 --- a/chain/standard_cheat_code_contract.go +++ b/chain/standard_cheat_code_contract.go @@ -17,6 +17,9 @@ import ( // StandardCheatcodeContractAddress is the address for the standard cheatcode contract var StandardCheatcodeContractAddress = common.HexToAddress("0x7109709ECfa91a80626fF3989D68f67F5b1DD12D") +// MaxUint64 holds the max value an uint64 can take +var _, MaxUint64 = utils.GetIntegerConstraints(false, 64) + // getStandardCheatCodeContract obtains a CheatCodeContract which implements common cheat codes. // Returns the precompiled contract, or an error if one occurs. func getStandardCheatCodeContract(tracer *cheatCodeTracer) (*CheatCodeContract, error) { @@ -67,13 +70,22 @@ func getStandardCheatCodeContract(tracer *cheatCodeTracer) (*CheatCodeContract, // Warp: Sets VM timestamp contract.addMethod( - "warp", abi.Arguments{{Type: typeUint64}}, abi.Arguments{}, + "warp", abi.Arguments{{Type: typeUint256}}, abi.Arguments{}, func(tracer *cheatCodeTracer, inputs []any) ([]any, *cheatCodeRawReturnData) { // Maintain our changes until the transaction exits. - original := tracer.evm.Context.Time - tracer.evm.Context.Time = inputs[0].(uint64) + originalTime := tracer.evm.Context.Time + + // Retrieve new timestamp and make sure it is LEQ max value of an uint64 + newTime := inputs[0].(*big.Int) + if newTime.Cmp(MaxUint64) > 0 { + return nil, cheatCodeRevertData([]byte("warp: timestamp exceeds max value of type(uint64).max")) + } + + // Set the time + tracer.evm.Context.Time = newTime.Uint64() tracer.CurrentCallFrame().onTopFrameExitRestoreHooks.Push(func() { - tracer.evm.Context.Time = original + // Reset the time + tracer.evm.Context.Time = originalTime }) return nil, nil }, diff --git a/fuzzing/testdata/contracts/cheat_codes/vm/warp.sol b/fuzzing/testdata/contracts/cheat_codes/vm/warp.sol index c70dcf13..536a49ee 100644 --- a/fuzzing/testdata/contracts/cheat_codes/vm/warp.sol +++ b/fuzzing/testdata/contracts/cheat_codes/vm/warp.sol @@ -1,6 +1,6 @@ // This test ensures that the block timestamp can be set with cheat codes interface CheatCodes { - function warp(uint64) external; + function warp(uint256) external; } contract TestContract { @@ -15,5 +15,13 @@ contract TestContract { assert(block.timestamp == 7); cheats.warp(9); assert(block.timestamp == 9); + + // Ensure that a value greater than type(uint64).max will cause warp to revert + // This is not the best way to test it but gets the job done + try cheats.warp(type(uint64).max + 1) { + assert(false); + } catch { + assert(true); + } } } From 7065f74d883b9368440226c7734e56559acc8bee Mon Sep 17 00:00:00 2001 From: anishnaik Date: Mon, 12 Feb 2024 21:08:05 -0500 Subject: [PATCH 24/55] Explicitly reset the state trie's cache to prevent mem leak (#290) --- chain/test_chain.go | 8 ++++++++ fuzzing/fuzzer_worker.go | 3 +++ go.mod | 2 +- go.sum | 6 ++++-- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/chain/test_chain.go b/chain/test_chain.go index 9e9ff4e2..a23ac5d9 100644 --- a/chain/test_chain.go +++ b/chain/test_chain.go @@ -199,6 +199,14 @@ func NewTestChain(genesisAlloc core.GenesisAlloc, testChainConfig *config.TestCh return chain, nil } +// Close will release any objects from the TestChain that must be _explicitly_ released. Currently, the one object that +// must be explicitly released is the stateDB trie's underlying cache. This cache, if not released, prevents the TestChain +// object from being freed by the garbage collector and causes a severe memory leak. +func (t *TestChain) Close() { + // Reset the state DB's cache + t.stateDatabase.TrieDB().ResetCache() +} + // Clone recreates the current TestChain state into a new instance. This simply reconstructs the block/chain state // but does not perform any other API-related changes such as adding additional tracers the original had. Additionally, // this does not clone pending blocks. The provided method, if non-nil, is used as callback to provide an intermediate diff --git a/fuzzing/fuzzer_worker.go b/fuzzing/fuzzer_worker.go index 27848a23..a7ac3f4d 100644 --- a/fuzzing/fuzzer_worker.go +++ b/fuzzing/fuzzer_worker.go @@ -532,6 +532,9 @@ func (fw *FuzzerWorker) run(baseTestChain *chain.TestChain) (bool, error) { return false, err } + // Defer the closing of the test chain object + defer fw.chain.Close() + // Emit an event indicating the worker has setup its chain. err = fw.Events.FuzzerWorkerChainSetup.Publish(FuzzerWorkerChainSetupEvent{ Worker: fw, diff --git a/go.mod b/go.mod index 358eff17..8e16ad95 100644 --- a/go.mod +++ b/go.mod @@ -70,4 +70,4 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/ethereum/go-ethereum => github.com/crytic/medusa-geth v0.0.0-20230811005223-cee04520a2f9 +replace github.com/ethereum/go-ethereum => github.com/crytic/medusa-geth v0.0.0-20240209160711-dfded09070ca diff --git a/go.sum b/go.sum index 5e083873..4dad80c9 100644 --- a/go.sum +++ b/go.sum @@ -46,8 +46,8 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/crytic/medusa-geth v0.0.0-20230811005223-cee04520a2f9 h1:BUuL3h23IdVSmyq3I7LVUYRInKgXShMLKElYaFgn4RM= -github.com/crytic/medusa-geth v0.0.0-20230811005223-cee04520a2f9/go.mod h1:/oo2X/dZLJjf2mJ6YT9wcWxa4nNJDBKDBU6sFIpx1Gs= +github.com/crytic/medusa-geth v0.0.0-20240209160711-dfded09070ca h1:oGRWjrs9pStgFbnk1K5aLTQoY/aeO5zkzqADZH/maFw= +github.com/crytic/medusa-geth v0.0.0-20240209160711-dfded09070ca/go.mod h1:/oo2X/dZLJjf2mJ6YT9wcWxa4nNJDBKDBU6sFIpx1Gs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -396,6 +396,8 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= From 7f0c072f3c2dd27dc051a74d5cc7ec3c412268b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 21:24:26 -0500 Subject: [PATCH 25/55] Bump github.com/spf13/cobra from 1.7.0 to 1.8.0 (#257) Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.7.0 to 1.8.0. - [Release notes](https://github.com/spf13/cobra/releases) - [Commits](https://github.com/spf13/cobra/compare/v1.7.0...v1.8.0) --- updated-dependencies: - dependency-name: github.com/spf13/cobra dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: David Pokora --- go.mod | 2 +- go.sum | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 8e16ad95..c4981b78 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.31.0 github.com/shopspring/decimal v1.3.1 - github.com/spf13/cobra v1.7.0 + github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 golang.org/x/crypto v0.14.0 diff --git a/go.sum b/go.sum index 4dad80c9..c7a0f633 100644 --- a/go.sum +++ b/go.sum @@ -44,7 +44,7 @@ github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8Nz github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/crytic/medusa-geth v0.0.0-20240209160711-dfded09070ca h1:oGRWjrs9pStgFbnk1K5aLTQoY/aeO5zkzqADZH/maFw= github.com/crytic/medusa-geth v0.0.0-20240209160711-dfded09070ca/go.mod h1:/oo2X/dZLJjf2mJ6YT9wcWxa4nNJDBKDBU6sFIpx1Gs= @@ -267,8 +267,8 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= From 6313c92c88060cc3d29a0d55f102e0946ba8fd8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 21:39:05 -0500 Subject: [PATCH 26/55] Bump github.com/google/uuid from 1.3.1 to 1.6.0 (#288) Bumps [github.com/google/uuid](https://github.com/google/uuid) from 1.3.1 to 1.6.0. - [Release notes](https://github.com/google/uuid/releases) - [Changelog](https://github.com/google/uuid/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/uuid/compare/v1.3.1...v1.6.0) --- updated-dependencies: - dependency-name: github.com/google/uuid dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c4981b78..d0124e2a 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Masterminds/semver v1.5.0 github.com/ethereum/go-ethereum v1.12.0 github.com/fxamacker/cbor v1.5.1 - github.com/google/uuid v1.3.1 + github.com/google/uuid v1.6.0 github.com/pkg/errors v0.9.1 github.com/rs/zerolog v1.31.0 github.com/shopspring/decimal v1.3.1 diff --git a/go.sum b/go.sum index c7a0f633..596b00b0 100644 --- a/go.sum +++ b/go.sum @@ -129,8 +129,8 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= From b1663639d4af363c314d460e019887e67b6b925c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 21:51:07 -0500 Subject: [PATCH 27/55] Bump golang.org/x/net from 0.14.0 to 0.21.0 (#291) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.14.0 to 0.21.0. - [Commits](https://github.com/golang/net/compare/v0.14.0...v0.21.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: David Pokora --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index d0124e2a..27b9f68c 100644 --- a/go.mod +++ b/go.mod @@ -13,10 +13,10 @@ require ( github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 - golang.org/x/crypto v0.14.0 + golang.org/x/crypto v0.19.0 golang.org/x/exp v0.0.0-20230206171751-46f607a40771 - golang.org/x/net v0.14.0 - golang.org/x/sys v0.13.0 + golang.org/x/net v0.21.0 + golang.org/x/sys v0.17.0 ) require ( @@ -64,7 +64,7 @@ require ( github.com/tklauser/numcpus v0.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/text v0.14.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 596b00b0..4c98ad4e 100644 --- a/go.sum +++ b/go.sum @@ -321,8 +321,8 @@ golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= @@ -353,8 +353,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -398,8 +398,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -407,8 +407,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 38ed335144b9dcd97c3a946694c75b368909cca5 Mon Sep 17 00:00:00 2001 From: David Pokora Date: Mon, 12 Feb 2024 21:19:43 -0800 Subject: [PATCH 28/55] Fix the copy length based panic in the parseBytes32 cheatcode (#293) --- chain/standard_cheat_code_contract.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain/standard_cheat_code_contract.go b/chain/standard_cheat_code_contract.go index 88b8f6d2..79c2d3ad 100644 --- a/chain/standard_cheat_code_contract.go +++ b/chain/standard_cheat_code_contract.go @@ -452,7 +452,7 @@ func getStandardCheatCodeContract(tracer *cheatCodeTracer) (*CheatCodeContract, // Use a fixed array and copy the data over var bArray [32]byte - copy(bArray[:], bSlice[:32]) + copy(bArray[:], bSlice) return []any{bArray}, nil }, From 6c13d2ae9de078ae6352b813a45c774b2731c468 Mon Sep 17 00:00:00 2001 From: anishnaik Date: Tue, 20 Feb 2024 12:28:41 -0500 Subject: [PATCH 29/55] Enable all testing modes by default, update property mode testing, improve UX, allow for contracts to have starting balances, and fix coverage panic (#216) * - merged all three testing modes - created testing provider utils file to evaluate whether an abi.method is an optimization / property test - updated fuzzer tests - made default property test prefix "invariant_" * enable all testing modes, update fuzzer tests * add verification for config * improve config-related errors * update deploymentOrder to targetContracts * update edge case where coverage is 0/0 lines * add support for payable constructors * linting * fix bug * fix panic and improve return data printing in execution trace * encode bytes and byteX as hex strings * fix console * updates from PR review * change config language --- cmd/fuzz_flags.go | 49 ++---- cmd/init_flags.go | 10 +- fuzzing/config/config.go | 88 +++++++--- fuzzing/config/config_defaults.go | 30 ++-- fuzzing/config/gen_fuzzing_config.go | 148 +++++++++++++++++ fuzzing/coverage/coverage_maps.go | 6 +- fuzzing/coverage/report_generation.go | 4 +- fuzzing/executiontracer/execution_trace.go | 9 +- fuzzing/fuzzer.go | 45 +++-- fuzzing/fuzzer_test.go | 155 +++++++++++------- fuzzing/test_case_assertion_provider.go | 19 ++- fuzzing/test_case_optimization_provider.go | 31 ++-- fuzzing/test_case_property_provider.go | 28 +--- .../assertions/assert_and_property_test.sol | 2 +- .../contracts/chain/tx_out_of_gas.sol | 2 +- .../specific_call_sequence.sol | 2 +- .../deploy_payable_constructors.sol | 18 ++ .../deployments/deployment_order.sol | 4 +- .../deployments/deployment_with_args.sol | 8 +- .../deployments/inner_deployment.sol | 2 +- .../inner_deployment_on_construction.sol | 2 +- .../deployments/inner_inner_deployment.sol | 2 +- .../deployments/internal_library.sol | 2 +- .../contracts/deployments/testing_scope.sol | 4 +- .../value_generation/generate_all_types.sol | 2 +- .../value_generation/match_addr_contract.sol | 2 +- .../value_generation/match_addr_exact.sol | 2 +- .../value_generation/match_addr_sender.sol | 2 +- .../value_generation/match_ints_xy.sol | 2 +- .../value_generation/match_payable_xy.sol | 2 +- .../value_generation/match_string_exact.sol | 2 +- .../value_generation/match_structs_xy.sol | 2 +- .../value_generation/match_uints_xy.sol | 2 +- .../vm_tests/block_hash_store_check.sol | 2 +- .../vm_tests/block_number_increasing.sol | 2 +- .../vm_tests/block_timestamp_increasing.sol | 2 +- fuzzing/utils/fuzz_method_utils.go | 34 ++++ fuzzing/valuegeneration/abi_values.go | 12 +- go.mod | 4 + go.sum | 21 +++ logging/logger.go | 8 +- 41 files changed, 530 insertions(+), 243 deletions(-) create mode 100644 fuzzing/config/gen_fuzzing_config.go create mode 100644 fuzzing/testdata/contracts/deployments/deploy_payable_constructors.sol create mode 100644 fuzzing/utils/fuzz_method_utils.go diff --git a/cmd/fuzz_flags.go b/cmd/fuzz_flags.go index e1999804..9e1c9d37 100644 --- a/cmd/fuzz_flags.go +++ b/cmd/fuzz_flags.go @@ -21,8 +21,8 @@ func addFuzzFlags() error { // Config file fuzzCmd.Flags().String("config", "", "path to config file") - // Target - fuzzCmd.Flags().String("target", "", TargetFlagDescription) + // Compilation Target + fuzzCmd.Flags().String("compilation-target", "", TargetFlagDescription) // Number of workers fuzzCmd.Flags().Int("workers", 0, @@ -40,14 +40,13 @@ func addFuzzFlags() error { fuzzCmd.Flags().Int("seq-len", 0, fmt.Sprintf("maximum transactions to run in sequence (unless a config file is provided, default is %d)", defaultConfig.Fuzzing.CallSequenceLength)) - // Deployment order - fuzzCmd.Flags().StringSlice("deployment-order", []string{}, - fmt.Sprintf("order in which to deploy target contracts (unless a config file is provided, default is %v)", defaultConfig.Fuzzing.DeploymentOrder)) + // Target contracts + fuzzCmd.Flags().StringSlice("target-contracts", []string{}, + fmt.Sprintf("target contracts for fuzz testing (unless a config file is provided, default is %v)", defaultConfig.Fuzzing.TargetContracts)) // Corpus directory - // TODO: Update description when we add "coverage reports" feature fuzzCmd.Flags().String("corpus-dir", "", - fmt.Sprintf("directory path for corpus items (unless a config file is provided, default is %q)", defaultConfig.Fuzzing.CorpusDirectory)) + fmt.Sprintf("directory path for corpus items and coverage reports (unless a config file is provided, default is %q)", defaultConfig.Fuzzing.CorpusDirectory)) // Senders fuzzCmd.Flags().StringSlice("senders", []string{}, @@ -57,14 +56,6 @@ func addFuzzFlags() error { fuzzCmd.Flags().String("deployer", "", "account address used to deploy contracts") - // Assertion mode - fuzzCmd.Flags().Bool("assertion-mode", false, - fmt.Sprintf("enable assertion mode (unless a config file is provided, default is %t)", defaultConfig.Fuzzing.Testing.AssertionTesting.Enabled)) - - // Optimization mode - fuzzCmd.Flags().Bool("optimization-mode", false, - fmt.Sprintf("enable optimization mode (unless a config file is provided, default is %t)", defaultConfig.Fuzzing.Testing.OptimizationTesting.Enabled)) - // Trace all fuzzCmd.Flags().Bool("trace-all", false, fmt.Sprintf("print the execution trace for every element in a shrunken call sequence instead of only the last element (unless a config file is provided, default is %t)", defaultConfig.Fuzzing.Testing.TraceAll)) @@ -79,10 +70,10 @@ func addFuzzFlags() error { func updateProjectConfigWithFuzzFlags(cmd *cobra.Command, projectConfig *config.ProjectConfig) error { var err error - // If --target was used - if cmd.Flags().Changed("target") { + // If --compilation-target was used + if cmd.Flags().Changed("compilation-target") { // Get the new target - newTarget, err := cmd.Flags().GetString("target") + newTarget, err := cmd.Flags().GetString("compilation-target") if err != nil { return err } @@ -125,9 +116,9 @@ func updateProjectConfigWithFuzzFlags(cmd *cobra.Command, projectConfig *config. } } - // Update deployment order - if cmd.Flags().Changed("deployment-order") { - projectConfig.Fuzzing.DeploymentOrder, err = cmd.Flags().GetStringSlice("deployment-order") + // Update target contracts + if cmd.Flags().Changed("target-contracts") { + projectConfig.Fuzzing.TargetContracts, err = cmd.Flags().GetStringSlice("target-contracts") if err != nil { return err } @@ -157,22 +148,6 @@ func updateProjectConfigWithFuzzFlags(cmd *cobra.Command, projectConfig *config. } } - // Update assertion mode enablement - if cmd.Flags().Changed("assertion-mode") { - projectConfig.Fuzzing.Testing.AssertionTesting.Enabled, err = cmd.Flags().GetBool("assertion-mode") - if err != nil { - return err - } - } - - // Update optimization mode enablement - if cmd.Flags().Changed("optimization-mode") { - projectConfig.Fuzzing.Testing.OptimizationTesting.Enabled, err = cmd.Flags().GetBool("optimization-mode") - if err != nil { - return err - } - } - // Update trace all enablement if cmd.Flags().Changed("trace-all") { projectConfig.Fuzzing.Testing.TraceAll, err = cmd.Flags().GetBool("trace-all") diff --git a/cmd/init_flags.go b/cmd/init_flags.go index 40c3499a..dfe6d8d6 100644 --- a/cmd/init_flags.go +++ b/cmd/init_flags.go @@ -10,18 +10,18 @@ func addInitFlags() error { // Output path for configuration initCmd.Flags().String("out", "", "output path for the new project configuration file") - // Target file / directory - initCmd.Flags().String("target", "", TargetFlagDescription) + // Target file / directory for compilation + initCmd.Flags().String("compilation-target", "", TargetFlagDescription) return nil } // updateProjectConfigWithInitFlags will update the given projectConfig with any CLI arguments that were provided to the init command func updateProjectConfigWithInitFlags(cmd *cobra.Command, projectConfig *config.ProjectConfig) error { - // If --target was used - if cmd.Flags().Changed("target") { + // If --compilation-target was used + if cmd.Flags().Changed("compilation-target") { // Get the new target - newTarget, err := cmd.Flags().GetString("target") + newTarget, err := cmd.Flags().GetString("compilation-target") if err != nil { return err } diff --git a/fuzzing/config/config.go b/fuzzing/config/config.go index 561a091f..cfdde41d 100644 --- a/fuzzing/config/config.go +++ b/fuzzing/config/config.go @@ -3,15 +3,21 @@ package config import ( "encoding/json" "errors" - "os" - "github.com/crytic/medusa/chain/config" - "github.com/rs/zerolog" - "github.com/crytic/medusa/compilation" + "github.com/crytic/medusa/logging" "github.com/crytic/medusa/utils" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/rs/zerolog" + "math/big" + "os" ) +// The following directives will be picked up by the `go generate` command to generate JSON marshaling code from +// templates defined below. They should be preserved for re-use in case we change our structures. +//go:generate go get github.com/fjl/gencodec +//go:generate go run github.com/fjl/gencodec -type FuzzingConfig -field-override fuzzingConfigMarshaling -out gen_fuzzing_config.go + type ProjectConfig struct { // Fuzzing describes the configuration used in fuzzing campaigns. Fuzzing FuzzingConfig `json:"fuzzing"` @@ -58,10 +64,15 @@ type FuzzingConfig struct { // CoverageEnabled describes whether to use coverage-guided fuzzing CoverageEnabled bool `json:"coverageEnabled"` - // DeploymentOrder determines the order in which the contracts should be deployed - DeploymentOrder []string `json:"deploymentOrder"` + // TargetContracts are the target contracts for fuzz testing + TargetContracts []string `json:"targetContracts"` + + // TargetContractsBalances holds the amount of wei that should be sent during deployment for one or more contracts in + // TargetContracts + TargetContractsBalances []*big.Int `json:"targetContractsBalances"` - // Constructor arguments for contracts deployment. It is available only in init mode + // ConstructorArgs holds the constructor arguments for TargetContracts deployments. It is available via the project + // configuration ConstructorArgs map[string]map[string]any `json:"constructorArgs"` // DeployerAddress describe the account address to be used to deploy contracts. @@ -93,6 +104,13 @@ type FuzzingConfig struct { TestChainConfig config.TestChainConfig `json:"chainConfig"` } +// fuzzingConfigMarshaling is a structure that overrides field types during JSON marshaling. It allows FuzzingConfig to +// have its custom marshaling methods auto-generated and will handle type conversions for serialization purposes. +// For example, this enables serialization of big.Int but specifying a different field type to control serialization. +type fuzzingConfigMarshaling struct { + TargetContractsBalances []*hexutil.Big +} + // TestingConfig describes the configuration options used for testing type TestingConfig struct { // StopOnFailedTest describes whether the fuzzing.Fuzzer should stop after detecting the first failed test. @@ -119,7 +137,7 @@ type TestingConfig struct { AssertionTesting AssertionTestingConfig `json:"assertionTesting"` // PropertyTesting describes the configuration used for property testing. - PropertyTesting PropertyTestConfig `json:"propertyTesting"` + PropertyTesting PropertyTestingConfig `json:"propertyTesting"` // OptimizationTesting describes the configuration used for optimization testing. OptimizationTesting OptimizationTestingConfig `json:"optimizationTesting"` @@ -133,13 +151,12 @@ type AssertionTestingConfig struct { // TestViewMethods dictates whether constant/pure/view methods should be tested. TestViewMethods bool `json:"testViewMethods"` - // AssertionModes describes the various panic codes that can be enabled and be treated as a "failing case" - AssertionModes AssertionModesConfig `json:"assertionModes"` + // PanicCodeConfig describes the various panic codes that can be enabled and be treated as a "failing case" + PanicCodeConfig PanicCodeConfig `json:"panicCodeConfig"` } -// AssertionModesConfig describes the configuration options for the various modes that can be enabled for assertion -// testing -type AssertionModesConfig struct { +// PanicCodeConfig describes the various panic codes that can be enabled and be treated as a failing assertion test +type PanicCodeConfig struct { // FailOnCompilerInsertedPanic describes whether a generic compiler inserted panic should be treated as a failing case FailOnCompilerInsertedPanic bool `json:"failOnCompilerInsertedPanic"` @@ -171,8 +188,8 @@ type AssertionModesConfig struct { FailOnCallUninitializedVariable bool `json:"failOnCallUninitializedVariable"` } -// PropertyTestConfig describes the configuration options used for property testing -type PropertyTestConfig struct { +// PropertyTestingConfig describes the configuration options used for property testing +type PropertyTestingConfig struct { // Enabled describes whether testing is enabled. Enabled bool `json:"enabled"` @@ -263,6 +280,12 @@ func (p *ProjectConfig) WriteToFile(path string) error { // Validate validates that the ProjectConfig meets certain requirements. // Returns an error if one occurs. func (p *ProjectConfig) Validate() error { + // Create logger instance if global logger is available + logger := logging.NewLogger(zerolog.Disabled) + if logging.GlobalLogger != nil { + logger = logging.GlobalLogger.NewSubLogger("module", "fuzzer config") + } + // Verify the worker count is a positive number. if p.Fuzzing.Workers <= 0 { return errors.New("project configuration must specify a positive number for the worker count") @@ -270,7 +293,7 @@ func (p *ProjectConfig) Validate() error { // Verify that the sequence length is a positive number if p.Fuzzing.CallSequenceLength <= 0 { - return errors.New("project configuration must specify a positive number for the transaction sequence length") + return errors.New("project configuration must specify a positive number for the transaction sequence lengt") } // Verify the worker reset limit is a positive number @@ -278,12 +301,30 @@ func (p *ProjectConfig) Validate() error { return errors.New("project configuration must specify a positive number for the worker reset limit") } + // Verify timeout + if p.Fuzzing.Timeout < 0 { + return errors.New("project configuration must specify a positive number for the timeout") + } + // Verify gas limits are appropriate if p.Fuzzing.BlockGasLimit < p.Fuzzing.TransactionGasLimit { return errors.New("project configuration must specify a block gas limit which is not less than the transaction gas limit") } if p.Fuzzing.BlockGasLimit == 0 || p.Fuzzing.TransactionGasLimit == 0 { - return errors.New("project configuration must specify a block and transaction gas limit which is non-zero") + return errors.New("project configuration must specify a block and transaction gas limit which are non-zero") + } + + // Log warning if max block delay is zero + if p.Fuzzing.MaxBlockNumberDelay == 0 { + logger.Warn("The maximum block number delay is set to zero. Please be aware that transactions will " + + "always be fit in the same block until the block gas limit is reached and that the block number will always " + + "increment by one.") + } + + // Log warning if max timestamp delay is zero + if p.Fuzzing.MaxBlockTimestampDelay == 0 { + logger.Warn("The maximum timestamp delay is set to zero. Please be aware that block time jumps will " + + "always be exactly one.") } // Verify that senders are well-formed addresses @@ -296,17 +337,10 @@ func (p *ProjectConfig) Validate() error { return errors.New("project configuration must specify only a well-formed deployer address") } - // Verify property testing fields. - if p.Fuzzing.Testing.PropertyTesting.Enabled { - // Test prefixes must be supplied if property testing is enabled. - if len(p.Fuzzing.Testing.PropertyTesting.TestPrefixes) == 0 { - return errors.New("project configuration must specify test name prefixes if property testing is enabled") - } - } - // Ensure that the log level is a valid one - if _, err := zerolog.ParseLevel(p.Logging.Level.String()); err != nil { - return err + level, err := zerolog.ParseLevel(p.Logging.Level.String()) + if err != nil || level == zerolog.FatalLevel { + return errors.New("project config must specify a valid log level (trace, debug, info, warn, error, or panic)") } return nil diff --git a/fuzzing/config/config_defaults.go b/fuzzing/config/config_defaults.go index cf6cf515..51e79ffb 100644 --- a/fuzzing/config/config_defaults.go +++ b/fuzzing/config/config_defaults.go @@ -4,6 +4,7 @@ import ( testChainConfig "github.com/crytic/medusa/chain/config" "github.com/crytic/medusa/compilation" "github.com/rs/zerolog" + "math/big" ) // GetDefaultProjectConfig obtains a default configuration for a project. It populates a default compilation config @@ -32,17 +33,18 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) { // Create a project configuration projectConfig := &ProjectConfig{ Fuzzing: FuzzingConfig{ - Workers: 10, - WorkerResetLimit: 50, - Timeout: 0, - TestLimit: 0, - CallSequenceLength: 100, - DeploymentOrder: []string{}, - ConstructorArgs: map[string]map[string]any{}, - CorpusDirectory: "", HtmlReportFile: "coverage_report.html", JsonReportFile: "coverage_report.json", - CoverageEnabled: true, + Workers: 10, + WorkerResetLimit: 50, + Timeout: 0, + TestLimit: 0, + CallSequenceLength: 100, + TargetContracts: []string{}, + TargetContractsBalances: []*big.Int{}, + ConstructorArgs: map[string]map[string]any{}, + CorpusDirectory: "", + CoverageEnabled: true, SenderAddresses: []string{ "0x10000", "0x20000", @@ -60,20 +62,20 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) { TestAllContracts: false, TraceAll: false, AssertionTesting: AssertionTestingConfig{ - Enabled: false, + Enabled: true, TestViewMethods: false, - AssertionModes: AssertionModesConfig{ + PanicCodeConfig: PanicCodeConfig{ FailOnAssertion: true, }, }, - PropertyTesting: PropertyTestConfig{ + PropertyTesting: PropertyTestingConfig{ Enabled: true, TestPrefixes: []string{ - "fuzz_", + "property_", }, }, OptimizationTesting: OptimizationTestingConfig{ - Enabled: false, + Enabled: true, TestPrefixes: []string{ "optimize_", }, diff --git a/fuzzing/config/gen_fuzzing_config.go b/fuzzing/config/gen_fuzzing_config.go new file mode 100644 index 00000000..5f1de304 --- /dev/null +++ b/fuzzing/config/gen_fuzzing_config.go @@ -0,0 +1,148 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package config + +import ( + "encoding/json" + "math/big" + + "github.com/crytic/medusa/chain/config" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ = (*fuzzingConfigMarshaling)(nil) + +// MarshalJSON marshals as JSON. +func (f FuzzingConfig) MarshalJSON() ([]byte, error) { + type FuzzingConfig struct { + Workers int `json:"workers"` + WorkerResetLimit int `json:"workerResetLimit"` + Timeout int `json:"timeout"` + TestLimit uint64 `json:"testLimit"` + CallSequenceLength int `json:"callSequenceLength"` + CorpusDirectory string `json:"corpusDirectory"` + CoverageEnabled bool `json:"coverageEnabled"` + TargetContracts []string `json:"targetContracts"` + TargetContractsBalances []*hexutil.Big `json:"targetContractsBalances"` + ConstructorArgs map[string]map[string]any `json:"constructorArgs"` + DeployerAddress string `json:"deployerAddress"` + SenderAddresses []string `json:"senderAddresses"` + MaxBlockNumberDelay uint64 `json:"blockNumberDelayMax"` + MaxBlockTimestampDelay uint64 `json:"blockTimestampDelayMax"` + BlockGasLimit uint64 `json:"blockGasLimit"` + TransactionGasLimit uint64 `json:"transactionGasLimit"` + Testing TestingConfig `json:"testing"` + TestChainConfig config.TestChainConfig `json:"chainConfig"` + } + var enc FuzzingConfig + enc.Workers = f.Workers + enc.WorkerResetLimit = f.WorkerResetLimit + enc.Timeout = f.Timeout + enc.TestLimit = f.TestLimit + enc.CallSequenceLength = f.CallSequenceLength + enc.CorpusDirectory = f.CorpusDirectory + enc.CoverageEnabled = f.CoverageEnabled + enc.TargetContracts = f.TargetContracts + if f.TargetContractsBalances != nil { + enc.TargetContractsBalances = make([]*hexutil.Big, len(f.TargetContractsBalances)) + for k, v := range f.TargetContractsBalances { + enc.TargetContractsBalances[k] = (*hexutil.Big)(v) + } + } + enc.ConstructorArgs = f.ConstructorArgs + enc.DeployerAddress = f.DeployerAddress + enc.SenderAddresses = f.SenderAddresses + enc.MaxBlockNumberDelay = f.MaxBlockNumberDelay + enc.MaxBlockTimestampDelay = f.MaxBlockTimestampDelay + enc.BlockGasLimit = f.BlockGasLimit + enc.TransactionGasLimit = f.TransactionGasLimit + enc.Testing = f.Testing + enc.TestChainConfig = f.TestChainConfig + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (f *FuzzingConfig) UnmarshalJSON(input []byte) error { + type FuzzingConfig struct { + Workers *int `json:"workers"` + WorkerResetLimit *int `json:"workerResetLimit"` + Timeout *int `json:"timeout"` + TestLimit *uint64 `json:"testLimit"` + CallSequenceLength *int `json:"callSequenceLength"` + CorpusDirectory *string `json:"corpusDirectory"` + CoverageEnabled *bool `json:"coverageEnabled"` + TargetContracts []string `json:"targetContracts"` + TargetContractsBalances []*hexutil.Big `json:"targetContractsBalances"` + ConstructorArgs map[string]map[string]any `json:"constructorArgs"` + DeployerAddress *string `json:"deployerAddress"` + SenderAddresses []string `json:"senderAddresses"` + MaxBlockNumberDelay *uint64 `json:"blockNumberDelayMax"` + MaxBlockTimestampDelay *uint64 `json:"blockTimestampDelayMax"` + BlockGasLimit *uint64 `json:"blockGasLimit"` + TransactionGasLimit *uint64 `json:"transactionGasLimit"` + Testing *TestingConfig `json:"testing"` + TestChainConfig *config.TestChainConfig `json:"chainConfig"` + } + var dec FuzzingConfig + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Workers != nil { + f.Workers = *dec.Workers + } + if dec.WorkerResetLimit != nil { + f.WorkerResetLimit = *dec.WorkerResetLimit + } + if dec.Timeout != nil { + f.Timeout = *dec.Timeout + } + if dec.TestLimit != nil { + f.TestLimit = *dec.TestLimit + } + if dec.CallSequenceLength != nil { + f.CallSequenceLength = *dec.CallSequenceLength + } + if dec.CorpusDirectory != nil { + f.CorpusDirectory = *dec.CorpusDirectory + } + if dec.CoverageEnabled != nil { + f.CoverageEnabled = *dec.CoverageEnabled + } + if dec.TargetContracts != nil { + f.TargetContracts = dec.TargetContracts + } + if dec.TargetContractsBalances != nil { + f.TargetContractsBalances = make([]*big.Int, len(dec.TargetContractsBalances)) + for k, v := range dec.TargetContractsBalances { + f.TargetContractsBalances[k] = (*big.Int)(v) + } + } + if dec.ConstructorArgs != nil { + f.ConstructorArgs = dec.ConstructorArgs + } + if dec.DeployerAddress != nil { + f.DeployerAddress = *dec.DeployerAddress + } + if dec.SenderAddresses != nil { + f.SenderAddresses = dec.SenderAddresses + } + if dec.MaxBlockNumberDelay != nil { + f.MaxBlockNumberDelay = *dec.MaxBlockNumberDelay + } + if dec.MaxBlockTimestampDelay != nil { + f.MaxBlockTimestampDelay = *dec.MaxBlockTimestampDelay + } + if dec.BlockGasLimit != nil { + f.BlockGasLimit = *dec.BlockGasLimit + } + if dec.TransactionGasLimit != nil { + f.TransactionGasLimit = *dec.TransactionGasLimit + } + if dec.Testing != nil { + f.Testing = *dec.Testing + } + if dec.TestChainConfig != nil { + f.TestChainConfig = *dec.TestChainConfig + } + return nil +} diff --git a/fuzzing/coverage/coverage_maps.go b/fuzzing/coverage/coverage_maps.go index 71ed9852..e9a1343c 100644 --- a/fuzzing/coverage/coverage_maps.go +++ b/fuzzing/coverage/coverage_maps.go @@ -2,7 +2,6 @@ package coverage import ( "bytes" - "fmt" compilationTypes "github.com/crytic/medusa/compilation/types" "github.com/crytic/medusa/utils" "github.com/ethereum/go-ethereum/common" @@ -373,5 +372,8 @@ func (cm *CoverageMapBytecodeData) setCoveredAt(codeSize int, pc uint64) (bool, } return false, nil } - return false, fmt.Errorf("tried to set coverage map out of bounds (pc: %d, code size %d)", pc, len(cm.executedFlags)) + + // Since it is possible that the program counter is larger than the code size (e.g., malformed bytecode), we will + // simply return false with no error + return false, nil } diff --git a/fuzzing/coverage/report_generation.go b/fuzzing/coverage/report_generation.go index abe8cc7c..57232f1e 100644 --- a/fuzzing/coverage/report_generation.go +++ b/fuzzing/coverage/report_generation.go @@ -143,9 +143,9 @@ func exportHtmlCoverageReport(sourceAnalysis *SourceAnalysis, outputPath string) // Determine our precision string formatStr := "%." + strconv.Itoa(decimals) + "f" - // If no lines are active and none are covered, show 100% coverage + // If no lines are active and none are covered, show 0% coverage if x == 0 && y == 0 { - return fmt.Sprintf(formatStr, float64(100)) + return fmt.Sprintf(formatStr, float64(0)) } return fmt.Sprintf(formatStr, (float64(x)/float64(y))*100) }, diff --git a/fuzzing/executiontracer/execution_trace.go b/fuzzing/executiontracer/execution_trace.go index 287ce52c..695f595b 100644 --- a/fuzzing/executiontracer/execution_trace.go +++ b/fuzzing/executiontracer/execution_trace.go @@ -195,14 +195,19 @@ func (t *ExecutionTrace) generateCallFrameExitElements(callFrame *CallFrame) []a // If we could not correctly obtain the unpacked arguments in a nice display string (due to not having a resolved // contract or method definition, or failure to unpack), we display as raw data in the worst case. - if outputArgumentsDisplayText == nil { + // TODO: Fix if return data is empty len byte array + if outputArgumentsDisplayText == nil && len(callFrame.ReturnData) > 0 { temp := fmt.Sprintf("return_data=%v", hex.EncodeToString(callFrame.ReturnData)) outputArgumentsDisplayText = &temp } // Wrap our return message and output it at the end. if callFrame.ReturnError == nil { - elements = append(elements, colors.GreenBold, fmt.Sprintf("[return (%v)]", *outputArgumentsDisplayText), colors.Reset, "\n") + if outputArgumentsDisplayText != nil { + elements = append(elements, colors.GreenBold, fmt.Sprintf("[return (%v)]", *outputArgumentsDisplayText), colors.Reset, "\n") + } else { + elements = append(elements, colors.GreenBold, "[return]", colors.Reset, "\n") + } return elements } diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index b792a55d..f6caecbc 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -3,6 +3,10 @@ package fuzzing import ( "context" "fmt" + "github.com/crytic/medusa/fuzzing/coverage" + "github.com/crytic/medusa/logging" + "github.com/crytic/medusa/logging/colors" + "github.com/rs/zerolog" "math/big" "math/rand" "os" @@ -14,11 +18,6 @@ import ( "sync" "time" - "github.com/crytic/medusa/fuzzing/coverage" - "github.com/crytic/medusa/logging" - "github.com/crytic/medusa/logging/colors" - "github.com/rs/zerolog" - "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/utils/randomutils" "github.com/ethereum/go-ethereum/core/types" @@ -93,7 +92,9 @@ func NewFuzzer(config config.ProjectConfig) (*Fuzzer, error) { if config.Logging.NoColor { colors.DisableColor() } + // Create the global logger and add stdout as an unstructured output stream + // Note that we are not using the project config's log level because we have not validated it yet logging.GlobalLogger = logging.NewLogger(config.Logging.Level) logging.GlobalLogger.AddWriter(os.Stdout, logging.UNSTRUCTURED, !config.Logging.NoColor) @@ -110,16 +111,19 @@ func NewFuzzer(config config.ProjectConfig) (*Fuzzer, error) { logging.GlobalLogger.AddWriter(file, logging.UNSTRUCTURED, false) } - // Get the fuzzer's custom sub-logger - logger := logging.GlobalLogger.NewSubLogger("module", "fuzzer") - // Validate our provided config err := config.Validate() if err != nil { - logger.Error("Invalid configuration", err) + logging.GlobalLogger.Error("Invalid configuration", err) return nil, err } + // Update the log level of the global logger now + logging.GlobalLogger.SetLevel(config.Logging.Level) + + // Get the fuzzer's custom sub-logger + logger := logging.GlobalLogger.NewSubLogger("module", "fuzzer") + // Parse the senders addresses from our account config. senders, err := utils.HexStringsToAddresses(config.Fuzzing.SenderAddresses) if err != nil { @@ -330,19 +334,20 @@ func (f *Fuzzer) createTestChain() (*chain.TestChain, error) { // definitions, as well as those added by Fuzzer.AddCompilationTargets. The contract deployment order is defined by // the Fuzzer.config. func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) error { - // Verify contract deployment order is not empty. If it's empty, but we only have one contract definition, - // we can infer the deployment order. Otherwise, we report an error. - if len(fuzzer.config.Fuzzing.DeploymentOrder) == 0 { + // Verify that target contracts is not empty. If it's empty, but we only have one contract definition, + // we can infer the target contracts. Otherwise, we report an error. + if len(fuzzer.config.Fuzzing.TargetContracts) == 0 { if len(fuzzer.contractDefinitions) == 1 { - fuzzer.config.Fuzzing.DeploymentOrder = []string{fuzzer.contractDefinitions[0].Name()} + fuzzer.config.Fuzzing.TargetContracts = []string{fuzzer.contractDefinitions[0].Name()} } else { - return fmt.Errorf("you must specify a contract deployment order within your project configuration") + return fmt.Errorf("missing target contracts (update fuzzing.targetContracts in the project config " + + "or use the --target-contracts CLI flag)") } } // Loop for all contracts to deploy deployedContractAddr := make(map[string]common.Address) - for _, contractName := range fuzzer.config.Fuzzing.DeploymentOrder { + for i, contractName := range fuzzer.config.Fuzzing.TargetContracts { // Look for a contract in our compiled contract definitions that matches this one found := false for _, contract := range fuzzer.contractDefinitions { @@ -368,9 +373,15 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) erro return fmt.Errorf("initial contract deployment failed for contract \"%v\", error: %v", contractName, err) } + // If our project config has a non-zero balance for this target contract, retrieve it + contractBalance := big.NewInt(0) + if len(fuzzer.config.Fuzzing.TargetContractsBalances) > i { + contractBalance = new(big.Int).Set(fuzzer.config.Fuzzing.TargetContractsBalances[i]) + } + // Create a message to represent our contract deployment (we let deployments consume the whole block // gas limit rather than use tx gas limit) - msg := calls.NewCallMessage(fuzzer.deployer, nil, 0, big.NewInt(0), fuzzer.config.Fuzzing.BlockGasLimit, nil, nil, nil, msgData) + msg := calls.NewCallMessage(fuzzer.deployer, nil, 0, contractBalance, fuzzer.config.Fuzzing.BlockGasLimit, nil, nil, nil, msgData) msg.FillFromTestChainProperties(testChain) // Create a new pending block we'll commit to chain @@ -410,7 +421,7 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) erro // If we did not find a contract corresponding to this item in the deployment order, we throw an error. if !found { - return fmt.Errorf("DeploymentOrder specified a contract name which was not found in the compilation: %v\n", contractName) + return fmt.Errorf("%v was specified in the target contracts but was not found in the compilation artifacts", contractName) } } return nil diff --git a/fuzzing/fuzzer_test.go b/fuzzing/fuzzer_test.go index 7fdc4f47..9167b183 100644 --- a/fuzzing/fuzzer_test.go +++ b/fuzzing/fuzzer_test.go @@ -21,9 +21,9 @@ func TestFuzzerHooks(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/assertions/assert_immediate.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.Testing.PropertyTesting.Enabled = false - config.Fuzzing.Testing.AssertionTesting.Enabled = true + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Attach to fuzzer hooks which simply set a success state. @@ -76,18 +76,18 @@ func TestAssertionMode(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: filePath, configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} + config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnAssertion = true + config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnAllocateTooMuchMemory = true + config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnArithmeticUnderflow = true + config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnCallUninitializedVariable = true + config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnEnumTypeConversionOutOfBounds = true + config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnDivideByZero = true + config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnIncorrectStorageAccess = true + config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnOutOfBoundsArrayAccess = true + config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnPopEmptyArray = true config.Fuzzing.Testing.PropertyTesting.Enabled = false - config.Fuzzing.Testing.AssertionTesting.Enabled = true - config.Fuzzing.Testing.AssertionTesting.AssertionModes.FailOnAssertion = true - config.Fuzzing.Testing.AssertionTesting.AssertionModes.FailOnAllocateTooMuchMemory = true - config.Fuzzing.Testing.AssertionTesting.AssertionModes.FailOnArithmeticUnderflow = true - config.Fuzzing.Testing.AssertionTesting.AssertionModes.FailOnCallUninitializedVariable = true - config.Fuzzing.Testing.AssertionTesting.AssertionModes.FailOnEnumTypeConversionOutOfBounds = true - config.Fuzzing.Testing.AssertionTesting.AssertionModes.FailOnDivideByZero = true - config.Fuzzing.Testing.AssertionTesting.AssertionModes.FailOnIncorrectStorageAccess = true - config.Fuzzing.Testing.AssertionTesting.AssertionModes.FailOnOutOfBoundsArrayAccess = true - config.Fuzzing.Testing.AssertionTesting.AssertionModes.FailOnPopEmptyArray = true + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -107,10 +107,10 @@ func TestAssertionsNotRequire(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/assertions/assert_not_require.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.TestLimit = 500 config.Fuzzing.Testing.PropertyTesting.Enabled = false - config.Fuzzing.Testing.AssertionTesting.Enabled = true + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -129,11 +129,10 @@ func TestAssertionsAndProperties(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/assertions/assert_and_property_test.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.TestLimit = 500 config.Fuzzing.Testing.StopOnFailedTest = false - config.Fuzzing.Testing.PropertyTesting.Enabled = true - config.Fuzzing.Testing.AssertionTesting.Enabled = true + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -155,11 +154,10 @@ func TestOptimizationMode(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: filePath, configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} + config.Fuzzing.TestLimit = 10_000 // this test should expose a failure quickly. config.Fuzzing.Testing.PropertyTesting.Enabled = false config.Fuzzing.Testing.AssertionTesting.Enabled = false - config.Fuzzing.Testing.OptimizationTesting.Enabled = true - config.Fuzzing.TestLimit = 10_000 // this test should expose a failure quickly. }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -168,11 +166,10 @@ func TestOptimizationMode(t *testing.T) { // Check the value found for optimization test var testCases = f.fuzzer.TestCasesWithStatus(TestCaseStatusPassed) - switch v := testCases[0].(type) { - case *OptimizationTestCase: - assert.EqualValues(t, v.Value().Cmp(big.NewInt(4241)), 0) - default: - t.Errorf("invalid test case found %T", v) + for _, testCase := range testCases { + if optimizationTestCase, ok := testCase.(*OptimizationTestCase); ok { + assert.EqualValues(t, optimizationTestCase.Value().Cmp(big.NewInt(4241)), 0) + } } }, }) @@ -185,11 +182,13 @@ func TestChainBehaviour(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/chain/tx_out_of_gas.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.Workers = 1 config.Fuzzing.TestLimit = uint64(config.Fuzzing.CallSequenceLength) // we just need a few oog txs to test config.Fuzzing.Timeout = 10 // to be safe, we set a 10s timeout config.Fuzzing.TransactionGasLimit = 500000 // we set this low, so contract execution runs out of gas earlier. + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -237,7 +236,7 @@ func TestCheatCodes(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: filePath, configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} // some tests require full sequence + revert to test fully config.Fuzzing.Workers = 3 @@ -266,8 +265,8 @@ func TestConsoleLog(t *testing.T) { // These are the logs that should show up in the execution trace expectedLogs := []string{ "2", - "hello world", - "byte", + "68656c6c6f20776f726c64", // This is "hello world" in hex + "62797465", // This is "byte" in hex "i is 2", "% bool is true, addr is 0x0000000000000000000000000000000000000000, u is 100", } @@ -279,11 +278,10 @@ func TestConsoleLog(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: filePath, configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.TestLimit = 10000 - // enable assertion testing only - config.Fuzzing.Testing.PropertyTesting.Enabled = true - config.Fuzzing.Testing.AssertionTesting.Enabled = true + config.Fuzzing.Testing.PropertyTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -326,10 +324,12 @@ func TestDeploymentsInnerDeployments(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: filePath, configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"InnerDeploymentFactory"} + config.Fuzzing.TargetContracts = []string{"InnerDeploymentFactory"} config.Fuzzing.TestLimit = 1_000 // this test should expose a failure quickly. config.Fuzzing.Testing.StopOnFailedContractMatching = true config.Fuzzing.Testing.TestAllContracts = true // test dynamically deployed contracts + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -347,10 +347,12 @@ func TestDeploymentsInnerDeployments(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/deployments/inner_deployment_on_construction.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"InnerDeploymentFactory"} + config.Fuzzing.TargetContracts = []string{"InnerDeploymentFactory"} config.Fuzzing.TestLimit = 1_000 // this test should expose a failure quickly. config.Fuzzing.Testing.StopOnFailedContractMatching = true config.Fuzzing.Testing.TestAllContracts = true // test dynamically deployed contracts + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -368,8 +370,33 @@ func TestDeploymentsInternalLibrary(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/deployments/internal_library.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestInternalLibrary"} + config.Fuzzing.TargetContracts = []string{"TestInternalLibrary"} config.Fuzzing.TestLimit = 100 // this test should expose a failure quickly. + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false + }, + method: func(f *fuzzerTestContext) { + // Start the fuzzer + err := f.fuzzer.Start() + assert.NoError(t, err) + + // Check for any failed tests and verify coverage was captured + assertFailedTestsExpected(f, false) + assertCorpusCallSequencesCollected(f, true) + }, + }) +} + +// TestDeploymentsWithPayableConstructor runs a test to ensure that we can send ether to payable constructors +func TestDeploymentsWithPayableConstructors(t *testing.T) { + runFuzzerTest(t, &fuzzerSolcFileTest{ + filePath: "testdata/contracts/deployments/deploy_payable_constructors.sol", + configUpdates: func(config *config.ProjectConfig) { + config.Fuzzing.TargetContracts = []string{"FirstContract", "SecondContract"} + config.Fuzzing.TargetContractsBalances = []*big.Int{big.NewInt(0), big.NewInt(1e18)} + config.Fuzzing.TestLimit = 1 // this should happen immediately + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -395,9 +422,11 @@ func TestDeploymentsSelfDestruct(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: filePath, configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"InnerDeploymentFactory"} + config.Fuzzing.TargetContracts = []string{"InnerDeploymentFactory"} config.Fuzzing.TestLimit = 500 // this test should expose a failure quickly. config.Fuzzing.Testing.StopOnNoTests = false + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false config.Fuzzing.Testing.TestAllContracts = true }, method: func(f *fuzzerTestContext) { @@ -442,9 +471,9 @@ func TestExecutionTraces(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: filePath, configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.Testing.PropertyTesting.Enabled = false - config.Fuzzing.Testing.AssertionTesting.Enabled = true + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -482,12 +511,11 @@ func TestTestingScope(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/deployments/testing_scope.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.TestLimit = 1_000 // this test should expose a failure quickly. config.Fuzzing.Testing.TestAllContracts = testingAllContracts config.Fuzzing.Testing.StopOnFailedTest = false - config.Fuzzing.Testing.AssertionTesting.Enabled = true - config.Fuzzing.Testing.PropertyTesting.Enabled = true + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -517,7 +545,7 @@ func TestDeploymentsWithArgs(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/deployments/deployment_with_args.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"DeploymentWithArgs", "Dependent"} + config.Fuzzing.TargetContracts = []string{"DeploymentWithArgs", "Dependent"} config.Fuzzing.ConstructorArgs = map[string]map[string]any{ "DeploymentWithArgs": { "_x": "123456789", @@ -533,6 +561,8 @@ func TestDeploymentsWithArgs(t *testing.T) { } config.Fuzzing.Testing.StopOnFailedTest = false config.Fuzzing.TestLimit = 500 // this test should expose a failure quickly. + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -550,8 +580,10 @@ func TestValueGenerationGenerateAllTypes(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/value_generation/generate_all_types.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"GenerateAllTypes"} + config.Fuzzing.TargetContracts = []string{"GenerateAllTypes"} config.Fuzzing.TestLimit = 10_000 + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -583,7 +615,9 @@ func TestValueGenerationSolving(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: filePath, configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -639,8 +673,9 @@ func TestASTValueExtraction(t *testing.T) { filePath: "testdata/contracts/value_generation/ast_value_extraction.sol", configUpdates: func(config *config.ProjectConfig) { config.Fuzzing.TestLimit = 1 // stop immediately to simply see what values were mined. - config.Fuzzing.Testing.AssertionTesting.Enabled = true config.Fuzzing.Testing.PropertyTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false + config.Fuzzing.TargetContracts = []string{"TestContract"} }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -674,9 +709,11 @@ func TestVMCorrectness(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/vm_tests/block_number_increasing.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.MaxBlockTimestampDelay = 1 // this contract require calls every block config.Fuzzing.MaxBlockNumberDelay = 1 // this contract require calls every block + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Start the fuzzer @@ -693,7 +730,7 @@ func TestVMCorrectness(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/vm_tests/block_number_increasing.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.MaxBlockTimestampDelay = 1 // this contract require calls every block config.Fuzzing.MaxBlockNumberDelay = 1 // this contract require calls every block }, @@ -712,7 +749,7 @@ func TestVMCorrectness(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/vm_tests/block_hash_store_check.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.TestLimit = 1_000 // this test should expose a failure quickly. config.Fuzzing.MaxBlockTimestampDelay = 1 // this contract require calls every block config.Fuzzing.MaxBlockNumberDelay = 1 // this contract require calls every block @@ -737,8 +774,10 @@ func TestCorpusReplayability(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/value_generation/match_uints_xy.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"TestContract"} + config.Fuzzing.TargetContracts = []string{"TestContract"} config.Fuzzing.CorpusDirectory = "corpus" + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Setup checks for event emissions @@ -776,14 +815,16 @@ func TestCorpusReplayability(t *testing.T) { }) } -// TestDeploymentOrderWithCoverage will ensure that changing the deployment order does not lead to the same coverage -// This is also proof that changing the order changes the addresses of the contracts leading to the coverage not being -// useful. +// TestDeploymentOrderWithCoverage will ensure that changing the order of deployment for the target contracts does not +// lead to the same coverage. This is also proof that changing the order changes the addresses of the contracts leading +// to the coverage not being useful. func TestDeploymentOrderWithCoverage(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/deployments/deployment_order.sol", configUpdates: func(config *config.ProjectConfig) { - config.Fuzzing.DeploymentOrder = []string{"InheritedFirstContract", "InheritedSecondContract"} + config.Fuzzing.TargetContracts = []string{"InheritedFirstContract", "InheritedSecondContract"} + config.Fuzzing.Testing.AssertionTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, method: func(f *fuzzerTestContext) { // Setup checks for event emissions @@ -806,8 +847,8 @@ func TestDeploymentOrderWithCoverage(t *testing.T) { return nil }) - // Update the deployment order - f.fuzzer.config.Fuzzing.DeploymentOrder = []string{"InheritedSecondContract", "InheritedFirstContract"} + // Update the order of target contracts + f.fuzzer.config.Fuzzing.TargetContracts = []string{"InheritedSecondContract", "InheritedFirstContract"} // Note that the fuzzer won't spin up any workers or fuzz anything. We just want to test that the coverage // maps don't populate due to deployment order changes diff --git a/fuzzing/test_case_assertion_provider.go b/fuzzing/test_case_assertion_provider.go index 300d97dc..af1a0b47 100644 --- a/fuzzing/test_case_assertion_provider.go +++ b/fuzzing/test_case_assertion_provider.go @@ -5,6 +5,7 @@ import ( "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/fuzzing/config" "github.com/crytic/medusa/fuzzing/contracts" + "github.com/crytic/medusa/fuzzing/utils" "github.com/ethereum/go-ethereum/accounts/abi" "golang.org/x/exp/slices" "sync" @@ -44,6 +45,16 @@ func attachAssertionTestCaseProvider(fuzzer *Fuzzer) *AssertionTestCaseProvider // isTestableMethod checks whether the method is configured by the attached fuzzer to be a target of assertion testing. // Returns true if this target should be tested, false otherwise. func (t *AssertionTestCaseProvider) isTestableMethod(method abi.Method) bool { + // Do not test optimization tests + if utils.IsOptimizationTest(method, t.fuzzer.config.Fuzzing.Testing.OptimizationTesting.TestPrefixes) { + return false + } + + // Do not test property tests + if utils.IsPropertyTest(method, t.fuzzer.config.Fuzzing.Testing.PropertyTesting.TestPrefixes) { + return false + } + // Only test constant methods (pure/view) if we are configured to. return !method.IsConstant() || t.fuzzer.config.Fuzzing.Testing.AssertionTesting.TestViewMethods } @@ -73,7 +84,7 @@ func (t *AssertionTestCaseProvider) checkAssertionFailures(callSequence calls.Ca panicCode := abiutils.GetSolidityPanicCode(lastExecutionResult.Err, lastExecutionResult.ReturnData, true) failure := false if panicCode != nil { - failure = encounteredAssertionFailure(panicCode.Uint64(), t.fuzzer.config.Fuzzing.Testing.AssertionTesting.AssertionModes) + failure = encounteredAssertionFailure(panicCode.Uint64(), t.fuzzer.config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig) } return &methodId, failure, nil @@ -87,8 +98,8 @@ func (t *AssertionTestCaseProvider) onFuzzerStarting(event FuzzerStartingEvent) // Create a test case for every test method. for _, contract := range t.fuzzer.ContractDefinitions() { - // If we're not testing all contracts, verify the current contract is one we specified in our deployment order. - if !t.fuzzer.config.Fuzzing.Testing.TestAllContracts && !slices.Contains(t.fuzzer.config.Fuzzing.DeploymentOrder, contract.Name()) { + // If we're not testing all contracts, verify the current contract is one we specified in our target contracts + if !t.fuzzer.config.Fuzzing.Testing.TestAllContracts && !slices.Contains(t.fuzzer.config.Fuzzing.TargetContracts, contract.Name()) { continue } @@ -241,7 +252,7 @@ func (t *AssertionTestCaseProvider) callSequencePostCallTest(worker *FuzzerWorke // code was enabled in the config. Note that the panic codes are defined in the abiutils package and that this function // panic if it is provided a panic code that is not defined in the abiutils package. // TODO: This is a terrible design and a future PR should be made to maintain assertion and panic logic correctly -func encounteredAssertionFailure(panicCode uint64, conf config.AssertionModesConfig) bool { +func encounteredAssertionFailure(panicCode uint64, conf config.PanicCodeConfig) bool { // Switch on panic code switch panicCode { case abiutils.PanicCodeCompilerInserted: diff --git a/fuzzing/test_case_optimization_provider.go b/fuzzing/test_case_optimization_provider.go index 424db7e5..93c7e536 100644 --- a/fuzzing/test_case_optimization_provider.go +++ b/fuzzing/test_case_optimization_provider.go @@ -5,10 +5,10 @@ import ( "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/fuzzing/contracts" "github.com/crytic/medusa/fuzzing/executiontracer" - "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/crytic/medusa/fuzzing/utils" "github.com/ethereum/go-ethereum/core" + "golang.org/x/exp/slices" "math/big" - "strings" "sync" ) @@ -45,6 +45,11 @@ type optimizationTestCaseProviderWorkerState struct { // attachOptimizationTestCaseProvider attaches a new OptimizationTestCaseProvider to the Fuzzer and returns it. func attachOptimizationTestCaseProvider(fuzzer *Fuzzer) *OptimizationTestCaseProvider { + // If there are no testing prefixes, then there is no reason to attach a test case provider and subscribe to events + if len(fuzzer.config.Fuzzing.Testing.OptimizationTesting.TestPrefixes) == 0 { + return nil + } + // Create a test case provider t := &OptimizationTestCaseProvider{ fuzzer: fuzzer, @@ -60,21 +65,6 @@ func attachOptimizationTestCaseProvider(fuzzer *Fuzzer) *OptimizationTestCasePro return t } -// isOptimizationTest check whether the method is an optimization test given potential naming prefixes it must conform to -// and its underlying input/output arguments. -func (t *OptimizationTestCaseProvider) isOptimizationTest(method abi.Method) bool { - // Loop through all enabled prefixes to find a match - for _, prefix := range t.fuzzer.Config().Fuzzing.Testing.OptimizationTesting.TestPrefixes { - if strings.HasPrefix(method.Name, prefix) { - // An optimization test must take no inputs and return an int256 - if len(method.Inputs) == 0 && len(method.Outputs) == 1 && method.Outputs[0].Type.T == abi.IntTy && method.Outputs[0].Type.Size == 256 { - return true - } - } - } - return false -} - // runOptimizationTest executes a given optimization test method (w/ an optional execution trace) and returns the return value // from the optimization test method. This is called after every call the Fuzzer makes when testing call sequences for each test case. func (t *OptimizationTestCaseProvider) runOptimizationTest(worker *FuzzerWorker, optimizationTestMethod *contracts.DeployedContractMethod, trace bool) (*big.Int, *executiontracer.ExecutionTrace, error) { @@ -141,9 +131,14 @@ func (t *OptimizationTestCaseProvider) onFuzzerStarting(event FuzzerStartingEven // Create a test case for every optimization test method. for _, contract := range t.fuzzer.ContractDefinitions() { + // If we're not testing all contracts, verify the current contract is one we specified in our target contracts + if !t.fuzzer.config.Fuzzing.Testing.TestAllContracts && !slices.Contains(t.fuzzer.config.Fuzzing.TargetContracts, contract.Name()) { + continue + } + for _, method := range contract.CompiledContract().Abi.Methods { // Verify this method is an optimization test method - if !t.isOptimizationTest(method) { + if !utils.IsOptimizationTest(method, t.fuzzer.config.Fuzzing.Testing.OptimizationTesting.TestPrefixes) { continue } // Create local variables to avoid pointer types in the loop being overridden. diff --git a/fuzzing/test_case_property_provider.go b/fuzzing/test_case_property_provider.go index 10399cc5..9f5d8277 100644 --- a/fuzzing/test_case_property_provider.go +++ b/fuzzing/test_case_property_provider.go @@ -5,11 +5,10 @@ import ( "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/fuzzing/contracts" "github.com/crytic/medusa/fuzzing/executiontracer" - "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/crytic/medusa/fuzzing/utils" "github.com/ethereum/go-ethereum/core" "golang.org/x/exp/slices" "math/big" - "strings" "sync" ) @@ -47,6 +46,11 @@ type propertyTestCaseProviderWorkerState struct { // attachPropertyTestCaseProvider attaches a new PropertyTestCaseProvider to the Fuzzer and returns it. func attachPropertyTestCaseProvider(fuzzer *Fuzzer) *PropertyTestCaseProvider { + // If there are no testing prefixes, then there is no reason to attach a test case provider and subscribe to events + if len(fuzzer.config.Fuzzing.Testing.PropertyTesting.TestPrefixes) == 0 { + return nil + } + // Create a test case provider t := &PropertyTestCaseProvider{ fuzzer: fuzzer, @@ -62,20 +66,6 @@ func attachPropertyTestCaseProvider(fuzzer *Fuzzer) *PropertyTestCaseProvider { return t } -// isPropertyTest check whether the method is a property test given potential naming prefixes it must conform to -// and its underlying input/output arguments. -func (t *PropertyTestCaseProvider) isPropertyTest(method abi.Method) bool { - // Loop through all enabled prefixes to find a match - for _, prefix := range t.fuzzer.Config().Fuzzing.Testing.PropertyTesting.TestPrefixes { - if strings.HasPrefix(method.Name, prefix) { - if len(method.Inputs) == 0 && len(method.Outputs) == 1 && method.Outputs[0].Type.T == abi.BoolTy { - return true - } - } - } - return false -} - // checkPropertyTestFailed executes a given property test method to see if it returns a failed status. This is used to // facilitate testing of property test methods after every call the Fuzzer makes when testing call sequences. // A boolean indicating whether an execution trace should be captured and returned is provided to the method. @@ -143,14 +133,14 @@ func (t *PropertyTestCaseProvider) onFuzzerStarting(event FuzzerStartingEvent) e // Create a test case for every property test method. for _, contract := range t.fuzzer.ContractDefinitions() { - // If we're not testing all contracts, verify the current contract is one we specified in our deployment order. - if !t.fuzzer.config.Fuzzing.Testing.TestAllContracts && !slices.Contains(t.fuzzer.config.Fuzzing.DeploymentOrder, contract.Name()) { + // If we're not testing all contracts, verify the current contract is one we specified in our target contracts. + if !t.fuzzer.config.Fuzzing.Testing.TestAllContracts && !slices.Contains(t.fuzzer.config.Fuzzing.TargetContracts, contract.Name()) { continue } for _, method := range contract.CompiledContract().Abi.Methods { // Verify this method is a property test method - if !t.isPropertyTest(method) { + if !utils.IsPropertyTest(method, t.fuzzer.config.Fuzzing.Testing.PropertyTesting.TestPrefixes) { continue } diff --git a/fuzzing/testdata/contracts/assertions/assert_and_property_test.sol b/fuzzing/testdata/contracts/assertions/assert_and_property_test.sol index 90051ba5..03100042 100644 --- a/fuzzing/testdata/contracts/assertions/assert_and_property_test.sol +++ b/fuzzing/testdata/contracts/assertions/assert_and_property_test.sol @@ -5,7 +5,7 @@ contract TestContract { assert(false); } - function fuzz_failing_property() public view returns (bool) { + function property_failing_property() public view returns (bool) { // ASSERTION: fail immediately. return false; } diff --git a/fuzzing/testdata/contracts/chain/tx_out_of_gas.sol b/fuzzing/testdata/contracts/chain/tx_out_of_gas.sol index 96fb55d0..6054a7d8 100644 --- a/fuzzing/testdata/contracts/chain/tx_out_of_gas.sol +++ b/fuzzing/testdata/contracts/chain/tx_out_of_gas.sol @@ -10,7 +10,7 @@ contract TestContract { } } - function fuzz_never_apply_state_when_oog() public view returns (bool) { + function property_never_apply_state_when_oog() public view returns (bool) { // ASSERTION: this state should never be applied, as our out of gas error should revert changes. return x == 0; } diff --git a/fuzzing/testdata/contracts/corpus_mutation/specific_call_sequence.sol b/fuzzing/testdata/contracts/corpus_mutation/specific_call_sequence.sol index e66c3771..b4356dac 100644 --- a/fuzzing/testdata/contracts/corpus_mutation/specific_call_sequence.sol +++ b/fuzzing/testdata/contracts/corpus_mutation/specific_call_sequence.sol @@ -29,7 +29,7 @@ contract TestContract { } } - function fuzz_solve_me() public view returns (bool) { + function property_solve_me() public view returns (bool) { // ASSERTION: The fuzzer should be able to fail this test case and solve all challenges. return index < 7; } diff --git a/fuzzing/testdata/contracts/deployments/deploy_payable_constructors.sol b/fuzzing/testdata/contracts/deployments/deploy_payable_constructors.sol new file mode 100644 index 00000000..223d85c4 --- /dev/null +++ b/fuzzing/testdata/contracts/deployments/deploy_payable_constructors.sol @@ -0,0 +1,18 @@ +// This source file provides two contracts to test whether we are able to send ether to payable constructors. FirstContract +// should get no ether and while SecondContract should receive 1 ether. +contract FirstContract { + constructor() payable {} + + function property_contract_has_no_balance() public returns(bool) { + return address(this).balance == 0; + } +} + + +contract SecondContract { + constructor() payable {} + + function property_contract_has_balance() public returns(bool) { + return address(this).balance == 1 ether; + } +} diff --git a/fuzzing/testdata/contracts/deployments/deployment_order.sol b/fuzzing/testdata/contracts/deployments/deployment_order.sol index efdff8bc..f7c6f3ed 100644 --- a/fuzzing/testdata/contracts/deployments/deployment_order.sol +++ b/fuzzing/testdata/contracts/deployments/deployment_order.sol @@ -15,7 +15,7 @@ contract InheritedFirstContract is FirstContract { y = value + 9; } - function fuzz_never_specific_values() public view returns (bool) { + function property_never_specific_values() public view returns (bool) { // ASSERTION: x should never be 10 at the same time y is 80 return !(x == 10 && y == 80); } @@ -41,7 +41,7 @@ contract InheritedSecondContract is SecondContract { c = value + 7; } - function fuzz_never_specific_values() public view returns (bool) { + function property_never_specific_values() public view returns (bool) { // ASSERTION: a should never be 10 at the same time b is 80 at the same time c is 14 return !(a == 10 && b == 80 && c == 14); } diff --git a/fuzzing/testdata/contracts/deployments/deployment_with_args.sol b/fuzzing/testdata/contracts/deployments/deployment_with_args.sol index 11e62d12..05991266 100644 --- a/fuzzing/testdata/contracts/deployments/deployment_with_args.sol +++ b/fuzzing/testdata/contracts/deployments/deployment_with_args.sol @@ -15,15 +15,15 @@ contract DeploymentWithArgs { z = _z; } - function fuzz_checkX() public returns (bool) { + function property_checkX() public returns (bool) { return x != 123456789; } - function fuzz_checkY() public returns (bool) { + function property_checkY() public returns (bool) { return y != 0x5465; } - function fuzz_checkZ() public returns (bool) { + function property_checkZ() public returns (bool) { return z.a != 0x4d2; } @@ -40,7 +40,7 @@ contract Dependent { deployed = _deployed; } - function fuzz_checkDeployed() public returns (bool) { + function property_checkDeployed() public returns (bool) { return deployed == 0x0000000000000000000000000000000000000000; } diff --git a/fuzzing/testdata/contracts/deployments/inner_deployment.sol b/fuzzing/testdata/contracts/deployments/inner_deployment.sol index d853687e..dc26b5b7 100644 --- a/fuzzing/testdata/contracts/deployments/inner_deployment.sol +++ b/fuzzing/testdata/contracts/deployments/inner_deployment.sol @@ -1,7 +1,7 @@ // InnerDeploymentFactory deploys InnerDeployment when a method is called after deployment, and verifies the fuzzer can // match bytecode and fail the test appropriately. contract InnerDeployment { - function fuzz_inner_deployment() public view returns (bool) { + function property_inner_deployment() public view returns (bool) { // ASSERTION: Fail immediately. return false; } diff --git a/fuzzing/testdata/contracts/deployments/inner_deployment_on_construction.sol b/fuzzing/testdata/contracts/deployments/inner_deployment_on_construction.sol index 15aec27c..4e08a111 100644 --- a/fuzzing/testdata/contracts/deployments/inner_deployment_on_construction.sol +++ b/fuzzing/testdata/contracts/deployments/inner_deployment_on_construction.sol @@ -6,7 +6,7 @@ contract InnerDeployment { x = 7; } - function fuzz_inner_deployment() public view returns (bool) { + function property_inner_deployment() public view returns (bool) { // ASSERTION: Fail immediately. return false; } diff --git a/fuzzing/testdata/contracts/deployments/inner_inner_deployment.sol b/fuzzing/testdata/contracts/deployments/inner_inner_deployment.sol index 8a1b1140..7718fc82 100644 --- a/fuzzing/testdata/contracts/deployments/inner_inner_deployment.sol +++ b/fuzzing/testdata/contracts/deployments/inner_inner_deployment.sol @@ -2,7 +2,7 @@ // deployed, a method can be used to deploy an InnerInnerDeployment. We verify we can violate an invariant // in a two-layer deep dynamic deployment. contract InnerInnerDeployment { - function fuzz_inner_inner_deployment() public view returns (bool) { + function property_inner_inner_deployment() public view returns (bool) { // ASSERTION: Fail immediately. return false; } diff --git a/fuzzing/testdata/contracts/deployments/internal_library.sol b/fuzzing/testdata/contracts/deployments/internal_library.sol index ba03a08a..27201d43 100644 --- a/fuzzing/testdata/contracts/deployments/internal_library.sol +++ b/fuzzing/testdata/contracts/deployments/internal_library.sol @@ -28,7 +28,7 @@ contract TestInternalLibrary { return a + b; } - function fuzz_library_linking_broken() public view returns (bool) { + function property_library_linking_broken() public view returns (bool) { // ASSERTION: We should always be able to compute correctly. return !failedTest; } diff --git a/fuzzing/testdata/contracts/deployments/testing_scope.sol b/fuzzing/testdata/contracts/deployments/testing_scope.sol index af4f6605..e98aef38 100644 --- a/fuzzing/testdata/contracts/deployments/testing_scope.sol +++ b/fuzzing/testdata/contracts/deployments/testing_scope.sol @@ -6,7 +6,7 @@ contract TestContractChild { assert(false); } - function fuzz_failing_property_test_method_child() public view returns (bool) { + function property_failing_property_test_method_child() public view returns (bool) { return false; } } @@ -22,7 +22,7 @@ contract TestContract { assert(false); } - function fuzz_failing_property_test_method() public view returns (bool) { + function property_failing_property_test_method() public view returns (bool) { return false; } } diff --git a/fuzzing/testdata/contracts/value_generation/generate_all_types.sol b/fuzzing/testdata/contracts/value_generation/generate_all_types.sol index a4d0114c..e9a6fa75 100644 --- a/fuzzing/testdata/contracts/value_generation/generate_all_types.sol +++ b/fuzzing/testdata/contracts/value_generation/generate_all_types.sol @@ -24,7 +24,7 @@ contract GenerateAllTypes { s = ""; } - function fuzz_never_fail() public view returns (bool) { + function property_never_fail() public view returns (bool) { // ASSERTION: never fail, to keep testing value generation return true; } diff --git a/fuzzing/testdata/contracts/value_generation/match_addr_contract.sol b/fuzzing/testdata/contracts/value_generation/match_addr_contract.sol index 27654a16..9a5cdb7e 100644 --- a/fuzzing/testdata/contracts/value_generation/match_addr_contract.sol +++ b/fuzzing/testdata/contracts/value_generation/match_addr_contract.sol @@ -6,7 +6,7 @@ contract TestContract { a = value; } - function fuzz_never_specific_values() public view returns (bool) { + function property_never_specific_values() public view returns (bool) { // ASSERTION: a should not be the contract's address itself. return !(a == address(this)); } diff --git a/fuzzing/testdata/contracts/value_generation/match_addr_exact.sol b/fuzzing/testdata/contracts/value_generation/match_addr_exact.sol index 385e2bfb..b46d594e 100644 --- a/fuzzing/testdata/contracts/value_generation/match_addr_exact.sol +++ b/fuzzing/testdata/contracts/value_generation/match_addr_exact.sol @@ -12,7 +12,7 @@ contract TestContract { y = value; } - function fuzz_never_specific_values() public view returns (bool) { + function property_never_specific_values() public view returns (bool) { // ASSERTION: x and y should not equal the exact addresses below. return !(x == address(0x12345) && y == address(7)); } diff --git a/fuzzing/testdata/contracts/value_generation/match_addr_sender.sol b/fuzzing/testdata/contracts/value_generation/match_addr_sender.sol index 6be91398..b67d80e4 100644 --- a/fuzzing/testdata/contracts/value_generation/match_addr_sender.sol +++ b/fuzzing/testdata/contracts/value_generation/match_addr_sender.sol @@ -8,7 +8,7 @@ contract TestContract { sender = msg.sender; } - function fuzz_never_specific_values() public view returns (bool) { + function property_never_specific_values() public view returns (bool) { // ASSERTION: a should not be sender's address who set it. return a != sender; } diff --git a/fuzzing/testdata/contracts/value_generation/match_ints_xy.sol b/fuzzing/testdata/contracts/value_generation/match_ints_xy.sol index 2cb5ca2b..3aeff78e 100644 --- a/fuzzing/testdata/contracts/value_generation/match_ints_xy.sol +++ b/fuzzing/testdata/contracts/value_generation/match_ints_xy.sol @@ -12,7 +12,7 @@ contract TestContract { } - function fuzz_never_specific_values() public view returns (bool) { + function property_never_specific_values() public view returns (bool) { // ASSERTION: x should never be -10 at the same time y is -62 return !(x == -10 && y == -62); } diff --git a/fuzzing/testdata/contracts/value_generation/match_payable_xy.sol b/fuzzing/testdata/contracts/value_generation/match_payable_xy.sol index 68221d87..b885c42e 100644 --- a/fuzzing/testdata/contracts/value_generation/match_payable_xy.sol +++ b/fuzzing/testdata/contracts/value_generation/match_payable_xy.sol @@ -11,7 +11,7 @@ contract TestContract { paidAmount2 = msg.value; } - function fuzz_never_pay_exact_amounts() public view returns (bool) { + function property_never_pay_exact_amounts() public view returns (bool) { // ASSERTION: paid amounts should never equal the exact numbers below. return !(paidAmount == 7777 && paidAmount2 == 8888); } diff --git a/fuzzing/testdata/contracts/value_generation/match_string_exact.sol b/fuzzing/testdata/contracts/value_generation/match_string_exact.sol index 6dda6770..80f15fae 100644 --- a/fuzzing/testdata/contracts/value_generation/match_string_exact.sol +++ b/fuzzing/testdata/contracts/value_generation/match_string_exact.sol @@ -6,7 +6,7 @@ contract TestContract { s = value; } - function fuzz_never_specific_values() public view returns (bool) { + function property_never_specific_values() public view returns (bool) { // ASSERTION: s should not be the MAGIC_STRING return keccak256(abi.encodePacked((s))) != keccak256(abi.encodePacked((MAGIC_STRING))); } diff --git a/fuzzing/testdata/contracts/value_generation/match_structs_xy.sol b/fuzzing/testdata/contracts/value_generation/match_structs_xy.sol index cc6d8495..817e079b 100644 --- a/fuzzing/testdata/contracts/value_generation/match_structs_xy.sol +++ b/fuzzing/testdata/contracts/value_generation/match_structs_xy.sol @@ -23,7 +23,7 @@ contract TestContract { s = ts; } - function fuzz_never_specific_values() public view returns (bool) { + function property_never_specific_values() public view returns (bool) { // ASSERTION: x should never be 10 at the same time y is 80 return !(s.x == 10 && s.i.y == 80); } diff --git a/fuzzing/testdata/contracts/value_generation/match_uints_xy.sol b/fuzzing/testdata/contracts/value_generation/match_uints_xy.sol index a064f423..d465708a 100644 --- a/fuzzing/testdata/contracts/value_generation/match_uints_xy.sol +++ b/fuzzing/testdata/contracts/value_generation/match_uints_xy.sol @@ -12,7 +12,7 @@ contract TestContract { } - function fuzz_never_specific_values() public view returns (bool) { + function property_never_specific_values() public view returns (bool) { // ASSERTION: x should never be 10 at the same time y is 80 return !(x == 10 && y == 80); } diff --git a/fuzzing/testdata/contracts/vm_tests/block_hash_store_check.sol b/fuzzing/testdata/contracts/vm_tests/block_hash_store_check.sol index d9f3d8df..cbc757b8 100644 --- a/fuzzing/testdata/contracts/vm_tests/block_hash_store_check.sol +++ b/fuzzing/testdata/contracts/vm_tests/block_hash_store_check.sol @@ -49,7 +49,7 @@ contract TestContract { lastBlockNumber = block.number; } - function fuzz_violate_block_hash_continuity() public view returns (bool) { + function property_violate_block_hash_continuity() public view returns (bool) { // ASSERTION: we fail if our blockHash works as expected so our fuzzer will catch it. return !failedTest; } diff --git a/fuzzing/testdata/contracts/vm_tests/block_number_increasing.sol b/fuzzing/testdata/contracts/vm_tests/block_number_increasing.sol index cbcecab4..b8a6450c 100644 --- a/fuzzing/testdata/contracts/vm_tests/block_number_increasing.sol +++ b/fuzzing/testdata/contracts/vm_tests/block_number_increasing.sol @@ -10,7 +10,7 @@ contract TestContract { // This method does nothing but is left exposed so it can be called by the fuzzer to advance block.number } - function fuzz_increase_block_number_by_10() public view returns (bool) { + function property_increase_block_number_by_10() public view returns (bool) { // ASSERTION: block number should never increase more than 10 (we expect failure) return !(block.number - startingBlockNumber >= 10); } diff --git a/fuzzing/testdata/contracts/vm_tests/block_timestamp_increasing.sol b/fuzzing/testdata/contracts/vm_tests/block_timestamp_increasing.sol index f14bbaa7..7d6b0961 100644 --- a/fuzzing/testdata/contracts/vm_tests/block_timestamp_increasing.sol +++ b/fuzzing/testdata/contracts/vm_tests/block_timestamp_increasing.sol @@ -10,7 +10,7 @@ contract TestContract { // This method does nothing but is left exposed so it can be called by the fuzzer to advance blocks/timestamps. } - function fuzz_increase_block_timestamp() public view returns (bool) { + function property_increase_block_timestamp() public view returns (bool) { // ASSERTION: block timestamp should never increase more than 10 (we expect failure) return !(block.timestamp - startingBlockTimestamp >= 10); } diff --git a/fuzzing/utils/fuzz_method_utils.go b/fuzzing/utils/fuzz_method_utils.go new file mode 100644 index 00000000..1174246b --- /dev/null +++ b/fuzzing/utils/fuzz_method_utils.go @@ -0,0 +1,34 @@ +package utils + +import ( + "github.com/ethereum/go-ethereum/accounts/abi" + "strings" +) + +// IsOptimizationTest checks whether the method is an optimization test given potential naming prefixes it must conform to +// and its underlying input/output arguments. +func IsOptimizationTest(method abi.Method, prefixes []string) bool { + // Loop through all enabled prefixes to find a match + for _, prefix := range prefixes { + if strings.HasPrefix(method.Name, prefix) { + // An optimization test must take no inputs and return an int256 + if len(method.Inputs) == 0 && len(method.Outputs) == 1 && method.Outputs[0].Type.T == abi.IntTy && method.Outputs[0].Type.Size == 256 { + return true + } + } + } + return false +} + +// IsPropertyTest checks whether the method is a property test given potential naming prefixes it must conform to +// and its underlying input/output arguments. +func IsPropertyTest(method abi.Method, prefixes []string) bool { + // Loop through all enabled prefixes to find a match + for _, prefix := range prefixes { + // The property test must simply have the right prefix and take no inputs + if strings.HasPrefix(method.Name, prefix) && len(method.Inputs) == 0 { + return true + } + } + return false +} diff --git a/fuzzing/valuegeneration/abi_values.go b/fuzzing/valuegeneration/abi_values.go index 56232e9d..616c8305 100644 --- a/fuzzing/valuegeneration/abi_values.go +++ b/fuzzing/valuegeneration/abi_values.go @@ -443,21 +443,17 @@ func encodeABIArgumentToString(inputType *abi.Type, value any) (string, error) { } return strconv.QuoteToASCII(str), nil case abi.BytesTy: - // Prepare a byte array. Return as a string enclosed with "". The returned string uses Go escape - // sequences (\t, \n, \xFF, \u0100) for non-ASCII characters and non-printable characters. b, ok := value.([]byte) if !ok { return "", fmt.Errorf("could not encode dynamic-sized bytes as the value provided is not of the correct type") } - return strconv.QuoteToASCII(string(b)), nil + // Convert the fixed byte array to a hex string + return hex.EncodeToString(b), nil case abi.FixedBytesTy: - // Prepare a fixed-size byte array. Return as a string enclosed with "". The returned string uses Go escape - // sequences (\t, \n, \xFF, \u0100) for non-ASCII characters and non-printable characters. - // TODO: Error checking to ensure `value` is of the correct type. b := reflectionutils.ArrayToSlice(reflect.ValueOf(value)).([]byte) - // Convert the byte array to a string and use the QuoteToASCII method to format the string with Go escape sequences. - return strconv.QuoteToASCII(string(b)), nil + // Convert the byte array to a hex string + return hex.EncodeToString(b), nil case abi.ArrayTy: // Prepare an array. Return as a string enclosed with [], where specific elements are comma-separated. reflectedArray := reflect.ValueOf(value) diff --git a/go.mod b/go.mod index 27b9f68c..45b23f24 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,8 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/deckarep/golang-set/v2 v2.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect + github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e // indirect + github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 // indirect github.com/getsentry/sentry-go v0.18.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-stack/stack v1.8.1 // indirect @@ -64,6 +66,8 @@ require ( github.com/tklauser/numcpus v0.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect + golang.org/x/mod v0.9.0 // indirect + golang.org/x/tools v0.7.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect diff --git a/go.sum b/go.sum index 4c98ad4e..bb098adc 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqR github.com/VictoriaMetrics/fastcache v1.12.0 h1:vnVi/y9yKDcD9akmc4NqAoqgQhJrOwUF+j9LTgn4QDE= github.com/VictoriaMetrics/fastcache v1.12.0/go.mod h1:tjiYeEfYXCqacuvYw/7UoDIeJaNxq6132xHICNP77w8= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -38,6 +40,7 @@ github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoG github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lgvnQ2+oGlE/EyZy4+2/CxRh9KfvCXnag1vtpxVM= github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= @@ -54,6 +57,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= @@ -67,11 +71,15 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e h1:bBLctRc7kr01YGvaDfgLbTwjFNW5jdp5y5rj8XXBHfY= +github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fxamacker/cbor v1.5.1 h1:XjQWBgdmQyqimslUh5r4tUGmoqzHmBFQOImkWGi2awg= github.com/fxamacker/cbor v1.5.1/go.mod h1:3aPGItF174ni7dDzd6JZ206H8cmr4GDNBGpPa971zsU= +github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 h1:IZqZOB2fydHte3kUgxrzK5E1fW7RQGeDwE8F/ZZnUYc= +github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61/go.mod h1:Q0X6pkwTILDlzrGEckF6HKjXe48EgsY/l7K7vhY4MW8= github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= @@ -81,6 +89,7 @@ github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/ github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= @@ -152,9 +161,12 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/ github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= @@ -207,7 +219,9 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= @@ -275,6 +289,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -334,6 +349,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -423,6 +440,8 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -454,6 +473,7 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -472,6 +492,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/logging/logger.go b/logging/logger.go index 48d5c539..7a6c09e4 100644 --- a/logging/logger.go +++ b/logging/logger.go @@ -5,7 +5,6 @@ import ( "github.com/crytic/medusa/logging/colors" "github.com/rs/zerolog" "io" - "os" "strings" ) @@ -57,11 +56,11 @@ type StructuredLogInfo map[string]any func NewLogger(level zerolog.Level) *Logger { return &Logger{ level: level, - structuredLogger: zerolog.New(os.Stdout).Level(zerolog.Disabled), + structuredLogger: zerolog.New(nil).Level(level), structuredWriters: make([]io.Writer, 0), - unstructuredLogger: zerolog.New(os.Stdout).Level(zerolog.Disabled), + unstructuredLogger: zerolog.New(nil).Level(level), unstructuredWriters: make([]io.Writer, 0), - unstructuredColorLogger: zerolog.New(os.Stdout).Level(zerolog.Disabled), + unstructuredColorLogger: zerolog.New(nil).Level(level), unstructuredColorWriters: make([]io.Writer, 0), } } @@ -206,6 +205,7 @@ func (l *Logger) SetLevel(level zerolog.Level) { l.structuredLogger = l.structuredLogger.Level(level) l.unstructuredColorLogger = l.unstructuredColorLogger.Level(level) l.unstructuredLogger = l.unstructuredLogger.Level(level) + } // Trace is a wrapper function that will log a trace event From 57cc20cee30d1b8571a96a907ca1fa1067f2acdc Mon Sep 17 00:00:00 2001 From: anishnaik Date: Tue, 20 Feb 2024 23:53:16 -0500 Subject: [PATCH 30/55] fix OOB panic (#303) --- fuzzing/coverage/coverage_maps.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fuzzing/coverage/coverage_maps.go b/fuzzing/coverage/coverage_maps.go index e9a1343c..1e8a48d6 100644 --- a/fuzzing/coverage/coverage_maps.go +++ b/fuzzing/coverage/coverage_maps.go @@ -344,10 +344,9 @@ func (cm *CoverageMapBytecodeData) update(coverageMap *CoverageMapBytecodeData) return true, nil } - // Update each byte which represents a position in the bytecode which was covered. We ignore any size - // differences as init bytecode can have arbitrary length arguments appended. + // Update each byte which represents a position in the bytecode which was covered. changed := false - for i := 0; i < len(cm.executedFlags) || i < len(coverageMap.executedFlags); i++ { + for i := 0; i < len(cm.executedFlags) && i < len(coverageMap.executedFlags); i++ { if cm.executedFlags[i] == 0 && coverageMap.executedFlags[i] != 0 { cm.executedFlags[i] = 1 changed = true From 6692150ab4aecb631aaa1bfb0ae63abe1567d27d Mon Sep 17 00:00:00 2001 From: David Pokora Date: Mon, 26 Feb 2024 12:11:50 -0800 Subject: [PATCH 31/55] Added a unique exit code for failed tests, added generic way to bubble up errors with exit codes from cmd, organized exit code definitions into one package (#301) Co-authored-by: anishnaik --- cmd/exitcodes/error_with_exit_code.go | 37 +++++++++++++++++++++++++++ cmd/exitcodes/exit_codes.go | 21 +++++++++++++++ cmd/fuzz.go | 6 +++++ main.go | 6 ++--- 4 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 cmd/exitcodes/error_with_exit_code.go create mode 100644 cmd/exitcodes/exit_codes.go diff --git a/cmd/exitcodes/error_with_exit_code.go b/cmd/exitcodes/error_with_exit_code.go new file mode 100644 index 00000000..eb2367a7 --- /dev/null +++ b/cmd/exitcodes/error_with_exit_code.go @@ -0,0 +1,37 @@ +package exitcodes + +// ErrorWithExitCode is an `error` type that wraps an existing error and exit code, providing exit codes +// for a given error if they are bubbled up to the top-level. +type ErrorWithExitCode struct { + err error + exitCode int +} + +// NewErrorWithExitCode creates a new error (ErrorWithExitCode) with the provided internal error and exit code. +func NewErrorWithExitCode(err error, exitCode int) *ErrorWithExitCode { + return &ErrorWithExitCode{ + err: err, + exitCode: exitCode, + } +} + +// Error returns the error message string, implementing the `error` interface. +func (e *ErrorWithExitCode) Error() string { + return e.err.Error() +} + +// GetErrorExitCode checks the given exit code that the application should exit with, if this error is bubbled to +// the top-level. This will be 0 for a nil error, 1 for a generic error, or arbitrary if the error is of type +// ErrorWithExitCode. +// Returns the exit code associated with the error. +func GetErrorExitCode(err error) int { + // If we have no error, return 0, if we have a generic error, return 1, if we have a custom error code, unwrap + // and return it. + if err == nil { + return ExitCodeSuccess + } else if unwrappedErr, ok := err.(*ErrorWithExitCode); ok { + return unwrappedErr.exitCode + } else { + return ExitCodeGeneralError + } +} diff --git a/cmd/exitcodes/exit_codes.go b/cmd/exitcodes/exit_codes.go new file mode 100644 index 00000000..cb6c7c98 --- /dev/null +++ b/cmd/exitcodes/exit_codes.go @@ -0,0 +1,21 @@ +package exitcodes + +const ( + // ================================ + // Platform-universal exit codes + // ================================ + + // ExitCodeSuccess indicates no errors or failures had occurred. + ExitCodeSuccess = 0 + + // ExitCodeGeneralError indicates some type of general error occurred. + ExitCodeGeneralError = 1 + + // ================================ + // Application-specific exit codes + // ================================ + // Note: Despite not being standardized, exit codes 2-5 are often used for common use cases, so we avoid them. + + // ExitCodeTestFailed indicates a test case had failed. + ExitCodeTestFailed = 7 +) diff --git a/cmd/fuzz.go b/cmd/fuzz.go index 3fc22fd3..28453ae8 100644 --- a/cmd/fuzz.go +++ b/cmd/fuzz.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/crytic/medusa/cmd/exitcodes" "github.com/crytic/medusa/logging/colors" "os" "os/signal" @@ -160,5 +161,10 @@ func cmdRunFuzz(cmd *cobra.Command, args []string) error { // Start the fuzzing process with our cancellable context. err = fuzzer.Start() + // If we have no error and failed test cases, we'll want to return a special exit code + if err == nil && len(fuzzer.TestCasesWithStatus(fuzzing.TestCaseStatusFailed)) > 0 { + return exitcodes.NewErrorWithExitCode(err, exitcodes.ExitCodeTestFailed) + } + return err } diff --git a/main.go b/main.go index 4902eaeb..ad2537e1 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "github.com/crytic/medusa/cmd" + "github.com/crytic/medusa/cmd/exitcodes" "os" ) @@ -9,7 +10,6 @@ func main() { // Run our root CLI command, which contains all underlying command logic and will handle parsing/invocation. err := cmd.Execute() - if err != nil { - os.Exit(1) - } + // Determine the exit code from any potential error and exit out. + os.Exit(exitcodes.GetErrorExitCode(err)) } From 3320811c37a7f0ada96d20ad647eee941baded1a Mon Sep 17 00:00:00 2001 From: David Pokora Date: Wed, 28 Feb 2024 13:58:42 -0800 Subject: [PATCH 32/55] Fix corpus call method resolution bug, improve startup logging (#308) * * Fixed a corpus call method resolution bug. * Deprecated "methodName" in corpus (lack of support for function overloading). To be removed later, in favor of new "methodSignature" key. * Improved fuzzer initialization logging * Print basic metrics for corpus health on startup * Reorder printing to avoid "Creating X workers" message after "fuzz: elapsed[...]" message. * Update corpus health log for readability --------- Co-authored-by: anishnaik --- fuzzing/calls/call_message_abi_values.go | 44 +++++++++++++++++++----- fuzzing/calls/call_sequence.go | 12 ++++++- fuzzing/corpus/corpus.go | 21 +++++++---- fuzzing/fuzzer.go | 21 +++++++++-- 4 files changed, 79 insertions(+), 19 deletions(-) diff --git a/fuzzing/calls/call_message_abi_values.go b/fuzzing/calls/call_message_abi_values.go index 1f3bbdbf..8aec6da3 100644 --- a/fuzzing/calls/call_message_abi_values.go +++ b/fuzzing/calls/call_message_abi_values.go @@ -6,6 +6,7 @@ import ( "github.com/crytic/medusa/fuzzing/valuegeneration" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" ) // CallMessageDataAbiValues describes a CallMessage Data field which is represented by ABI input argument values. @@ -22,8 +23,17 @@ type CallMessageDataAbiValues struct { // methodName stores the name of Method when decoding from JSON. The Method will be resolved using this internal // reference when Resolve is called. + // + // TODO: Note, this field is deprecated and should be removed after methodSignature is adopted for some time. + // This will help transition old corpuses in the meantime. methodName string + // methodSignature stores the function prototype which is used to calculate the method ID. This is human-readable, + // and easily editable, so it is used in favor of the method ID derived from it. + // + // The Method will be resolved using this internal reference when Resolve is called. + methodSignature string + // encodedInputValues stores the raw encoded input values when decoding from JSON. The actual InputValues will be // decoded using this and the resolved Method once Resolve is called. encodedInputValues []any @@ -32,7 +42,8 @@ type CallMessageDataAbiValues struct { // callMessageDataAbiValuesMarshal is used as an internal struct to represent JSON serialized data for // CallMessageDataAbiValues. type callMessageDataAbiValuesMarshal struct { - MethodName string `json:"methodName"` + MethodName string `json:"methodName,omitempty"` + MethodSignature string `json:"methodSignature"` EncodedInputValues []any `json:"inputValues"` } @@ -43,6 +54,7 @@ func (m *CallMessageDataAbiValues) Clone() (*CallMessageDataAbiValues, error) { Method: m.Method, InputValues: nil, // set lower methodName: m.methodName, + methodSignature: m.methodSignature, encodedInputValues: m.encodedInputValues, } @@ -65,17 +77,32 @@ func (m *CallMessageDataAbiValues) Clone() (*CallMessageDataAbiValues, error) { // Resolve takes a previously unmarshalled CallMessageDataAbiValues and resolves all internal data needed for it to be // used at runtime by resolving the abi.Method it references from the provided contract ABI. func (d *CallMessageDataAbiValues) Resolve(contractAbi abi.ABI) error { - // Try to resolve the method from our contract ABI. - if resolvedMethod, ok := contractAbi.Methods[d.methodName]; ok { - d.Method = &resolvedMethod - } else { - return fmt.Errorf("could not resolve method '%v' from the given contract ABI", d.methodName) + // If we have a method signature, try to resolve it by calculating a method ID from this. + d.Method = nil + if d.methodSignature != "" { + methodId := crypto.Keccak256([]byte(d.methodSignature))[:4] + if resolvedMethod, err := contractAbi.MethodById(methodId); err == nil { + d.Method = resolvedMethod + } else { + return fmt.Errorf("could not resolve method signature '%v'", d.methodSignature) + } } + // TODO: Deprecated old way of resolving methods. This is left for compatibility with old corpuses, but should be + // removed at a later date in favor of methodSignature resolution. It resolves a method by name if it has not been. + if d.Method == nil { + if resolvedMethod, ok := contractAbi.Methods[d.methodName]; ok { + d.Method = &resolvedMethod + } else { + return fmt.Errorf("could not resolve method name '%v'", d.methodName) + } + } + d.methodSignature = d.Method.Sig + // Now that we've resolved the method, decode our encoded input values. decodedArguments, err := valuegeneration.DecodeJSONArgumentsFromSlice(d.Method.Inputs, d.encodedInputValues, make(map[string]common.Address)) if err != nil { - return err + return fmt.Errorf("error decoding arguments for method '%v': %v", d.methodSignature, err) } // If we've decoded arguments successfully, set them and clear our encoded arguments as they're no longer needed. @@ -132,7 +159,7 @@ func (d *CallMessageDataAbiValues) MarshalJSON() ([]byte, error) { // Now create our outer struct and marshal all the data and return it. marshalData := callMessageDataAbiValuesMarshal{ - MethodName: d.Method.Name, + MethodSignature: d.Method.Sig, EncodedInputValues: inputValuesEncoded, } return json.Marshal(marshalData) @@ -150,6 +177,7 @@ func (d *CallMessageDataAbiValues) UnmarshalJSON(b []byte) error { // Set our data in our actual structure now d.methodName = marshalData.MethodName + d.methodSignature = marshalData.MethodSignature d.encodedInputValues = marshalData.EncodedInputValues return nil } diff --git a/fuzzing/calls/call_sequence.go b/fuzzing/calls/call_sequence.go index 53ae874e..1321da28 100644 --- a/fuzzing/calls/call_sequence.go +++ b/fuzzing/calls/call_sequence.go @@ -205,7 +205,17 @@ func (cse *CallSequenceElement) Method() (*abi.Method, error) { if cse.Contract == nil { return nil, nil } - return cse.Contract.CompiledContract().Abi.MethodById(cse.Call.Data) + + // If we have a method resolved, return it. + if cse.Call != nil && cse.Call.DataAbiValues != nil { + if cse.Call.DataAbiValues.Method != nil { + return cse.Call.DataAbiValues.Method, nil + } + } + + // Try to resolve the method by ID from the call data. + method, err := cse.Contract.CompiledContract().Abi.MethodById(cse.Call.Data) + return method, err } // String returns a displayable string representing the CallSequenceElement. diff --git a/fuzzing/corpus/corpus.go b/fuzzing/corpus/corpus.go index 9cc4cda6..a452ddea 100644 --- a/fuzzing/corpus/corpus.go +++ b/fuzzing/corpus/corpus.go @@ -189,6 +189,7 @@ func (c *Corpus) initializeSequences(sequenceFiles *corpusDirectory[calls.CallSe if callAbiValues != nil { sequenceInvalidError = callAbiValues.Resolve(currentSequenceElement.Contract.CompiledContract().Abi) if sequenceInvalidError != nil { + sequenceInvalidError = fmt.Errorf("error resolving method in contract '%v': %v", currentSequenceElement.Contract.Name(), sequenceInvalidError) return nil, nil } } @@ -236,7 +237,9 @@ func (c *Corpus) initializeSequences(sequenceFiles *corpusDirectory[calls.CallSe // Initialize initializes any runtime data needed for a Corpus on startup. Call sequences are replayed on the post-setup // (deployment) test chain to calculate coverage, while resolving references to compiled contracts. -func (c *Corpus) Initialize(baseTestChain *chain.TestChain, contractDefinitions contracts.Contracts) error { +// Returns the active number of corpus items, total number of corpus items, or an error if one occurred. If an error +// is returned, then the corpus counts returned will always be zero. +func (c *Corpus) Initialize(baseTestChain *chain.TestChain, contractDefinitions contracts.Contracts) (int, int, error) { // Acquire our call sequences lock during the duration of this method. c.callSequencesLock.Lock() defer c.callSequencesLock.Unlock() @@ -273,7 +276,7 @@ func (c *Corpus) Initialize(baseTestChain *chain.TestChain, contractDefinitions return nil }) if err != nil { - return fmt.Errorf("failed to initialize coverage maps, base test chain cloning encountered error: %v", err) + return 0, 0, fmt.Errorf("failed to initialize coverage maps, base test chain cloning encountered error: %v", err) } // Set our coverage maps to those collected when replaying all blocks when cloning. @@ -283,7 +286,7 @@ func (c *Corpus) Initialize(baseTestChain *chain.TestChain, contractDefinitions covMaps := coverage.GetCoverageTracerResults(messageResults) _, _, covErr := c.coverageMaps.Update(covMaps) if covErr != nil { - return err + return 0, 0, err } } } @@ -292,18 +295,22 @@ func (c *Corpus) Initialize(baseTestChain *chain.TestChain, contractDefinitions // are added to the corpus for mutations, re-execution, etc. err = c.initializeSequences(c.mutableSequenceFiles, testChain, deployedContracts, true) if err != nil { - return err + return 0, 0, err } err = c.initializeSequences(c.immutableSequenceFiles, testChain, deployedContracts, false) if err != nil { - return err + return 0, 0, err } err = c.initializeSequences(c.testResultSequenceFiles, testChain, deployedContracts, false) if err != nil { - return err + return 0, 0, err } - return nil + // Calculate corpus health metrics + corpusSequencesTotal := len(c.mutableSequenceFiles.files) + len(c.immutableSequenceFiles.files) + len(c.testResultSequenceFiles.files) + corpusSequencesActive := len(c.unexecutedCallSequences) + + return corpusSequencesActive, corpusSequencesTotal, nil } // addCallSequence adds a call sequence to the corpus in a given corpus directory. diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index f6caecbc..de93d742 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -513,8 +513,7 @@ func (f *Fuzzer) spawnWorkersLoop(baseTestChain *chain.TestChain) error { // Define a flag that indicates whether we have not cancelled o working := !utils.CheckContextDone(f.ctx) - // Log that we are about to create the workers and start fuzzing - f.logger.Info("Creating ", colors.Bold, f.config.Fuzzing.Workers, colors.Reset, " workers...") + // Create workers and start fuzzing. var err error for err == nil && working { // Send an item into our channel to queue up a spot. This will block us if we hit capacity until a worker @@ -617,6 +616,7 @@ func (f *Fuzzer) Start() error { } // Set up the corpus + f.logger.Info("Initializing corpus") f.corpus, err = corpus.NewCorpus(f.config.Fuzzing.CorpusDirectory) if err != nil { f.logger.Error("Failed to create the corpus", err) @@ -640,6 +640,7 @@ func (f *Fuzzer) Start() error { } // Set it up with our deployment/setup strategy defined by the fuzzer. + f.logger.Info("Setting up base chain") err = f.Hooks.ChainSetupFunc(f, baseTestChain) if err != nil { f.logger.Error("Failed to initialize the test chain", err) @@ -647,12 +648,26 @@ func (f *Fuzzer) Start() error { } // Initialize our coverage maps by measuring the coverage we get from the corpus. - err = f.corpus.Initialize(baseTestChain, f.contractDefinitions) + var corpusActiveSequences, corpusTotalSequences int + f.logger.Info("Initializing and validating corpus call sequences") + corpusActiveSequences, corpusTotalSequences, err = f.corpus.Initialize(baseTestChain, f.contractDefinitions) if err != nil { f.logger.Error("Failed to initialize the corpus", err) return err } + // Log corpus health statistics, if we have any existing sequences. + if corpusTotalSequences > 0 { + f.logger.Info( + colors.Bold, "corpus: ", colors.Reset, + "health: ", colors.Bold, int(float32(corpusActiveSequences)/float32(corpusTotalSequences)*100.0), "%", colors.Reset, ", ", + "sequences: ", colors.Bold, corpusTotalSequences, " (", corpusActiveSequences, " valid, ", corpusTotalSequences-corpusActiveSequences, " invalid)", colors.Reset, + ) + } + + // Log the start of our fuzzing campaign. + f.logger.Info("Fuzzing with ", colors.Bold, f.config.Fuzzing.Workers, colors.Reset, " workers") + // Start our printing loop now that we're about to begin fuzzing. go f.printMetricsLoop() From c4b9d98ad920ea7fcb5ec6065fe9c3a7ce92ad2e Mon Sep 17 00:00:00 2001 From: David Pokora Date: Wed, 28 Feb 2024 14:43:48 -0800 Subject: [PATCH 33/55] Add shrinking limits (#297) * Add shrinking limits, improve "call removal" logic, fixed a bug where corpus results could re-record if replayed/reshrunk differently * Removed comment indicating shrinkLimit must be greater than zero (untrue) * Re-order unexecuted sequences so test result corpus items are replayed first * change where shrinkIncrement is updated --------- Co-authored-by: Anish Naik --- fuzzing/config/config.go | 7 +- fuzzing/config/config_defaults.go | 1 + fuzzing/corpus/corpus.go | 12 +- fuzzing/fuzzer.go | 2 + fuzzing/fuzzer_metrics.go | 14 +++ fuzzing/fuzzer_worker.go | 128 ++++++++++++-------- fuzzing/fuzzer_worker_sequence_generator.go | 2 +- 7 files changed, 107 insertions(+), 59 deletions(-) diff --git a/fuzzing/config/config.go b/fuzzing/config/config.go index cfdde41d..7ccae530 100644 --- a/fuzzing/config/config.go +++ b/fuzzing/config/config.go @@ -38,14 +38,17 @@ type FuzzingConfig struct { // so that memory from its underlying chain is freed. WorkerResetLimit int `json:"workerResetLimit"` - // Timeout describes a time in seconds for which the fuzzing operation should run. Providing negative or zero value - // will result in no timeout. + // Timeout describes a time threshold in seconds for which the fuzzing operation should run. Providing negative or + // zero value will result in no timeout. Timeout int `json:"timeout"` // TestLimit describes a threshold for the number of transactions to test, after which it will exit. This number // must be non-negative. A zero value indicates the test limit should not be enforced. TestLimit uint64 `json:"testLimit"` + // ShrinkLimit describes a threshold for the iterations (call sequence tests) which shrinking should perform. + ShrinkLimit uint64 `json:"shrinkLimit"` + // CallSequenceLength describes the maximum length a transaction sequence can be generated as. CallSequenceLength int `json:"callSequenceLength"` diff --git a/fuzzing/config/config_defaults.go b/fuzzing/config/config_defaults.go index 51e79ffb..65ca6f04 100644 --- a/fuzzing/config/config_defaults.go +++ b/fuzzing/config/config_defaults.go @@ -39,6 +39,7 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) { WorkerResetLimit: 50, Timeout: 0, TestLimit: 0, + ShrinkLimit: 5_000, CallSequenceLength: 100, TargetContracts: []string{}, TargetContractsBalances: []*big.Int{}, diff --git a/fuzzing/corpus/corpus.go b/fuzzing/corpus/corpus.go index a452ddea..819da24a 100644 --- a/fuzzing/corpus/corpus.go +++ b/fuzzing/corpus/corpus.go @@ -293,15 +293,21 @@ func (c *Corpus) Initialize(baseTestChain *chain.TestChain, contractDefinitions // Next we replay every call sequence, checking its validity on this chain and measuring coverage. Valid sequences // are added to the corpus for mutations, re-execution, etc. - err = c.initializeSequences(c.mutableSequenceFiles, testChain, deployedContracts, true) + // + // The order of initializations here is important, as it determines the order of "unexecuted sequences" to replay + // when the fuzzer's worker starts up. We want to replay test results first, so that other corpus items + // do not trigger the same test failures instead. + err = c.initializeSequences(c.testResultSequenceFiles, testChain, deployedContracts, false) if err != nil { return 0, 0, err } - err = c.initializeSequences(c.immutableSequenceFiles, testChain, deployedContracts, false) + + err = c.initializeSequences(c.mutableSequenceFiles, testChain, deployedContracts, true) if err != nil { return 0, 0, err } - err = c.initializeSequences(c.testResultSequenceFiles, testChain, deployedContracts, false) + + err = c.initializeSequences(c.immutableSequenceFiles, testChain, deployedContracts, false) if err != nil { return 0, 0, err } diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index de93d742..f28660c0 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -752,6 +752,7 @@ func (f *Fuzzer) printMetricsLoop() { callsTested := f.metrics.CallsTested() sequencesTested := f.metrics.SequencesTested() workerStartupCount := f.metrics.WorkerStartupCount() + workersShrinking := f.metrics.WorkersShrinkingCount() // Calculate time elapsed since the last update secondsSinceLastUpdate := time.Since(lastPrintedTime).Seconds() @@ -770,6 +771,7 @@ func (f *Fuzzer) printMetricsLoop() { logBuffer.Append(", seq/s: ", colors.Bold, fmt.Sprintf("%d", uint64(float64(new(big.Int).Sub(sequencesTested, lastSequencesTested).Uint64())/secondsSinceLastUpdate)), colors.Reset) logBuffer.Append(", coverage: ", colors.Bold, fmt.Sprintf("%d", f.corpus.ActiveMutableSequenceCount()), colors.Reset) if f.logger.Level() <= zerolog.DebugLevel { + logBuffer.Append(", shrinking: ", colors.Bold, fmt.Sprintf("%v", workersShrinking), colors.Reset) logBuffer.Append(", mem: ", colors.Bold, fmt.Sprintf("%v/%v MB", memoryUsedMB, memoryTotalMB), colors.Reset) logBuffer.Append(", resets/s: ", colors.Bold, fmt.Sprintf("%d", uint64(float64(new(big.Int).Sub(workerStartupCount, lastWorkerStartupCount).Uint64())/secondsSinceLastUpdate)), colors.Reset) } diff --git a/fuzzing/fuzzer_metrics.go b/fuzzing/fuzzer_metrics.go index 9d64c91a..70fc3788 100644 --- a/fuzzing/fuzzer_metrics.go +++ b/fuzzing/fuzzer_metrics.go @@ -19,6 +19,9 @@ type fuzzerWorkerMetrics struct { // workerStartupCount describes the amount of times the worker was generated, or re-generated for this index. workerStartupCount *big.Int + + // shrinking indicates whether the fuzzer worker is currently shrinking. + shrinking bool } // newFuzzerMetrics obtains a new FuzzerMetrics struct for a given number of workers specified by workerCount. @@ -63,3 +66,14 @@ func (m *FuzzerMetrics) WorkerStartupCount() *big.Int { } return workerStartupCount } + +// WorkersShrinkingCount returns the amount of workers currently performing shrinking operations. +func (m *FuzzerMetrics) WorkersShrinkingCount() uint64 { + shrinkingCount := uint64(0) + for _, workerMetrics := range m.workerMetrics { + if workerMetrics.shrinking { + shrinkingCount++ + } + } + return shrinkingCount +} diff --git a/fuzzing/fuzzer_worker.go b/fuzzing/fuzzer_worker.go index a7ac3f4d..7ee4e93a 100644 --- a/fuzzing/fuzzer_worker.go +++ b/fuzzing/fuzzer_worker.go @@ -315,8 +315,8 @@ func (fw *FuzzerWorker) testNextCallSequence() (calls.CallSequence, []ShrinkCall // If this was not a new call sequence, indicate not to save the shrunken result to the corpus again. if !isNewSequence { - for _, shrinkRequest := range shrinkCallSequenceRequests { - shrinkRequest.RecordResultInCorpus = false + for i := 0; i < len(shrinkCallSequenceRequests); i++ { + shrinkCallSequenceRequests[i].RecordResultInCorpus = false } } @@ -391,73 +391,95 @@ func (fw *FuzzerWorker) testShrunkenCallSequence(possibleShrunkSequence calls.Ca // shrinkCallSequence takes a provided call sequence and attempts to shrink it by looking for redundant // calls which can be removed, and values which can be minimized, while continuing to satisfy the provided shrink // verifier. +// +// This function should *always* be called if there are shrink requests, and should always report a result, +// even if it is the original sequence provided. +// // Returns a call sequence that was optimized to include as little calls as possible to trigger the // expected conditions, or an error if one occurred. func (fw *FuzzerWorker) shrinkCallSequence(callSequence calls.CallSequence, shrinkRequest ShrinkCallSequenceRequest) (calls.CallSequence, error) { // Define a variable to track our most optimized sequence across all optimization iterations. optimizedSequence := callSequence - // First try to remove any calls we can. We go from start to end to avoid index shifting. - for i := 0; i < len(optimizedSequence); { - // If our fuzzer context is done, exit out immediately without results. - if utils.CheckContextDone(fw.fuzzer.ctx) { - return nil, nil - } - - // Recreate our current optimized sequence without the item at this index - possibleShrunkSequence, err := optimizedSequence.Clone() - if err != nil { - return nil, err - } - possibleShrunkSequence = append(possibleShrunkSequence[:i], possibleShrunkSequence[i+1:]...) - - // Test the shrunken sequence. - validShrunkSequence, err := fw.testShrunkenCallSequence(possibleShrunkSequence, shrinkRequest) - if err != nil { - return nil, err - } - - // If this current sequence satisfied our conditions, set it as our optimized sequence. - if validShrunkSequence { - optimizedSequence = possibleShrunkSequence - } else { - // We didn't remove an item at this index, so we'll iterate to the next one. - i++ - } + // Obtain our shrink limits and begin shrinking. + shrinkIteration := uint64(0) + shrinkLimit := fw.fuzzer.config.Fuzzing.ShrinkLimit + shrinkingEnded := func() bool { + return shrinkIteration >= shrinkLimit || utils.CheckContextDone(fw.fuzzer.ctx) } + if shrinkLimit > 0 { + // The first pass of shrinking is greedy towards trying to remove any unnecessary calls. + // For each call in the sequence, the following removal strategies are used: + // 1) Plain removal (lower block/time gap between surrounding blocks, maintain properties of max delay) + // 2) Add block/time delay to previous call (retain original block/time, possibly exceed max delays) + // At worst, this costs `2 * len(callSequence)` shrink iterations. + fw.workerMetrics().shrinking = true + for removalStrategy := 0; removalStrategy < 2 && !shrinkingEnded(); removalStrategy++ { + for i := len(optimizedSequence) - 1; i >= 0 && !shrinkingEnded(); i-- { + // Recreate our current optimized sequence without the item at this index + possibleShrunkSequence, err := optimizedSequence.Clone() + removedCall := possibleShrunkSequence[i] + if err != nil { + return nil, err + } + possibleShrunkSequence = append(possibleShrunkSequence[:i], possibleShrunkSequence[i+1:]...) + + // Exercise the next removal strategy for this call. + if removalStrategy == 0 { + // Case 1: Plain removal. + } else if removalStrategy == 1 { + // Case 2: Add block/time delay to previous call. + if i > 0 { + possibleShrunkSequence[i-1].BlockNumberDelay += removedCall.BlockNumberDelay + possibleShrunkSequence[i-1].BlockTimestampDelay += removedCall.BlockTimestampDelay + } + } - // Next try to shrink our values of every transaction a given number of rounds. - for i := 0; i < len(optimizedSequence); i++ { - for optimizationRound := 0; optimizationRound < 200; optimizationRound++ { - // If our fuzzer context is done, exit out immediately without results. - if utils.CheckContextDone(fw.fuzzer.ctx) { - return nil, nil + // Test the shrunken sequence. + validShrunkSequence, err := fw.testShrunkenCallSequence(possibleShrunkSequence, shrinkRequest) + shrinkIteration++ + if err != nil { + return nil, err + } + + // If the current sequence satisfied our conditions, set it as our optimized sequence. + if validShrunkSequence { + optimizedSequence = possibleShrunkSequence + } } + } - // Clone the optimized sequence. - possibleShrunkSequence, _ := optimizedSequence.Clone() + // The second pass of shrinking attempts to shrink values for each call in our call sequence. + // This is performed exhaustively in a round-robin fashion for each call, until the shrink limit is hit. + for !shrinkingEnded() { + for i := len(optimizedSequence) - 1; i >= 0 && !shrinkingEnded(); i-- { + // Clone the optimized sequence. + possibleShrunkSequence, _ := optimizedSequence.Clone() + + // Loop for each argument in the currently indexed call to mutate it. + abiValuesMsgData := possibleShrunkSequence[i].Call.DataAbiValues + for j := 0; j < len(abiValuesMsgData.InputValues); j++ { + mutatedInput, err := valuegeneration.MutateAbiValue(fw.sequenceGenerator.config.ValueGenerator, fw.shrinkingValueMutator, &abiValuesMsgData.Method.Inputs[j].Type, abiValuesMsgData.InputValues[j]) + if err != nil { + return nil, fmt.Errorf("error when shrinking call sequence input argument: %v", err) + } + abiValuesMsgData.InputValues[j] = mutatedInput + } - // Loop for each argument in the currently indexed call to mutate it. - abiValuesMsgData := possibleShrunkSequence[i].Call.DataAbiValues - for j := 0; j < len(abiValuesMsgData.InputValues); j++ { - mutatedInput, err := valuegeneration.MutateAbiValue(fw.sequenceGenerator.config.ValueGenerator, fw.shrinkingValueMutator, &abiValuesMsgData.Method.Inputs[j].Type, abiValuesMsgData.InputValues[j]) + // Test the shrunken sequence. + validShrunkSequence, err := fw.testShrunkenCallSequence(possibleShrunkSequence, shrinkRequest) + shrinkIteration++ if err != nil { - return nil, fmt.Errorf("error when shrinking call sequence input argument: %v", err) + return nil, err } - abiValuesMsgData.InputValues[j] = mutatedInput - } - // Test the shrunken sequence. - validShrunkSequence, err := fw.testShrunkenCallSequence(possibleShrunkSequence, shrinkRequest) - if err != nil { - return nil, err - } - - // If this current sequence satisfied our conditions, set it as our optimized sequence. - if validShrunkSequence { - optimizedSequence = possibleShrunkSequence + // If this current sequence satisfied our conditions, set it as our optimized sequence. + if validShrunkSequence { + optimizedSequence = possibleShrunkSequence + } } } + fw.workerMetrics().shrinking = false } // If the shrink request wanted the sequence recorded in the corpus, do so now. diff --git a/fuzzing/fuzzer_worker_sequence_generator.go b/fuzzing/fuzzer_worker_sequence_generator.go index 2b9358a4..8cfe21e2 100644 --- a/fuzzing/fuzzer_worker_sequence_generator.go +++ b/fuzzing/fuzzer_worker_sequence_generator.go @@ -192,7 +192,7 @@ func (g *CallSequenceGenerator) InitializeNextSequence() (bool, error) { g.fetchIndex = 0 g.prefetchModifyCallFunc = nil - // Check if there are any previously une-xecuted corpus call sequences. If there are, the fuzzer should execute + // Check if there are any previously un-executed corpus call sequences. If there are, the fuzzer should execute // those first. unexecutedSequence := g.worker.fuzzer.corpus.UnexecutedCallSequence() if unexecutedSequence != nil { From 4a0f0c1e1989da2f11d72668339ed7feeee8345a Mon Sep 17 00:00:00 2001 From: anishnaik Date: Wed, 28 Feb 2024 17:58:12 -0500 Subject: [PATCH 34/55] update version (#310) --- cmd/root.go | 2 +- go.mod | 4 ---- go.sum | 21 --------------------- 3 files changed, 1 insertion(+), 26 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index ae03d15d..6dd04a3d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,7 +7,7 @@ import ( "os" ) -const version = "0.1.2" +const version = "0.1.3" // rootCmd represents the root CLI command object which all other commands stem from. var rootCmd = &cobra.Command{ diff --git a/go.mod b/go.mod index 45b23f24..27b9f68c 100644 --- a/go.mod +++ b/go.mod @@ -32,8 +32,6 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/deckarep/golang-set/v2 v2.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect - github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e // indirect - github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 // indirect github.com/getsentry/sentry-go v0.18.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-stack/stack v1.8.1 // indirect @@ -66,8 +64,6 @@ require ( github.com/tklauser/numcpus v0.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect - golang.org/x/mod v0.9.0 // indirect - golang.org/x/tools v0.7.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect diff --git a/go.sum b/go.sum index bb098adc..4c98ad4e 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,6 @@ github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqR github.com/VictoriaMetrics/fastcache v1.12.0 h1:vnVi/y9yKDcD9akmc4NqAoqgQhJrOwUF+j9LTgn4QDE= github.com/VictoriaMetrics/fastcache v1.12.0/go.mod h1:tjiYeEfYXCqacuvYw/7UoDIeJaNxq6132xHICNP77w8= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -40,7 +38,6 @@ github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoG github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lgvnQ2+oGlE/EyZy4+2/CxRh9KfvCXnag1vtpxVM= github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= -github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= @@ -57,7 +54,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= @@ -71,15 +67,11 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e h1:bBLctRc7kr01YGvaDfgLbTwjFNW5jdp5y5rj8XXBHfY= -github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fxamacker/cbor v1.5.1 h1:XjQWBgdmQyqimslUh5r4tUGmoqzHmBFQOImkWGi2awg= github.com/fxamacker/cbor v1.5.1/go.mod h1:3aPGItF174ni7dDzd6JZ206H8cmr4GDNBGpPa971zsU= -github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 h1:IZqZOB2fydHte3kUgxrzK5E1fW7RQGeDwE8F/ZZnUYc= -github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61/go.mod h1:Q0X6pkwTILDlzrGEckF6HKjXe48EgsY/l7K7vhY4MW8= github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= @@ -89,7 +81,6 @@ github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/ github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= @@ -161,12 +152,9 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/ github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= @@ -219,9 +207,7 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= @@ -289,7 +275,6 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -349,8 +334,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -440,8 +423,6 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -473,7 +454,6 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -492,7 +472,6 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 3f6a58de368fe2d109acc91a4da3bf6d0f306e90 Mon Sep 17 00:00:00 2001 From: Damilola Edwards Date: Mon, 25 Mar 2024 18:52:41 +0100 Subject: [PATCH 35/55] Feat: snapshot and revertTo cheatcodes (#276) --- chain/standard_cheat_code_contract.go | 21 +++++++ fuzzing/fuzzer_test.go | 1 + .../cheat_codes/vm/snapshot_and_revert_to.sol | 58 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 fuzzing/testdata/contracts/cheat_codes/vm/snapshot_and_revert_to.sol diff --git a/chain/standard_cheat_code_contract.go b/chain/standard_cheat_code_contract.go index 79c2d3ad..144c7e77 100644 --- a/chain/standard_cheat_code_contract.go +++ b/chain/standard_cheat_code_contract.go @@ -283,6 +283,27 @@ func getStandardCheatCodeContract(tracer *cheatCodeTracer) (*CheatCodeContract, }, ) + // snapshot: Takes a snapshot of the current state of the evm and returns the id associated with the snapshot + contract.addMethod( + "snapshot", abi.Arguments{}, abi.Arguments{{Type: typeUint256}}, + func(tracer *cheatCodeTracer, inputs []any) ([]any, *cheatCodeRawReturnData) { + snapshotID := tracer.evm.StateDB.Snapshot() + + return []any{snapshotID}, nil + }, + ) + + // revertTo(uint256): Revert the state of the evm to a previous snapshot. Takes the snapshot id to revert to. + contract.addMethod( + "revertTo", abi.Arguments{{Type: typeUint256}}, abi.Arguments{{Type: typeBool}}, + func(tracer *cheatCodeTracer, inputs []any) ([]any, *cheatCodeRawReturnData) { + snapshotID := inputs[0].(*big.Int) + tracer.evm.StateDB.RevertToSnapshot(int(snapshotID.Int64())) + + return []any{true}, nil + }, + ) + // FFI: Run arbitrary command on base OS contract.addMethod( "ffi", abi.Arguments{{Type: typeStringSlice}}, abi.Arguments{{Type: typeBytes}}, diff --git a/fuzzing/fuzzer_test.go b/fuzzing/fuzzer_test.go index 9167b183..b5ad96cc 100644 --- a/fuzzing/fuzzer_test.go +++ b/fuzzing/fuzzer_test.go @@ -208,6 +208,7 @@ func TestCheatCodes(t *testing.T) { "testdata/contracts/cheat_codes/utils/to_string.sol", "testdata/contracts/cheat_codes/utils/sign.sol", "testdata/contracts/cheat_codes/utils/parse.sol", + "testdata/contracts/cheat_codes/vm/snapshot_and_revert_to.sol", "testdata/contracts/cheat_codes/vm/coinbase.sol", "testdata/contracts/cheat_codes/vm/chain_id.sol", "testdata/contracts/cheat_codes/vm/deal.sol", diff --git a/fuzzing/testdata/contracts/cheat_codes/vm/snapshot_and_revert_to.sol b/fuzzing/testdata/contracts/cheat_codes/vm/snapshot_and_revert_to.sol new file mode 100644 index 00000000..577ff194 --- /dev/null +++ b/fuzzing/testdata/contracts/cheat_codes/vm/snapshot_and_revert_to.sol @@ -0,0 +1,58 @@ +// This test ensures that we can take a snapshot of the current state of the testchain and revert to the state at that snapshot using the snapshot and revertTo cheatcodes +pragma solidity ^0.8.0; + +interface CheatCodes { + function warp(uint256) external; + + function deal(address, uint256) external; + + function snapshot() external returns (uint256); + + function revertTo(uint256) external returns (bool); +} + +struct Storage { + uint slot0; + uint slot1; +} + +contract TestContract { + Storage store; + uint256 timestamp; + + function test() public { + // Obtain our cheat code contract reference. + CheatCodes cheats = CheatCodes( + 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D + ); + + store.slot0 = 10; + store.slot1 = 20; + timestamp = block.timestamp; + cheats.deal(address(this), 5 ether); + + // Save state + uint256 snapshot = cheats.snapshot(); + + // Change state + store.slot0 = 300; + store.slot1 = 400; + cheats.deal(address(this), 500 ether); + cheats.warp(12345); + + // Assert that state has been changed + assert(store.slot0 == 300); + assert(store.slot1 == 400); + assert(address(this).balance == 500 ether); + assert(block.timestamp == 12345); + + // Revert to snapshot + cheats.revertTo(snapshot); + + // Ensure state has been reset + assert(store.slot0 == 10); + assert(store.slot1 == 20); + assert(address(this).balance == 5 ether); + assert(block.timestamp == timestamp); + } +} From 75ac86e6261a2ee61d9afba4806b31604ff4f3c5 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Mon, 25 Mar 2024 13:03:04 -0500 Subject: [PATCH 36/55] fix: attach execution trace to reverting properties (#335) Co-authored-by: anishnaik --- fuzzing/test_case_property_provider.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fuzzing/test_case_property_provider.go b/fuzzing/test_case_property_provider.go index 9f5d8277..6d85db17 100644 --- a/fuzzing/test_case_property_provider.go +++ b/fuzzing/test_case_property_provider.go @@ -2,14 +2,15 @@ package fuzzing import ( "fmt" + "math/big" + "sync" + "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/fuzzing/contracts" "github.com/crytic/medusa/fuzzing/executiontracer" "github.com/crytic/medusa/fuzzing/utils" "github.com/ethereum/go-ethereum/core" "golang.org/x/exp/slices" - "math/big" - "sync" ) // PropertyTestCaseProvider is a provider for on-chain property tests. @@ -100,7 +101,7 @@ func (t *PropertyTestCaseProvider) checkPropertyTestFailed(worker *FuzzerWorker, // If our property test method call failed, we flag a failed test. if executionResult.Failed() { - return true, nil, nil + return true, executionTrace, nil } // Decode our ABI outputs From c3127e45b3304e1dfa087394dec46d889bac5794 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Mon, 25 Mar 2024 13:14:56 -0500 Subject: [PATCH 37/55] fix: use signature in traces to handle overloaded function names (#336) * fix: use signature in traces to handle overloaded function names * fix test --------- Co-authored-by: anishnaik --- fuzzing/calls/call_sequence.go | 5 +++-- fuzzing/executiontracer/execution_trace.go | 7 ++++--- fuzzing/fuzzer_test.go | 9 +++++---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/fuzzing/calls/call_sequence.go b/fuzzing/calls/call_sequence.go index 1321da28..f4e06c4f 100644 --- a/fuzzing/calls/call_sequence.go +++ b/fuzzing/calls/call_sequence.go @@ -3,6 +3,8 @@ package calls import ( "encoding/binary" "fmt" + "strconv" + "github.com/crytic/medusa/chain" chainTypes "github.com/crytic/medusa/chain/types" fuzzingTypes "github.com/crytic/medusa/fuzzing/contracts" @@ -13,7 +15,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "strconv" ) // CallSequence describes a sequence of calls sent to a chain. @@ -230,7 +231,7 @@ func (cse *CallSequenceElement) String() string { method, err := cse.Method() methodName := "" if err == nil && method != nil { - methodName = method.Name + methodName = method.Sig } // Next decode our arguments (we jump four bytes to skip the function selector) diff --git a/fuzzing/executiontracer/execution_trace.go b/fuzzing/executiontracer/execution_trace.go index 695f595b..90a4df05 100644 --- a/fuzzing/executiontracer/execution_trace.go +++ b/fuzzing/executiontracer/execution_trace.go @@ -3,6 +3,9 @@ package executiontracer import ( "encoding/hex" "fmt" + "regexp" + "strings" + "github.com/crytic/medusa/chain" "github.com/crytic/medusa/compilation/abiutils" "github.com/crytic/medusa/fuzzing/contracts" @@ -12,8 +15,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" coreTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" - "regexp" - "strings" ) // ExecutionTrace contains information recorded by an ExecutionTracer. It contains information about each call @@ -77,7 +78,7 @@ func (t *ExecutionTrace) generateCallFrameEnterElements(callFrame *CallFrame) ([ } else { method, err = callFrame.CodeContractAbi.MethodById(callFrame.InputData) if err == nil { - methodName = method.Name + methodName = method.Sig } } } diff --git a/fuzzing/fuzzer_test.go b/fuzzing/fuzzer_test.go index b5ad96cc..d0a47392 100644 --- a/fuzzing/fuzzer_test.go +++ b/fuzzing/fuzzer_test.go @@ -2,15 +2,16 @@ package fuzzing import ( "encoding/hex" + "math/big" + "math/rand" + "testing" + "github.com/crytic/medusa/chain" "github.com/crytic/medusa/events" "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/fuzzing/valuegeneration" "github.com/crytic/medusa/utils" "github.com/ethereum/go-ethereum/common" - "math/big" - "math/rand" - "testing" "github.com/crytic/medusa/fuzzing/config" "github.com/stretchr/testify/assert" @@ -461,7 +462,7 @@ func TestDeploymentsSelfDestruct(t *testing.T) { func TestExecutionTraces(t *testing.T) { expectedMessagesPerTest := map[string][]string{ "testdata/contracts/execution_tracing/call_and_deployment_args.sol": {"Hello from deployment args!", "Hello from call args!"}, - "testdata/contracts/execution_tracing/cheatcodes.sol": {"StdCheats.toString(true)"}, + "testdata/contracts/execution_tracing/cheatcodes.sol": {"StdCheats.toString(bool)(true)"}, "testdata/contracts/execution_tracing/event_emission.sol": {"TestEvent", "TestIndexedEvent", "TestMixedEvent", "Hello from event args!", "Hello from library event args!"}, "testdata/contracts/execution_tracing/proxy_call.sol": {"TestContract -> InnerDeploymentContract.setXY", "Hello from proxy call args!"}, "testdata/contracts/execution_tracing/revert_custom_error.sol": {"CustomError", "Hello from a custom error!"}, From e50b36df83ab6884a3ec6aeb07f51ee12611e077 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Wed, 27 Mar 2024 11:26:56 -0500 Subject: [PATCH 38/55] feat: add execution trace for failed target contract deployments (#337) * feat: add execution trace for failed target contract deployments * fix bugs in logging and improve trace output to console --------- Co-authored-by: anishnaik --- fuzzing/executiontracer/execution_tracer.go | 9 ++- fuzzing/fuzzer.go | 67 +++++++++++++++------ fuzzing/fuzzer_hooks.go | 4 +- fuzzing/fuzzer_test.go | 3 +- logging/log_buffer.go | 8 ++- logging/logger.go | 16 ----- 6 files changed, 67 insertions(+), 40 deletions(-) diff --git a/fuzzing/executiontracer/execution_tracer.go b/fuzzing/executiontracer/execution_tracer.go index ed96dbe1..17ec57fe 100644 --- a/fuzzing/executiontracer/execution_tracer.go +++ b/fuzzing/executiontracer/execution_tracer.go @@ -1,6 +1,8 @@ package executiontracer import ( + "math/big" + "github.com/crytic/medusa/chain" "github.com/crytic/medusa/fuzzing/contracts" "github.com/ethereum/go-ethereum/common" @@ -8,7 +10,6 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/vm" "golang.org/x/exp/slices" - "math/big" ) // CallWithExecutionTrace obtains an execution trace for a given call, on the provided chain, using the state @@ -118,6 +119,12 @@ func (t *ExecutionTracer) resolveCallFrameContractDefinitions(callFrame *CallFra callFrame.ToContractName = toContract.Name() callFrame.ToContractAbi = &toContract.CompiledContract().Abi t.resolveCallFrameConstructorArgs(callFrame, toContract) + + // If this is a contract creation, set the code address to the address of the contract we just deployed. + if callFrame.IsContractCreation() { + callFrame.CodeContractName = toContract.Name() + callFrame.CodeContractAbi = &toContract.CompiledContract().Abi + } } } } diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index f28660c0..e2245164 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -2,11 +2,9 @@ package fuzzing import ( "context" + "errors" "fmt" - "github.com/crytic/medusa/fuzzing/coverage" - "github.com/crytic/medusa/logging" - "github.com/crytic/medusa/logging/colors" - "github.com/rs/zerolog" + "github.com/crytic/medusa/fuzzing/executiontracer" "math/big" "math/rand" "os" @@ -18,6 +16,11 @@ import ( "sync" "time" + "github.com/crytic/medusa/fuzzing/coverage" + "github.com/crytic/medusa/logging" + "github.com/crytic/medusa/logging/colors" + "github.com/rs/zerolog" + "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/utils/randomutils" "github.com/ethereum/go-ethereum/core/types" @@ -333,7 +336,7 @@ func (f *Fuzzer) createTestChain() (*chain.TestChain, error) { // all compiled contract definitions. This includes any successful compilations as a result of the Fuzzer.config // definitions, as well as those added by Fuzzer.AddCompilationTargets. The contract deployment order is defined by // the Fuzzer.config. -func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) error { +func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (error, *executiontracer.ExecutionTrace) { // Verify that target contracts is not empty. If it's empty, but we only have one contract definition, // we can infer the target contracts. Otherwise, we report an error. if len(fuzzer.config.Fuzzing.TargetContracts) == 0 { @@ -341,7 +344,7 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) erro fuzzer.config.Fuzzing.TargetContracts = []string{fuzzer.contractDefinitions[0].Name()} } else { return fmt.Errorf("missing target contracts (update fuzzing.targetContracts in the project config " + - "or use the --target-contracts CLI flag)") + "or use the --target-contracts CLI flag)"), nil } } @@ -357,20 +360,20 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) erro if len(contract.CompiledContract().Abi.Constructor.Inputs) > 0 { jsonArgs, ok := fuzzer.config.Fuzzing.ConstructorArgs[contractName] if !ok { - return fmt.Errorf("constructor arguments for contract %s not provided", contractName) + return fmt.Errorf("constructor arguments for contract %s not provided", contractName), nil } decoded, err := valuegeneration.DecodeJSONArgumentsFromMap(contract.CompiledContract().Abi.Constructor.Inputs, jsonArgs, deployedContractAddr) if err != nil { - return err + return err, nil } args = decoded } - // Constructor our deployment message/tx data field + // Construct our deployment message/tx data field msgData, err := contract.CompiledContract().GetDeploymentMessageData(args) if err != nil { - return fmt.Errorf("initial contract deployment failed for contract \"%v\", error: %v", contractName, err) + return fmt.Errorf("initial contract deployment failed for contract \"%v\", error: %v", contractName, err), nil } // If our project config has a non-zero balance for this target contract, retrieve it @@ -387,25 +390,45 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) erro // Create a new pending block we'll commit to chain block, err := testChain.PendingBlockCreate() if err != nil { - return err + return err, nil } // Add our transaction to the block // Add our transaction to the block err = testChain.PendingBlockAddTx(msg.ToCoreMessage()) if err != nil { - return err + return err, nil } // Commit the pending block to the chain, so it becomes the new head. err = testChain.PendingBlockCommit() if err != nil { - return err + return err, nil } - // Ensure our transaction succeeded + // Ensure our transaction succeeded and, if it did not, attach an execution trace to it and re-run it. + // The execution trace will be returned so that it can be provided to the user for debugging if block.MessageResults[0].Receipt.Status != types.ReceiptStatusSuccessful { - return fmt.Errorf("contract deployment tx returned a failed status: %v", block.MessageResults[0].ExecutionResult.Err) + // Create a call sequence element to represent the failed contract deployment tx + cse := calls.NewCallSequenceElement(nil, msg, 0, 0) + cse.ChainReference = &calls.CallSequenceElementChainReference{ + Block: block, + TransactionIndex: len(block.Messages) - 1, + } + + // Replay the execution trace for the failed contract deployment tx + err = cse.AttachExecutionTrace(testChain, fuzzer.contractDefinitions) + + // Throw an error if execution tracing threw an error or the trace is nil + if err != nil { + return fmt.Errorf("failed to attach execution trace to failed contract deployment tx: %v", err), nil + } + if cse.ExecutionTrace == nil { + return fmt.Errorf("contract deployment tx returned a failed status: %v", block.MessageResults[0].ExecutionResult.Err), nil + } + + // Return the execution error and the execution trace + return fmt.Errorf("contract deployment tx returned a failed status: %v", block.MessageResults[0].ExecutionResult.Err), cse.ExecutionTrace } // Record our deployed contract so the next config-specified constructor args can reference this @@ -421,10 +444,10 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) erro // If we did not find a contract corresponding to this item in the deployment order, we throw an error. if !found { - return fmt.Errorf("%v was specified in the target contracts but was not found in the compilation artifacts", contractName) + return fmt.Errorf("%v was specified in the target contracts but was not found in the compilation artifacts", contractName), nil } } - return nil + return nil, nil } // defaultCallSequenceGeneratorConfigFunc is a NewCallSequenceGeneratorConfigFunc which creates a @@ -641,9 +664,13 @@ func (f *Fuzzer) Start() error { // Set it up with our deployment/setup strategy defined by the fuzzer. f.logger.Info("Setting up base chain") - err = f.Hooks.ChainSetupFunc(f, baseTestChain) + err, trace := f.Hooks.ChainSetupFunc(f, baseTestChain) if err != nil { - f.logger.Error("Failed to initialize the test chain", err) + if trace != nil { + f.logger.Error("Failed to initialize the test chain", err, errors.New(trace.Log().ColorString())) + } else { + f.logger.Error("Failed to initialize the test chain", err) + } return err } @@ -828,7 +855,7 @@ func (f *Fuzzer) printExitingResults() { // Print the results of each individual test case. f.logger.Info("Fuzzer stopped, test results follow below ...") for _, testCase := range f.testCases { - f.logger.Info(testCase.LogMessage().Elements()...) + f.logger.Info(testCase.LogMessage().ColorString()) // Tally our pass/fail count. if testCase.Status() == TestCaseStatusPassed { diff --git a/fuzzing/fuzzer_hooks.go b/fuzzing/fuzzer_hooks.go index ea2d8486..cf458192 100644 --- a/fuzzing/fuzzer_hooks.go +++ b/fuzzing/fuzzer_hooks.go @@ -1,6 +1,7 @@ package fuzzing import ( + "github.com/crytic/medusa/fuzzing/executiontracer" "math/rand" "github.com/crytic/medusa/chain" @@ -41,7 +42,8 @@ type NewShrinkingValueMutatorFunc func(fuzzer *Fuzzer, valueSet *valuegeneration type NewCallSequenceGeneratorConfigFunc func(fuzzer *Fuzzer, valueSet *valuegeneration.ValueSet, randomProvider *rand.Rand) (*CallSequenceGeneratorConfig, error) // TestChainSetupFunc describes a function which sets up a test chain's initial state prior to fuzzing. -type TestChainSetupFunc func(fuzzer *Fuzzer, testChain *chain.TestChain) error +// An execution trace can also be returned in case of a deployment error for an improved debugging experience +type TestChainSetupFunc func(fuzzer *Fuzzer, testChain *chain.TestChain) (error, *executiontracer.ExecutionTrace) // CallSequenceTestFunc defines a method called after a fuzzing.FuzzerWorker sends another call in a types.CallSequence // during a fuzzing campaign. It returns a ShrinkCallSequenceRequest set, which represents a set of requests for diff --git a/fuzzing/fuzzer_test.go b/fuzzing/fuzzer_test.go index d0a47392..860ebd93 100644 --- a/fuzzing/fuzzer_test.go +++ b/fuzzing/fuzzer_test.go @@ -2,6 +2,7 @@ package fuzzing import ( "encoding/hex" + "github.com/crytic/medusa/fuzzing/executiontracer" "math/big" "math/rand" "testing" @@ -35,7 +36,7 @@ func TestFuzzerHooks(t *testing.T) { return existingSeqGenConfigFunc(fuzzer, valueSet, randomProvider) } existingChainSetupFunc := f.fuzzer.Hooks.ChainSetupFunc - f.fuzzer.Hooks.ChainSetupFunc = func(fuzzer *Fuzzer, testChain *chain.TestChain) error { + f.fuzzer.Hooks.ChainSetupFunc = func(fuzzer *Fuzzer, testChain *chain.TestChain) (error, *executiontracer.ExecutionTrace) { chainSetupOk = true return existingChainSetupFunc(fuzzer, testChain) } diff --git a/logging/log_buffer.go b/logging/log_buffer.go index 70abf4a4..761d7ab5 100644 --- a/logging/log_buffer.go +++ b/logging/log_buffer.go @@ -28,6 +28,12 @@ func (l *LogBuffer) Elements() []any { // String provides the non-colorized string representation of the LogBuffer func (l LogBuffer) String() string { - _, msg, _, _ := buildMsgs(l.elements) + _, msg, _, _ := buildMsgs(l.elements...) + return msg +} + +// ColorString provides the colorized string representation of the LogBuffer +func (l LogBuffer) ColorString() string { + msg, _, _, _ := buildMsgs(l.elements...) return msg } diff --git a/logging/logger.go b/logging/logger.go index 7a6c09e4..e52f4414 100644 --- a/logging/logger.go +++ b/logging/logger.go @@ -343,22 +343,6 @@ func chainStructuredLogInfoErrorsAndMsgs(structuredLog *zerolog.Event, unstructu // First, we need to create a formatted error string for unstructured output var errStr string for _, err := range errs { - // To make the formatting a little nicer, we will add a tab after each new line in the error so that - // errors can be better differentiated on unstructured channels - lines := make([]string, 0) - for i, line := range strings.Split(err.Error(), "\n") { - // Add a tab to the line only after the first new line in the error message - if i != 0 { - line = "\t" + line - } - lines = append(lines, line) - } - - // Update the error string to be based on the tabbed lines array - if len(lines) > 0 { - err = fmt.Errorf("%v", strings.Join(lines, "\n")) - } - // Append a bullet point and the formatted error to the error string errStr += "\n" + colors.BULLET_POINT + " " + err.Error() } From 7fdab2573f09c9b40d73ea9bdd023dd1155c96ce Mon Sep 17 00:00:00 2001 From: David Pokora Date: Wed, 27 Mar 2024 14:11:22 -0700 Subject: [PATCH 39/55] Output non-fuzzing failure related error messages (#312) * Output error message if there was one * Slight cleanup/refactor * Updated exit code and error return so error messages are only printed if there is an error (despite exiting with a special exit code) * proposed changed to prevent double logging/printing of fuzzer errors * change name of exit code * fix bug --------- Co-authored-by: anishnaik --- cmd/exitcodes/error_with_exit_code.go | 18 +++++++++++------- cmd/exitcodes/exit_codes.go | 4 ++++ cmd/fuzz.go | 17 ++++++++++------- main.go | 16 ++++++++++++++-- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/cmd/exitcodes/error_with_exit_code.go b/cmd/exitcodes/error_with_exit_code.go index eb2367a7..96102f7f 100644 --- a/cmd/exitcodes/error_with_exit_code.go +++ b/cmd/exitcodes/error_with_exit_code.go @@ -17,21 +17,25 @@ func NewErrorWithExitCode(err error, exitCode int) *ErrorWithExitCode { // Error returns the error message string, implementing the `error` interface. func (e *ErrorWithExitCode) Error() string { + if e.err == nil { + return "" + } return e.err.Error() } -// GetErrorExitCode checks the given exit code that the application should exit with, if this error is bubbled to -// the top-level. This will be 0 for a nil error, 1 for a generic error, or arbitrary if the error is of type +// GetInnerErrorAndExitCode checks the given exit code that the application should exit with, if this error is bubbled +// to the top-level. This will be 0 for a nil error, 1 for a generic error, or arbitrary if the error is of type // ErrorWithExitCode. -// Returns the exit code associated with the error. -func GetErrorExitCode(err error) int { +// Returns the error (or inner error if it is an ErrorWithExitCode error type), along with the exit code associated +// with the error. +func GetInnerErrorAndExitCode(err error) (error, int) { // If we have no error, return 0, if we have a generic error, return 1, if we have a custom error code, unwrap // and return it. if err == nil { - return ExitCodeSuccess + return nil, ExitCodeSuccess } else if unwrappedErr, ok := err.(*ErrorWithExitCode); ok { - return unwrappedErr.exitCode + return unwrappedErr.err, unwrappedErr.exitCode } else { - return ExitCodeGeneralError + return err, ExitCodeGeneralError } } diff --git a/cmd/exitcodes/exit_codes.go b/cmd/exitcodes/exit_codes.go index cb6c7c98..5bbed621 100644 --- a/cmd/exitcodes/exit_codes.go +++ b/cmd/exitcodes/exit_codes.go @@ -16,6 +16,10 @@ const ( // ================================ // Note: Despite not being standardized, exit codes 2-5 are often used for common use cases, so we avoid them. + // ExitCodeHandledError indicates that there was an error that was logged already and does not need to be handled + // by main. + ExitCodeHandledError = 6 + // ExitCodeTestFailed indicates a test case had failed. ExitCodeTestFailed = 7 ) diff --git a/cmd/fuzz.go b/cmd/fuzz.go index 28453ae8..188f5d95 100644 --- a/cmd/fuzz.go +++ b/cmd/fuzz.go @@ -145,9 +145,9 @@ func cmdRunFuzz(cmd *cobra.Command, args []string) error { } // Create our fuzzing - fuzzer, err := fuzzing.NewFuzzer(*projectConfig) - if err != nil { - return err + fuzzer, fuzzErr := fuzzing.NewFuzzer(*projectConfig) + if fuzzErr != nil { + return exitcodes.NewErrorWithExitCode(fuzzErr, exitcodes.ExitCodeHandledError) } // Stop our fuzzing on keyboard interrupts @@ -159,12 +159,15 @@ func cmdRunFuzz(cmd *cobra.Command, args []string) error { }() // Start the fuzzing process with our cancellable context. - err = fuzzer.Start() + fuzzErr = fuzzer.Start() + if fuzzErr != nil { + return exitcodes.NewErrorWithExitCode(fuzzErr, exitcodes.ExitCodeHandledError) + } // If we have no error and failed test cases, we'll want to return a special exit code - if err == nil && len(fuzzer.TestCasesWithStatus(fuzzing.TestCaseStatusFailed)) > 0 { - return exitcodes.NewErrorWithExitCode(err, exitcodes.ExitCodeTestFailed) + if fuzzErr == nil && len(fuzzer.TestCasesWithStatus(fuzzing.TestCaseStatusFailed)) > 0 { + return exitcodes.NewErrorWithExitCode(fuzzErr, exitcodes.ExitCodeTestFailed) } - return err + return fuzzErr } diff --git a/main.go b/main.go index ad2537e1..b0bb8eaf 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "github.com/crytic/medusa/cmd" "github.com/crytic/medusa/cmd/exitcodes" "os" @@ -10,6 +11,17 @@ func main() { // Run our root CLI command, which contains all underlying command logic and will handle parsing/invocation. err := cmd.Execute() - // Determine the exit code from any potential error and exit out. - os.Exit(exitcodes.GetErrorExitCode(err)) + // Obtain the actual error and exit code from the error, if any. + var exitCode int + err, exitCode = exitcodes.GetInnerErrorAndExitCode(err) + + // If we have an error, print it. + if err != nil && exitCode != exitcodes.ExitCodeHandledError { + fmt.Println(err) + } + + // If we have a non-success exit code, exit with it. + if exitCode != exitcodes.ExitCodeSuccess { + os.Exit(exitCode) + } } From fa498d1c44c4bd9197da136a4a4b39983a051f51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20L=C3=B3pez?= <2642849+elopez@users.noreply.github.com> Date: Wed, 3 Apr 2024 10:53:07 -0300 Subject: [PATCH 40/55] Update README to reflect current CLI and config options (#343) Closes #341 --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c4597293..90ce585b 100644 --- a/README.md +++ b/README.md @@ -45,20 +45,20 @@ You can then fetch the latest binaries for your platform from our [GitHub Releas Although we recommend users run `medusa` in a configuration file driven format for more customizability, you can also run `medusa` through the CLI directly. We provide instructions for both below. -We recommend you familiarize yourself with writing [assertion](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/echidna/basic/assertion-checking.md) and [property](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/echidna/introduction/how-to-test-a-property.md) tests for Echidna. `medusa` supports Echidna-like property testing with config-defined function prefixes (default: `fuzz_`) and assertion testing using Solidity `assert(...)` statements. +We recommend you familiarize yourself with writing [assertion](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/echidna/basic/assertion-checking.md) and [property](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/echidna/introduction/how-to-test-a-property.md) tests for Echidna. `medusa` supports Echidna-like property testing and function optimization with config-defined function prefixes (default: `property_` and `optimize_`, respectively) and assertion testing using Solidity `assert(...)` statements. ### Command-line only You can use the following command to run `medusa` against a contract: ```console -medusa fuzz --target contract.sol --deployment-order ContractName +medusa fuzz --compilation-target contract.sol --target-contracts ContractName ``` Where: -- `--target` specifies the path `crytic-compile` should use to compile contracts -- `--deployment-order` specifies comma-separated names of contracts to be deployed for testing. +- `--compilation-target` specifies the path `crytic-compile` should use to compile contracts +- `--target-contracts` specifies comma-separated names of contracts to be deployed for testing. **Note:** Check out the [command-line interface](https://github.com/crytic/medusa/wiki/Command-Line-Interface) wiki page, or run `medusa --help` for more information. @@ -74,7 +74,7 @@ medusa init This will create a `medusa.json` in your current folder. There are two required fields that should be set correctly: - Set your `"target"` under `"compilation"` to point to the file/directory which `crytic-compile` should use to build your contracts. -- Put the names of any contracts you wish to deploy and run tests against in the `"deploymentOrder"` field. This must be non-empty. +- Put the names of any contracts you wish to deploy and run tests against in the `"targetContracts"` field. This must be non-empty. After you have a configuration in place, you can execute: From c75568343754bc2e77f85f0dd77d0f16cdaa651a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 09:50:06 -0400 Subject: [PATCH 41/55] Bump golang.org/x/crypto from 0.19.0 to 0.22.0 (#345) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.19.0 to 0.22.0. - [Commits](https://github.com/golang/crypto/compare/v0.19.0...v0.22.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 27b9f68c..b4eccdd5 100644 --- a/go.mod +++ b/go.mod @@ -13,10 +13,10 @@ require ( github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 - golang.org/x/crypto v0.19.0 + golang.org/x/crypto v0.22.0 golang.org/x/exp v0.0.0-20230206171751-46f607a40771 golang.org/x/net v0.21.0 - golang.org/x/sys v0.17.0 + golang.org/x/sys v0.19.0 ) require ( diff --git a/go.sum b/go.sum index 4c98ad4e..2ec373b5 100644 --- a/go.sum +++ b/go.sum @@ -321,8 +321,8 @@ golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= @@ -398,8 +398,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= From f46c4e1bf987b6cfcdc54f0067f24a587d292695 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 10:18:57 -0400 Subject: [PATCH 42/55] Bump google.golang.org/protobuf from 1.28.1 to 1.33.0 (#332) Bumps google.golang.org/protobuf from 1.28.1 to 1.33.0. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: anishnaik --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b4eccdd5..bfae4653 100644 --- a/go.mod +++ b/go.mod @@ -65,7 +65,7 @@ require ( github.com/x448/float16 v0.8.4 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 2ec373b5..82679dec 100644 --- a/go.sum +++ b/go.sum @@ -452,8 +452,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 42a16b0556be704387f350ab4ed52195cc3ea80f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 11:43:33 -0400 Subject: [PATCH 43/55] Bump github.com/stretchr/testify from 1.8.4 to 1.9.0 (#314) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.4 to 1.9.0. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.8.4...v1.9.0) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: anishnaik --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bfae4653..16fa2f1b 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/shopspring/decimal v1.3.1 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 golang.org/x/crypto v0.22.0 golang.org/x/exp v0.0.0-20230206171751-46f607a40771 golang.org/x/net v0.21.0 diff --git a/go.sum b/go.sum index 82679dec..ff4eb54b 100644 --- a/go.sum +++ b/go.sum @@ -280,8 +280,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= From e58ae6e9a06557502f4a98379a364ff3ade25b94 Mon Sep 17 00:00:00 2001 From: Igor Konnov Date: Wed, 10 Apr 2024 18:23:29 +0200 Subject: [PATCH 44/55] fix two outdated comments (#347) --- fuzzing/fuzzer_worker_sequence_generator.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fuzzing/fuzzer_worker_sequence_generator.go b/fuzzing/fuzzer_worker_sequence_generator.go index 8cfe21e2..a931b04a 100644 --- a/fuzzing/fuzzer_worker_sequence_generator.go +++ b/fuzzing/fuzzer_worker_sequence_generator.go @@ -2,11 +2,12 @@ package fuzzing import ( "fmt" + "math/big" + "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/fuzzing/valuegeneration" "github.com/crytic/medusa/utils" "github.com/crytic/medusa/utils/randomutils" - "math/big" ) // CallSequenceGenerator generates call sequences iteratively per element, for use in fuzzing campaigns. It is attached @@ -336,10 +337,10 @@ func callSeqGenFuncCorpusHead(sequenceGenerator *CallSequenceGenerator, sequence // Obtain a call sequence from the corpus corpusSequence, err := sequenceGenerator.worker.fuzzer.corpus.RandomMutationTargetSequence() if err != nil { - return fmt.Errorf("could not obtain corpus call sequence for tail mutation: %v", err) + return fmt.Errorf("could not obtain corpus call sequence for head mutation: %v", err) } - // Determine a random position to slice the call sequence. + // Determine the length of the slice to be copied in the head. maxLength := utils.Min(len(sequence), len(corpusSequence)) copy(sequence, corpusSequence[:maxLength]) From 74dbf0e84ef4b907e6fb11d018ad6258f1cfe4bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20L=C3=B3pez?= <2642849+elopez@users.noreply.github.com> Date: Wed, 10 Apr 2024 13:43:22 -0300 Subject: [PATCH 45/55] ci: automated release builds (#342) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add M1 mac arch build and test * Update .github/workflows/ci.yml Co-authored-by: Emilio López <2642849+elopez@users.noreply.github.com> * Update .github/workflows/ci.yml Co-authored-by: Emilio López <2642849+elopez@users.noreply.github.com> * ci: automate release creation when pushing a tag * ci: upgrade actions/setup-{node,go} * ci: fix Python dependency installation on macOS error: externally-managed-environment × This environment is externally managed ╰─> To install Python packages system-wide, try brew install xyz, where xyz is the package you are trying to install. If you wish to install a non-brew-packaged Python package, create a virtual environment using python3 -m venv path/to/venv. Then use path/to/venv/bin/python and path/to/venv/bin/pip. If you wish to install a non-brew packaged Python application, it may be easiest to use pipx install xyz, which will manage a virtual environment for you. Make sure you have pipx installed. note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages. hint: See PEP 668 for the detailed specification. * ci: use `alls-green` to decide success status `release` can be skipped, but GitHub will then skip `all-checks`. We need to check that `release` is successful when it runs, but ignore it when it is skipped. --------- Co-authored-by: Anish Naik --- .github/workflows/ci.yml | 90 +++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c5c97e7d..ea20f899 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,8 @@ on: push: branches: - master + tags: + - "v*" pull_request: branches: - master @@ -22,7 +24,10 @@ jobs: needs: [lint, test] strategy: matrix: - environment: [ubuntu-latest, macos-latest, windows-latest] + environment: [ubuntu-latest, macos-12, macos-14, windows-latest] + permissions: + contents: read + id-token: write runs-on: ${{ matrix.environment }} timeout-minutes: 10 @@ -44,9 +49,11 @@ jobs: printf 'TEMP=%s\\tmpdir\n' "$DIR" | tee -a "$GITHUB_ENV" go env - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: "^1.18.1" + # disable caching during release (tag) builds + cache: ${{ !startsWith(github.ref, 'refs/tags/') }} - name: Build (Linux and macOS) if: runner.os == 'Linux' || runner.os == 'macOS' @@ -54,7 +61,7 @@ jobs: - name: Compress (Linux and macOS) if: runner.os == 'Linux' || runner.os == 'macOS' - run: tar -czvf medusa.tar.gz medusa + run: tar -czvf medusa-${{ runner.os }}-${{ runner.arch }}.tar.gz medusa - name: Build (Windows) if: runner.os == 'Windows' @@ -62,14 +69,56 @@ jobs: - name: Compress (Windows) if: runner.os == 'Windows' - run: tar -czvf medusa.tar.gz medusa.exe + run: tar -czvf medusa-${{ runner.os }}-${{ runner.arch }}.tar.gz medusa.exe - - name: Upload artifact on merge to master - if: github.ref == 'refs/heads/master' - uses: actions/upload-artifact@v3 + - name: Rename for release + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + shell: bash + run: | + [ ! -f medusa-Linux-X64.tar.gz ] || mv medusa-Linux-X64.tar.gz medusa-linux-x64.tar.gz + [ ! -f medusa-macOS-X64.tar.gz ] || mv medusa-macOS-X64.tar.gz medusa-mac-x64.tar.gz + [ ! -f medusa-macOS-ARM64.tar.gz ] || mv medusa-macOS-ARM64.tar.gz medusa-mac-arm64.tar.gz + [ ! -f medusa-Windows-X64.tar.gz ] || mv medusa-Windows-X64.tar.gz medusa-win-x64.tar.gz + + - name: Sign artifact + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + uses: sigstore/gh-action-sigstore-python@v2.1.1 + with: + inputs: ./medusa-*.tar.gz + + - name: Upload artifact + if: github.ref == 'refs/heads/master' || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')) + uses: actions/upload-artifact@v4 with: - name: medusa-${{ runner.os }} - path: medusa.tar.gz + name: medusa-${{ runner.os }}-${{ runner.arch }} + path: | + ./medusa-*.tar.gz + ./medusa-*.tar.gz.sigstore + + release: + needs: [build] + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') + permissions: + contents: write + + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Download binaries + uses: actions/download-artifact@v4 + with: + pattern: medusa-* + merge-multiple: true + + - name: Create GitHub release and upload binaries + uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564 # v2.0.4 + with: + draft: true + name: "${{ github.ref_name }}" + files: | + ./medusa-*.tar.gz + ./medusa-*.tar.gz.sigstore lint: runs-on: ubuntu-latest @@ -78,7 +127,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: "^1.18.1" @@ -110,7 +159,7 @@ jobs: test: strategy: matrix: - environment: [ubuntu-latest, macos-latest, windows-latest] + environment: [ubuntu-latest, macos-12, macos-14, windows-latest] runs-on: ${{ matrix.environment }} timeout-minutes: 20 @@ -118,6 +167,10 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.10" + - name: Speed up Go, Python, Node (Windows) if: runner.os == 'Windows' run: | @@ -142,11 +195,11 @@ jobs: npm config set cache "$DIR\\npm-cache" --global echo "::endgroup::" - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: "^1.18.1" - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 18.15 @@ -155,7 +208,7 @@ jobs: - name: Install Python dependencies run: | - pip3 install --no-cache-dir setuptools solc-select crytic-compile + pip3 install --no-cache-dir solc-select crytic-compile - name: Install solc run: | @@ -165,9 +218,14 @@ jobs: run: go test ./... all-checks: - needs: [lint, test, build] + if: always() + needs: [lint, test, build, release] runs-on: ubuntu-latest steps: - - run: true + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 + with: + allowed-skips: release + jobs: ${{ toJSON(needs) }} From 68c5be2cd18e6aa2a1742e8b021c327ac2c17c55 Mon Sep 17 00:00:00 2001 From: anishnaik Date: Thu, 2 May 2024 21:29:20 -0400 Subject: [PATCH 46/55] `mdbook` for medusa (#348) * Migrate to mdbook (#223) Co-authored-by: Zach McManus <59886732+zachmdsi@users.noreply.github.com> Co-authored-by: Damilola Edwards --- .gitignore | 5 +- README.md | 94 +------- docs/book.toml | 17 ++ docs/src/README.md | 18 ++ docs/src/SUMMARY.md | 69 ++++++ docs/src/advanced.md | 16 ++ docs/src/api/api_overview.md | 185 ++++++++++++++++ docs/src/cheatcodes/addr.md | 24 ++ docs/src/cheatcodes/chain_id.md | 22 ++ docs/src/cheatcodes/cheatcodes_overview.md | 118 ++++++++++ docs/src/cheatcodes/coinbase.md | 22 ++ docs/src/cheatcodes/deal.md | 23 ++ docs/src/cheatcodes/difficulty.md | 25 +++ docs/src/cheatcodes/etch.md | 29 +++ docs/src/cheatcodes/fee.md | 22 ++ docs/src/cheatcodes/ffi.md | 58 +++++ docs/src/cheatcodes/get_nonce.md | 22 ++ docs/src/cheatcodes/load.md | 27 +++ docs/src/cheatcodes/parse_address.md | 30 +++ docs/src/cheatcodes/parse_bool.md | 30 +++ docs/src/cheatcodes/parse_bytes.md | 30 +++ docs/src/cheatcodes/parse_bytes32.md | 30 +++ docs/src/cheatcodes/parse_int.md | 30 +++ docs/src/cheatcodes/parse_uint.md | 30 +++ docs/src/cheatcodes/prank.md | 38 ++++ docs/src/cheatcodes/prank_here.md | 49 +++++ docs/src/cheatcodes/roll.md | 24 ++ docs/src/cheatcodes/set_nonce.md | 24 ++ docs/src/cheatcodes/sign.md | 28 +++ docs/src/cheatcodes/snapshot.md | 68 ++++++ docs/src/cheatcodes/store.md | 27 +++ docs/src/cheatcodes/to_string.md | 84 +++++++ docs/src/cheatcodes/warp.md | 24 ++ docs/src/cli/completion.md | 21 ++ docs/src/cli/fuzz.md | 131 +++++++++++ docs/src/cli/init.md | 36 +++ docs/src/cli/overview.md | 10 + docs/src/console_logging.md | 55 +++++ docs/src/coverage_reports.md | 3 + docs/src/faq.md | 16 ++ docs/src/getting_started/first_steps.md | 36 +++ docs/src/getting_started/installation.md | 64 ++++++ .../src/project_configuration/chain_config.md | 25 +++ .../compilation_config.md | 59 +++++ .../project_configuration/fuzzing_config.md | 205 ++++++++++++++++++ .../project_configuration/logging_config.md | 25 +++ docs/src/project_configuration/overview.md | 49 +++++ .../project_configuration/testing_config.md | 178 +++++++++++++++ docs/src/static/contract_deployment.png | Bin 0 -> 7790 bytes docs/src/static/coverage.png | Bin 0 -> 36174 bytes docs/src/static/custom.css | 6 + .../static/function_level_testing_medusa.json | 72 ++++++ docs/src/static/medusa.json | 72 ++++++ docs/src/static/medusa_logo.png | Bin 0 -> 110398 bytes docs/src/testing/coverage_reports.md | 1 + docs/src/testing/fuzzing_lifecycle.md | 136 ++++++++++++ docs/src/testing/invariants.md | 69 ++++++ docs/src/testing/overview.md | 26 +++ docs/src/testing/tips.md | 32 +++ .../writing-function-level-invariants.md | 145 +++++++++++++ .../writing-system-level-invariants.md | 3 + docs/src/testing/writing-tests.md | 189 ++++++++++++++++ docs/theme/favicon.png | Bin 0 -> 121411 bytes docs/theme/favicon.svg | 129 +++++++++++ docs/theme/highlight.js | 6 + fuzzing/executiontracer/execution_trace.go | 5 +- fuzzing/valuegeneration/abi_values.go | 2 +- 67 files changed, 3057 insertions(+), 91 deletions(-) create mode 100644 docs/book.toml create mode 100644 docs/src/README.md create mode 100644 docs/src/SUMMARY.md create mode 100644 docs/src/advanced.md create mode 100644 docs/src/api/api_overview.md create mode 100644 docs/src/cheatcodes/addr.md create mode 100644 docs/src/cheatcodes/chain_id.md create mode 100644 docs/src/cheatcodes/cheatcodes_overview.md create mode 100644 docs/src/cheatcodes/coinbase.md create mode 100644 docs/src/cheatcodes/deal.md create mode 100644 docs/src/cheatcodes/difficulty.md create mode 100644 docs/src/cheatcodes/etch.md create mode 100644 docs/src/cheatcodes/fee.md create mode 100644 docs/src/cheatcodes/ffi.md create mode 100644 docs/src/cheatcodes/get_nonce.md create mode 100644 docs/src/cheatcodes/load.md create mode 100644 docs/src/cheatcodes/parse_address.md create mode 100644 docs/src/cheatcodes/parse_bool.md create mode 100644 docs/src/cheatcodes/parse_bytes.md create mode 100644 docs/src/cheatcodes/parse_bytes32.md create mode 100644 docs/src/cheatcodes/parse_int.md create mode 100644 docs/src/cheatcodes/parse_uint.md create mode 100644 docs/src/cheatcodes/prank.md create mode 100644 docs/src/cheatcodes/prank_here.md create mode 100644 docs/src/cheatcodes/roll.md create mode 100644 docs/src/cheatcodes/set_nonce.md create mode 100644 docs/src/cheatcodes/sign.md create mode 100644 docs/src/cheatcodes/snapshot.md create mode 100644 docs/src/cheatcodes/store.md create mode 100644 docs/src/cheatcodes/to_string.md create mode 100644 docs/src/cheatcodes/warp.md create mode 100644 docs/src/cli/completion.md create mode 100644 docs/src/cli/fuzz.md create mode 100644 docs/src/cli/init.md create mode 100644 docs/src/cli/overview.md create mode 100644 docs/src/console_logging.md create mode 100644 docs/src/coverage_reports.md create mode 100644 docs/src/faq.md create mode 100644 docs/src/getting_started/first_steps.md create mode 100644 docs/src/getting_started/installation.md create mode 100644 docs/src/project_configuration/chain_config.md create mode 100644 docs/src/project_configuration/compilation_config.md create mode 100644 docs/src/project_configuration/fuzzing_config.md create mode 100644 docs/src/project_configuration/logging_config.md create mode 100644 docs/src/project_configuration/overview.md create mode 100644 docs/src/project_configuration/testing_config.md create mode 100644 docs/src/static/contract_deployment.png create mode 100644 docs/src/static/coverage.png create mode 100644 docs/src/static/custom.css create mode 100644 docs/src/static/function_level_testing_medusa.json create mode 100644 docs/src/static/medusa.json create mode 100755 docs/src/static/medusa_logo.png create mode 100644 docs/src/testing/coverage_reports.md create mode 100644 docs/src/testing/fuzzing_lifecycle.md create mode 100644 docs/src/testing/invariants.md create mode 100644 docs/src/testing/overview.md create mode 100644 docs/src/testing/tips.md create mode 100644 docs/src/testing/writing-function-level-invariants.md create mode 100644 docs/src/testing/writing-system-level-invariants.md create mode 100644 docs/src/testing/writing-tests.md create mode 100755 docs/theme/favicon.png create mode 100755 docs/theme/favicon.svg create mode 100644 docs/theme/highlight.js diff --git a/.gitignore b/.gitignore index 051bf3b0..b1251402 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,7 @@ *node_modules/ # Medusa binary -medusa \ No newline at end of file +medusa + +# Medusa docs +docs/book diff --git a/README.md b/README.md index 90ce585b..18f154b5 100644 --- a/README.md +++ b/README.md @@ -17,98 +17,18 @@ It provides parallelized fuzz testing of smart contracts through CLI, or its Go - ✔️**Extensible low-level testing API** through events and hooks provided throughout the fuzzer, workers, and test chains. - ❌ **Extensible high-level testing API** allowing for the addition of per-contract or global post call/event property tests with minimal effort. -## Installation +## Documentation -### Precompiled binaries +To learn more about how to install and use `medusa`, please refer to our [documentation](./docs/src/SUMMARY.md). -To use `medusa`, ensure you have: +For a better viewing experience, we recommend you install [mdbook](https://rust-lang.github.io/mdBook/guide/installation.html) +and then running the following steps from medusa's source directory: -- [crytic-compile](https://github.com/crytic/crytic-compile) (`pip3 install crytic-compile`) -- a suitable compilation framework (e.g. `solc`, `hardhat`) installed on your machine. We recommend [solc-select](https://github.com/crytic/solc-select) to quickly switch between Solidity compiler versions. - -You can then fetch the latest binaries for your platform from our [GitHub Releases](https://github.com/crytic/medusa/releases) page. - -### Building from source - -#### Requirements - -- You must have at least go 1.18 installed. -- [Windows only] The `go-ethereum` dependency may require [TDM-GCC](https://jmeubank.github.io/tdm-gcc/) to build. - -#### Steps - -- Clone the repository, then execute `go build` in the repository root. -- Go will automatically fetch all dependencies and build a binary for you in the same folder when completed. - -## Usage - -Although we recommend users run `medusa` in a configuration file driven format for more customizability, you can also run `medusa` through the CLI directly. -We provide instructions for both below. - -We recommend you familiarize yourself with writing [assertion](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/echidna/basic/assertion-checking.md) and [property](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/echidna/introduction/how-to-test-a-property.md) tests for Echidna. `medusa` supports Echidna-like property testing and function optimization with config-defined function prefixes (default: `property_` and `optimize_`, respectively) and assertion testing using Solidity `assert(...)` statements. - -### Command-line only - -You can use the following command to run `medusa` against a contract: - -```console -medusa fuzz --compilation-target contract.sol --target-contracts ContractName +```bash +cd docs +mdbook serve ``` -Where: - -- `--compilation-target` specifies the path `crytic-compile` should use to compile contracts -- `--target-contracts` specifies comma-separated names of contracts to be deployed for testing. - -**Note:** Check out the [command-line interface](https://github.com/crytic/medusa/wiki/Command-Line-Interface) wiki page, or run `medusa --help` for more information. - -### Configuration file driven - -The preferred method to use medusa is to enter your project directory (hardhat directory, or directory with your contracts), -then execute the following command: - -```console -medusa init -``` - -This will create a `medusa.json` in your current folder. There are two required fields that should be set correctly: - -- Set your `"target"` under `"compilation"` to point to the file/directory which `crytic-compile` should use to build your contracts. -- Put the names of any contracts you wish to deploy and run tests against in the `"targetContracts"` field. This must be non-empty. - -After you have a configuration in place, you can execute: - -```console -medusa fuzz -``` - -This will use the `medusa.json` configuration in the current directory and begin the fuzzing campaign. - -**Note:** Check out the [project configuration](https://github.com/crytic/medusa/wiki/Project-Configuration) wiki page, or run `medusa --help` for more information. - -## Running Unit Tests - -First, install [crytic-compile](https://github.com/crytic/crytic-compile), [solc-select](https://github.com/crytic/solc-select), and ensure you have `solc` (version >=0.8.7), and `hardhat` available on your system. - -- From the root of the repository, invoke `go test -v ./...` on through command-line to run tests from all packages at or below the root. - - Or enter each package directory to run `go test -v .` to test the immediate package. - - Note: the `-v` parameter provides verbose output. -- Otherwise, use an IDE like [GoLand](https://www.jetbrains.com/go/) to visualize the tests and logically separate output. - -## FAQs - -**Why create `medusa` if Echidna is already working just fine?** - -With `medusa`, we are exploring a different EVM implementation and language for our smart contract fuzzer. We believe that -experimenting with a new fuzzer provides us with the following benefits: - -- Since `medusa` is written in Go, we believe that this will **lower the barrier of entry for external contributions**. - We have taken great care in thoroughly commenting our code so that it is easy for new contributors to get up-to-speed and start contributing! -- The use of Go allows us to build an API to hook into the various parts of the fuzzer to build custom testing methodologies. See the [API Overview (WIP)]() section in the Wiki for more details. -- Our forked version of go-ethereum, [`medusa-geth`](https://github.com/crytic/medusa-geth), exhibits behavior that is closer to that of the EVM in production environments. -- We can take the lessons we learned while developing Echidna to create a fuzzer that is just as feature-rich but with additional capabilities to - create powerful and unique testing methodologies. - ## Contributing For information about how to contribute to this project, check out the [CONTRIBUTING](./CONTRIBUTING.md) guidelines. diff --git a/docs/book.toml b/docs/book.toml new file mode 100644 index 00000000..c09f7b92 --- /dev/null +++ b/docs/book.toml @@ -0,0 +1,17 @@ +[book] +title = "medusa" +authors = ["Trail of Bits"] +language = "en" +multilingual = false +src = "src" +description = "This repository, brought to you by Trail of Bits, contains the documentation files for the medusa fuzzer." + +[output.html] +git-repository-url = "https://github.com/crytic/medusa" +edit-url-template = "https://github.com/crytic/medusa/edit/master/docs/{path}" +additional-css = ["src/static/custom.css"] +default-theme = "light" + +[output.html.fold] +enable = true +level = 1 \ No newline at end of file diff --git a/docs/src/README.md b/docs/src/README.md new file mode 100644 index 00000000..e9ceb477 --- /dev/null +++ b/docs/src/README.md @@ -0,0 +1,18 @@ +![medusa_logo](./static/medusa_logo.png) + +`medusa` is a cross-platform go-ethereum-based smart contract fuzzer inspired by Echidna. It provides parallelized fuzz +testing of smart contracts through CLI, or its Go API that allows custom user-extended testing methodology. + +## Table of Contents + +- [Getting Started](./getting_started/installation.md): Learn how to install `medusa` and how to set it up for your first project. +- [Project Configuration](./project_configuration/overview.md): Learn how to set up `medusa` for your project as well as + the vast number of configuration options that can be set up based on your project needs. +- [Command Line Interface](./cli/overview.md): Learn how to use `medusa`'s CLI. +- [Writing Tests](./testing/overview.md): Learn how to write tests with `medusa` +- [API (WIP)](./api/api_overview.md): Learn about `medusa`'s Go API that can be used to perform advanced testing + methodologies and extend `medusa`'s capabilities. +- Appendices + - [Cheatcodes](./cheatcodes/cheatcodes_overview.md): Learn about the various cheatcodes that are supported by `medusa`. + - [Console Logging](./console_logging.md): Learn about how to use `console.log` with `medusa`. + - [FAQ](./faq.md) diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md new file mode 100644 index 00000000..383a71ef --- /dev/null +++ b/docs/src/SUMMARY.md @@ -0,0 +1,69 @@ +# Summary + +[Introduction](./README.md) + +# Getting Started + +- [Installation](./getting_started/installation.md) +- [First Steps](./getting_started/first_steps.md) + +# Project Configuration + +- [Configuration Overview](project_configuration/overview.md) +- [Fuzzing Configuration](project_configuration/fuzzing_config.md) +- [Testing Configuration](project_configuration/testing_config.md) +- [Chain Configuration](project_configuration/chain_config.md) +- [Compilation Configuration](project_configuration/compilation_config.md) +- [Logging Configuration](project_configuration/logging_config.md) + +# Command Line Interface (CLI) + +- [CLI Overview](./cli/overview.md) +- [init](./cli/init.md) +- [fuzz](./cli/fuzz.md) +- [completion](./cli/completion.md) + +# Writing Tests + +- [Testing Overview](./testing/overview.md) +- [The Fuzzing Lifecycle](./testing/fuzzing_lifecycle.md) +- [Types of Invariants](./testing/invariants.md) +- [Writing Function-Level Invariants](./testing/writing-function-level-invariants.md) +- [Writing System-Level Invariants (WIP)](./testing/writing-system-level-invariants.md) +- [Coverage Reports (WIP)](./testing/coverage_reports.md) + +# API + +- [API Overview (WIP)](api/api_overview.md) + +# Appendices + +- [Cheatcodes](cheatcodes/cheatcodes_overview.md) + - [warp](./cheatcodes/warp.md) + - [roll](./cheatcodes/roll.md) + - [fee](./cheatcodes/fee.md) + - [difficulty](./cheatcodes/difficulty.md) + - [chainId](./cheatcodes/chain_id.md) + - [store](./cheatcodes/store.md) + - [load](./cheatcodes/load.md) + - [etch](./cheatcodes/etch.md) + - [deal](./cheatcodes/deal.md) + - [snapshot](./cheatcodes/snapshot.md) + - [getNonce](./cheatcodes/get_nonce.md) + - [setNonce](./cheatcodes/set_nonce.md) + - [coinbase](./cheatcodes/coinbase.md) + - [prank](./cheatcodes/prank.md) + - [prankHere](./cheatcodes/prank_here.md) + - [ffi](./cheatcodes/ffi.md) + - [addr](./cheatcodes/addr.md) + - [sign](./cheatcodes/sign.md) + - [toString](./cheatcodes/to_string.md) + - [parseBytes](./cheatcodes/parse_bytes.md) + - [parseBytes32](./cheatcodes/parse_bytes32.md) + - [parseInt](./cheatcodes/parse_int.md) + - [parseUint](./cheatcodes/parse_uint.md) + - [parseBool](./cheatcodes/parse_bool.md) + - [parseAddress](./cheatcodes/parse_address.md) +- [Console Logging](./console_logging.md) + +[FAQ](./faq.md) diff --git a/docs/src/advanced.md b/docs/src/advanced.md new file mode 100644 index 00000000..a4a1f87f --- /dev/null +++ b/docs/src/advanced.md @@ -0,0 +1,16 @@ +> **Definition**: Stateful fuzzing is the process of maintaining EVM state across multiple fuzzed transactions. + +Stateful fuzzing is an incredibly powerful feature because it allows medusa to test your system **end-to-end**. Let's +take, for example, a staking system where you have the ability to `deposit`, `stake`, `unstake`, and `withdraw`. Because +medusa can execute an array of transactions, medusa can call [`deposit`, `stake`, `unstake`, `withdraw`] inorder and test the +whole system in one fell swoop. It is very important to note that medusa was not _forced_ to call those functions in +sequence. Medusa, over time, will identify that calling deposit allows it to stake tokens and having a staked balance +allows it to unstake, and so on. + +In contrast, having a call sequence length of 1 is called **stateless fuzzing**. + +> **Definition**: Stateless fuzzing is the process of executing a single transaction before resetting the EVM state. + +Stateless fuzzing is useful for arithmetic libraries or isolated functions where state does not need to be maintained +across transactions. Stateless fuzzing, although faster, is not useful for larger systems that have many code paths with +nuanced and complex invariants. diff --git a/docs/src/api/api_overview.md b/docs/src/api/api_overview.md new file mode 100644 index 00000000..ab3e6dee --- /dev/null +++ b/docs/src/api/api_overview.md @@ -0,0 +1,185 @@ +# API Overview (WIP) + +`medusa` offers a lower level API to hook into various parts of the fuzzer, its workers, and underlying chains. Although assertion and property testing are two built-in testing providers, they are implementing using events and hooks offered throughout the `Fuzzer`, `FuzzerWorker`(s), and underlying `TestChain`. These same hooks can be used by external developers wishing to implement their own customing testing methodology. In the sections below, we explore some of the relevant components throughout `medusa`, their events/hooks, an example of creating custom testing methodology with it. + +## Component overview + +A rudimentary description of the objects/providers and their roles are explained below. + +### Data types + +- `ProjectConfig`: This defines the configuration for the Fuzzer, including the targets to compile, deploy, and how to fuzz or test them. + +- `ValueSet`: This is an object that acts as a dictionary of values, used in mutation operations. It is populated at compilation time with some rudimentary static analysis. + +- `Contract`: Can be thought of as a "contract definition", it is a data type which stores the name of the contract, and a reference to the underlying `CompiledContract`, a definition derived from compilation, containing the bytecode, source maps, ABI, etc. + +- `CallSequence`: This represents a list of `CallSequenceElement`s, which define a transaction to send, the suggested block number and timestamp delay to use, and stores a reference to the block/transaction/results when it is executed (for later querying in tests). They are used to generate and execute transaction sequences in the fuzzer. + +- `CoverageMaps` define a list of `CoverageMap` objects, which record all instruction offsets executed for a given contract address and code hash. + +- `TestCase` defines the interface for a test that the `Fuzzer` will track. It simply defines a name, ID, status (not started, running, passed, failed) and message for the `Fuzzer`. + +### Providers + +- `ValueGenerator`: This is an object that provides methods to generate values of different kinds for transactions. Examples include the `RandomValueGenerator` and superceding `MutationalValueGenerator`. They are provided a `ValueSet` by their worker, which they may use in generation operations. + +- `TestChain`: This is a fake chain that operates on fake block structures created for the purpose of testing. Rather than operating on `types.Transaction` (which requires signing), it operates on `core.Message`s, which are derived from transactions and simply allow you to set the `sender` field. It is responsible for: + + - Maintaining state of the chain (blocks, transactions in them, results/receipts) + - Providing methods to create blocks, add transactions to them, commit them to chain, revert to previous block numbers. + - Allowing spoofing of block number and timestamp (commiting block number 1, then 50, jumping 49 blocks ahead), while simulating the existence of intermediate blocks. + - Provides methods to add tracers such as `evm.Logger` (standard go-ethereum tracers) or extend them with an additional interface (`TestChainTracer`) to also store any captured traced information in the execution results. This allows you to trace EVM execution for certain conditions, store results, and query them at a later time for testing. + +- `Fuzzer`: This is the main provider for the fuzzing process. It takes a `ProjectConfig` and is responsible for: + + - Housing data shared between the `FuzzerWorker`s such as contract definitions, a `ValueSet` derived from compilation to use in value generation, the reference to `Corpus`, the `CoverageMaps` representing all coverage achieved, as well as maintaining `TestCase`s registered to it and printing their results. + - Compiling the targets defined by the project config and setting up state. + - Provides methods to start/stop the fuzzing process, add additional compilation targets, access the initial value set prior to fuzzing start, access corpus, config, register new test cases and report them finished. + - Starts the fuzzing process by creating a "base" `TestChain`, deploys compiled contracts, replays all corpus sequences to measure existing coverage from previous fuzzing campaign, then spawns as many `FuzzerWorker`s as configured on their own goroutines ("threads") and passes them the "base" `TestChain` (which they clone) to begin the fuzzing operation. + - Respawns `FuzzerWorker`s when they hit a config-defined reset limit for the amount of transaction sequences they should process before destroying themselves and freeing memory. + - Maintains the context for when fuzzing should stop, which all workers track. + +- `FuzzerWorker`: This describes an object spawned by the `Fuzzer` with a given "base" `TestChain` with target contracts already deployed, ready to be fuzzed. It clones this chain, then is called upon to begin creating fuzz transactions. It is responsible for: + - Maintaining a reference to the parent `Fuzzer` for any shared information between it and other workers (`Corpus`, total `CoverageMaps`, contract definitions to match deployment's bytecode, etc) + - Maintaining its own `TestChain` to run fuzzed transaction sequences. + - Maintaining its own `ValueSet` which derives from the `Fuzzer`'s `ValueSet` (populated by compilation or user-provided values through API), as each `FuzzerWorker` may populate its `ValueSet` with different runtime values depending on their own chain state. + - Spawning a `ValueGenerator` which uses the `ValueSet`, to generate values used to construct fuzzed transaction sequences. + - Most importantly, it continuously: + - Generates `CallSequence`s (a series of transactions), plays them on its `TestChain`, records the results of in each `CallSequenceElement`, and calls abstract/hookable "test functions" to indicate they should perform post-tx tests (for which they can return requests for a shrunk test sequence). + - Updates the total `CoverageMaps` and `Corpus` with the current `CallSequence` if the most recent call increased coverage. + - Processes any shrink requests from the previous step (shrink requests can define arbitrary criteria for shrinking). + - Eventually, hits the config-defined reset limit for how many sequences it should process, and destroys itself to free all memory, expecting the `Fuzzer` to respawn another in its place. + +## Creating a project configuration + +`medusa` is config-driven. To begin a fuzzing campaign on an API level, you must first define a project configuration so the fuzzer knows what contracts to compile, deploy, and how it should operate. + +When using `medusa` over command-line, it operates a project config similarly (see [docs](https://github.com/trailofbits/medusa/wiki/Project-Configuration) or [example](https://github.com/trailofbits/medusa/wiki/Example-Project-Configuration-File)). Similarly, interfacing with a `Fuzzer` requires a `ProjectConfig` object. After importing `medusa` into your Go project, you can create one like this: + +```go +// Initialize a default project config with using crytic-compile as a compilation platform, and set the target it should compile. +projectConfig := config.GetDefaultProjectConfig("crytic-compile") +err := projectConfig.Compilation.SetTarget("contract.sol") +if err != nil { + return err +} + +// You can edit any of the values as you please. +projectConfig.Fuzzing.Workers = 20 +projectConfig.Fuzzing.DeploymentOrder = []string{"TestContract1", "TestContract2"} +``` + +You may also instantiate the whole config in-line with all the fields you'd like, setting the underlying platform config yourself. + +> **NOTE**: The `CompilationConfig` and `PlatformConfig` WILL BE deprecated and replaced with something more intuitive in the future, as the `compilation` package has not been updated since the project's inception, prior to the release of generics in go 1.18. + +## Creating and starting the fuzzer + +After you have created a `ProjectConfig`, you can create a new `Fuzzer` with it, and tell it to start: + +```go + // Create our fuzzer + fuzzer, err := fuzzing.NewFuzzer(*projectConfig) + if err != nil { + return err + } + + // Start the fuzzer + err = fuzzer.Start() + if err != nil { + return err + } + + // Fetch test cases results + testCases := fuzzer.TestCases() +[...] +``` + +> **Note**: `Fuzzer.Start()` is a blocking operation. If you wish to stop, you must define a TestLimit or Timeout in your config. Otherwise start it on another goroutine and call `Fuzzer.Stop()` to stop it. + +## Events/Hooks + +### Events + +Now it may be the case that you wish to hook the `Fuzzer`, `FuzzerWorker`, or `TestChain` to provide your own functionality. You can add your own testing methodology, and even power it with your own low-level EVM execution tracers to store and query results about each call. + +There are a few events/hooks that may be useful of the bat: + +The `Fuzzer` maintains event emitters for the following events under `Fuzzer.Events.*`: + +- `FuzzerStartingEvent`: Indicates a `Fuzzer` is starting and provides a reference to it. + +- `FuzzerStoppingEvent`: Indicates a `Fuzzer` has just stopped all workers and is about to print results and exit. + +- `FuzzerWorkerCreatedEvent`: Indicates a `FuzzerWorker` was created by a `Fuzzer`. It provides a reference to the `FuzzerWorker` spawned. The parent `Fuzzer` can be accessed through `FuzzerWorker.Fuzzer()`. +- `FuzzerWorkerDestroyedEvent`: Indicates a `FuzzerWorker` was destroyed. This can happen either due to hitting the config-defined worker reset limit or the fuzzing operation stopping. It provides a reference to the destroyed worker (for reference, though this should not be stored, to allow memory to free). + +The `FuzzerWorker` maintains event emiters for the following events under `FuzzerWorker.Events.*`: + +- `FuzzerWorkerChainCreatedEvent`: This indicates the `FuzzerWorker` is about to begin working and has created its chain (but not yet copied data from the "base" `TestChain` the `Fuzzer` provided). This offers an opportunity to attach tracers for calls made during chain setup. It provides a reference to the `FuzzerWorker` and its underlying `TestChain`. + +- `FuzzerWorkerChainSetupEvent`: This indicates the `FuzzerWorker` is about to begin working and has both created its chain, and copied data from the "base" `TestChain`, so the initial deployment of contracts is complete and fuzzing is ready to begin. It provides a reference to the `FuzzerWorker` and its underlying `TestChain`. + +- `CallSequenceTesting`: This indicates a new `CallSequence` is about to be generated and tested by the `FuzzerWorker`. It provides a reference to the `FuzzerWorker`. + +- `CallSequenceTested`: This indicates a `CallSequence` was just tested by the `FuzzerWorker`. It provides a reference to the `FuzzerWorker`. + +- `FuzzerWorkerContractAddedEvent`: This indicates a contract was added on the `FuzzerWorker`'s underlying `TestChain`. This event is emitted when the contract byte code is resolved to a `Contract` definition known by the `Fuzzer`. It may be emitted due to a contract deployment, or the reverting of a block which caused a SELFDESTRUCT. It provides a reference to the `FuzzerWorker`, the deployed contract address, and the `Contract` definition that it was matched to. + +- `FuzzerWorkerContractDeletedEvent`: This indicates a contract was removed on the `FuzzerWorker`'s underlying `TestChain`. It may be emitted due to a contract deployment which was reverted, or a SELFDESTRUCT operation. It provides a reference to the `FuzzerWorker`, the deployed contract address, and the `Contract` definition that it was matched to. + +The `TestChain` maintains event emitters for the following events under `TestChain.Events.*`: + +- `PendingBlockCreatedEvent`: This indicates a new block is being created but has not yet been committed to the chain. The block is empty at this point but will likely be populated. It provides a reference to the `Block` and `TestChain`. + +- `PendingBlockAddedTxEvent`: This indicates a pending block which has not yet been commited to chain has added a transaction to it, as it is being constructed. It provides a reference to the `Block`, `TestChain`, and index of the transaction in the `Block`. + +- `PendingBlockCommittedEvent`: This indicates a pending block was committed to chain as the new head. It provides a reference to the `Block` and `TestChain`. + +- `PendingBlockDiscardedEvent`: This indicates a pending block was not committed to chain and was instead discarded. + +- `BlocksRemovedEvent`: This indicates blocks were removed from the chain. This happens when a chain revert to a previous block number is invoked. It provides a reference to the `Block` and `TestChain`. + +- `ContractDeploymentsAddedEvent`: This indicates a new contract deployment was detected on chain. It provides a reference to the `TestChain`, as well as information captured about the bytecode. This may be triggered on contract deployment, or the reverting of a SELFDESTRUCT operation. + +- `ContractDeploymentsRemovedEvent`: This indicates a previously deployed contract deployment was removed from chain. It provides a reference to the `TestChain`, as well as information captured about the bytecode. This may be triggered on revert of a contract deployment, or a SELFDESTRUCT operation. + +### Hooks + +The `Fuzzer` maintains hooks for some of its functionality under `Fuzzer.Hooks.*`: + +- `NewValueGeneratorFunc`: This method is used to create a `ValueGenerator` for each `FuzzerWorker`. By default, this uses a `MutationalValueGenerator` constructed with the provided `ValueSet`. It can be replaced to provide a custom `ValueGenerator`. + +- `TestChainSetupFunc`: This method is used to set up a chain's initial state before fuzzing. By default, this method deploys all contracts compiled and marked for deployment in the `ProjectConfig` provided to the `Fuzzer`. It only deploys contracts if they have no constructor arguments. This can be replaced with your own method to do custom deployments. + + - **Note**: We do not recommend replacing this for now, as the `Contract` definitions may not be known to the `Fuzzer`. Additionally, `SenderAddresses` and `DeployerAddress` are the only addresses funded at genesis. This will be updated at a later time. + +- `CallSequenceTestFuncs`: This is a list of functions which are called after each `FuzzerWorker` executed another call in its current `CallSequence`. It takes the `FuzzerWorker` and `CallSequence` as input, and is expected to return a list of `ShinkRequest`s if some interesting result was found and we wish for the `FuzzerWorker` to shrink the sequence. You can add a function here as part of custom post-call testing methodology to check if some property was violated, then request a shrunken sequence for it with arbitrary criteria to verify the shrunk sequence satisfies your requirements (e.g. violating the same property again). + +### Extending testing methodology + +Although we will build out guidance on how you can solve different challenges or employ different tests with this lower level API, we intend to wrap some of this into a higher level API that allows testing complex post-call/event conditions with just a few lines of code externally. The lower level API will serve for more granular control across the system, and fine tuned optimizations. + +To ensure testing methodology was agnostic and extensible in `medusa`, we note that both assertion and property testing is implemented through the abovementioned events and hooks. When a higher level API is introduced, we intend to migrate these test case providers to that API. + +For now, the built-in `AssertionTestCaseProvider` (found [here](https://github.com/trailofbits/medusa/blob/8036697794481b7bf9fa78c922ec7fa6a8a3005c/fuzzing/test_case_assertion_provider.go)) and its test cases (found [here](https://github.com/trailofbits/medusa/blob/8036697794481b7bf9fa78c922ec7fa6a8a3005c/fuzzing/test_case_assertion.go)) are an example of code that _could_ exist externally outside of `medusa`, but plug into it to offer extended testing methodology. Although it makes use of some private variables, they can be replaced with public getter functions that are available. As such, if assertion testing didn't exist in `medusa` natively, you could've implemented it yourself externally! + +In the end, using it would look something like this: + +```go + // Create our fuzzer + fuzzer, err := fuzzing.NewFuzzer(*projectConfig) + if err != nil { + return err + } + + // Attach our custom test case provider + attachAssertionTestCaseProvider(fuzzer) + + // Start the fuzzer + err = fuzzer.Start() + if err != nil { + return err + } +``` diff --git a/docs/src/cheatcodes/addr.md b/docs/src/cheatcodes/addr.md new file mode 100644 index 00000000..5fe04c04 --- /dev/null +++ b/docs/src/cheatcodes/addr.md @@ -0,0 +1,24 @@ +# `addr` + +## Description + +The `addr` cheatcode will compute the address for a given private key. + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Test with random private key +uint256 pkOne = 0x6df21769a2082e03f7e21f6395561279e9a7feb846b2bf740798c794ad196e00; +address addrOne = 0xdf8Ef652AdE0FA4790843a726164df8cf8649339; +address result = cheats.addr(pkOne); +assert(result == addrOne); +``` + +## Function Signature + +```solidity +function addr(uint256 privateKey) external returns (address); +``` diff --git a/docs/src/cheatcodes/chain_id.md b/docs/src/cheatcodes/chain_id.md new file mode 100644 index 00000000..ae8fedeb --- /dev/null +++ b/docs/src/cheatcodes/chain_id.md @@ -0,0 +1,22 @@ +# `chainId` + +## Description + +The `chainId` cheatcode will set the `block.chainid` + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Change value and verify. +cheats.chainId(777123); +assert(block.chainid == 777123); +``` + +## Function Signature + +```solidity +function chainId(uint256) external; +``` diff --git a/docs/src/cheatcodes/cheatcodes_overview.md b/docs/src/cheatcodes/cheatcodes_overview.md new file mode 100644 index 00000000..ec2dcf46 --- /dev/null +++ b/docs/src/cheatcodes/cheatcodes_overview.md @@ -0,0 +1,118 @@ +# Cheatcodes Overview + +Cheatcodes allow users to manipulate EVM state, blockchain behavior, provide easy ways to manipulate data, and much more. +The cheatcode contract is deployed at `0x7109709ECfa91a80626fF3989D68f67F5b1DD12D`. + +## Cheatcode Interface + +The following interface must be added to your Solidity project if you wish to use cheatcodes. Note that if you use Foundry +as your compilation platform that the cheatcode interface is already provided [here](https://book.getfoundry.sh/reference/forge-std/#forge-stds-test). +However, it is important to note that medusa does not support all the cheatcodes provided out-of-box +by Foundry (see below for supported cheatcodes). + +```solidity +interface StdCheats { + // Set block.timestamp + function warp(uint256) external; + + // Set block.number + function roll(uint256) external; + + // Set block.basefee + function fee(uint256) external; + + // Set block.difficulty and block.prevrandao + function difficulty(uint256) external; + + // Set block.chainid + function chainId(uint256) external; + + // Sets the block.coinbase + function coinbase(address) external; + + // Loads a storage slot from an address + function load(address account, bytes32 slot) external returns (bytes32); + + // Stores a value to an address' storage slot + function store(address account, bytes32 slot, bytes32 value) external; + + // Sets the *next* call's msg.sender to be the input address + function prank(address) external; + + // Set msg.sender to the input address until the current call exits + function prankHere(address) external; + + // Sets an address' balance + function deal(address who, uint256 newBalance) external; + + // Sets an address' code + function etch(address who, bytes calldata code) external; + + // Signs data + function sign(uint256 privateKey, bytes32 digest) + external + returns (uint8 v, bytes32 r, bytes32 s); + + // Computes address for a given private key + function addr(uint256 privateKey) external returns (address); + + // Gets the nonce of an account + function getNonce(address account) external returns (uint64); + + // Sets the nonce of an account + // The new nonce must be higher than the current nonce of the account + function setNonce(address account, uint64 nonce) external; + + // Performs a foreign function call via terminal + function ffi(string[] calldata) external returns (bytes memory); + + // Take a snapshot of the current state of the EVM + function snapshot() external returns (uint256); + + // Revert state back to a snapshot + function revertTo(uint256) external returns (bool); + + // Convert Solidity types to strings + function toString(address) external returns(string memory); + function toString(bytes calldata) external returns(string memory); + function toString(bytes32) external returns(string memory); + function toString(bool) external returns(string memory); + function toString(uint256) external returns(string memory); + function toString(int256) external returns(string memory); + + // Convert strings into Solidity types + function parseBytes(string memory) external returns(bytes memory); + function parseBytes32(string memory) external returns(bytes32); + function parseAddress(string memory) external returns(address); + function parseUint(string memory)external returns(uint256); + function parseInt(string memory) external returns(int256); + function parseBool(string memory) external returns(bool); +} +``` + +# Using cheatcodes + +Below is an example snippet of how you would import the cheatcode interface into your project and use it. + +```solidity +// Assuming cheatcode interface is in the same directory +import "./IStdCheats.sol"; + +// MyContract will utilize the cheatcode interface +contract MyContract { + // Set up reference to cheatcode contract + IStdCheats cheats = IStdCheats(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + // This is a test function that will set the msg.sender's nonce to the provided input argument + function testFunc(uint256 _x) public { + // Ensure that the input argument is greater than msg.sender's current nonce + require(_x > cheats.getNonce(msg.sender)); + + // Set sender's nonce + cheats.setNonce(msg.sender, x); + + // Assert that the nonce has been correctly updated + assert(cheats.getNonce(msg.sender) == x); + } +} +``` diff --git a/docs/src/cheatcodes/coinbase.md b/docs/src/cheatcodes/coinbase.md new file mode 100644 index 00000000..a0ab068e --- /dev/null +++ b/docs/src/cheatcodes/coinbase.md @@ -0,0 +1,22 @@ +# `coinbase` + +## Description + +The `coinbase` cheatcode will set the `block.coinbase` + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Change value and verify. +cheats.coinbase(address(7)); +assert(block.coinbase == address(7)); +``` + +## Function Signature + +```solidity +function coinbase(address) external; +``` diff --git a/docs/src/cheatcodes/deal.md b/docs/src/cheatcodes/deal.md new file mode 100644 index 00000000..4518f61f --- /dev/null +++ b/docs/src/cheatcodes/deal.md @@ -0,0 +1,23 @@ +# `deal` + +## Description + +The `deal` cheatcode will set the ETH balance of address `who` to `newBalance` + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Change value and verify. +address acc = address(777); +cheats.deal(acc, x); +assert(acc.balance == x); +``` + +## Function Signature + +```solidity +function deal(address who, uint256 newBalance) external; +``` diff --git a/docs/src/cheatcodes/difficulty.md b/docs/src/cheatcodes/difficulty.md new file mode 100644 index 00000000..fa901849 --- /dev/null +++ b/docs/src/cheatcodes/difficulty.md @@ -0,0 +1,25 @@ +# `difficulty` + +## Description + +The `difficulty` cheatcode will set the `block.difficulty` and the `block.prevrandao` value. At the moment, both values +are changed since the cheatcode does not check what EVM version is running. + +Note that this behavior will change in the future. + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Change value and verify. +cheats.difficulty(x); +assert(block.difficulty == x); +``` + +## Function Signature + +```solidity +function difficulty(uint256) external; +``` diff --git a/docs/src/cheatcodes/etch.md b/docs/src/cheatcodes/etch.md new file mode 100644 index 00000000..4ba045bf --- /dev/null +++ b/docs/src/cheatcodes/etch.md @@ -0,0 +1,29 @@ +# `etch` + +## Description + +The `etch` cheatcode will set the `who` address's bytecode to `code`. + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Obtain our original code hash for an account. +address acc = address(777); +bytes32 originalCodeHash; +assembly { originalCodeHash := extcodehash(acc) } + +// Change value and verify. +cheats.etch(acc, address(someContract).code); +bytes32 updatedCodeHash; +assembly { updatedCodeHash := extcodehash(acc) } +assert(originalCodeHash != updatedCodeHash); +``` + +## Function Signature + +```solidity +function etch(address who, bytes calldata code) external; +``` diff --git a/docs/src/cheatcodes/fee.md b/docs/src/cheatcodes/fee.md new file mode 100644 index 00000000..a4c6f115 --- /dev/null +++ b/docs/src/cheatcodes/fee.md @@ -0,0 +1,22 @@ +# `fee` + +## Description + +The `fee` cheatcode will set the `block.basefee`. + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Change value and verify. +cheats.fee(7); +assert(block.basefee == 7); +``` + +## Function Signature + +```solidity +function fee(uint256) external; +``` diff --git a/docs/src/cheatcodes/ffi.md b/docs/src/cheatcodes/ffi.md new file mode 100644 index 00000000..562528ad --- /dev/null +++ b/docs/src/cheatcodes/ffi.md @@ -0,0 +1,58 @@ +# `ffi` + +## Description + +The `ffi` cheatcode is used to call an arbitrary command on your host OS. Note that `ffi` must be enabled via the project +configuration file by setting `fuzzing.chainConfig.cheatCodes.enableFFI` to `true`. + +Note that enabling `ffi` allows anyone to execute arbitrary commands on devices that run the fuzz tests which may +become a security risk. + +Please review [Foundry's documentation on the `ffi` cheatcode](https://book.getfoundry.sh/cheatcodes/ffi#tips) for general tips. + +## Example with ABI-encoded hex + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Create command +string[] memory inputs = new string[](3); +inputs[0] = "echo"; +inputs[1] = "-n"; +// Encoded "hello" +inputs[2] = "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000568656C6C6F000000000000000000000000000000000000000000000000000000"; + +// Call cheats.ffi +bytes memory res = cheats.ffi(inputs); + +// ABI decode +string memory output = abi.decode(res, (string)); +assert(keccak256(abi.encodePacked(output)) == keccak256(abi.encodePacked("hello"))); +``` + +## Example with UTF8 encoding + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Create command +string[] memory inputs = new string[](3); +inputs[0] = "echo"; +inputs[1] = "-n"; +inputs[2] = "hello"; + +// Call cheats.ffi +bytes memory res = cheats.ffi(inputs); + +// Convert to UTF-8 string +string memory output = string(res); +assert(keccak256(abi.encodePacked(output)) == keccak256(abi.encodePacked("hello"))); +``` + +## Function Signature + +```solidity +function ffi(string[] calldata) external returns (bytes memory); +``` diff --git a/docs/src/cheatcodes/get_nonce.md b/docs/src/cheatcodes/get_nonce.md new file mode 100644 index 00000000..b94f3773 --- /dev/null +++ b/docs/src/cheatcodes/get_nonce.md @@ -0,0 +1,22 @@ +# `getNonce` + +## Description + +The `getNonce` cheatcode will get the current nonce of `account`. + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Get nonce and verify that the sender has sent at least one transaction +address acc = address(msg.sender); +assert(cheats.getNonce(acc) > 0); +``` + +## Function Signature + +```solidity +function getNonce(address account) external returns (uint64); +``` diff --git a/docs/src/cheatcodes/load.md b/docs/src/cheatcodes/load.md new file mode 100644 index 00000000..488891b4 --- /dev/null +++ b/docs/src/cheatcodes/load.md @@ -0,0 +1,27 @@ +# `load` + +## Description + +The `load` cheatcode will load storage slot `slot` for `account` + +## Example + +```solidity +contract TestContract { + uint x = 123; + function test() public { + // Obtain our cheat code contract reference. + IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + // Load and verify x + bytes32 value = cheats.load(address(this), bytes32(uint(0))); + assert(value == bytes32(uint(123))); + } +} +``` + +## Function Signature + +```solidity +function load(address account, bytes32 slot) external returns (bytes32); +``` diff --git a/docs/src/cheatcodes/parse_address.md b/docs/src/cheatcodes/parse_address.md new file mode 100644 index 00000000..335a7eb0 --- /dev/null +++ b/docs/src/cheatcodes/parse_address.md @@ -0,0 +1,30 @@ +# `parseAddress` + +## Description + +The `parseAddress` cheatcode will parse the input string into an address + +## Example + +```solidity +contract TestContract { + uint x = 123; + function test() public { + // Obtain our cheat code contract reference. + IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + address expectedAddress = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; + string memory test = "0x7109709ECfa91a80626fF3989D68f67F5b1DD12D"; + + // Call cheats.parseAddress + address result = cheats.parseAddress(test); + assert(expectedAddress == result); + } +} +``` + +## Function Signature + +```solidity +function parseAddress(string calldata) external returns (address); +``` diff --git a/docs/src/cheatcodes/parse_bool.md b/docs/src/cheatcodes/parse_bool.md new file mode 100644 index 00000000..dbbc7241 --- /dev/null +++ b/docs/src/cheatcodes/parse_bool.md @@ -0,0 +1,30 @@ +# `parseBool` + +## Description + +The `parseBool` cheatcode will parse the input string into a boolean + +## Example + +```solidity +contract TestContract { + uint x = 123; + function test() public { + // Obtain our cheat code contract reference. + IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + bool expectedBool = true; + string memory test = "true"; + + // Call cheats.parseBool + bool result = cheats.parseBool(test); + assert(expectedBool == result); + } +} +``` + +## Function Signature + +```solidity +function parseBool(string calldata) external returns (bool); +``` diff --git a/docs/src/cheatcodes/parse_bytes.md b/docs/src/cheatcodes/parse_bytes.md new file mode 100644 index 00000000..4612116e --- /dev/null +++ b/docs/src/cheatcodes/parse_bytes.md @@ -0,0 +1,30 @@ +# `parseBytes` + +## Description + +The `parseBytes` cheatcode will parse the input string into bytes + +## Example + +```solidity +contract TestContract { + uint x = 123; + function test() public { + // Obtain our cheat code contract reference. + IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + bytes memory expectedBytes = "medusa"; + string memory test = "medusa"; + + // Call cheats.parseBytes + bytes memory result = cheats.parseBytes(test); + assert(keccak256(expectedBytes) == keccak256(result)); + } +} +``` + +## Function Signature + +```solidity +function parseBytes(string calldata) external returns (bytes memory); +``` diff --git a/docs/src/cheatcodes/parse_bytes32.md b/docs/src/cheatcodes/parse_bytes32.md new file mode 100644 index 00000000..6fb0ab2a --- /dev/null +++ b/docs/src/cheatcodes/parse_bytes32.md @@ -0,0 +1,30 @@ +# `parseBytes32` + +## Description + +The `parseBytes32` cheatcode will parse the input string into bytes32 + +## Example + +```solidity +contract TestContract { + uint x = 123; + function test() public { + // Obtain our cheat code contract reference. + IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + int256 expectedInt = -12345; + string memory test = "-12345"; + + // Call cheats.parseInt + int256 result = cheats.parseInt(test); + assert(expectedInt == result); + } +} +``` + +## Function Signature + +```solidity +function parseBytes32(string calldata) external returns (bytes32); +``` diff --git a/docs/src/cheatcodes/parse_int.md b/docs/src/cheatcodes/parse_int.md new file mode 100644 index 00000000..6a820a3c --- /dev/null +++ b/docs/src/cheatcodes/parse_int.md @@ -0,0 +1,30 @@ +# `parseInt` + +## Description + +The `parseInt` cheatcode will parse the input string into a int256 + +## Example + +```solidity +contract TestContract { + uint x = 123; + function test() public { + // Obtain our cheat code contract reference. + IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + address expectedAddress = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; + string memory test = "0x7109709ECfa91a80626fF3989D68f67F5b1DD12D"; + + // Call cheats.parseAddress + address result = cheats.parseAddress(test); + assert(expectedAddress == result); + } +} +``` + +## Function Signature + +```solidity +function parseInt(string calldata) external returns (int256); +``` diff --git a/docs/src/cheatcodes/parse_uint.md b/docs/src/cheatcodes/parse_uint.md new file mode 100644 index 00000000..8c9f2e46 --- /dev/null +++ b/docs/src/cheatcodes/parse_uint.md @@ -0,0 +1,30 @@ +# `parseUint` + +## Description + +The `parseUint` cheatcode will parse the input string into a uint256 + +## Example + +```solidity +contract TestContract { + uint x = 123; + function test() public { + // Obtain our cheat code contract reference. + IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + uint256 expectedUint = 12345; + string memory test = "12345"; + + // Call cheats.parseUint + uint256 result = cheats.parseUint(test); + assert(expectedUint == result); + } +} +``` + +## Function Signature + +```solidity +function parseUint(string calldata) external returns (uint256); +``` diff --git a/docs/src/cheatcodes/prank.md b/docs/src/cheatcodes/prank.md new file mode 100644 index 00000000..f309ba3f --- /dev/null +++ b/docs/src/cheatcodes/prank.md @@ -0,0 +1,38 @@ +# `prank` + +## Description + +The `prank` cheatcode will set the `msg.sender` for _only the next call_ to the specified input address. Note that, +contrary to [`prank` in Foundry](https://book.getfoundry.sh/cheatcodes/prank#description), calling the cheatcode contract will count as a +valid "next call" + +## Example + +```solidity +contract TestContract { + address owner = address(123); + function transferOwnership(address _newOwner) public { + require(msg.sender == owner); + + // Change ownership + owner = _newOwner; + } + + function test() public { + // Obtain our cheat code contract reference. + IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + // Prank, change ownership, and verify + address newOwner = address(456); + cheats.prank(owner); + transferOwnership(newOwner); + assert(owner == newOwner); + } + } +``` + +## Function Signature + +```solidity +function prank(address) external; +``` diff --git a/docs/src/cheatcodes/prank_here.md b/docs/src/cheatcodes/prank_here.md new file mode 100644 index 00000000..5723cfb8 --- /dev/null +++ b/docs/src/cheatcodes/prank_here.md @@ -0,0 +1,49 @@ +# `prankHere` + +## Description + +The `prankHere` cheatcode will set the `msg.sender` to the specified input address until the current call exits. Compared +to `prank`, `prankHere` can persist for multiple calls. + +## Example + +```solidity +contract TestContract { + address owner = address(123); + uint256 x = 0; + uint256 y = 0; + + function updateX() public { + require(msg.sender == owner); + + // Update x + x = 1; + } + + function updateY() public { + require(msg.sender == owner); + + // Update y + y = 1; + } + + function test() public { + // Obtain our cheat code contract reference. + IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + // Prank, update variables, and verify + cheats.prank(owner); + updateX(); + updateY(); + assert((x == 1) && (y == 1)); + + // Once this function returns, the `msg.sender` is reset + } +} +``` + +## Function Signature + +```solidity +function prankHere(address) external; +``` diff --git a/docs/src/cheatcodes/roll.md b/docs/src/cheatcodes/roll.md new file mode 100644 index 00000000..57b901e0 --- /dev/null +++ b/docs/src/cheatcodes/roll.md @@ -0,0 +1,24 @@ +# `roll` + +## Description + +The `roll` cheatcode sets the `block.number` + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Change value and verify. +cheats.roll(7); +assert(block.number == 7); +cheats.roll(9); +assert(block.number == 9); +``` + +## Function Signature + +```solidity +function roll(uint256) external; +``` diff --git a/docs/src/cheatcodes/set_nonce.md b/docs/src/cheatcodes/set_nonce.md new file mode 100644 index 00000000..8d949c8e --- /dev/null +++ b/docs/src/cheatcodes/set_nonce.md @@ -0,0 +1,24 @@ +# setNonce + +## Description + +The `setNonce` cheatcode will set the nonce of `account` to `nonce`. Note that the `nonce` must be strictly greater than +the current nonce + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Set nonce and verify (assume nonce before `setNonce` was less than 7) +address acc = address(msg.sender); +cheats.setNonce(acc, 7); +assert(cheats.getNonce(acc) == 7); +``` + +## Function Signature + +```solidity +function setNonce(address account, uint64 nonce) external; +``` diff --git a/docs/src/cheatcodes/sign.md b/docs/src/cheatcodes/sign.md new file mode 100644 index 00000000..dea23c2c --- /dev/null +++ b/docs/src/cheatcodes/sign.md @@ -0,0 +1,28 @@ +# `sign` + +## Description + +The `sign` cheatcode will take in a private key `privateKey` and a hash digest `digest` to generate a `(v, r, s)` +signature + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +bytes32 digest = keccak256("Data To Sign"); + +// Call cheats.sign +(uint8 v, bytes32 r, bytes32 s) = cheats.sign(0x6df21769a2082e03f7e21f6395561279e9a7feb846b2bf740798c794ad196e00, digest); +address signer = ecrecover(digest, v, r, s); +assert(signer == 0xdf8Ef652AdE0FA4790843a726164df8cf8649339); +``` + +## Function Signature + +```solidity +function sign(uint256 privateKey, bytes32 digest) +external +returns (uint8 v, bytes32 r, bytes32 s); +``` diff --git a/docs/src/cheatcodes/snapshot.md b/docs/src/cheatcodes/snapshot.md new file mode 100644 index 00000000..27298f6b --- /dev/null +++ b/docs/src/cheatcodes/snapshot.md @@ -0,0 +1,68 @@ +# `snapshot` and `revertTo` + +## Description + +The `snapshot` cheatcode will take a snapshot of the current state of the blockchain and return an identifier for the +snapshot. + +On the flipside, the `revertTo` cheatcode will revert the EVM state back based on the provided identifier. + +## Example + +```solidity +interface CheatCodes { + function warp(uint256) external; + + function deal(address, uint256) external; + + function snapshot() external returns (uint256); + + function revertTo(uint256) external returns (bool); +} + +struct Storage { + uint slot0; + uint slot1; +} + +contract TestContract { + Storage store; + uint256 timestamp; + + function test() public { + // Obtain our cheat code contract reference. + CheatCodes cheats = CheatCodes( + 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D + ); + + store.slot0 = 10; + store.slot1 = 20; + timestamp = block.timestamp; + cheats.deal(address(this), 5 ether); + + // Save state + uint256 snapshot = cheats.snapshot(); + + // Change state + store.slot0 = 300; + store.slot1 = 400; + cheats.deal(address(this), 500 ether); + cheats.warp(12345); + + // Assert that state has been changed + assert(store.slot0 == 300); + assert(store.slot1 == 400); + assert(address(this).balance == 500 ether); + assert(block.timestamp == 12345); + + // Revert to snapshot + cheats.revertTo(snapshot); + + // Ensure state has been reset + assert(store.slot0 == 10); + assert(store.slot1 == 20); + assert(address(this).balance == 5 ether); + assert(block.timestamp == timestamp); + } +} +``` diff --git a/docs/src/cheatcodes/store.md b/docs/src/cheatcodes/store.md new file mode 100644 index 00000000..d3fb580d --- /dev/null +++ b/docs/src/cheatcodes/store.md @@ -0,0 +1,27 @@ +# `store` + +## Description + +The `store` cheatcode will store `value` in storage slot `slot` for `account` + +## Example + +```solidity +contract TestContract { + uint x = 123; + function test() public { + // Obtain our cheat code contract reference. + IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + // Store into x, verify it. + cheats.store(address(this), bytes32(uint(0)), bytes32(uint(456))); + assert(y == 456); + } +} +``` + +## Function Signature + +```solidity +function store(address account, bytes32 slot, bytes32 value) external; +``` diff --git a/docs/src/cheatcodes/to_string.md b/docs/src/cheatcodes/to_string.md new file mode 100644 index 00000000..75e4182f --- /dev/null +++ b/docs/src/cheatcodes/to_string.md @@ -0,0 +1,84 @@ +# `toString` + +## Description + +The `toString` cheatcodes aid in converting primitive Solidity types into strings. Similar to +[Foundry's behavior](https://book.getfoundry.sh/cheatcodes/to-string?highlight=toStr#description), bytes are converted +to a hex-encoded string with `0x` prefixed. + +## Example + +```solidity +contract TestContract { + IStdCheats cheats; + + constructor() { + cheats = IStdCheats(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + } + + function testAddress() public { + address test = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; + string memory expectedString = "0x7109709ECfa91a80626fF3989D68f67F5b1DD12D"; + + // Call cheats.toString + string memory result = cheats.toString(test); + assert(keccak256(abi.encodePacked(result)) == keccak256(abi.encodePacked(expectedString))); + } + + function testBool() public { + bool test = true; + string memory expectedString = "true"; + + // Call cheats.toString + string memory result = cheats.toString(test); + assert(keccak256(abi.encodePacked(result)) == keccak256(abi.encodePacked(expectedString))); + } + + function testUint256() public { + uint256 test = 12345; + string memory expectedString = "12345"; + + // Call cheats.toString + string memory result = cheats.toString(test); + assert(keccak256(abi.encodePacked(result)) == keccak256(abi.encodePacked(expectedString))); + } + + function testInt256() public { + int256 test = -12345; + string memory expectedString = "-12345"; + + // Call cheats.toString + string memory result = cheats.toString(test); + assert(keccak256(abi.encodePacked(result)) == keccak256(abi.encodePacked(expectedString))); + } + + function testBytes32() public { + bytes32 test = "medusa"; + string memory expectedString = "0x6d65647573610000000000000000000000000000000000000000000000000000"; + + // Call cheats.toString + string memory result = cheats.toString(test); + assert(keccak256(abi.encodePacked(result)) == keccak256(abi.encodePacked(expectedString))); + } + + function testBytes() public { + bytes memory test = "medusa"; + string memory expectedString = "0x6d6564757361"; + + // Call cheats.toString + string memory result = cheats.toString(test); + assert(keccak256(abi.encodePacked(result)) == keccak256(abi.encodePacked(expectedString))); + } +} +``` + +## Function Signatures + +```solidity +function toString(address) external returns (string memory); +function toString(bool) external returns (string memory); +function toString(uint256) external returns (string memory); +function toString(int256) external returns (string memory); +function toString(bytes32) external returns (string memory); +function toString(bytes) external returns (string memory); +``` diff --git a/docs/src/cheatcodes/warp.md b/docs/src/cheatcodes/warp.md new file mode 100644 index 00000000..22830645 --- /dev/null +++ b/docs/src/cheatcodes/warp.md @@ -0,0 +1,24 @@ +# warp + +## Description + +The `warp` cheatcode sets the `block.timestamp` + +## Example + +```solidity +// Obtain our cheat code contract reference. +IStdCheats cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + +// Change value and verify. +cheats.warp(7); +assert(block.timestamp == 7); +cheats.warp(9); +assert(block.timestamp == 9); +``` + +## Function Signature + +```solidity +function warp(uint256) external; +``` diff --git a/docs/src/cli/completion.md b/docs/src/cli/completion.md new file mode 100644 index 00000000..242355db --- /dev/null +++ b/docs/src/cli/completion.md @@ -0,0 +1,21 @@ +# `completion` + +`medusa` provides the ability to generate autocompletion scripts for a given shell. +Once the autocompletion script is ran for a given shell, `medusa`'s commands and flags can be tab-autocompleted. +The following shells are supported: + +1. `bash` +2. `zsh` +3. `Powershell` + +To understand how to run the autocompletion script for a given shell, run the following command: + +```shell +medusa completion --help +``` + +Once you know how to run the autocompletion script, retrieve the script for that given shell using the following command: + +```shell +medusa completion +``` diff --git a/docs/src/cli/fuzz.md b/docs/src/cli/fuzz.md new file mode 100644 index 00000000..ed70d15a --- /dev/null +++ b/docs/src/cli/fuzz.md @@ -0,0 +1,131 @@ +# `fuzz` + +The `fuzz` command will initiate a fuzzing campaign: + +```shell +medusa fuzz [flags] +``` + +## Supported Flags + +### `--config` + +The `--config` flag allows you to specify the path for your [project configuration](../project_configuration/overview.md) +file. If the `--config` flag is not used, `medusa` will look for a [`medusa.json`](../static/medusa.json) file in the +current working directory. + +```shell +# Set config file path +medusa fuzz --out myConfig.json +``` + +### `--compilation-target` + +The `--compilation-target` flag allows you to specify the compilation target. If you are using `crytic-compile`, please review the +warning [here](../project_configuration/compilation_config.md#target) about changing the compilation target. + +```shell +# Set compilation target +medusa fuzz --target TestMyContract.sol +``` + +### `--workers` + +The `--workers` flag allows you to update the number of threads that will perform parallelized fuzzing (equivalent to +[`fuzzing.workers`](../project_configuration/fuzzing_config.md#workers)) + +```shell +# Set workers +medusa fuzz --workers 20 +``` + +### `--timeout` + +The `--timeout` flag allows you to update the duration of the fuzzing campaign (equivalent to +[`fuzzing.timeout`](../project_configuration/fuzzing_config.md#timeout)) + +```shell +# Set timeout +medusa fuzz --timeout 100 +``` + +### `--test-limit` + +The `--test-limit` flag allows you to update the number of transactions to run before stopping the fuzzing campaign +(equivalent to [`fuzzing.testLimit`](../project_configuration/fuzzing_config.md#testlimit)) + +```shell +# Set test limit +medusa fuzz --test-limit 100000 +``` + +### `--seq-len` + +The `--seq-len` flag allows you to update the length of a call sequence (equivalent to +[`fuzzing.callSequenceLength`](../project_configuration/fuzzing_config.md#callsequencelength)) + +```shell +# Set sequence length +medusa fuzz --seq-len 50 +``` + +### `--target-contracts` + +The `--target-contracts` flag allows you to update the target contracts for fuzzing (equivalent to +[`fuzzing.targetContracts`](../project_configuration/fuzzing_config.md#targetcontracts)) + +```shell +# Set target contracts +medusa fuzz --target-contracts "TestMyContract, TestMyOtherContract" +``` + +### `--corpus-dir` + +The `--corpus-dir` flag allows you to set the path for the corpus directory (equivalent to +[`fuzzing.corpusDirectory`](../project_configuration/fuzzing_config.md#corpusdirectory)) + +```shell +# Set corpus directory +medusa fuzz --corpus-dir corpus +``` + +### `--senders` + +The `--senders` flag allows you to update `medusa`'s senders (equivalent to +[`fuzzing.senderAddresses`](../project_configuration/fuzzing_config.md#senderaddresses)) + +```shell +# Set sender addresses +medusa fuzz --senders "0x50000,0x60000,0x70000" +``` + +### `--deployer` + +The `--deployer` flag allows you to update `medusa`'s contract deployer (equivalent to +[`fuzzing.deployerAddress`](../project_configuration/fuzzing_config.md#deployeraddress)) + +```shell +# Set deployer address +medusa fuzz --deployer "0x40000" +``` + +### `--trace-all` + +The `--trace-all` flag allows you to retrieve an execution trace for each element of a call sequence that triggered a test +failure (equivalent to +[`testing.traceAll`](../project_configuration/testing_config.md#traceall) + +```shell +# Trace each call +medusa fuzz --trace-all +``` + +### `--no-color` + +The `--no-color` flag disables colored console output (equivalent to +[`logging.NoColor`](../project_configuration/logging_config.md#nocolor)) + +```shell +# Disable colored output +medusa fuzz --no-color +``` diff --git a/docs/src/cli/init.md b/docs/src/cli/init.md new file mode 100644 index 00000000..e6f13cac --- /dev/null +++ b/docs/src/cli/init.md @@ -0,0 +1,36 @@ +# `init` + +The `init` command will generate the project configuration file within your current working directory: + +```shell +medusa init [platform] [flags] +``` + +By default, the project configuration file will be named `medusa.json`. You can learn more about `medusa`'s project +configuration [here](../project_configuration/overview.md) and also view an [example project configuration file](../static/medusa.json). + +Invoking this command without a `platform` argument will result in `medusa` using `crytic-compile` as the default compilation platform. +Currently, the only other supported platform is `solc`. If you are using a compilation platform such as Foundry or Hardhat, +it is best to use `crytic-compile`. + +## Supported Flags + +### `--out` + +The `--out` flag allows you to specify the output path for the project configuration file. Thus, you can name the file +something different from `medusa.json` or have the configuration file be placed elsewhere in your filesystem. + +```shell +# Set config file path +medusa init --out myConfig.json +``` + +### `--compilation-target` + +The `--compilation-target` flag allows you to specify the compilation target. If you are using `crytic-compile`, please review the +warning [here](../project_configuration/compilation_config.md#target) about changing the compilation target. + +```shell +# Set compilation target +medusa init --compilation-target TestMyContract.sol +``` diff --git a/docs/src/cli/overview.md b/docs/src/cli/overview.md new file mode 100644 index 00000000..b35fb74d --- /dev/null +++ b/docs/src/cli/overview.md @@ -0,0 +1,10 @@ +# CLI Overview + +The `medusa` CLI is used to perform parallelized fuzz testing of smart contracts. After you have `medusa` +[installed](../getting_started/installation.md), you can run `medusa help` in your terminal to view the available commands. + +The CLI supports three main commands with each command having a variety of flags: + +- [`medusa init`](./init.md) +- [`medusa fuzz`](./fuzz.md) +- [`medusa completion`](./completion.md) diff --git a/docs/src/console_logging.md b/docs/src/console_logging.md new file mode 100644 index 00000000..63d69510 --- /dev/null +++ b/docs/src/console_logging.md @@ -0,0 +1,55 @@ +# Console Logging + +Console logging in medusa is similar to the functionality found in Foundry or Hardhat (except for string formatting, +see [below](#differences-in-consolelogformatargs)). Note that if you are not using +Foundry or Hardhat as your compilation platform, you can retrieve the necessary `console.sol` library +[here](https://github.com/foundry-rs/forge-std/blob/master/src/console.sol). + +For more information on the available function signatures and general tips on console logging, please review [Foundry's +documentation](https://book.getfoundry.sh/reference/forge-std/console-log#console-logging). + +## Differences in `console.log(format[,...args])` + +The core functionality of string formatting is the same. If you want to string format an `int256`, the only supported function signature is: +`function log(string memory, int256) external;`. Otherwise, the supported argument types are `string`, `bool`, `address`, +and `uint256`. This capability is the same as in Foundry. + +The core difference in medusa's string formatting is the specifiers that are allowed for the +formatted string. The supported specifiers are as follows: + +- `%v`: The value will be printed in its default format. This will work for `uint256`, `int256`, `address`, + `bool`, and `string`. Using `%v` is the **recommended** specifier for all argument types. +- `%s`: The values will be converted into a human-readable string. This will work for `uint256`, `int256`, `address`, and + `string`. Contrary to Foundry or Hardhat, `%s` will not work for `bool`. Additionally, `uint256` and `int256` will _not_ + be provided in their hex-encoded format. This is the **recommended** specifier for projects that wish to maintain + compatibility with an existing fuzz test suite from Foundry. Special exceptions will need to be made for `bool` arguments. + For example, you could use the `console.logBool(bool)` function to separately log the `bool`. +- `%d`: This can be used for `uint256` and `int256`. +- `%i`: This specifier is not supported by medusa for `int256` and `uint256` +- `%e`: This specifier is not supported by medusa for `int256` and `uint256`. +- `%x`: This provides the hexadecimal representation of `int256` and `uint256`. +- `%o`: This specifier is not supported by medusa. `%o` in medusa will provide the base-8 representation of `int256` and + `uint256`. +- `%t`: This can be used for `bool`. +- `%%`: This will print out "%" and not consume an argument. + +If a specifier does not have a corresponding argument, the following is returned: + +```solidity +console.log("My name is %s %s", "medusa"); +// Returns: "My name is medusa %!s(MISSING)" +``` + +If there are more arguments than specifiers, the following is returned: + +```solidity +console.log("My name is %s", "medusa", "fuzzer"); +// Returns: "My name is medusa%!(EXTRA string=fuzzer)" +``` + +If only a format string with no arguments is provided, the string is returned with no formatting: + +```solidity +console.log("%% %s"); +// Returns: "%% %s" +``` diff --git a/docs/src/coverage_reports.md b/docs/src/coverage_reports.md new file mode 100644 index 00000000..cd24b564 --- /dev/null +++ b/docs/src/coverage_reports.md @@ -0,0 +1,3 @@ +# Coverage Reports + +WIP diff --git a/docs/src/faq.md b/docs/src/faq.md new file mode 100644 index 00000000..4d5ea1b9 --- /dev/null +++ b/docs/src/faq.md @@ -0,0 +1,16 @@ +# Frequently Asked Questions + +**Why create a new fuzzer if Echidna is already a great fuzzer?** + +With medusa, we are exploring a different EVM implementation and language for our smart contract fuzzer. While Echidna is already doing an amazing job, medusa offers the following advantages: + +- It is written in Go, easing the maintenance and allowing the creation of a native API for future integration into other projects. +- It uses geth as a base, ensuring the EVM equivalence. + +**Should I switch to medusa right away?** + +We do not recommend switching to medusa until it is extensively tested. However we encourage you to try it, and [let us know your experience](https://github.com/trailofbits/medusa/issues). In that sense, Echidna is our robust and well tested fuzzer, while medusa is our new exploratory fuzzer. [Follow us](https://twitter.com/trailofbits/) to hear updates about medusa as it grows in maturity. + +**Will all the previous available documentation from [secure-contracts.com](https://secure-contracts.com/) will apply to medusa?** + +In general, yes. All the information on testing approaches and techniques will apply for medusa. There are, however, different configuration options names and a few missing or different features in medusa from Echidna that we will be updating over time. diff --git a/docs/src/getting_started/first_steps.md b/docs/src/getting_started/first_steps.md new file mode 100644 index 00000000..7cd8456d --- /dev/null +++ b/docs/src/getting_started/first_steps.md @@ -0,0 +1,36 @@ +# First Steps + +After installation, you are ready to use `medusa` on your first codebase. This chapter will walk you through initializing +`medusa` for a project and then starting to fuzz. + +To initialize medusa for a project, `cd` into your project and run [`medusa init`](../cli/init.md): + +```shell +# Change working directory +cd my_project + +# Initialize medusa +medusa init +``` + +This will create a `medusa.json` file which holds a large number of [configuration options](../project_configuration/overview.md). +`medusa` will use this configuration file to determine how and what to fuzz. + +All there is left to do now is to run `medusa` on some fuzz tests: + +```shell +medusa fuzz --target-contracts "TestContract" --test-limit 10_000 +``` + +The `--target-contracts` flag tells `medusa` which contracts to run fuzz tests on. You can specify more than one +contract to fuzz test at once (e.g. `--target-contracts "TestContract, TestOtherContract"`). The `--test-limit` flag +tells `medusa` to execute `10_000` transactions before stopping the fuzzing campaign. + +> Note: The target contracts and the test limit can also be configured via the project configuration file, which is the +> **recommended** route. The `--target-contracts` flag is equivalent to the +> [`fuzzing.targetContracts`](../project_configuration/fuzzing_config.md#targetcontracts) configuration option and the +> `-test-limit` flag is equivalent to the [`fuzzing.testLimit`](../project_configuration/fuzzing_config.md#testlimit) +> configuration option. + +It is recommended to review the [Configuration Overview](../project_configuration/overview.md) next and learn more about +[`medusa`'s CLI](../cli/overview.md). diff --git a/docs/src/getting_started/installation.md b/docs/src/getting_started/installation.md new file mode 100644 index 00000000..531758ec --- /dev/null +++ b/docs/src/getting_started/installation.md @@ -0,0 +1,64 @@ +# Installation + +There are three main ways to install `medusa` at the moment. The first is using Homebrew, +building from source, or installing a precompiled binary. + +If you have any difficulty with installing `medusa`, please [open an issue](https://github.com/crytic/medusa/issues) on GitHub. + +## Installing with Homebrew + +Note that using Homebrew is only viable (and recommended) for macOS and Linux users. For Windows users, you must +[build from source](#building-from-source) or [install a precompiled binary](#precompiled-binaries). + +### Prerequisites + +Installation instructions for Homebrew can be found [here](https://brew.sh/). + +### Install `medusa` + +Run the following command to install `medusa`: + +```shell +brew install medusa +``` + +## Building from source + +### Prerequisites + +Before downloading `medusa`, you will need to download Golang and `crytic-compile`. + +- Installation instructions for Golang can be found [here](https://go.dev/doc/install) +- Installation instructions for `crytic-compile` can be found [here](https://github.com/crytic/crytic-compile#installation) + - Note that `crytic-compile` requires a Python environment. Installation instructions for Python can be found + [here](https://www.python.org/downloads/). + +### Build `medusa` + +Run the following commands to build `medusa` (this should work on all OSes): + +```shell +# Clone the repository +git clone https://github.com/crytic/medusa + +# Build medusa +cd medusa +go build -trimpath +``` + +You will now need to move the binary (`medusa` or `medusa.exe`) to somewhere in your `PATH` environment variable so that +it is accessible via the command line. Please review the instructions +[here](https://zwbetz.com/how-to-add-a-binary-to-your-path-on-macos-linux-windows/) (if you are a Windows user, we +recommend using the Windows GUI). + +## Precompiled binaries + +The precompiled binaries can be downloaded on `medusa`'s [GitHub releases page](https://github.com/crytic/medusa/releases). + +> **_NOTE:_** macOS may set the [quarantine extended attribute](https://superuser.com/questions/28384/what-should-i-do-about-com-apple-quarantine) +> on the downloaded zip file. To remove this attribute, run the following command: +> `sudo xattr -rd com.apple.quarantine `. + +Once installed, you will need to unzip the file and move the binary to somewhere in your `$PATH`. Please review the instructions +[here](https://zwbetz.com/how-to-add-a-binary-to-your-path-on-macos-linux-windows/) (if you are a Windows user, we +recommend using the Windows GUI). diff --git a/docs/src/project_configuration/chain_config.md b/docs/src/project_configuration/chain_config.md new file mode 100644 index 00000000..b101b56d --- /dev/null +++ b/docs/src/project_configuration/chain_config.md @@ -0,0 +1,25 @@ +# Chain Configuration + +The chain configuration defines the parameters for setting up `medusa`'s underlying blockchain. + +### `codeSizeCheckDisabled` + +- **Type**: Boolean +- **Description**: If `true`, the maximum code size check of 24576 bytes in `go-ethereum` is disabled. +- > 🚩 Setting `codeSizeCheckDisabled` to `false` is not recommended since it complicates the fuzz testing process. +- **Default**: `true` + +## Cheatcode Configuration + +### `cheatCodesEnabled` + +- **Type**: Boolean +- **Description**: Determines whether cheatcodes are enabled. +- **Default**: `true` + +### `enableFFI` + +- **Type**: Boolean +- **Description**: Determines whether the `ffi` cheatcode is enabled. + > 🚩 Enabling the `ffi` cheatcode may allow for arbitrary code execution on your machine. +- **Default**: `false` diff --git a/docs/src/project_configuration/compilation_config.md b/docs/src/project_configuration/compilation_config.md new file mode 100644 index 00000000..4e298fdf --- /dev/null +++ b/docs/src/project_configuration/compilation_config.md @@ -0,0 +1,59 @@ +# Compilation Configuration + +The compilation configuration defines the parameters to use while compiling a target file or project. + +### `platform` + +- **Type**: String +- **Description**: Refers to the type of platform to be used to compile the underlying target. Currently, + `crytic-compile` or `solc` can be used as the compilation platform. +- **Default**: `crytic-compile` + +### `platformConfig` + +- **Type**: Struct +- **Description**: This struct is a platform-dependent structure which offers parameters for compiling the underlying project. + See below for the structure of `platformConfig` for each compilation platform. +- **Default**: The `platformConfig` for `crytic-compile` is the default value for this struct. + +### `platformConfig` for `crytic-compile` + +#### `target` + +- **Type**: String +- **Description**: Refers to the target that is being compiled. + > 🚩 Note that if you are using a compilation platform, such as Foundry or Hardhat, the default value for `target`, `.`, + > should **not** be changed. The `.` is equivalent to telling `crytic-compile` that the entire project needs to compiled, + > including any dependencies and remappings. In fact, unless you want to compile a single file, that has no third-party + > imports from, for example, OpenZeppelin, the default value should not be changed. +- **Default**: `.` + +#### `solcVersion` + +- **Type**: String +- **Description**: Describes the version of `solc` that will be installed and then used for compilation. Note that if you + are using a compilation platform, such as Foundry or Hardhat, this option does not need to be set. +- **Default**: "" + +#### `exportDirectory` + +- **Type**: String +- **Description**: Describes the directory where all compilation artifacts should be stored after compilation. Leaving it + empty will lead to the compilation artifacts being stored in `crytic-export/`. +- **Default**: "" + +#### `args` + +- **Type**: [String] +- **Description**: Refers to any additional args that one may want to provide to `crytic-compile`. Run `crytic-compile --help` + to view all of its supported flags. For example, if you would like to specify `--compile-force-framework foundry`, the + `args` value will be `"args": ["--compile-force-framework", "foundry"]`. + > 🚩 The `--export-format` and `--export-dir` are already used during compilation with `crytic-compile`. + > Re-using these flags in `args` will cause the compilation to fail. + +### `platformConfig` for `solc` + +#### `target` + +- **Type**: String +- **Description**: Refers to the target that is being compiled. The target must be a single `.sol` file. diff --git a/docs/src/project_configuration/fuzzing_config.md b/docs/src/project_configuration/fuzzing_config.md new file mode 100644 index 00000000..0fa1f1e5 --- /dev/null +++ b/docs/src/project_configuration/fuzzing_config.md @@ -0,0 +1,205 @@ +# Fuzzing Configuration + +The fuzzing configuration defines the parameters for the fuzzing campaign. + +### `workers` + +- **Type**: Integer +- **Description**: The number of worker threads to parallelize fuzzing operations on. +- **Default**: 10 workers + +### `workerResetLimit` + +- **Type**: Integer +- **Description**: The number of call sequences a worker should process on its underlying chain before being fully reset, + freeing memory. After resetting, the worker will be re-created and continue processing of call sequences. + > 🚩 This setting, along with `workers` influence the speed and memory consumption of the fuzzer. Setting this value + > higher will result in greater memory consumption per worker. Setting it too high will result in the in-memory + > chain's database growing to a size that is slower to process. Setting it too low may result in frequent worker resets + > that are computationally expensive for complex contract deployments that need to be replayed during worker reconstruction. +- **Default**: 50 sequences + +### `timeout` + +- **Type**: Integer +- **Description**: 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 after compilation succeeds and the fuzzing campaign has started. +- **Default**: 0 seconds + +### `testLimit` + +- **Type**: Integer +- **Description**: The number of function calls to make before the fuzzing campaign should be terminated. If a zero value + is provided, no test limit will be enforced. +- **Default**: 0 calls + +### `callSequenceLength` + +- **Type**: Integer +- **Description**: The maximum number of function calls to generate in a single call sequence in the attempt to violate + properties. After every `callSequenceLength` function calls, the blockchain is reset for the next sequence of transactions. +- **Default**: 100 calls/sequence + +### `coverageEnabled` + +- **Type**: Boolean +- **Description**: Whether coverage-increasing call sequences should be saved for the fuzzer to mutate/re-use. + Enabling coverage allows for improved code exploration. +- **Default**: `true` + +### `corpusDirectory` + +- **Type**: String +- **Description**: The file path where the corpus should be saved. The corpus collects sequences during a fuzzing campaign + that help drive fuzzer features (e.g. a call sequence that increases code coverage is stored in the corpus). These sequences + can then be re-used/mutated by the fuzzer during the next fuzzing campaign. +- **Default**: "" + +### `targetContracts` + +- **Type**: [String] (e.g. `[FirstContract, SecondContract, ThirdContract]`) +- **Description**: The list of contracts that will be deployed on the blockchain and then targeted for fuzzing by `medusa`. + For single-contract compilations, this value can be left as `[]`. This, however, is rare since most projects are multi-contract compilations. + > 🚩 Note that the order specified in the array is the _order_ in which the contracts are deployed to the blockchain. + > Thus, if you have a `corpusDirectory` set up, and you change the order of the contracts in the array, the corpus may no + > longer work since the contract addresses of the target contracts will change. This may render the entire corpus useless. +- **Default**: `[]` + +### `targetContractBalances` + +- **Type**: [Base-16 Strings] (e.g. `[0x123, 0x456, 0x789]`) +- **Description**: The starting balance for each contract in `targetContracts`. If the `constructor` for a target contract + is marked `payable`, this configuration option can be used to send ether during contract deployment. Note that this array + has a one-to-one mapping to `targetContracts`. Thus, if `targetContracts` is `[A, B, C]` and `targetContractsBalances` is + `["0", "0xff", "0"]`, then `B` will have a starting balance of 255 wei and `A` and `C` will have zero wei. Note that the wei-value + has to be hex-encoded and _cannot_ have leading zeros. For an improved user-experience, the balances may be encoded as base-10 + format strings in the future. +- **Default**: `[]` + +### `constructorArgs` + +- **Type**: `{"contractName": {"variableName": _value}}` +- **Description**: If a contract in the `targetContracts` has a `constructor` that takes in variables, these can be specified here. + An example can be found [here](#using-constructorargs). +- **Default**: `{}` + +### `deployerAddress` + +- **Type**: Address +- **Description**: The address used to deploy contracts on startup, represented as a hex string. + > 🚩 Changing this address may render entries in the corpus invalid since the addresses of the target contracts will change. +- **Default**: `0x30000` + +### `senderAddresses` + +- **Type**: [Address] +- **Description**: Defines the account addresses used to send function calls to deployed contracts in the fuzzing campaign. + > 🚩 Changing these addresses may render entries in the corpus invalid since the sender(s) of corpus transactions may no + > longer be valid. +- **Default**: `[0x10000, 0x20000, 0x30000]` + +### `blockNumberDelayMax` + +- **Type**: Integer +- **Description**: Defines the maximum block number jump the fuzzer should make between test transactions. The fuzzer + will use this value to make the next block's `block.number` between `[1, blockNumberDelayMax]` more than that of the previous + block. Jumping `block.number` allows `medusa` to enter code paths that require a given number of blocks to pass. +- **Default**: `60_480` + +### `blockTimestampDelayMax` + +- **Type**: Integer +- **Description**: The number of the maximum block timestamp jump the fuzzer should make between test transactions. + The fuzzer will use this value to make the next block's `block.timestamp` between `[1, blockTimestampDelayMax]` more + than that of the previous block. Jumping `block.timestamp`time allows `medusa` to enter code paths that require a given amount of time to pass. +- **Default**: `604_800` + +### `blockGasLimit` + +- **Type**: Integer +- **Description**: The maximum amount of gas a block's transactions can use in total (thus defining max transactions per block). + > 🚩 It is advised not to change this naively, as a minimum must be set for the chain to operate. +- **Default**: `125_000_000` + +### `transactionGasLimit` + +- **Type**: Integer +- **Description**: Defines the amount of gas sent with each fuzzer-generated transaction. + > 🚩 It is advised not to change this naively, as a minimum must be set for the chain to operate. +- **Default**: `12_500_000` + +## Using `constructorArgs` + +There might be use cases where contracts in `targetContracts` have constructors that accept arguments. The `constructorArgs` +configuration option allows you to specify those arguments. `constructorArgs` is a nested dictionary that maps +contract name -> variable name -> variable value. Let's look at an example below: + +```solidity +// This contract is used to test deployment of contracts with constructor arguments. +contract TestContract { + struct Abc { + uint a; + bytes b; + } + + uint x; + bytes2 y; + Abc z; + + constructor(uint _x, bytes2 _y, Abc memory _z) { + x = _x; + y = _y; + z = _z; + } +} + +contract DependentOnTestContract { + address deployed; + + constructor(address _deployed) { + deployed = _deployed; + } +} +``` + +In the example above, we have two contracts `TestContract` and `DependentOnTestContract`. You will note that +`DependentOnTestContract` requires the deployment of `TestContract` _first_ so that it can accept the address of where +`TestContract` was deployed. On the other hand, `TestContract` requires `_x`, `_y`, and `_z`. Here is what the +`constructorArgs` value would look like for the above deployment: + +> **Note**: The example below has removed all the other project configuration options outside of `targetContracts` and +> `constructorArgs` + +```json +{ + "fuzzing": { + "targetContracts": ["TestContract", "DependentOnTestContract"], + "constructorArgs": { + "TestContract": { + "_x": "123456789", + "_y": "0x5465", + "_z": { + "a": "0x4d2", + "b": "0x54657374206465706c6f796d656e74207769746820617267756d656e7473" + } + }, + "DependentOnTestContract": { + "_deployed": "DeployedContract:TestContract" + } + } + } +} +``` + +First, let us look at `targetContracts`. As mentioned in the [documentation for `targetContracts`](#targetcontracts), +the order of the contracts in the array determine the order of deployment. This means that `TestContract` will be +deployed first, which is what we want. + +Now, let us look at `constructorArgs`. `TestContract`'s dictionary specifies the _exact name_ of the constructor argument +(e.g. `_x` or `_y`) with their associated value. Since `_z` is of type `TestContract.Abc`, `_z` is also a dictionary +that specifies each field in the `TestContract.Abc` struct. + +For `DependentOnTestContract`, the `_deployed` key has +a value of `DeployedContract:TestContract`. This tells `medusa` to look for a deployed contract that has the name +`TestContract` and provide its address as the value for `_deployed`. Thus, whenever you need a deployed contract's +address as an argument for another contract, you must follow the format `DeployedContract:`. diff --git a/docs/src/project_configuration/logging_config.md b/docs/src/project_configuration/logging_config.md new file mode 100644 index 00000000..fb0af649 --- /dev/null +++ b/docs/src/project_configuration/logging_config.md @@ -0,0 +1,25 @@ +# Logging Configuration + +The logging configuration defines the parameters for logging to console and/or file. + +### `level` + +- **Type**: String +- **Description**: The log level will determine which logs are emitted or discarded. If `level` is "info" then all logs + with informational level or higher will be logged. The supported values for `level` are "trace", "debug", "info", "warn", "error", + and "panic". +- **Default**: "info" + +### `logDirectory` + +- **Type**: String +- **Description**: Describes what directory log files should be outputted. Have a non-empty `logDirectory` value will + enable "file logging" which will result in logs to be output to both console and file. Note that the directory path is + _relative_ to the directory containing the project configuration file. +- **Default**: "" + +### `noColor` + +- **Type**: Boolean +- **Description**: Disables colored output to console. +- **Default**: `false` diff --git a/docs/src/project_configuration/overview.md b/docs/src/project_configuration/overview.md new file mode 100644 index 00000000..af7d3a46 --- /dev/null +++ b/docs/src/project_configuration/overview.md @@ -0,0 +1,49 @@ +# Configuration Overview + +`medusa`'s project configuration provides extensive and granular control over the execution of the fuzzer. The project +configuration is a `.json` file that is broken down into five core components. + +- [Fuzzing Configuration](./fuzzing_config.md): The fuzzing configuration dictates the parameters with which the fuzzer will execute. +- [Testing Configuration](./testing_config.md): The testing configuration dictates how and what `medusa` should fuzz test. +- [Chain Configuration](./chain_config.md): The chain configuration dictates how `medusa`'s underlying blockchain should be configured. +- [Compilation Configuration](./compilation_config.md): The compilation configuration dictates how to compile the fuzzing target. +- [Logging Configuration](./logging_config.md): The logging configuration dictates when and where to log events. + +To generate a project configuration file, run [`medusa init`](../cli/init.md). + +You can also view this [example project configuration file](../static/medusa.json) for visualization. + +## Recommended Configuration + +A common issue that first-time users face is identifying which configuration options to change. `medusa` provides an +incredible level of flexibility on how the fuzzer should run but this comes with a tradeoff of understanding the nuances +of what configuration options control what feature. Outlined below is a list of configuration options that we recommend +you become familiar with and change before starting to fuzz test. + +> **Note:** Having an [example project configuration file](../static/medusa.json) open will aid in visualizing which +> configuration options to change. + +### `fuzzing.targetContracts` + +Updating this configuration option is **required**! The `targetContracts` configuration option tells `medusa` which contracts +to fuzz test. You can specify one or more contracts for this option which is why it accepts an array +of strings. Let's say you have a fuzz testing contract called `TestStakingContract` that you want to test. +Then, you would set the value of `targetContracts` to `["TestStakingContract"]`. +You can learn more about this option [here](./fuzzing_config.md#targetcontracts). + +### `fuzzing.testLimit` + +Updating test limit is optional but recommended. Test limit determines how many transactions `medusa` will execute before +stopping the fuzzing campaign. By default, the `testLimit` is set to 0. This means that `medusa` will run indefinitely. +While you iterate over your fuzz tests, it is beneficial to have a non-zero value. Thus, it is recommended to update this +value to `10_000` or `100_000` depending on the use case. You can learn more about this option [here](./fuzzing_config.md#testlimit). + +### `fuzzing.corpusDirectory` + +Updating the corpus directory is optional but recommended. The corpus directory determines where corpus items should be +stored on disk. A corpus item is a sequence of transactions that increased `medusa`'s coverage of the system. Thus, these +corpus items are valuable to store so that they can be re-used for the next fuzzing campaign. Additionally, the directory +will also hold [coverage reports](TODO) which is a valuable tool for debugging and validation. For most cases, you may set +`corpusDirectory`'s value to "corpus". This will create a `corpus/` directory in the same directory as the `medusa.json` +file. +You can learn more about this option [here](./fuzzing_config.md#corpusdirectory). diff --git a/docs/src/project_configuration/testing_config.md b/docs/src/project_configuration/testing_config.md new file mode 100644 index 00000000..3adfe08c --- /dev/null +++ b/docs/src/project_configuration/testing_config.md @@ -0,0 +1,178 @@ +# Testing Configuration + +The testing configuration can be broken down into a few subcomponents: + +- **High-level configuration**: Configures global testing parameters, regardless of the type of testing. +- **Assertion testing configuration**: Configures what kind of EVM panics should be treated as a failing fuzz test. +- **Property testing configuration**: Configures what kind of function signatures should be treated as property tests. +- **Optimization testing configuration**: Configures what kind of function signatures should be treated as optimization tests. + +We will go over each subcomponent one-by-one: + +## High-level Configuration + +### `stopOnFailedTest` + +- **Type**: Boolean +- **Description**: Determines whether the fuzzer should stop execution after the first _failed_ test. If `false`, `medusa` + will continue fuzzing until either the [`testLimit`](./fuzzing_config.md#testlimit) is hit, the [`timeout`](./fuzzing_config.md#timeout) + is hit, or the user manually stops execution. +- **Default**: `true` + +### `stopOnFailedContractMatching` + +- **Type**: Boolean +- **Description**: Determines whether the fuzzer should stop execution if it is unable to match the bytecode of a dynamically + deployed contract. A dynamically deployed contract is one that is created during the fuzzing campaign + (versus one that is specified in the [`fuzzing.targetContracts`](./fuzzing_config.md#targetcontracts)). + Here is an example of a dynamically deployed contract: + +```solidity + +contract MyContract { + OtherContract otherContract; + constructor() { + // This is a dynamically deployed contract + otherContract = new otherContract(); + } +} +``` + +- **Default**: `false` + +### `stopOnNoTests` + +- **Type**: Boolean +- **Description**: Determines whether the fuzzer should stop execution if no tests are found + (property tests, assertion tests, optimization tests, or custom API-level tests). If `false` and no tests are found, + `medusa` will continue fuzzing until either the [`testLimit`](./fuzzing_config.md#testlimit) is hit, + the [`timeout`](./fuzzing_config.md#timeout) is hit, or the user manually stops execution. +- **Default**: `true` + +### `testAllContracts` + +- **Type**: Boolean +- **Description**: Determines whether all contracts should be tested (including dynamically deployed ones), rather than + just the contracts specified in the project configuration's [`fuzzing.targetContracts`](./fuzzing_config.md#targetcontracts). +- **Default**: `false` + +### `traceAll`: + +- **Type**: Boolean +- **Description**: Determines whether an [execution trace](TODO) should be attached to each element of a call sequence + that triggered a test failure. +- **Default**: `false` + +## Assertion Testing Configuration + +### `enabled` + +- **Type**: Boolean +- **Description**: Enable or disable assertion testing +- **Default**: `true` + +### `testViewMethods` + +- **Type**: Boolean +- **Description**: Whether `pure` / `view` functions should be tested for assertion failures. + > 🚩 Fuzzing `pure` and `view` functions is not currently implemented. Thus, enabling this option to `true` does not + > update the fuzzer's behavior. +- **Default**: `false` + +### `panicCodeConfig` + +- **Type**: Struct +- **Description**: This struct describes the various types of EVM-level panics that should be considered a "failing case". + By default, only an `assert(false)` is considered a failing case. However, these configuration options would allow a user + to treat arithmetic overflows or division by zero as failing cases as well. + +#### `failOnAssertion` + +- **Type**: Boolean +- **Description**: Triggering an assertion failure (e.g. `assert(false)`) should be treated as a failing case. +- **Default**: `true` + +#### `failOnCompilerInsertedPanic` + +- **Type**: Boolean +- **Description**: Triggering a compiler-inserted panic should be treated as a failing case. +- **Default**: `false` + +#### `failOnArithmeticUnderflow` + +- **Type**: Boolean +- **Description**: Arithmetic underflow or overflow should be treated as a failing case +- **Default**: `false` + +#### `failOnDivideByZero` + +- **Type**: Boolean +- **Description**: Dividing by zero should be treated as a failing case +- **Default**: `false` + +#### `failOnEnumTypeConversionOutOfBounds` + +- **Type**: Boolean +- **Description**: An out-of-bounds enum access should be treated as a failing case +- **Default**: `false` + +#### `failOnIncorrectStorageAccess` + +- **Type**: Boolean +- **Description**: An out-of-bounds storage access should be treated as a failing case +- **Default**: `false` + +#### `failOnPopEmptyArray` + +- **Type**: Boolean +- **Description**: A `pop()` operation on an empty array should be treated as a failing case +- **Default**: `false` + +#### `failOnOutOfBoundsArrayAccess` + +- **Type**: Boolean +- **Description**: An out-of-bounds array access should be treated as a failing case +- **Default**: `false` + +#### `failOnAllocateTooMuchMemory` + +- **Type**: Boolean +- **Description**: Overallocation/excessive memory usage should be treated as a failing case +- **Default**: `false` + +#### `failOnCallUninitializedVariable` + +- **Type**: Boolean +- **Description**: Calling an uninitialized variable should be treated as a failing case +- **Default**: `false` + +## Property Testing Configuration + +### `enabled` + +- **Type**: Boolean +- **Description**: Enable or disable property testing. +- **Default**: `true` + +### `testPrefixes` + +- **Type**: [String] +- **Description**: The list of prefixes that the fuzzer will use to determine whether a given function is a property test or not. + For example, if `property_` is a test prefix, then any function name in the form `property_*` may be a property test. + > **Note**: If you are moving over from Echidna, you can add `echidna_` as a test prefix to quickly port over the property tests from it. +- **Default**: `[property_]` + +## Optimization Testing Configuration + +### `enabled` + +- **Type**: Boolean +- **Description**: Enable or disable optimization testing. +- **Default**: `true` + +### `testPrefixes` + +- **Type**: [String] +- **Description**: The list of prefixes that the fuzzer will use to determine whether a given function is an optimization + test or not. For example, if `optimize_` is a test prefix, then any function name in the form `optimize_*` may be a property test. +- **Default**: `[optimize_]` diff --git a/docs/src/static/contract_deployment.png b/docs/src/static/contract_deployment.png new file mode 100644 index 0000000000000000000000000000000000000000..a021b01cca7460f816cbc3072f2217ab0a8b36e0 GIT binary patch literal 7790 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 SnoP`hyFt1SbZ~bZpZ*7Q<6tHL literal 0 HcmV?d00001 diff --git a/docs/src/static/coverage.png b/docs/src/static/coverage.png new file mode 100644 index 0000000000000000000000000000000000000000..f15676ea08840f0d56c8a2a7ff123ea715d675de GIT binary patch literal 36174 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 literal 0 HcmV?d00001 diff --git a/docs/src/static/custom.css b/docs/src/static/custom.css new file mode 100644 index 00000000..e4565d1a --- /dev/null +++ b/docs/src/static/custom.css @@ -0,0 +1,6 @@ +img[alt="medusa_logo"] { + width: 60%; + margin-left: auto; + margin-right: auto; + display: block; +} diff --git a/docs/src/static/function_level_testing_medusa.json b/docs/src/static/function_level_testing_medusa.json new file mode 100644 index 00000000..79f2aa21 --- /dev/null +++ b/docs/src/static/function_level_testing_medusa.json @@ -0,0 +1,72 @@ +{ + "fuzzing": { + "workers": 10, + "workerResetLimit": 50, + "timeout": 0, + "testLimit": 1000, + "callSequenceLength": 1, + "corpusDirectory": "", + "coverageEnabled": true, + "targetContracts": ["TestDepositContract"], + "targetContractsBalances": ["0xfffffffffffffffffffffffffffffff"], + "constructorArgs": {}, + "deployerAddress": "0x30000", + "senderAddresses": ["0x10000", "0x20000", "0x30000"], + "blockNumberDelayMax": 60480, + "blockTimestampDelayMax": 604800, + "blockGasLimit": 125000000, + "transactionGasLimit": 12500000, + "testing": { + "stopOnFailedTest": true, + "stopOnFailedContractMatching": false, + "stopOnNoTests": true, + "testAllContracts": false, + "traceAll": false, + "assertionTesting": { + "enabled": true, + "testViewMethods": false, + "panicCodeConfig": { + "failOnCompilerInsertedPanic": false, + "failOnAssertion": true, + "failOnArithmeticUnderflow": false, + "failOnDivideByZero": false, + "failOnEnumTypeConversionOutOfBounds": false, + "failOnIncorrectStorageAccess": false, + "failOnPopEmptyArray": false, + "failOnOutOfBoundsArrayAccess": false, + "failOnAllocateTooMuchMemory": false, + "failOnCallUninitializedVariable": false + } + }, + "propertyTesting": { + "enabled": true, + "testPrefixes": ["property_"] + }, + "optimizationTesting": { + "enabled": true, + "testPrefixes": ["optimize_"] + } + }, + "chainConfig": { + "codeSizeCheckDisabled": true, + "cheatCodes": { + "cheatCodesEnabled": true, + "enableFFI": false + } + } + }, + "compilation": { + "platform": "crytic-compile", + "platformConfig": { + "target": "test.sol", + "solcVersion": "", + "exportDirectory": "", + "args": [] + } + }, + "logging": { + "level": "info", + "logDirectory": "", + "noColor": false + } +} diff --git a/docs/src/static/medusa.json b/docs/src/static/medusa.json new file mode 100644 index 00000000..2e8644b6 --- /dev/null +++ b/docs/src/static/medusa.json @@ -0,0 +1,72 @@ +{ + "fuzzing": { + "workers": 10, + "workerResetLimit": 50, + "timeout": 0, + "testLimit": 0, + "callSequenceLength": 100, + "corpusDirectory": "", + "coverageEnabled": true, + "targetContracts": [], + "targetContractsBalances": [], + "constructorArgs": {}, + "deployerAddress": "0x30000", + "senderAddresses": ["0x10000", "0x20000", "0x30000"], + "blockNumberDelayMax": 60480, + "blockTimestampDelayMax": 604800, + "blockGasLimit": 125000000, + "transactionGasLimit": 12500000, + "testing": { + "stopOnFailedTest": true, + "stopOnFailedContractMatching": false, + "stopOnNoTests": true, + "testAllContracts": false, + "traceAll": false, + "assertionTesting": { + "enabled": true, + "testViewMethods": false, + "panicCodeConfig": { + "failOnCompilerInsertedPanic": false, + "failOnAssertion": true, + "failOnArithmeticUnderflow": false, + "failOnDivideByZero": false, + "failOnEnumTypeConversionOutOfBounds": false, + "failOnIncorrectStorageAccess": false, + "failOnPopEmptyArray": false, + "failOnOutOfBoundsArrayAccess": false, + "failOnAllocateTooMuchMemory": false, + "failOnCallUninitializedVariable": false + } + }, + "propertyTesting": { + "enabled": true, + "testPrefixes": ["property_"] + }, + "optimizationTesting": { + "enabled": true, + "testPrefixes": ["optimize_"] + } + }, + "chainConfig": { + "codeSizeCheckDisabled": true, + "cheatCodes": { + "cheatCodesEnabled": true, + "enableFFI": false + } + } + }, + "compilation": { + "platform": "crytic-compile", + "platformConfig": { + "target": ".", + "solcVersion": "", + "exportDirectory": "", + "args": [] + } + }, + "logging": { + "level": "info", + "logDirectory": "", + "noColor": false + } +} diff --git a/docs/src/static/medusa_logo.png b/docs/src/static/medusa_logo.png new file mode 100755 index 0000000000000000000000000000000000000000..11c6061e8ee3bfbcbc453e0067f45a2e0ce0b824 GIT binary patch literal 110398 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�H2f!!6P0V8s?lb>mYtf>RoV$uH>Mcx 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

Ze;os+cTGmWQ1(k8)BG~Yku(y=3B!U_;|%=1 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 **Definition**: A random transaction is a call to a random method in one of the target contracts. Any input arguments +> to the method are fuzzed values. + +The second possibility is more nuanced. To understand how to mutate an existing call sequence from the corpus, we need +to first discuss the idea of coverage and what a corpus is. + +### Coverage and the corpus + +Tracking coverage is one of the most powerful features of `medusa`. + +> **Definition**: Coverage is a measure of what parts of the code have been executed by the fuzzer + +Coverage is tracked in a rather simple fashion. For each target contract, we maintain a byte array where the length of the +byte array is equal to the length of that contract's bytecode. If a certain transaction caused us to execute an opcode +that we had not executed before, we increased coverage of that contract. + +![Coverage Tracking Diagram](../static/coverage.png) + +As shown in the figure above, the `CALL` opcode was just executed causing the coverage array's value to be updated at that +index. The next natural question is, how do we harness this information to improve the fuzzer? + +This is where the idea of a **corpus** comes in. + +> **Definition**: The corpus is a structure that holds "interesting" or "coverage-increasing" call sequences. + +Thus, when `medusa` runs, if it finds a call sequence that increased its coverage of the system, it will add it to the corpus. +These call sequences are invaluable to `medusa` because they allowed it to explore a larger portion of the system. This is +what makes `medusa` a **coverage-guided fuzzer**. + +> **Definition**: A coverage-guided fuzzer is one that aims to maximize its coverage of the system. + +Tracking coverage and storing coverage-increasing sequences in the corpus also allows `medusa` to re-use these sequences. +This takes us back to the second possibility when generating call sequences: mutating an existing sequence from the corpus. + +The reason we re-use call sequences is that we know that the call sequence in question improved our coverage. So, we +might as well re-use it, **mutate** it, and then execute that mutated call sequence in hopes of further increasing our coverage. +There are a variety of mutational strategies that `medusa` employs. For example, `medusa` can take a call sequence from the corpus and append a new random +transaction at the end of it. This is called **mutational fuzzing**. + +> **Definition**: Mutational fuzzing is the practice of taking existing data samples and generating new variants of them +> (mutants). + +Now that we know what a call sequence is, how to generate them, and how to track coverage, we can finally discuss how +these call sequences are executed. + +### Executing the call sequence + +Call sequence execution happens in an _iterative_ fashion. Here is some pseudocode on how it happens: + +``` +# Generate a new call sequence or mutate one from the corpus +sequence = generator.NewCallSequence() + +# Iteratively execute each call in the call sequence +for i < len(sequence) { + # Retrieve the i-th element in the sequence + tx = sequence[i] + + # Run the transaction on the blockchain and retrieve the result + result = blockchain.executeTransaction(tx) + + # Update coverage + increasedCoverage = coverageTracker.updateCoverage() + + # If coverage increased, add sequence[:i+1] to the corpus + if increasedCoveraged { + corpus.addCallSequence(tx[:i+1]) + } + + # Check for invariant failures + encounteredFailure = tester.checkForInvariantFailures(result) + + # Let user know we had a failing test case + if encounteredFailure { + reportFailedTestCase() + } +} +``` + +The one portion of the above pseudocode that we did not discuss is checking for invariant failures. We will discuss +the different types of invariants and what an invariant failure means in the [next chapter](./invariants.md). + +## Resetting the blockchain + +The final step in the fuzzing lifecycle is resetting the blockchain. Resetting the blockchain is as simple as reverting +to the "initial deployment state" of the blockchain. Once we reset back to the "initial deployment state", we can now generate and execute +another call sequence! diff --git a/docs/src/testing/invariants.md b/docs/src/testing/invariants.md new file mode 100644 index 00000000..4994c81b --- /dev/null +++ b/docs/src/testing/invariants.md @@ -0,0 +1,69 @@ +# Types of Invariants + +As discussed in the [testing overview](./overview.md) chapter, invariants describe the "truths" of your system. These +are unchanging properties that arise from the design of a codebase. + +> **Note**: We will interchange the use of the word property and invariant often. For all intents and purposes, they +> mean the same thing. + +Defining and testing your invariants is critical to assessing the **expected system behavior**. + +We like to break down invariants into two general categories: function-level invariants and system-level invariants. +Note that there are other ways of defining and scoping invariants, but this distinction is generally sufficient to +start fuzz testing even the most complex systems. + +## Function-level invariants + +A function-level invariant can be defined as follows: + +> **Definition**: A function-level invariant is a property that arises from the execution of a specific function. + +Let's take the following function from a smart contract: + +```solidity +function deposit() public payable { + // Make sure that the total deposited amount does not exceed the limit + uint256 amount = msg.value; + require(totalDeposited + amount <= MAX_DEPOSIT_AMOUNT); + + // Update the user balance and total deposited + balances[msg.sender] += amount; + totalDeposited += amount; + + emit Deposit(msg.sender, amount, totalDeposited); +} +``` + +The `deposit` function has the following function-level invariants: + +1. The ETH balance of `msg.sender` must decrease by `amount`. +2. The ETH of `address(this)` must increase by `amount`. +3. `balances[msg.sender]` should increase by `amount`. +4. The `totalDeposited` value should increase by `amount`. + +Note that there other properties that can also be tested for but the above should highlight what a function-level +invariant is. In general, function-level invariants can be identified by assessing what must be true _before_ the execution +of a function and what must be true _after_ the execution of that same function. In the next chapter, we will write a +fuzz test to test the `deposit` function and how to use medusa to run that test. + +Let's now look at system-level invariants. + +## System-level invariants + +A system-level invariant can be defined as follows: + +> **Definition**: A system-level invariant is a property that holds true across the _entire_ execution of a system + +Thus, a system-level invariant is a lot more generalized than a function-level invariant. Here are two common examples +of a function-level invariant: + +1. The `xy=k` constant product formula should always hold for Uniswap pools +2. No user's balance should ever exceed the total supply for an ERC20 token. + +In the `deposit` function above, we also see the presence of a system-level invariant: + +**The `totalDeposited` amount should always be less than or equal to the `MAX_DEPOSIT_AMOUNT`**. + +Since the `totalDeposited` value can be affected by the presence of other functions in the system +(e.g. `withdraw` or `stake`), it is best tested at the system level instead of the function level. We will look at how +to write system-level invariants in the [Writing System-Level Invariants](./writing-system-level-invariants.md) chapter. diff --git a/docs/src/testing/overview.md b/docs/src/testing/overview.md new file mode 100644 index 00000000..0ee8181c --- /dev/null +++ b/docs/src/testing/overview.md @@ -0,0 +1,26 @@ +# Testing Overview + +This chapter discusses the overarching goal of smart contract fuzzing. + +Traditional fuzz testing (e.g. with [`AFL`](https://lcamtuf.coredump.cx/afl/)) aims to generally explore a binary by providing +random inputs in an effort to identify new system states or crash the program (please note that this is a pretty crude generalization). +This model, however, does not translate to the smart contract ecosystem since you cannot cause a smart contract to "crash". +A transaction that reverts, for example, is not equivalent to a binary crashing or panicking. + +Thus, with smart contracts, we have to change the fuzzing paradigm. When you hear of "fuzzing smart contracts", you are +not trying to crash the program but, instead, you are trying to validate the **invariants** of the program. + +> **Definition**: An invariant is a property that remains unchanged after one or more operations are applied to it. + +More generally, an invariant is a "truth" about some system. For smart contracts, this can take many faces. + +1. **Mathematical invariants**: `a + b = b + a`. The commutative property is an invariant and any Solidity math library + should uphold this property. +2. **ERC20 tokens**: The sum of all user balances should never exceed the total supply of the token. +3. **Automated market maker (e.g. Uniswap)**: `xy = k`. The constant-product formula is an invariant that maintains the + economic guarantees of AMMs such as Uniswap. + +> **Definition**: Smart contract fuzzing uses random sequences of transactions to test the invariants of the smart contract system. + +Before we explore how to identify, write, and test invariants, it is beneficial to understand how smart contract fuzzing +works under-the-hood. diff --git a/docs/src/testing/tips.md b/docs/src/testing/tips.md new file mode 100644 index 00000000..a2de2928 --- /dev/null +++ b/docs/src/testing/tips.md @@ -0,0 +1,32 @@ +## Tips for Testing with Medusa + +### General + +- **Use multiple testing modes:** Medusa supports property testing, assertion testing, and optimization testing. Use a combination of modes to thoroughly test your contracts. +- **Write clear and concise tests:** Your tests should be easy to read and understand. Avoid complex logic or unnecessary code. +- **Test edge cases:** Consider testing extreme values and unusual inputs to ensure your contracts handle them correctly. +- **Use a variety of test inputs:** Generate a diverse set of test inputs to cover a wide range of scenarios. +- **Monitor gas consumption:** Medusa can track gas consumption during testing. Use this information to identify areas where your contracts can be optimized. + +### Property Testing + +- **Choose meaningful properties:** The properties you test should be important invariants of your contract. + +### Assertion Testing + +- **Use assertions judiciously:** Assertions can be useful for catching errors, but they can also slow down testing. Use them only when necessary. +- **Test for both valid and invalid inputs:** Ensure your assertions check for both valid and invalid inputs to thoroughly test your contract's behavior. +- **Use pre-conditions and post-conditions to verify the state of the contract before and after a function call.:** Pre-conditions and post-conditions are assertions that can be used to verify the state of the contract before and after a function call. This can help to ensure that the function is called with the correct inputs, that it produces the expected outputs, and that the state of the contract is valid. + +### Optimization Testing + +- **Choose a meaningful optimization goal:** The goal of your optimization test should be to maximize a specific metric, such as the return value of a function. +- **Use a variety of optimization techniques:** Medusa supports multiple optimization techniques, such as genetic algorithms and simulated annealing. Consider using different techniques to find the best solution. + +### Additional Tips + +- **Use a configuration file:** A configuration file allows you to customize Medusa's behavior and specify additional testing parameters. +- **Use corpus and coverage information to improve the effectiveness of your fuzzing campaigns:** Corpus and coverage information can be used to improve the effectiveness of your fuzzing campaigns by providing feedback on the quality of the test inputs. +- **Run Medusa in parallel:** Medusa can run tests in parallel to speed up the testing process. +- **Review the test results carefully:** Medusa provides detailed test results. Take the time to review them carefully and identify any potential issues. +- **Use Medusa as part of your development process:** Integrate Medusa into your development workflow to regularly test your contracts and identify potential bugs early on. diff --git a/docs/src/testing/writing-function-level-invariants.md b/docs/src/testing/writing-function-level-invariants.md new file mode 100644 index 00000000..ebe4e856 --- /dev/null +++ b/docs/src/testing/writing-function-level-invariants.md @@ -0,0 +1,145 @@ +## Writing Function-Level Invariants + +This chapter will walk you through writing function-level fuzz tests for the `deposit` function that we saw in the [previous chapter](./invariants.md#function-level-invariants). + +Before we write the fuzz tests, let's look into how we would write a unit test for the `deposit` function: + +```solidity +function testDeposit() public { + // The amount of tokens to deposit + uint256 amount = 10 ether; + + // Retrieve balance of user before deposit + preBalance = depositContract.balances(address(this)); + + // Call the deposit contract (let's assume this contract has 10 ether) + depositContract.deposit{value: amount}(); + + // Assert post-conditions + assert(depositContract.balances(msg.sender) == preBalance + amount); + // Add other assertions here +} +``` + +What we will notice about the test above is that it _fixes_ the value that is being sent. It is unable to test how the +`deposit` function behaves across a variety of input spaces. Thus, a function-level fuzz test can be thought of as a +"unit test on steroids". Instead of fixing the `amount`, we let the fuzzer control the `amount` value to any number between +`[0, type(uint256).max]` and see how the system behaves to that. + +> **Note**: One of the core differences between a traditional unit test versus a fuzz test is that a fuzz test accepts input arguments that the fuzzer can control. + +### Writing a Fuzz Test for the `deposit` Function + +Here is what a fuzz test for the `deposit` function would look like: + +```solidity +function testDeposit(uint256 _amount) public { + // Let's bound the input to be _at most_ the ETH balance of this contract + // The amount value will now in between [0, address(this).balance] + uint256 amount = clampLte(_amount, address(this).balance); + + // Retrieve balance of user before deposit + uint256 preBalance = depositContract.balances(address(this)); + + // Call the deposit contract with a variable amount + depositContract.deposit{value: _amount}(); + + // Assert post-conditions + assert(depositContract.balances(address(this)) == preBalance + amount); + // Add other assertions here +} +``` + +Notice that we bounded the `_amount` variable to be less than or equal to the test contract's ETH balance. +This type of bounding is very common when writing fuzz tests. Bounding allows you to only test values that are reasonable. +If `address(this)` doesn't have enough ETH, it does not make sense to try and call the `deposit` function. Additionally, +although we only tested one of the function-level invariants from the [previous chapter](./invariants.md), writing the remaining +would follow a similar pattern as the one written above. + +## Running a function-level test with medusa + +Let's now run the above example with medusa. Here is the test code: + +```solidity +contract DepositContract { + // @notice MAX_DEPOSIT_AMOUNT is the maximum amount that can be deposited into this contract + uint256 public constant MAX_DEPOSIT_AMOUNT = 1_000_000e18; + + // @notice balances holds user balances + mapping(address => uint256) public balances; + + // @notice totalDeposited represents the current deposited amount across all users + uint256 public totalDeposited; + + // @notice Deposit event is emitted after a deposit occurs + event Deposit(address depositor, uint256 amount, uint256 totalDeposited); + + // @notice deposit allows user to deposit into the system + function deposit() public payable { + // Make sure that the total deposited amount does not exceed the limit + uint256 amount = msg.value; + require(totalDeposited + amount <= MAX_DEPOSIT_AMOUNT); + + // Update the user balance and total deposited + balances[msg.sender] += amount; + totalDeposited += amount; + + emit Deposit(msg.sender, amount, totalDeposited); + } +} + +contract TestDepositContract { + + // @notice depositContract is an instance of DepositContract + DepositContract depositContract; + + constructor() payable { + // Deploy the deposit contract + depositContract = new DepositContract(); + } + + // @notice testDeposit tests the DepositContract.deposit function + function testDeposit(uint256 _amount) public { + // Let's bound the input to be _at most_ the ETH balance of this contract + // The amount value will now in between [0, address(this).balance] + uint256 amount = clampLte(_amount, address(this).balance); + + // Retrieve balance of user before deposit + uint256 preBalance = depositContract.balances(address(this)); + + // Call the deposit contract with a variable amount + depositContract.deposit{value: _amount}(); + + // Assert post-conditions + assert(depositContract.balances(address(this)) == preBalance + amount); + // Add other assertions here + } + + // @notice clampLte returns a value between [a, b] + function clampLte(uint256 a, uint256 b) internal returns (uint256) { + if (!(a <= b)) { + uint256 value = a % (b + 1); + return value; + } + return a; + } + +} +``` + +To run this test contract, download the project configuration file [here](../static/function_level_testing_medusa.json), +rename it to `medusa.json`, and run: + +``` +medusa fuzz --config medusa.json +``` + +The following changes were made to the default project configuration file to allow this test to run: + +- `fuzzing.targetContracts`: The `fuzzing.targetContracts` value was updated to `["TestDepositContract"]`. +- `fuzzing.targetContractsBalances`: The `fuzzing.targetContractsBalances` was updated to `["0xfffffffffffffffffffffffffffffff"]` + to allow the `TestDepositContract` contract to have an ETH balance allowing the fuzzer to correctly deposit funds into the + `DepositContract`. +- `fuzzing.testLimit`: The `fuzzing.testLimit` was set to `1_000` to shorten the duration of the fuzzing campign. +- `fuzzing.callSequenceLength`: The `fuzzing.callSequenceLength` was set to `1` so that the `TestDepositContract` can be + reset with its full ETH balance after each transaction. diff --git a/docs/src/testing/writing-system-level-invariants.md b/docs/src/testing/writing-system-level-invariants.md new file mode 100644 index 00000000..5cf2b0e1 --- /dev/null +++ b/docs/src/testing/writing-system-level-invariants.md @@ -0,0 +1,3 @@ +## Writing System-Level Invariants with Medusa + +WIP diff --git a/docs/src/testing/writing-tests.md b/docs/src/testing/writing-tests.md new file mode 100644 index 00000000..86da3ec6 --- /dev/null +++ b/docs/src/testing/writing-tests.md @@ -0,0 +1,189 @@ +# Testing with `medusa` + +`medusa`, like Echidna, supports the following testing modes: + +1. [Property Mode](https://secure-contracts.com/program-analysis/echidna/introduction/how-to-test-a-property.html) +2. [Assertion Mode](https://secure-contracts.com/program-analysis/echidna/basic/assertion-checking.html) +3. [Optimization Mode](https://secure-contracts.com/program-analysis/echidna/advanced/optimization_mode.html) + +For more advanced information and documentation on how the various modes work and their pros/cons, check out [secure-contracts.com](https://secure-contracts.com/program-analysis/echidna/index.html) + +## Writing property tests + +Property tests are represented as functions within a Solidity contract whose names are prefixed with a prefix specified by the `testPrefixes` configuration option (`fuzz_` is the default test prefix). Additionally, they must take no arguments and return a `bool` indicating if the test succeeded. + +```solidity +contract TestXY { + uint x; + uint y; + + function setX(uint value) public { + x = value + 3; + } + + function setY(uint value) public { + y = value + 9; + } + + function fuzz_never_specific_values() public returns (bool) { + // ASSERTION: x should never be 10 at the same time y is 80 + return !(x == 10 && y == 80); + } +} +``` + +`medusa` deploys your contract containing property tests and generates a sequence of calls to execute against all publicly accessible methods. After each function call, it calls upon your property tests to ensure they return a `true` (success) status. + +### Testing in property-mode + +To begin a fuzzing campaign in property-mode, you can run `medusa fuzz` or `medusa fuzz --config [config_path]`. + +> **Note**: Learn more about running `medusa` with its CLI [here](./Command-Line-Interface.md). + +Invoking this fuzzing campaign, `medusa` will: + +- Compile the given targets +- Start the configured number of worker threads, each with their own local Ethereum test chain. +- Deploy all contracts to each worker's test chain. +- Begin to generate and send call sequences to update contract state. +- Check property tests all succeed after each call executed. + +Upon discovery of a failed property test, `medusa` will halt, reporting the call sequence used to violate any property test(s): + +``` +[FAILED] Property Test: TestXY.fuzz_never_specific_values() +Test "TestXY.fuzz_never_specific_values()" failed after the following call sequence: +1) TestXY.setY([71]) (gas=4712388, gasprice=1, value=0, sender=0x2222222222222222222222222222222222222222) +2) TestXY.setX([7]) (gas=4712388, gasprice=1, value=0, sender=0x3333333333333333333333333333333333333333) +``` + +## Writing assertion tests + +Although both property-mode and assertion-mode try to validate / invalidate invariants of the system, they do so in different ways. In property-mode, `medusa` will look for functions with a specific test prefix (e.g. `fuzz_`) and test those. In assertion-mode, `medusa` will test to see if a given call sequence can cause the Ethereum Virtual Machine (EVM) to "panic". The EVM has a variety of panic codes for different scenarios. For example, there is a unique panic code when an `assert(x)` statement returns `false` or when a division by zero is encountered. In assertion mode, which panics should or should not be treated as "failing test cases" can be toggled by updating the [Project Configuration](./Project-Configuration.md#fuzzing-configuration). By default, only `FailOnAssertion` is enabled. Check out the [Example Project Configuration File](https://github.com/crytic/medusa/wiki/Example-Project-Configuration-File) for a visualization of the various panic codes that can be enabled. An explanation of the various panic codes can be found in the [Solidity documentation](https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require). + +Please note that the behavior of assertion mode is different between `medusa` and Echidna. Echidna will only test for `assert(x)` statements while `medusa` provides additional flexibility. + +```solidity +contract TestContract { + uint x; + uint y; + + function setX(uint value) public { + x = value; + + // ASSERTION: x should be an even number + assert(x % 2 == 0); + } +} +``` + +During a call sequence, if `setX` is called with a `value` that breaks the assertion (e.g. `value = 3`), `medusa` will treat this as a failing property and report it back to the user. + +### Testing in assertion-mode + +To begin a fuzzing campaign in assertion-mode, you can run `medusa fuzz --assertion-mode` or `medusa fuzz --config [config_path] --assertion-mode`. + +> **Note**: Learn more about running `medusa` with its CLI [here](./Command-Line-Interface.md). + +Invoking this fuzzing campaign, `medusa` will: + +- Compile the given targets +- Start the configured number of worker threads, each with their own local Ethereum test chain. +- Deploy all contracts to each worker's test chain. +- Begin to generate and send call sequences to update contract state. +- Check to see if there any failing assertions after each call executed. + +Upon discovery of a failed assertion, `medusa` will halt, reporting the call sequence used to violate any assertions: + +``` +Fuzzer stopped, test results follow below ... +[FAILED] Assertion Test: TestContract.setX(uint256) +Test for method "TestContract.setX(uint256)" failed after the following call sequence resulted in an assertion: +1) TestContract.setX([102552480437485684723695021980667056378352338398148431990087576385563741034353]) (block=2, time=4, gas=12500000, gasprice=1, value=0, sender=0x1111111111111111111111111111111111111111) +``` + +## Writing optimization tests + +Optimization mode's goal is not to validate/invalidate properties but instead to maximize the return value of a function. Similar to property mode, these functions must be prefixed with a prefix specified by the `testPrefixes` configuration option (`optimize_` is the default test prefix). Additionally, they must take no arguments and return an `int256`. A good use case for optimization mode is to try to quantify the impact of a bug (e.g. a rounding error). + +```solidity +contract TestContract { + int256 input; + + function set(int256 _input) public { + input = _input; + } + + function optimize_opt_linear() public view returns (int256) { + if (input > -4242) return -input; + else return 0; + } +} +``` + +`medusa` deploys your contract containing optimization tests and generates a sequence of calls to execute against all publicly accessible methods. After each function call, it calls upon your otpimization tests to identify whether the return value of those tests are greater than the currently stored values. + +### Testing in optimization-mode + +To begin a fuzzing campaign in optimization-mode, you can run `medusa fuzz --optimization-mode` or `medusa fuzz --config [config_path] --optimization-mode`. + +> **Note**: Learn more about running `medusa` with its CLI [here](./Command-Line-Interface.md). + +Invoking this fuzzing campaign, `medusa` will: + +- Compile the given targets +- Start the configured number of worker threads, each with their own local Ethereum test chain. +- Deploy all contracts to each worker's test chain. +- Begin to generate and send call sequences to update contract state. +- Check to see if the return value of the optimization test is greater than the cached value. + - If the value is greater, update the cached value. + +Once the test limit or timeout for the fuzzing campaign has been reached, `medusa` will halt and report the call sequence that maximized the return value of the function: + +``` +Fuzzer stopped, test results follow below ... +[PASSED] Optimization Test: TestContract.optimize_opt_linear() +Optimization test "TestContract.optimize_opt_linear()" resulted in the maximum value: 4241 with the following sequence: +1) TestContract.set(-4241) (block=2, time=3, gas=12500000, gasprice=1, value=0, sender=0x0000000000000000000000000000000000010000) +``` + +## Testing with multiple modes + +Note that we can run `medusa` with one, many, or no modes enabled. Running `medusa fuzz --assertion-mode --optimization-mode` will run all three modes at the same time, since property-mode is enabled by default. If a project configuration file is used, any combination of the three modes can be toggled. In fact, all three modes can be disabled and `medusa` will still run. Please review the [Project Configuration](./Project-Configuration.md) wiki page and the [Project Configuration Example](/Example-Project-Configuration-File.md) for more information. + +```solidity +contract TestContract { + int256 input; + + function set(int256 _input) public { + input = _input; + } + + function failing_assert_method(uint value) public { + // ASSERTION: We always fail when you call this function. + assert(false); + } + + function fuzz_failing_property() public view returns (bool) { + // ASSERTION: fail immediately. + return false; + } + + function optimize_opt_linear() public view returns (int256) { + if (input > -4242) return -input; + else return 0; + } +} +``` + +Invoking a fuzzing campaign with `medusa fuzz --assertion-mode --optimization-mode` (note all three modes are enabled), `medusa` will: + +- Compile the given targets +- Start the configured number of worker threads, each with their own local Ethereum test chain. +- Deploy all contracts to each worker's test chain. +- Begin to generate and send call sequences to update contract state. +- Check to see: + - If property tests all succeed after each call executed. + - If a panic (which was enabled in the project configuration) has been triggered after each call. + - Whether the return value of the optimization test is greater than the cached value. + - Update the cached value if it is greater. diff --git a/docs/theme/favicon.png b/docs/theme/favicon.png new file mode 100755 index 0000000000000000000000000000000000000000..a72d998fb92c1a12c25604c6507712ee3657ce2c GIT binary patch literal 121411 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 z\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 L_@hK0AN&6RV$zYK literal 0 HcmV?d00001 diff --git a/docs/theme/favicon.svg b/docs/theme/favicon.svg new file mode 100755 index 00000000..78259cf9 --- /dev/null +++ b/docs/theme/favicon.svg @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/theme/highlight.js b/docs/theme/highlight.js new file mode 100644 index 00000000..8d926897 --- /dev/null +++ b/docs/theme/highlight.js @@ -0,0 +1,6 @@ +/* + Highlight.js 10.1.1 (93fd0d73) + License: BSD-3-Clause + Copyright (c) 2006-2020, Ivan Sagalaev +*/ +var hljs=function(){"use strict";function e(n){Object.freeze(n);var t="function"==typeof n;return Object.getOwnPropertyNames(n).forEach((function(r){!Object.hasOwnProperty.call(n,r)||null===n[r]||"object"!=typeof n[r]&&"function"!=typeof n[r]||t&&("caller"===r||"callee"===r||"arguments"===r)||Object.isFrozen(n[r])||e(n[r])})),n}class n{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data}ignoreMatch(){this.ignore=!0}}function t(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function r(e,...n){var t={};for(const n in e)t[n]=e[n];return n.forEach((function(e){for(const n in e)t[n]=e[n]})),t}function a(e){return e.nodeName.toLowerCase()}var i=Object.freeze({__proto__:null,escapeHTML:t,inherit:r,nodeStream:function(e){var n=[];return function e(t,r){for(var i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?r+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:r,node:i}),r=e(i,r),a(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:r,node:i}));return r}(e,0),n},mergeStreams:function(e,n,r){var i=0,s="",o=[];function l(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function u(e){s+=""}function d(e){("start"===e.event?c:u)(e.node)}for(;e.length||n.length;){var g=l();if(s+=t(r.substring(i,g[0].offset)),i=g[0].offset,g===e){o.reverse().forEach(u);do{d(g.splice(0,1)[0]),g=l()}while(g===e&&g.length&&g[0].offset===i);o.reverse().forEach(c)}else"start"===g[0].event?o.push(g[0].node):o.pop(),d(g.splice(0,1)[0])}return s+t(r.substr(i))}});const s="",o=e=>!!e.kind;class l{constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=t(e)}openNode(e){if(!o(e))return;let n=e.kind;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){o(e)&&(this.buffer+=s)}value(){return this.buffer}span(e){this.buffer+=``}}class c{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach(n=>this._walk(e,n)),e.closeNode(n)),e}static _collapse(e){"string"!=typeof e&&e.children&&(e.children.every(e=>"string"==typeof e)?e.children=[e.children.join("")]:e.children.forEach(e=>{c._collapse(e)}))}}class u extends c{constructor(e){super(),this.options=e}addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,n){const t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new l(this,this.options).value()}finalize(){return!0}}function d(e){return e?"string"==typeof e?e:e.source:null}const g="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",h={begin:"\\\\[\\s\\S]",relevance:0},f={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[h]},p={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[h]},b={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},m=function(e,n,t={}){var a=r({className:"comment",begin:e,end:n,contains:[]},t);return a.contains.push(b),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),a},v=m("//","$"),x=m("/\\*","\\*/"),E=m("#","$");var _=Object.freeze({__proto__:null,IDENT_RE:"[a-zA-Z]\\w*",UNDERSCORE_IDENT_RE:"[a-zA-Z_]\\w*",NUMBER_RE:"\\b\\d+(\\.\\d+)?",C_NUMBER_RE:g,BINARY_NUMBER_RE:"\\b(0b[01]+)",RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{const n=/^#![ ]*\//;return e.binary&&(e.begin=function(...e){return e.map(e=>d(e)).join("")}(n,/.*\b/,e.binary,/\b.*/)),r({className:"meta",begin:n,end:/$/,relevance:0,"on:begin":(e,n)=>{0!==e.index&&n.ignoreMatch()}},e)},BACKSLASH_ESCAPE:h,APOS_STRING_MODE:f,QUOTE_STRING_MODE:p,PHRASAL_WORDS_MODE:b,COMMENT:m,C_LINE_COMMENT_MODE:v,C_BLOCK_COMMENT_MODE:x,HASH_COMMENT_MODE:E,NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?",relevance:0},C_NUMBER_MODE:{className:"number",begin:g,relevance:0},BINARY_NUMBER_MODE:{className:"number",begin:"\\b(0b[01]+)",relevance:0},CSS_NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[h,{begin:/\[/,end:/\]/,relevance:0,contains:[h]}]}]},TITLE_MODE:{className:"title",begin:"[a-zA-Z]\\w*",relevance:0},UNDERSCORE_TITLE_MODE:{className:"title",begin:"[a-zA-Z_]\\w*",relevance:0},METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,n)=>{n.data._beginMatch=e[1]},"on:end":(e,n)=>{n.data._beginMatch!==e[1]&&n.ignoreMatch()}})}}),N="of and for in not or if then".split(" ");function w(e,n){return n?+n:function(e){return N.includes(e.toLowerCase())}(e)?0:1}const R=t,y=r,{nodeStream:k,mergeStreams:O}=i,M=Symbol("nomatch");return function(t){var a=[],i={},s={},o=[],l=!0,c=/(^(<[^>]+>|\t|)+|\n)/gm,g="Could not find the language '{}', did you forget to load/include a language module?";const h={disableAutodetect:!0,name:"Plain text",contains:[]};var f={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:u};function p(e){return f.noHighlightRe.test(e)}function b(e,n,t,r){var a={code:n,language:e};S("before:highlight",a);var i=a.result?a.result:m(a.language,a.code,t,r);return i.code=a.code,S("after:highlight",i),i}function m(e,t,a,s){var o=t;function c(e,n){var t=E.case_insensitive?n[0].toLowerCase():n[0];return Object.prototype.hasOwnProperty.call(e.keywords,t)&&e.keywords[t]}function u(){null!=y.subLanguage?function(){if(""!==A){var e=null;if("string"==typeof y.subLanguage){if(!i[y.subLanguage])return void O.addText(A);e=m(y.subLanguage,A,!0,k[y.subLanguage]),k[y.subLanguage]=e.top}else e=v(A,y.subLanguage.length?y.subLanguage:null);y.relevance>0&&(I+=e.relevance),O.addSublanguage(e.emitter,e.language)}}():function(){if(!y.keywords)return void O.addText(A);let e=0;y.keywordPatternRe.lastIndex=0;let n=y.keywordPatternRe.exec(A),t="";for(;n;){t+=A.substring(e,n.index);const r=c(y,n);if(r){const[e,a]=r;O.addText(t),t="",I+=a,O.addKeyword(n[0],e)}else t+=n[0];e=y.keywordPatternRe.lastIndex,n=y.keywordPatternRe.exec(A)}t+=A.substr(e),O.addText(t)}(),A=""}function h(e){return e.className&&O.openNode(e.className),y=Object.create(e,{parent:{value:y}})}function p(e){return 0===y.matcher.regexIndex?(A+=e[0],1):(L=!0,0)}var b={};function x(t,r){var i=r&&r[0];if(A+=t,null==i)return u(),0;if("begin"===b.type&&"end"===r.type&&b.index===r.index&&""===i){if(A+=o.slice(r.index,r.index+1),!l){const n=Error("0 width match regex");throw n.languageName=e,n.badRule=b.rule,n}return 1}if(b=r,"begin"===r.type)return function(e){var t=e[0],r=e.rule;const a=new n(r),i=[r.__beforeBegin,r["on:begin"]];for(const n of i)if(n&&(n(e,a),a.ignore))return p(t);return r&&r.endSameAsBegin&&(r.endRe=RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),r.skip?A+=t:(r.excludeBegin&&(A+=t),u(),r.returnBegin||r.excludeBegin||(A=t)),h(r),r.returnBegin?0:t.length}(r);if("illegal"===r.type&&!a){const e=Error('Illegal lexeme "'+i+'" for mode "'+(y.className||"")+'"');throw e.mode=y,e}if("end"===r.type){var s=function(e){var t=e[0],r=o.substr(e.index),a=function e(t,r,a){let i=function(e,n){var t=e&&e.exec(n);return t&&0===t.index}(t.endRe,a);if(i){if(t["on:end"]){const e=new n(t);t["on:end"](r,e),e.ignore&&(i=!1)}if(i){for(;t.endsParent&&t.parent;)t=t.parent;return t}}if(t.endsWithParent)return e(t.parent,r,a)}(y,e,r);if(!a)return M;var i=y;i.skip?A+=t:(i.returnEnd||i.excludeEnd||(A+=t),u(),i.excludeEnd&&(A=t));do{y.className&&O.closeNode(),y.skip||y.subLanguage||(I+=y.relevance),y=y.parent}while(y!==a.parent);return a.starts&&(a.endSameAsBegin&&(a.starts.endRe=a.endRe),h(a.starts)),i.returnEnd?0:t.length}(r);if(s!==M)return s}if("illegal"===r.type&&""===i)return 1;if(B>1e5&&B>3*r.index)throw Error("potential infinite loop, way more iterations than matches");return A+=i,i.length}var E=T(e);if(!E)throw console.error(g.replace("{}",e)),Error('Unknown language: "'+e+'"');var _=function(e){function n(n,t){return RegExp(d(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))}class t{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=function(e){return RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map(e=>e[1]);this.matcherRe=n(function(e,n="|"){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i0&&(a+=n),a+="(";o.length>0;){var l=t.exec(o);if(null==l){a+=o;break}a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length),"\\"===l[0][0]&&l[1]?a+="\\"+(+l[1]+s):(a+=l[0],"("===l[0]&&r++)}a+=")"}return a}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const n=this.matcherRe.exec(e);if(!n)return null;const t=n.findIndex((e,n)=>n>0&&void 0!==e),r=this.matchIndexes[t];return n.splice(0,t),Object.assign(n,r)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const n=new t;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;const t=n.exec(e);return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&(this.regexIndex=0)),t}}function i(e,n){const t=e.input[e.index-1],r=e.input[e.index+e[0].length];"."!==t&&"."!==r||n.ignoreMatch()}if(e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return function t(s,o){const l=s;if(s.compiled)return l;s.compiled=!0,s.__beforeBegin=null,s.keywords=s.keywords||s.beginKeywords;let c=null;if("object"==typeof s.keywords&&(c=s.keywords.$pattern,delete s.keywords.$pattern),s.keywords&&(s.keywords=function(e,n){var t={};return"string"==typeof e?r("keyword",e):Object.keys(e).forEach((function(n){r(n,e[n])})),t;function r(e,r){n&&(r=r.toLowerCase()),r.split(" ").forEach((function(n){var r=n.split("|");t[r[0]]=[e,w(r[0],r[1])]}))}}(s.keywords,e.case_insensitive)),s.lexemes&&c)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return l.keywordPatternRe=n(s.lexemes||c||/\w+/,!0),o&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)",s.__beforeBegin=i),s.begin||(s.begin=/\B|\b/),l.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(l.endRe=n(s.end)),l.terminator_end=d(s.end)||"",s.endsWithParent&&o.terminator_end&&(l.terminator_end+=(s.end?"|":"")+o.terminator_end)),s.illegal&&(l.illegalRe=n(s.illegal)),void 0===s.relevance&&(s.relevance=1),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(e){return function(e){return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){return r(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(e)?r(e,{starts:e.starts?r(e.starts):null}):Object.isFrozen(e)?r(e):e}("self"===e?s:e)}))),s.contains.forEach((function(e){t(e,l)})),s.starts&&t(s.starts,o),l.matcher=function(e){const n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:"begin"})),e.terminator_end&&n.addRule(e.terminator_end,{type:"end"}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(l),l}(e)}(E),N="",y=s||_,k={},O=new f.__emitter(f);!function(){for(var e=[],n=y;n!==E;n=n.parent)n.className&&e.unshift(n.className);e.forEach(e=>O.openNode(e))}();var A="",I=0,S=0,B=0,L=!1;try{for(y.matcher.considerAll();;){B++,L?L=!1:(y.matcher.lastIndex=S,y.matcher.considerAll());const e=y.matcher.exec(o);if(!e)break;const n=x(o.substring(S,e.index),e);S=e.index+n}return x(o.substr(S)),O.closeAllNodes(),O.finalize(),N=O.toHTML(),{relevance:I,value:N,language:e,illegal:!1,emitter:O,top:y}}catch(n){if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:n.message,context:o.slice(S-100,S+100),mode:n.mode},sofar:N,relevance:0,value:R(o),emitter:O};if(l)return{illegal:!1,relevance:0,value:R(o),emitter:O,language:e,top:y,errorRaised:n};throw n}}function v(e,n){n=n||f.languages||Object.keys(i);var t=function(e){const n={relevance:0,emitter:new f.__emitter(f),value:R(e),illegal:!1,top:h};return n.emitter.addText(e),n}(e),r=t;return n.filter(T).filter(I).forEach((function(n){var a=m(n,e,!1);a.language=n,a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a)})),r.language&&(t.second_best=r),t}function x(e){return f.tabReplace||f.useBR?e.replace(c,e=>"\n"===e?f.useBR?"
":e:f.tabReplace?e.replace(/\t/g,f.tabReplace):e):e}function E(e){let n=null;const t=function(e){var n=e.className+" ";n+=e.parentNode?e.parentNode.className:"";const t=f.languageDetectRe.exec(n);if(t){var r=T(t[1]);return r||(console.warn(g.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),r?t[1]:"no-highlight"}return n.split(/\s+/).find(e=>p(e)||T(e))}(e);if(p(t))return;S("before:highlightBlock",{block:e,language:t}),f.useBR?(n=document.createElement("div")).innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n"):n=e;const r=n.textContent,a=t?b(t,r,!0):v(r),i=k(n);if(i.length){const e=document.createElement("div");e.innerHTML=a.value,a.value=O(i,k(e),r)}a.value=x(a.value),S("after:highlightBlock",{block:e,result:a}),e.innerHTML=a.value,e.className=function(e,n,t){var r=n?s[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),e.includes(r)||a.push(r),a.join(" ").trim()}(e.className,t,a.language),e.result={language:a.language,re:a.relevance,relavance:a.relevance},a.second_best&&(e.second_best={language:a.second_best.language,re:a.second_best.relevance,relavance:a.second_best.relevance})}const N=()=>{if(!N.called){N.called=!0;var e=document.querySelectorAll("pre code");a.forEach.call(e,E)}};function T(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]}function A(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach(e=>{s[e]=n})}function I(e){var n=T(e);return n&&!n.disableAutodetect}function S(e,n){var t=e;o.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(t,{highlight:b,highlightAuto:v,fixMarkup:x,highlightBlock:E,configure:function(e){f=y(f,e)},initHighlighting:N,initHighlightingOnLoad:function(){window.addEventListener("DOMContentLoaded",N,!1)},registerLanguage:function(e,n){var r=null;try{r=n(t)}catch(n){if(console.error("Language definition for '{}' could not be registered.".replace("{}",e)),!l)throw n;console.error(n),r=h}r.name||(r.name=e),i[e]=r,r.rawDefinition=n.bind(null,t),r.aliases&&A(r.aliases,{languageName:e})},listLanguages:function(){return Object.keys(i)},getLanguage:T,registerAliases:A,requireLanguage:function(e){var n=T(e);if(n)return n;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:I,inherit:y,addPlugin:function(e){o.push(e)}}),t.debugMode=function(){l=!1},t.safeMode=function(){l=!0},t.versionString="10.1.1";for(const n in _)"object"==typeof _[n]&&e(_[n]);return Object.assign(t,_),t}({})}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);hljs.registerLanguage("php",function(){"use strict";return function(e){var r={begin:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},t={className:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?[=]?/},{begin:/\?>/}]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:'b"',end:'"'},{begin:"b'",end:"'"},e.inherit(e.APOS_STRING_MODE,{illegal:null}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null})]},n={variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]},i={keyword:"__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ die echo exit include include_once print require require_once array abstract and as binary bool boolean break callable case catch class clone const continue declare default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval extends final finally float for foreach from global goto if implements instanceof insteadof int integer interface isset iterable list new object or private protected public real return string switch throw trait try unset use var void while xor yield",literal:"false null true",built_in:"Error|0 AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Throwable Traversable WeakReference Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass"};return{aliases:["php","php3","php4","php5","php6","php7"],case_insensitive:!0,keywords:i,contains:[e.HASH_COMMENT_MODE,e.COMMENT("//","$",{contains:[t]}),e.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0,keywords:"__halt_compiler"}),{className:"string",begin:/<<<['"]?\w+['"]?$/,end:/^\w+;?$/,contains:[e.BACKSLASH_ESCAPE,{className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]}]},t,{className:"keyword",begin:/\$this\b/},r,{begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:i,contains:["self",r,e.C_BLOCK_COMMENT_MODE,a,n]}]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",end:";",illegal:/[\.']/,contains:[e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",end:";",contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"=>"},a,n]}}}());hljs.registerLanguage("nginx",function(){"use strict";return function(e){var n={className:"variable",variants:[{begin:/\$\d+/},{begin:/\$\{/,end:/}/},{begin:"[\\$\\@]"+e.UNDERSCORE_IDENT_RE}]},a={endsWithParent:!0,keywords:{$pattern:"[a-z/_]+",literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},relevance:0,illegal:"=>",contains:[e.HASH_COMMENT_MODE,{className:"string",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:/"/,end:/"/},{begin:/'/,end:/'/}]},{begin:"([a-z]+):/",end:"\\s",endsWithParent:!0,excludeEnd:!0,contains:[n]},{className:"regexp",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:"\\s\\^",end:"\\s|{|;",returnEnd:!0},{begin:"~\\*?\\s+",end:"\\s|{|;",returnEnd:!0},{begin:"\\*(\\.[a-z\\-]+)+"},{begin:"([a-z\\-]+\\.)+\\*"}]},{className:"number",begin:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{className:"number",begin:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},n]};return{name:"Nginx config",aliases:["nginxconf"],contains:[e.HASH_COMMENT_MODE,{begin:e.UNDERSCORE_IDENT_RE+"\\s+{",returnBegin:!0,end:"{",contains:[{className:"section",begin:e.UNDERSCORE_IDENT_RE}],relevance:0},{begin:e.UNDERSCORE_IDENT_RE+"\\s",end:";|{",returnBegin:!0,contains:[{className:"attribute",begin:e.UNDERSCORE_IDENT_RE,starts:a}],relevance:0}],illegal:"[^\\s\\}]"}}}());hljs.registerLanguage("csharp",function(){"use strict";return function(e){var n={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},i=e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},t=e.inherit(s,{illegal:/\n/}),l={className:"subst",begin:"{",end:"}",keywords:n},r=e.inherit(l,{illegal:/\n/}),c={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},e.BACKSLASH_ESCAPE,r]},o={className:"string",begin:/\$@"/,end:'"',contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},l]},g=e.inherit(o,{illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},r]});l.contains=[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],r.contains=[g,c,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];var d={variants:[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},E={begin:"<",end:">",contains:[{beginKeywords:"in out"},i]},_=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",b={begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:"\x3c!--|--\x3e"},{begin:""}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},d,a,{beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},i,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",end:/[{;=]/,illegal:/[^\s:]/,contains:[i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"meta-string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+_+"\\s+)+"+e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{begin:e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,contains:[e.TITLE_MODE,E],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,contains:[d,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},b]}}}());hljs.registerLanguage("perl",function(){"use strict";return function(e){var n={$pattern:/[\w.]+/,keyword:"getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qq fileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmget sub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedir ioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when"},t={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:n},s={begin:"->{",end:"}"},r={variants:[{begin:/\$\d/},{begin:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{begin:/[\$%@][^\s\w{]/,relevance:0}]},i=[e.BACKSLASH_ESCAPE,t,r],a=[r,e.HASH_COMMENT_MODE,e.COMMENT("^\\=\\w","\\=cut",{endsWithParent:!0}),s,{className:"string",contains:i,variants:[{begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[",end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*\\<",end:"\\>",relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'",contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE]},{begin:"{\\w+}",contains:[],relevance:0},{begin:"-?\\w+\\s*\\=\\>",contains:[],relevance:0}]},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*",keywords:"split return print reverse grep",relevance:0,contains:[e.HASH_COMMENT_MODE,{className:"regexp",begin:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",relevance:10},{className:"regexp",begin:"(m|qr)?/",end:"/[a-z]*",contains:[e.BACKSLASH_ESCAPE],relevance:0}]},{className:"function",beginKeywords:"sub",end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$",subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}]}];return t.contains=a,s.contains=a,{name:"Perl",aliases:["pl","pm"],keywords:n,contains:a}}}());hljs.registerLanguage("swift",function(){"use strict";return function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},n=e.COMMENT("/\\*","\\*/",{contains:["self"]}),t={className:"subst",begin:/\\\(/,end:"\\)",keywords:i,contains:[]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:/"""/,end:/"""/},{begin:/"/,end:/"/}]},r={className:"number",begin:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return t.contains=[r],{name:"Swift",keywords:i,contains:[a,e.C_LINE_COMMENT_MODE,n,{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},r,{className:"function",beginKeywords:"func",end:"{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{begin://},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:i,contains:["self",r,a,e.C_BLOCK_COMMENT_MODE,{begin:":"}],illegal:/["']/}],illegal:/\[|%/},{className:"class",beginKeywords:"struct protocol class extension enum",keywords:i,end:"\\{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{className:"meta",begin:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)\\b"},{beginKeywords:"import",end:/$/,contains:[e.C_LINE_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("makefile",function(){"use strict";return function(e){var i={className:"variable",variants:[{begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)",contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin:"",relevance:10,contains:[a,i,t,s,{begin:"\\[",end:"\\]",contains:[{className:"meta",begin:"",contains:[a,s,i,t]}]}]},e.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",end:"\\]\\]>",relevance:10},n,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:")",end:">",keywords:{name:"style"},contains:[c],starts:{end:"",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:")",end:">",keywords:{name:"script"},contains:[c],starts:{end:"<\/script>",returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:"",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},c]}]}}}());hljs.registerLanguage("bash",function(){"use strict";return function(e){const s={};Object.assign(s,{className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{/,end:/\}/,contains:[{begin:/:-/,contains:[s]}]}]});const t={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},n={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,t]};t.contains.push(n);const a={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,s]},i=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b-?[a-z\._]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[i,e.SHEBANG(),c,a,e.HASH_COMMENT_MODE,n,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},s]}}}());hljs.registerLanguage("c-like",function(){"use strict";return function(e){function t(e){return"(?:"+e+")?"}var n="(decltype\\(auto\\)|"+t("[a-zA-Z_]\\w*::")+"[a-zA-Z_]\\w*"+t("<.*?>")+")",r={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},a={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",end:"'",illegal:"."},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},i={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(a,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},o={className:"title",begin:t("[a-zA-Z_]\\w*::")+e.IDENT_RE,relevance:0},c=t("[a-zA-Z_]\\w*::")+e.IDENT_RE+"\\s*\\(",l={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},d=[r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,i,a],_={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:l,contains:d.concat([{begin:/\(/,end:/\)/,keywords:l,contains:d.concat(["self"]),relevance:0}]),relevance:0},u={className:"function",begin:"("+n+"[\\*&\\s]+)+"+c,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:l,illegal:/[^\w\s\*&:<>]/,contains:[{begin:"decltype\\(auto\\)",keywords:l,relevance:0},{begin:c,returnBegin:!0,contains:[o],relevance:0},{className:"params",begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r,{begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r]}]},r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],keywords:l,disableAutodetect:!0,illegal:"",keywords:l,contains:["self",r]},{begin:e.IDENT_RE+"::",keywords:l},{className:"class",beginKeywords:"class struct",end:/[{;:]/,contains:[{begin://,contains:["self"]},e.TITLE_MODE]}]),exports:{preprocessor:s,strings:a,keywords:l}}}}());hljs.registerLanguage("coffeescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={keyword:e.concat(["then","unless","until","loop","by","when","and","or","is","isnt","not"]).filter((e=>n=>!e.includes(n))(["var","const","let","function","static"])).join(" "),literal:n.concat(["yes","no","on","off"]).join(" "),built_in:a.concat(["npm","print"]).join(" ")},i="[A-Za-z$_][0-9A-Za-z$_]*",s={className:"subst",begin:/#\{/,end:/}/,keywords:t},o=[r.BINARY_NUMBER_MODE,r.inherit(r.C_NUMBER_MODE,{starts:{end:"(\\s*/)?",relevance:0}}),{className:"string",variants:[{begin:/'''/,end:/'''/,contains:[r.BACKSLASH_ESCAPE]},{begin:/'/,end:/'/,contains:[r.BACKSLASH_ESCAPE]},{begin:/"""/,end:/"""/,contains:[r.BACKSLASH_ESCAPE,s]},{begin:/"/,end:/"/,contains:[r.BACKSLASH_ESCAPE,s]}]},{className:"regexp",variants:[{begin:"///",end:"///",contains:[s,r.HASH_COMMENT_MODE]},{begin:"//[gim]{0,3}(?=\\W)",relevance:0},{begin:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{begin:"@"+i},{subLanguage:"javascript",excludeBegin:!0,excludeEnd:!0,variants:[{begin:"```",end:"```"},{begin:"`",end:"`"}]}];s.contains=o;var c=r.inherit(r.TITLE_MODE,{begin:i}),l={className:"params",begin:"\\([^\\(]",returnBegin:!0,contains:[{begin:/\(/,end:/\)/,keywords:t,contains:["self"].concat(o)}]};return{name:"CoffeeScript",aliases:["coffee","cson","iced"],keywords:t,illegal:/\/\*/,contains:o.concat([r.COMMENT("###","###"),r.HASH_COMMENT_MODE,{className:"function",begin:"^\\s*"+i+"\\s*=\\s*(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[c,l]},{begin:/[:\(,=]\s*/,relevance:0,contains:[{className:"function",begin:"(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[l]}]},{className:"class",beginKeywords:"class",end:"$",illegal:/[:="\[\]]/,contains:[{beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[c]},c]},{begin:i+":",end:":",returnBegin:!0,returnEnd:!0,relevance:0}])}}}());hljs.registerLanguage("ruby",function(){"use strict";return function(e){var n="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",a={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},s={className:"doctag",begin:"@[A-Za-z]+"},i={begin:"#<",end:">"},r=[e.COMMENT("#","$",{contains:[s]}),e.COMMENT("^\\=begin","^\\=end",{contains:[s],relevance:10}),e.COMMENT("^__END__","\\n$")],c={className:"subst",begin:"#\\{",end:"}",keywords:a},t={className:"string",contains:[e.BACKSLASH_ESCAPE,c],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:"%[qQwWx]?\\(",end:"\\)"},{begin:"%[qQwWx]?\\[",end:"\\]"},{begin:"%[qQwWx]?{",end:"}"},{begin:"%[qQwWx]?<",end:">"},{begin:"%[qQwWx]?/",end:"/"},{begin:"%[qQwWx]?%",end:"%"},{begin:"%[qQwWx]?-",end:"-"},{begin:"%[qQwWx]?\\|",end:"\\|"},{begin:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{begin:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,returnBegin:!0,contains:[{begin:/<<[-~]?'?/},e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,contains:[e.BACKSLASH_ESCAPE,c]})]}]},b={className:"params",begin:"\\(",end:"\\)",endsParent:!0,keywords:a},d=[t,i,{className:"class",beginKeywords:"class module",end:"$|;",illegal:/=/,contains:[e.inherit(e.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{begin:"<\\s*",contains:[{begin:"("+e.IDENT_RE+"::)?"+e.IDENT_RE}]}].concat(r)},{className:"function",beginKeywords:"def",end:"$|;",contains:[e.inherit(e.TITLE_MODE,{begin:n}),b].concat(r)},{begin:e.IDENT_RE+"::"},{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(\\!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[t,{begin:n}],relevance:0},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{className:"params",begin:/\|/,end:/\|/,keywords:a},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[i,{className:"regexp",contains:[e.BACKSLASH_ESCAPE,c],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:"%r{",end:"}[a-z]*"},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(r),relevance:0}].concat(r);c.contains=d,b.contains=d;var g=[{begin:/^\s*=>/,starts:{end:"$",contains:d}},{className:"meta",begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{end:"$",contains:d}}];return{name:"Ruby",aliases:["rb","gemspec","podspec","thor","irb"],keywords:a,illegal:/\/\*/,contains:r.concat(g).concat(d)}}}());hljs.registerLanguage("yaml",function(){"use strict";return function(e){var n="true false yes no null",a="[\\w#;/?:@&=+$,.~*\\'()[\\]]+",s={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:"{{",end:"}}"},{begin:"%{",end:"}"}]}]},i=e.inherit(s,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={end:",",endsWithParent:!0,excludeEnd:!0,contains:[],keywords:n,relevance:0},t={begin:"{",end:"}",contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]",contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---s*$",relevance:10},{className:"string",begin:"[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type",begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"\\-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},{className:"number",begin:e.C_NUMBER_RE+"\\b"},t,g,s],c=[...b];return c.pop(),c.push(i),l.contains=c,{name:"YAML",case_insensitive:!0,aliases:["yml","YAML"],contains:b}}}());hljs.registerLanguage("d",function(){"use strict";return function(e){var a={$pattern:e.UNDERSCORE_IDENT_RE,keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",literal:"false null true"},d="((0|[1-9][\\d_]*)|0[bB][01_]+|0[xX]([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))",n="\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};",t={className:"number",begin:"\\b"+d+"(L|u|U|Lu|LU|uL|UL)?",relevance:0},_={className:"number",begin:"\\b(((0[xX](([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)\\.([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)|\\.?([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))[pP][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))|((0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(\\.\\d*|([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)))|\\d+\\.(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)|\\.(0|[1-9][\\d_]*)([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))?))([fF]|L|i|[fF]i|Li)?|"+d+"(i|[fF]i|Li))",relevance:0},r={className:"string",begin:"'("+n+"|.)",end:"'",illegal:"."},i={className:"string",begin:'"',contains:[{begin:n,relevance:0}],end:'"[cwd]?'},s=e.COMMENT("\\/\\+","\\+\\/",{contains:["self"],relevance:10});return{name:"D",keywords:a,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,{className:"string",begin:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',relevance:10},i,{className:"string",begin:'[rq]"',end:'"[cwd]?',relevance:5},{className:"string",begin:"`",end:"`[cwd]?"},{className:"string",begin:'q"\\{',end:'\\}"'},_,t,r,{className:"meta",begin:"^#!",end:"$",relevance:5},{className:"meta",begin:"#(line)",end:"$",relevance:5},{className:"keyword",begin:"@[a-zA-Z_][a-zA-Z_\\d]*"}]}}}());hljs.registerLanguage("properties",function(){"use strict";return function(e){var n="[ \\t\\f]*",t="("+n+"[:=]"+n+"|[ \\t\\f]+)",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",s={end:t,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{begin:"\\\\\\n"}]}};return{name:".properties",case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+"+t,returnBegin:!0,contains:[{className:"attr",begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",endsParent:!0,relevance:0}],starts:s},{begin:a+t,returnBegin:!0,relevance:0,contains:[{className:"meta",begin:a,endsParent:!0,relevance:0}],starts:s},{className:"attr",relevance:0,begin:a+n+"$"}]}}}());hljs.registerLanguage("http",function(){"use strict";return function(e){var n="HTTP/[0-9\\.]+";return{name:"HTTP",aliases:["https"],illegal:"\\S",contains:[{begin:"^"+n,end:"$",contains:[{className:"number",begin:"\\b\\d{3}\\b"}]},{begin:"^[A-Z]+ (.*?) "+n+"$",returnBegin:!0,end:"$",contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{begin:n},{className:"keyword",begin:"[A-Z]+"}]},{className:"attribute",begin:"^\\w",end:": ",excludeEnd:!0,illegal:"\\n|\\s|=",starts:{end:"$",relevance:0}},{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}]}}}());hljs.registerLanguage("haskell",function(){"use strict";return function(e){var n={variants:[e.COMMENT("--","$"),e.COMMENT("{-","-}",{contains:["self"]})]},i={className:"meta",begin:"{-#",end:"#-}"},a={className:"meta",begin:"^#",end:"$"},s={className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},l={begin:"\\(",end:"\\)",illegal:'"',contains:[i,a,{className:"type",begin:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},e.inherit(e.TITLE_MODE,{begin:"[_a-z][\\w']*"}),n]};return{name:"Haskell",aliases:["hs"],keywords:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",contains:[{beginKeywords:"module",end:"where",keywords:"module where",contains:[l,n],illegal:"\\W\\.|;"},{begin:"\\bimport\\b",end:"$",keywords:"import qualified as hiding",contains:[l,n],illegal:"\\W\\.|;"},{className:"class",begin:"^(\\s*)?(class|instance)\\b",end:"where",keywords:"class family instance where",contains:[s,l,n]},{className:"class",begin:"\\b(data|(new)?type)\\b",end:"$",keywords:"data family type newtype deriving",contains:[i,s,l,{begin:"{",end:"}",contains:l.contains},n]},{beginKeywords:"default",end:"$",contains:[s,l,n]},{beginKeywords:"infix infixl infixr",end:"$",contains:[e.C_NUMBER_MODE,n]},{begin:"\\bforeign\\b",end:"$",keywords:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",contains:[s,e.QUOTE_STRING_MODE,n]},{className:"meta",begin:"#!\\/usr\\/bin\\/env runhaskell",end:"$"},i,a,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,s,e.inherit(e.TITLE_MODE,{begin:"^[_a-z][\\w']*"}),n,{begin:"->|<-"}]}}}());hljs.registerLanguage("handlebars",function(){"use strict";function e(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(n){const a={"builtin-name":"action bindattr collection component concat debugger each each-in get hash if in input link-to loc log lookup mut outlet partial query-params render template textarea unbound unless view with yield"},t=/\[.*?\]/,s=/[^\s!"#%&'()*+,.\/;<=>@\[\\\]^`{|}~]+/,i=e("(",/'.*?'/,"|",/".*?"/,"|",t,"|",s,"|",/\.|\//,")+"),r=e("(",t,"|",s,")(?==)"),l={begin:i,lexemes:/[\w.\/]+/},c=n.inherit(l,{keywords:{literal:"true false undefined null"}}),o={begin:/\(/,end:/\)/},m={className:"attr",begin:r,relevance:0,starts:{begin:/=/,end:/=/,starts:{contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,c,o]}}},d={contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,{begin:/as\s+\|/,keywords:{keyword:"as"},end:/\|/,contains:[{begin:/\w+/}]},m,c,o],returnEnd:!0},g=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/\)/})});o.contains=[g];const u=n.inherit(l,{keywords:a,className:"name",starts:n.inherit(d,{end:/}}/})}),b=n.inherit(l,{keywords:a,className:"name"}),h=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/}}/})});return{name:"Handlebars",aliases:["hbs","html.hbs","html.handlebars","htmlbars"],case_insensitive:!0,subLanguage:"xml",contains:[{begin:/\\\{\{/,skip:!0},{begin:/\\\\(?=\{\{)/,skip:!0},n.COMMENT(/\{\{!--/,/--\}\}/),n.COMMENT(/\{\{!/,/\}\}/),{className:"template-tag",begin:/\{\{\{\{(?!\/)/,end:/\}\}\}\}/,contains:[u],starts:{end:/\{\{\{\{\//,returnEnd:!0,subLanguage:"xml"}},{className:"template-tag",begin:/\{\{\{\{\//,end:/\}\}\}\}/,contains:[b]},{className:"template-tag",begin:/\{\{#/,end:/\}\}/,contains:[u]},{className:"template-tag",begin:/\{\{(?=else\}\})/,end:/\}\}/,keywords:"else"},{className:"template-tag",begin:/\{\{\//,end:/\}\}/,contains:[b]},{className:"template-variable",begin:/\{\{\{/,end:/\}\}\}/,contains:[h]},{className:"template-variable",begin:/\{\{/,end:/\}\}/,contains:[h]}]}}}());hljs.registerLanguage("rust",function(){"use strict";return function(e){var n="([ui](8|16|32|64|128|size)|f(32|64))?",t="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield",literal:"true false Some None Ok Err",built_in:t},illegal:""}]}}}());hljs.registerLanguage("cpp",function(){"use strict";return function(e){var t=e.getLanguage("c-like").rawDefinition();return t.disableAutodetect=!1,t.name="C++",t.aliases=["cc","c++","h++","hpp","hh","hxx","cxx"],t}}());hljs.registerLanguage("ini",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(...n){return n.map(n=>e(n)).join("")}return function(a){var s={className:"number",relevance:0,variants:[{begin:/([\+\-]+)?[\d]+_[\d_]+/},{begin:a.NUMBER_RE}]},i=a.COMMENT();i.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];var t={className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)}/}]},r={className:"literal",begin:/\bon|off|true|false|yes|no\b/},l={className:"string",contains:[a.BACKSLASH_ESCAPE],variants:[{begin:"'''",end:"'''",relevance:10},{begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"'},{begin:"'",end:"'"}]},c={begin:/\[/,end:/\]/,contains:[i,r,t,l,s,"self"],relevance:0},g="("+[/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/].map(n=>e(n)).join("|")+")";return{name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/,contains:[i,{className:"section",begin:/\[+/,end:/\]+/},{begin:n(g,"(\\s*\\.\\s*",g,")*",n("(?=",/\s*=\s*[^#\s]/,")")),className:"attr",starts:{end:/$/,contains:[i,c,r,t,l,s]}}]}}}());hljs.registerLanguage("objectivec",function(){"use strict";return function(e){var n=/[a-zA-Z@][a-zA-Z0-9_]*/,_={$pattern:n,keyword:"@interface @class @protocol @implementation"};return{name:"Objective-C",aliases:["mm","objc","obj-c"],keywords:{$pattern:n,keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},illegal:"/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"class",begin:"("+_.keyword.split(" ").join("|")+")\\b",end:"({|$)",excludeEnd:!0,keywords:_,contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"\\."+e.UNDERSCORE_IDENT_RE,relevance:0}]}}}());hljs.registerLanguage("apache",function(){"use strict";return function(e){var n={className:"number",begin:"\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?"};return{name:"Apache config",aliases:["apacheconf"],case_insensitive:!0,contains:[e.HASH_COMMENT_MODE,{className:"section",begin:"",contains:[n,{className:"number",begin:":\\d{1,5}"},e.inherit(e.QUOTE_STRING_MODE,{relevance:0})]},{className:"attribute",begin:/\w+/,relevance:0,keywords:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{end:/$/,relevance:0,keywords:{literal:"on off all deny allow"},contains:[{className:"meta",begin:"\\s\\[",end:"\\]$"},{className:"variable",begin:"[\\$%]\\{",end:"\\}",contains:["self",{className:"number",begin:"[\\$%]\\d+"}]},n,{className:"number",begin:"\\d+"},e.QUOTE_STRING_MODE]}}],illegal:/\S/}}}());hljs.registerLanguage("java",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(e){return a("(",e,")?")}function a(...n){return n.map(n=>e(n)).join("")}function s(...n){return"("+n.map(n=>e(n)).join("|")+")"}return function(e){var t="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",i={className:"meta",begin:"@[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",contains:[{begin:/\(/,end:/\)/,contains:["self"]}]},r=e=>a("[",e,"]+([",e,"_]*[",e,"]+)?"),c={className:"number",variants:[{begin:`\\b(0[bB]${r("01")})[lL]?`},{begin:`\\b(0${r("0-7")})[dDfFlL]?`},{begin:a(/\b0[xX]/,s(a(r("a-fA-F0-9"),/\./,r("a-fA-F0-9")),a(r("a-fA-F0-9"),/\.?/),a(/\./,r("a-fA-F0-9"))),/([pP][+-]?(\d+))?/,/[fFdDlL]?/)},{begin:a(/\b/,s(a(/\d*\./,r("\\d")),r("\\d")),/[eE][+-]?[\d]+[dDfF]?/)},{begin:a(/\b/,r(/\d/),n(/\.?/),n(r(/\d/)),/[dDfFlL]?/)}],relevance:0};return{name:"Java",aliases:["jsp"],keywords:t,illegal:/<\/|#/,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"class",beginKeywords:"class interface",end:/[{;=]/,excludeEnd:!0,keywords:"class interface",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"new throw return else",relevance:0},{className:"function",begin:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:t,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:t,relevance:0,contains:[i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},c,i]}}}());hljs.registerLanguage("x86asm",function(){"use strict";return function(s){return{name:"Intel x86 Assembly",case_insensitive:!0,keywords:{$pattern:"[.%]?"+s.IDENT_RE,keyword:"lock rep repe repz repne repnz xaquire xrelease bnd nobnd aaa aad aam aas adc add and arpl bb0_reset bb1_reset bound bsf bsr bswap bt btc btr bts call cbw cdq cdqe clc cld cli clts cmc cmp cmpsb cmpsd cmpsq cmpsw cmpxchg cmpxchg486 cmpxchg8b cmpxchg16b cpuid cpu_read cpu_write cqo cwd cwde daa das dec div dmint emms enter equ f2xm1 fabs fadd faddp fbld fbstp fchs fclex fcmovb fcmovbe fcmove fcmovnb fcmovnbe fcmovne fcmovnu fcmovu fcom fcomi fcomip fcomp fcompp fcos fdecstp fdisi fdiv fdivp fdivr fdivrp femms feni ffree ffreep fiadd ficom ficomp fidiv fidivr fild fimul fincstp finit fist fistp fisttp fisub fisubr fld fld1 fldcw fldenv fldl2e fldl2t fldlg2 fldln2 fldpi fldz fmul fmulp fnclex fndisi fneni fninit fnop fnsave fnstcw fnstenv fnstsw fpatan fprem fprem1 fptan frndint frstor fsave fscale fsetpm fsin fsincos fsqrt fst fstcw fstenv fstp fstsw fsub fsubp fsubr fsubrp ftst fucom fucomi fucomip fucomp fucompp fxam fxch fxtract fyl2x fyl2xp1 hlt ibts icebp idiv imul in inc incbin insb insd insw int int01 int1 int03 int3 into invd invpcid invlpg invlpga iret iretd iretq iretw jcxz jecxz jrcxz jmp jmpe lahf lar lds lea leave les lfence lfs lgdt lgs lidt lldt lmsw loadall loadall286 lodsb lodsd lodsq lodsw loop loope loopne loopnz loopz lsl lss ltr mfence monitor mov movd movq movsb movsd movsq movsw movsx movsxd movzx mul mwait neg nop not or out outsb outsd outsw packssdw packsswb packuswb paddb paddd paddsb paddsiw paddsw paddusb paddusw paddw pand pandn pause paveb pavgusb pcmpeqb pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw pdistib pf2id pfacc pfadd pfcmpeq pfcmpge pfcmpgt pfmax pfmin pfmul pfrcp pfrcpit1 pfrcpit2 pfrsqit1 pfrsqrt pfsub pfsubr pi2fd pmachriw pmaddwd pmagw pmulhriw pmulhrwa pmulhrwc pmulhw pmullw pmvgezb pmvlzb pmvnzb pmvzb pop popa popad popaw popf popfd popfq popfw por prefetch prefetchw pslld psllq psllw psrad psraw psrld psrlq psrlw psubb psubd psubsb psubsiw psubsw psubusb psubusw psubw punpckhbw punpckhdq punpckhwd punpcklbw punpckldq punpcklwd push pusha pushad pushaw pushf pushfd pushfq pushfw pxor rcl rcr rdshr rdmsr rdpmc rdtsc rdtscp ret retf retn rol ror rdm rsdc rsldt rsm rsts sahf sal salc sar sbb scasb scasd scasq scasw sfence sgdt shl shld shr shrd sidt sldt skinit smi smint smintold smsw stc std sti stosb stosd stosq stosw str sub svdc svldt svts swapgs syscall sysenter sysexit sysret test ud0 ud1 ud2b ud2 ud2a umov verr verw fwait wbinvd wrshr wrmsr xadd xbts xchg xlatb xlat xor cmove cmovz cmovne cmovnz cmova cmovnbe cmovae cmovnb cmovb cmovnae cmovbe cmovna cmovg cmovnle cmovge cmovnl cmovl cmovnge cmovle cmovng cmovc cmovnc cmovo cmovno cmovs cmovns cmovp cmovpe cmovnp cmovpo je jz jne jnz ja jnbe jae jnb jb jnae jbe jna jg jnle jge jnl jl jnge jle jng jc jnc jo jno js jns jpo jnp jpe jp sete setz setne setnz seta setnbe setae setnb setnc setb setnae setcset setbe setna setg setnle setge setnl setl setnge setle setng sets setns seto setno setpe setp setpo setnp addps addss andnps andps cmpeqps cmpeqss cmpleps cmpless cmpltps cmpltss cmpneqps cmpneqss cmpnleps cmpnless cmpnltps cmpnltss cmpordps cmpordss cmpunordps cmpunordss cmpps cmpss comiss cvtpi2ps cvtps2pi cvtsi2ss cvtss2si cvttps2pi cvttss2si divps divss ldmxcsr maxps maxss minps minss movaps movhps movlhps movlps movhlps movmskps movntps movss movups mulps mulss orps rcpps rcpss rsqrtps rsqrtss shufps sqrtps sqrtss stmxcsr subps subss ucomiss unpckhps unpcklps xorps fxrstor fxrstor64 fxsave fxsave64 xgetbv xsetbv xsave xsave64 xsaveopt xsaveopt64 xrstor xrstor64 prefetchnta prefetcht0 prefetcht1 prefetcht2 maskmovq movntq pavgb pavgw pextrw pinsrw pmaxsw pmaxub pminsw pminub pmovmskb pmulhuw psadbw pshufw pf2iw pfnacc pfpnacc pi2fw pswapd maskmovdqu clflush movntdq movnti movntpd movdqa movdqu movdq2q movq2dq paddq pmuludq pshufd pshufhw pshuflw pslldq psrldq psubq punpckhqdq punpcklqdq addpd addsd andnpd andpd cmpeqpd cmpeqsd cmplepd cmplesd cmpltpd cmpltsd cmpneqpd cmpneqsd cmpnlepd cmpnlesd cmpnltpd cmpnltsd cmpordpd cmpordsd cmpunordpd cmpunordsd cmppd comisd cvtdq2pd cvtdq2ps cvtpd2dq cvtpd2pi cvtpd2ps cvtpi2pd cvtps2dq cvtps2pd cvtsd2si cvtsd2ss cvtsi2sd cvtss2sd cvttpd2pi cvttpd2dq cvttps2dq cvttsd2si divpd divsd maxpd maxsd minpd minsd movapd movhpd movlpd movmskpd movupd mulpd mulsd orpd shufpd sqrtpd sqrtsd subpd subsd ucomisd unpckhpd unpcklpd xorpd addsubpd addsubps haddpd haddps hsubpd hsubps lddqu movddup movshdup movsldup clgi stgi vmcall vmclear vmfunc vmlaunch vmload vmmcall vmptrld vmptrst vmread vmresume vmrun vmsave vmwrite vmxoff vmxon invept invvpid pabsb pabsw pabsd palignr phaddw phaddd phaddsw phsubw phsubd phsubsw pmaddubsw pmulhrsw pshufb psignb psignw psignd extrq insertq movntsd movntss lzcnt blendpd blendps blendvpd blendvps dppd dpps extractps insertps movntdqa mpsadbw packusdw pblendvb pblendw pcmpeqq pextrb pextrd pextrq phminposuw pinsrb pinsrd pinsrq pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq pmuldq pmulld ptest roundpd roundps roundsd roundss crc32 pcmpestri pcmpestrm pcmpistri pcmpistrm pcmpgtq popcnt getsec pfrcpv pfrsqrtv movbe aesenc aesenclast aesdec aesdeclast aesimc aeskeygenassist vaesenc vaesenclast vaesdec vaesdeclast vaesimc vaeskeygenassist vaddpd vaddps vaddsd vaddss vaddsubpd vaddsubps vandpd vandps vandnpd vandnps vblendpd vblendps vblendvpd vblendvps vbroadcastss vbroadcastsd vbroadcastf128 vcmpeq_ospd vcmpeqpd vcmplt_ospd vcmpltpd vcmple_ospd vcmplepd vcmpunord_qpd vcmpunordpd vcmpneq_uqpd vcmpneqpd vcmpnlt_uspd vcmpnltpd vcmpnle_uspd vcmpnlepd vcmpord_qpd vcmpordpd vcmpeq_uqpd vcmpnge_uspd vcmpngepd vcmpngt_uspd vcmpngtpd vcmpfalse_oqpd vcmpfalsepd vcmpneq_oqpd vcmpge_ospd vcmpgepd vcmpgt_ospd vcmpgtpd vcmptrue_uqpd vcmptruepd vcmplt_oqpd vcmple_oqpd vcmpunord_spd vcmpneq_uspd vcmpnlt_uqpd vcmpnle_uqpd vcmpord_spd vcmpeq_uspd vcmpnge_uqpd vcmpngt_uqpd vcmpfalse_ospd vcmpneq_ospd vcmpge_oqpd vcmpgt_oqpd vcmptrue_uspd vcmppd vcmpeq_osps vcmpeqps vcmplt_osps vcmpltps vcmple_osps vcmpleps vcmpunord_qps vcmpunordps vcmpneq_uqps vcmpneqps vcmpnlt_usps vcmpnltps vcmpnle_usps vcmpnleps vcmpord_qps vcmpordps vcmpeq_uqps vcmpnge_usps vcmpngeps vcmpngt_usps vcmpngtps vcmpfalse_oqps vcmpfalseps vcmpneq_oqps vcmpge_osps vcmpgeps vcmpgt_osps vcmpgtps vcmptrue_uqps vcmptrueps vcmplt_oqps vcmple_oqps vcmpunord_sps vcmpneq_usps vcmpnlt_uqps vcmpnle_uqps vcmpord_sps vcmpeq_usps vcmpnge_uqps vcmpngt_uqps vcmpfalse_osps vcmpneq_osps vcmpge_oqps vcmpgt_oqps vcmptrue_usps vcmpps vcmpeq_ossd vcmpeqsd vcmplt_ossd vcmpltsd vcmple_ossd vcmplesd vcmpunord_qsd vcmpunordsd vcmpneq_uqsd vcmpneqsd vcmpnlt_ussd vcmpnltsd vcmpnle_ussd vcmpnlesd vcmpord_qsd vcmpordsd vcmpeq_uqsd vcmpnge_ussd vcmpngesd vcmpngt_ussd vcmpngtsd vcmpfalse_oqsd vcmpfalsesd vcmpneq_oqsd vcmpge_ossd vcmpgesd vcmpgt_ossd vcmpgtsd vcmptrue_uqsd vcmptruesd vcmplt_oqsd vcmple_oqsd vcmpunord_ssd vcmpneq_ussd vcmpnlt_uqsd vcmpnle_uqsd vcmpord_ssd vcmpeq_ussd vcmpnge_uqsd vcmpngt_uqsd vcmpfalse_ossd vcmpneq_ossd vcmpge_oqsd vcmpgt_oqsd vcmptrue_ussd vcmpsd vcmpeq_osss vcmpeqss vcmplt_osss vcmpltss vcmple_osss vcmpless vcmpunord_qss vcmpunordss vcmpneq_uqss vcmpneqss vcmpnlt_usss vcmpnltss vcmpnle_usss vcmpnless vcmpord_qss vcmpordss vcmpeq_uqss vcmpnge_usss vcmpngess vcmpngt_usss vcmpngtss vcmpfalse_oqss vcmpfalsess vcmpneq_oqss vcmpge_osss vcmpgess vcmpgt_osss vcmpgtss vcmptrue_uqss vcmptruess vcmplt_oqss vcmple_oqss vcmpunord_sss vcmpneq_usss vcmpnlt_uqss vcmpnle_uqss vcmpord_sss vcmpeq_usss vcmpnge_uqss vcmpngt_uqss vcmpfalse_osss vcmpneq_osss vcmpge_oqss vcmpgt_oqss vcmptrue_usss vcmpss vcomisd vcomiss vcvtdq2pd vcvtdq2ps vcvtpd2dq vcvtpd2ps vcvtps2dq vcvtps2pd vcvtsd2si vcvtsd2ss vcvtsi2sd vcvtsi2ss vcvtss2sd vcvtss2si vcvttpd2dq vcvttps2dq vcvttsd2si vcvttss2si vdivpd vdivps vdivsd vdivss vdppd vdpps vextractf128 vextractps vhaddpd vhaddps vhsubpd vhsubps vinsertf128 vinsertps vlddqu vldqqu vldmxcsr vmaskmovdqu vmaskmovps vmaskmovpd vmaxpd vmaxps vmaxsd vmaxss vminpd vminps vminsd vminss vmovapd vmovaps vmovd vmovq vmovddup vmovdqa vmovqqa vmovdqu vmovqqu vmovhlps vmovhpd vmovhps vmovlhps vmovlpd vmovlps vmovmskpd vmovmskps vmovntdq vmovntqq vmovntdqa vmovntpd vmovntps vmovsd vmovshdup vmovsldup vmovss vmovupd vmovups vmpsadbw vmulpd vmulps vmulsd vmulss vorpd vorps vpabsb vpabsw vpabsd vpacksswb vpackssdw vpackuswb vpackusdw vpaddb vpaddw vpaddd vpaddq vpaddsb vpaddsw vpaddusb vpaddusw vpalignr vpand vpandn vpavgb vpavgw vpblendvb vpblendw vpcmpestri vpcmpestrm vpcmpistri vpcmpistrm vpcmpeqb vpcmpeqw vpcmpeqd vpcmpeqq vpcmpgtb vpcmpgtw vpcmpgtd vpcmpgtq vpermilpd vpermilps vperm2f128 vpextrb vpextrw vpextrd vpextrq vphaddw vphaddd vphaddsw vphminposuw vphsubw vphsubd vphsubsw vpinsrb vpinsrw vpinsrd vpinsrq vpmaddwd vpmaddubsw vpmaxsb vpmaxsw vpmaxsd vpmaxub vpmaxuw vpmaxud vpminsb vpminsw vpminsd vpminub vpminuw vpminud vpmovmskb vpmovsxbw vpmovsxbd vpmovsxbq vpmovsxwd vpmovsxwq vpmovsxdq vpmovzxbw vpmovzxbd vpmovzxbq vpmovzxwd vpmovzxwq vpmovzxdq vpmulhuw vpmulhrsw vpmulhw vpmullw vpmulld vpmuludq vpmuldq vpor vpsadbw vpshufb vpshufd vpshufhw vpshuflw vpsignb vpsignw vpsignd vpslldq vpsrldq vpsllw vpslld vpsllq vpsraw vpsrad vpsrlw vpsrld vpsrlq vptest vpsubb vpsubw vpsubd vpsubq vpsubsb vpsubsw vpsubusb vpsubusw vpunpckhbw vpunpckhwd vpunpckhdq vpunpckhqdq vpunpcklbw vpunpcklwd vpunpckldq vpunpcklqdq vpxor vrcpps vrcpss vrsqrtps vrsqrtss vroundpd vroundps vroundsd vroundss vshufpd vshufps vsqrtpd vsqrtps vsqrtsd vsqrtss vstmxcsr vsubpd vsubps vsubsd vsubss vtestps vtestpd vucomisd vucomiss vunpckhpd vunpckhps vunpcklpd vunpcklps vxorpd vxorps vzeroall vzeroupper pclmullqlqdq pclmulhqlqdq pclmullqhqdq pclmulhqhqdq pclmulqdq vpclmullqlqdq vpclmulhqlqdq vpclmullqhqdq vpclmulhqhqdq vpclmulqdq vfmadd132ps vfmadd132pd vfmadd312ps vfmadd312pd vfmadd213ps vfmadd213pd vfmadd123ps vfmadd123pd vfmadd231ps vfmadd231pd vfmadd321ps vfmadd321pd vfmaddsub132ps vfmaddsub132pd vfmaddsub312ps vfmaddsub312pd vfmaddsub213ps vfmaddsub213pd vfmaddsub123ps vfmaddsub123pd vfmaddsub231ps vfmaddsub231pd vfmaddsub321ps vfmaddsub321pd vfmsub132ps vfmsub132pd vfmsub312ps vfmsub312pd vfmsub213ps vfmsub213pd vfmsub123ps vfmsub123pd vfmsub231ps vfmsub231pd vfmsub321ps vfmsub321pd vfmsubadd132ps vfmsubadd132pd vfmsubadd312ps vfmsubadd312pd vfmsubadd213ps vfmsubadd213pd vfmsubadd123ps vfmsubadd123pd vfmsubadd231ps vfmsubadd231pd vfmsubadd321ps vfmsubadd321pd vfnmadd132ps vfnmadd132pd vfnmadd312ps vfnmadd312pd vfnmadd213ps vfnmadd213pd vfnmadd123ps vfnmadd123pd vfnmadd231ps vfnmadd231pd vfnmadd321ps vfnmadd321pd vfnmsub132ps vfnmsub132pd vfnmsub312ps vfnmsub312pd vfnmsub213ps vfnmsub213pd vfnmsub123ps vfnmsub123pd vfnmsub231ps vfnmsub231pd vfnmsub321ps vfnmsub321pd vfmadd132ss vfmadd132sd vfmadd312ss vfmadd312sd vfmadd213ss vfmadd213sd vfmadd123ss vfmadd123sd vfmadd231ss vfmadd231sd vfmadd321ss vfmadd321sd vfmsub132ss vfmsub132sd vfmsub312ss vfmsub312sd vfmsub213ss vfmsub213sd vfmsub123ss vfmsub123sd vfmsub231ss vfmsub231sd vfmsub321ss vfmsub321sd vfnmadd132ss vfnmadd132sd vfnmadd312ss vfnmadd312sd vfnmadd213ss vfnmadd213sd vfnmadd123ss vfnmadd123sd vfnmadd231ss vfnmadd231sd vfnmadd321ss vfnmadd321sd vfnmsub132ss vfnmsub132sd vfnmsub312ss vfnmsub312sd vfnmsub213ss vfnmsub213sd vfnmsub123ss vfnmsub123sd vfnmsub231ss vfnmsub231sd vfnmsub321ss vfnmsub321sd rdfsbase rdgsbase rdrand wrfsbase wrgsbase vcvtph2ps vcvtps2ph adcx adox rdseed clac stac xstore xcryptecb xcryptcbc xcryptctr xcryptcfb xcryptofb montmul xsha1 xsha256 llwpcb slwpcb lwpval lwpins vfmaddpd vfmaddps vfmaddsd vfmaddss vfmaddsubpd vfmaddsubps vfmsubaddpd vfmsubaddps vfmsubpd vfmsubps vfmsubsd vfmsubss vfnmaddpd vfnmaddps vfnmaddsd vfnmaddss vfnmsubpd vfnmsubps vfnmsubsd vfnmsubss vfrczpd vfrczps vfrczsd vfrczss vpcmov vpcomb vpcomd vpcomq vpcomub vpcomud vpcomuq vpcomuw vpcomw vphaddbd vphaddbq vphaddbw vphadddq vphaddubd vphaddubq vphaddubw vphaddudq vphadduwd vphadduwq vphaddwd vphaddwq vphsubbw vphsubdq vphsubwd vpmacsdd vpmacsdqh vpmacsdql vpmacssdd vpmacssdqh vpmacssdql vpmacsswd vpmacssww vpmacswd vpmacsww vpmadcsswd vpmadcswd vpperm vprotb vprotd vprotq vprotw vpshab vpshad vpshaq vpshaw vpshlb vpshld vpshlq vpshlw vbroadcasti128 vpblendd vpbroadcastb vpbroadcastw vpbroadcastd vpbroadcastq vpermd vpermpd vpermps vpermq vperm2i128 vextracti128 vinserti128 vpmaskmovd vpmaskmovq vpsllvd vpsllvq vpsravd vpsrlvd vpsrlvq vgatherdpd vgatherqpd vgatherdps vgatherqps vpgatherdd vpgatherqd vpgatherdq vpgatherqq xabort xbegin xend xtest andn bextr blci blcic blsi blsic blcfill blsfill blcmsk blsmsk blsr blcs bzhi mulx pdep pext rorx sarx shlx shrx tzcnt tzmsk t1mskc valignd valignq vblendmpd vblendmps vbroadcastf32x4 vbroadcastf64x4 vbroadcasti32x4 vbroadcasti64x4 vcompresspd vcompressps vcvtpd2udq vcvtps2udq vcvtsd2usi vcvtss2usi vcvttpd2udq vcvttps2udq vcvttsd2usi vcvttss2usi vcvtudq2pd vcvtudq2ps vcvtusi2sd vcvtusi2ss vexpandpd vexpandps vextractf32x4 vextractf64x4 vextracti32x4 vextracti64x4 vfixupimmpd vfixupimmps vfixupimmsd vfixupimmss vgetexppd vgetexpps vgetexpsd vgetexpss vgetmantpd vgetmantps vgetmantsd vgetmantss vinsertf32x4 vinsertf64x4 vinserti32x4 vinserti64x4 vmovdqa32 vmovdqa64 vmovdqu32 vmovdqu64 vpabsq vpandd vpandnd vpandnq vpandq vpblendmd vpblendmq vpcmpltd vpcmpled vpcmpneqd vpcmpnltd vpcmpnled vpcmpd vpcmpltq vpcmpleq vpcmpneqq vpcmpnltq vpcmpnleq vpcmpq vpcmpequd vpcmpltud vpcmpleud vpcmpnequd vpcmpnltud vpcmpnleud vpcmpud vpcmpequq vpcmpltuq vpcmpleuq vpcmpnequq vpcmpnltuq vpcmpnleuq vpcmpuq vpcompressd vpcompressq vpermi2d vpermi2pd vpermi2ps vpermi2q vpermt2d vpermt2pd vpermt2ps vpermt2q vpexpandd vpexpandq vpmaxsq vpmaxuq vpminsq vpminuq vpmovdb vpmovdw vpmovqb vpmovqd vpmovqw vpmovsdb vpmovsdw vpmovsqb vpmovsqd vpmovsqw vpmovusdb vpmovusdw vpmovusqb vpmovusqd vpmovusqw vpord vporq vprold vprolq vprolvd vprolvq vprord vprorq vprorvd vprorvq vpscatterdd vpscatterdq vpscatterqd vpscatterqq vpsraq vpsravq vpternlogd vpternlogq vptestmd vptestmq vptestnmd vptestnmq vpxord vpxorq vrcp14pd vrcp14ps vrcp14sd vrcp14ss vrndscalepd vrndscaleps vrndscalesd vrndscaless vrsqrt14pd vrsqrt14ps vrsqrt14sd vrsqrt14ss vscalefpd vscalefps vscalefsd vscalefss vscatterdpd vscatterdps vscatterqpd vscatterqps vshuff32x4 vshuff64x2 vshufi32x4 vshufi64x2 kandnw kandw kmovw knotw kortestw korw kshiftlw kshiftrw kunpckbw kxnorw kxorw vpbroadcastmb2q vpbroadcastmw2d vpconflictd vpconflictq vplzcntd vplzcntq vexp2pd vexp2ps vrcp28pd vrcp28ps vrcp28sd vrcp28ss vrsqrt28pd vrsqrt28ps vrsqrt28sd vrsqrt28ss vgatherpf0dpd vgatherpf0dps vgatherpf0qpd vgatherpf0qps vgatherpf1dpd vgatherpf1dps vgatherpf1qpd vgatherpf1qps vscatterpf0dpd vscatterpf0dps vscatterpf0qpd vscatterpf0qps vscatterpf1dpd vscatterpf1dps vscatterpf1qpd vscatterpf1qps prefetchwt1 bndmk bndcl bndcu bndcn bndmov bndldx bndstx sha1rnds4 sha1nexte sha1msg1 sha1msg2 sha256rnds2 sha256msg1 sha256msg2 hint_nop0 hint_nop1 hint_nop2 hint_nop3 hint_nop4 hint_nop5 hint_nop6 hint_nop7 hint_nop8 hint_nop9 hint_nop10 hint_nop11 hint_nop12 hint_nop13 hint_nop14 hint_nop15 hint_nop16 hint_nop17 hint_nop18 hint_nop19 hint_nop20 hint_nop21 hint_nop22 hint_nop23 hint_nop24 hint_nop25 hint_nop26 hint_nop27 hint_nop28 hint_nop29 hint_nop30 hint_nop31 hint_nop32 hint_nop33 hint_nop34 hint_nop35 hint_nop36 hint_nop37 hint_nop38 hint_nop39 hint_nop40 hint_nop41 hint_nop42 hint_nop43 hint_nop44 hint_nop45 hint_nop46 hint_nop47 hint_nop48 hint_nop49 hint_nop50 hint_nop51 hint_nop52 hint_nop53 hint_nop54 hint_nop55 hint_nop56 hint_nop57 hint_nop58 hint_nop59 hint_nop60 hint_nop61 hint_nop62 hint_nop63",built_in:"ip eip rip al ah bl bh cl ch dl dh sil dil bpl spl r8b r9b r10b r11b r12b r13b r14b r15b ax bx cx dx si di bp sp r8w r9w r10w r11w r12w r13w r14w r15w eax ebx ecx edx esi edi ebp esp eip r8d r9d r10d r11d r12d r13d r14d r15d rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 cs ds es fs gs ss st st0 st1 st2 st3 st4 st5 st6 st7 mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7 xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15 xmm16 xmm17 xmm18 xmm19 xmm20 xmm21 xmm22 xmm23 xmm24 xmm25 xmm26 xmm27 xmm28 xmm29 xmm30 xmm31 ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm8 ymm9 ymm10 ymm11 ymm12 ymm13 ymm14 ymm15 ymm16 ymm17 ymm18 ymm19 ymm20 ymm21 ymm22 ymm23 ymm24 ymm25 ymm26 ymm27 ymm28 ymm29 ymm30 ymm31 zmm0 zmm1 zmm2 zmm3 zmm4 zmm5 zmm6 zmm7 zmm8 zmm9 zmm10 zmm11 zmm12 zmm13 zmm14 zmm15 zmm16 zmm17 zmm18 zmm19 zmm20 zmm21 zmm22 zmm23 zmm24 zmm25 zmm26 zmm27 zmm28 zmm29 zmm30 zmm31 k0 k1 k2 k3 k4 k5 k6 k7 bnd0 bnd1 bnd2 bnd3 cr0 cr1 cr2 cr3 cr4 cr8 dr0 dr1 dr2 dr3 dr8 tr3 tr4 tr5 tr6 tr7 r0 r1 r2 r3 r4 r5 r6 r7 r0b r1b r2b r3b r4b r5b r6b r7b r0w r1w r2w r3w r4w r5w r6w r7w r0d r1d r2d r3d r4d r5d r6d r7d r0h r1h r2h r3h r0l r1l r2l r3l r4l r5l r6l r7l r8l r9l r10l r11l r12l r13l r14l r15l db dw dd dq dt ddq do dy dz resb resw resd resq rest resdq reso resy resz incbin equ times byte word dword qword nosplit rel abs seg wrt strict near far a32 ptr",meta:"%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif %if %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep %endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment .nolist __FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ __UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend align alignb sectalign daz nodaz up down zero default option assume public bits use16 use32 use64 default section segment absolute extern global common cpu float __utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ __float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ __Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__"},contains:[s.COMMENT(";","$",{relevance:0}),{className:"number",variants:[{begin:"\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|(0[Xx])?[0-9][0-9_]*\\.?[0-9_]*(?:[pP](?:[+-]?[0-9_]+)?)?)\\b",relevance:0},{begin:"\\$[0-9][0-9A-Fa-f]*",relevance:0},{begin:"\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[Hh]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b"},{begin:"\\b(?:0[Xx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b"}]},s.QUOTE_STRING_MODE,{className:"string",variants:[{begin:"'",end:"[^\\\\]'"},{begin:"`",end:"[^\\\\]`"}],relevance:0},{className:"symbol",variants:[{begin:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)"},{begin:"^\\s*%%[A-Za-z0-9_$#@~.?]*:"}],relevance:0},{className:"subst",begin:"%[0-9]+",relevance:0},{className:"subst",begin:"%!S+",relevance:0},{className:"meta",begin:/^\s*\.[\w_-]+/}]}}}());hljs.registerLanguage("kotlin",function(){"use strict";return function(e){var n={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual trait volatile transient native default",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},a={className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"@"},i={className:"subst",begin:"\\${",end:"}",contains:[e.C_NUMBER_MODE]},s={className:"variable",begin:"\\$"+e.UNDERSCORE_IDENT_RE},t={className:"string",variants:[{begin:'"""',end:'"""(?=[^"])',contains:[s,i]},{begin:"'",end:"'",illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/,contains:[e.BACKSLASH_ESCAPE,s,i]}]};i.contains.push(t);var r={className:"meta",begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UNDERSCORE_IDENT_RE+")?"},l={className:"meta",begin:"@"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/,end:/\)/,contains:[e.inherit(t,{className:"meta-string"})]}]},c=e.COMMENT("/\\*","\\*/",{contains:[e.C_BLOCK_COMMENT_MODE]}),o={variants:[{className:"type",begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/,contains:[]}]},d=o;return d.variants[1].contains=[o],o.variants[1].contains=[d],{name:"Kotlin",aliases:["kt"],keywords:n,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,c,{className:"keyword",begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol",begin:/@\w+/}]}},a,r,l,{className:"function",beginKeywords:"fun",end:"[(]|$",returnBegin:!0,excludeEnd:!0,keywords:n,illegal:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,relevance:5,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"type",begin://,keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\/]/,endsWithParent:!0,contains:[o,e.C_LINE_COMMENT_MODE,c],relevance:0},e.C_LINE_COMMENT_MODE,c,r,l,t,e.C_NUMBER_MODE]},c]},{className:"class",beginKeywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0,illegal:"extends implements",contains:[{beginKeywords:"public protected internal private constructor"},e.UNDERSCORE_TITLE_MODE,{className:"type",begin://,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,]|$/,excludeBegin:!0,returnEnd:!0},r,l]},t,{className:"meta",begin:"^#!/usr/bin/env",end:"$",illegal:"\n"},{className:"number",begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0}]}}}());hljs.registerLanguage("armasm",function(){"use strict";return function(s){const e={variants:[s.COMMENT("^[ \\t]*(?=#)","$",{relevance:0,excludeBegin:!0}),s.COMMENT("[;@]","$",{relevance:0}),s.C_LINE_COMMENT_MODE,s.C_BLOCK_COMMENT_MODE]};return{name:"ARM Assembly",case_insensitive:!0,aliases:["arm"],keywords:{$pattern:"\\.?"+s.IDENT_RE,meta:".2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .arm .thumb .code16 .code32 .force_thumb .thumb_func .ltorg ALIAS ALIGN ARM AREA ASSERT ATTR CN CODE CODE16 CODE32 COMMON CP DATA DCB DCD DCDU DCDO DCFD DCFDU DCI DCQ DCQU DCW DCWU DN ELIF ELSE END ENDFUNC ENDIF ENDP ENTRY EQU EXPORT EXPORTAS EXTERN FIELD FILL FUNCTION GBLA GBLL GBLS GET GLOBAL IF IMPORT INCBIN INCLUDE INFO KEEP LCLA LCLL LCLS LTORG MACRO MAP MEND MEXIT NOFP OPT PRESERVE8 PROC QN READONLY RELOC REQUIRE REQUIRE8 RLIST FN ROUT SETA SETL SETS SN SPACE SUBT THUMB THUMBX TTL WHILE WEND ",built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 pc lr sp ip sl sb fp a1 a2 a3 a4 v1 v2 v3 v4 v5 v6 v7 v8 f0 f1 f2 f3 f4 f5 f6 f7 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13 p14 p15 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13 c14 c15 q0 q1 q2 q3 q4 q5 q6 q7 q8 q9 q10 q11 q12 q13 q14 q15 cpsr_c cpsr_x cpsr_s cpsr_f cpsr_cx cpsr_cxs cpsr_xs cpsr_xsf cpsr_sf cpsr_cxsf spsr_c spsr_x spsr_s spsr_f spsr_cx spsr_cxs spsr_xs spsr_xsf spsr_sf spsr_cxsf s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s20 s21 s22 s23 s24 s25 s26 s27 s28 s29 s30 s31 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13 d14 d15 d16 d17 d18 d19 d20 d21 d22 d23 d24 d25 d26 d27 d28 d29 d30 d31 {PC} {VAR} {TRUE} {FALSE} {OPT} {CONFIG} {ENDIAN} {CODESIZE} {CPU} {FPU} {ARCHITECTURE} {PCSTOREOFFSET} {ARMASM_VERSION} {INTER} {ROPI} {RWPI} {SWST} {NOSWST} . @"},contains:[{className:"keyword",begin:"\\b(adc|(qd?|sh?|u[qh]?)?add(8|16)?|usada?8|(q|sh?|u[qh]?)?(as|sa)x|and|adrl?|sbc|rs[bc]|asr|b[lx]?|blx|bxj|cbn?z|tb[bh]|bic|bfc|bfi|[su]bfx|bkpt|cdp2?|clz|clrex|cmp|cmn|cpsi[ed]|cps|setend|dbg|dmb|dsb|eor|isb|it[te]{0,3}|lsl|lsr|ror|rrx|ldm(([id][ab])|f[ds])?|ldr((s|ex)?[bhd])?|movt?|mvn|mra|mar|mul|[us]mull|smul[bwt][bt]|smu[as]d|smmul|smmla|mla|umlaal|smlal?([wbt][bt]|d)|mls|smlsl?[ds]|smc|svc|sev|mia([bt]{2}|ph)?|mrr?c2?|mcrr2?|mrs|msr|orr|orn|pkh(tb|bt)|rbit|rev(16|sh)?|sel|[su]sat(16)?|nop|pop|push|rfe([id][ab])?|stm([id][ab])?|str(ex)?[bhd]?|(qd?)?sub|(sh?|q|u[qh]?)?sub(8|16)|[su]xt(a?h|a?b(16)?)|srs([id][ab])?|swpb?|swi|smi|tst|teq|wfe|wfi|yield)(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al|hs|lo)?[sptrx]?(?=\\s)"},e,s.QUOTE_STRING_MODE,{className:"string",begin:"'",end:"[^\\\\]'",relevance:0},{className:"title",begin:"\\|",end:"\\|",illegal:"\\n",relevance:0},{className:"number",variants:[{begin:"[#$=]?0x[0-9a-f]+"},{begin:"[#$=]?0b[01]+"},{begin:"[#$=]\\d+"},{begin:"\\b\\d+"}],relevance:0},{className:"symbol",variants:[{begin:"^[ \\t]*[a-z_\\.\\$][a-z0-9_\\.\\$]+:"},{begin:"^[a-z_\\.\\$][a-z0-9_\\.\\$]+"},{begin:"[=#]\\w+"}],relevance:0}]}}}());hljs.registerLanguage("go",function(){"use strict";return function(e){var n={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{name:"Go",aliases:["golang"],keywords:n,illegal:">>|\.\.\.) /},i={className:"subst",begin:/\{/,end:/\}/,keywords:n,illegal:/#/},s={begin:/\{\{/,relevance:0},r={className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/(u|b)?r?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(fr|rf|f)'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(fr|rf|f)"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,relevance:10},{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},{begin:/(fr|rf|f)'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,s,i]},{begin:/(fr|rf|f)"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,i]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},l={className:"number",relevance:0,variants:[{begin:e.BINARY_NUMBER_RE+"[lLjJ]?"},{begin:"\\b(0o[0-7]+)[lLjJ]?"},{begin:e.C_NUMBER_RE+"[lLjJ]?"}]},t={className:"params",variants:[{begin:/\(\s*\)/,skip:!0,className:null},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:["self",a,l,r,e.HASH_COMMENT_MODE]}]};return i.contains=[r,l,a],{name:"Python",aliases:["py","gyp","ipython"],keywords:n,illegal:/(<\/|->|\?)|=>/,contains:[a,l,{beginKeywords:"if",relevance:0},r,e.HASH_COMMENT_MODE,{variants:[{className:"function",beginKeywords:"def"},{className:"class",beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/,contains:[e.UNDERSCORE_TITLE_MODE,t,{begin:/->/,endsWithParent:!0,keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,end:/$/},{begin:/\b(print|exec)\(/}]}}}());hljs.registerLanguage("shell",function(){"use strict";return function(s){return{name:"Shell Session",aliases:["console"],contains:[{className:"meta",begin:"^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]",starts:{end:"$",subLanguage:"bash"}}]}}}());hljs.registerLanguage("scala",function(){"use strict";return function(e){var n={className:"subst",variants:[{begin:"\\$[A-Za-z0-9_]+"},{begin:"\\${",end:"}"}]},a={className:"string",variants:[{begin:'"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:'"""',end:'"""',relevance:10},{begin:'[a-z]+"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE,n]},{className:"string",begin:'[a-z]+"""',end:'"""',contains:[n],relevance:10}]},s={className:"type",begin:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},t={className:"title",begin:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,relevance:0},i={className:"class",beginKeywords:"class object trait type",end:/[:={\[\n;]/,excludeEnd:!0,contains:[{beginKeywords:"extends with",relevance:10},{begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},t]},l={className:"function",beginKeywords:"def",end:/[:={\[(\n;]/,excludeEnd:!0,contains:[t]};return{name:"Scala",keywords:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,{className:"symbol",begin:"'\\w[\\w\\d_]*(?!')"},s,l,i,e.C_NUMBER_MODE,{className:"meta",begin:"@[A-Za-z]+"}]}}}());hljs.registerLanguage("julia",function(){"use strict";return function(e){var r="[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF]*",t={$pattern:r,keyword:"in isa where baremodule begin break catch ccall const continue do else elseif end export false finally for function global if import importall let local macro module quote return true try using while type immutable abstract bitstype typealias ",literal:"true false ARGS C_NULL DevNull ENDIAN_BOM ENV I Inf Inf16 Inf32 Inf64 InsertionSort JULIA_HOME LOAD_PATH MergeSort NaN NaN16 NaN32 NaN64 PROGRAM_FILE QuickSort RoundDown RoundFromZero RoundNearest RoundNearestTiesAway RoundNearestTiesUp RoundToZero RoundUp STDERR STDIN STDOUT VERSION catalan e|0 eu|0 eulergamma golden im nothing pi γ π φ ",built_in:"ANY AbstractArray AbstractChannel AbstractFloat AbstractMatrix AbstractRNG AbstractSerializer AbstractSet AbstractSparseArray AbstractSparseMatrix AbstractSparseVector AbstractString AbstractUnitRange AbstractVecOrMat AbstractVector Any ArgumentError Array AssertionError Associative Base64DecodePipe Base64EncodePipe Bidiagonal BigFloat BigInt BitArray BitMatrix BitVector Bool BoundsError BufferStream CachingPool CapturedException CartesianIndex CartesianRange Cchar Cdouble Cfloat Channel Char Cint Cintmax_t Clong Clonglong ClusterManager Cmd CodeInfo Colon Complex Complex128 Complex32 Complex64 CompositeException Condition ConjArray ConjMatrix ConjVector Cptrdiff_t Cshort Csize_t Cssize_t Cstring Cuchar Cuint Cuintmax_t Culong Culonglong Cushort Cwchar_t Cwstring DataType Date DateFormat DateTime DenseArray DenseMatrix DenseVecOrMat DenseVector Diagonal Dict DimensionMismatch Dims DirectIndexString Display DivideError DomainError EOFError EachLine Enum Enumerate ErrorException Exception ExponentialBackOff Expr Factorization FileMonitor Float16 Float32 Float64 Function Future GlobalRef GotoNode HTML Hermitian IO IOBuffer IOContext IOStream IPAddr IPv4 IPv6 IndexCartesian IndexLinear IndexStyle InexactError InitError Int Int128 Int16 Int32 Int64 Int8 IntSet Integer InterruptException InvalidStateException Irrational KeyError LabelNode LinSpace LineNumberNode LoadError LowerTriangular MIME Matrix MersenneTwister Method MethodError MethodTable Module NTuple NewvarNode NullException Nullable Number ObjectIdDict OrdinalRange OutOfMemoryError OverflowError Pair ParseError PartialQuickSort PermutedDimsArray Pipe PollingFileWatcher ProcessExitedException Ptr QuoteNode RandomDevice Range RangeIndex Rational RawFD ReadOnlyMemoryError Real ReentrantLock Ref Regex RegexMatch RemoteChannel RemoteException RevString RoundingMode RowVector SSAValue SegmentationFault SerializationState Set SharedArray SharedMatrix SharedVector Signed SimpleVector Slot SlotNumber SparseMatrixCSC SparseVector StackFrame StackOverflowError StackTrace StepRange StepRangeLen StridedArray StridedMatrix StridedVecOrMat StridedVector String SubArray SubString SymTridiagonal Symbol Symmetric SystemError TCPSocket Task Text TextDisplay Timer Tridiagonal Tuple Type TypeError TypeMapEntry TypeMapLevel TypeName TypeVar TypedSlot UDPSocket UInt UInt128 UInt16 UInt32 UInt64 UInt8 UndefRefError UndefVarError UnicodeError UniformScaling Union UnionAll UnitRange Unsigned UpperTriangular Val Vararg VecElement VecOrMat Vector VersionNumber Void WeakKeyDict WeakRef WorkerConfig WorkerPool "},a={keywords:t,illegal:/<\//},n={className:"subst",begin:/\$\(/,end:/\)/,keywords:t},o={className:"variable",begin:"\\$"+r},i={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],variants:[{begin:/\w*"""/,end:/"""\w*/,relevance:10},{begin:/\w*"/,end:/"\w*/}]},l={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],begin:"`",end:"`"},s={className:"meta",begin:"@"+r};return a.name="Julia",a.contains=[{className:"number",begin:/(\b0x[\d_]*(\.[\d_]*)?|0x\.\d[\d_]*)p[-+]?\d+|\b0[box][a-fA-F0-9][a-fA-F0-9_]*|(\b\d[\d_]*(\.[\d_]*)?|\.\d[\d_]*)([eEfF][-+]?\d+)?/,relevance:0},{className:"string",begin:/'(.|\\[xXuU][a-zA-Z0-9]+)'/},i,l,s,{className:"comment",variants:[{begin:"#=",end:"=#",relevance:10},{begin:"#",end:"$"}]},e.HASH_COMMENT_MODE,{className:"keyword",begin:"\\b(((abstract|primitive)\\s+)type|(mutable\\s+)?struct)\\b"},{begin:/<:/}],n.contains=a.contains,a}}());hljs.registerLanguage("php-template",function(){"use strict";return function(n){return{name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},n.inherit(n.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),n.inherit(n.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}}());hljs.registerLanguage("scss",function(){"use strict";return function(e){var t={className:"variable",begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},i={className:"number",begin:"#[0-9A-Fa-f]+"};return e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{name:"SCSS",case_insensitive:!0,illegal:"[=/|']",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:"\\#[A-Za-z0-9_-]+",relevance:0},{className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0},{className:"selector-attr",begin:"\\[",end:"\\]",illegal:"$"},{className:"selector-tag",begin:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{className:"selector-pseudo",begin:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{className:"selector-pseudo",begin:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},t,{className:"attribute",begin:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",illegal:"[^\\s]"},{begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{begin:":",end:";",contains:[t,i,e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"meta",begin:"!important"}]},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",returnBegin:!0,keywords:"and or not only",contains:[{begin:"@[a-z-]+",className:"keyword"},t,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,i,e.CSS_NUMBER_MODE]}]}}}());hljs.registerLanguage("r",function(){"use strict";return function(e){var n="([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*";return{name:"R",contains:[e.HASH_COMMENT_MODE,{begin:n,keywords:{$pattern:n,keyword:"function if in break next repeat else for return switch while try tryCatch stop warning require library attach detach source setMethod setGeneric setGroupGeneric setClass ...",literal:"NULL NA TRUE FALSE T F Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10"},relevance:0},{className:"number",begin:"0[xX][0-9a-fA-F]+[Li]?\\b",relevance:0},{className:"number",begin:"\\d+(?:[eE][+\\-]?\\d*)?L\\b",relevance:0},{className:"number",begin:"\\d+\\.(?!\\d)(?:i\\b)?",relevance:0},{className:"number",begin:"\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{className:"number",begin:"\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{begin:"`",end:"`",relevance:0},{className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:'"',end:'"'},{begin:"'",end:"'"}]}]}}}());hljs.registerLanguage("sql",function(){"use strict";return function(e){var t=e.COMMENT("--","$");return{name:"SQL",case_insensitive:!0,illegal:/[<>{}*]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",end:/;/,endsWithParent:!0,keywords:{$pattern:/[\w\.]+/,keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},contains:[{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{className:"string",begin:'"',end:'"',contains:[{begin:'""'}]},{className:"string",begin:"`",end:"`"},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]},e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]}}}());hljs.registerLanguage("c",function(){"use strict";return function(e){var n=e.getLanguage("c-like").rawDefinition();return n.name="C",n.aliases=["c","h"],n}}());hljs.registerLanguage("json",function(){"use strict";return function(n){var e={literal:"true false null"},i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],t=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],a={end:",",endsWithParent:!0,excludeEnd:!0,contains:t,keywords:e},l={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(a,{begin:/:/})].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(a)],illegal:"\\S"};return t.push(l,s),i.forEach((function(n){t.push(n)})),{name:"JSON",contains:t,keywords:e,illegal:"\\S"}}}());hljs.registerLanguage("python-repl",function(){"use strict";return function(n){return{aliases:["pycon"],contains:[{className:"meta",starts:{end:/ |$/,starts:{end:"$",subLanguage:"python"}},variants:[{begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}}}());hljs.registerLanguage("markdown",function(){"use strict";return function(n){const e={begin:"<",end:">",subLanguage:"xml",relevance:0},a={begin:"\\[.+?\\][\\(\\[].*?[\\)\\]]",returnBegin:!0,contains:[{className:"string",begin:"\\[",end:"\\]",excludeBegin:!0,returnEnd:!0,relevance:0},{className:"link",begin:"\\]\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0},{className:"symbol",begin:"\\]\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0}],relevance:10},i={className:"strong",contains:[],variants:[{begin:/_{2}/,end:/_{2}/},{begin:/\*{2}/,end:/\*{2}/}]},s={className:"emphasis",contains:[],variants:[{begin:/\*(?!\*)/,end:/\*/},{begin:/_(?!_)/,end:/_/,relevance:0}]};i.contains.push(s),s.contains.push(i);var c=[e,a];return i.contains=i.contains.concat(c),s.contains=s.contains.concat(c),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:c=c.concat(i,s)},{begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",contains:c}]}]},e,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)",end:"\\s+",excludeEnd:!0},i,s,{className:"quote",begin:"^>\\s+",contains:c,end:"$"},{className:"code",variants:[{begin:"(`{3,})(.|\\n)*?\\1`*[ ]*"},{begin:"(~{3,})(.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{begin:"^[-\\*]{3,}",end:"$"},a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}}());hljs.registerLanguage("javascript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function s(e){return r("(?=",e,")")}function r(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(t){var i="[A-Za-z$_][0-9A-Za-z$_]*",c={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/},o={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.join(" "),literal:n.join(" "),built_in:a.join(" ")},l={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:t.C_NUMBER_RE+"n?"}],relevance:0},E={className:"subst",begin:"\\$\\{",end:"\\}",keywords:o,contains:[]},d={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"xml"}},g={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"css"}},u={className:"string",begin:"`",end:"`",contains:[t.BACKSLASH_ESCAPE,E]};E.contains=[t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,l,t.REGEXP_MODE];var b=E.contains.concat([{begin:/\(/,end:/\)/,contains:["self"].concat(E.contains,[t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE])},t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE]),_={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:b};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:o,contains:[t.SHEBANG({binary:"node",relevance:5}),{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,t.C_LINE_COMMENT_MODE,t.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",end:"\\}",relevance:0},{className:"variable",begin:i+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),t.C_BLOCK_COMMENT_MODE,l,{begin:r(/[{,\n]\s*/,s(r(/(((\/\/.*)|(\/\*(.|\n)*\*\/))\s*)*/,i+"\\s*:"))),relevance:0,contains:[{className:"attr",begin:i+s("\\s*:"),relevance:0}]},{begin:"("+t.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[t.C_LINE_COMMENT_MODE,t.C_BLOCK_COMMENT_MODE,t.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+t.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:t.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:o,contains:b}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:"<>",end:""},{begin:c.begin,end:c.end}],subLanguage:"xml",contains:[{begin:c.begin,end:c.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[t.inherit(t.TITLE_MODE,{begin:i}),_],illegal:/\[|%/},{begin:/\$[(.]/},t.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},t.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0},{begin:"(get|set)\\s+(?="+i+"\\()",end:/{/,keywords:"get set",contains:[t.inherit(t.TITLE_MODE,{begin:i}),{begin:/\(\)/},_]}],illegal:/#(?!!)/}}}());hljs.registerLanguage("typescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.concat(["type","namespace","typedef","interface","public","private","protected","implements","declare","abstract","readonly"]).join(" "),literal:n.join(" "),built_in:a.concat(["any","void","number","boolean","string","object","never","enum"]).join(" ")},s={className:"meta",begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},i={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:r.C_NUMBER_RE+"n?"}],relevance:0},o={className:"subst",begin:"\\$\\{",end:"\\}",keywords:t,contains:[]},c={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"xml"}},l={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"css"}},E={className:"string",begin:"`",end:"`",contains:[r.BACKSLASH_ESCAPE,o]};o.contains=[r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,i,r.REGEXP_MODE];var d={begin:"\\(",end:/\)/,keywords:t,contains:["self",r.QUOTE_STRING_MODE,r.APOS_STRING_MODE,r.NUMBER_MODE]},u={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,s,d]};return{name:"TypeScript",aliases:["ts"],keywords:t,contains:[r.SHEBANG(),{className:"meta",begin:/^\s*['"]use strict['"]/},r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,i,{begin:"("+r.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,r.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+r.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:r.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:d.contains}]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[\{;]/,excludeEnd:!0,keywords:t,contains:["self",r.inherit(r.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),u],illegal:/%/,relevance:0},{beginKeywords:"constructor",end:/[\{;]/,excludeEnd:!0,contains:["self",u]},{begin:/module\./,keywords:{built_in:"module"},relevance:0},{beginKeywords:"module",end:/\{/,excludeEnd:!0},{beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:"interface extends"},{begin:/\$[(.]/},{begin:"\\."+r.IDENT_RE,relevance:0},s,d]}}}());hljs.registerLanguage("plaintext",function(){"use strict";return function(t){return{name:"Plain text",aliases:["text","txt"],disableAutodetect:!0}}}());hljs.registerLanguage("less",function(){"use strict";return function(e){var n="([\\w-]+|@{[\\w-]+})",a=[],s=[],t=function(e){return{className:"string",begin:"~?"+e+".*?"+e}},r=function(e,n,a){return{className:e,begin:n,relevance:a}},i={begin:"\\(",end:"\\)",contains:s,relevance:0};s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t("'"),t('"'),e.CSS_NUMBER_MODE,{begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",excludeEnd:!0}},r("number","#[0-9A-Fa-f]+\\b"),i,r("variable","@@?[\\w-]+",10),r("variable","@{[\\w-]+}"),r("built_in","~?`[^`]*?`"),{className:"attribute",begin:"[\\w-]+\\s*:",end:":",returnBegin:!0,excludeEnd:!0},{className:"meta",begin:"!important"});var c=s.concat({begin:"{",end:"}",contains:a}),l={beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not"}].concat(s)},o={begin:n+"\\s*:",returnBegin:!0,end:"[;}]",relevance:0,contains:[{className:"attribute",begin:n,end:":",excludeEnd:!0,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s}}]},g={className:"keyword",begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{end:"[;{}]",returnEnd:!0,contains:s,relevance:0}},d={className:"variable",variants:[{begin:"@[\\w-]+\\s*:",relevance:15},{begin:"@[\\w-]+"}],starts:{end:"[;}]",returnEnd:!0,contains:c}},b={variants:[{begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:n,end:"{"}],returnBegin:!0,returnEnd:!0,illegal:"[<='$\"]",relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,l,r("keyword","all\\b"),r("variable","@{[\\w-]+}"),r("selector-tag",n+"%?",0),r("selector-id","#"+n),r("selector-class","\\."+n,0),r("selector-tag","&",0),{className:"selector-attr",begin:"\\[",end:"\\]"},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"\\(",end:"\\)",contains:c},{begin:"!important"}]};return a.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,g,d,o,b),{name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:a}}}());hljs.registerLanguage("lua",function(){"use strict";return function(e){var t={begin:"\\[=*\\[",end:"\\]=*\\]",contains:["self"]},a=[e.COMMENT("--(?!\\[=*\\[)","$"),e.COMMENT("--\\[=*\\[","\\]=*\\]",{contains:[t],relevance:10})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE,literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},contains:a.concat([{className:"function",beginKeywords:"function",end:"\\)",contains:[e.inherit(e.TITLE_MODE,{begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params",begin:"\\(",endsWithParent:!0,contains:a}].concat(a)},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string",begin:"\\[=*\\[",end:"\\]=*\\]",contains:[t],relevance:5}])}}}());hljs.registerLanguage("solidity",(()=>{"use strict";function e(){try{return!0}catch(e){return!1}}var a=/-?(\b0[xX]([a-fA-F0-9]_?)*[a-fA-F0-9]|(\b[1-9](_?\d)*(\.((\d_?)*\d)?)?|\.\d(_?\d)*)([eE][-+]?\d(_?\d)*)?|\b0)(?!\w|\$)/;e()&&(a=a.source.replace(/\\b/g,"(?{var a=r(e),o=l(e),c=/[A-Za-z_$][A-Za-z_$0-9.]*/,d=e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/,lexemes:c,keywords:n}),u={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,lexemes:c,keywords:n,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,o,s]},_={className:"operator",begin:/:=|->/};return{keywords:n,lexemes:c,contains:[a,o,i,t,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,_,{className:"function",lexemes:c,beginKeywords:"function",end:"{",excludeEnd:!0,contains:[d,u,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,_]}]}},solAposStringMode:r,solQuoteStringMode:l,HEX_APOS_STRING_MODE:i,HEX_QUOTE_STRING_MODE:t,SOL_NUMBER:s,isNegativeLookbehindAvailable:e};const{baseAssembly:c,solAposStringMode:d,solQuoteStringMode:u,HEX_APOS_STRING_MODE:_,HEX_QUOTE_STRING_MODE:m,SOL_NUMBER:b,isNegativeLookbehindAvailable:g}=o;return e=>{for(var a=d(e),s=u(e),n=[],i=0;i<32;i++)n[i]=i+1;var t=n.map((e=>8*e)),r=[];for(i=0;i<=80;i++)r[i]=i;var l=n.map((e=>"bytes"+e)).join(" ")+" ",o=t.map((e=>"uint"+e)).join(" ")+" ",E=t.map((e=>"int"+e)).join(" ")+" ",M=[].concat.apply([],t.map((e=>r.map((a=>e+"x"+a))))),p={keyword:"var bool string int uint "+E+o+"byte bytes "+l+"fixed ufixed "+M.map((e=>"fixed"+e)).join(" ")+" "+M.map((e=>"ufixed"+e)).join(" ")+" enum struct mapping address new delete if else for while continue break return throw emit try catch revert unchecked _ function modifier event constructor fallback receive error virtual override constant immutable anonymous indexed storage memory calldata external public internal payable pure view private returns import from as using global pragma contract interface library is abstract type assembly",literal:"true false wei gwei szabo finney ether seconds minutes hours days weeks years",built_in:"self this super selfdestruct suicide now msg block tx abi blockhash gasleft assert require Error Panic sha3 sha256 keccak256 ripemd160 ecrecover addmod mulmod log0 log1 log2 log3 log4"},O={className:"operator",begin:/[+\-!~*\/%<>&^|=]/},C=/[A-Za-z_$][A-Za-z_$0-9]*/,N={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,lexemes:C,keywords:p,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,s,b,"self"]},f={begin:/\.\s*/,end:/[^A-Za-z0-9$_\.]/,excludeBegin:!0,excludeEnd:!0,keywords:{built_in:"gas value selector address length push pop send transfer call callcode delegatecall staticcall balance code codehash wrap unwrap name creationCode runtimeCode interfaceId min max"},relevance:2},y=e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/,lexemes:C,keywords:p}),w={className:"built_in",begin:(g()?"(?{"use strict";function e(){try{return!0}catch(e){return!1}}var a=/-?(\b0[xX]([a-fA-F0-9]_?)*[a-fA-F0-9]|(\b[1-9](_?\d)*(\.((\d_?)*\d)?)?|\.\d(_?\d)*)([eE][-+]?\d(_?\d)*)?|\b0)(?!\w|\$)/;e()&&(a=a.source.replace(/\\b/g,"(?{var a=d(e),n=r(e),o=/[A-Za-z_$][A-Za-z_$0-9.]*/,c=e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/,lexemes:o,keywords:t}),u={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,lexemes:o,keywords:t,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,n,s]},p={className:"operator",begin:/:=|->/};return{keywords:t,lexemes:o,contains:[a,n,i,l,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,p,{className:"function",lexemes:o,beginKeywords:"function",end:"{",excludeEnd:!0,contains:[c,u,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,p]}]}},solAposStringMode:d,solQuoteStringMode:r,HEX_APOS_STRING_MODE:i,HEX_QUOTE_STRING_MODE:l,SOL_NUMBER:s,isNegativeLookbehindAvailable:e};const{SOL_ASSEMBLY_KEYWORDS:o,baseAssembly:c,isNegativeLookbehindAvailable:u}=n;return e=>{var a={keyword:o.keyword+" object code data",built_in:o.built_in+" datasize dataoffset datacopy setimmutable loadimmutable linkersymbol memoryguard",literal:o.literal},s=/\bverbatim_[1-9]?[0-9]i_[1-9]?[0-9]o\b(?!\$)/;u()&&(s=s.source.replace(/\\b/,"(? 1 { // Format the string and add it to the list of logs consoleLogString = fmt.Sprintf(inputValues[0].(string), inputValues[1:]...) } else { diff --git a/fuzzing/valuegeneration/abi_values.go b/fuzzing/valuegeneration/abi_values.go index 616c8305..c0610efc 100644 --- a/fuzzing/valuegeneration/abi_values.go +++ b/fuzzing/valuegeneration/abi_values.go @@ -675,7 +675,7 @@ func DecodeJSONArgumentsFromMap(inputs abi.Arguments, values map[string]any, dep for i, input := range inputs { value, ok := values[input.Name] if !ok { - err := fmt.Errorf("constructor argument not provided for: name: %v", input.Name) + err := fmt.Errorf("value not not provided for argument: name: %v", input.Name) return nil, err } arg, err := decodeJSONArgument(&input.Type, value, deployedContractAddr) From 69a997928e3248d34e139ddb3c1f996bc5abba0f Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Mon, 24 Jun 2024 17:08:01 -0500 Subject: [PATCH 47/55] fix: use the shrunken abi values as the msg's data (#374) --- fuzzing/fuzzer_worker.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fuzzing/fuzzer_worker.go b/fuzzing/fuzzer_worker.go index 7ee4e93a..08bd5f6b 100644 --- a/fuzzing/fuzzer_worker.go +++ b/fuzzing/fuzzer_worker.go @@ -10,6 +10,7 @@ import ( fuzzerTypes "github.com/crytic/medusa/fuzzing/contracts" "github.com/crytic/medusa/fuzzing/coverage" "github.com/crytic/medusa/fuzzing/valuegeneration" + "github.com/crytic/medusa/logging" "github.com/crytic/medusa/utils" "github.com/ethereum/go-ethereum/common" "golang.org/x/exp/maps" @@ -466,6 +467,13 @@ func (fw *FuzzerWorker) shrinkCallSequence(callSequence calls.CallSequence, shri abiValuesMsgData.InputValues[j] = mutatedInput } + // Re-encode the ABI values as calldata. + abiData, err := abiValuesMsgData.Pack() + if err != nil { + logging.GlobalLogger.Panic("Failed to pack call message ABI values", err) + } + possibleShrunkSequence[i].Call.Data = abiData + // Test the shrunken sequence. validShrunkSequence, err := fw.testShrunkenCallSequence(possibleShrunkSequence, shrinkRequest) shrinkIteration++ From 5e5d0e034414bbc41fec7ad6198be62f6b7fcd18 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 2 Jul 2024 10:24:43 -0500 Subject: [PATCH 48/55] chore: fix typos (#387) --- fuzzing/config/config.go | 2 +- fuzzing/test_case_optimization.go | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/fuzzing/config/config.go b/fuzzing/config/config.go index 7ccae530..b37a583c 100644 --- a/fuzzing/config/config.go +++ b/fuzzing/config/config.go @@ -296,7 +296,7 @@ func (p *ProjectConfig) Validate() error { // Verify that the sequence length is a positive number if p.Fuzzing.CallSequenceLength <= 0 { - return errors.New("project configuration must specify a positive number for the transaction sequence lengt") + return errors.New("project configuration must specify a positive number for the transaction sequence length") } // Verify the worker reset limit is a positive number diff --git a/fuzzing/test_case_optimization.go b/fuzzing/test_case_optimization.go index 8b3f4e41..7abf0b9f 100644 --- a/fuzzing/test_case_optimization.go +++ b/fuzzing/test_case_optimization.go @@ -2,15 +2,16 @@ package fuzzing import ( "fmt" + "math/big" + "strings" + "sync" + "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/fuzzing/contracts" "github.com/crytic/medusa/fuzzing/executiontracer" "github.com/crytic/medusa/logging" "github.com/crytic/medusa/logging/colors" "github.com/ethereum/go-ethereum/accounts/abi" - "math/big" - "strings" - "sync" ) // OptimizationTestCase describes a test being run by a OptimizationTestCaseProvider. @@ -47,7 +48,7 @@ func (t *OptimizationTestCase) Name() string { return fmt.Sprintf("Optimization Test: %s.%s", t.targetContract.Name(), t.targetMethod.Sig) } -// LogMessage obtains a buffer that represents the result of the AssertionTestCase. This buffer can be passed to a logger for +// LogMessage obtains a buffer that represents the result of the OptimizationTestCase. This buffer can be passed to a logger for // console or file logging. func (t *OptimizationTestCase) LogMessage() *logging.LogBuffer { buffer := logging.NewLogBuffer() From 827dbed2c55224fdb82d7fc77a376858d80a7ae1 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 2 Jul 2024 13:33:00 -0500 Subject: [PATCH 49/55] feat: call constant methods occasionally and allow assertion testing them (#363) * feat: call constant methods ocassionally and allow assertion testing them * add test --- .../project_configuration/testing_config.md | 2 - fuzzing/fuzzer.go | 8 +- fuzzing/fuzzer_test.go | 5 +- fuzzing/fuzzer_worker.go | 33 ++-- fuzzing/fuzzer_worker_sequence_generator.go | 13 +- .../assertions/assert_constant_method.sol | 148 ++++++++++++++++++ 6 files changed, 190 insertions(+), 19 deletions(-) create mode 100644 fuzzing/testdata/contracts/assertions/assert_constant_method.sol diff --git a/docs/src/project_configuration/testing_config.md b/docs/src/project_configuration/testing_config.md index 3adfe08c..98047e72 100644 --- a/docs/src/project_configuration/testing_config.md +++ b/docs/src/project_configuration/testing_config.md @@ -75,8 +75,6 @@ contract MyContract { - **Type**: Boolean - **Description**: Whether `pure` / `view` functions should be tested for assertion failures. - > 🚩 Fuzzing `pure` and `view` functions is not currently implemented. Thus, enabling this option to `true` does not - > update the fuzzer's behavior. - **Default**: `false` ### `panicCodeConfig` diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index e2245164..c3b7e9f2 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "github.com/crytic/medusa/fuzzing/executiontracer" "math/big" "math/rand" "os" @@ -16,6 +15,8 @@ import ( "sync" "time" + "github.com/crytic/medusa/fuzzing/executiontracer" + "github.com/crytic/medusa/fuzzing/coverage" "github.com/crytic/medusa/logging" "github.com/crytic/medusa/logging/colors" @@ -707,7 +708,10 @@ func (f *Fuzzer) Start() error { // If StopOnNoTests is true and there are no test cases, then throw an error if f.config.Fuzzing.Testing.StopOnNoTests && len(f.testCases) == 0 { - err = fmt.Errorf("no tests of any kind (assertion/property/optimization/custom) have been identified for fuzzing") + err = fmt.Errorf("no assertion, property, optimization, or custom tests were found to fuzz") + if !f.config.Fuzzing.Testing.AssertionTesting.TestViewMethods { + err = fmt.Errorf("no assertion, property, optimization, or custom tests were found to fuzz and testing view methods is disabled") + } f.logger.Error("Failed to start fuzzer", err) return err } diff --git a/fuzzing/fuzzer_test.go b/fuzzing/fuzzer_test.go index 860ebd93..ce2aa536 100644 --- a/fuzzing/fuzzer_test.go +++ b/fuzzing/fuzzer_test.go @@ -2,11 +2,12 @@ package fuzzing import ( "encoding/hex" - "github.com/crytic/medusa/fuzzing/executiontracer" "math/big" "math/rand" "testing" + "github.com/crytic/medusa/fuzzing/executiontracer" + "github.com/crytic/medusa/chain" "github.com/crytic/medusa/events" "github.com/crytic/medusa/fuzzing/calls" @@ -73,6 +74,7 @@ func TestAssertionMode(t *testing.T) { "testdata/contracts/assertions/assert_outofbounds_array_access.sol", "testdata/contracts/assertions/assert_allocate_too_much_memory.sol", "testdata/contracts/assertions/assert_call_uninitialized_variable.sol", + "testdata/contracts/assertions/assert_constant_method.sol", } for _, filePath := range filePaths { runFuzzerTest(t, &fuzzerSolcFileTest{ @@ -88,6 +90,7 @@ func TestAssertionMode(t *testing.T) { config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnIncorrectStorageAccess = true config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnOutOfBoundsArrayAccess = true config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnPopEmptyArray = true + config.Fuzzing.Testing.AssertionTesting.TestViewMethods = true config.Fuzzing.Testing.PropertyTesting.Enabled = false config.Fuzzing.Testing.OptimizationTesting.Enabled = false }, diff --git a/fuzzing/fuzzer_worker.go b/fuzzing/fuzzer_worker.go index 08bd5f6b..1413dd7e 100644 --- a/fuzzing/fuzzer_worker.go +++ b/fuzzing/fuzzer_worker.go @@ -12,6 +12,7 @@ import ( "github.com/crytic/medusa/fuzzing/valuegeneration" "github.com/crytic/medusa/logging" "github.com/crytic/medusa/utils" + "github.com/crytic/medusa/utils/randomutils" "github.com/ethereum/go-ethereum/common" "golang.org/x/exp/maps" ) @@ -35,11 +36,18 @@ type FuzzerWorker struct { // deployedContracts describes a mapping of deployed contractDefinitions and the addresses they were deployed to. deployedContracts map[common.Address]*fuzzerTypes.Contract + // stateChangingMethods is a list of contract functions which are suspected of changing contract state // (non-read-only). A sequence of calls is generated by the FuzzerWorker, targeting stateChangingMethods // before executing tests. stateChangingMethods []fuzzerTypes.DeployedContractMethod + // pureMethods is a list of contract functions which are side-effect free with respect to the EVM (view and/or pure in terms of Solidity mutability). + pureMethods []fuzzerTypes.DeployedContractMethod + + // methodChooser uses a weighted selection algorithm to choose a method to call, prioritizing state changing methods over pure ones. + methodChooser *randomutils.WeightedRandomChooser[fuzzerTypes.DeployedContractMethod] + // randomProvider provides random data as inputs to decisions throughout the worker. randomProvider *rand.Rand // sequenceGenerator creates entirely new or mutated call sequences based on corpus call sequences, for use in @@ -86,6 +94,7 @@ func newFuzzerWorker(fuzzer *Fuzzer, workerIndex int, randomProvider *rand.Rand) coverageTracer: nil, randomProvider: randomProvider, valueSet: valueSet, + methodChooser: randomutils.NewWeightedRandomChooser[fuzzerTypes.DeployedContractMethod](), } worker.sequenceGenerator = NewCallSequenceGenerator(worker, callSequenceGenConfig) worker.shrinkingValueMutator = shrinkingValueMutator @@ -175,8 +184,8 @@ func (fw *FuzzerWorker) onChainContractDeploymentAddedEvent(event chain.Contract // Set our deployed contract address in our deployed contract lookup, so we can reference it later. fw.deployedContracts[event.Contract.Address] = matchedDefinition - // Update our state changing methods - fw.updateStateChangingMethods() + // Update our methods + fw.updateMethods() // Emit an event indicating the worker detected a new contract deployment on its chain. err := fw.Events.ContractAdded.Publish(FuzzerWorkerContractAddedEvent{ @@ -206,8 +215,8 @@ func (fw *FuzzerWorker) onChainContractDeploymentRemovedEvent(event chain.Contra // Remove the contract from our deployed contracts mapping the worker maintains. delete(fw.deployedContracts, event.Contract.Address) - // Update our state changing methods - fw.updateStateChangingMethods() + // Update our methods + fw.updateMethods() // Emit an event indicating the worker detected the removal of a previously deployed contract on its chain. err := fw.Events.ContractDeleted.Publish(FuzzerWorkerContractDeletedEvent{ @@ -221,19 +230,25 @@ func (fw *FuzzerWorker) onChainContractDeploymentRemovedEvent(event chain.Contra return nil } -// updateStateChangingMethods updates the list of state changing methods used by the worker by re-evaluating them +// updateMethods updates the list of methods used by the worker by re-evaluating them // from the deployedContracts lookup. -func (fw *FuzzerWorker) updateStateChangingMethods() { - // Clear our list of state changing methods +func (fw *FuzzerWorker) updateMethods() { + // Clear our list of methods fw.stateChangingMethods = make([]fuzzerTypes.DeployedContractMethod, 0) + fw.pureMethods = make([]fuzzerTypes.DeployedContractMethod, 0) // Loop through each deployed contract for contractAddress, contractDefinition := range fw.deployedContracts { // If we deployed the contract, also enumerate property tests and state changing methods. for _, method := range contractDefinition.CompiledContract().Abi.Methods { - if !method.IsConstant() { - // Any non-constant method should be tracked as a state changing method. + // Any non-constant method should be tracked as a state changing method. + // We favor calling state changing methods over view methods. + if method.IsConstant() { + fw.pureMethods = append(fw.pureMethods, fuzzerTypes.DeployedContractMethod{Address: contractAddress, Contract: contractDefinition, Method: method}) + fw.methodChooser.AddChoices(randomutils.NewWeightedRandomChoice(fuzzerTypes.DeployedContractMethod{Address: contractAddress, Contract: contractDefinition, Method: method}, big.NewInt(1))) + } else { fw.stateChangingMethods = append(fw.stateChangingMethods, fuzzerTypes.DeployedContractMethod{Address: contractAddress, Contract: contractDefinition, Method: method}) + fw.methodChooser.AddChoices(randomutils.NewWeightedRandomChoice(fuzzerTypes.DeployedContractMethod{Address: contractAddress, Contract: contractDefinition, Method: method}, big.NewInt(100))) } } } diff --git a/fuzzing/fuzzer_worker_sequence_generator.go b/fuzzing/fuzzer_worker_sequence_generator.go index a931b04a..0ff65681 100644 --- a/fuzzing/fuzzer_worker_sequence_generator.go +++ b/fuzzing/fuzzer_worker_sequence_generator.go @@ -270,17 +270,20 @@ func (g *CallSequenceGenerator) PopSequenceElement() (*calls.CallSequenceElement return element, nil } -// generateNewElement generates a new call sequence element which targets a state changing method in a contract +// generateNewElement generates a new call sequence element which targets a method in a contract // deployed to the CallSequenceGenerator's parent FuzzerWorker chain, with fuzzed call data. // Returns the call sequence element, or an error if one was encountered. func (g *CallSequenceGenerator) generateNewElement() (*calls.CallSequenceElement, error) { - // Verify we have state changing methods to call - if len(g.worker.stateChangingMethods) == 0 { + // Verify we have state changing methods to call if we are not testing view/pure methods. + if len(g.worker.stateChangingMethods) == 0 && !g.worker.fuzzer.config.Fuzzing.Testing.AssertionTesting.TestViewMethods { return nil, fmt.Errorf("cannot generate fuzzed tx as there are no state changing methods to call") } - // Select a random method and sender - selectedMethod := &g.worker.stateChangingMethods[g.worker.randomProvider.Intn(len(g.worker.stateChangingMethods))] + selectedMethod, err := g.worker.methodChooser.Choose() + if err != nil { + return nil, err + } + selectedSender := g.worker.fuzzer.senders[g.worker.randomProvider.Intn(len(g.worker.fuzzer.senders))] // Generate fuzzed parameters for the function call diff --git a/fuzzing/testdata/contracts/assertions/assert_constant_method.sol b/fuzzing/testdata/contracts/assertions/assert_constant_method.sol new file mode 100644 index 00000000..0ddd2fdf --- /dev/null +++ b/fuzzing/testdata/contracts/assertions/assert_constant_method.sol @@ -0,0 +1,148 @@ +// An assertion failure in the constant method should be detected +// Contract updated to solc > 0.8.0 and taken from https://github.com/crytic/echidna/blob/5757f8c3c07d0248cbe1728506ff0f8daccbef12/tests/solidity/assert/fullmath.sol +library FullMath { + /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + /// @param a The multiplicand + /// @param b The multiplier + /// @param denominator The divisor + /// @return result The 256-bit result + /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv + function mulDiv( + uint256 a, + uint256 b, + uint256 denominator + ) internal pure returns (uint256 result) { + unchecked { + + + // 512-bit multiply [prod1 prod0] = a * b + // Compute the product mod 2**256 and mod 2**256 - 1 + // then use the Chinese Remainder Theorem to reconstruct + // the 512 bit result. The result is stored in two 256 + // variables such that product = prod1 * 2**256 + prod0 + uint256 prod0; // Least significant 256 bits of the product + uint256 prod1; // Most significant 256 bits of the product + assembly { + let mm := mulmod(a, b, not(0)) + prod0 := mul(a, b) + prod1 := sub(sub(mm, prod0), lt(mm, prod0)) + } + + // Handle non-overflow cases, 256 by 256 division + if (prod1 == 0) { + require(denominator > 0); + assembly { + result := div(prod0, denominator) + } + return result; + } + + // Make sure the result is less than 2**256. + // Also prevents denominator == 0 + require(denominator > prod1); + + /////////////////////////////////////////////// + // 512 by 256 division. + /////////////////////////////////////////////// + + // Make division exact by subtracting the remainder from [prod1 prod0] + // Compute remainder using mulmod + uint256 remainder; + assembly { + remainder := mulmod(a, b, denominator) + } + // Subtract 256 bit number from 512 bit number + assembly { + prod1 := sub(prod1, gt(remainder, prod0)) + prod0 := sub(prod0, remainder) + } + + // Factor powers of two out of denominator + // Compute largest power of two divisor of denominator. + // Always >= 1. + uint256 twos = (0 - denominator) & denominator; + // Divide denominator by power of two + assembly { + denominator := div(denominator, twos) + } + + // Divide [prod1 prod0] by the factors of two + assembly { + prod0 := div(prod0, twos) + } + // Shift in bits from prod1 into prod0. For this we need + // to flip `twos` such that it is 2**256 / twos. + // If twos is zero, then it becomes one + assembly { + twos := add(div(sub(0, twos), twos), 1) + } + prod0 |= prod1 * twos; + + // Invert denominator mod 2**256 + // Now that denominator is an odd number, it has an inverse + // modulo 2**256 such that denominator * inv = 1 mod 2**256. + // Compute the inverse by starting with a seed that is correct + // correct for four bits. That is, denominator * inv = 1 mod 2**4 + uint256 inv = (3 * denominator) ^ 2; + // Now use Newton-Raphson iteration to improve the precision. + // Thanks to Hensel's lifting lemma, this also works in modular + // arithmetic, doubling the correct bits in each step. + inv *= 2 - denominator * inv; // inverse mod 2**8 + inv *= 2 - denominator * inv; // inverse mod 2**16 + inv *= 2 - denominator * inv; // inverse mod 2**32 + inv *= 2 - denominator * inv; // inverse mod 2**64 + inv *= 2 - denominator * inv; // inverse mod 2**128 + inv *= 2 - denominator * inv; // inverse mod 2**256 + + // Because the division is now exact we can divide by multiplying + // with the modular inverse of denominator. This will give us the + // correct result modulo 2**256. Since the precoditions guarantee + // that the outcome is less than 2**256, this is the final result. + // We don't need to compute the high bits of the result and prod1 + // is no longer required. + result = prod0 * inv; + return result; + } + } + + /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0 + /// @param a The multiplicand + /// @param b The multiplier + /// @param denominator The divisor + /// @return result The 256-bit result + function mulDivRoundingUp( + uint256 a, + uint256 b, + uint256 denominator + ) internal pure returns (uint256 result) { + // BUG + unchecked { + return mulDiv(a, b, denominator) + (mulmod(a, b, denominator) > 0 ? 1 : 0); + } + } +} + +contract TestContract { + function checkMulDivRoundingUp( + uint256 x, + uint256 y, + uint256 d + ) external pure { + require(d > 0); + uint256 z = FullMath.mulDivRoundingUp(x, y, d); + if (x == 0 || y == 0) { + assert(z == 0); + return; + } + + // recompute x and y via mulDiv of the result of floor(x*y/d), should always be less than original inputs by < d + uint256 x2 = FullMath.mulDiv(z, d, y); + uint256 y2 = FullMath.mulDiv(z, d, x); + assert(x2 >= x); + assert(y2 >= y); + + assert(x2 - x < d); + assert(y2 - y < d); + } + fallback() external {} +} From 8320d6ec79235cb0c54363c65b01bf4cdc131f26 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 2 Jul 2024 13:44:10 -0500 Subject: [PATCH 50/55] fix: re-encode calldata in mutator and refactor (#380) * fix: re-encode calldata in mutator and refactor * panic on misuse --- fuzzing/calls/call_message.go | 22 ++++++++++++++++++++- fuzzing/fuzzer_worker.go | 9 ++------- fuzzing/fuzzer_worker_sequence_generator.go | 3 +++ 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/fuzzing/calls/call_message.go b/fuzzing/calls/call_message.go index 47dc0263..6b6dfa2f 100644 --- a/fuzzing/calls/call_message.go +++ b/fuzzing/calls/call_message.go @@ -1,6 +1,8 @@ package calls import ( + "math/big" + "github.com/crytic/medusa/chain" "github.com/crytic/medusa/logging" "github.com/ethereum/go-ethereum/common" @@ -8,7 +10,6 @@ import ( "github.com/ethereum/go-ethereum/core" coreTypes "github.com/ethereum/go-ethereum/core/types" "golang.org/x/exp/slices" - "math/big" ) // The following directives will be picked up by the `go generate` command to generate JSON marshaling code from @@ -126,6 +127,25 @@ func NewCallMessageWithAbiValueData(from common.Address, to *common.Address, non } } +// WithDataAbiValues resets the call message's data and ABI values, ensuring the values are in sync and +// reusing the other existing fields. +func (m *CallMessage) WithDataAbiValues(abiData *CallMessageDataAbiValues) { + if abiData == nil { + logging.GlobalLogger.Panic("Method ABI and data should always be defined") + } + + // Pack the ABI value data + var data []byte + var err error + data, err = abiData.Pack() + if err != nil { + logging.GlobalLogger.Panic("Failed to pack call message ABI values", err) + } + // Set our data and ABI values + m.DataAbiValues = abiData + m.Data = data +} + // FillFromTestChainProperties populates gas limit, price, nonce, and other fields automatically based on the worker's // underlying test chain properties if they are not yet set. func (m *CallMessage) FillFromTestChainProperties(chain *chain.TestChain) { diff --git a/fuzzing/fuzzer_worker.go b/fuzzing/fuzzer_worker.go index 1413dd7e..344063f8 100644 --- a/fuzzing/fuzzer_worker.go +++ b/fuzzing/fuzzer_worker.go @@ -10,7 +10,6 @@ import ( fuzzerTypes "github.com/crytic/medusa/fuzzing/contracts" "github.com/crytic/medusa/fuzzing/coverage" "github.com/crytic/medusa/fuzzing/valuegeneration" - "github.com/crytic/medusa/logging" "github.com/crytic/medusa/utils" "github.com/crytic/medusa/utils/randomutils" "github.com/ethereum/go-ethereum/common" @@ -482,12 +481,8 @@ func (fw *FuzzerWorker) shrinkCallSequence(callSequence calls.CallSequence, shri abiValuesMsgData.InputValues[j] = mutatedInput } - // Re-encode the ABI values as calldata. - abiData, err := abiValuesMsgData.Pack() - if err != nil { - logging.GlobalLogger.Panic("Failed to pack call message ABI values", err) - } - possibleShrunkSequence[i].Call.Data = abiData + // Re-encode the message's calldata + possibleShrunkSequence[i].Call.WithDataAbiValues(abiValuesMsgData) // Test the shrunken sequence. validShrunkSequence, err := fw.testShrunkenCallSequence(possibleShrunkSequence, shrinkRequest) diff --git a/fuzzing/fuzzer_worker_sequence_generator.go b/fuzzing/fuzzer_worker_sequence_generator.go index 0ff65681..edc3b224 100644 --- a/fuzzing/fuzzer_worker_sequence_generator.go +++ b/fuzzing/fuzzer_worker_sequence_generator.go @@ -460,5 +460,8 @@ func prefetchModifyCallFuncMutate(sequenceGenerator *CallSequenceGenerator, elem } abiValuesMsgData.InputValues[i] = mutatedInput } + // Re-encode the message's calldata + element.Call.WithDataAbiValues(abiValuesMsgData) + return nil } From 3259c27762c198ce171c97f7c318e86b0e3105c0 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 2 Jul 2024 14:10:19 -0500 Subject: [PATCH 51/55] excluding compilation platform fallsback to default (#362) * fix: excluding compilation platform fallsback to default * warn if coverage is disabled --------- Co-authored-by: anishnaik --- cmd/fuzz.go | 12 +++++++++--- fuzzing/config/config.go | 9 +++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/cmd/fuzz.go b/cmd/fuzz.go index 188f5d95..c9905b84 100644 --- a/cmd/fuzz.go +++ b/cmd/fuzz.go @@ -2,12 +2,13 @@ package cmd import ( "fmt" - "github.com/crytic/medusa/cmd/exitcodes" - "github.com/crytic/medusa/logging/colors" "os" "os/signal" "path/filepath" + "github.com/crytic/medusa/cmd/exitcodes" + "github.com/crytic/medusa/logging/colors" + "github.com/crytic/medusa/fuzzing" "github.com/crytic/medusa/fuzzing/config" "github.com/spf13/cobra" @@ -102,7 +103,8 @@ func cmdRunFuzz(cmd *cobra.Command, args []string) error { if existenceError == nil { // Try to read the configuration file and throw an error if something goes wrong cmdLogger.Info("Reading the configuration file at: ", colors.Bold, configPath, colors.Reset) - projectConfig, err = config.ReadProjectConfigFromFile(configPath) + // Use the default compilation platform if the config file doesn't specify one + projectConfig, err = config.ReadProjectConfigFromFile(configPath, DefaultCompilationPlatform) if err != nil { cmdLogger.Error("Failed to run the fuzz command", err) return err @@ -144,6 +146,10 @@ func cmdRunFuzz(cmd *cobra.Command, args []string) error { return err } + if !projectConfig.Fuzzing.CoverageEnabled { + cmdLogger.Warn("Disabling coverage may limit efficacy of fuzzing. Consider enabling coverage for better results.") + } + // Create our fuzzing fuzzer, fuzzErr := fuzzing.NewFuzzer(*projectConfig) if fuzzErr != nil { diff --git a/fuzzing/config/config.go b/fuzzing/config/config.go index b37a583c..25149e10 100644 --- a/fuzzing/config/config.go +++ b/fuzzing/config/config.go @@ -3,14 +3,15 @@ package config import ( "encoding/json" "errors" + "math/big" + "os" + "github.com/crytic/medusa/chain/config" "github.com/crytic/medusa/compilation" "github.com/crytic/medusa/logging" "github.com/crytic/medusa/utils" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/rs/zerolog" - "math/big" - "os" ) // The following directives will be picked up by the `go generate` command to generate JSON marshaling code from @@ -242,7 +243,7 @@ type FileLoggingConfig struct { // ReadProjectConfigFromFile reads a JSON-serialized ProjectConfig from a provided file path. // Returns the ProjectConfig if it succeeds, or an error if one occurs. -func ReadProjectConfigFromFile(path string) (*ProjectConfig, error) { +func ReadProjectConfigFromFile(path string, platform string) (*ProjectConfig, error) { // Read our project configuration file data b, err := os.ReadFile(path) if err != nil { @@ -250,7 +251,7 @@ func ReadProjectConfigFromFile(path string) (*ProjectConfig, error) { } // Parse the project configuration - projectConfig, err := GetDefaultProjectConfig("") + projectConfig, err := GetDefaultProjectConfig(platform) if err != nil { return nil, err } From 3cbe484d44dfed2fda0783541383b9780e19fbba Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 2 Jul 2024 14:21:00 -0500 Subject: [PATCH 52/55] feat: display test cases discovered by the fuzzer (#382) * feat: display test cases discovered by the fuzzer * check whether optimization test case has started --- fuzzing/fuzzer.go | 3 +++ fuzzing/test_case_optimization.go | 10 ++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index c3b7e9f2..f8d2f7c3 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -246,6 +246,9 @@ func (f *Fuzzer) RegisterTestCase(testCase TestCase) { f.testCasesLock.Lock() defer f.testCasesLock.Unlock() + // Display what is being tested + f.logger.Info(testCase.LogMessage().Elements()...) + // Append our test case to our list f.testCases = append(f.testCases, testCase) } diff --git a/fuzzing/test_case_optimization.go b/fuzzing/test_case_optimization.go index 7abf0b9f..3c785047 100644 --- a/fuzzing/test_case_optimization.go +++ b/fuzzing/test_case_optimization.go @@ -55,10 +55,12 @@ func (t *OptimizationTestCase) LogMessage() *logging.LogBuffer { // Note that optimization tests will always pass buffer.Append(colors.GreenBold, fmt.Sprintf("[%s] ", t.Status()), colors.Bold, t.Name(), colors.Reset, "\n") - buffer.Append(fmt.Sprintf("Test for method \"%s.%s\" resulted in the maximum value: ", t.targetContract.Name(), t.targetMethod.Sig)) - buffer.Append(colors.Bold, t.value, colors.Reset, "\n") - buffer.Append(colors.Bold, "[Call Sequence]", colors.Reset, "\n") - buffer.Append(t.CallSequence().Log().Elements()...) + if t.Status() != TestCaseStatusNotStarted { + buffer.Append(fmt.Sprintf("Test for method \"%s.%s\" resulted in the maximum value: ", t.targetContract.Name(), t.targetMethod.Sig)) + buffer.Append(colors.Bold, t.value, colors.Reset, "\n") + buffer.Append(colors.Bold, "[Call Sequence]", colors.Reset, "\n") + buffer.Append(t.CallSequence().Log().Elements()...) + } // If an execution trace is attached then add it to the message if t.optimizationTestTrace != nil { buffer.Append(colors.Bold, "[Optimization Test Execution Trace]", colors.Reset, "\n") From f37287f879fd26a7d5e082f3040a9407fad9109d Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Fri, 4 Aug 2023 17:20:59 +0200 Subject: [PATCH 53/55] fix json template, change htmlReportPath to corpus path --- fuzzing/coverage/report_generation.go | 3 +++ fuzzing/fuzzer.go | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/fuzzing/coverage/report_generation.go b/fuzzing/coverage/report_generation.go index 57232f1e..70e235ac 100644 --- a/fuzzing/coverage/report_generation.go +++ b/fuzzing/coverage/report_generation.go @@ -163,6 +163,9 @@ func exportHtmlCoverageReport(sourceAnalysis *SourceAnalysis, outputPath string) return fmt.Errorf("could not export report, failed to parse report template: %v", err) } + // Add the report file to the path + outputPath = filepath.Join(outputPath, "coverage_report.html") + // If the parent directory doesn't exist, create it. parentDirectory := filepath.Dir(outputPath) err = utils.MakeDirectory(parentDirectory) diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index f8d2f7c3..e983e3fa 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -7,7 +7,6 @@ import ( "math/big" "math/rand" "os" - "path/filepath" "runtime" "sort" "strconv" @@ -16,7 +15,6 @@ import ( "time" "github.com/crytic/medusa/fuzzing/executiontracer" - "github.com/crytic/medusa/fuzzing/coverage" "github.com/crytic/medusa/logging" "github.com/crytic/medusa/logging/colors" @@ -749,12 +747,18 @@ func (f *Fuzzer) Start() error { // Finally, generate our coverage report if we have set a valid corpus directory. if err == nil && f.config.Fuzzing.CorpusDirectory != "" { +<<<<<<< HEAD htmlReportPath := f.config.Fuzzing.HtmlReportFile jsonReportPath := f.config.Fuzzing.JsonReportFile corpusDir := f.config.Fuzzing.CorpusDirectory err = coverage.GenerateReport(f.compilations, f.corpus.CoverageMaps(), corpusDir, htmlReportPath, jsonReportPath) f.logger.Info("HTML Coverage report saved to file: ", colors.Bold, filepath.Join(corpusDir, htmlReportPath), colors.Reset) f.logger.Info("JSON Coverage report saved to file: ", colors.Bold, filepath.Join(corpusDir, jsonReportPath), colors.Reset) +======= + coverageReportPath := f.config.Fuzzing.CorpusDirectory + err = coverage.GenerateReport(f.compilations, f.corpus.CoverageMaps(), coverageReportPath) + f.logger.Info("Coverage report saved to file: ", colors.Bold, coverageReportPath, colors.Reset) +>>>>>>> aa8e54b (fix json template, change htmlReportPath to corpus path) } // Return any encountered error. From ed6627f6d8276a117a9553d006da00fd3aed8dec Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Fri, 4 Aug 2023 17:50:32 +0200 Subject: [PATCH 54/55] added html and json file path configs, updated report generation --- fuzzing/config/config.go | 3 ++- fuzzing/config/config_defaults.go | 2 ++ fuzzing/coverage/report_generation.go | 3 --- fuzzing/fuzzer.go | 7 +------ 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/fuzzing/config/config.go b/fuzzing/config/config.go index 25149e10..bceb3c3f 100644 --- a/fuzzing/config/config.go +++ b/fuzzing/config/config.go @@ -7,11 +7,12 @@ import ( "os" "github.com/crytic/medusa/chain/config" + "github.com/rs/zerolog" + "github.com/crytic/medusa/compilation" "github.com/crytic/medusa/logging" "github.com/crytic/medusa/utils" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/rs/zerolog" ) // The following directives will be picked up by the `go generate` command to generate JSON marshaling code from diff --git a/fuzzing/config/config_defaults.go b/fuzzing/config/config_defaults.go index 65ca6f04..f1b5f889 100644 --- a/fuzzing/config/config_defaults.go +++ b/fuzzing/config/config_defaults.go @@ -46,6 +46,8 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) { ConstructorArgs: map[string]map[string]any{}, CorpusDirectory: "", CoverageEnabled: true, + HtmlReportFile: "coverage_report.html", + JsonReportFile: "coverage_report.json", SenderAddresses: []string{ "0x10000", "0x20000", diff --git a/fuzzing/coverage/report_generation.go b/fuzzing/coverage/report_generation.go index 70e235ac..57232f1e 100644 --- a/fuzzing/coverage/report_generation.go +++ b/fuzzing/coverage/report_generation.go @@ -163,9 +163,6 @@ func exportHtmlCoverageReport(sourceAnalysis *SourceAnalysis, outputPath string) return fmt.Errorf("could not export report, failed to parse report template: %v", err) } - // Add the report file to the path - outputPath = filepath.Join(outputPath, "coverage_report.html") - // If the parent directory doesn't exist, create it. parentDirectory := filepath.Dir(outputPath) err = utils.MakeDirectory(parentDirectory) diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index e983e3fa..9b3d280e 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -8,6 +8,7 @@ import ( "math/rand" "os" "runtime" + "path/filepath" "sort" "strconv" "strings" @@ -747,18 +748,12 @@ func (f *Fuzzer) Start() error { // Finally, generate our coverage report if we have set a valid corpus directory. if err == nil && f.config.Fuzzing.CorpusDirectory != "" { -<<<<<<< HEAD htmlReportPath := f.config.Fuzzing.HtmlReportFile jsonReportPath := f.config.Fuzzing.JsonReportFile corpusDir := f.config.Fuzzing.CorpusDirectory err = coverage.GenerateReport(f.compilations, f.corpus.CoverageMaps(), corpusDir, htmlReportPath, jsonReportPath) f.logger.Info("HTML Coverage report saved to file: ", colors.Bold, filepath.Join(corpusDir, htmlReportPath), colors.Reset) f.logger.Info("JSON Coverage report saved to file: ", colors.Bold, filepath.Join(corpusDir, jsonReportPath), colors.Reset) -======= - coverageReportPath := f.config.Fuzzing.CorpusDirectory - err = coverage.GenerateReport(f.compilations, f.corpus.CoverageMaps(), coverageReportPath) - f.logger.Info("Coverage report saved to file: ", colors.Bold, coverageReportPath, colors.Reset) ->>>>>>> aa8e54b (fix json template, change htmlReportPath to corpus path) } // Return any encountered error. From 4fb36455563b71a465c214e376137b5c9721ea44 Mon Sep 17 00:00:00 2001 From: s4nsec Date: Tue, 9 Jul 2024 15:06:11 +0400 Subject: [PATCH 55/55] Remove duplicate member values passed to ProjectConfig --- fuzzing/config/config_defaults.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/fuzzing/config/config_defaults.go b/fuzzing/config/config_defaults.go index f1b5f889..65ca6f04 100644 --- a/fuzzing/config/config_defaults.go +++ b/fuzzing/config/config_defaults.go @@ -46,8 +46,6 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) { ConstructorArgs: map[string]map[string]any{}, CorpusDirectory: "", CoverageEnabled: true, - HtmlReportFile: "coverage_report.html", - JsonReportFile: "coverage_report.json", SenderAddresses: []string{ "0x10000", "0x20000",