From cc8c4af905bbbb0614700e31252430d91c6fe046 Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Tue, 9 Jul 2024 14:09:23 -0500 Subject: [PATCH] re-execute call sequences, lookup exec. trace by tx hash --- chain/test_chain.go | 97 ++++++++++++++++----- chain/test_chain_test.go | 14 +-- compilation/types/compiled_contract.go | 3 +- fuzzing/calls/call_sequence_execution.go | 22 ++++- fuzzing/corpus/corpus.go | 5 +- fuzzing/executiontracer/execution_tracer.go | 25 ++++-- fuzzing/fuzzer.go | 24 +++-- fuzzing/fuzzer_worker.go | 4 +- fuzzing/test_case_assertion_provider.go | 22 +++-- fuzzing/test_case_optimization_provider.go | 32 ++++--- fuzzing/test_case_property_provider.go | 26 +++--- logging/logger_test.go | 5 +- 12 files changed, 191 insertions(+), 88 deletions(-) diff --git a/chain/test_chain.go b/chain/test_chain.go index 05c01139..5c32e81a 100644 --- a/chain/test_chain.go +++ b/chain/test_chain.go @@ -9,6 +9,8 @@ import ( "github.com/crytic/medusa/chain/config" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb/hashdb" "github.com/holiman/uint256" @@ -84,7 +86,7 @@ type TestChain struct { // NewTestChain creates a simulated Ethereum backend used for testing, or returns an error if one occurred. // This creates a test chain with a test chain configuration and the provided genesis allocation and config. // If a nil config is provided, a default one is used. -func NewTestChain(genesisAlloc core.GenesisAlloc, testChainConfig *config.TestChainConfig) (*TestChain, error) { +func NewTestChain(genesisAlloc types.GenesisAlloc, testChainConfig *config.TestChainConfig) (*TestChain, error) { // Copy our chain config, so it is not shared across chains. chainConfig, err := utils.CopyChainConfig(params.TestChainConfig) if err != nil { @@ -143,7 +145,7 @@ func NewTestChain(genesisAlloc core.GenesisAlloc, testChainConfig *config.TestCh return nil, err } for _, cheatContract := range cheatContracts { - genesisDefinition.Alloc[cheatContract.address] = core.GenesisAccount{ + genesisDefinition.Alloc[cheatContract.address] = types.Account{ Balance: big.NewInt(0), Code: []byte{0xFF}, } @@ -251,7 +253,7 @@ func (t *TestChain) Clone(onCreateFunc func(chain *TestChain) error) (*TestChain // Now add each transaction/message to it. messages := t.blocks[i].Messages for j := 0; j < len(messages); j++ { - err = targetChain.PendingBlockAddTx(messages[j]) + err = targetChain.PendingBlockAddTx(messages[j], nil) if err != nil { return nil, err } @@ -561,7 +563,7 @@ func (t *TestChain) CallContract(msg *core.Message, state *state.StateDB, additi } // Obtain our state snapshot to revert any changes after our call - // snapshot := state.Snapshot() + snapshot := state.Snapshot() // Set infinite balance to the fake caller account state.AddBalance(msg.From, uint256.MustFromBig(math.MaxBig256), tracing.BalanceChangeUnspecified) @@ -585,19 +587,57 @@ func (t *TestChain) CallContract(msg *core.Message, state *state.StateDB, additi }) t.evm = evm + tx := utils.MessageToTransaction(msg) if evm.Config.Tracer != nil && evm.Config.Tracer.OnTxStart != nil { - evm.Config.Tracer.OnTxStart(evm.GetVMContext(), utils.MessageToTransaction(msg), msg.From) + evm.Config.Tracer.OnTxStart(evm.GetVMContext(), tx, msg.From) } // Fund the gas pool, so it can execute endlessly (no block gas limit). gasPool := new(core.GasPool).AddGas(math.MaxUint64) // Perform our state transition to obtain the result. - res, err := core.NewStateTransition(evm, msg, gasPool).TransitionDb() + msgResult, err := core.ApplyMessage(evm, msg, gasPool) // Revert to our state snapshot to undo any changes. - // state.RevertToSnapshot(snapshot) + if err != nil { + state.RevertToSnapshot(snapshot) + } + + // Receipt: + var root []byte + if t.chainConfig.IsByzantium(blockContext.BlockNumber) { + t.state.Finalise(true) + } else { + root = state.IntermediateRoot(t.chainConfig.IsEIP158(blockContext.BlockNumber)).Bytes() + } - return res, err + // Create a new receipt for the transaction, storing the intermediate root and + // gas used by the tx. + receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: msgResult.UsedGas} + if msgResult.Failed() { + receipt.Status = types.ReceiptStatusFailed + } else { + receipt.Status = types.ReceiptStatusSuccessful + } + receipt.TxHash = tx.Hash() + receipt.GasUsed = msgResult.UsedGas + + // If the transaction created a contract, store the creation address in the receipt. + if msg.To == nil { + receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) + } + + // Set the receipt logs and create the bloom filter. + receipt.Logs = t.state.GetLogs(tx.Hash(), blockContext.BlockNumber.Uint64(), blockContext.GetHash(blockContext.BlockNumber.Uint64())) + receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) + receipt.TransactionIndex = uint(0) + + if evm.Config.Tracer != nil { + if evm.Config.Tracer.OnTxEnd != nil { + evm.Config.Tracer.OnTxEnd(receipt, nil) + } + } + + return msgResult, err } // PendingBlock describes the current pending block which is being constructed and awaiting commitment to the chain. @@ -701,16 +741,19 @@ func (t *TestChain) PendingBlockCreateWithParameters(blockNumber uint64, blockTi // PendingBlockAddTx takes a message (internal txs) and adds it to the current pending block, updating the header // with relevant execution information. If a pending block was not created, an error is returned. -// Returns the constructed block, or an error if one occurred. -func (t *TestChain) PendingBlockAddTx(message *core.Message) error { +// Returns an error if one occurred. +func (t *TestChain) PendingBlockAddTx(message *core.Message, getTracerFn func(txIndex int, txHash common.Hash) *tracers.Tracer) error { + if getTracerFn == nil { + getTracerFn = func(txIndex int, txHash common.Hash) *tracers.Tracer { + return t.transactionTracerRouter.NativeTracer.Tracer + } + } + // If we don't have a pending block, return an error if t.pendingBlock == nil { return errors.New("could not add tx to the chain's pending block because no pending block was created") } - // Obtain our state root hash prior to execution. - // previousStateRoot := t.pendingBlock.Header.Root - // Create a gas pool indicating how much gas can be spent executing the transaction. gasPool := new(core.GasPool).AddGas(t.pendingBlock.Header.GasLimit - t.pendingBlock.Header.GasUsed) @@ -721,17 +764,25 @@ func (t *TestChain) PendingBlockAddTx(message *core.Message) error { // TODO reuse blockContext := newTestChainBlockContext(t, t.pendingBlock.Header) - // Create our EVM instance. - evm := vm.NewEVM(blockContext, core.NewEVMTxContext(message), t.state, t.chainConfig, vm.Config{ + vmConfig := vm.Config{ //Debug: true, - Tracer: t.transactionTracerRouter.NativeTracer.Hooks, NoBaseFee: true, ConfigExtensions: t.vmConfigExtensions, - }) - t.evm = evm + } + + tracer := getTracerFn(len(t.pendingBlock.Messages), tx.Hash()) + if tracer != nil { + vmConfig.Tracer = tracer.Hooks + } t.state.SetTxContext(tx.Hash(), len(t.pendingBlock.Messages)) + // Create our EVM instance. + evm := vm.NewEVM(blockContext, core.NewEVMTxContext(message), t.state, t.chainConfig, vmConfig) + + // Set our EVM instance for the test chain in order for cheatcodes to access EVM interpreter's block context. + t.evm = evm + if evm.Config.Tracer != nil && evm.Config.Tracer.OnTxStart != nil { evm.Config.Tracer.OnTxStart(evm.GetVMContext(), tx, message.From) } @@ -934,11 +985,11 @@ func (t *TestChain) emitContractChangeEvents(reverting bool, messageResults ...* Contract: deploymentChange.Contract, }) } else if deploymentChange.Destroyed { - err = t.Events.ContractDeploymentAddedEventEmitter.Publish(ContractDeploymentsAddedEvent{ - Chain: t, - Contract: deploymentChange.Contract, - DynamicDeployment: deploymentChange.DynamicCreation, - }) + // err = t.Events.ContractDeploymentAddedEventEmitter.Publish(ContractDeploymentsAddedEvent{ + // Chain: t, + // Contract: deploymentChange.Contract, + // DynamicDeployment: deploymentChange.DynamicCreation, + // }) } if err != nil { return err diff --git a/chain/test_chain_test.go b/chain/test_chain_test.go index 5c8e6449..693260fc 100644 --- a/chain/test_chain_test.go +++ b/chain/test_chain_test.go @@ -64,12 +64,12 @@ func createChain(t *testing.T) (*TestChain, []common.Address) { assert.NoError(t, err) // NOTE: Sharing GenesisAlloc between nodes will result in some accounts not being funded for some reason. - genesisAlloc := make(core.GenesisAlloc) + genesisAlloc := make(types.GenesisAlloc) // Fund all of our sender addresses in the genesis block initBalance := new(big.Int).Div(abi.MaxInt256, big.NewInt(2)) for _, sender := range senders { - genesisAlloc[sender] = core.GenesisAccount{ + genesisAlloc[sender] = types.Account{ Balance: initBalance, } } @@ -260,7 +260,7 @@ func TestChainDynamicDeployments(t *testing.T) { assert.NoError(t, err) // Add our transaction to the block - err = chain.PendingBlockAddTx(&msg) + err = chain.PendingBlockAddTx(&msg, nil) assert.NoError(t, err) // Commit the pending block to the chain, so it becomes the new head. @@ -385,7 +385,7 @@ func TestChainDeploymentWithArgs(t *testing.T) { assert.NoError(t, err) // Add our transaction to the block - err = chain.PendingBlockAddTx(&msg) + err = chain.PendingBlockAddTx(&msg, nil) assert.NoError(t, err) // Commit the pending block to the chain, so it becomes the new head. @@ -494,7 +494,7 @@ func TestChainCloning(t *testing.T) { assert.NoError(t, err) // Add our transaction to the block - err = chain.PendingBlockAddTx(&msg) + err = chain.PendingBlockAddTx(&msg, nil) assert.NoError(t, err) // Commit the pending block to the chain, so it becomes the new head. @@ -588,7 +588,7 @@ func TestChainCallSequenceReplayMatchSimple(t *testing.T) { assert.NoError(t, err) // Add our transaction to the block - err = chain.PendingBlockAddTx(&msg) + err = chain.PendingBlockAddTx(&msg, nil) assert.NoError(t, err) // Commit the pending block to the chain, so it becomes the new head. @@ -627,7 +627,7 @@ func TestChainCallSequenceReplayMatchSimple(t *testing.T) { _, err := recreatedChain.PendingBlockCreate() assert.NoError(t, err) for _, message := range chain.blocks[i].Messages { - err = recreatedChain.PendingBlockAddTx(message) + err = recreatedChain.PendingBlockAddTx(message, nil) assert.NoError(t, err) } err = recreatedChain.PendingBlockCommit() diff --git a/compilation/types/compiled_contract.go b/compilation/types/compiled_contract.go index 67268fb6..8cb9f57a 100644 --- a/compilation/types/compiled_contract.go +++ b/compilation/types/compiled_contract.go @@ -48,8 +48,7 @@ func (c *CompiledContract) IsMatch(initBytecode []byte, runtimeBytecode []byte) deploymentBytecodeHash := deploymentMetadata.ExtractBytecodeHash() definitionBytecodeHash := definitionMetadata.ExtractBytecodeHash() if deploymentBytecodeHash != nil && definitionBytecodeHash != nil { - x := bytes.Equal(deploymentBytecodeHash, definitionBytecodeHash) - return x + return bytes.Equal(deploymentBytecodeHash, definitionBytecodeHash) } } } diff --git a/fuzzing/calls/call_sequence_execution.go b/fuzzing/calls/call_sequence_execution.go index 824c9c8e..8d1cdb1e 100644 --- a/fuzzing/calls/call_sequence_execution.go +++ b/fuzzing/calls/call_sequence_execution.go @@ -2,7 +2,10 @@ package calls import ( "fmt" + "github.com/crytic/medusa/chain" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/eth/tracers" ) // ExecuteCallSequenceFetchElementFunc describes a function that is called to obtain the next call sequence element to @@ -22,7 +25,7 @@ type ExecuteCallSequenceExecutionCheckFunc func(currentExecutedSequence CallSequ // A "post element executed check" function is provided to check whether execution should stop after each element is // executed. // Returns the call sequence which was executed and an error if one occurs. -func ExecuteCallSequenceIteratively(chain *chain.TestChain, fetchElementFunc ExecuteCallSequenceFetchElementFunc, executionCheckFunc ExecuteCallSequenceExecutionCheckFunc) (CallSequence, error) { +func ExecuteCallSequenceIteratively(chain *chain.TestChain, fetchElementFunc ExecuteCallSequenceFetchElementFunc, executionCheckFunc ExecuteCallSequenceExecutionCheckFunc, getTracerFn func(txIndex int, txHash common.Hash) *tracers.Tracer) (CallSequence, error) { // If there is no fetch element function provided, throw an error if fetchElementFunc == nil { return nil, fmt.Errorf("could not execute call sequence on chain as the 'fetch element function' provided was nil") @@ -84,7 +87,8 @@ func ExecuteCallSequenceIteratively(chain *chain.TestChain, fetchElementFunc Exe } // Try to add our transaction to this block. - err = chain.PendingBlockAddTx(callSequenceElement.Call.ToCoreMessage()) + err = chain.PendingBlockAddTx(callSequenceElement.Call.ToCoreMessage(), getTracerFn) + if err != nil { // If we encountered a block gas limit error, this tx is too expensive to fit in this block. // If there are other transactions in the block, this makes sense. The block is "full". @@ -161,6 +165,18 @@ func ExecuteCallSequence(chain *chain.TestChain, callSequence CallSequence) (Cal return nil, nil } + return ExecuteCallSequenceIteratively(chain, fetchElementFunc, nil, nil) +} + +func ExecuteCallSequenceWithTracer(chain *chain.TestChain, callSequence CallSequence, getTracerFn func(txIndex int, txHash common.Hash) *tracers.Tracer) (CallSequence, error) { + // Execute our sequence with a simple fetch operation provided to obtain each element. + fetchElementFunc := func(currentIndex int) (*CallSequenceElement, error) { + if currentIndex < len(callSequence) { + return callSequence[currentIndex], nil + } + return nil, nil + } + // Execute our provided call sequence iteratively. - return ExecuteCallSequenceIteratively(chain, fetchElementFunc, nil) + return ExecuteCallSequenceIteratively(chain, fetchElementFunc, nil, getTracerFn) } diff --git a/fuzzing/corpus/corpus.go b/fuzzing/corpus/corpus.go index 5a55d498..30583d0c 100644 --- a/fuzzing/corpus/corpus.go +++ b/fuzzing/corpus/corpus.go @@ -210,7 +210,7 @@ func (c *Corpus) initializeSequences(sequenceFiles *corpusDirectory[calls.CallSe } // Execute each call sequence, populating runtime data and collecting coverage data along the way. - _, err = calls.ExecuteCallSequenceIteratively(testChain, fetchElementFunc, executionCheckFunc) + _, err = calls.ExecuteCallSequenceIteratively(testChain, fetchElementFunc, executionCheckFunc, nil) // If we failed to replay a sequence and measure coverage due to an unexpected error, report it. if err != nil { @@ -228,8 +228,7 @@ func (c *Corpus) initializeSequences(sequenceFiles *corpusDirectory[calls.CallSe } // Revert chain state to our starting point to test the next sequence. - err = testChain.RevertToBlockNumber(baseBlockNumber) - if err != nil { + if err := testChain.RevertToBlockNumber(baseBlockNumber); err != nil { return fmt.Errorf("failed to reset the chain while seeding coverage: %v\n", err) } } diff --git a/fuzzing/executiontracer/execution_tracer.go b/fuzzing/executiontracer/execution_tracer.go index b985ff98..d276a2d9 100644 --- a/fuzzing/executiontracer/execution_tracer.go +++ b/fuzzing/executiontracer/execution_tracer.go @@ -6,6 +6,7 @@ import ( "github.com/crytic/medusa/chain" "github.com/crytic/medusa/fuzzing/contracts" + "github.com/crytic/medusa/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" @@ -23,7 +24,7 @@ import ( func CallWithExecutionTrace(testChain *chain.TestChain, contractDefinitions contracts.Contracts, msg *core.Message, state *state.StateDB) (*core.ExecutionResult, *ExecutionTrace, error) { // Create an execution tracer executionTracer := NewExecutionTracer(contractDefinitions, testChain.CheatCodeContracts()) - + defer executionTracer.Close() // Call the contract on our chain with the provided state. executionResult, err := testChain.CallContract(msg, state, executionTracer.NativeTracer) if err != nil { @@ -31,7 +32,8 @@ func CallWithExecutionTrace(testChain *chain.TestChain, contractDefinitions cont } // Obtain our trace - trace := executionTracer.Trace() + hash := utils.MessageToTransaction(msg).Hash() + trace := executionTracer.GetTrace(hash) // Return the trace return executionResult, trace, nil @@ -49,6 +51,8 @@ type ExecutionTracer struct { // trace represents the current execution trace captured by this tracer. trace *ExecutionTrace + traceMap map[common.Hash]*ExecutionTrace + // currentCallFrame references the current call frame being traced. currentCallFrame *CallFrame @@ -72,11 +76,13 @@ func NewExecutionTracer(contractDefinitions contracts.Contracts, cheatCodeContra tracer := &ExecutionTracer{ contractDefinitions: contractDefinitions, cheatCodeContracts: cheatCodeContracts, + traceMap: make(map[common.Hash]*ExecutionTrace), } nativeTracer := &tracers.Tracer{ Hooks: &tracing.Hooks{ OnTxStart: tracer.OnTxStart, OnEnter: tracer.OnEnter, + OnTxEnd: tracer.OnTxEnd, OnExit: tracer.OnExit, OnOpcode: tracer.OnOpcode, }, @@ -85,10 +91,19 @@ func NewExecutionTracer(contractDefinitions contracts.Contracts, cheatCodeContra return tracer } +func (t *ExecutionTracer) Close() { + t.traceMap = nil +} -// Trace returns the currently recording or last recorded execution trace by the tracer. -func (t *ExecutionTracer) Trace() *ExecutionTrace { - return t.trace +// GetTrace returns the currently recording or last recorded execution trace by the tracer. +func (t *ExecutionTracer) GetTrace(txHash common.Hash) *ExecutionTrace { + if trace, ok := t.traceMap[txHash]; ok { + return trace + } + return nil +} +func (t *ExecutionTracer) OnTxEnd(receipt *coretypes.Receipt, err error) { + t.traceMap[receipt.TxHash] = t.trace } // CaptureTxStart is called upon the start of transaction execution, as defined by tracers.Tracer. diff --git a/fuzzing/fuzzer.go b/fuzzing/fuzzer.go index 7add61c5..b58cf6ab 100644 --- a/fuzzing/fuzzer.go +++ b/fuzzing/fuzzer.go @@ -16,6 +16,7 @@ import ( "time" "github.com/crytic/medusa/fuzzing/executiontracer" + msgutils "github.com/crytic/medusa/utils" "github.com/crytic/medusa/fuzzing/coverage" "github.com/crytic/medusa/logging" @@ -25,6 +26,7 @@ import ( "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/utils/randomutils" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/tracers" "github.com/crytic/medusa/chain" compilationTypes "github.com/crytic/medusa/compilation/types" @@ -35,7 +37,6 @@ import ( "github.com/crytic/medusa/utils" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" "golang.org/x/exp/slices" ) @@ -313,18 +314,18 @@ func (f *Fuzzer) AddCompilationTargets(compilations []compilationTypes.Compilati func (f *Fuzzer) createTestChain() (*chain.TestChain, error) { // Create our genesis allocations. // NOTE: Sharing GenesisAlloc between chains will result in some accounts not being funded for some reason. - genesisAlloc := make(core.GenesisAlloc) + genesisAlloc := make(types.GenesisAlloc) // Fund all of our sender addresses in the genesis block initBalance := new(big.Int).Div(abi.MaxInt256, big.NewInt(2)) // TODO: make this configurable for _, sender := range f.senders { - genesisAlloc[sender] = core.GenesisAccount{ + genesisAlloc[sender] = types.Account{ Balance: initBalance, } } // Fund our deployer address in the genesis block - genesisAlloc[f.deployer] = core.GenesisAccount{ + genesisAlloc[f.deployer] = types.Account{ Balance: initBalance, } @@ -398,7 +399,7 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (err } // Add our transaction to the block - err = testChain.PendingBlockAddTx(msg.ToCoreMessage()) + err = testChain.PendingBlockAddTx(msg.ToCoreMessage(), nil) if err != nil { return err, nil } @@ -418,10 +419,17 @@ func chainSetupFromCompilations(fuzzer *Fuzzer, testChain *chain.TestChain) (err Block: block, TransactionIndex: len(block.Messages) - 1, } - // TODO + // TODO determine if this is always the right block num to revert to testChain.RevertToBlockNumber(0) - // Replay the execution trace for the failed contract deployment tx - err = cse.AttachExecutionTrace(testChain, fuzzer.contractDefinitions) + executionTracer := executiontracer.NewExecutionTracer(fuzzer.contractDefinitions, testChain.CheatCodeContracts()) + defer executionTracer.Close() + getTracerFunc := func(txIndex int, txHash common.Hash) *tracers.Tracer { + return executionTracer.NativeTracer.Tracer + } + + _, err = calls.ExecuteCallSequenceWithTracer(testChain, []*calls.CallSequenceElement{cse}, getTracerFunc) + hash := msgutils.MessageToTransaction(cse.Call.ToCoreMessage()).Hash() + cse.ExecutionTrace = executionTracer.GetTrace(hash) // We should be able to attach an execution trace; however, if it fails, we provide the ExecutionResult at a minimum. if err != nil { diff --git a/fuzzing/fuzzer_worker.go b/fuzzing/fuzzer_worker.go index da206e43..f199c5c2 100644 --- a/fuzzing/fuzzer_worker.go +++ b/fuzzing/fuzzer_worker.go @@ -316,7 +316,7 @@ func (fw *FuzzerWorker) testNextCallSequence() (calls.CallSequence, []ShrinkCall } // Execute our call sequence. - testedCallSequence, err := calls.ExecuteCallSequenceIteratively(fw.chain, fetchElementFunc, executionCheckFunc) + testedCallSequence, err := calls.ExecuteCallSequenceIteratively(fw.chain, fetchElementFunc, executionCheckFunc, nil) // If we encountered an error, report it. if err != nil { @@ -382,7 +382,7 @@ func (fw *FuzzerWorker) testShrunkenCallSequence(possibleShrunkSequence calls.Ca } // Execute our call sequence. - _, err = calls.ExecuteCallSequenceIteratively(fw.chain, fetchElementFunc, executionCheckFunc) + _, err = calls.ExecuteCallSequenceIteratively(fw.chain, fetchElementFunc, executionCheckFunc, nil) if err != nil { return false, err } diff --git a/fuzzing/test_case_assertion_provider.go b/fuzzing/test_case_assertion_provider.go index bf06f098..ff5114ba 100644 --- a/fuzzing/test_case_assertion_provider.go +++ b/fuzzing/test_case_assertion_provider.go @@ -7,8 +7,13 @@ import ( "github.com/crytic/medusa/fuzzing/calls" "github.com/crytic/medusa/fuzzing/config" "github.com/crytic/medusa/fuzzing/contracts" + "github.com/crytic/medusa/fuzzing/executiontracer" "github.com/crytic/medusa/fuzzing/utils" + msgutils "github.com/crytic/medusa/utils" "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/eth/tracers" + "golang.org/x/exp/slices" ) @@ -226,14 +231,17 @@ func (t *AssertionTestCaseProvider) callSequencePostCallTest(worker *FuzzerWorke FinishedCallback: func(worker *FuzzerWorker, shrunkenCallSequence calls.CallSequence) error { // When we're finished shrinking, attach an execution trace to the last call if len(shrunkenCallSequence) > 0 { - toExecute := shrunkenCallSequence[:len(shrunkenCallSequence)-1] - if len(toExecute) > 0 { - _, err = calls.ExecuteCallSequence(worker.chain, toExecute) - if err != nil { - panic(err) - } + executionTracer := executiontracer.NewExecutionTracer(worker.fuzzer.contractDefinitions, worker.chain.CheatCodeContracts()) + defer executionTracer.Close() + getTracerFunc := func(txIndex int, txHash common.Hash) *tracers.Tracer { + return executionTracer.NativeTracer.Tracer + } + + _, err = calls.ExecuteCallSequenceWithTracer(worker.chain, shrunkenCallSequence, getTracerFunc) + for _, callSequenceElement := range shrunkenCallSequence { + hash := msgutils.MessageToTransaction(callSequenceElement.Call.ToCoreMessage()).Hash() + callSequenceElement.ExecutionTrace = executionTracer.GetTrace(hash) } - err = shrunkenCallSequence[len(shrunkenCallSequence)-1].AttachExecutionTrace(worker.chain, worker.fuzzer.contractDefinitions) if err != nil { return err } diff --git a/fuzzing/test_case_optimization_provider.go b/fuzzing/test_case_optimization_provider.go index 652d7f0e..edfa899c 100644 --- a/fuzzing/test_case_optimization_provider.go +++ b/fuzzing/test_case_optimization_provider.go @@ -9,7 +9,10 @@ import ( "github.com/crytic/medusa/fuzzing/contracts" "github.com/crytic/medusa/fuzzing/executiontracer" "github.com/crytic/medusa/fuzzing/utils" + msgutils "github.com/crytic/medusa/utils" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/eth/tracers" "golang.org/x/exp/slices" ) @@ -87,8 +90,11 @@ func (t *OptimizationTestCaseProvider) runOptimizationTest(worker *FuzzerWorker, var executionTrace *executiontracer.ExecutionTrace if trace { executionTracer := executiontracer.NewExecutionTracer(worker.fuzzer.contractDefinitions, worker.chain.CheatCodeContracts()) - executionResult, err = worker.Chain().CallContract(msg.ToCoreMessage(), nil, executionTracer.NativeTracer) - executionTrace = executionTracer.Trace() + defer executionTracer.Close() + coreMsg := msg.ToCoreMessage() + tx := msgutils.MessageToTransaction(coreMsg) + executionResult, err = worker.Chain().CallContract(coreMsg, nil, executionTracer.NativeTracer) + executionTrace = executionTracer.GetTrace(tx.Hash()) } else { executionResult, err = worker.Chain().CallContract(msg.ToCoreMessage(), nil) } @@ -283,7 +289,7 @@ func (t *OptimizationTestCaseProvider) callSequencePostCallTest(worker *FuzzerWo for optimizationTestMethodId, workerOptimizationTestMethod := range workerState.optimizationTestMethods { // Obtain the test case for this optimization test method t.testCasesLock.Lock() - testCase, _ := t.testCases[optimizationTestMethodId] + testCase := t.testCases[optimizationTestMethodId] t.testCasesLock.Unlock() // Run our optimization test (create a local copy to avoid loop overwriting the method) @@ -325,19 +331,17 @@ func (t *OptimizationTestCaseProvider) callSequencePostCallTest(worker *FuzzerWo FinishedCallback: func(worker *FuzzerWorker, shrunkenCallSequence calls.CallSequence) error { // When we're finished shrinking, attach an execution trace to the last call if len(shrunkenCallSequence) > 0 { - - err := worker.chain.RevertToBlockNumber(worker.testingBaseBlockNumber) - if err != nil { - panic(err) + executionTracer := executiontracer.NewExecutionTracer(worker.fuzzer.contractDefinitions, worker.chain.CheatCodeContracts()) + defer executionTracer.Close() + getTracerFunc := func(txIndex int, txHash common.Hash) *tracers.Tracer { + return executionTracer.NativeTracer.Tracer } - toExecute := shrunkenCallSequence[:len(shrunkenCallSequence)-1] - if len(toExecute) > 0 { - _, err = calls.ExecuteCallSequence(worker.chain, toExecute) - if err != nil { - panic(err) - } + + _, err = calls.ExecuteCallSequenceWithTracer(worker.chain, shrunkenCallSequence, getTracerFunc) + for _, callSequenceElement := range shrunkenCallSequence { + hash := msgutils.MessageToTransaction(callSequenceElement.Call.ToCoreMessage()).Hash() + callSequenceElement.ExecutionTrace = executionTracer.GetTrace(hash) } - err = shrunkenCallSequence[len(shrunkenCallSequence)-1].AttachExecutionTrace(worker.chain, worker.fuzzer.contractDefinitions) if err != nil { return err } diff --git a/fuzzing/test_case_property_provider.go b/fuzzing/test_case_property_provider.go index bba9a8f6..d370b773 100644 --- a/fuzzing/test_case_property_provider.go +++ b/fuzzing/test_case_property_provider.go @@ -9,7 +9,10 @@ import ( "github.com/crytic/medusa/fuzzing/contracts" "github.com/crytic/medusa/fuzzing/executiontracer" "github.com/crytic/medusa/fuzzing/utils" + msgutils "github.com/crytic/medusa/utils" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/eth/tracers" "golang.org/x/exp/slices" ) @@ -93,7 +96,6 @@ func (t *PropertyTestCaseProvider) checkPropertyTestFailed(worker *FuzzerWorker, } else { executionResult, err = worker.Chain().CallContract(msg.ToCoreMessage(), nil) } - fmt.Println("Execution result", executionResult) if err != nil { return false, nil, fmt.Errorf("failed to call property test method: %v", err) } @@ -320,23 +322,23 @@ func (t *PropertyTestCaseProvider) callSequencePostCallTest(worker *FuzzerWorker FinishedCallback: func(worker *FuzzerWorker, shrunkenCallSequence calls.CallSequence) error { // // When we're finished shrinking, attach an execution trace to the last call if len(shrunkenCallSequence) > 0 { - fmt.Println("Attaching execution trace to last call") - - toExecute := shrunkenCallSequence[:len(shrunkenCallSequence)-1] - if len(toExecute) > 0 { - fmt.Println("Executing shrunken call sequence", toExecute.String()) - _, err = calls.ExecuteCallSequence(worker.chain, toExecute) - if err != nil { - panic(err) - } + executionTracer := executiontracer.NewExecutionTracer(worker.fuzzer.contractDefinitions, worker.chain.CheatCodeContracts()) + defer executionTracer.Close() + getTracerFunc := func(txIndex int, txHash common.Hash) *tracers.Tracer { + return executionTracer.NativeTracer.Tracer + } + + _, err = calls.ExecuteCallSequenceWithTracer(worker.chain, shrunkenCallSequence, getTracerFunc) + for _, callSequenceElement := range shrunkenCallSequence { + hash := msgutils.MessageToTransaction(callSequenceElement.Call.ToCoreMessage()).Hash() + callSequenceElement.ExecutionTrace = executionTracer.GetTrace(hash) } - err = shrunkenCallSequence[len(shrunkenCallSequence)-1].AttachExecutionTrace(worker.chain, worker.fuzzer.contractDefinitions) // shrunkenCallSequence.AttachExecutionTraces(worker.chain, worker.fuzzer.ContractDefinitions()) if err != nil { return err } } - fmt.Println(workerPropertyTestMethod.Method.Name, "failed") + // Execute the property test a final time, this time obtaining an execution trace shrunkenSequenceFailedTest, executionTrace, err := t.checkPropertyTestFailed(worker, &workerPropertyTestMethod, true) if err != nil { diff --git a/logging/logger_test.go b/logging/logger_test.go index 53e0d6a4..f613fcdf 100644 --- a/logging/logger_test.go +++ b/logging/logger_test.go @@ -68,8 +68,9 @@ func TestDisabledColors(t *testing.T) { colors.DisableColor() logger.Info("foo") - // Ensure that msg doesn't include colors afterwards - prefix := fmt.Sprintf("%s %s", colors.LEFT_ARROW, "foo") + // Ensure that msg doesn't include colors afterwards (it is bolded) + prefix := fmt.Sprintf("%s \033[1m%s\033[0m", colors.LEFT_ARROW, "foo") + _, _, ok := strings.Cut(buf.String(), prefix) assert.True(t, ok) }