From 95c223a69e6ad91e755082958de1ef7ad735f335 Mon Sep 17 00:00:00 2001 From: priyankabose Date: Tue, 28 Jan 2025 16:07:45 -0800 Subject: [PATCH 01/10] Implementation for label cheat --- chain/cheat_code_tracer.go | 4 +++ chain/standard_cheat_code_contract.go | 11 +++++++ chain/test_chain.go | 6 ++++ fuzzing/calls/call_sequence.go | 12 ++++++-- fuzzing/executiontracer/execution_trace.go | 9 +++--- utils/address_utils.go | 36 ++++++++++++++++++++++ 6 files changed, 70 insertions(+), 8 deletions(-) diff --git a/chain/cheat_code_tracer.go b/chain/cheat_code_tracer.go index 208b7115..4d931662 100644 --- a/chain/cheat_code_tracer.go +++ b/chain/cheat_code_tracer.go @@ -225,6 +225,10 @@ func (t *cheatCodeTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope t // tracer is used during transaction execution (block creation), the results can later be queried from the block. // This method will only be called on the added tracer if it implements the extended TestChainTracer interface. func (t *cheatCodeTracer) CaptureTxEndSetAdditionalResults(results *types.MessageResults) { + + // Add the address label mappings + results.AdditionalResults["AddressToLabel"] = t.chain.AddressToLabel + // Add our revert operations we collected for this transaction. results.OnRevertHookFuncs = append(results.OnRevertHookFuncs, t.results.onChainRevertHooks...) } diff --git a/chain/standard_cheat_code_contract.go b/chain/standard_cheat_code_contract.go index 97eb3288..edafac35 100644 --- a/chain/standard_cheat_code_contract.go +++ b/chain/standard_cheat_code_contract.go @@ -168,6 +168,17 @@ func getStandardCheatCodeContract(tracer *cheatCodeTracer) (*CheatCodeContract, }, ) + // Label: Sets a label for an address. + contract.addMethod( + "label", abi.Arguments{{Type: typeAddress}, {Type: typeString}}, abi.Arguments{}, + func(tracer *cheatCodeTracer, inputs []any) ([]any, *cheatCodeRawReturnData) { + addr := inputs[0].(common.Address) + lbl := inputs[1].(string) + tracer.chain.AddressToLabel[addr] = lbl + return nil, nil + }, + ) + // Load: Loads a storage slot value from a given account. contract.addMethod( "load", abi.Arguments{{Type: typeAddress}, {Type: typeBytes32}}, abi.Arguments{{Type: typeBytes32}}, diff --git a/chain/test_chain.go b/chain/test_chain.go index 0137c6c3..46eebd6d 100644 --- a/chain/test_chain.go +++ b/chain/test_chain.go @@ -75,6 +75,9 @@ type TestChain struct { // This is constructed over the kvstore. db ethdb.Database + // AddressToLabel maps an address to its label if one exists + AddressToLabel map[common.Address]string + // callTracerRouter forwards tracers.Tracer and TestChainTracer calls to any instances added to it. This // router is used for non-state changing calls. callTracerRouter *TestChainTracerRouter @@ -156,6 +159,8 @@ func NewTestChain(genesisAlloc types.GenesisAlloc, testChainConfig *config.TestC vmConfigExtensions.AdditionalPrecompiles[cheatContract.address] = cheatContract } } + // Initialize address to label mapping + AddressToLabel := make(map[common.Address]string) // Create an in-memory database db := rawdb.NewMemoryDatabase() @@ -188,6 +193,7 @@ func NewTestChain(genesisAlloc types.GenesisAlloc, testChainConfig *config.TestC db: db, state: nil, stateDatabase: stateDatabase, + AddressToLabel: AddressToLabel, transactionTracerRouter: transactionTracerRouter, callTracerRouter: callTracerRouter, testChainConfig: testChainConfig, diff --git a/fuzzing/calls/call_sequence.go b/fuzzing/calls/call_sequence.go index 8e0c8bda..6b73096b 100644 --- a/fuzzing/calls/call_sequence.go +++ b/fuzzing/calls/call_sequence.go @@ -46,12 +46,18 @@ func (cs CallSequence) Log() *logging.LogBuffer { // Construct the buffer for each call made in the sequence for i := 0; i < len(cs); i++ { + addressToLabel, ok := cs[i].ChainReference.MessageResults().AdditionalResults["AddressToLabel"].(map[common.Address]string) + // Add the string representing the call - buffer.Append(fmt.Sprintf("%d) %s\n", i+1, cs[i].String())) + buffer.Append(utils.ResolveAddressToLabelFromString(fmt.Sprintf("%d) %s\n", i+1, cs[i].String()), addressToLabel)) + + if !ok { + panic("AdditionalResults[\"AddressToLabel\"] is not of type map[common.Address]string") + } // If we have an execution trace attached, print information about it. if cs[i].ExecutionTrace != nil { - buffer.Append(cs[i].ExecutionTrace.Log().Elements()...) + buffer.Append(utils.ResolveAddressToLabelFromElements(cs[i].ExecutionTrace.Log().Elements(), addressToLabel)...) buffer.Append("\n") } } @@ -286,7 +292,7 @@ func (cse *CallSequenceElement) String() string { cse.Call.GasLimit, cse.Call.GasPrice.String(), cse.Call.Value.String(), - utils.TrimLeadingZeroesFromAddress(cse.Call.From.String()), + cse.Call.From.String(), ) } diff --git a/fuzzing/executiontracer/execution_trace.go b/fuzzing/executiontracer/execution_trace.go index 3f572d64..76953094 100644 --- a/fuzzing/executiontracer/execution_trace.go +++ b/fuzzing/executiontracer/execution_trace.go @@ -13,7 +13,6 @@ import ( "github.com/crytic/medusa/fuzzing/valuegeneration" "github.com/crytic/medusa/logging" "github.com/crytic/medusa/logging/colors" - "github.com/crytic/medusa/utils" "github.com/ethereum/go-ethereum/accounts/abi" coreTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -142,19 +141,19 @@ func (t *ExecutionTrace) generateCallFrameEnterElements(callFrame *CallFrame) ([ var callInfo string if callFrame.IsProxyCall() { if callFrame.ExecutedCode { - callInfo = fmt.Sprintf("%v -> %v.%v(%v) (addr=%v, code=%v, value=%v, sender=%v)", proxyContractName, codeContractName, methodName, *inputArgumentsDisplayText, utils.TrimLeadingZeroesFromAddress(callFrame.ToAddress.String()), utils.TrimLeadingZeroesFromAddress(callFrame.CodeAddress.String()), callFrame.CallValue, utils.TrimLeadingZeroesFromAddress(callFrame.SenderAddress.String())) + callInfo = fmt.Sprintf("%v -> %v.%v(%v) (addr=%v, code=%v, value=%v, sender=%v)", proxyContractName, codeContractName, methodName, *inputArgumentsDisplayText, callFrame.ToAddress.String(), callFrame.CodeAddress.String(), callFrame.CallValue, callFrame.SenderAddress.String()) } else { - callInfo = fmt.Sprintf("(addr=%v, value=%v, sender=%v)", utils.TrimLeadingZeroesFromAddress(callFrame.ToAddress.String()), callFrame.CallValue, utils.TrimLeadingZeroesFromAddress(callFrame.SenderAddress.String())) + callInfo = fmt.Sprintf("(addr=%v, value=%v, sender=%v)", callFrame.ToAddress.String(), callFrame.CallValue, callFrame.SenderAddress.String()) } } else { if callFrame.ExecutedCode { if callFrame.ToAddress == chain.ConsoleLogContractAddress { callInfo = fmt.Sprintf("%v.%v(%v)", codeContractName, methodName, *inputArgumentsDisplayText) } else { - callInfo = fmt.Sprintf("%v.%v(%v) (addr=%v, value=%v, sender=%v)", codeContractName, methodName, *inputArgumentsDisplayText, utils.TrimLeadingZeroesFromAddress(callFrame.ToAddress.String()), callFrame.CallValue, utils.TrimLeadingZeroesFromAddress(callFrame.SenderAddress.String())) + callInfo = fmt.Sprintf("%v.%v(%v) (addr=%v, value=%v, sender=%v)", codeContractName, methodName, *inputArgumentsDisplayText, callFrame.ToAddress.String(), callFrame.CallValue, callFrame.SenderAddress.String()) } } else { - callInfo = fmt.Sprintf("(addr=%v, value=%v, sender=%v)", utils.TrimLeadingZeroesFromAddress(callFrame.ToAddress.String()), callFrame.CallValue, utils.TrimLeadingZeroesFromAddress(callFrame.SenderAddress.String())) + callInfo = fmt.Sprintf("(addr=%v, value=%v, sender=%v)", callFrame.ToAddress.String(), callFrame.CallValue, callFrame.SenderAddress.String()) } } diff --git a/utils/address_utils.go b/utils/address_utils.go index d1ff41ef..f6c868c6 100644 --- a/utils/address_utils.go +++ b/utils/address_utils.go @@ -2,6 +2,8 @@ package utils import ( "encoding/hex" + "fmt" + "regexp" "strings" "github.com/ethereum/go-ethereum/common" @@ -47,6 +49,40 @@ func HexStringsToAddresses(addressHexStrings []string) ([]common.Address, error) return addresses, nil } +// ResolveAddressToLabelFromElements parse array elements for ethereum addresses and if found replace that with a label if one exists +func ResolveAddressToLabelFromElements(elements []any, addressToLabel map[common.Address]string) []any { + updatedElements := []any{} + for _, element := range elements { + // Check if the element is a string + if str, ok := element.(string); ok { + // Replace addresses in the string with their labels + updatedElements = append(updatedElements, ResolveAddressToLabelFromString(str, addressToLabel)) + } else { + // Keep non-string elements unchanged + updatedElements = append(updatedElements, element) + } + } + + return updatedElements +} + +// ResolveAddressToLabelFromString parse a string for ethereum addresses and if found replace that with a label if one exists +func ResolveAddressToLabelFromString(str string, addressToLabel map[common.Address]string) string { + addressRegex := regexp.MustCompile(`0x[a-fA-F0-9]{40}`) + processedString := addressRegex.ReplaceAllStringFunc(str, func(match string) string { + address := common.HexToAddress(match) // Convert the match to an Ethereum address + if label, exists := addressToLabel[address]; exists { + fmt.Printf("Replacing address %s with label %s in element: %s\n", match, label, str) + return label // Replace address with label + } + trimmedAddress := TrimLeadingZeroesFromAddress(match) + fmt.Printf("Address %s does not have a label, trimming leading zeroes to: %s\n", match, trimmedAddress) + return trimmedAddress // Keep the address unchanged if no label is found + }) + + return processedString +} + // TrimLeadingZeroesFromAddress removes the leading zeroes from an address for readability // Example: sender=0x0000000000000000000000000000000000030000 becomes sender=0x30000 when shown on console func TrimLeadingZeroesFromAddress(hexString string) string { From e7a7da53efc570d6020ec5929670aec42aa744e4 Mon Sep 17 00:00:00 2001 From: priyankabose Date: Tue, 28 Jan 2025 17:55:48 -0800 Subject: [PATCH 02/10] Test case added --- fuzzing/fuzzer_test.go | 1 + .../contracts/cheat_codes/vm/label.sol | 25 +++++++++++++++++++ utils/address_utils.go | 5 ++-- 3 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 fuzzing/testdata/contracts/cheat_codes/vm/label.sol diff --git a/fuzzing/fuzzer_test.go b/fuzzing/fuzzer_test.go index aa25bcb4..e107b3e8 100644 --- a/fuzzing/fuzzer_test.go +++ b/fuzzing/fuzzer_test.go @@ -276,6 +276,7 @@ func TestCheatCodes(t *testing.T) { "testdata/contracts/cheat_codes/vm/roll.sol", "testdata/contracts/cheat_codes/vm/store_load.sol", "testdata/contracts/cheat_codes/vm/warp.sol", + "testdata/contracts/cheat_codes/vm/label.sol", } // FFI test will fail on Windows because "echo" is a shell command, not a system command, so we diverge these diff --git a/fuzzing/testdata/contracts/cheat_codes/vm/label.sol b/fuzzing/testdata/contracts/cheat_codes/vm/label.sol new file mode 100644 index 00000000..e0d1f75e --- /dev/null +++ b/fuzzing/testdata/contracts/cheat_codes/vm/label.sol @@ -0,0 +1,25 @@ +// This test ensures that label can be set for an address +interface CheatCodes { + function label(address, string memory) external; +} + +contract LabelContract { + function testLabel() public { + assert(false); + } +} + +contract TestContract { + + function test() public { + // Obtain our cheat code contract reference. + CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + + // Create a contract + LabelContract alice = new LabelContract(); + + // set label and verify. + cheats.label(address(alice), "Alice"); + alice.testLabel(); + } +} diff --git a/utils/address_utils.go b/utils/address_utils.go index f6c868c6..18103fb8 100644 --- a/utils/address_utils.go +++ b/utils/address_utils.go @@ -2,7 +2,6 @@ package utils import ( "encoding/hex" - "fmt" "regexp" "strings" @@ -72,11 +71,11 @@ func ResolveAddressToLabelFromString(str string, addressToLabel map[common.Addre processedString := addressRegex.ReplaceAllStringFunc(str, func(match string) string { address := common.HexToAddress(match) // Convert the match to an Ethereum address if label, exists := addressToLabel[address]; exists { - fmt.Printf("Replacing address %s with label %s in element: %s\n", match, label, str) + //fmt.Printf("Replacing address %s with label: %s\n", match, label) return label // Replace address with label } trimmedAddress := TrimLeadingZeroesFromAddress(match) - fmt.Printf("Address %s does not have a label, trimming leading zeroes to: %s\n", match, trimmedAddress) + //fmt.Printf("Address %s does not have a label, trimming leading zeroes to: %s\n", match, trimmedAddress) return trimmedAddress // Keep the address unchanged if no label is found }) From 3431898d93c88f3aece5cfccfc6355dd72af89b9 Mon Sep 17 00:00:00 2001 From: priyankabose Date: Wed, 29 Jan 2025 19:46:59 -0800 Subject: [PATCH 03/10] Unit test for label cheat --- fuzzing/fuzzer_test.go | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/fuzzing/fuzzer_test.go b/fuzzing/fuzzer_test.go index e107b3e8..4ab6f497 100644 --- a/fuzzing/fuzzer_test.go +++ b/fuzzing/fuzzer_test.go @@ -5,6 +5,7 @@ import ( "math/big" "math/rand" "reflect" + "regexp" "testing" "github.com/crytic/medusa/fuzzing/executiontracer" @@ -276,7 +277,6 @@ func TestCheatCodes(t *testing.T) { "testdata/contracts/cheat_codes/vm/roll.sol", "testdata/contracts/cheat_codes/vm/store_load.sol", "testdata/contracts/cheat_codes/vm/warp.sol", - "testdata/contracts/cheat_codes/vm/label.sol", } // FFI test will fail on Windows because "echo" is a shell command, not a system command, so we diverge these @@ -599,6 +599,40 @@ func TestExecutionTraces(t *testing.T) { } } +func TestLabelCheatCode(t *testing.T) { + runFuzzerTest(t, &fuzzerSolcFileTest{ + filePath: "testdata/contracts/cheat_codes/vm/label.sol", + configUpdates: func(config *config.ProjectConfig) { + config.Fuzzing.TargetContracts = []string{"TestContract"} + config.Fuzzing.Testing.PropertyTesting.Enabled = false + config.Fuzzing.Testing.OptimizationTesting.Enabled = false + config.Slither.UseSlither = false + }, + method: func(f *fuzzerTestContext) { + // Start the fuzzer + err := f.fuzzer.Start() + assert.NoError(t, err) + + // Check for failed assertion tests. + failedTestCase := f.fuzzer.TestCasesWithStatus(TestCaseStatusFailed) + assert.NotEmpty(t, failedTestCase, "expected to have failed test cases") + + // Obtain our first failed test case, get the message, and verify it contains our assertion failed. + failingSequence := *failedTestCase[0].CallSequence() + assert.NotEmpty(t, failingSequence, "expected to have calls in the call sequence failing an assertion test") + + // Obtain the last execution trace mg + executionTraceMessages := failingSequence.Log().String() + + pattern := `addr=[a-zA-Z]+` + match, _ := regexp.MatchString(pattern, executionTraceMessages) + + // Verify it contains all expected strings + assert.True(t, match) + }, + }) +} + // TestTestingScope runs tests to ensure dynamically deployed contracts are tested when the "test all contracts" // config option is specified. It also runs the fuzzer without the option enabled to ensure they are not tested. func TestTestingScope(t *testing.T) { From 8cfbcbb42c431cfed1a4d5c0212fb8a0cbee4dec Mon Sep 17 00:00:00 2001 From: Anish Naik Date: Thu, 30 Jan 2025 15:06:00 -0500 Subject: [PATCH 04/10] update test case --- .../contracts/cheat_codes/vm/label.sol | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/fuzzing/testdata/contracts/cheat_codes/vm/label.sol b/fuzzing/testdata/contracts/cheat_codes/vm/label.sol index e0d1f75e..9c098357 100644 --- a/fuzzing/testdata/contracts/cheat_codes/vm/label.sol +++ b/fuzzing/testdata/contracts/cheat_codes/vm/label.sol @@ -5,21 +5,24 @@ interface CheatCodes { contract LabelContract { function testLabel() public { + // Throw an assertion failure so that we can capture the execution trace assert(false); } } contract TestContract { + // Obtain our cheat code contract reference. + CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + // Create a contract + LabelContract alice = new LabelContract(); - function test() public { - // Obtain our cheat code contract reference. - CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - - // Create a contract - LabelContract alice = new LabelContract(); - - // set label and verify. + constructor() { + // Set label for LabelContract to "Alice" cheats.label(address(alice), "Alice"); + } + + function test() public { + // Call the label contract alice.testLabel(); } } From 327388734e98ac222fe48c93731a71f8c0df7ed6 Mon Sep 17 00:00:00 2001 From: priyankabose Date: Thu, 30 Jan 2025 12:20:27 -0800 Subject: [PATCH 05/10] Execution trace logging --- chain/cheat_code_contract.go | 3 +++ chain/cheat_code_tracer.go | 7 +++---- fuzzing/calls/call_sequence.go | 10 ++-------- fuzzing/executiontracer/execution_trace.go | 16 ++++++++++++++-- fuzzing/executiontracer/execution_tracer.go | 2 +- 5 files changed, 23 insertions(+), 15 deletions(-) diff --git a/chain/cheat_code_contract.go b/chain/cheat_code_contract.go index 08ceb6ea..ee2616e1 100644 --- a/chain/cheat_code_contract.go +++ b/chain/cheat_code_contract.go @@ -116,6 +116,9 @@ func (c *CheatCodeContract) Address() common.Address { return c.address } +// Tracer represents the tracer associated with the cheat code contract +func (c *CheatCodeContract) Tracer() *cheatCodeTracer { return c.tracer } + // Abi provides the cheat code contract interface. func (c *CheatCodeContract) Abi() *abi.ABI { return &c.abi diff --git a/chain/cheat_code_tracer.go b/chain/cheat_code_tracer.go index 4d931662..da835aac 100644 --- a/chain/cheat_code_tracer.go +++ b/chain/cheat_code_tracer.go @@ -99,6 +99,9 @@ func (t *cheatCodeTracer) NativeTracer() *TestChainTracer { return t.nativeTracer } +// TestChain returns the underlying TestChain +func (t *cheatCodeTracer) TestChain() *TestChain { return t.chain } + // bindToChain is called by the TestChain which created the tracer to set its reference. // Note: This is done because of the cheat code system's dependency on the genesis block, as well as chain's dependency // on it, which prevents the chain being set in the tracer on initialization. @@ -225,10 +228,6 @@ func (t *cheatCodeTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope t // tracer is used during transaction execution (block creation), the results can later be queried from the block. // This method will only be called on the added tracer if it implements the extended TestChainTracer interface. func (t *cheatCodeTracer) CaptureTxEndSetAdditionalResults(results *types.MessageResults) { - - // Add the address label mappings - results.AdditionalResults["AddressToLabel"] = t.chain.AddressToLabel - // Add our revert operations we collected for this transaction. results.OnRevertHookFuncs = append(results.OnRevertHookFuncs, t.results.onChainRevertHooks...) } diff --git a/fuzzing/calls/call_sequence.go b/fuzzing/calls/call_sequence.go index 6b73096b..b04e6b0e 100644 --- a/fuzzing/calls/call_sequence.go +++ b/fuzzing/calls/call_sequence.go @@ -46,18 +46,12 @@ func (cs CallSequence) Log() *logging.LogBuffer { // Construct the buffer for each call made in the sequence for i := 0; i < len(cs); i++ { - addressToLabel, ok := cs[i].ChainReference.MessageResults().AdditionalResults["AddressToLabel"].(map[common.Address]string) - // Add the string representing the call - buffer.Append(utils.ResolveAddressToLabelFromString(fmt.Sprintf("%d) %s\n", i+1, cs[i].String()), addressToLabel)) - - if !ok { - panic("AdditionalResults[\"AddressToLabel\"] is not of type map[common.Address]string") - } + buffer.Append(fmt.Sprintf("%d) %s\n", i+1, cs[i].String())) // If we have an execution trace attached, print information about it. if cs[i].ExecutionTrace != nil { - buffer.Append(utils.ResolveAddressToLabelFromElements(cs[i].ExecutionTrace.Log().Elements(), addressToLabel)...) + buffer.Append(cs[i].ExecutionTrace.Log().Elements()...) buffer.Append("\n") } } diff --git a/fuzzing/executiontracer/execution_trace.go b/fuzzing/executiontracer/execution_trace.go index 76953094..7310a42d 100644 --- a/fuzzing/executiontracer/execution_trace.go +++ b/fuzzing/executiontracer/execution_trace.go @@ -4,6 +4,8 @@ import ( "encoding/hex" "errors" "fmt" + "github.com/crytic/medusa/utils" + "github.com/ethereum/go-ethereum/common" "regexp" "strings" @@ -28,13 +30,16 @@ type ExecutionTrace struct { // contractDefinitions represents the known contract definitions at the time of tracing. This is used to help // obtain any additional information regarding execution. contractDefinitions contracts.Contracts + + cheatCodeContracts map[common.Address]*chain.CheatCodeContract } // newExecutionTrace creates and returns a new ExecutionTrace, to be used by the ExecutionTracer. -func newExecutionTrace(contracts contracts.Contracts) *ExecutionTrace { +func newExecutionTrace(contracts contracts.Contracts, cheatCodeContracts map[common.Address]*chain.CheatCodeContract) *ExecutionTrace { return &ExecutionTrace{ TopLevelCallFrame: nil, contractDefinitions: contracts, + cheatCodeContracts: cheatCodeContracts, } } @@ -368,9 +373,16 @@ func (t *ExecutionTrace) Log() *logging.LogBuffer { // Create a buffer buffer := logging.NewLogBuffer() + // Get addressToLabel mapping from the underlying test chain + addressToLabel := make(map[common.Address]string) + for _, contract := range t.cheatCodeContracts { + addressToLabel = contract.Tracer().TestChain().AddressToLabel + break // exits after first iteration + } + // First, add the elements that make up the overarching execution trace elements, logs := t.generateElementsAndLogsForCallFrame(0, t.TopLevelCallFrame) - buffer.Append(elements...) + buffer.Append(utils.ResolveAddressToLabelFromElements(elements, addressToLabel)...) // If we captured any logs during tracing, add them to the overarching execution trace if len(logs) > 0 { diff --git a/fuzzing/executiontracer/execution_tracer.go b/fuzzing/executiontracer/execution_tracer.go index 0b876c0c..15aedf87 100644 --- a/fuzzing/executiontracer/execution_tracer.go +++ b/fuzzing/executiontracer/execution_tracer.go @@ -122,7 +122,7 @@ func (t *ExecutionTracer) OnTxEnd(receipt *coretypes.Receipt, err error) { // OnTxStart is called upon the start of transaction execution, as defined by tracers.Tracer. func (t *ExecutionTracer) OnTxStart(vm *tracing.VMContext, tx *coretypes.Transaction, from common.Address) { // Reset our capture state - t.trace = newExecutionTrace(t.contractDefinitions) + t.trace = newExecutionTrace(t.contractDefinitions, t.cheatCodeContracts) t.currentCallFrame = nil t.onNextCaptureState = nil t.traceMap = make(map[common.Hash]*ExecutionTrace) From 05495cec306b325fe10bbeb69c2c87ce4c65ff63 Mon Sep 17 00:00:00 2001 From: priyankabose Date: Thu, 30 Jan 2025 18:58:00 -0800 Subject: [PATCH 06/10] Added support for both call sequence, execution trace, input arguments --- chain/cheat_code_tracer.go | 3 +- chain/standard_cheat_code_contract.go | 2 +- chain/test_chain.go | 9 ++++-- fuzzing/calls/call_sequence.go | 5 ++- fuzzing/executiontracer/execution_trace.go | 31 +++++++++++-------- fuzzing/executiontracer/execution_tracer.go | 13 +++++++- fuzzing/fuzzer_test.go | 10 ++++-- .../contracts/cheat_codes/vm/label.sol | 7 +++-- utils/address_utils.go | 19 +----------- 9 files changed, 56 insertions(+), 43 deletions(-) diff --git a/chain/cheat_code_tracer.go b/chain/cheat_code_tracer.go index da835aac..0378c1c1 100644 --- a/chain/cheat_code_tracer.go +++ b/chain/cheat_code_tracer.go @@ -99,7 +99,6 @@ func (t *cheatCodeTracer) NativeTracer() *TestChainTracer { return t.nativeTracer } -// TestChain returns the underlying TestChain func (t *cheatCodeTracer) TestChain() *TestChain { return t.chain } // bindToChain is called by the TestChain which created the tracer to set its reference. @@ -230,4 +229,6 @@ func (t *cheatCodeTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope t func (t *cheatCodeTracer) CaptureTxEndSetAdditionalResults(results *types.MessageResults) { // Add our revert operations we collected for this transaction. results.OnRevertHookFuncs = append(results.OnRevertHookFuncs, t.results.onChainRevertHooks...) + // Add AddressToLabel to the additional results + results.AdditionalResults["AddressToLabelResults"] = t.chain.AddressToLabel() } diff --git a/chain/standard_cheat_code_contract.go b/chain/standard_cheat_code_contract.go index edafac35..a8ee2424 100644 --- a/chain/standard_cheat_code_contract.go +++ b/chain/standard_cheat_code_contract.go @@ -174,7 +174,7 @@ func getStandardCheatCodeContract(tracer *cheatCodeTracer) (*CheatCodeContract, func(tracer *cheatCodeTracer, inputs []any) ([]any, *cheatCodeRawReturnData) { addr := inputs[0].(common.Address) lbl := inputs[1].(string) - tracer.chain.AddressToLabel[addr] = lbl + tracer.chain.AddressToLabel()[addr] = lbl return nil, nil }, ) diff --git a/chain/test_chain.go b/chain/test_chain.go index 46eebd6d..494c8111 100644 --- a/chain/test_chain.go +++ b/chain/test_chain.go @@ -76,7 +76,7 @@ type TestChain struct { db ethdb.Database // AddressToLabel maps an address to its label if one exists - AddressToLabel map[common.Address]string + addressToLabel map[common.Address]string // callTracerRouter forwards tracers.Tracer and TestChainTracer calls to any instances added to it. This // router is used for non-state changing calls. @@ -160,7 +160,7 @@ func NewTestChain(genesisAlloc types.GenesisAlloc, testChainConfig *config.TestC } } // Initialize address to label mapping - AddressToLabel := make(map[common.Address]string) + addressToLabel := make(map[common.Address]string) // Create an in-memory database db := rawdb.NewMemoryDatabase() @@ -193,7 +193,7 @@ func NewTestChain(genesisAlloc types.GenesisAlloc, testChainConfig *config.TestC db: db, state: nil, stateDatabase: stateDatabase, - AddressToLabel: AddressToLabel, + addressToLabel: addressToLabel, transactionTracerRouter: transactionTracerRouter, callTracerRouter: callTracerRouter, testChainConfig: testChainConfig, @@ -307,6 +307,9 @@ func (t *TestChain) State() *state.StateDB { return t.state } +// AddressToLabel returns the addressToLabel mapping +func (t *TestChain) AddressToLabel() map[common.Address]string { return t.addressToLabel } + // CheatCodeContracts returns all cheat code contracts which are installed in the chain. func (t *TestChain) CheatCodeContracts() map[common.Address]*CheatCodeContract { // Create a map of cheat code contracts to store our results diff --git a/fuzzing/calls/call_sequence.go b/fuzzing/calls/call_sequence.go index b04e6b0e..415e703e 100644 --- a/fuzzing/calls/call_sequence.go +++ b/fuzzing/calls/call_sequence.go @@ -46,8 +46,11 @@ func (cs CallSequence) Log() *logging.LogBuffer { // Construct the buffer for each call made in the sequence for i := 0; i < len(cs); i++ { + + // Get the addressToLabel mapping to map addresses to their labels in the log + addressToLabel := cs[i].ChainReference.MessageResults().AdditionalResults["AddressToLabelResults"].(map[common.Address]string) // Add the string representing the call - buffer.Append(fmt.Sprintf("%d) %s\n", i+1, cs[i].String())) + buffer.Append(utils.ResolveAddressToLabelFromString(fmt.Sprintf("%d) %s\n", i+1, cs[i].String()), addressToLabel)) // If we have an execution trace attached, print information about it. if cs[i].ExecutionTrace != nil { diff --git a/fuzzing/executiontracer/execution_trace.go b/fuzzing/executiontracer/execution_trace.go index 7310a42d..ffc71bea 100644 --- a/fuzzing/executiontracer/execution_trace.go +++ b/fuzzing/executiontracer/execution_trace.go @@ -31,15 +31,16 @@ type ExecutionTrace struct { // obtain any additional information regarding execution. contractDefinitions contracts.Contracts - cheatCodeContracts map[common.Address]*chain.CheatCodeContract + // Address to label mapping used to map addresses to their labels in a execution trace + addressToLabel map[common.Address]string } // newExecutionTrace creates and returns a new ExecutionTrace, to be used by the ExecutionTracer. -func newExecutionTrace(contracts contracts.Contracts, cheatCodeContracts map[common.Address]*chain.CheatCodeContract) *ExecutionTrace { +func newExecutionTrace(contracts contracts.Contracts, addressToLabel map[common.Address]string) *ExecutionTrace { return &ExecutionTrace{ TopLevelCallFrame: nil, contractDefinitions: contracts, - cheatCodeContracts: cheatCodeContracts, + addressToLabel: addressToLabel, } } @@ -141,11 +142,21 @@ func (t *ExecutionTrace) generateCallFrameEnterElements(callFrame *CallFrame) ([ inputArgumentsDisplayText = &temp } + // Check if a code/proxy contract address exists in the addressToLabel mapping + if addr, exists := t.addressToLabel[callFrame.ToAddress]; exists { + codeContractName = addr + proxyContractName = addr + } + // Generate the message we wish to output finally, using all these display string components. // If we executed code, attach additional context such as the contract name, method, etc. var callInfo string if callFrame.IsProxyCall() { if callFrame.ExecutedCode { + // Resets codeContractName if this is a proxy call + if _, exists := t.addressToLabel[callFrame.CodeAddress]; exists { + codeContractName = t.addressToLabel[callFrame.CodeAddress] + } callInfo = fmt.Sprintf("%v -> %v.%v(%v) (addr=%v, code=%v, value=%v, sender=%v)", proxyContractName, codeContractName, methodName, *inputArgumentsDisplayText, callFrame.ToAddress.String(), callFrame.CodeAddress.String(), callFrame.CallValue, callFrame.SenderAddress.String()) } else { callInfo = fmt.Sprintf("(addr=%v, value=%v, sender=%v)", callFrame.ToAddress.String(), callFrame.CallValue, callFrame.SenderAddress.String()) @@ -162,8 +173,8 @@ func (t *ExecutionTrace) generateCallFrameEnterElements(callFrame *CallFrame) ([ } } - // Add call information to the elements - elements = append(elements, callInfo, "\n") + // Labels the addresses using addressToLabel and add call information to the elements + elements = append(elements, utils.ResolveAddressToLabelFromString(callInfo, t.addressToLabel), "\n") return elements, consoleLogString } @@ -373,16 +384,10 @@ func (t *ExecutionTrace) Log() *logging.LogBuffer { // Create a buffer buffer := logging.NewLogBuffer() - // Get addressToLabel mapping from the underlying test chain - addressToLabel := make(map[common.Address]string) - for _, contract := range t.cheatCodeContracts { - addressToLabel = contract.Tracer().TestChain().AddressToLabel - break // exits after first iteration - } - // First, add the elements that make up the overarching execution trace elements, logs := t.generateElementsAndLogsForCallFrame(0, t.TopLevelCallFrame) - buffer.Append(utils.ResolveAddressToLabelFromElements(elements, addressToLabel)...) + // Replace addresses to their corresponding labels if exist + buffer.Append(elements...) // If we captured any logs during tracing, add them to the overarching execution trace if len(logs) > 0 { diff --git a/fuzzing/executiontracer/execution_tracer.go b/fuzzing/executiontracer/execution_tracer.go index 15aedf87..36def723 100644 --- a/fuzzing/executiontracer/execution_tracer.go +++ b/fuzzing/executiontracer/execution_tracer.go @@ -122,7 +122,7 @@ func (t *ExecutionTracer) OnTxEnd(receipt *coretypes.Receipt, err error) { // OnTxStart is called upon the start of transaction execution, as defined by tracers.Tracer. func (t *ExecutionTracer) OnTxStart(vm *tracing.VMContext, tx *coretypes.Transaction, from common.Address) { // Reset our capture state - t.trace = newExecutionTrace(t.contractDefinitions, t.cheatCodeContracts) + t.trace = newExecutionTrace(t.contractDefinitions, t.getAddressToLabelFromTestChain()) t.currentCallFrame = nil t.onNextCaptureState = nil t.traceMap = make(map[common.Hash]*ExecutionTrace) @@ -309,3 +309,14 @@ func (t *ExecutionTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope t }) } } + +// getAddressToLabelFromTestChain gets the AddressToLabel stored as part of the TestChain through +// the underlying cheatCodeContract's Tracer +func (t *ExecutionTracer) getAddressToLabelFromTestChain() map[common.Address]string { + addressToLabel := make(map[common.Address]string) + for _, contract := range t.cheatCodeContracts { + addressToLabel = contract.Tracer().TestChain().AddressToLabel() + break // exits after first iteration + } + return addressToLabel +} diff --git a/fuzzing/fuzzer_test.go b/fuzzing/fuzzer_test.go index 4ab6f497..9fd23cb9 100644 --- a/fuzzing/fuzzer_test.go +++ b/fuzzing/fuzzer_test.go @@ -621,11 +621,15 @@ func TestLabelCheatCode(t *testing.T) { failingSequence := *failedTestCase[0].CallSequence() assert.NotEmpty(t, failingSequence, "expected to have calls in the call sequence failing an assertion test") - // Obtain the last execution trace mg - executionTraceMessages := failingSequence.Log().String() + // Obtain the last call + lastCall := failingSequence[len(failingSequence)-1] + assert.NotNilf(t, lastCall.ExecutionTrace, "expected to have an execution trace attached to call sequence for this test") + + // Get the execution trace message + executionTraceMsg := lastCall.ExecutionTrace.Log().String() pattern := `addr=[a-zA-Z]+` - match, _ := regexp.MatchString(pattern, executionTraceMessages) + match, _ := regexp.MatchString(pattern, executionTraceMsg) // Verify it contains all expected strings assert.True(t, match) diff --git a/fuzzing/testdata/contracts/cheat_codes/vm/label.sol b/fuzzing/testdata/contracts/cheat_codes/vm/label.sol index 9c098357..e3d0a7fd 100644 --- a/fuzzing/testdata/contracts/cheat_codes/vm/label.sol +++ b/fuzzing/testdata/contracts/cheat_codes/vm/label.sol @@ -4,7 +4,7 @@ interface CheatCodes { } contract LabelContract { - function testLabel() public { + function testLabel(address addr) public { // Throw an assertion failure so that we can capture the execution trace assert(false); } @@ -15,14 +15,17 @@ contract TestContract { CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); // Create a contract LabelContract alice = new LabelContract(); + // Sets address for Bob + address bob = address(0x1); constructor() { // Set label for LabelContract to "Alice" cheats.label(address(alice), "Alice"); + cheats.label(address(bob), "Bob"); } function test() public { // Call the label contract - alice.testLabel(); + alice.testLabel(address(bob)); } } diff --git a/utils/address_utils.go b/utils/address_utils.go index 18103fb8..7e7ae635 100644 --- a/utils/address_utils.go +++ b/utils/address_utils.go @@ -48,23 +48,6 @@ func HexStringsToAddresses(addressHexStrings []string) ([]common.Address, error) return addresses, nil } -// ResolveAddressToLabelFromElements parse array elements for ethereum addresses and if found replace that with a label if one exists -func ResolveAddressToLabelFromElements(elements []any, addressToLabel map[common.Address]string) []any { - updatedElements := []any{} - for _, element := range elements { - // Check if the element is a string - if str, ok := element.(string); ok { - // Replace addresses in the string with their labels - updatedElements = append(updatedElements, ResolveAddressToLabelFromString(str, addressToLabel)) - } else { - // Keep non-string elements unchanged - updatedElements = append(updatedElements, element) - } - } - - return updatedElements -} - // ResolveAddressToLabelFromString parse a string for ethereum addresses and if found replace that with a label if one exists func ResolveAddressToLabelFromString(str string, addressToLabel map[common.Address]string) string { addressRegex := regexp.MustCompile(`0x[a-fA-F0-9]{40}`) @@ -72,7 +55,7 @@ func ResolveAddressToLabelFromString(str string, addressToLabel map[common.Addre address := common.HexToAddress(match) // Convert the match to an Ethereum address if label, exists := addressToLabel[address]; exists { //fmt.Printf("Replacing address %s with label: %s\n", match, label) - return label // Replace address with label + return label + " " + "[" + TrimLeadingZeroesFromAddress(address.String()) + "]" // Replace address with label } trimmedAddress := TrimLeadingZeroesFromAddress(match) //fmt.Printf("Address %s does not have a label, trimming leading zeroes to: %s\n", match, trimmedAddress) From 2c1839afa486161d52e55caaf632a0e0eedf8661 Mon Sep 17 00:00:00 2001 From: Anish Naik Date: Sat, 1 Feb 2025 15:01:00 -0500 Subject: [PATCH 07/10] fix some bugs and clean up how the logic is handled --- chain/cheat_code_contract.go | 3 -- chain/cheat_code_tracer.go | 4 -- chain/standard_cheat_code_contract.go | 4 +- chain/test_chain.go | 11 ++--- fuzzing/calls/call_sequence.go | 7 +-- fuzzing/calls/call_sequence_execution.go | 2 +- fuzzing/executiontracer/execution_trace.go | 49 ++++++++++--------- fuzzing/executiontracer/execution_tracer.go | 32 +++++------- fuzzing/fuzzer_test.go | 23 ++++++--- fuzzing/fuzzer_worker.go | 14 ++++-- .../contracts/cheat_codes/utils/label.sol | 49 +++++++++++++++++++ .../contracts/cheat_codes/vm/label.sol | 31 ------------ fuzzing/valuegeneration/abi_values.go | 16 +++++- utils/address_utils.go | 30 +++++------- 14 files changed, 147 insertions(+), 128 deletions(-) create mode 100644 fuzzing/testdata/contracts/cheat_codes/utils/label.sol delete mode 100644 fuzzing/testdata/contracts/cheat_codes/vm/label.sol diff --git a/chain/cheat_code_contract.go b/chain/cheat_code_contract.go index ee2616e1..08ceb6ea 100644 --- a/chain/cheat_code_contract.go +++ b/chain/cheat_code_contract.go @@ -116,9 +116,6 @@ func (c *CheatCodeContract) Address() common.Address { return c.address } -// Tracer represents the tracer associated with the cheat code contract -func (c *CheatCodeContract) Tracer() *cheatCodeTracer { return c.tracer } - // Abi provides the cheat code contract interface. func (c *CheatCodeContract) Abi() *abi.ABI { return &c.abi diff --git a/chain/cheat_code_tracer.go b/chain/cheat_code_tracer.go index 0378c1c1..208b7115 100644 --- a/chain/cheat_code_tracer.go +++ b/chain/cheat_code_tracer.go @@ -99,8 +99,6 @@ func (t *cheatCodeTracer) NativeTracer() *TestChainTracer { return t.nativeTracer } -func (t *cheatCodeTracer) TestChain() *TestChain { return t.chain } - // bindToChain is called by the TestChain which created the tracer to set its reference. // Note: This is done because of the cheat code system's dependency on the genesis block, as well as chain's dependency // on it, which prevents the chain being set in the tracer on initialization. @@ -229,6 +227,4 @@ func (t *cheatCodeTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope t func (t *cheatCodeTracer) CaptureTxEndSetAdditionalResults(results *types.MessageResults) { // Add our revert operations we collected for this transaction. results.OnRevertHookFuncs = append(results.OnRevertHookFuncs, t.results.onChainRevertHooks...) - // Add AddressToLabel to the additional results - results.AdditionalResults["AddressToLabelResults"] = t.chain.AddressToLabel() } diff --git a/chain/standard_cheat_code_contract.go b/chain/standard_cheat_code_contract.go index 174e22b9..81f0fe6d 100644 --- a/chain/standard_cheat_code_contract.go +++ b/chain/standard_cheat_code_contract.go @@ -255,8 +255,8 @@ func getStandardCheatCodeContract(tracer *cheatCodeTracer) (*CheatCodeContract, "label", abi.Arguments{{Type: typeAddress}, {Type: typeString}}, abi.Arguments{}, func(tracer *cheatCodeTracer, inputs []any) ([]any, *cheatCodeRawReturnData) { addr := inputs[0].(common.Address) - lbl := inputs[1].(string) - tracer.chain.AddressToLabel()[addr] = lbl + label := inputs[1].(string) + tracer.chain.Labels[addr] = label return nil, nil }, ) diff --git a/chain/test_chain.go b/chain/test_chain.go index d10c7f7c..86c36f45 100644 --- a/chain/test_chain.go +++ b/chain/test_chain.go @@ -74,8 +74,8 @@ type TestChain struct { // This is constructed over the kvstore. db ethdb.Database - // AddressToLabel maps an address to its label if one exists - addressToLabel map[common.Address]string + // Labels maps an address to its label if one exists. This is useful for execution tracing. + Labels map[common.Address]string // callTracerRouter forwards tracers.Tracer and TestChainTracer calls to any instances added to it. This // router is used for non-state changing calls. @@ -158,8 +158,6 @@ func NewTestChain(genesisAlloc types.GenesisAlloc, testChainConfig *config.TestC vmConfigExtensions.AdditionalPrecompiles[cheatContract.address] = cheatContract } } - // Initialize address to label mapping - addressToLabel := make(map[common.Address]string) // Create an in-memory database db := rawdb.NewMemoryDatabase() @@ -192,7 +190,7 @@ func NewTestChain(genesisAlloc types.GenesisAlloc, testChainConfig *config.TestC db: db, state: nil, stateDatabase: stateDatabase, - addressToLabel: addressToLabel, + Labels: make(map[common.Address]string), transactionTracerRouter: transactionTracerRouter, callTracerRouter: callTracerRouter, testChainConfig: testChainConfig, @@ -306,9 +304,6 @@ func (t *TestChain) State() *state.StateDB { return t.state } -// AddressToLabel returns the addressToLabel mapping -func (t *TestChain) AddressToLabel() map[common.Address]string { return t.addressToLabel } - // CheatCodeContracts returns all cheat code contracts which are installed in the chain. func (t *TestChain) CheatCodeContracts() map[common.Address]*CheatCodeContract { // Create a map of cheat code contracts to store our results diff --git a/fuzzing/calls/call_sequence.go b/fuzzing/calls/call_sequence.go index 415e703e..ab70e142 100644 --- a/fuzzing/calls/call_sequence.go +++ b/fuzzing/calls/call_sequence.go @@ -46,11 +46,8 @@ func (cs CallSequence) Log() *logging.LogBuffer { // Construct the buffer for each call made in the sequence for i := 0; i < len(cs); i++ { - - // Get the addressToLabel mapping to map addresses to their labels in the log - addressToLabel := cs[i].ChainReference.MessageResults().AdditionalResults["AddressToLabelResults"].(map[common.Address]string) // Add the string representing the call - buffer.Append(utils.ResolveAddressToLabelFromString(fmt.Sprintf("%d) %s\n", i+1, cs[i].String()), addressToLabel)) + buffer.Append(fmt.Sprintf("%d) %s\n", i+1, cs[i].String())) // If we have an execution trace attached, print information about it. if cs[i].ExecutionTrace != nil { @@ -264,7 +261,7 @@ func (cse *CallSequenceElement) String() string { args, err := method.Inputs.Unpack(cse.Call.Data[4:]) argsText := "" if err == nil { - argsText, err = valuegeneration.EncodeABIArgumentsToString(method.Inputs, args) + argsText, err = valuegeneration.EncodeABIArgumentsToString(method.Inputs, args, nil) if err != nil { argsText = "" } diff --git a/fuzzing/calls/call_sequence_execution.go b/fuzzing/calls/call_sequence_execution.go index 593465f4..a82837ee 100644 --- a/fuzzing/calls/call_sequence_execution.go +++ b/fuzzing/calls/call_sequence_execution.go @@ -172,7 +172,7 @@ func ExecuteCallSequence(chain *chain.TestChain, callSequence CallSequence) (Cal // ExecuteCallSequenceWithExecutionTracer attaches an executiontracer.ExecutionTracer to ExecuteCallSequenceIteratively and attaches execution traces to the call sequence elements. func ExecuteCallSequenceWithExecutionTracer(testChain *chain.TestChain, contractDefinitions contracts.Contracts, callSequence CallSequence, verboseTracing bool) (CallSequence, error) { // Create a new execution tracer - executionTracer := executiontracer.NewExecutionTracer(contractDefinitions, testChain.CheatCodeContracts()) + executionTracer := executiontracer.NewExecutionTracer(contractDefinitions, testChain) defer executionTracer.Close() // Execute our sequence with a simple fetch operation provided to obtain each element. diff --git a/fuzzing/executiontracer/execution_trace.go b/fuzzing/executiontracer/execution_trace.go index ffc71bea..01539ac5 100644 --- a/fuzzing/executiontracer/execution_trace.go +++ b/fuzzing/executiontracer/execution_trace.go @@ -31,16 +31,16 @@ type ExecutionTrace struct { // obtain any additional information regarding execution. contractDefinitions contracts.Contracts - // Address to label mapping used to map addresses to their labels in a execution trace - addressToLabel map[common.Address]string + // labels is a mapping that maps an address to its string representation for cleaner execution traces + labels map[common.Address]string } // newExecutionTrace creates and returns a new ExecutionTrace, to be used by the ExecutionTracer. -func newExecutionTrace(contracts contracts.Contracts, addressToLabel map[common.Address]string) *ExecutionTrace { +func newExecutionTrace(contracts contracts.Contracts, labels map[common.Address]string) *ExecutionTrace { return &ExecutionTrace{ TopLevelCallFrame: nil, contractDefinitions: contracts, - addressToLabel: addressToLabel, + labels: labels, } } @@ -75,10 +75,18 @@ func (t *ExecutionTrace) generateCallFrameEnterElements(callFrame *CallFrame) ([ // Resolve our contract names, as well as our method and its name from the code contract. if callFrame.ToContractAbi != nil { + // Check to see if there is a label for the proxy address proxyContractName = callFrame.ToContractName + if label, ok := t.labels[callFrame.ToAddress]; ok { + proxyContractName = label + } } if callFrame.CodeContractAbi != nil { + // Check to see if there is a label for the code address codeContractName = callFrame.CodeContractName + if label, ok := t.labels[callFrame.CodeAddress]; ok { + codeContractName = label + } if callFrame.IsContractCreation() { methodName = "constructor" method = &callFrame.CodeContractAbi.Constructor @@ -107,8 +115,8 @@ func (t *ExecutionTrace) generateCallFrameEnterElements(callFrame *CallFrame) ([ // Unpack our input values and obtain a string to represent them inputValues, err := method.Inputs.Unpack(abiDataInputBuffer) if err == nil { - // Encode the ABI arguments into strings - encodedInputString, err := valuegeneration.EncodeABIArgumentsToString(method.Inputs, inputValues) + // Encode the ABI arguments into strings and provide the label overrides + encodedInputString, err := valuegeneration.EncodeABIArgumentsToString(method.Inputs, inputValues, t.labels) if err == nil { inputArgumentsDisplayText = &encodedInputString } @@ -142,39 +150,34 @@ func (t *ExecutionTrace) generateCallFrameEnterElements(callFrame *CallFrame) ([ inputArgumentsDisplayText = &temp } - // Check if a code/proxy contract address exists in the addressToLabel mapping - if addr, exists := t.addressToLabel[callFrame.ToAddress]; exists { - codeContractName = addr - proxyContractName = addr - } + // Handle all label overrides + toAddress := utils.AttachLabelToAddress(callFrame.ToAddress, t.labels[callFrame.ToAddress]) + senderAddress := utils.AttachLabelToAddress(callFrame.SenderAddress, t.labels[callFrame.SenderAddress]) + codeAddress := utils.AttachLabelToAddress(callFrame.CodeAddress, t.labels[callFrame.CodeAddress]) // Generate the message we wish to output finally, using all these display string components. // If we executed code, attach additional context such as the contract name, method, etc. var callInfo string if callFrame.IsProxyCall() { if callFrame.ExecutedCode { - // Resets codeContractName if this is a proxy call - if _, exists := t.addressToLabel[callFrame.CodeAddress]; exists { - codeContractName = t.addressToLabel[callFrame.CodeAddress] - } - callInfo = fmt.Sprintf("%v -> %v.%v(%v) (addr=%v, code=%v, value=%v, sender=%v)", proxyContractName, codeContractName, methodName, *inputArgumentsDisplayText, callFrame.ToAddress.String(), callFrame.CodeAddress.String(), callFrame.CallValue, callFrame.SenderAddress.String()) + callInfo = fmt.Sprintf("%v -> %v.%v(%v) (addr=%v, code=%v, value=%v, sender=%v)", proxyContractName, codeContractName, methodName, *inputArgumentsDisplayText, toAddress, codeAddress, callFrame.CallValue, senderAddress) } else { - callInfo = fmt.Sprintf("(addr=%v, value=%v, sender=%v)", callFrame.ToAddress.String(), callFrame.CallValue, callFrame.SenderAddress.String()) + callInfo = fmt.Sprintf("(addr=%v, value=%v, sender=%v)", toAddress, callFrame.CallValue, senderAddress) } } else { if callFrame.ExecutedCode { if callFrame.ToAddress == chain.ConsoleLogContractAddress { callInfo = fmt.Sprintf("%v.%v(%v)", codeContractName, methodName, *inputArgumentsDisplayText) } else { - callInfo = fmt.Sprintf("%v.%v(%v) (addr=%v, value=%v, sender=%v)", codeContractName, methodName, *inputArgumentsDisplayText, callFrame.ToAddress.String(), callFrame.CallValue, callFrame.SenderAddress.String()) + callInfo = fmt.Sprintf("%v.%v(%v) (addr=%v, value=%v, sender=%v)", codeContractName, methodName, *inputArgumentsDisplayText, toAddress, callFrame.CallValue, senderAddress) } } else { - callInfo = fmt.Sprintf("(addr=%v, value=%v, sender=%v)", callFrame.ToAddress.String(), callFrame.CallValue, callFrame.SenderAddress.String()) + callInfo = fmt.Sprintf("(addr=%v, value=%v, sender=%v)", toAddress, callFrame.CallValue, senderAddress) } } // Labels the addresses using addressToLabel and add call information to the elements - elements = append(elements, utils.ResolveAddressToLabelFromString(callInfo, t.addressToLabel), "\n") + elements = append(elements, callInfo, "\n") return elements, consoleLogString } @@ -204,7 +207,7 @@ func (t *ExecutionTrace) generateCallFrameExitElements(callFrame *CallFrame) []a if callFrame.ReturnError == nil { outputValues, err := method.Outputs.Unpack(callFrame.ReturnData) if err == nil { - encodedOutputString, err := valuegeneration.EncodeABIArgumentsToString(method.Outputs, outputValues) + encodedOutputString, err := valuegeneration.EncodeABIArgumentsToString(method.Outputs, outputValues, t.labels) if err == nil { outputArgumentsDisplayText = &encodedOutputString } @@ -247,7 +250,7 @@ func (t *ExecutionTrace) generateCallFrameExitElements(callFrame *CallFrame) []a // Try to unpack a custom Solidity error from the return values. matchedCustomError, unpackedCustomErrorArgs := abiutils.GetSolidityCustomRevertError(callFrame.CodeContractAbi, callFrame.ReturnError, callFrame.ReturnData) if matchedCustomError != nil { - customErrorArgsDisplayText, err := valuegeneration.EncodeABIArgumentsToString(matchedCustomError.Inputs, unpackedCustomErrorArgs) + customErrorArgsDisplayText, err := valuegeneration.EncodeABIArgumentsToString(matchedCustomError.Inputs, unpackedCustomErrorArgs, t.labels) if err == nil { elements = append(elements, colors.RedBold, fmt.Sprintf("[revert (error: %v(%v))]", matchedCustomError.Name, customErrorArgsDisplayText), colors.Reset, "\n") return elements @@ -291,7 +294,7 @@ func (t *ExecutionTrace) generateEventEmittedElements(callFrame *CallFrame, even // If we resolved an event definition and unpacked data. if event != nil { // Format the values as a comma-separated string - encodedEventValuesString, err := valuegeneration.EncodeABIArgumentsToString(event.Inputs, eventInputValues) + encodedEventValuesString, err := valuegeneration.EncodeABIArgumentsToString(event.Inputs, eventInputValues, t.labels) if err == nil { // Format our event display text finally, with the event name. temp := fmt.Sprintf("%v(%v)", event.Name, encodedEventValuesString) diff --git a/fuzzing/executiontracer/execution_tracer.go b/fuzzing/executiontracer/execution_tracer.go index 36def723..c9843e27 100644 --- a/fuzzing/executiontracer/execution_tracer.go +++ b/fuzzing/executiontracer/execution_tracer.go @@ -22,7 +22,7 @@ import ( // Returns the ExecutionTrace for the call or an error if one occurs. 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()) + executionTracer := NewExecutionTracer(contractDefinitions, testChain) defer executionTracer.Close() // Call the contract on our chain with the provided state. @@ -48,6 +48,11 @@ type ExecutionTracer struct { // trace represents the current execution trace captured by this tracer. trace *ExecutionTrace + // testChain represents the underlying chain that the execution tracer runs on + testChain *chain.TestChain + + // traceMap describes a mapping that allows someone to retrieve the execution trace for a common transaction + // hash. traceMap map[common.Hash]*ExecutionTrace // currentCallFrame references the current call frame being traced. @@ -56,23 +61,21 @@ type ExecutionTracer struct { // contractDefinitions represents the contract definitions to match for execution traces. contractDefinitions contracts.Contracts - // cheatCodeContracts represents the cheat code contract definitions to match for execution traces. - cheatCodeContracts map[common.Address]*chain.CheatCodeContract - // onNextCaptureState refers to methods which should be executed the next time OnOpcode executes. // OnOpcode is called prior to execution of an instruction. This allows actions to be performed // after some state is captured, on the next state capture (e.g. detecting a log instruction, but // using this structure to execute code later once the log is committed). onNextCaptureState []func() + // nativeTracer is the underlying tracer interface that the execution tracer follows nativeTracer *chain.TestChainTracer } // NewExecutionTracer creates a ExecutionTracer and returns it. -func NewExecutionTracer(contractDefinitions contracts.Contracts, cheatCodeContracts map[common.Address]*chain.CheatCodeContract) *ExecutionTracer { +func NewExecutionTracer(contractDefinitions contracts.Contracts, testChain *chain.TestChain) *ExecutionTracer { tracer := &ExecutionTracer{ contractDefinitions: contractDefinitions, - cheatCodeContracts: cheatCodeContracts, + testChain: testChain, traceMap: make(map[common.Hash]*ExecutionTrace), } innerTracer := &tracers.Tracer{ @@ -122,7 +125,7 @@ func (t *ExecutionTracer) OnTxEnd(receipt *coretypes.Receipt, err error) { // OnTxStart is called upon the start of transaction execution, as defined by tracers.Tracer. func (t *ExecutionTracer) OnTxStart(vm *tracing.VMContext, tx *coretypes.Transaction, from common.Address) { // Reset our capture state - t.trace = newExecutionTrace(t.contractDefinitions, t.getAddressToLabelFromTestChain()) + t.trace = newExecutionTrace(t.contractDefinitions, t.testChain.Labels) t.currentCallFrame = nil t.onNextCaptureState = nil t.traceMap = make(map[common.Hash]*ExecutionTrace) @@ -151,7 +154,7 @@ func (t *ExecutionTracer) resolveCallFrameContractDefinitions(callFrame *CallFra // Try to resolve contract definitions for "to" address if callFrame.ToContractAbi == nil { // Try to resolve definitions from cheat code contracts - if cheatCodeContract, ok := t.cheatCodeContracts[callFrame.ToAddress]; ok { + if cheatCodeContract, ok := t.testChain.CheatCodeContracts()[callFrame.ToAddress]; ok { callFrame.ToContractName = cheatCodeContract.Name() callFrame.ToContractAbi = cheatCodeContract.Abi() callFrame.ExecutedCode = true @@ -175,7 +178,7 @@ func (t *ExecutionTracer) resolveCallFrameContractDefinitions(callFrame *CallFra // Try to resolve contract definitions for "code" address if callFrame.CodeContractAbi == nil { // Try to resolve definitions from cheat code contracts - if cheatCodeContract, ok := t.cheatCodeContracts[callFrame.CodeAddress]; ok { + if cheatCodeContract, ok := t.testChain.CheatCodeContracts()[callFrame.CodeAddress]; ok { callFrame.CodeContractName = cheatCodeContract.Name() callFrame.CodeContractAbi = cheatCodeContract.Abi() callFrame.ExecutedCode = true @@ -309,14 +312,3 @@ func (t *ExecutionTracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope t }) } } - -// getAddressToLabelFromTestChain gets the AddressToLabel stored as part of the TestChain through -// the underlying cheatCodeContract's Tracer -func (t *ExecutionTracer) getAddressToLabelFromTestChain() map[common.Address]string { - addressToLabel := make(map[common.Address]string) - for _, contract := range t.cheatCodeContracts { - addressToLabel = contract.Tracer().TestChain().AddressToLabel() - break // exits after first iteration - } - return addressToLabel -} diff --git a/fuzzing/fuzzer_test.go b/fuzzing/fuzzer_test.go index cf7abb32..c0674da1 100644 --- a/fuzzing/fuzzer_test.go +++ b/fuzzing/fuzzer_test.go @@ -6,7 +6,6 @@ import ( "math/big" "math/rand" "reflect" - "regexp" "testing" "github.com/crytic/medusa/fuzzing/executiontracer" @@ -603,11 +602,24 @@ func TestExecutionTraces(t *testing.T) { } } +// TestLabelCheatCode tests the vm.label cheatcode. func TestLabelCheatCode(t *testing.T) { + // These are the expected messages in the execution trace + expectedTraceMessages := []string{ + "ProxyContract.testVMLabel()()", + "addr=ProxyContract [0xA647ff3c36cFab592509E13860ab8c4F28781a66]", + "sender=MySender [0x10000]", + "ProxyContract -> ImplementationContract.emitEvent(address)(ProxyContract [0xA647ff3c36cFab592509E13860ab8c4F28781a66])", + "code=ImplementationContract [0x54919A19522Ce7c842E25735a9cFEcef1c0a06dA]", + "[event] TestEvent(RandomAddress [0x20000])", + "[return (ProxyContract [0xA647ff3c36cFab592509E13860ab8c4F28781a66])]", + } runFuzzerTest(t, &fuzzerSolcFileTest{ - filePath: "testdata/contracts/cheat_codes/vm/label.sol", + filePath: "testdata/contracts/cheat_codes/utils/label.sol", configUpdates: func(config *config.ProjectConfig) { config.Fuzzing.TargetContracts = []string{"TestContract"} + // Only allow for one sender for proper testing of this unit test + config.Fuzzing.SenderAddresses = []string{"0x10000"} config.Fuzzing.Testing.PropertyTesting.Enabled = false config.Fuzzing.Testing.OptimizationTesting.Enabled = false config.Slither.UseSlither = false @@ -632,11 +644,10 @@ func TestLabelCheatCode(t *testing.T) { // Get the execution trace message executionTraceMsg := lastCall.ExecutionTrace.Log().String() - pattern := `addr=[a-zA-Z]+` - match, _ := regexp.MatchString(pattern, executionTraceMsg) - // Verify it contains all expected strings - assert.True(t, match) + for _, expectedTraceMessage := range expectedTraceMessages { + assert.Contains(t, executionTraceMsg, expectedTraceMessage) + } }, }) } diff --git a/fuzzing/fuzzer_worker.go b/fuzzing/fuzzer_worker.go index d62adcb7..785e6917 100644 --- a/fuzzing/fuzzer_worker.go +++ b/fuzzing/fuzzer_worker.go @@ -570,11 +570,6 @@ func (fw *FuzzerWorker) run(baseTestChain *chain.TestChain) (bool, error) { initializedChain.Events.ContractDeploymentAddedEventEmitter.Subscribe(fw.onChainContractDeploymentAddedEvent) initializedChain.Events.ContractDeploymentRemovedEventEmitter.Subscribe(fw.onChainContractDeploymentRemovedEvent) - // Emit an event indicating the worker has created its chain. - err = fw.Events.FuzzerWorkerChainCreated.Publish(FuzzerWorkerChainCreatedEvent{ - Worker: fw, - Chain: initializedChain, - }) if err != nil { return fmt.Errorf("error returned by an event handler when emitting a worker chain created event: %v", err) } @@ -584,6 +579,15 @@ func (fw *FuzzerWorker) run(baseTestChain *chain.TestChain) (bool, error) { fw.coverageTracer = coverage.NewCoverageTracer() initializedChain.AddTracer(fw.coverageTracer.NativeTracer(), true, false) } + + // Copy the labels from the base chain to the worker's chain + initializedChain.Labels = maps.Clone(baseTestChain.Labels) + + // Emit an event indicating the worker has created its chain. + err = fw.Events.FuzzerWorkerChainCreated.Publish(FuzzerWorkerChainCreatedEvent{ + Worker: fw, + Chain: initializedChain, + }) return nil }) diff --git a/fuzzing/testdata/contracts/cheat_codes/utils/label.sol b/fuzzing/testdata/contracts/cheat_codes/utils/label.sol new file mode 100644 index 00000000..60c546e6 --- /dev/null +++ b/fuzzing/testdata/contracts/cheat_codes/utils/label.sol @@ -0,0 +1,49 @@ +// This test ensures that label can be set for an address +interface CheatCodes { + function label(address, string memory) external; +} + +// This contract is the implementation contract. +contract ImplementationContract { + event TestEvent(address); + + function emitEvent(address addr) public returns(address) { + // We are emitting this event to see if emitting the random address will capture the label for it + emit TestEvent(address(0x20000)); + + // We return an address to see if the label is captured + return addr; + } +} + +// This contract tests the label cheatcode. We use a delegatecall because it ensures that all possible cases are tested +// for the execution trace. We also provide an address as an input argument, return value, and event argument to see +// if the label works properly for those as well. +contract TestContract { + // Obtain our cheat code contract reference. + CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + // Reference to our implementation contract + ImplementationContract impl; + + constructor() public { + // Deploy our implementation contract + impl = new ImplementationContract(); + // Label this contract + cheats.label(address(this), "ProxyContract"); + // Label the sender + cheats.label(address(0x10000), "MySender"); + // Label the implementation contract + cheats.label(address(impl), "ImplementationContract"); + // Label a random address + cheats.label(address(0x20000), "RandomAddress"); + } + + function testVMLabel() public { + // Perform a delegate call + (bool success, bytes memory data) = address(impl).delegatecall(abi.encodeWithSignature("emitEvent(address)", address(this))); + + // Trigger an assertion failure to capture the execution trace + assert(false); + } +} + diff --git a/fuzzing/testdata/contracts/cheat_codes/vm/label.sol b/fuzzing/testdata/contracts/cheat_codes/vm/label.sol deleted file mode 100644 index e3d0a7fd..00000000 --- a/fuzzing/testdata/contracts/cheat_codes/vm/label.sol +++ /dev/null @@ -1,31 +0,0 @@ -// This test ensures that label can be set for an address -interface CheatCodes { - function label(address, string memory) external; -} - -contract LabelContract { - function testLabel(address addr) public { - // Throw an assertion failure so that we can capture the execution trace - assert(false); - } -} - -contract TestContract { - // Obtain our cheat code contract reference. - CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - // Create a contract - LabelContract alice = new LabelContract(); - // Sets address for Bob - address bob = address(0x1); - - constructor() { - // Set label for LabelContract to "Alice" - cheats.label(address(alice), "Alice"); - cheats.label(address(bob), "Bob"); - } - - function test() public { - // Call the label contract - alice.testLabel(address(bob)); - } -} diff --git a/fuzzing/valuegeneration/abi_values.go b/fuzzing/valuegeneration/abi_values.go index c0610efc..d75a68c7 100644 --- a/fuzzing/valuegeneration/abi_values.go +++ b/fuzzing/valuegeneration/abi_values.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "fmt" "github.com/crytic/medusa/logging" + "github.com/crytic/medusa/utils" "math/big" "reflect" "strconv" @@ -322,9 +323,10 @@ func EncodeJSONArgumentsToSlice(inputs abi.Arguments, values []any) ([]any, erro } // EncodeABIArgumentsToString encodes provided go-ethereum ABI package input values into string that is -// human-readable for console output purpose. +// human-readable for console output purpose. A mapping of overrides can also be provided to override the string +// representation of addresses for something else (useful for the vm.label cheatcode). // Returns the string, or an error if one occurs. -func EncodeABIArgumentsToString(inputs abi.Arguments, values []any) (string, error) { +func EncodeABIArgumentsToString(inputs abi.Arguments, values []any, overrides map[common.Address]string) (string, error) { // Create a variable to store string arguments, fill it with the respective arguments var encodedArgs = make([]string, len(inputs)) @@ -339,6 +341,16 @@ func EncodeABIArgumentsToString(inputs abi.Arguments, values []any) (string, err input.Name, input.Type, values[i], err) return "", err } + + // If the ABI-type is an address, see if there is a label override for it + if input.Type.T == abi.AddressTy { + // It's okay to type assert here without capturing an error since that is handled earlier in the flow + if label, ok := overrides[values[i].(common.Address)]; ok { + // Attach the label to the address + arg = utils.AttachLabelToAddress(values[i].(common.Address), label) + } + } + // Store the encoded argument at the current index in the encodedArgs slice encodedArgs[i] = arg } diff --git a/utils/address_utils.go b/utils/address_utils.go index 7e7ae635..b53f6f6f 100644 --- a/utils/address_utils.go +++ b/utils/address_utils.go @@ -2,7 +2,6 @@ package utils import ( "encoding/hex" - "regexp" "strings" "github.com/ethereum/go-ethereum/common" @@ -48,26 +47,21 @@ func HexStringsToAddresses(addressHexStrings []string) ([]common.Address, error) return addresses, nil } -// ResolveAddressToLabelFromString parse a string for ethereum addresses and if found replace that with a label if one exists -func ResolveAddressToLabelFromString(str string, addressToLabel map[common.Address]string) string { - addressRegex := regexp.MustCompile(`0x[a-fA-F0-9]{40}`) - processedString := addressRegex.ReplaceAllStringFunc(str, func(match string) string { - address := common.HexToAddress(match) // Convert the match to an Ethereum address - if label, exists := addressToLabel[address]; exists { - //fmt.Printf("Replacing address %s with label: %s\n", match, label) - return label + " " + "[" + TrimLeadingZeroesFromAddress(address.String()) + "]" // Replace address with label - } - trimmedAddress := TrimLeadingZeroesFromAddress(match) - //fmt.Printf("Address %s does not have a label, trimming leading zeroes to: %s\n", match, trimmedAddress) - return trimmedAddress // Keep the address unchanged if no label is found - }) - - return processedString +// AttachLabelToAddress appends a human-readable label to an address for console-output. If a label is not-provided, +// the address is returned back. Note that this function also trims any leading zeroes from the address to clean it +// up for console output. +func AttachLabelToAddress(address common.Address, label string) string { + trimmedHexString := TrimLeadingZeroesFromAddress(address) + if label == "" { + return trimmedHexString + } + return label + " [" + trimmedHexString + "]" } -// TrimLeadingZeroesFromAddress removes the leading zeroes from an address for readability +// TrimLeadingZeroesFromAddress removes the leading zeroes from an address for readability and returns it as a string // Example: sender=0x0000000000000000000000000000000000030000 becomes sender=0x30000 when shown on console -func TrimLeadingZeroesFromAddress(hexString string) string { +func TrimLeadingZeroesFromAddress(address common.Address) string { + hexString := address.String() if strings.HasPrefix(hexString, "0x") { // Retain "0x" and trim leading zeroes from the rest of the string return "0x" + strings.TrimLeft(hexString[2:], "0") From 0c9156f15c2b6bec1db02a4f785bd1bd870d9d30 Mon Sep 17 00:00:00 2001 From: Anish Naik Date: Sat, 1 Feb 2025 15:03:18 -0500 Subject: [PATCH 08/10] fix lint thing --- fuzzing/fuzzer_worker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuzzing/fuzzer_worker.go b/fuzzing/fuzzer_worker.go index 785e6917..4015a9e4 100644 --- a/fuzzing/fuzzer_worker.go +++ b/fuzzing/fuzzer_worker.go @@ -582,7 +582,7 @@ func (fw *FuzzerWorker) run(baseTestChain *chain.TestChain) (bool, error) { // Copy the labels from the base chain to the worker's chain initializedChain.Labels = maps.Clone(baseTestChain.Labels) - + // Emit an event indicating the worker has created its chain. err = fw.Events.FuzzerWorkerChainCreated.Publish(FuzzerWorkerChainCreatedEvent{ Worker: fw, From 6749190a90290b780a4b5a0a5a6c1a93511a8583 Mon Sep 17 00:00:00 2001 From: Anish Naik Date: Sat, 1 Feb 2025 15:12:37 -0500 Subject: [PATCH 09/10] clean up comments --- fuzzing/executiontracer/execution_trace.go | 3 +-- fuzzing/valuegeneration/abi_values.go | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/fuzzing/executiontracer/execution_trace.go b/fuzzing/executiontracer/execution_trace.go index 01539ac5..95c54035 100644 --- a/fuzzing/executiontracer/execution_trace.go +++ b/fuzzing/executiontracer/execution_trace.go @@ -176,7 +176,7 @@ func (t *ExecutionTrace) generateCallFrameEnterElements(callFrame *CallFrame) ([ } } - // Labels the addresses using addressToLabel and add call information to the elements + // Add call information to the elements elements = append(elements, callInfo, "\n") return elements, consoleLogString @@ -389,7 +389,6 @@ func (t *ExecutionTrace) Log() *logging.LogBuffer { // First, add the elements that make up the overarching execution trace elements, logs := t.generateElementsAndLogsForCallFrame(0, t.TopLevelCallFrame) - // Replace addresses to their corresponding labels if exist buffer.Append(elements...) // If we captured any logs during tracing, add them to the overarching execution trace diff --git a/fuzzing/valuegeneration/abi_values.go b/fuzzing/valuegeneration/abi_values.go index d75a68c7..e9c574a5 100644 --- a/fuzzing/valuegeneration/abi_values.go +++ b/fuzzing/valuegeneration/abi_values.go @@ -343,6 +343,8 @@ func EncodeABIArgumentsToString(inputs abi.Arguments, values []any, overrides ma } // If the ABI-type is an address, see if there is a label override for it + // TODO: This is a little hacky and maybe it should be handled by the internal encodeABIArgumentToString + // function. But realistically neither solution is great. if input.Type.T == abi.AddressTy { // It's okay to type assert here without capturing an error since that is handled earlier in the flow if label, ok := overrides[values[i].(common.Address)]; ok { From 669b1ffed8245054a9d93778a9a3e2350137d7b4 Mon Sep 17 00:00:00 2001 From: Anish Naik Date: Sat, 1 Feb 2025 15:22:23 -0500 Subject: [PATCH 10/10] add one final todo --- utils/address_utils.go | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/address_utils.go b/utils/address_utils.go index b53f6f6f..04faf1a3 100644 --- a/utils/address_utils.go +++ b/utils/address_utils.go @@ -50,6 +50,7 @@ func HexStringsToAddresses(addressHexStrings []string) ([]common.Address, error) // AttachLabelToAddress appends a human-readable label to an address for console-output. If a label is not-provided, // the address is returned back. Note that this function also trims any leading zeroes from the address to clean it // up for console output. +// TODO: Maybe we allow the user to determine whether they want to trim the address of leading zeroes? func AttachLabelToAddress(address common.Address, label string) string { trimmedHexString := TrimLeadingZeroesFromAddress(address) if label == "" {