Skip to content

Commit 6c13d2a

Browse files
anishnaiks4nsec
authored andcommitted
Enable all testing modes by default, update property mode testing, improve UX, allow for contracts to have starting balances, and fix coverage panic (#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 38ed335 commit 6c13d2a

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"`
@@ -58,10 +64,15 @@ type FuzzingConfig struct {
5864
// CoverageEnabled describes whether to use coverage-guided fuzzing
5965
CoverageEnabled bool `json:"coverageEnabled"`
6066

61-
// DeploymentOrder determines the order in which the contracts should be deployed
62-
DeploymentOrder []string `json:"deploymentOrder"`
67+
// TargetContracts are the target contracts for fuzz testing
68+
TargetContracts []string `json:"targetContracts"`
69+
70+
// TargetContractsBalances holds the amount of wei that should be sent during deployment for one or more contracts in
71+
// TargetContracts
72+
TargetContractsBalances []*big.Int `json:"targetContractsBalances"`
6373

64-
// Constructor arguments for contracts deployment. It is available only in init mode
74+
// ConstructorArgs holds the constructor arguments for TargetContracts deployments. It is available via the project
75+
// configuration
6576
ConstructorArgs map[string]map[string]any `json:"constructorArgs"`
6677

6778
// DeployerAddress describe the account address to be used to deploy contracts.
@@ -93,6 +104,13 @@ type FuzzingConfig struct {
93104
TestChainConfig config.TestChainConfig `json:"chainConfig"`
94105
}
95106

107+
// fuzzingConfigMarshaling is a structure that overrides field types during JSON marshaling. It allows FuzzingConfig to
108+
// have its custom marshaling methods auto-generated and will handle type conversions for serialization purposes.
109+
// For example, this enables serialization of big.Int but specifying a different field type to control serialization.
110+
type fuzzingConfigMarshaling struct {
111+
TargetContractsBalances []*hexutil.Big
112+
}
113+
96114
// TestingConfig describes the configuration options used for testing
97115
type TestingConfig struct {
98116
// StopOnFailedTest describes whether the fuzzing.Fuzzer should stop after detecting the first failed test.
@@ -119,7 +137,7 @@ type TestingConfig struct {
119137
AssertionTesting AssertionTestingConfig `json:"assertionTesting"`
120138

121139
// PropertyTesting describes the configuration used for property testing.
122-
PropertyTesting PropertyTestConfig `json:"propertyTesting"`
140+
PropertyTesting PropertyTestingConfig `json:"propertyTesting"`
123141

124142
// OptimizationTesting describes the configuration used for optimization testing.
125143
OptimizationTesting OptimizationTestingConfig `json:"optimizationTesting"`
@@ -133,13 +151,12 @@ type AssertionTestingConfig struct {
133151
// TestViewMethods dictates whether constant/pure/view methods should be tested.
134152
TestViewMethods bool `json:"testViewMethods"`
135153

136-
// AssertionModes describes the various panic codes that can be enabled and be treated as a "failing case"
137-
AssertionModes AssertionModesConfig `json:"assertionModes"`
154+
// PanicCodeConfig describes the various panic codes that can be enabled and be treated as a "failing case"
155+
PanicCodeConfig PanicCodeConfig `json:"panicCodeConfig"`
138156
}
139157

140-
// AssertionModesConfig describes the configuration options for the various modes that can be enabled for assertion
141-
// testing
142-
type AssertionModesConfig struct {
158+
// PanicCodeConfig describes the various panic codes that can be enabled and be treated as a failing assertion test
159+
type PanicCodeConfig struct {
143160
// FailOnCompilerInsertedPanic describes whether a generic compiler inserted panic should be treated as a failing case
144161
FailOnCompilerInsertedPanic bool `json:"failOnCompilerInsertedPanic"`
145162

@@ -171,8 +188,8 @@ type AssertionModesConfig struct {
171188
FailOnCallUninitializedVariable bool `json:"failOnCallUninitializedVariable"`
172189
}
173190

174-
// PropertyTestConfig describes the configuration options used for property testing
175-
type PropertyTestConfig struct {
191+
// PropertyTestingConfig describes the configuration options used for property testing
192+
type PropertyTestingConfig struct {
176193
// Enabled describes whether testing is enabled.
177194
Enabled bool `json:"enabled"`
178195

@@ -263,27 +280,51 @@ func (p *ProjectConfig) WriteToFile(path string) error {
263280
// Validate validates that the ProjectConfig meets certain requirements.
264281
// Returns an error if one occurs.
265282
func (p *ProjectConfig) Validate() error {
283+
// Create logger instance if global logger is available
284+
logger := logging.NewLogger(zerolog.Disabled)
285+
if logging.GlobalLogger != nil {
286+
logger = logging.GlobalLogger.NewSubLogger("module", "fuzzer config")
287+
}
288+
266289
// Verify the worker count is a positive number.
267290
if p.Fuzzing.Workers <= 0 {
268291
return errors.New("project configuration must specify a positive number for the worker count")
269292
}
270293

271294
// Verify that the sequence length is a positive number
272295
if p.Fuzzing.CallSequenceLength <= 0 {
273-
return errors.New("project configuration must specify a positive number for the transaction sequence length")
296+
return errors.New("project configuration must specify a positive number for the transaction sequence lengt")
274297
}
275298

276299
// Verify the worker reset limit is a positive number
277300
if p.Fuzzing.WorkerResetLimit <= 0 {
278301
return errors.New("project configuration must specify a positive number for the worker reset limit")
279302
}
280303

304+
// Verify timeout
305+
if p.Fuzzing.Timeout < 0 {
306+
return errors.New("project configuration must specify a positive number for the timeout")
307+
}
308+
281309
// Verify gas limits are appropriate
282310
if p.Fuzzing.BlockGasLimit < p.Fuzzing.TransactionGasLimit {
283311
return errors.New("project configuration must specify a block gas limit which is not less than the transaction gas limit")
284312
}
285313
if p.Fuzzing.BlockGasLimit == 0 || p.Fuzzing.TransactionGasLimit == 0 {
286-
return errors.New("project configuration must specify a block and transaction gas limit which is non-zero")
314+
return errors.New("project configuration must specify a block and transaction gas limit which are non-zero")
315+
}
316+
317+
// Log warning if max block delay is zero
318+
if p.Fuzzing.MaxBlockNumberDelay == 0 {
319+
logger.Warn("The maximum block number delay is set to zero. Please be aware that transactions will " +
320+
"always be fit in the same block until the block gas limit is reached and that the block number will always " +
321+
"increment by one.")
322+
}
323+
324+
// Log warning if max timestamp delay is zero
325+
if p.Fuzzing.MaxBlockTimestampDelay == 0 {
326+
logger.Warn("The maximum timestamp delay is set to zero. Please be aware that block time jumps will " +
327+
"always be exactly one.")
287328
}
288329

289330
// Verify that senders are well-formed addresses
@@ -296,17 +337,10 @@ func (p *ProjectConfig) Validate() error {
296337
return errors.New("project configuration must specify only a well-formed deployer address")
297338
}
298339

299-
// Verify property testing fields.
300-
if p.Fuzzing.Testing.PropertyTesting.Enabled {
301-
// Test prefixes must be supplied if property testing is enabled.
302-
if len(p.Fuzzing.Testing.PropertyTesting.TestPrefixes) == 0 {
303-
return errors.New("project configuration must specify test name prefixes if property testing is enabled")
304-
}
305-
}
306-
307340
// Ensure that the log level is a valid one
308-
if _, err := zerolog.ParseLevel(p.Logging.Level.String()); err != nil {
309-
return err
341+
level, err := zerolog.ParseLevel(p.Logging.Level.String())
342+
if err != nil || level == zerolog.FatalLevel {
343+
return errors.New("project config must specify a valid log level (trace, debug, info, warn, error, or panic)")
310344
}
311345

312346
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,17 +33,18 @@ 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: "",
4336
HtmlReportFile: "coverage_report.html",
4437
JsonReportFile: "coverage_report.json",
45-
CoverageEnabled: true,
38+
Workers: 10,
39+
WorkerResetLimit: 50,
40+
Timeout: 0,
41+
TestLimit: 0,
42+
CallSequenceLength: 100,
43+
TargetContracts: []string{},
44+
TargetContractsBalances: []*big.Int{},
45+
ConstructorArgs: map[string]map[string]any{},
46+
CorpusDirectory: "",
47+
CoverageEnabled: true,
4648
SenderAddresses: []string{
4749
"0x10000",
4850
"0x20000",
@@ -60,20 +62,20 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) {
6062
TestAllContracts: false,
6163
TraceAll: false,
6264
AssertionTesting: AssertionTestingConfig{
63-
Enabled: false,
65+
Enabled: true,
6466
TestViewMethods: false,
65-
AssertionModes: AssertionModesConfig{
67+
PanicCodeConfig: PanicCodeConfig{
6668
FailOnAssertion: true,
6769
},
6870
},
69-
PropertyTesting: PropertyTestConfig{
71+
PropertyTesting: PropertyTestingConfig{
7072
Enabled: true,
7173
TestPrefixes: []string{
72-
"fuzz_",
74+
"property_",
7375
},
7476
},
7577
OptimizationTesting: OptimizationTestingConfig{
76-
Enabled: false,
78+
Enabled: true,
7779
TestPrefixes: []string{
7880
"optimize_",
7981
},

0 commit comments

Comments
 (0)