From c6136a8356ed81a59f70b5caf47c20883fa7791e Mon Sep 17 00:00:00 2001 From: 0xZRA <0xrebaseit@gmail.com> Date: Thu, 24 Apr 2025 17:37:46 -0400 Subject: [PATCH 01/12] initial commit for init fns --- chain/test_chain.go | 3 +- .../static/function_level_testing_medusa.json | 1 + docs/src/static/medusa.json | 1 + fuzzing/config/config_defaults.go | 27 +++--- fuzzing/fuzzer.go | 84 ++++++++++++++++++- fuzzing/fuzzer_hooks.go | 3 +- fuzzing/fuzzer_test.go | 5 +- 7 files changed, 106 insertions(+), 18 deletions(-) diff --git a/chain/test_chain.go b/chain/test_chain.go index 9d244d3d..5a7013c6 100644 --- a/chain/test_chain.go +++ b/chain/test_chain.go @@ -3,9 +3,10 @@ package chain import ( "errors" "fmt" - compilationTypes "github.com/crytic/medusa/compilation/types" "math/big" + compilationTypes "github.com/crytic/medusa/compilation/types" + "github.com/crytic/medusa/chain/state" "golang.org/x/net/context" diff --git a/docs/src/static/function_level_testing_medusa.json b/docs/src/static/function_level_testing_medusa.json index 11934058..060e167b 100644 --- a/docs/src/static/function_level_testing_medusa.json +++ b/docs/src/static/function_level_testing_medusa.json @@ -9,6 +9,7 @@ "coverageEnabled": true, "targetContracts": ["TestDepositContract"], "targetContractsBalances": ["21267647932558653966460912964485513215"], + "TargetContractsInitFunctions": ["setUp"], "constructorArgs": {}, "deployerAddress": "0x30000", "senderAddresses": ["0x10000", "0x20000", "0x30000"], diff --git a/docs/src/static/medusa.json b/docs/src/static/medusa.json index d8d4d3a9..6bf05cba 100644 --- a/docs/src/static/medusa.json +++ b/docs/src/static/medusa.json @@ -13,6 +13,7 @@ "targetContracts": [], "predeployedContracts": {}, "targetContractsBalances": [], + "TargetContractsInitFunctions": [], "constructorArgs": {}, "deployerAddress": "0x30000", "senderAddresses": ["0x10000", "0x20000", "0x30000"], diff --git a/fuzzing/config/config_defaults.go b/fuzzing/config/config_defaults.go index 0e04e201..341c2aac 100644 --- a/fuzzing/config/config_defaults.go +++ b/fuzzing/config/config_defaults.go @@ -39,19 +39,20 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) { // Create a project configuration projectConfig := &ProjectConfig{ Fuzzing: FuzzingConfig{ - Workers: 10, - WorkerResetLimit: 50, - Timeout: 0, - TestLimit: 0, - ShrinkLimit: 5_000, - CallSequenceLength: 100, - TargetContracts: []string{}, - TargetContractsBalances: []*ContractBalance{}, - PredeployedContracts: map[string]string{}, - ConstructorArgs: map[string]map[string]any{}, - CorpusDirectory: "", - CoverageEnabled: true, - CoverageFormats: []string{"html", "lcov"}, + Workers: 10, + WorkerResetLimit: 50, + Timeout: 0, + TestLimit: 0, + ShrinkLimit: 5_000, + CallSequenceLength: 100, + TargetContracts: []string{}, + TargetContractsBalances: []*ContractBalance{}, + TargetContractsInitFunctions: []string{}, + PredeployedContracts: map[string]string{}, + ConstructorArgs: map[string]map[string]any{}, + CorpusDirectory: "", + CoverageEnabled: true, + CoverageFormats: []string{"html", "lcov"}, SenderAddresses: []string{ "0x10000", "0x20000", diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index d37d4770..8b775987 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -483,15 +483,33 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex // while still being able to use the contract address overrides contractsToDeploy := make([]string, 0) balances := make([]*config.ContractBalance, 0) + initFunctions := make([]string, 0) for contractName := range fuzzer.config.Fuzzing.PredeployedContracts { contractsToDeploy = append(contractsToDeploy, contractName) // Preserve index of target contract balances balances = append(balances, &config.ContractBalance{Int: *big.NewInt(0)}) + // Determine which initialization function to call + initFunctions = append(initFunctions, "setUp") // Default init function } + contractsToDeploy = append(contractsToDeploy, fuzzer.config.Fuzzing.TargetContracts...) balances = append(balances, fuzzer.config.Fuzzing.TargetContractsBalances...) + targetContractsCount := len(fuzzer.config.Fuzzing.TargetContracts) + initConfigCount := len(fuzzer.config.Fuzzing.TargetContractsInitFunctions) + + for i := range targetContractsCount { + initFunction := "setUp" // Default + + // Use custom init function if available + if i < initConfigCount && fuzzer.config.Fuzzing.TargetContractsInitFunctions[i] != "" { + initFunction = fuzzer.config.Fuzzing.TargetContractsInitFunctions[i] + } + + initFunctions = append(initFunctions, initFunction) + } + deployedContractAddr := make(map[string]common.Address) // Loop for all contracts to deploy for i, contractName := range contractsToDeploy { @@ -585,9 +603,70 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex // Record our deployed contract so the next config-specified constructor args can reference this // contract by name. deployedContractAddr[contractName] = block.MessageResults[0].Receipt.ContractAddress + contractAddr := deployedContractAddr[contractName] + + // Get the initialization function name + initFunction := "setUp" // Default + if i < len(initFunctions) { + initFunction = initFunctions[i] + } + + // Check if the initialization function exists + contractABI := contract.CompiledContract().Abi + if _, exists := contractABI.Methods[initFunction]; !exists { + fuzzer.logger.Debug(fmt.Sprintf("Init function %s not found on %s, skipping", initFunction, contractName)) + continue + } + + // Pack the function call data + callData, err := contractABI.Pack(initFunction) + if err != nil { + return nil, fmt.Errorf("failed to encode init call to %s: %v", initFunction, err) + } + + // Create and send the transaction + msg = calls.NewCallMessage(fuzzer.deployer, &contractAddr, 0, big.NewInt(0), + fuzzer.config.Fuzzing.BlockGasLimit, nil, nil, nil, callData) + msg.FillFromTestChainProperties(testChain) + + // Create and commit a block with the transaction + block, err = testChain.PendingBlockCreate() + if err != nil { + return nil, fmt.Errorf("failed to create and commit a block with the transaction %s: %v", initFunction, err) + } + + if err = testChain.PendingBlockAddTx(msg.ToCoreMessage()); err != nil { + return nil, fmt.Errorf("failed in PendingBlockAddTx %s: %v", initFunction, err) + } + + if err = testChain.PendingBlockCommit(); err != nil { + return nil, fmt.Errorf("failed in PendingBlockCommit %s: %v", initFunction, err) + } + + // Check if the call succeeded + if block.MessageResults[0].Receipt.Status != types.ReceiptStatusSuccessful { + // Create a call sequence element for the trace + cse := calls.NewCallSequenceElement(nil, msg, 0, 0) + cse.ChainReference = &calls.CallSequenceElementChainReference{ + Block: block, + TransactionIndex: len(block.Messages) - 1, + } - // Flag that we found a matching compiled contract definition and deployed it, then exit out of this - // inner loop to process the next contract to deploy in the outer loop. + // Get execution trace + if err = testChain.RevertToBlockIndex(uint64(len(testChain.CommittedBlocks()) - 1)); err == nil { + calls.ExecuteCallSequenceWithExecutionTracer(testChain, fuzzer.contractDefinitions, + []*calls.CallSequenceElement{cse}, + config.VeryVeryVerbose) + } + + return cse.ExecutionTrace, fmt.Errorf("init function %s on %s failed: %v", + initFunction, contractName, + block.MessageResults[0].ExecutionResult.Err) + } + + fuzzer.logger.Debug(fmt.Sprintf("Successfully called %s on %s", initFunction, contractName)) + // Flag that we found a matching compiled contract definition, deployed it and called available init functions if any, + // then exit out of this inner loop to process the next contract to deploy in the outer loop. found = true break } @@ -598,6 +677,7 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex return nil, fmt.Errorf("%v was specified in the target contracts but was not found in the compilation artifacts", contractName) } } + return nil, nil } diff --git a/fuzzing/fuzzer_hooks.go b/fuzzing/fuzzer_hooks.go index f6be4324..ede734bf 100644 --- a/fuzzing/fuzzer_hooks.go +++ b/fuzzing/fuzzer_hooks.go @@ -1,9 +1,10 @@ package fuzzing import ( - "github.com/crytic/medusa/fuzzing/config" "math/rand" + "github.com/crytic/medusa/fuzzing/config" + "github.com/crytic/medusa/fuzzing/executiontracer" "github.com/crytic/medusa/chain" diff --git a/fuzzing/fuzzer_test.go b/fuzzing/fuzzer_test.go index 6b1292ee..14225613 100644 --- a/fuzzing/fuzzer_test.go +++ b/fuzzing/fuzzer_test.go @@ -349,6 +349,7 @@ func TestConsoleLog(t *testing.T) { filePath: filePath, configUpdates: func(config *config.ProjectConfig) { config.Fuzzing.TargetContracts = []string{"TestContract"} + config.Fuzzing.TargetContractsInitFunctions = []string{""} config.Fuzzing.TestLimit = 10000 config.Fuzzing.Testing.PropertyTesting.Enabled = false config.Fuzzing.Testing.OptimizationTesting.Enabled = false @@ -468,6 +469,7 @@ func TestDeploymentsWithPredeploy(t *testing.T) { configUpdates: func(pkgConfig *config.ProjectConfig) { pkgConfig.Fuzzing.TargetContracts = []string{"TestContract"} pkgConfig.Fuzzing.TargetContractsBalances = []*config.ContractBalance{{Int: *big.NewInt(1)}} + pkgConfig.Fuzzing.TargetContractsInitFunctions = []string{"setUp"} pkgConfig.Fuzzing.TestLimit = 1000 // this test should expose a failure immediately pkgConfig.Fuzzing.Testing.PropertyTesting.Enabled = false pkgConfig.Fuzzing.Testing.OptimizationTesting.Enabled = false @@ -497,7 +499,8 @@ func TestDeploymentsWithPayableConstructors(t *testing.T) { {Int: *big.NewInt(1e18)}, {Int: *big.NewInt(0x1234)}, } - pkgConfig.Fuzzing.TestLimit = 1 // this should happen immediately + pkgConfig.Fuzzing.TestLimit = 1 // this should happen immediately + pkgConfig.Fuzzing.TargetContractsInitFunctions = []string{"", "", ""} // this should execute the setUp functions in the respective contracts pkgConfig.Fuzzing.Testing.AssertionTesting.Enabled = false pkgConfig.Fuzzing.Testing.OptimizationTesting.Enabled = false pkgConfig.Slither.UseSlither = false From b7f6b610fc0f375406193c4b1a875837799eec1b Mon Sep 17 00:00:00 2001 From: 0xZRA <0xrebaseit@gmail.com> Date: Thu, 24 Apr 2025 22:54:14 -0400 Subject: [PATCH 02/12] tests are failing --- fuzzing/config/config.go | 3 ++ fuzzing/fuzzer.go | 93 +++++++++++++++++++--------------------- fuzzing/fuzzer_test.go | 7 +-- 3 files changed, 50 insertions(+), 53 deletions(-) diff --git a/fuzzing/config/config.go b/fuzzing/config/config.go index 1f7aa0f3..2e105aad 100644 --- a/fuzzing/config/config.go +++ b/fuzzing/config/config.go @@ -79,6 +79,9 @@ type FuzzingConfig struct { // TargetContracts TargetContractsBalances []*ContractBalance `json:"targetContractsBalances"` + // TargetContractsInitFunctions is the list of functions to users to specify an "init function" (with setUp() as the default) + TargetContractsInitFunctions []string `json:"targetContractsInitFunctions"` + // ConstructorArgs holds the constructor arguments for TargetContracts deployments. It is available via the project // configuration ConstructorArgs map[string]map[string]any `json:"constructorArgs"` diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index 8b775987..200e5920 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -489,8 +489,6 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex contractsToDeploy = append(contractsToDeploy, contractName) // Preserve index of target contract balances balances = append(balances, &config.ContractBalance{Int: *big.NewInt(0)}) - // Determine which initialization function to call - initFunctions = append(initFunctions, "setUp") // Default init function } contractsToDeploy = append(contractsToDeploy, fuzzer.config.Fuzzing.TargetContracts...) @@ -606,67 +604,62 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex contractAddr := deployedContractAddr[contractName] // Get the initialization function name - initFunction := "setUp" // Default + // initFunction := "setUp" // Default if i < len(initFunctions) { - initFunction = initFunctions[i] - } + initFunction := initFunctions[i] + fuzzer.logger.Info(fmt.Sprintf("Checking if init function %s on %s exists", initFunction, contractName)) - // Check if the initialization function exists - contractABI := contract.CompiledContract().Abi - if _, exists := contractABI.Methods[initFunction]; !exists { - fuzzer.logger.Debug(fmt.Sprintf("Init function %s not found on %s, skipping", initFunction, contractName)) - continue - } + // Check if the initialization function exists + contractABI := contract.CompiledContract().Abi - // Pack the function call data - callData, err := contractABI.Pack(initFunction) - if err != nil { - return nil, fmt.Errorf("failed to encode init call to %s: %v", initFunction, err) - } + if _, exists := contractABI.Methods[initFunction]; !exists { + fuzzer.logger.Info(fmt.Sprintf("Init function %s not found on %s, skipping", initFunction, contractName)) + } else { - // Create and send the transaction - msg = calls.NewCallMessage(fuzzer.deployer, &contractAddr, 0, big.NewInt(0), - fuzzer.config.Fuzzing.BlockGasLimit, nil, nil, nil, callData) - msg.FillFromTestChainProperties(testChain) + // Pack the function call data + callData, err := contractABI.Pack(initFunction) + if err != nil { + fuzzer.logger.Error(fmt.Errorf("failed to encode init call to %s: %v", initFunction, err)) + } - // Create and commit a block with the transaction - block, err = testChain.PendingBlockCreate() - if err != nil { - return nil, fmt.Errorf("failed to create and commit a block with the transaction %s: %v", initFunction, err) - } + // Create and send the transaction + msg = calls.NewCallMessage(fuzzer.deployer, &contractAddr, 0, big.NewInt(0), + fuzzer.config.Fuzzing.BlockGasLimit, nil, nil, nil, callData) + msg.FillFromTestChainProperties(testChain) - if err = testChain.PendingBlockAddTx(msg.ToCoreMessage()); err != nil { - return nil, fmt.Errorf("failed in PendingBlockAddTx %s: %v", initFunction, err) - } + // Create and commit a block with the transaction + block, err = testChain.PendingBlockCreate() + if err != nil { + fuzzer.logger.Error(fmt.Errorf("failed to create and commit a block with the transaction %s: %v", initFunction, err)) + } - if err = testChain.PendingBlockCommit(); err != nil { - return nil, fmt.Errorf("failed in PendingBlockCommit %s: %v", initFunction, err) - } + if err = testChain.PendingBlockAddTx(msg.ToCoreMessage()); err != nil { + fuzzer.logger.Error(fmt.Errorf("failed in PendingBlockAddTx %s: %v", initFunction, err)) + } - // Check if the call succeeded - if block.MessageResults[0].Receipt.Status != types.ReceiptStatusSuccessful { - // Create a call sequence element for the trace - cse := calls.NewCallSequenceElement(nil, msg, 0, 0) - cse.ChainReference = &calls.CallSequenceElementChainReference{ - Block: block, - TransactionIndex: len(block.Messages) - 1, - } + if err = testChain.PendingBlockCommit(); err != nil { + fuzzer.logger.Error(fmt.Errorf("failed in PendingBlockCommit %s: %v", initFunction, err)) + } - // Get execution trace - if err = testChain.RevertToBlockIndex(uint64(len(testChain.CommittedBlocks()) - 1)); err == nil { - calls.ExecuteCallSequenceWithExecutionTracer(testChain, fuzzer.contractDefinitions, - []*calls.CallSequenceElement{cse}, - config.VeryVeryVerbose) + // Check if the call succeeded + if block.MessageResults[0].Receipt.Status != types.ReceiptStatusSuccessful { + // Create a call sequence element for the trace + cse := calls.NewCallSequenceElement(nil, msg, 0, 0) + cse.ChainReference = &calls.CallSequenceElementChainReference{ + Block: block, + TransactionIndex: len(block.Messages) - 1, + } + + fuzzer.logger.Info(fmt.Errorf("init function %s on %s failed: %v", + initFunction, contractName, + block.MessageResults[0].ExecutionResult.Err)) + } + fuzzer.logger.Info(fmt.Sprintf("Successfully called %s on %s", initFunction, contractName)) } - - return cse.ExecutionTrace, fmt.Errorf("init function %s on %s failed: %v", - initFunction, contractName, - block.MessageResults[0].ExecutionResult.Err) } - fuzzer.logger.Debug(fmt.Sprintf("Successfully called %s on %s", initFunction, contractName)) // Flag that we found a matching compiled contract definition, deployed it and called available init functions if any, - // then exit out of this inner loop to process the next contract to deploy in the outer loop. + // then exit out of this inner loop to process the next contract to deploy in the outer loop. found = true break } diff --git a/fuzzing/fuzzer_test.go b/fuzzing/fuzzer_test.go index 14225613..59247c6f 100644 --- a/fuzzing/fuzzer_test.go +++ b/fuzzing/fuzzer_test.go @@ -469,7 +469,7 @@ func TestDeploymentsWithPredeploy(t *testing.T) { configUpdates: func(pkgConfig *config.ProjectConfig) { pkgConfig.Fuzzing.TargetContracts = []string{"TestContract"} pkgConfig.Fuzzing.TargetContractsBalances = []*config.ContractBalance{{Int: *big.NewInt(1)}} - pkgConfig.Fuzzing.TargetContractsInitFunctions = []string{"setUp"} + pkgConfig.Fuzzing.TargetContractsInitFunctions = []string{"testPredeploy"} pkgConfig.Fuzzing.TestLimit = 1000 // this test should expose a failure immediately pkgConfig.Fuzzing.Testing.PropertyTesting.Enabled = false pkgConfig.Fuzzing.Testing.OptimizationTesting.Enabled = false @@ -499,8 +499,8 @@ func TestDeploymentsWithPayableConstructors(t *testing.T) { {Int: *big.NewInt(1e18)}, {Int: *big.NewInt(0x1234)}, } - pkgConfig.Fuzzing.TestLimit = 1 // this should happen immediately - pkgConfig.Fuzzing.TargetContractsInitFunctions = []string{"", "", ""} // this should execute the setUp functions in the respective contracts + pkgConfig.Fuzzing.TestLimit = 1 // this should happen immediately + pkgConfig.Fuzzing.TargetContractsInitFunctions = []string{"setX", "setA", "dummy"} // this should execute predefined functions in the respective contracts pkgConfig.Fuzzing.Testing.AssertionTesting.Enabled = false pkgConfig.Fuzzing.Testing.OptimizationTesting.Enabled = false pkgConfig.Slither.UseSlither = false @@ -1108,6 +1108,7 @@ func TestVerbosityLevels(t *testing.T) { filePath: "testdata/contracts/execution_tracing/verbosity_levels.sol", configUpdates: func(projectConfig *config.ProjectConfig) { projectConfig.Fuzzing.TargetContracts = []string{"TestContract", "HelperContract"} + projectConfig.Fuzzing.TargetContractsInitFunctions = []string{"", ""} projectConfig.Fuzzing.Testing.AssertionTesting.Enabled = true projectConfig.Fuzzing.Testing.PropertyTesting.Enabled = false projectConfig.Fuzzing.Testing.OptimizationTesting.Enabled = false From c8a51444e5b44e9f139159e7f3034ce840214af1 Mon Sep 17 00:00:00 2001 From: 0xZRA <0xrebaseit@gmail.com> Date: Fri, 25 Apr 2025 07:24:34 -0400 Subject: [PATCH 03/12] wip, add init args --- .../static/function_level_testing_medusa.json | 1 + docs/src/static/medusa.json | 1 + fuzzing/config/config.go | 4 +++ fuzzing/config/config_defaults.go | 1 + fuzzing/fuzzer.go | 36 ++++++++++++++++--- 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/docs/src/static/function_level_testing_medusa.json b/docs/src/static/function_level_testing_medusa.json index 060e167b..0030784a 100644 --- a/docs/src/static/function_level_testing_medusa.json +++ b/docs/src/static/function_level_testing_medusa.json @@ -11,6 +11,7 @@ "targetContractsBalances": ["21267647932558653966460912964485513215"], "TargetContractsInitFunctions": ["setUp"], "constructorArgs": {}, + "initializationArgs": {}, "deployerAddress": "0x30000", "senderAddresses": ["0x10000", "0x20000", "0x30000"], "blockNumberDelayMax": 60480, diff --git a/docs/src/static/medusa.json b/docs/src/static/medusa.json index 6bf05cba..7b33624c 100644 --- a/docs/src/static/medusa.json +++ b/docs/src/static/medusa.json @@ -15,6 +15,7 @@ "targetContractsBalances": [], "TargetContractsInitFunctions": [], "constructorArgs": {}, + "initializationArgs": {}, "deployerAddress": "0x30000", "senderAddresses": ["0x10000", "0x20000", "0x30000"], "blockNumberDelayMax": 60480, diff --git a/fuzzing/config/config.go b/fuzzing/config/config.go index 2e105aad..c4018759 100644 --- a/fuzzing/config/config.go +++ b/fuzzing/config/config.go @@ -86,6 +86,10 @@ type FuzzingConfig struct { // configuration ConstructorArgs map[string]map[string]any `json:"constructorArgs"` + // InitializationArgs holds the arguments for TargetContractsInitFunctions deployments. It is available via the project + // configuration + InitializationArgs map[string]map[string]any `json:"initializationArgs"` + // DeployerAddress describe the account address to be used to deploy contracts. DeployerAddress string `json:"deployerAddress"` diff --git a/fuzzing/config/config_defaults.go b/fuzzing/config/config_defaults.go index 341c2aac..58deeff7 100644 --- a/fuzzing/config/config_defaults.go +++ b/fuzzing/config/config_defaults.go @@ -50,6 +50,7 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) { TargetContractsInitFunctions: []string{}, PredeployedContracts: map[string]string{}, ConstructorArgs: map[string]map[string]any{}, + InitializationArgs: map[string]map[string]any{}, CorpusDirectory: "", CoverageEnabled: true, CoverageFormats: []string{"html", "lcov"}, diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index 200e5920..826cc587 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -611,19 +611,42 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex // Check if the initialization function exists contractABI := contract.CompiledContract().Abi - - if _, exists := contractABI.Methods[initFunction]; !exists { + if method, exists := contractABI.Methods[initFunction]; !exists { fuzzer.logger.Info(fmt.Sprintf("Init function %s not found on %s, skipping", initFunction, contractName)) } else { + // Initialization function exists, proceed with calling it + + // Check if the init function accepts parameters and process them if needed + var args []any + if len(method.Inputs) > 0 { + // Look for initialization arguments in the config + jsonArgs, ok := fuzzer.config.Fuzzing.InitializationArgs[contractName] + if !ok { + fuzzer.logger.Error(fmt.Errorf("initialization arguments for contract %s not provided", contractName)) + continue + } + + // Decode the arguments + decoded, err := valuegeneration.DecodeJSONArgumentsFromMap(method.Inputs, + jsonArgs, deployedContractAddr) + if err != nil { + fuzzer.logger.Error(fmt.Errorf("decoding failed for initialization arguments for contract %s", contractName)) + continue + + } + args = decoded + } - // Pack the function call data - callData, err := contractABI.Pack(initFunction) + // Pack the function call data with arguments + callData, err := contractABI.Pack(initFunction, args...) if err != nil { fuzzer.logger.Error(fmt.Errorf("failed to encode init call to %s: %v", initFunction, err)) + continue } // Create and send the transaction - msg = calls.NewCallMessage(fuzzer.deployer, &contractAddr, 0, big.NewInt(0), + destAddr := contractAddr + msg := calls.NewCallMessage(fuzzer.deployer, &destAddr, 0, big.NewInt(0), fuzzer.config.Fuzzing.BlockGasLimit, nil, nil, nil, callData) msg.FillFromTestChainProperties(testChain) @@ -631,14 +654,17 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex block, err = testChain.PendingBlockCreate() if err != nil { fuzzer.logger.Error(fmt.Errorf("failed to create and commit a block with the transaction %s: %v", initFunction, err)) + continue } if err = testChain.PendingBlockAddTx(msg.ToCoreMessage()); err != nil { fuzzer.logger.Error(fmt.Errorf("failed in PendingBlockAddTx %s: %v", initFunction, err)) + continue } if err = testChain.PendingBlockCommit(); err != nil { fuzzer.logger.Error(fmt.Errorf("failed in PendingBlockCommit %s: %v", initFunction, err)) + continue } // Check if the call succeeded From 5b69f15961e3c284bbcffc6e2b7cd7b1257a1efc Mon Sep 17 00:00:00 2001 From: 0xZRA <0xrebaseit@gmail.com> Date: Fri, 25 Apr 2025 08:04:22 -0400 Subject: [PATCH 04/12] remove comment --- fuzzing/fuzzer.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index 826cc587..d6724d7d 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -603,8 +603,7 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex deployedContractAddr[contractName] = block.MessageResults[0].Receipt.ContractAddress contractAddr := deployedContractAddr[contractName] - // Get the initialization function name - // initFunction := "setUp" // Default + // Get the initialization function name if exists if i < len(initFunctions) { initFunction := initFunctions[i] fuzzer.logger.Info(fmt.Sprintf("Checking if init function %s on %s exists", initFunction, contractName)) From 346c172608f88757123d3c899687162c26e51b18 Mon Sep 17 00:00:00 2001 From: 0xZRA <0xrebaseit@gmail.com> Date: Fri, 25 Apr 2025 08:07:28 -0400 Subject: [PATCH 05/12] update error messages, still wip --- fuzzing/fuzzer.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index d6724d7d..042c64ef 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -657,12 +657,14 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex } if err = testChain.PendingBlockAddTx(msg.ToCoreMessage()); err != nil { - fuzzer.logger.Error(fmt.Errorf("failed in PendingBlockAddTx %s: %v", initFunction, err)) + fuzzer.logger.Error(fmt.Errorf("failed to add initialization transaction for function %s on contract %s to pending block: %v", + initFunction, contractName, err)) continue } if err = testChain.PendingBlockCommit(); err != nil { - fuzzer.logger.Error(fmt.Errorf("failed in PendingBlockCommit %s: %v", initFunction, err)) + fuzzer.logger.Error(fmt.Errorf("failed to commit block containing initialization call to function %s on contract %s: %v", + initFunction, contractName, err)) continue } From 7ac775a90e9eb8ed1595e71da63aec188757cb6f Mon Sep 17 00:00:00 2001 From: 0xZRA <0xrebaseit@gmail.com> Date: Fri, 25 Apr 2025 14:04:09 -0400 Subject: [PATCH 06/12] add specific test for init fn --- fuzzing/fuzzer.go | 42 +++++++++--- fuzzing/fuzzer_test.go | 64 +++++++++++++++++-- .../deployments/deploy_with_init_fns.sol | 19 ++++++ 3 files changed, 111 insertions(+), 14 deletions(-) create mode 100644 fuzzing/testdata/contracts/deployments/deploy_with_init_fns.sol diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index 042c64ef..537926f1 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -489,16 +489,20 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex contractsToDeploy = append(contractsToDeploy, contractName) // Preserve index of target contract balances balances = append(balances, &config.ContractBalance{Int: *big.NewInt(0)}) + // Set default empty init function for predeployed contracts + initFunctions = append(initFunctions, "") } contractsToDeploy = append(contractsToDeploy, fuzzer.config.Fuzzing.TargetContracts...) balances = append(balances, fuzzer.config.Fuzzing.TargetContractsBalances...) + // Process target contracts init functions targetContractsCount := len(fuzzer.config.Fuzzing.TargetContracts) initConfigCount := len(fuzzer.config.Fuzzing.TargetContractsInitFunctions) - for i := range targetContractsCount { - initFunction := "setUp" // Default + // Add initialization functions for target contracts + for i := 0; i < targetContractsCount; i++ { + initFunction := "" // No default initialization // Use custom init function if available if i < initConfigCount && fuzzer.config.Fuzzing.TargetContractsInitFunctions[i] != "" { @@ -604,7 +608,7 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex contractAddr := deployedContractAddr[contractName] // Get the initialization function name if exists - if i < len(initFunctions) { + if i < len(initFunctions) && initFunctions[i] != "" { initFunction := initFunctions[i] fuzzer.logger.Info(fmt.Sprintf("Checking if init function %s on %s exists", initFunction, contractName)) @@ -614,10 +618,17 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex fuzzer.logger.Info(fmt.Sprintf("Init function %s not found on %s, skipping", initFunction, contractName)) } else { // Initialization function exists, proceed with calling it + fuzzer.logger.Info(fmt.Sprintf("Found init function %s with %d inputs", initFunction, len(method.Inputs))) // Check if the init function accepts parameters and process them if needed var args []any if len(method.Inputs) > 0 { + // Verify InitializationArgs map exists + if fuzzer.config.Fuzzing.InitializationArgs == nil { + fuzzer.logger.Error(fmt.Errorf("initialization args map is nil but function requires args")) + continue + } + // Look for initialization arguments in the config jsonArgs, ok := fuzzer.config.Fuzzing.InitializationArgs[contractName] if !ok { @@ -625,17 +636,27 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex continue } + // Debug what args we found + fuzzer.logger.Info(fmt.Sprintf("Found args for %s: %+v", contractName, jsonArgs)) + // Decode the arguments decoded, err := valuegeneration.DecodeJSONArgumentsFromMap(method.Inputs, jsonArgs, deployedContractAddr) if err != nil { - fuzzer.logger.Error(fmt.Errorf("decoding failed for initialization arguments for contract %s", contractName)) + fuzzer.logger.Error(fmt.Errorf("decoding failed for initialization arguments for contract %s: %v", + contractName, err)) continue - } + args = decoded + fuzzer.logger.Info(fmt.Sprintf("Decoded %d args for %s function %s", + len(args), contractName, initFunction)) } + // Log before packing + fuzzer.logger.Info(fmt.Sprintf("About to call initialization function %s on contract %s with %d args", + initFunction, contractName, len(args))) + // Pack the function call data with arguments callData, err := contractABI.Pack(initFunction, args...) if err != nil { @@ -649,10 +670,13 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex fuzzer.config.Fuzzing.BlockGasLimit, nil, nil, nil, callData) msg.FillFromTestChainProperties(testChain) + // Debug log after creating the message + fuzzer.logger.Info(fmt.Sprintf("Created message for init function call to %s", initFunction)) + // Create and commit a block with the transaction block, err = testChain.PendingBlockCreate() if err != nil { - fuzzer.logger.Error(fmt.Errorf("failed to create and commit a block with the transaction %s: %v", initFunction, err)) + fuzzer.logger.Error(fmt.Errorf("failed to create pending block for init call: %v", err)) continue } @@ -677,11 +701,13 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex TransactionIndex: len(block.Messages) - 1, } - fuzzer.logger.Info(fmt.Errorf("init function %s on %s failed: %v", + fuzzer.logger.Error(fmt.Errorf("init function %s call failed on %s: %v", initFunction, contractName, block.MessageResults[0].ExecutionResult.Err)) + } else { + fuzzer.logger.Info(fmt.Sprintf("Successfully called %s on %s with %d args", + initFunction, contractName, len(args))) } - fuzzer.logger.Info(fmt.Sprintf("Successfully called %s on %s", initFunction, contractName)) } } diff --git a/fuzzing/fuzzer_test.go b/fuzzing/fuzzer_test.go index 59247c6f..f9b2825e 100644 --- a/fuzzing/fuzzer_test.go +++ b/fuzzing/fuzzer_test.go @@ -349,7 +349,7 @@ func TestConsoleLog(t *testing.T) { filePath: filePath, configUpdates: func(config *config.ProjectConfig) { config.Fuzzing.TargetContracts = []string{"TestContract"} - config.Fuzzing.TargetContractsInitFunctions = []string{""} + config.Fuzzing.TargetContractsInitFunctions = []string{"testConsoleLog"} config.Fuzzing.TestLimit = 10000 config.Fuzzing.Testing.PropertyTesting.Enabled = false config.Fuzzing.Testing.OptimizationTesting.Enabled = false @@ -469,7 +469,6 @@ func TestDeploymentsWithPredeploy(t *testing.T) { configUpdates: func(pkgConfig *config.ProjectConfig) { pkgConfig.Fuzzing.TargetContracts = []string{"TestContract"} pkgConfig.Fuzzing.TargetContractsBalances = []*config.ContractBalance{{Int: *big.NewInt(1)}} - pkgConfig.Fuzzing.TargetContractsInitFunctions = []string{"testPredeploy"} pkgConfig.Fuzzing.TestLimit = 1000 // this test should expose a failure immediately pkgConfig.Fuzzing.Testing.PropertyTesting.Enabled = false pkgConfig.Fuzzing.Testing.OptimizationTesting.Enabled = false @@ -499,8 +498,7 @@ func TestDeploymentsWithPayableConstructors(t *testing.T) { {Int: *big.NewInt(1e18)}, {Int: *big.NewInt(0x1234)}, } - pkgConfig.Fuzzing.TestLimit = 1 // this should happen immediately - pkgConfig.Fuzzing.TargetContractsInitFunctions = []string{"setX", "setA", "dummy"} // this should execute predefined functions in the respective contracts + pkgConfig.Fuzzing.TestLimit = 1 // this should happen immediately pkgConfig.Fuzzing.Testing.AssertionTesting.Enabled = false pkgConfig.Fuzzing.Testing.OptimizationTesting.Enabled = false pkgConfig.Slither.UseSlither = false @@ -517,6 +515,51 @@ func TestDeploymentsWithPayableConstructors(t *testing.T) { }) } +// TestInitializationFunctions runs a test to ensure initialization functions work both with and without arguments +func TestInitializationWithParam(t *testing.T) { + runFuzzerTest(t, &fuzzerSolcFileTest{ + filePath: "testdata/contracts/deployments/deploy_with_init_fns.sol", + configUpdates: func(pkgConfig *config.ProjectConfig) { + // Just a single contract + pkgConfig.Fuzzing.TargetContracts = []string{"SimpleInitParamTest"} + + // With zero balance + pkgConfig.Fuzzing.TargetContractsBalances = []*config.ContractBalance{ + {Int: *big.NewInt(0)}, + } + + // Initialization function with a parameter + pkgConfig.Fuzzing.TargetContractsInitFunctions = []string{"initWithParam"} + + // Create the initialization args map if it doesn't exist + if pkgConfig.Fuzzing.InitializationArgs == nil { + pkgConfig.Fuzzing.InitializationArgs = make(map[string]map[string]any) + } + + // Specify the parameter value - must match the exact parameter name + pkgConfig.Fuzzing.InitializationArgs["SimpleInitParamTest"] = map[string]any{ + "_value": "42", + } + + // Enable property testing + pkgConfig.Fuzzing.Testing.PropertyTesting.Enabled = false + pkgConfig.Fuzzing.TestLimit = 10 + pkgConfig.Fuzzing.Testing.AssertionTesting.Enabled = true + pkgConfig.Fuzzing.Testing.OptimizationTesting.Enabled = false + pkgConfig.Slither.UseSlither = false + + }, + method: func(f *fuzzerTestContext) { + // Start the fuzzer + err := f.fuzzer.Start() + assert.NoError(t, err) + + assertFailedTestsExpected(f, false) + + }, + }) +} + // TestDeploymentsSelfDestruct runs a test to ensure dynamically deployed contracts are detected by the Fuzzer and // their properties are tested appropriately. func TestDeploymentsSelfDestruct(t *testing.T) { @@ -700,7 +743,7 @@ func TestTestingScope(t *testing.T) { // TestDeploymentsWithArgs runs tests to ensure contracts deployed with config provided constructor arguments are // deployed as expected. It expects all properties should fail (indicating values provided were set accordingly). func TestDeploymentsWithArgs(t *testing.T) { - // This contract deploys a contract with specific constructor arguments. Property tests will fail if they are + // This contract deploys a contract with specific constructor arguments as well as init functions with arguments. Property tests will fail if they are // set correctly. runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/deployments/deployment_with_args.sol", @@ -719,6 +762,16 @@ func TestDeploymentsWithArgs(t *testing.T) { "_deployed": "DeployedContract:DeploymentWithArgs", }, } + config.Fuzzing.TargetContractsInitFunctions = []string{"dummyFunction", "dummyFunction"} // this should execute predefined functions in the respective contracts + config.Fuzzing.InitializationArgs = map[string]map[string]any{ + "DeploymentWithArgs": { + "a": "100", // argument for DeploymentWithArgs.dummyFunction + }, + "Dependent": { + "a": "200", // argument for Dependent.dummyFunction + }, + } + config.Fuzzing.Testing.StopOnFailedTest = false config.Fuzzing.TestLimit = 500 // this test should expose a failure quickly. config.Fuzzing.Testing.AssertionTesting.Enabled = false @@ -1108,7 +1161,6 @@ func TestVerbosityLevels(t *testing.T) { filePath: "testdata/contracts/execution_tracing/verbosity_levels.sol", configUpdates: func(projectConfig *config.ProjectConfig) { projectConfig.Fuzzing.TargetContracts = []string{"TestContract", "HelperContract"} - projectConfig.Fuzzing.TargetContractsInitFunctions = []string{"", ""} projectConfig.Fuzzing.Testing.AssertionTesting.Enabled = true projectConfig.Fuzzing.Testing.PropertyTesting.Enabled = false projectConfig.Fuzzing.Testing.OptimizationTesting.Enabled = false diff --git a/fuzzing/testdata/contracts/deployments/deploy_with_init_fns.sol b/fuzzing/testdata/contracts/deployments/deploy_with_init_fns.sol new file mode 100644 index 00000000..6556de80 --- /dev/null +++ b/fuzzing/testdata/contracts/deployments/deploy_with_init_fns.sol @@ -0,0 +1,19 @@ +// Ultra-simple test for initialization functions with parameters +contract SimpleInitParamTest { + // Track if functions were called and parameter values + bool public initCalled; + uint public initValue; + + // Empty constructor + constructor() {} + + // Initialization function with a parameter + function initWithParam(uint _value) public { + initCalled = true; + initValue = _value; + emit InitCalled("initWithParam", _value); + } + + // Event for tracking + event InitCalled(string functionName, uint value); +} \ No newline at end of file From d780d3c67f5eb4f1d011feae111be228e9be503a Mon Sep 17 00:00:00 2001 From: 0xZRA <0xrebaseit@gmail.com> Date: Sun, 27 Apr 2025 11:48:32 -0400 Subject: [PATCH 07/12] update docs --- .../project_configuration/fuzzing_config.md | 19 +++++++++++++++++++ .../static/function_level_testing_medusa.json | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/src/project_configuration/fuzzing_config.md b/docs/src/project_configuration/fuzzing_config.md index 13974545..082e01cd 100644 --- a/docs/src/project_configuration/fuzzing_config.md +++ b/docs/src/project_configuration/fuzzing_config.md @@ -103,6 +103,12 @@ The fuzzing configuration defines the parameters for the fuzzing campaign. then `A` will have a starting balance of `1,234 wei`, `B` will have `4,660 wei (0x1234 in decimal)`, and `C` will have `1.2 ETH (1.2 × 10^18 wei)`. - **Default**: `[]` +### `targetContractsInitFunctions` +- **Type**: [String] (e.g. `["setUp", "initialize", ""]`) +- **Description**: Specifies post-deployment initialization functions to call for each contract in `targetContracts`. This array has a one-to-one mapping with `targetContracts`, where each element corresponds to the initialization function for the contract at the same index. Empty strings indicate no initialization for that contract. +- **Default**: `[]` + + ### `constructorArgs` - **Type**: `{"contractName": {"variableName": _value}}` @@ -110,6 +116,19 @@ The fuzzing configuration defines the parameters for the fuzzing campaign. An example can be found [here](#using-constructorargs). - **Default**: `{}` +### `initializationArgs` + +- **Type**: `{"contractName": {"parameterName": _value}}` +- **Description**: Specifies arguments to pass to initialization functions defined in `targetContractsInitFunctions`. The keys in this map must match the contract names exactly, and the parameter names must match the parameter names in the function signature. + For example, if contract `MyContract` has an initialization function `initialize(uint256 _value, address _owner)`, then you would configure: + ```json + { + "MyContract": { + "_value": "100", + "_owner": "0x1234..." + } + } + ### `deployerAddress` - **Type**: Address diff --git a/docs/src/static/function_level_testing_medusa.json b/docs/src/static/function_level_testing_medusa.json index 0030784a..e036c562 100644 --- a/docs/src/static/function_level_testing_medusa.json +++ b/docs/src/static/function_level_testing_medusa.json @@ -9,7 +9,7 @@ "coverageEnabled": true, "targetContracts": ["TestDepositContract"], "targetContractsBalances": ["21267647932558653966460912964485513215"], - "TargetContractsInitFunctions": ["setUp"], + "TargetContractsInitFunctions": [], "constructorArgs": {}, "initializationArgs": {}, "deployerAddress": "0x30000", From da180341f66eec98a13b583bf5add13828b69eb8 Mon Sep 17 00:00:00 2001 From: 0xZRA <0xrebaseit@gmail.com> Date: Mon, 28 Apr 2025 08:32:54 -0400 Subject: [PATCH 08/12] run prettier --- docs/src/project_configuration/fuzzing_config.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/src/project_configuration/fuzzing_config.md b/docs/src/project_configuration/fuzzing_config.md index 082e01cd..29b5feaa 100644 --- a/docs/src/project_configuration/fuzzing_config.md +++ b/docs/src/project_configuration/fuzzing_config.md @@ -104,11 +104,11 @@ The fuzzing configuration defines the parameters for the fuzzing campaign. - **Default**: `[]` ### `targetContractsInitFunctions` + - **Type**: [String] (e.g. `["setUp", "initialize", ""]`) - **Description**: Specifies post-deployment initialization functions to call for each contract in `targetContracts`. This array has a one-to-one mapping with `targetContracts`, where each element corresponds to the initialization function for the contract at the same index. Empty strings indicate no initialization for that contract. - **Default**: `[]` - ### `constructorArgs` - **Type**: `{"contractName": {"variableName": _value}}` @@ -119,7 +119,7 @@ The fuzzing configuration defines the parameters for the fuzzing campaign. ### `initializationArgs` - **Type**: `{"contractName": {"parameterName": _value}}` -- **Description**: Specifies arguments to pass to initialization functions defined in `targetContractsInitFunctions`. The keys in this map must match the contract names exactly, and the parameter names must match the parameter names in the function signature. +- **Description**: Specifies arguments to pass to initialization functions defined in `targetContractsInitFunctions`. The keys in this map must match the contract names exactly, and the parameter names must match the parameter names in the function signature. For example, if contract `MyContract` has an initialization function `initialize(uint256 _value, address _owner)`, then you would configure: ```json { @@ -128,6 +128,7 @@ The fuzzing configuration defines the parameters for the fuzzing campaign. "_owner": "0x1234..." } } + ``` ### `deployerAddress` From 31b52184ece6f21da6c7327d94097fc94ef8a544 Mon Sep 17 00:00:00 2001 From: rs-ifflabs Date: Tue, 8 Jul 2025 10:37:43 -0400 Subject: [PATCH 09/12] add use-init-fns flag --- cmd/fuzz_flags.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cmd/fuzz_flags.go b/cmd/fuzz_flags.go index 3a1c0c71..9d190847 100644 --- a/cmd/fuzz_flags.go +++ b/cmd/fuzz_flags.go @@ -44,6 +44,9 @@ func addFuzzFlags() error { fuzzCmd.Flags().StringSlice("target-contracts", []string{}, fmt.Sprintf("target contracts for fuzz testing (unless a config file is provided, default is %v)", defaultConfig.Fuzzing.TargetContracts)) + // Will call post-deployment initialization function defined by `targetContractsInitFunctions` to be called on all contracts that have the implementation + fuzzCmd.Flags().Bool("use-init-fns", false, "runs init functions (`setUp`, `initialize`) on all contracts that have the implementation") + // Corpus directory fuzzCmd.Flags().String("corpus-dir", "", fmt.Sprintf("directory path for corpus items and coverage reports (unless a config file is provided, default is %q)", defaultConfig.Fuzzing.CorpusDirectory)) @@ -79,7 +82,9 @@ func addFuzzFlags() error { // Verbosity levels (-v, -vv, -vvv) fuzzCmd.Flags().CountP("verbosity", "v", "set execution trace verbosity levels: -v (top-level calls only), -vv (detailed, default), -vvv (trace all call sequence elements)") + return nil + } // updateProjectConfigWithFuzzFlags will update the given projectConfig with any CLI arguments that were provided to the fuzz command @@ -255,5 +260,20 @@ func updateProjectConfigWithFuzzFlags(cmd *cobra.Command, projectConfig *config. } } + // Update configuration to run init functions + if cmd.Flags().Changed("use-init-fns") { + useInitFns, err := cmd.Flags().GetBool("use-init-fns") + if err != nil { + return err + } + if useInitFns { + defaultConfig, err := config.GetDefaultProjectConfig(DefaultCompilationPlatform) + if err != nil { + return err + } + projectConfig.Fuzzing.TargetContractsInitFunctions = defaultConfig.Fuzzing.TargetContractsInitFunctions + } + } + return nil } From 1e2c1f67427512579d4f3ce168a74ecabe2f6da9 Mon Sep 17 00:00:00 2001 From: rs-ifflabs Date: Wed, 9 Jul 2025 13:07:03 -0400 Subject: [PATCH 10/12] add UseInitFunctions and enableFoundrySetUp --- cmd/fuzz_flags.go | 22 +++++++++++++++++----- docs/src/api/api_overview.md | 3 --- docs/src/static/medusa.json | 3 ++- fuzzing/config/config.go | 29 +++++++++++++++++++++++++---- fuzzing/config/config_defaults.go | 1 + fuzzing/fuzzer.go | 12 +++++++----- fuzzing/fuzzer_test.go | 1 + 7 files changed, 53 insertions(+), 18 deletions(-) diff --git a/cmd/fuzz_flags.go b/cmd/fuzz_flags.go index 9d190847..62eb24f7 100644 --- a/cmd/fuzz_flags.go +++ b/cmd/fuzz_flags.go @@ -47,6 +47,9 @@ func addFuzzFlags() error { // Will call post-deployment initialization function defined by `targetContractsInitFunctions` to be called on all contracts that have the implementation fuzzCmd.Flags().Bool("use-init-fns", false, "runs init functions (`setUp`, `initialize`) on all contracts that have the implementation") + // Will call setUp() function if implemented + fuzzCmd.Flags().Bool("enable-foundry-setup", false, "runs `setUp` function on all contracts that have it implemented") + // Corpus directory fuzzCmd.Flags().String("corpus-dir", "", fmt.Sprintf("directory path for corpus items and coverage reports (unless a config file is provided, default is %q)", defaultConfig.Fuzzing.CorpusDirectory)) @@ -267,11 +270,20 @@ func updateProjectConfigWithFuzzFlags(cmd *cobra.Command, projectConfig *config. return err } if useInitFns { - defaultConfig, err := config.GetDefaultProjectConfig(DefaultCompilationPlatform) - if err != nil { - return err - } - projectConfig.Fuzzing.TargetContractsInitFunctions = defaultConfig.Fuzzing.TargetContractsInitFunctions + // Enable the init functions feature but the actual functions need to be specified in config + projectConfig.Fuzzing.UseInitFunctions = true + } + } + + // Update configuration to run `setUp` function where implemented + if cmd.Flags().Changed("enable-foundry-setup") { + enableFoundrySetUp, err := cmd.Flags().GetBool("enable-foundry-setup") + if err != nil { + return err + } + if enableFoundrySetUp { + projectConfig.Fuzzing.UseInitFunctions = true + projectConfig.Fuzzing.TargetContractsInitFunctions = []string{"setUp"} } } diff --git a/docs/src/api/api_overview.md b/docs/src/api/api_overview.md index 02eb32c2..e688f752 100644 --- a/docs/src/api/api_overview.md +++ b/docs/src/api/api_overview.md @@ -25,14 +25,12 @@ A rudimentary description of the objects/providers and their roles are explained - `ValueGenerator`: This is an object that provides methods to generate values of different kinds for transactions. Examples include the `RandomValueGenerator` and superceding `MutationalValueGenerator`. They are provided a `ValueSet` by their worker, which they may use in generation operations. - `TestChain`: This is a fake chain that operates on fake block structures created for the purpose of testing. Rather than operating on `types.Transaction` (which requires signing), it operates on `core.Message`s, which are derived from transactions and simply allow you to set the `sender` field. It is responsible for: - - Maintaining state of the chain (blocks, transactions in them, results/receipts) - Providing methods to create blocks, add transactions to them, commit them to chain, revert to previous block numbers. - Allowing spoofing of block number and timestamp (committing block number 1, then 50, jumping 49 blocks ahead), while simulating the existence of intermediate blocks. - Provides methods to add tracers such as `evm.Logger` (standard go-ethereum tracers) or extend them with an additional interface (`TestChainTracer`) to also store any captured traced information in the execution results. This allows you to trace EVM execution for certain conditions, store results, and query them at a later time for testing. - `Fuzzer`: This is the main provider for the fuzzing process. It takes a `ProjectConfig` and is responsible for: - - Housing data shared between the `FuzzerWorker`s such as contract definitions, a `ValueSet` derived from compilation to use in value generation, the reference to `Corpus`, the `CoverageMaps` representing all coverage achieved, as well as maintaining `TestCase`s registered to it and printing their results. - Compiling the targets defined by the project config and setting up state. - Provides methods to start/stop the fuzzing process, add additional compilation targets, access the initial value set prior to fuzzing start, access corpus, config, register new test cases and report them finished. @@ -152,7 +150,6 @@ The `Fuzzer` maintains hooks for some of its functionality under `Fuzzer.Hooks.* - `NewValueGeneratorFunc`: This method is used to create a `ValueGenerator` for each `FuzzerWorker`. By default, this uses a `MutationalValueGenerator` constructed with the provided `ValueSet`. It can be replaced to provide a custom `ValueGenerator`. - `TestChainSetupFunc`: This method is used to set up a chain's initial state before fuzzing. By default, this method deploys all contracts compiled and marked for deployment in the `ProjectConfig` provided to the `Fuzzer`. It only deploys contracts if they have no constructor arguments. This can be replaced with your own method to do custom deployments. - - **Note**: We do not recommend replacing this for now, as the `Contract` definitions may not be known to the `Fuzzer`. Additionally, `SenderAddresses` and `DeployerAddress` are the only addresses funded at genesis. This will be updated at a later time. - `CallSequenceTestFuncs`: This is a list of functions which are called after each `FuzzerWorker` executed another call in its current `CallSequence`. It takes the `FuzzerWorker` and `CallSequence` as input, and is expected to return a list of `ShinkRequest`s if some interesting result was found and we wish for the `FuzzerWorker` to shrink the sequence. You can add a function here as part of custom post-call testing methodology to check if some property was violated, then request a shrunken sequence for it with arbitrary criteria to verify the shrunk sequence satisfies your requirements (e.g. violating the same property again). diff --git a/docs/src/static/medusa.json b/docs/src/static/medusa.json index 7b33624c..0ed1d8b2 100644 --- a/docs/src/static/medusa.json +++ b/docs/src/static/medusa.json @@ -13,7 +13,8 @@ "targetContracts": [], "predeployedContracts": {}, "targetContractsBalances": [], - "TargetContractsInitFunctions": [], + "targetContractsInitFunctions": [], + "useInitFunctions": false, "constructorArgs": {}, "initializationArgs": {}, "deployerAddress": "0x30000", diff --git a/fuzzing/config/config.go b/fuzzing/config/config.go index c4018759..65c67f36 100644 --- a/fuzzing/config/config.go +++ b/fuzzing/config/config.go @@ -79,17 +79,20 @@ type FuzzingConfig struct { // TargetContracts TargetContractsBalances []*ContractBalance `json:"targetContractsBalances"` + // Holds the logic whether to run initialization functions supplied by `enable-foundry-setup` or `use-init-fns` + UseInitFunctions bool `json:"useInitFunctions"` + // TargetContractsInitFunctions is the list of functions to users to specify an "init function" (with setUp() as the default) TargetContractsInitFunctions []string `json:"targetContractsInitFunctions"` - // ConstructorArgs holds the constructor arguments for TargetContracts deployments. It is available via the project - // configuration - ConstructorArgs map[string]map[string]any `json:"constructorArgs"` - // InitializationArgs holds the arguments for TargetContractsInitFunctions deployments. It is available via the project // configuration InitializationArgs map[string]map[string]any `json:"initializationArgs"` + // ConstructorArgs holds the constructor arguments for TargetContracts deployments. It is available via the project + // configuration + ConstructorArgs map[string]map[string]any `json:"constructorArgs"` + // DeployerAddress describe the account address to be used to deploy contracts. DeployerAddress string `json:"deployerAddress"` @@ -487,3 +490,21 @@ func (p *ProjectConfig) Validate() error { return nil } + +// Helper function to enable init functions with specific functions +func (p *ProjectConfig) EnableInitFunctions(initFunctions []string) { + p.Fuzzing.UseInitFunctions = true + p.Fuzzing.TargetContractsInitFunctions = initFunctions +} + +// Helper function to enable Foundry setup +func (p *ProjectConfig) EnableFoundrySetup() { + p.Fuzzing.UseInitFunctions = true + p.Fuzzing.TargetContractsInitFunctions = []string{"setUp"} +} + +// Helper function to disable init functions +func (p *ProjectConfig) DisableInitFunctions() { + p.Fuzzing.UseInitFunctions = false + p.Fuzzing.TargetContractsInitFunctions = []string{} +} diff --git a/fuzzing/config/config_defaults.go b/fuzzing/config/config_defaults.go index 58deeff7..d742ee66 100644 --- a/fuzzing/config/config_defaults.go +++ b/fuzzing/config/config_defaults.go @@ -48,6 +48,7 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) { TargetContracts: []string{}, TargetContractsBalances: []*ContractBalance{}, TargetContractsInitFunctions: []string{}, + UseInitFunctions: false, PredeployedContracts: map[string]string{}, ConstructorArgs: map[string]map[string]any{}, InitializationArgs: map[string]map[string]any{}, diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index 537926f1..42ec14d4 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -504,9 +504,11 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex for i := 0; i < targetContractsCount; i++ { initFunction := "" // No default initialization - // Use custom init function if available - if i < initConfigCount && fuzzer.config.Fuzzing.TargetContractsInitFunctions[i] != "" { - initFunction = fuzzer.config.Fuzzing.TargetContractsInitFunctions[i] + if fuzzer.config.Fuzzing.UseInitFunctions { + // Use custom init function if available + if i < initConfigCount && fuzzer.config.Fuzzing.TargetContractsInitFunctions[i] != "" { + initFunction = fuzzer.config.Fuzzing.TargetContractsInitFunctions[i] + } } initFunctions = append(initFunctions, initFunction) @@ -607,8 +609,8 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex deployedContractAddr[contractName] = block.MessageResults[0].Receipt.ContractAddress contractAddr := deployedContractAddr[contractName] - // Get the initialization function name if exists - if i < len(initFunctions) && initFunctions[i] != "" { + // Get the initialization function name if exists and feature is enabled + if fuzzer.config.Fuzzing.UseInitFunctions && i < len(initFunctions) && initFunctions[i] != "" { initFunction := initFunctions[i] fuzzer.logger.Info(fmt.Sprintf("Checking if init function %s on %s exists", initFunction, contractName)) diff --git a/fuzzing/fuzzer_test.go b/fuzzing/fuzzer_test.go index f9b2825e..62256128 100644 --- a/fuzzing/fuzzer_test.go +++ b/fuzzing/fuzzer_test.go @@ -520,6 +520,7 @@ func TestInitializationWithParam(t *testing.T) { runFuzzerTest(t, &fuzzerSolcFileTest{ filePath: "testdata/contracts/deployments/deploy_with_init_fns.sol", configUpdates: func(pkgConfig *config.ProjectConfig) { + pkgConfig.EnableInitFunctions([]string{"initWithParam"}) // Just a single contract pkgConfig.Fuzzing.TargetContracts = []string{"SimpleInitParamTest"} From e7adb949532e2ceb17aac3f6abd8feecdd80469d Mon Sep 17 00:00:00 2001 From: rs-ifflabs Date: Wed, 9 Jul 2025 16:47:11 -0400 Subject: [PATCH 11/12] fix flags file --- cmd/fuzz_flags.go | 3 +++ fuzzing/fuzzer.go | 8 +++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/cmd/fuzz_flags.go b/cmd/fuzz_flags.go index b64e466e..7a9e160e 100644 --- a/cmd/fuzz_flags.go +++ b/cmd/fuzz_flags.go @@ -289,6 +289,8 @@ func updateProjectConfigWithFuzzFlags(cmd *cobra.Command, projectConfig *config. projectConfig.Fuzzing.UseInitFunctions = true projectConfig.Fuzzing.TargetContractsInitFunctions = []string{"setUp"} } + } + // Update log level if cmd.Flags().Changed("log-level") { levelStr, err := cmd.Flags().GetString("log-level") @@ -302,6 +304,7 @@ func updateProjectConfigWithFuzzFlags(cmd *cobra.Command, projectConfig *config. } projectConfig.Logging.Level = level + } return nil diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index 4863990e..d4c2a9be 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -573,15 +573,17 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex // Add initialization functions for target contracts for i := 0; i < targetContractsCount; i++ { - initFunction := "" // No default initialization + initFunction := "" // Default: no initialization if fuzzer.config.Fuzzing.UseInitFunctions { - // Use custom init function if available if i < initConfigCount && fuzzer.config.Fuzzing.TargetContractsInitFunctions[i] != "" { + // Use explicit per-contract config initFunction = fuzzer.config.Fuzzing.TargetContractsInitFunctions[i] + } else if len(fuzzer.config.Fuzzing.TargetContractsInitFunctions) == 1 { + // If only one init function specified (like "setUp") apply it to all contracts + initFunction = fuzzer.config.Fuzzing.TargetContractsInitFunctions[0] } } - initFunctions = append(initFunctions, initFunction) } From c50a8468eb8613475fb923781c44c871f721f90f Mon Sep 17 00:00:00 2001 From: sregz Date: Thu, 30 Oct 2025 11:31:39 -0400 Subject: [PATCH 12/12] address codex comments --- fuzzing/config/config_defaults.go | 2 +- fuzzing/fuzzer.go | 56 +++++++++++++++++++++---------- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/fuzzing/config/config_defaults.go b/fuzzing/config/config_defaults.go index 6b965813..559cff3d 100644 --- a/fuzzing/config/config_defaults.go +++ b/fuzzing/config/config_defaults.go @@ -39,7 +39,7 @@ func GetDefaultProjectConfig(platform string) (*ProjectConfig, error) { // Create a project configuration projectConfig := &ProjectConfig{ Fuzzing: FuzzingConfig{ - Workers: 10, + Workers: 10, WorkerResetLimit: 50, Timeout: 0, TestLimit: 0, diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index ec98637a..0d1d2d49 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -551,12 +551,26 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex // Create a set of target contracts for easy lookup targetContracts := make(map[string]bool) targetContractBalances := make(map[string]*config.ContractBalance) + targetContractInitFunctions := make(map[string]string) for i, name := range fuzzer.config.Fuzzing.TargetContracts { targetContracts[name] = true if i < len(fuzzer.config.Fuzzing.TargetContractsBalances) { targetContractBalances[name] = fuzzer.config.Fuzzing.TargetContractsBalances[i] } + + // Map init functions by contract name + if fuzzer.config.Fuzzing.UseInitFunctions { + initFunction := "" // Default: no initialization + if i < len(fuzzer.config.Fuzzing.TargetContractsInitFunctions) && fuzzer.config.Fuzzing.TargetContractsInitFunctions[i] != "" { + // Use explicit per-contract config + initFunction = fuzzer.config.Fuzzing.TargetContractsInitFunctions[i] + } else if len(fuzzer.config.Fuzzing.TargetContractsInitFunctions) == 1 { + // If only one init function specified (like "setUp") apply it to all contracts + initFunction = fuzzer.config.Fuzzing.TargetContractsInitFunctions[0] + } + targetContractInitFunctions[name] = initFunction + } } // Add contracts from the deployment order for _, name := range fuzzer.deploymentOrder { @@ -569,31 +583,37 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex } else { balances = append(balances, &config.ContractBalance{Int: *big.NewInt(0)}) } + // Add init function for target contracts, empty for libraries/predeployed + if initFunc, ok := targetContractInitFunctions[name]; ok { + initFunctions = append(initFunctions, initFunc) + } else { + initFunctions = append(initFunctions, "") + } } } } else { contractsToDeploy = append(contractsToDeploy, fuzzer.config.Fuzzing.TargetContracts...) balances = append(balances, fuzzer.config.Fuzzing.TargetContractsBalances...) - } - - // Process target contracts init functions - targetContractsCount := len(fuzzer.config.Fuzzing.TargetContracts) - initConfigCount := len(fuzzer.config.Fuzzing.TargetContractsInitFunctions) - // Add initialization functions for target contracts - for i := 0; i < targetContractsCount; i++ { - initFunction := "" // Default: no initialization - - if fuzzer.config.Fuzzing.UseInitFunctions { - if i < initConfigCount && fuzzer.config.Fuzzing.TargetContractsInitFunctions[i] != "" { - // Use explicit per-contract config - initFunction = fuzzer.config.Fuzzing.TargetContractsInitFunctions[i] - } else if len(fuzzer.config.Fuzzing.TargetContractsInitFunctions) == 1 { - // If only one init function specified (like "setUp") apply it to all contracts - initFunction = fuzzer.config.Fuzzing.TargetContractsInitFunctions[0] + // Process target contracts init functions + targetContractsCount := len(fuzzer.config.Fuzzing.TargetContracts) + initConfigCount := len(fuzzer.config.Fuzzing.TargetContractsInitFunctions) + + // Add initialization functions for target contracts + for i := 0; i < targetContractsCount; i++ { + initFunction := "" // Default: no initialization + + if fuzzer.config.Fuzzing.UseInitFunctions { + if i < initConfigCount && fuzzer.config.Fuzzing.TargetContractsInitFunctions[i] != "" { + // Use explicit per-contract config + initFunction = fuzzer.config.Fuzzing.TargetContractsInitFunctions[i] + } else if len(fuzzer.config.Fuzzing.TargetContractsInitFunctions) == 1 { + // If only one init function specified (like "setUp") apply it to all contracts + initFunction = fuzzer.config.Fuzzing.TargetContractsInitFunctions[0] + } } + initFunctions = append(initFunctions, initFunction) } - initFunctions = append(initFunctions, initFunction) } deployedContractAddr := make(map[string]common.Address) @@ -706,7 +726,7 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (*ex // Create and send the transaction destAddr := contractAddr msg := calls.NewCallMessage(fuzzer.deployer, &destAddr, 0, big.NewInt(0), - fuzzer.config.Fuzzing.BlockGasLimit, nil, nil, nil, callData) + blockGasLimit, nil, nil, nil, callData) msg.FillFromTestChainProperties(testChain) // Debug log after creating the message