Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: try removing all reverting txs during shrinking #459

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
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--
0xalpharush marked this conversation as resolved.
Show resolved Hide resolved
}
// 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