diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index 5234ac1d..a520d511 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -589,7 +589,7 @@ func defaultCallSequenceGeneratorConfigFunc(fuzzer *Fuzzer, valueSet *valuegener func defaultShrinkingValueMutatorFunc(fuzzer *Fuzzer, valueSet *valuegeneration.ValueSet, randomProvider *rand.Rand) (valuegeneration.ValueMutator, error) { // Create the shrinking value mutator for the worker. shrinkingValueMutatorConfig := &valuegeneration.ShrinkingValueMutatorConfig{ - ShrinkValueProbability: 0.1, + ShrinkValueProbability: 1, // TODO remove? } shrinkingValueMutator := valuegeneration.NewShrinkingValueMutator(shrinkingValueMutatorConfig, valueSet, randomProvider) return shrinkingValueMutator, nil diff --git a/fuzzing/fuzzer_worker.go b/fuzzing/fuzzer_worker.go index dcac1d0f..7fa09cbf 100644 --- a/fuzzing/fuzzer_worker.go +++ b/fuzzing/fuzzer_worker.go @@ -12,6 +12,7 @@ import ( "github.com/crytic/medusa/fuzzing/valuegeneration" "github.com/crytic/medusa/utils" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "golang.org/x/exp/maps" ) @@ -401,6 +402,22 @@ func (fw *FuzzerWorker) testShrunkenCallSequence(possibleShrunkSequence calls.Ca return validShrunkSequence, nil } +func (fw *FuzzerWorker) shrinkParam(callSequence calls.CallSequence) { + i := fw.randomProvider.Intn(len(callSequence)) + abiValuesMsgData := callSequence[i].Call.DataAbiValues + for j := 0; j < len(abiValuesMsgData.InputValues); j++ { + mutatedInput, _ := valuegeneration.MutateAbiValue(fw.sequenceGenerator.config.ValueGenerator, fw.shrinkingValueMutator, &abiValuesMsgData.Method.Inputs[j].Type, abiValuesMsgData.InputValues[j]) + abiValuesMsgData.InputValues[j] = mutatedInput + } + // Re-encode the message's calldata + callSequence[i].Call.WithDataAbiValues(abiValuesMsgData) +} + +func (fw *FuzzerWorker) shorten(callSequence calls.CallSequence) { + i := fw.randomProvider.Intn(len(callSequence)) + callSequence = append(callSequence[:i], callSequence[i+1:]...) +} + // shrinkCallSequence takes a provided call sequence and attempts to shrink it by looking for redundant // calls which can be removed, and values which can be minimized, while continuing to satisfy the provided shrink // verifier. @@ -421,81 +438,60 @@ func (fw *FuzzerWorker) shrinkCallSequence(callSequence calls.CallSequence, shri return shrinkIteration >= shrinkLimit || utils.CheckContextDone(fw.fuzzer.ctx) } if shrinkLimit > 0 { - // The first pass of shrinking is greedy towards trying to remove any unnecessary calls. - // For each call in the sequence, the following removal strategies are used: - // 1) Plain removal (lower block/time gap between surrounding blocks, maintain properties of max delay) - // 2) Add block/time delay to previous call (retain original block/time, possibly exceed max delays) - // At worst, this costs `2 * len(callSequence)` shrink iterations. fw.workerMetrics().shrinking = true fw.fuzzer.logger.Info(fmt.Sprintf("[Worker %d] Shrinking call sequence with %d call(s)", fw.workerIndex, len(callSequence))) - for removalStrategy := 0; removalStrategy < 2 && !shrinkingEnded(); removalStrategy++ { - for i := len(optimizedSequence) - 1; i >= 0 && !shrinkingEnded(); i-- { - // Recreate our current optimized sequence without the item at this index - possibleShrunkSequence, err := optimizedSequence.Clone() - removedCall := possibleShrunkSequence[i] - if err != nil { - return nil, err - } - possibleShrunkSequence = append(possibleShrunkSequence[:i], possibleShrunkSequence[i+1:]...) - - // Exercise the next removal strategy for this call. - if removalStrategy == 0 { - // Case 1: Plain removal. - } else if removalStrategy == 1 { - // Case 2: Add block/time delay to previous call. - if i > 0 { - possibleShrunkSequence[i-1].BlockNumberDelay += removedCall.BlockNumberDelay - possibleShrunkSequence[i-1].BlockTimestampDelay += removedCall.BlockTimestampDelay - } - } - - // Test the shrunken sequence. - validShrunkSequence, err := fw.testShrunkenCallSequence(possibleShrunkSequence, shrinkRequest) - shrinkIteration++ - if err != nil { - return nil, err - } - - // If the current sequence satisfied our conditions, set it as our optimized sequence. - if validShrunkSequence { - optimizedSequence = possibleShrunkSequence - } + // First, remove all reverting txs from the sequence. + var withoutReverts calls.CallSequence + for i := 0; i < len(optimizedSequence) && !shrinkingEnded(); i++ { + var err error + withoutReverts, err = optimizedSequence.Clone() + if err != nil { + return nil, err + } + lastCall := withoutReverts[i] + lastCallChainReference := lastCall.ChainReference + lastMessageResult := lastCallChainReference.Block.MessageResults[lastCallChainReference.TransactionIndex] + if lastMessageResult.Receipt.Status == types.ReceiptStatusFailed { + withoutReverts = append(withoutReverts[:i], withoutReverts[i+1:]...) } } + // Test the sequence with all reverts removed. + validShrunkSequence, err := fw.testShrunkenCallSequence(withoutReverts, shrinkRequest) + if err != nil { + return nil, err + } + + if validShrunkSequence { + optimizedSequence = withoutReverts + } - // The second pass of shrinking attempts to shrink values for each call in our call sequence. - // This is performed exhaustively in a round-robin fashion for each call, until the shrink limit is hit. for !shrinkingEnded() { - for i := len(optimizedSequence) - 1; i >= 0 && !shrinkingEnded(); i-- { - // Clone the optimized sequence. - possibleShrunkSequence, _ := optimizedSequence.Clone() - - // Loop for each argument in the currently indexed call to mutate it. - abiValuesMsgData := possibleShrunkSequence[i].Call.DataAbiValues - for j := 0; j < len(abiValuesMsgData.InputValues); j++ { - mutatedInput, err := valuegeneration.MutateAbiValue(fw.sequenceGenerator.config.ValueGenerator, fw.shrinkingValueMutator, &abiValuesMsgData.Method.Inputs[j].Type, abiValuesMsgData.InputValues[j]) - if err != nil { - return nil, fmt.Errorf("error when shrinking call sequence input argument: %v", err) - } - abiValuesMsgData.InputValues[j] = mutatedInput - } - // Re-encode the message's calldata - possibleShrunkSequence[i].Call.WithDataAbiValues(abiValuesMsgData) + // Clone the optimized sequence. + possibleShrunkSequence, _ := optimizedSequence.Clone() - // Test the shrunken sequence. - validShrunkSequence, err := fw.testShrunkenCallSequence(possibleShrunkSequence, shrinkRequest) - shrinkIteration++ - if err != nil { - return nil, err - } + // Alternate + coinToss := fw.randomProvider.Int() % 2 + if coinToss == 0 || len(possibleShrunkSequence) == 1 { + fw.shrinkParam(possibleShrunkSequence) + } else { + fw.shorten(possibleShrunkSequence) + } - // If this current sequence satisfied our conditions, set it as our optimized sequence. - if validShrunkSequence { - optimizedSequence = possibleShrunkSequence - } + // Test the shrunken sequence. + validShrunkSequence, err := fw.testShrunkenCallSequence(possibleShrunkSequence, shrinkRequest) + shrinkIteration++ + if err != nil { + return nil, err } + + // If this current sequence satisfied our conditions, set it as our optimized sequence. + if validShrunkSequence { + optimizedSequence = possibleShrunkSequence + } + + shrinkLimit-- } fw.workerMetrics().shrinking = false }