Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fuzzing/fuzzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,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
Expand Down
125 changes: 61 additions & 64 deletions fuzzing/fuzzer_worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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.
Expand All @@ -421,81 +438,61 @@ 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:]...)
}
shrinkLimit--
}
// 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
}
Expand Down