Skip to content

Commit ac0b42f

Browse files
authored
Enable all testing modes by default, update property mode testing, improve UX, allow for contracts to have starting balances, and fix coverage panic (crytic#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
1 parent 18aa011 commit ac0b42f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+530
-243
lines changed

cmd/fuzz_flags.go

+12-37
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ func addFuzzFlags() error {
2121
// Config file
2222
fuzzCmd.Flags().String("config", "", "path to config file")
2323

24-
// Target
25-
fuzzCmd.Flags().String("target", "", TargetFlagDescription)
24+
// Compilation Target
25+
fuzzCmd.Flags().String("compilation-target", "", TargetFlagDescription)
2626

2727
// Number of workers
2828
fuzzCmd.Flags().Int("workers", 0,
@@ -40,14 +40,13 @@ func addFuzzFlags() error {
4040
fuzzCmd.Flags().Int("seq-len", 0,
4141
fmt.Sprintf("maximum transactions to run in sequence (unless a config file is provided, default is %d)", defaultConfig.Fuzzing.CallSequenceLength))
4242

43-
// Deployment order
44-
fuzzCmd.Flags().StringSlice("deployment-order", []string{},
45-
fmt.Sprintf("order in which to deploy target contracts (unless a config file is provided, default is %v)", defaultConfig.Fuzzing.DeploymentOrder))
43+
// Target contracts
44+
fuzzCmd.Flags().StringSlice("target-contracts", []string{},
45+
fmt.Sprintf("target contracts for fuzz testing (unless a config file is provided, default is %v)", defaultConfig.Fuzzing.TargetContracts))
4646

4747
// Corpus directory
48-
// TODO: Update description when we add "coverage reports" feature
4948
fuzzCmd.Flags().String("corpus-dir", "",
50-
fmt.Sprintf("directory path for corpus items (unless a config file is provided, default is %q)", defaultConfig.Fuzzing.CorpusDirectory))
49+
fmt.Sprintf("directory path for corpus items and coverage reports (unless a config file is provided, default is %q)", defaultConfig.Fuzzing.CorpusDirectory))
5150

5251
// Senders
5352
fuzzCmd.Flags().StringSlice("senders", []string{},
@@ -57,14 +56,6 @@ func addFuzzFlags() error {
5756
fuzzCmd.Flags().String("deployer", "",
5857
"account address used to deploy contracts")
5958

60-
// Assertion mode
61-
fuzzCmd.Flags().Bool("assertion-mode", false,
62-
fmt.Sprintf("enable assertion mode (unless a config file is provided, default is %t)", defaultConfig.Fuzzing.Testing.AssertionTesting.Enabled))
63-
64-
// Optimization mode
65-
fuzzCmd.Flags().Bool("optimization-mode", false,
66-
fmt.Sprintf("enable optimization mode (unless a config file is provided, default is %t)", defaultConfig.Fuzzing.Testing.OptimizationTesting.Enabled))
67-
6859
// Trace all
6960
fuzzCmd.Flags().Bool("trace-all", false,
7061
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 {
7970
func updateProjectConfigWithFuzzFlags(cmd *cobra.Command, projectConfig *config.ProjectConfig) error {
8071
var err error
8172

82-
// If --target was used
83-
if cmd.Flags().Changed("target") {
73+
// If --compilation-target was used
74+
if cmd.Flags().Changed("compilation-target") {
8475
// Get the new target
85-
newTarget, err := cmd.Flags().GetString("target")
76+
newTarget, err := cmd.Flags().GetString("compilation-target")
8677
if err != nil {
8778
return err
8879
}
@@ -125,9 +116,9 @@ func updateProjectConfigWithFuzzFlags(cmd *cobra.Command, projectConfig *config.
125116
}
126117
}
127118

128-
// Update deployment order
129-
if cmd.Flags().Changed("deployment-order") {
130-
projectConfig.Fuzzing.DeploymentOrder, err = cmd.Flags().GetStringSlice("deployment-order")
119+
// Update target contracts
120+
if cmd.Flags().Changed("target-contracts") {
121+
projectConfig.Fuzzing.TargetContracts, err = cmd.Flags().GetStringSlice("target-contracts")
131122
if err != nil {
132123
return err
133124
}
@@ -157,22 +148,6 @@ func updateProjectConfigWithFuzzFlags(cmd *cobra.Command, projectConfig *config.
157148
}
158149
}
159150

160-
// Update assertion mode enablement
161-
if cmd.Flags().Changed("assertion-mode") {
162-
projectConfig.Fuzzing.Testing.AssertionTesting.Enabled, err = cmd.Flags().GetBool("assertion-mode")
163-
if err != nil {
164-
return err
165-
}
166-
}
167-
168-
// Update optimization mode enablement
169-
if cmd.Flags().Changed("optimization-mode") {
170-
projectConfig.Fuzzing.Testing.OptimizationTesting.Enabled, err = cmd.Flags().GetBool("optimization-mode")
171-
if err != nil {
172-
return err
173-
}
174-
}
175-
176151
// Update trace all enablement
177152
if cmd.Flags().Changed("trace-all") {
178153
projectConfig.Fuzzing.Testing.TraceAll, err = cmd.Flags().GetBool("trace-all")

cmd/init_flags.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@ func addInitFlags() error {
1010
// Output path for configuration
1111
initCmd.Flags().String("out", "", "output path for the new project configuration file")
1212

13-
// Target file / directory
14-
initCmd.Flags().String("target", "", TargetFlagDescription)
13+
// Target file / directory for compilation
14+
initCmd.Flags().String("compilation-target", "", TargetFlagDescription)
1515

1616
return nil
1717
}
1818

1919
// updateProjectConfigWithInitFlags will update the given projectConfig with any CLI arguments that were provided to the init command
2020
func updateProjectConfigWithInitFlags(cmd *cobra.Command, projectConfig *config.ProjectConfig) error {
21-
// If --target was used
22-
if cmd.Flags().Changed("target") {
21+
// If --compilation-target was used
22+
if cmd.Flags().Changed("compilation-target") {
2323
// Get the new target
24-
newTarget, err := cmd.Flags().GetString("target")
24+
newTarget, err := cmd.Flags().GetString("compilation-target")
2525
if err != nil {
2626
return err
2727
}

fuzzing/config/config.go

+61-27
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,21 @@ package config
33
import (
44
"encoding/json"
55
"errors"
6-
"os"
7-
86
"github.com/crytic/medusa/chain/config"
9-
"github.com/rs/zerolog"
10-
117
"github.com/crytic/medusa/compilation"
8+
"github.com/crytic/medusa/logging"
129
"github.com/crytic/medusa/utils"
10+
"github.com/ethereum/go-ethereum/common/hexutil"
11+
"github.com/rs/zerolog"
12+
"math/big"
13+
"os"
1314
)
1415

16+
// The following directives will be picked up by the `go generate` command to generate JSON marshaling code from
17+
// templates defined below. They should be preserved for re-use in case we change our structures.
18+
//go:generate go get github.com/fjl/gencodec
19+
//go:generate go run github.com/fjl/gencodec -type FuzzingConfig -field-override fuzzingConfigMarshaling -out gen_fuzzing_config.go
20+
1521
type ProjectConfig struct {
1622
// Fuzzing describes the configuration used in fuzzing campaigns.
1723
Fuzzing FuzzingConfig `json:"fuzzing"`
@@ -50,10 +56,15 @@ type FuzzingConfig struct {
5056
// CoverageEnabled describes whether to use coverage-guided fuzzing
5157
CoverageEnabled bool `json:"coverageEnabled"`
5258

53-
// DeploymentOrder determines the order in which the contracts should be deployed
54-
DeploymentOrder []string `json:"deploymentOrder"`
59+
// TargetContracts are the target contracts for fuzz testing
60+
TargetContracts []string `json:"targetContracts"`
61+
62+
// TargetContractsBalances holds the amount of wei that should be sent during deployment for one or more contracts in
63+
// TargetContracts
64+
TargetContractsBalances []*big.Int `json:"targetContractsBalances"`
5565

56-
// Constructor arguments for contracts deployment. It is available only in init mode
66+
// ConstructorArgs holds the constructor arguments for TargetContracts deployments. It is available via the project
67+
// configuration
5768
ConstructorArgs map[string]map[string]any `json:"constructorArgs"`
5869

5970
// DeployerAddress describe the account address to be used to deploy contracts.
@@ -85,6 +96,13 @@ type FuzzingConfig struct {
8596
TestChainConfig config.TestChainConfig `json:"chainConfig"`
8697
}
8798

99+
// fuzzingConfigMarshaling is a structure that overrides field types during JSON marshaling. It allows FuzzingConfig to
100+
// have its custom marshaling methods auto-generated and will handle type conversions for serialization purposes.
101+
// For example, this enables serialization of big.Int but specifying a different field type to control serialization.
102+
type fuzzingConfigMarshaling struct {
103+
TargetContractsBalances []*hexutil.Big
104+
}
105+
88106
// TestingConfig describes the configuration options used for testing
89107
type TestingConfig struct {
90108
// StopOnFailedTest describes whether the fuzzing.Fuzzer should stop after detecting the first failed test.
@@ -111,7 +129,7 @@ type TestingConfig struct {
111129
AssertionTesting AssertionTestingConfig `json:"assertionTesting"`
112130

113131
// PropertyTesting describes the configuration used for property testing.
114-
PropertyTesting PropertyTestConfig `json:"propertyTesting"`
132+
PropertyTesting PropertyTestingConfig `json:"propertyTesting"`
115133

116134
// OptimizationTesting describes the configuration used for optimization testing.
117135
OptimizationTesting OptimizationTestingConfig `json:"optimizationTesting"`
@@ -125,13 +143,12 @@ type AssertionTestingConfig struct {
125143
// TestViewMethods dictates whether constant/pure/view methods should be tested.
126144
TestViewMethods bool `json:"testViewMethods"`
127145

128-
// AssertionModes describes the various panic codes that can be enabled and be treated as a "failing case"
129-
AssertionModes AssertionModesConfig `json:"assertionModes"`
146+
// PanicCodeConfig describes the various panic codes that can be enabled and be treated as a "failing case"
147+
PanicCodeConfig PanicCodeConfig `json:"panicCodeConfig"`
130148
}
131149

132-
// AssertionModesConfig describes the configuration options for the various modes that can be enabled for assertion
133-
// testing
134-
type AssertionModesConfig struct {
150+
// PanicCodeConfig describes the various panic codes that can be enabled and be treated as a failing assertion test
151+
type PanicCodeConfig struct {
135152
// FailOnCompilerInsertedPanic describes whether a generic compiler inserted panic should be treated as a failing case
136153
FailOnCompilerInsertedPanic bool `json:"failOnCompilerInsertedPanic"`
137154

@@ -163,8 +180,8 @@ type AssertionModesConfig struct {
163180
FailOnCallUninitializedVariable bool `json:"failOnCallUninitializedVariable"`
164181
}
165182

166-
// PropertyTestConfig describes the configuration options used for property testing
167-
type PropertyTestConfig struct {
183+
// PropertyTestingConfig describes the configuration options used for property testing
184+
type PropertyTestingConfig struct {
168185
// Enabled describes whether testing is enabled.
169186
Enabled bool `json:"enabled"`
170187

@@ -255,27 +272,51 @@ func (p *ProjectConfig) WriteToFile(path string) error {
255272
// Validate validates that the ProjectConfig meets certain requirements.
256273
// Returns an error if one occurs.
257274
func (p *ProjectConfig) Validate() error {
275+
// Create logger instance if global logger is available
276+
logger := logging.NewLogger(zerolog.Disabled)
277+
if logging.GlobalLogger != nil {
278+
logger = logging.GlobalLogger.NewSubLogger("module", "fuzzer config")
279+
}
280+
258281
// Verify the worker count is a positive number.
259282
if p.Fuzzing.Workers <= 0 {
260283
return errors.New("project configuration must specify a positive number for the worker count")
261284
}
262285

263286
// Verify that the sequence length is a positive number
264287
if p.Fuzzing.CallSequenceLength <= 0 {
265-
return errors.New("project configuration must specify a positive number for the transaction sequence length")
288+
return errors.New("project configuration must specify a positive number for the transaction sequence lengt")
266289
}
267290

268291
// Verify the worker reset limit is a positive number
269292
if p.Fuzzing.WorkerResetLimit <= 0 {
270293
return errors.New("project configuration must specify a positive number for the worker reset limit")
271294
}
272295

296+
// Verify timeout
297+
if p.Fuzzing.Timeout < 0 {
298+
return errors.New("project configuration must specify a positive number for the timeout")
299+
}
300+
273301
// Verify gas limits are appropriate
274302
if p.Fuzzing.BlockGasLimit < p.Fuzzing.TransactionGasLimit {
275303
return errors.New("project configuration must specify a block gas limit which is not less than the transaction gas limit")
276304
}
277305
if p.Fuzzing.BlockGasLimit == 0 || p.Fuzzing.TransactionGasLimit == 0 {
278-
return errors.New("project configuration must specify a block and transaction gas limit which is non-zero")
306+
return errors.New("project configuration must specify a block and transaction gas limit which are non-zero")
307+
}
308+
309+
// Log warning if max block delay is zero
310+
if p.Fuzzing.MaxBlockNumberDelay == 0 {
311+
logger.Warn("The maximum block number delay is set to zero. Please be aware that transactions will " +
312+
"always be fit in the same block until the block gas limit is reached and that the block number will always " +
313+
"increment by one.")
314+
}
315+
316+
// Log warning if max timestamp delay is zero
317+
if p.Fuzzing.MaxBlockTimestampDelay == 0 {
318+
logger.Warn("The maximum timestamp delay is set to zero. Please be aware that block time jumps will " +
319+
"always be exactly one.")
279320
}
280321

281322
// Verify that senders are well-formed addresses
@@ -288,17 +329,10 @@ func (p *ProjectConfig) Validate() error {
288329
return errors.New("project configuration must specify only a well-formed deployer address")
289330
}
290331

291-
// Verify property testing fields.
292-
if p.Fuzzing.Testing.PropertyTesting.Enabled {
293-
// Test prefixes must be supplied if property testing is enabled.
294-
if len(p.Fuzzing.Testing.PropertyTesting.TestPrefixes) == 0 {
295-
return errors.New("project configuration must specify test name prefixes if property testing is enabled")
296-
}
297-
}
298-
299332
// Ensure that the log level is a valid one
300-
if _, err := zerolog.ParseLevel(p.Logging.Level.String()); err != nil {
301-
return err
333+
level, err := zerolog.ParseLevel(p.Logging.Level.String())
334+
if err != nil || level == zerolog.FatalLevel {
335+
return errors.New("project config must specify a valid log level (trace, debug, info, warn, error, or panic)")
302336
}
303337

304338
return nil

fuzzing/config/config_defaults.go

+16-14
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
testChainConfig "github.com/crytic/medusa/chain/config"
55
"github.com/crytic/medusa/compilation"
66
"github.com/rs/zerolog"
7+
"math/big"
78
)
89

910
// GetDefaultProjectConfig obtains a default configuration for a project. It populates a default compilation config
@@ -32,15 +33,16 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) {
3233
// Create a project configuration
3334
projectConfig := &ProjectConfig{
3435
Fuzzing: FuzzingConfig{
35-
Workers: 10,
36-
WorkerResetLimit: 50,
37-
Timeout: 0,
38-
TestLimit: 0,
39-
CallSequenceLength: 100,
40-
DeploymentOrder: []string{},
41-
ConstructorArgs: map[string]map[string]any{},
42-
CorpusDirectory: "",
43-
CoverageEnabled: true,
36+
Workers: 10,
37+
WorkerResetLimit: 50,
38+
Timeout: 0,
39+
TestLimit: 0,
40+
CallSequenceLength: 100,
41+
TargetContracts: []string{},
42+
TargetContractsBalances: []*big.Int{},
43+
ConstructorArgs: map[string]map[string]any{},
44+
CorpusDirectory: "",
45+
CoverageEnabled: true,
4446
SenderAddresses: []string{
4547
"0x10000",
4648
"0x20000",
@@ -58,20 +60,20 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) {
5860
TestAllContracts: false,
5961
TraceAll: false,
6062
AssertionTesting: AssertionTestingConfig{
61-
Enabled: false,
63+
Enabled: true,
6264
TestViewMethods: false,
63-
AssertionModes: AssertionModesConfig{
65+
PanicCodeConfig: PanicCodeConfig{
6466
FailOnAssertion: true,
6567
},
6668
},
67-
PropertyTesting: PropertyTestConfig{
69+
PropertyTesting: PropertyTestingConfig{
6870
Enabled: true,
6971
TestPrefixes: []string{
70-
"fuzz_",
72+
"property_",
7173
},
7274
},
7375
OptimizationTesting: OptimizationTestingConfig{
74-
Enabled: false,
76+
Enabled: true,
7577
TestPrefixes: []string{
7678
"optimize_",
7779
},

0 commit comments

Comments
 (0)