Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert "feat: add config to target/exclude func sig. by contract" #415

Closed
Closed
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
14 changes: 0 additions & 14 deletions docs/src/project_configuration/testing_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,6 @@ contract MyContract {
that triggered a test failure.
- **Default**: `false`

### `targetFunctionSignatures`:

- **Type**: [String]
- **Description**: A list of function signatures that the fuzzer should exclusively target by omitting calls to other signatures. The signatures should specify the contract name and signature in the ABI format like `Contract.func(uint256,bytes32)`.
> **Note**: Property and optimization tests will always be called even if they are not explicitly specified in this list.
- **Default**: `[]`

### `excludeFunctionSignatures`:

- **Type**: [String]
- **Description**: A list of function signatures that the fuzzer should exclude from the fuzzing campaign. The signatures should specify the contract name and signature in the ABI format like `Contract.func(uint256,bytes32)`.
> **Note**: Property and optimization tests will always be called and cannot be excluded.
- **Default**: `[]`

## Assertion Testing Configuration

### `enabled`
Expand Down
49 changes: 1 addition & 48 deletions fuzzing/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,48 +141,6 @@ type TestingConfig struct {

// OptimizationTesting describes the configuration used for optimization testing.
OptimizationTesting OptimizationTestingConfig `json:"optimizationTesting"`

// TargetFunctionSignatures is a list function signatures call the fuzzer should exclusively target by omitting calls to other signatures.
// The signatures should specify the contract name and signature in the ABI format like `Contract.func(uint256,bytes32)`.
TargetFunctionSignatures []string `json:"targetFunctionSignatures"`

// ExcludeFunctionSignatures is a list of function signatures that will be excluded from call sequences.
// The signatures should specify the contract name and signature in the ABI format like `Contract.func(uint256,bytes32)`.
ExcludeFunctionSignatures []string `json:"excludeFunctionSignatures"`
}

// Validate validates that the TestingConfig meets certain requirements.
func (testCfg *TestingConfig) Validate() error {
// Verify that target and exclude function signatures are used mutually exclusive.
if (len(testCfg.TargetFunctionSignatures) != 0) && (len(testCfg.ExcludeFunctionSignatures) != 0) {
return errors.New("project configuration must specify only one of blacklist or whitelist at a time")
}

// Verify property testing fields.
if testCfg.PropertyTesting.Enabled {
// Test prefixes must be supplied if property testing is enabled.
if len(testCfg.PropertyTesting.TestPrefixes) == 0 {
return errors.New("project configuration must specify test name prefixes if property testing is enabled")
}
}

if testCfg.OptimizationTesting.Enabled {
// Test prefixes must be supplied if optimization testing is enabled.
if len(testCfg.OptimizationTesting.TestPrefixes) == 0 {
return errors.New("project configuration must specify test name prefixes if optimization testing is enabled")
}
}

// Validate that prefixes do not overlap
for _, prefix := range testCfg.PropertyTesting.TestPrefixes {
for _, prefix2 := range testCfg.OptimizationTesting.TestPrefixes {
if prefix == prefix2 {
return errors.New("project configuration must specify unique test name prefixes for property and optimization testing")
}
}
}

return nil
}

// AssertionTestingConfig describes the configuration options used for assertion testing
Expand Down Expand Up @@ -258,7 +216,7 @@ type LoggingConfig struct {
// equivalent to enabling file logging.
LogDirectory string `json:"logDirectory"`

// NoColor indicates whether log messages should be displayed with colored formatting.
// NoColor indicates whether or not log messages should be displayed with colored formatting.
NoColor bool `json:"noColor"`
}

Expand Down Expand Up @@ -328,11 +286,6 @@ func (p *ProjectConfig) Validate() error {
logger = logging.GlobalLogger.NewSubLogger("module", "fuzzer config")
}

// Validate testing config
if err := p.Fuzzing.Testing.Validate(); err != nil {
return err
}

// 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")
Expand Down
5 changes: 1 addition & 4 deletions fuzzing/config/config_defaults.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package config

import (
"math/big"

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
Expand Down Expand Up @@ -62,8 +61,6 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) {
StopOnNoTests: true,
TestAllContracts: false,
TraceAll: false,
TargetFunctionSignatures: []string{},
ExcludeFunctionSignatures: []string{},
AssertionTesting: AssertionTestingConfig{
Enabled: true,
TestViewMethods: false,
Expand Down
41 changes: 0 additions & 41 deletions fuzzing/contracts/contract.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
package contracts

import (
"golang.org/x/exp/slices"
"strings"

"github.com/crytic/medusa/compilation/types"
"github.com/ethereum/go-ethereum/accounts/abi"
)

// Contracts describes an array of contracts
Expand Down Expand Up @@ -39,17 +35,6 @@ type Contract struct {

// compilation describes the compilation which contains the compiledContract.
compilation *types.Compilation

// PropertyTestMethods are the methods that are property tests.
PropertyTestMethods []abi.Method

// OptimizationTestMethods are the methods that are optimization tests.
OptimizationTestMethods []abi.Method

// AssertionTestMethods are ALL other methods that are not property or optimization tests by default.
// If configured, the methods will be targeted or excluded based on the targetFunctionSignatures
// and excludedFunctionSignatures, respectively.
AssertionTestMethods []abi.Method
}

// NewContract returns a new Contract instance with the provided information.
Expand All @@ -62,32 +47,6 @@ func NewContract(name string, sourcePath string, compiledContract *types.Compile
}
}

// WithTargetedAssertionMethods filters the assertion test methods to those in the target list.
func (c *Contract) WithTargetedAssertionMethods(target []string) *Contract {
var candidateMethods []abi.Method
for _, method := range c.AssertionTestMethods {
canonicalSig := strings.Join([]string{c.name, method.Sig}, ".")
if slices.Contains(target, canonicalSig) {
candidateMethods = append(candidateMethods, method)
}
}
c.AssertionTestMethods = candidateMethods
return c
}

// WithExcludedAssertionMethods filters the assertion test methods to all methods not in excluded list.
func (c *Contract) WithExcludedAssertionMethods(excludedMethods []string) *Contract {
var candidateMethods []abi.Method
for _, method := range c.AssertionTestMethods {
canonicalSig := strings.Join([]string{c.name, method.Sig}, ".")
if !slices.Contains(excludedMethods, canonicalSig) {
candidateMethods = append(candidateMethods, method)
}
}
c.AssertionTestMethods = candidateMethods
return c
}

// Name returns the name of the contract.
func (c *Contract) Name() string {
return c.name
Expand Down
24 changes: 1 addition & 23 deletions fuzzing/fuzzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import (
"github.com/crytic/medusa/fuzzing/config"
fuzzerTypes "github.com/crytic/medusa/fuzzing/contracts"
"github.com/crytic/medusa/fuzzing/corpus"
fuzzingutils "github.com/crytic/medusa/fuzzing/utils"
"github.com/crytic/medusa/fuzzing/valuegeneration"
"github.com/crytic/medusa/utils"
"github.com/ethereum/go-ethereum/accounts/abi"
Expand Down Expand Up @@ -283,7 +282,7 @@ func (f *Fuzzer) ReportTestCaseFinished(testCase TestCase) {
// AddCompilationTargets takes a compilation and updates the Fuzzer state with additional Fuzzer.ContractDefinitions
// definitions and Fuzzer.BaseValueSet values.
func (f *Fuzzer) AddCompilationTargets(compilations []compilationTypes.Compilation) {
// Loop for each contract in each compilation and deploy it to the test chain
// Loop for each contract in each compilation and deploy it to the test node.
for i := 0; i < len(compilations); i++ {
// Add our compilation to the list and get a reference to it.
f.compilations = append(f.compilations, compilations[i])
Expand All @@ -298,26 +297,6 @@ func (f *Fuzzer) AddCompilationTargets(compilations []compilationTypes.Compilati
for contractName := range source.Contracts {
contract := source.Contracts[contractName]
contractDefinition := fuzzerTypes.NewContract(contractName, sourcePath, &contract, compilation)

// Sort available methods by type
assertionTestMethods, propertyTestMethods, optimizationTestMethods := fuzzingutils.BinTestByType(&contract,
f.config.Fuzzing.Testing.PropertyTesting.TestPrefixes,
f.config.Fuzzing.Testing.OptimizationTesting.TestPrefixes,
f.config.Fuzzing.Testing.AssertionTesting.TestViewMethods)
contractDefinition.AssertionTestMethods = assertionTestMethods
contractDefinition.PropertyTestMethods = propertyTestMethods
contractDefinition.OptimizationTestMethods = optimizationTestMethods

// Filter and record methods available for assertion testing. Property and optimization tests are always run.
if len(f.config.Fuzzing.Testing.TargetFunctionSignatures) > 0 {
// Only consider methods that are in the target methods list
contractDefinition = contractDefinition.WithTargetedAssertionMethods(f.config.Fuzzing.Testing.TargetFunctionSignatures)
}
if len(f.config.Fuzzing.Testing.ExcludeFunctionSignatures) > 0 {
// Consider all methods except those in the exclude methods list
contractDefinition = contractDefinition.WithExcludedAssertionMethods(f.config.Fuzzing.Testing.ExcludeFunctionSignatures)
}

f.contractDefinitions = append(f.contractDefinitions, contractDefinition)
}
}
Expand Down Expand Up @@ -394,7 +373,6 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex
// 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 {
// TODO filter libraries
if len(fuzzer.contractDefinitions) == 1 {
fuzzer.config.Fuzzing.TargetContracts = []string{fuzzer.contractDefinitions[0].Name()}
} else {
Expand Down
43 changes: 0 additions & 43 deletions fuzzing/fuzzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"encoding/hex"
"math/big"
"math/rand"
"reflect"
"testing"

"github.com/crytic/medusa/fuzzing/executiontracer"
Expand Down Expand Up @@ -894,45 +893,3 @@ func TestDeploymentOrderWithCoverage(t *testing.T) {
},
})
}

// TestTargetingFuncSignatures tests whether functions will be correctly whitelisted for testing
func TestTargetingFuncSignatures(t *testing.T) {
targets := []string{"TestContract.f(), TestContract.g()"}
runFuzzerTest(t, &fuzzerSolcFileTest{
filePath: "testdata/contracts/filtering/target_and_exclude.sol",
configUpdates: func(config *config.ProjectConfig) {
config.Fuzzing.TargetContracts = []string{"TestContract"}
config.Fuzzing.Testing.TargetFunctionSignatures = targets
},
method: func(f *fuzzerTestContext) {
for _, contract := range f.fuzzer.ContractDefinitions() {
// The targets should be the only functions tested, excluding h and i
reflect.DeepEqual(contract.AssertionTestMethods, targets)

// ALL properties and optimizations should be tested
reflect.DeepEqual(contract.PropertyTestMethods, []string{"TestContract.property_a()"})
reflect.DeepEqual(contract.OptimizationTestMethods, []string{"TestContract.optimize_b()"})
}
}})
}

// TestExcludeFunctionSignatures tests whether functions will be blacklisted/excluded for testing
func TestExcludeFunctionSignatures(t *testing.T) {
excluded := []string{"TestContract.f(), TestContract.g()"}
runFuzzerTest(t, &fuzzerSolcFileTest{
filePath: "testdata/contracts/filtering/target_and_exclude.sol",
configUpdates: func(config *config.ProjectConfig) {
config.Fuzzing.TargetContracts = []string{"TestContract"}
config.Fuzzing.Testing.ExcludeFunctionSignatures = excluded
},
method: func(f *fuzzerTestContext) {
for _, contract := range f.fuzzer.ContractDefinitions() {
// Only h and i should be test since f and g are excluded
reflect.DeepEqual(contract.AssertionTestMethods, []string{"TestContract.h()", "TestContract.i()"})

// ALL properties and optimizations should be tested
reflect.DeepEqual(contract.PropertyTestMethods, []string{"TestContract.property_a()"})
reflect.DeepEqual(contract.OptimizationTestMethods, []string{"TestContract.optimize_b()"})
}
}})
}
5 changes: 2 additions & 3 deletions fuzzing/fuzzer_worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ func newFuzzerWorker(fuzzer *Fuzzer, workerIndex int, randomProvider *rand.Rand)
fuzzer: fuzzer,
deployedContracts: make(map[common.Address]*fuzzerTypes.Contract),
stateChangingMethods: make([]fuzzerTypes.DeployedContractMethod, 0),
pureMethods: make([]fuzzerTypes.DeployedContractMethod, 0),
coverageTracer: nil,
randomProvider: randomProvider,
valueSet: valueSet,
Expand Down Expand Up @@ -240,9 +239,9 @@ func (fw *FuzzerWorker) updateMethods() {
// 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.AssertionTestMethods {
for _, method := range contractDefinition.CompiledContract().Abi.Methods {
// Any non-constant method should be tracked as a state changing method.
// We favor calling state changing methods over view/pure methods.
// 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)))
Expand Down
26 changes: 25 additions & 1 deletion fuzzing/test_case_assertion_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ 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"
)
Expand Down Expand Up @@ -42,6 +44,23 @@ func attachAssertionTestCaseProvider(fuzzer *Fuzzer) *AssertionTestCaseProvider
return t
}

// 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
}

// checkAssertionFailures checks the results of the last call for assertion failures.
// Returns the method ID, a boolean indicating if an assertion test failed, or an error if one occurs.
func (t *AssertionTestCaseProvider) checkAssertionFailures(callSequence calls.CallSequence) (*contracts.ContractMethodID, bool, error) {
Expand Down Expand Up @@ -86,7 +105,12 @@ func (t *AssertionTestCaseProvider) onFuzzerStarting(event FuzzerStartingEvent)
continue
}

for _, method := range contract.AssertionTestMethods {
for _, method := range contract.CompiledContract().Abi.Methods {
// Verify this method is an assertion testable method
if !t.isTestableMethod(method) {
continue
}

// Create local variables to avoid pointer types in the loop being overridden.
contract := contract
method := method
Expand Down
7 changes: 6 additions & 1 deletion fuzzing/test_case_optimization_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"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"
)
Expand Down Expand Up @@ -134,7 +135,11 @@ func (t *OptimizationTestCaseProvider) onFuzzerStarting(event FuzzerStartingEven
continue
}

for _, method := range contract.OptimizationTestMethods {
for _, method := range contract.CompiledContract().Abi.Methods {
// Verify this method is an optimization test 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.
contract := contract
method := method
Expand Down
8 changes: 7 additions & 1 deletion fuzzing/test_case_property_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"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"
)
Expand Down Expand Up @@ -136,7 +137,12 @@ func (t *PropertyTestCaseProvider) onFuzzerStarting(event FuzzerStartingEvent) e
continue
}

for _, method := range contract.PropertyTestMethods {
for _, method := range contract.CompiledContract().Abi.Methods {
// Verify this method is a property test method
if !utils.IsPropertyTest(method, t.fuzzer.config.Fuzzing.Testing.PropertyTesting.TestPrefixes) {
continue
}

// Create local variables to avoid pointer types in the loop being overridden.
contract := contract
method := method
Expand Down
Loading