Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/src/project_configuration/testing_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 are using invariants developed for Foundry's invariant testing, set this to false.
- **Default**: `true`


## Optimization Testing Configuration

### `enabled`
Expand Down
3 changes: 2 additions & 1 deletion docs/src/static/function_level_testing_medusa.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
},
"propertyTesting": {
"enabled": true,
"testPrefixes": ["property_"]
"testPrefixes": ["property_"],
"returnBool": true
},
"optimizationTesting": {
"enabled": true,
Expand Down
3 changes: 2 additions & 1 deletion docs/src/static/medusa.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
},
"propertyTesting": {
"enabled": true,
"testPrefixes": ["property_"]
"testPrefixes": ["property_"],
"returnBool": true
},
"optimizationTesting": {
"enabled": true,
Expand Down
3 changes: 3 additions & 0 deletions docs/src/testing/writing-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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]`.
Expand Down
3 changes: 3 additions & 0 deletions fuzzing/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but maybe it needs to be unified the other way to testReturnBool? it seems JSON options match their Go counterparts.

}

// OptimizationTestingConfig describes the configuration options used for optimization testing
Expand Down
1 change: 1 addition & 0 deletions fuzzing/config/config_defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) {
TestPrefixes: []string{
"property_",
},
TestReturnBool: true,
},
OptimizationTesting: OptimizationTestingConfig{
Enabled: true,
Expand Down
3 changes: 2 additions & 1 deletion fuzzing/fuzzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions fuzzing/test_case_property_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ func (t *PropertyTestCaseProvider) checkPropertyTestFailed(worker *FuzzerWorker,
return true, executionTrace, nil
}

// If the properties 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 {
Expand Down
12 changes: 8 additions & 4 deletions fuzzing/utils/fuzz_method_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand All @@ -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)
Expand Down
Loading