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

WIP: Experimental valuegeneration techniques #384

Draft
wants to merge 57 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
b83bc3f
Move CallFrame to utils
Jun 18, 2024
d26cd47
Add ValueGenerationTracer
Jun 18, 2024
0024d74
Import CallFrame from utils for execution_tracer
Jun 18, 2024
385b6fb
Initialize ValueGenerationTracer in fuzzer_worker.go
Jun 18, 2024
af12408
Add a unit test for ValueGenerationTracer
Jun 18, 2024
faf0952
Enable ValueGenerationTracer in configs
Jun 18, 2024
937a7ad
Enable ValueGenerationTracer in medusa.json
Jun 18, 2024
39f7d8e
Modify existing contract to fit our test case
Jun 18, 2024
20ef5ce
Add necessary functions to get event values
Jun 18, 2024
63cf261
Add captureEnteredCallFrame in CaptureEnter
Jun 18, 2024
c60029f
grab events
anishnaik Jun 18, 2024
1695d01
make generating events a ValueGenerationTrace capability
anishnaik Jun 18, 2024
cdab504
Change emitted event values to check for changes in valueSet
s4nsec Jun 21, 2024
92b7699
Store captured event values and their types in a data structure and a…
s4nsec Jun 21, 2024
9118e21
Remove SetValueSet function
s4nsec Jun 24, 2024
6c6bdd2
Modify assert_immediate.sol to check return value collection function…
s4nsec Jun 24, 2024
aeabcdd
Modify fuzzer configs to make debugging easier
s4nsec Jun 24, 2024
f23d608
Remove deprecated storage alternatives for event values
s4nsec Jun 24, 2024
7f56635
Provide a new structure to store both event and return values
s4nsec Jun 24, 2024
0b2f9e6
Get return value of the current call frame
s4nsec Jun 24, 2024
7d1febf
Call getCallFrameReturnValue from captureExitedCallFrame
s4nsec Jun 24, 2024
fc21f10
Provide collected event and return values to MessageResults
s4nsec Jun 24, 2024
492e4c4
Write colleced transaction output values(return, event data) to curre…
s4nsec Jun 24, 2024
0cf6a86
Use AddTransactionOutputValuesToValueSet in fuzzer worker
s4nsec Jun 24, 2024
395667e
Rename ValueGenerationTracingEnabled parameter to ExperimentalValueGe…
s4nsec Jun 27, 2024
01c1de3
Modify the logic to preserve base value set
s4nsec Jun 27, 2024
ba1c045
Set TransactionOutputValues to []any
s4nsec Jun 27, 2024
aeef268
Perform modifications on ValueGenerationTracer methods to adapt to ne…
s4nsec Jun 27, 2024
be7c5b1
Emit and return values of various types
s4nsec Jun 27, 2024
f8874e7
Add a test to check if base value set is preserved throughout the fuz…
s4nsec Jun 27, 2024
233630d
Move ExperimantalValueGenerationEnabled to TestingConfig
s4nsec Jul 1, 2024
5c55b7f
Fix overwriting return values with event values issue
s4nsec Jul 1, 2024
84dd114
Use eventOrReturnValue as variable name to avoid ambiguity
s4nsec Jul 1, 2024
cb7d04f
Grab only if ReturnError is nil
s4nsec Jul 1, 2024
b1c3d0f
Don't expect values when it is contract creation
s4nsec Jul 1, 2024
93799c0
Move ExperimentalValueGenerationEnabled to TestingConfig
s4nsec Jul 17, 2024
ab6d871
Remove type TransactionOutputValues
s4nsec Jul 17, 2024
f2019f5
Add comment descriptions to ValueGenerationTracer members
s4nsec Jul 17, 2024
072c8cb
Remove stale comments and print statements
s4nsec Jul 17, 2024
d8cce90
Prevent return values from being reset
s4nsec Jul 17, 2024
5e10173
Make adding transaction output values value_set.go's responsibility
s4nsec Jul 17, 2024
b496c43
Use Add instead of deprecated AddTransactionOutputValuesToValueSet
s4nsec Jul 17, 2024
f9dd9a0
Use fw.fuzzer.config.Fuzzing.Testing.ExperimentalValueGenerationEnabled
s4nsec Jul 17, 2024
00e8edb
Check whether all values are added and if the base value set is prese…
s4nsec Jul 17, 2024
826aa8f
Reset assert_immediate.sol to default
s4nsec Jul 17, 2024
288337e
Add a contract to test event and return value collection
s4nsec Jul 17, 2024
30f9419
Add to value set inside the unit test
s4nsec Jul 22, 2024
a68363c
Merge remote-tracking branch 'origin/master' into valuegeneration-tracer
s4nsec Jul 23, 2024
0e1763f
Adapt value generation tracer to interface changes
s4nsec Jul 23, 2024
8ac6695
Add new mutation strategy - callSeqGenFuncDuplicateAtRandom
s4nsec Jul 30, 2024
2851e96
code cleanup
anishnaik Jul 30, 2024
14f677b
fix test contract
anishnaik Jul 30, 2024
64e3ca2
wipe value set before test
anishnaik Jul 31, 2024
e94a668
Add expected values to test whether those values are added to the val…
s4nsec Jul 31, 2024
42ab8d3
Merge branch 'master' into valuegeneration-tracer
anishnaik Aug 1, 2024
f12eb1f
Enable JSON coverage reports for valuegeneration tracer benchmarking …
anishnaik Aug 1, 2024
5e2c637
Add latest medusa config
s4nsec Aug 7, 2024
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
1 change: 1 addition & 0 deletions docs/src/static/medusa.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"callSequenceLength": 100,
"corpusDirectory": "",
"coverageEnabled": true,
"valueGenerationTracingEnabled": true,
"targetContracts": [],
"targetContractsBalances": [],
"constructorArgs": {},
Expand Down
2 changes: 2 additions & 0 deletions fuzzing/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ type FuzzingConfig struct {
// CoverageEnabled describes whether to use coverage-guided fuzzing
CoverageEnabled bool `json:"coverageEnabled"`

ValueGenerationTracingEnabled bool `json:"valueGenerationEnabled"`
s4nsec marked this conversation as resolved.
Show resolved Hide resolved

// TargetContracts are the target contracts for fuzz testing
TargetContracts []string `json:"targetContracts"`

Expand Down
23 changes: 12 additions & 11 deletions fuzzing/config/config_defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,18 @@ 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: []*big.Int{},
ConstructorArgs: map[string]map[string]any{},
CorpusDirectory: "",
CoverageEnabled: true,
Workers: 10,
WorkerResetLimit: 50,
Timeout: 0,
TestLimit: 0,
ShrinkLimit: 5_000,
CallSequenceLength: 100,
TargetContracts: []string{},
TargetContractsBalances: []*big.Int{},
ConstructorArgs: map[string]map[string]any{},
CorpusDirectory: "",
CoverageEnabled: true,
ValueGenerationTracingEnabled: true, // Sanan
s4nsec marked this conversation as resolved.
Show resolved Hide resolved
SenderAddresses: []string{
"0x10000",
"0x20000",
Expand Down
13 changes: 7 additions & 6 deletions fuzzing/executiontracer/execution_trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package executiontracer
import (
"encoding/hex"
"fmt"
"github.com/crytic/medusa/utils"
"regexp"
"strings"

Expand All @@ -22,7 +23,7 @@ import (
type ExecutionTrace struct {
// TopLevelCallFrame refers to the root call frame, the first EVM call scope entered when an externally-owned
// address calls upon a contract.
TopLevelCallFrame *CallFrame
TopLevelCallFrame *utils.CallFrame

// contractDefinitions represents the known contract definitions at the time of tracing. This is used to help
// obtain any additional information regarding execution.
Expand All @@ -41,7 +42,7 @@ func newExecutionTrace(contracts contracts.Contracts) *ExecutionTrace {
// This list of elements will hold information about what kind of call it is, wei sent, what method is called, and more.
// Additionally, the list may also hold formatting options for console output. This function also returns a non-empty
// string in case this call frame represents a call to the console.log precompile contract.
func (t *ExecutionTrace) generateCallFrameEnterElements(callFrame *CallFrame) ([]any, string) {
func (t *ExecutionTrace) generateCallFrameEnterElements(callFrame *utils.CallFrame) ([]any, string) {
// Create list of elements and console log string
elements := make([]any, 0)
var consoleLogString string
Expand Down Expand Up @@ -164,7 +165,7 @@ func (t *ExecutionTrace) generateCallFrameEnterElements(callFrame *CallFrame) ([

// generateCallFrameExitElements generates a list of elements describing the return data of the call frame (e.g.
// traditional return data, assertion failure, revert data, etc.). Additionally, the list may also hold formatting options for console output.
func (t *ExecutionTrace) generateCallFrameExitElements(callFrame *CallFrame) []any {
func (t *ExecutionTrace) generateCallFrameExitElements(callFrame *utils.CallFrame) []any {
// Create list of elements
elements := make([]any, 0)

Expand Down Expand Up @@ -250,7 +251,7 @@ func (t *ExecutionTrace) generateCallFrameExitElements(callFrame *CallFrame) []a

// generateEventEmittedElements generates a list of elements used to express an event emission. It contains information about an
// event log such as the topics and the event data. Additionally, the list may also hold formatting options for console output.
func (t *ExecutionTrace) generateEventEmittedElements(callFrame *CallFrame, eventLog *coreTypes.Log) []any {
func (t *ExecutionTrace) generateEventEmittedElements(callFrame *utils.CallFrame, eventLog *coreTypes.Log) []any {
// Create list of elements
elements := make([]any, 0)

Expand Down Expand Up @@ -301,7 +302,7 @@ func (t *ExecutionTrace) generateEventEmittedElements(callFrame *CallFrame, even
// generateElementsAndLogsForCallFrame generates a list of elements and logs for a given call frame and its children.
// The list of elements may also hold formatting options for console output. The list of logs represent calls to the
// console.log precompile contract.
func (t *ExecutionTrace) generateElementsAndLogsForCallFrame(currentDepth int, callFrame *CallFrame) ([]any, []any) {
func (t *ExecutionTrace) generateElementsAndLogsForCallFrame(currentDepth int, callFrame *utils.CallFrame) ([]any, []any) {
// Create list of elements and logs
elements := make([]any, 0)
consoleLogs := make([]any, 0)
Expand Down Expand Up @@ -334,7 +335,7 @@ func (t *ExecutionTrace) generateElementsAndLogsForCallFrame(currentDepth int, c
// Loop for each operation performed in the call frame, to provide a chronological history of operations in the
// frame.
for _, operation := range callFrame.Operations {
if childCallFrame, ok := operation.(*CallFrame); ok {
if childCallFrame, ok := operation.(*utils.CallFrame); ok {
// If this is a call frame being entered, generate information recursively.
childOutputLines, childConsoleLogStrings := t.generateElementsAndLogsForCallFrame(currentDepth+1, childCallFrame)
elements = append(elements, childOutputLines...)
Expand Down
9 changes: 5 additions & 4 deletions fuzzing/executiontracer/execution_tracer.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package executiontracer

import (
"github.com/crytic/medusa/utils"
"math/big"

"github.com/crytic/medusa/chain"
Expand Down Expand Up @@ -45,7 +46,7 @@ type ExecutionTracer struct {
trace *ExecutionTrace

// currentCallFrame references the current call frame being traced.
currentCallFrame *CallFrame
currentCallFrame *utils.CallFrame

// contractDefinitions represents the contract definitions to match for execution traces.
contractDefinitions contracts.Contracts
Expand Down Expand Up @@ -90,7 +91,7 @@ func (t *ExecutionTracer) CaptureTxEnd(restGas uint64) {

// resolveConstructorArgs resolves previously unresolved constructor argument ABI data from the call data, if
// the call frame provided represents a contract deployment.
func (t *ExecutionTracer) resolveCallFrameConstructorArgs(callFrame *CallFrame, contract *contracts.Contract) {
func (t *ExecutionTracer) resolveCallFrameConstructorArgs(callFrame *utils.CallFrame, contract *contracts.Contract) {
// If this is a contract creation and the constructor ABI argument data has not yet been resolved, do so now.
if callFrame.ConstructorArgsData == nil && callFrame.IsContractCreation() {
// We simply slice the compiled bytecode leading the input data off, and we are left with the constructor
Expand All @@ -104,7 +105,7 @@ func (t *ExecutionTracer) resolveCallFrameConstructorArgs(callFrame *CallFrame,

// resolveCallFrameContractDefinitions resolves previously unresolved contract definitions for the To and Code addresses
// used within the provided call frame.
func (t *ExecutionTracer) resolveCallFrameContractDefinitions(callFrame *CallFrame) {
func (t *ExecutionTracer) resolveCallFrameContractDefinitions(callFrame *utils.CallFrame) {
// Try to resolve contract definitions for "to" address
if callFrame.ToContractAbi == nil {
// Try to resolve definitions from cheat code contracts
Expand Down Expand Up @@ -150,7 +151,7 @@ func (t *ExecutionTracer) resolveCallFrameContractDefinitions(callFrame *CallFra
// captureEnteredCallFrame is a helper method used when a new call frame is entered to record information about it.
func (t *ExecutionTracer) captureEnteredCallFrame(fromAddress common.Address, toAddress common.Address, inputData []byte, isContractCreation bool, value *big.Int) {
// Create our call frame struct to track data for this call frame we entered.
callFrameData := &CallFrame{
callFrameData := &utils.CallFrame{
SenderAddress: fromAddress,
ToAddress: toAddress,
ToContractName: "",
Expand Down
36 changes: 36 additions & 0 deletions fuzzing/fuzzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,42 @@ func TestFuzzerHooks(t *testing.T) {
})
}

func TestGetEmittedEvents_ValueGeneration(t *testing.T) {
filePaths := []string{
"testdata/contracts/assertions/assert_immediate.sol",
}

for _, filePath := range filePaths {
runFuzzerTest(t, &fuzzerSolcFileTest{
filePath: filePath,
configUpdates: func(config *config.ProjectConfig) {
config.Fuzzing.TargetContracts = []string{"TestContract"}
//config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnAssertion = true
//config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnAllocateTooMuchMemory = true
//config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnArithmeticUnderflow = true
//config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnCallUninitializedVariable = true
//config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnEnumTypeConversionOutOfBounds = true
//config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnDivideByZero = true
//config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnIncorrectStorageAccess = true
//config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnOutOfBoundsArrayAccess = true
//config.Fuzzing.Testing.AssertionTesting.PanicCodeConfig.FailOnPopEmptyArray = true
config.Fuzzing.Testing.AssertionTesting.Enabled = false
config.Fuzzing.Testing.PropertyTesting.Enabled = false
config.Fuzzing.Testing.OptimizationTesting.Enabled = false
config.Fuzzing.Testing.StopOnNoTests = false
config.Fuzzing.Workers = 1
s4nsec marked this conversation as resolved.
Show resolved Hide resolved
},
method: func(f *fuzzerTestContext) {
// Start the fuzzer
err := f.fuzzer.Start()
assert.NoError(t, err)
// Check for failed assertion tests.
assertFailedTestsExpected(f, true)
},
})
}
}

// TestAssertionMode runs tests to ensure that assertion testing behaves as expected.
func TestAssertionMode(t *testing.T) {
filePaths := []string{
Expand Down
25 changes: 25 additions & 0 deletions fuzzing/fuzzer_worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package fuzzing

import (
"fmt"
"github.com/crytic/medusa/fuzzing/valuegenerationtracer"
"math/big"
"math/rand"

Expand All @@ -28,6 +29,10 @@ type FuzzerWorker struct {
// coverageTracer describes the tracer used to collect coverage maps during fuzzing campaigns.
coverageTracer *coverage.CoverageTracer

// valueGenerationTracer represents the structure that is used for collecting emitted event values and return
// values of executed functions in one sequence.
s4nsec marked this conversation as resolved.
Show resolved Hide resolved
valueGenerationTracer *valuegenerationtracer.ValueGenerationTracer

// testingBaseBlockNumber refers to the block number at which all contracts for testing have been deployed, prior
// to any fuzzing activity. This block number is reverted to after testing each call sequence to reset state.
testingBaseBlockNumber uint64
Expand Down Expand Up @@ -244,6 +249,9 @@ func (fw *FuzzerWorker) updateStateChangingMethods() {
// deployed in the Chain.
// Returns the length of the call sequence tested, any requests for call sequence shrinking, or an error if one occurs.
func (fw *FuzzerWorker) testNextCallSequence() (calls.CallSequence, []ShrinkCallSequenceRequest, error) {
// Copy the existing ValueSet
copyValueSet := fw.ValueSet().Clone()
s4nsec marked this conversation as resolved.
Show resolved Hide resolved

// After testing the sequence, we'll want to rollback changes to reset our testing state.
var err error
defer func() {
Expand Down Expand Up @@ -278,6 +286,14 @@ func (fw *FuzzerWorker) testNextCallSequence() (calls.CallSequence, []ShrinkCall
return true, err
}

// Add event values to copied ValueSet
lastExecutedSequenceElement := currentlyExecutedSequence[len(currentlyExecutedSequence)-1]
messageResults := lastExecutedSequenceElement.ChainReference.MessageResults()
valuegenerationtracer.AddTransactionOutputValuesToValueSet(messageResults, copyValueSet)
s4nsec marked this conversation as resolved.
Show resolved Hide resolved

// Set valueSet to copied valueSet
fw.valueSet = copyValueSet

anishnaik marked this conversation as resolved.
Show resolved Hide resolved
// Loop through each test function, signal our worker tested a call, and collect any requests to shrink
// this call sequence.
for _, callSequenceTestFunc := range fw.fuzzer.Hooks.CallSequenceTestFuncs {
Expand Down Expand Up @@ -546,6 +562,12 @@ func (fw *FuzzerWorker) run(baseTestChain *chain.TestChain) (bool, error) {
fw.coverageTracer = coverage.NewCoverageTracer()
initializedChain.AddTracer(fw.coverageTracer, true, false)
}

if fw.fuzzer.config.Fuzzing.ValueGenerationTracingEnabled {
s4nsec marked this conversation as resolved.
Show resolved Hide resolved
// TODO: Sanan
fw.valueGenerationTracer = valuegenerationtracer.NewValueGenerationTracer(fw.fuzzer.contractDefinitions)
anishnaik marked this conversation as resolved.
Show resolved Hide resolved
initializedChain.AddTracer(fw.valueGenerationTracer, true, false)
}
return nil
})

Expand Down Expand Up @@ -597,6 +619,9 @@ func (fw *FuzzerWorker) run(baseTestChain *chain.TestChain) (bool, error) {
return false, err
}

// Set valueSet to the one that includes event values
//fw.SetValueSet(copyValueSet)
s4nsec marked this conversation as resolved.
Show resolved Hide resolved

// If we have any requests to shrink call sequences, do so now.
for _, shrinkVerifier := range shrinkVerifiers {
_, err = fw.shrinkCallSequence(callSequence, shrinkVerifier)
Expand Down
38 changes: 38 additions & 0 deletions fuzzing/testdata/contracts/assertions/assert_immediate.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,45 @@
pragma solidity ^0.8.0;
anishnaik marked this conversation as resolved.
Show resolved Hide resolved

// This contract includes a function that we will call from TestContract.
contract AnotherContract {
// This function doesn't need to do anything specific for this example.
function externalFunction() public pure returns (string memory) {
return "External function called";
}
}

// This contract ensures the fuzzer can encounter an immediate assertion failure.
contract TestContract {
AnotherContract public anotherContract;

event ValueReceived(uint indexed value, uint second_val);
event ValueNonIndexedReceived(uint firstval, uint secondval);

s4nsec marked this conversation as resolved.
Show resolved Hide resolved
function internalFunction() public returns (string memory) {
anotherContract.externalFunction();
return "Internal function called";
s4nsec marked this conversation as resolved.
Show resolved Hide resolved
}

// Deploy AnotherContract within the TestContract
constructor() {
// internalFunction();
anotherContract = new AnotherContract();
}


function callingMeFails(uint value) public {
// Call internalFunction()
internalFunction();
// Call the external function in AnotherContract.
anotherContract.externalFunction();

uint second_val = 2+12;

emit ValueReceived(value, second_val);
emit ValueNonIndexedReceived(111+111, 444+444);

// ASSERTION: We always fail when you call this function.
assert(false);

}
}
Loading
Loading