From d630ff437d7d0028d50992efdba04dad6eeecfc0 Mon Sep 17 00:00:00 2001 From: montyly Date: Mon, 4 Aug 2025 11:21:51 +0200 Subject: [PATCH 1/6] Add flag: returnBool for property testing --- docs/src/project_configuration/testing_config.md | 9 +++++++++ docs/src/static/function_level_testing_medusa.json | 3 ++- docs/src/static/medusa.json | 3 ++- docs/src/testing/writing-tests.md | 3 +++ fuzzing/config/config.go | 3 +++ fuzzing/config/config_defaults.go | 1 + fuzzing/fuzzer.go | 3 ++- fuzzing/test_case_property_provider.go | 5 +++++ fuzzing/utils/fuzz_method_utils.go | 12 ++++++++---- 9 files changed, 35 insertions(+), 7 deletions(-) diff --git a/docs/src/project_configuration/testing_config.md b/docs/src/project_configuration/testing_config.md index 12d01b52..06c4eb32 100644 --- a/docs/src/project_configuration/testing_config.md +++ b/docs/src/project_configuration/testing_config.md @@ -177,6 +177,15 @@ contract MyContract { > **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_]` + +### `returnBool` + +- **Type**: [Bool] +- **Description**: If the property returns a boolean or not + > **Note**: If you using invariants developped for foundry's invariant testing, set this to false. +- **Default**: `true` + + ## Optimization Testing Configuration ### `enabled` diff --git a/docs/src/static/function_level_testing_medusa.json b/docs/src/static/function_level_testing_medusa.json index 5e284eef..c6e565f8 100644 --- a/docs/src/static/function_level_testing_medusa.json +++ b/docs/src/static/function_level_testing_medusa.json @@ -40,7 +40,8 @@ }, "propertyTesting": { "enabled": true, - "testPrefixes": ["property_"] + "testPrefixes": ["property_"], + "testReturnBool": true }, "optimizationTesting": { "enabled": true, diff --git a/docs/src/static/medusa.json b/docs/src/static/medusa.json index 4326aa56..c1c7772d 100644 --- a/docs/src/static/medusa.json +++ b/docs/src/static/medusa.json @@ -44,7 +44,8 @@ }, "propertyTesting": { "enabled": true, - "testPrefixes": ["property_"] + "testPrefixes": ["property_"], + "testReturnBool": true }, "optimizationTesting": { "enabled": true, diff --git a/docs/src/testing/writing-tests.md b/docs/src/testing/writing-tests.md index f8aacca2..358e3fa0 100644 --- a/docs/src/testing/writing-tests.md +++ b/docs/src/testing/writing-tests.md @@ -12,6 +12,7 @@ For more advanced information and documentation on how the various modes work an 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; @@ -34,6 +35,8 @@ contract TestXY { `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. +If you are using [foundry's invariant testing](https://getfoundry.sh/forge/advanced-testing/invariant-testing/#conditional-invariants), you can define the properties to not return a boolean by setting `returnBool` to false. + ### Testing in property-mode To begin a fuzzing campaign in property-mode, you can run `medusa fuzz` or `medusa fuzz --config [config_path]`. diff --git a/fuzzing/config/config.go b/fuzzing/config/config.go index c1d42bf6..1015adea 100644 --- a/fuzzing/config/config.go +++ b/fuzzing/config/config.go @@ -310,6 +310,9 @@ type PropertyTestingConfig struct { // TestPrefixes dictates what method name prefixes will determine if a contract method is a property test. TestPrefixes []string `json:"testPrefixes"` + + // TestReturnBool dictactes if the properties should return a bool or not + TestReturnBool bool `json:"returnBool"` } // OptimizationTestingConfig describes the configuration options used for optimization testing diff --git a/fuzzing/config/config_defaults.go b/fuzzing/config/config_defaults.go index 2c11e38e..455ac3f5 100644 --- a/fuzzing/config/config_defaults.go +++ b/fuzzing/config/config_defaults.go @@ -83,6 +83,7 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) { TestPrefixes: []string{ "property_", }, + TestReturnBool: true, }, OptimizationTesting: OptimizationTestingConfig{ Enabled: true, diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index d04d9ef0..048840ce 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -411,7 +411,8 @@ func (f *Fuzzer) AddCompilationTargets(compilations []compilationTypes.Compilati assertionTestMethods, propertyTestMethods, optimizationTestMethods := fuzzingutils.BinTestByType(&contract, f.config.Fuzzing.Testing.PropertyTesting.TestPrefixes, f.config.Fuzzing.Testing.OptimizationTesting.TestPrefixes, - f.config.Fuzzing.Testing.TestViewMethods) + f.config.Fuzzing.Testing.TestViewMethods, + f.config.Fuzzing.Testing.PropertyTesting.TestReturnBool) contractDefinition.AssertionTestMethods = assertionTestMethods contractDefinition.PropertyTestMethods = propertyTestMethods contractDefinition.OptimizationTestMethods = optimizationTestMethods diff --git a/fuzzing/test_case_property_provider.go b/fuzzing/test_case_property_provider.go index 9ba141bb..534b6c6f 100644 --- a/fuzzing/test_case_property_provider.go +++ b/fuzzing/test_case_property_provider.go @@ -102,6 +102,11 @@ func (t *PropertyTestCaseProvider) checkPropertyTestFailed(worker *FuzzerWorker, return true, executionTrace, nil } + // If the property don't have a return value + if !worker.fuzzer.config.Fuzzing.Testing.PropertyTesting.TestReturnBool { + return false, executionTrace, nil + } + // Decode our ABI outputs retVals, err := propertyTestMethod.Method.Outputs.Unpack(executionResult.Return()) if err != nil { diff --git a/fuzzing/utils/fuzz_method_utils.go b/fuzzing/utils/fuzz_method_utils.go index 88404c48..08550ad1 100644 --- a/fuzzing/utils/fuzz_method_utils.go +++ b/fuzzing/utils/fuzz_method_utils.go @@ -24,12 +24,16 @@ func IsOptimizationTest(method abi.Method, prefixes []string) bool { // 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 { +func IsPropertyTest(method abi.Method, prefixes []string, propertyTestReturnBool bool) 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 and return a boolean if strings.HasPrefix(method.Name, prefix) { - if len(method.Inputs) == 0 && len(method.Outputs) == 1 && method.Outputs[0].Type.T == abi.BoolTy { + // If returnBool is set to true, the property return a boolean, otherwise it return nothing + if len(method.Inputs) == 0 && len(method.Outputs) == 1 && method.Outputs[0].Type.T == abi.BoolTy && propertyTestReturnBool { + return true + } + if len(method.Inputs) == 0 && len(method.Outputs) == 0 && !propertyTestReturnBool { return true } } @@ -38,9 +42,9 @@ func IsPropertyTest(method abi.Method, prefixes []string) bool { } // BinTestByType sorts a contract's methods by whether they are assertion, property, or optimization tests. -func BinTestByType(contract *compilationTypes.CompiledContract, propertyTestPrefixes, optimizationTestPrefixes []string, testViewMethods bool) (assertionTests, propertyTests, optimizationTests []abi.Method) { +func BinTestByType(contract *compilationTypes.CompiledContract, propertyTestPrefixes, optimizationTestPrefixes []string, testViewMethods bool, propertyTestReturnBool bool) (assertionTests, propertyTests, optimizationTests []abi.Method) { for _, method := range contract.Abi.Methods { - if IsPropertyTest(method, propertyTestPrefixes) { + if IsPropertyTest(method, propertyTestPrefixes, propertyTestReturnBool) { propertyTests = append(propertyTests, method) } else if IsOptimizationTest(method, optimizationTestPrefixes) { optimizationTests = append(optimizationTests, method) From 5197b17dbb5f424e5b57f064a9ad7602e3d6b0a6 Mon Sep 17 00:00:00 2001 From: Josselin Feist Date: Mon, 4 Aug 2025 16:09:15 +0200 Subject: [PATCH 2/6] Update docs/src/project_configuration/testing_config.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Emilio López <2642849+elopez@users.noreply.github.com> --- docs/src/project_configuration/testing_config.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/project_configuration/testing_config.md b/docs/src/project_configuration/testing_config.md index 06c4eb32..aa01d47b 100644 --- a/docs/src/project_configuration/testing_config.md +++ b/docs/src/project_configuration/testing_config.md @@ -182,7 +182,7 @@ contract MyContract { - **Type**: [Bool] - **Description**: If the property returns a boolean or not - > **Note**: If you using invariants developped for foundry's invariant testing, set this to false. + > **Note**: If you are using invariants developed for Foundry's invariant testing, set this to false. - **Default**: `true` From 17e33db991d64856b77a7920822e1ec702c26d46 Mon Sep 17 00:00:00 2001 From: Josselin Feist Date: Mon, 4 Aug 2025 16:09:21 +0200 Subject: [PATCH 3/6] Update docs/src/testing/writing-tests.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Emilio López <2642849+elopez@users.noreply.github.com> --- docs/src/testing/writing-tests.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/testing/writing-tests.md b/docs/src/testing/writing-tests.md index 358e3fa0..aee5cd22 100644 --- a/docs/src/testing/writing-tests.md +++ b/docs/src/testing/writing-tests.md @@ -35,7 +35,7 @@ contract TestXY { `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. -If you are using [foundry's invariant testing](https://getfoundry.sh/forge/advanced-testing/invariant-testing/#conditional-invariants), you can define the properties to not return a boolean by setting `returnBool` to false. +If you are using [Foundry's invariant testing](https://getfoundry.sh/forge/advanced-testing/invariant-testing/#conditional-invariants), you can define the properties to not return a boolean by setting `returnBool` to false. ### Testing in property-mode From dc0054bfdadf06e47a4e2fb132fc474eb96befed Mon Sep 17 00:00:00 2001 From: Josselin Feist Date: Mon, 4 Aug 2025 16:09:28 +0200 Subject: [PATCH 4/6] Update docs/src/static/function_level_testing_medusa.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Emilio López <2642849+elopez@users.noreply.github.com> --- docs/src/static/function_level_testing_medusa.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/static/function_level_testing_medusa.json b/docs/src/static/function_level_testing_medusa.json index c6e565f8..398b2469 100644 --- a/docs/src/static/function_level_testing_medusa.json +++ b/docs/src/static/function_level_testing_medusa.json @@ -41,7 +41,7 @@ "propertyTesting": { "enabled": true, "testPrefixes": ["property_"], - "testReturnBool": true + "returnBool": true }, "optimizationTesting": { "enabled": true, From 221d198f5c25d264fc44fecd750a0f8d56f51249 Mon Sep 17 00:00:00 2001 From: Josselin Feist Date: Mon, 4 Aug 2025 16:09:35 +0200 Subject: [PATCH 5/6] Update fuzzing/test_case_property_provider.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Emilio López <2642849+elopez@users.noreply.github.com> --- fuzzing/test_case_property_provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuzzing/test_case_property_provider.go b/fuzzing/test_case_property_provider.go index 534b6c6f..5cabc930 100644 --- a/fuzzing/test_case_property_provider.go +++ b/fuzzing/test_case_property_provider.go @@ -102,7 +102,7 @@ func (t *PropertyTestCaseProvider) checkPropertyTestFailed(worker *FuzzerWorker, return true, executionTrace, nil } - // If the property don't have a return value + // If the properties don't have a return value if !worker.fuzzer.config.Fuzzing.Testing.PropertyTesting.TestReturnBool { return false, executionTrace, nil } From 3df9ac142b9420b7d9f047e63586527ba1e042a4 Mon Sep 17 00:00:00 2001 From: Josselin Feist Date: Mon, 4 Aug 2025 16:09:45 +0200 Subject: [PATCH 6/6] Update docs/src/static/medusa.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Emilio López <2642849+elopez@users.noreply.github.com> --- docs/src/static/medusa.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/static/medusa.json b/docs/src/static/medusa.json index c1c7772d..4ee6048f 100644 --- a/docs/src/static/medusa.json +++ b/docs/src/static/medusa.json @@ -45,7 +45,7 @@ "propertyTesting": { "enabled": true, "testPrefixes": ["property_"], - "testReturnBool": true + "returnBool": true }, "optimizationTesting": { "enabled": true,