diff --git a/cmd/fuzz.go b/cmd/fuzz.go index c9905b84..9a90de3c 100644 --- a/cmd/fuzz.go +++ b/cmd/fuzz.go @@ -24,7 +24,7 @@ var fuzzCmd = &cobra.Command{ ValidArgsFunction: cmdValidFuzzArgs, RunE: cmdRunFuzz, SilenceUsage: true, - SilenceErrors: true, + SilenceErrors: false, } func init() { diff --git a/fuzzing/config/config.go b/fuzzing/config/config.go index e488238c..439cba7e 100644 --- a/fuzzing/config/config.go +++ b/fuzzing/config/config.go @@ -199,6 +199,9 @@ type AssertionTestingConfig struct { // PanicCodeConfig describes the various panic codes that can be enabled and be treated as a failing assertion test type PanicCodeConfig struct { + // FailOnRevert describes whether a revert should be treated as a failing case + FailOnRevert bool `json:"failOnRevert"` + // FailOnCompilerInsertedPanic describes whether a generic compiler inserted panic should be treated as a failing case FailOnCompilerInsertedPanic bool `json:"failOnCompilerInsertedPanic"` diff --git a/fuzzing/config/config_defaults.go b/fuzzing/config/config_defaults.go index 10f45dc1..ba8275be 100644 --- a/fuzzing/config/config_defaults.go +++ b/fuzzing/config/config_defaults.go @@ -69,6 +69,7 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) { TestViewMethods: false, PanicCodeConfig: PanicCodeConfig{ FailOnAssertion: true, + FailOnRevert: false, }, }, PropertyTesting: PropertyTestingConfig{ diff --git a/fuzzing/fuzzer_test.go b/fuzzing/fuzzer_test.go index 5f56ca4c..ece62f47 100644 --- a/fuzzing/fuzzer_test.go +++ b/fuzzing/fuzzer_test.go @@ -129,6 +129,36 @@ func TestAssertionsNotRequire(t *testing.T) { }) } +// TestAssertionFailOnRevert runs a test to ensure that a revert failure causes a test to fail if FailOnRevert is set to true +func TestAssertionFailOnRevert(t *testing.T) { + runFuzzerTest(t, &fuzzerSolcFileTest{ + filePath: "testdata/contracts/assertions/assert_fail_on_revert.sol", + configUpdates: func(config *config.ProjectConfig) { + config.Fuzzing.TargetContracts = []string{"TestContract"} + config.Fuzzing.TestLimit = 500 + + // enable assertion checking mode only + config.Fuzzing.Testing.PropertyTesting.Enabled = false + config.Fuzzing.Testing.AssertionTesting.Enabled = true + + // enabling fail on revert + config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnRevert = true + + // to ensure all tests fail before fuzzer stops + config.Fuzzing.Testing.StopOnFailedTest = 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, true) + assert.EqualValues(t, 4, len(f.fuzzer.TestCasesWithStatus(TestCaseStatusFailed))) + }, + }) +} + // TestAssertionsAndProperties runs a test to property testing and assertion testing can both run in parallel. // This test does not stop on first failure and expects a failure from each after timeout. func TestAssertionsAndProperties(t *testing.T) { diff --git a/fuzzing/test_case_assertion_provider.go b/fuzzing/test_case_assertion_provider.go index f9b9978a..0d4bddd4 100644 --- a/fuzzing/test_case_assertion_provider.go +++ b/fuzzing/test_case_assertion_provider.go @@ -1,6 +1,7 @@ package fuzzing import ( + "errors" "math/big" "sync" @@ -8,7 +9,7 @@ import ( "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/fuzzing/config" "github.com/crytic/medusa/fuzzing/contracts" - + "github.com/ethereum/go-ethereum/core/vm" "golang.org/x/exp/slices" ) @@ -65,6 +66,13 @@ func (t *AssertionTestCaseProvider) checkAssertionFailures(callSequence calls.Ca // want to be backwards compatible with older Solidity which simply hit an invalid opcode and did not actually // have a panic code. lastExecutionResult := lastCall.ChainReference.MessageResults().ExecutionResult + + // Check for revert or require failures if FailOnRevert is set to true + if t.fuzzer.config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnRevert { + if errors.Is(lastExecutionResult.Err, vm.ErrExecutionReverted) { + return &methodId, true, nil + } + } panicCode := abiutils.GetSolidityPanicCode(lastExecutionResult.Err, lastExecutionResult.ReturnData, true) failure := false if panicCode != nil { diff --git a/fuzzing/testdata/contracts/assertions/assert_fail_on_revert.sol b/fuzzing/testdata/contracts/assertions/assert_fail_on_revert.sol new file mode 100644 index 00000000..d06f0725 --- /dev/null +++ b/fuzzing/testdata/contracts/assertions/assert_fail_on_revert.sol @@ -0,0 +1,22 @@ +// This contract ensures the fuzzer will report an error when it encounters a revert. +contract TestContract { + function failRequire(uint value) public { + // This should trigger a test failure due to a failing require statement (without an error message) + require(false); + } + + function failRequireWithErrorString(uint value) public { + // This should trigger a test failure due to a failing require statement (with an error message) + require(false, "Require error"); + } + + function failRevert(uint value) public { + // This should trigger a test failure on encountering a revert instruction (without an error message) + revert(); + } + + function failRevertWithErrorString(uint value) public { + // This should trigger a test failure on encountering a revert instruction (with an error message) + revert("Function reverted"); + } +} \ No newline at end of file