Skip to content

Commit

Permalink
add partial handling of event signature collision
Browse files Browse the repository at this point in the history
  • Loading branch information
Tofel committed Jan 9, 2025
1 parent a3f07b2 commit 0a0c4f0
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 171 deletions.
89 changes: 43 additions & 46 deletions seth/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"math/big"
"net/http"
"path/filepath"
"reflect"
"strings"
"time"

Expand Down Expand Up @@ -1167,28 +1168,56 @@ func (t TransactionLog) GetData() []byte {
}

func (m *Client) decodeContractLogs(l zerolog.Logger, logs []types.Log, allABIs []*abi.ABI) ([]DecodedTransactionLog, error) {
l.Trace().Msg("Decoding events")
l.Trace().
Msg("Decoding events")
sigMap := buildEventSignatureMap(allABIs)

var eventsParsed []DecodedTransactionLog
for _, lo := range logs {
if len(lo.Topics) == 0 {
l.Debug().Msg("Log has no topics; skipping")
l.Debug().
Msg("Log has no topics; skipping")
continue
}

eventSig := lo.Topics[0].Hex()
possibleEvents, exists := sigMap[eventSig]
if !exists {
l.Trace().Str("Signature", eventSig).Msg("No matching events found for signature")
l.Trace().
Str("Event signature", eventSig).
Msg("No matching events found for signature")
continue
}

// Check if we know what contract is this log from and if we do, get its ABI to skip unnecessary iterations
var knownContractABI *abi.ABI
if contractName := m.ContractAddressToNameMap.GetContractName(lo.Address.Hex()); contractName != "" {
maybeABI, ok := m.ContractStore.GetABI(contractName)
if !ok {
l.Trace().
Str("Event signature", eventSig).
Str("Contract name", contractName).
Str("Contract address", lo.Address.Hex()).
Msg("No ABI found for known contract; this is unexpected. Continuing with step-by-step ABI search")
} else {
knownContractABI = maybeABI
}
}

// Iterate over possible events with the same signature
matched := false
for _, evWithABI := range possibleEvents {
evSpec := evWithABI.EventSpec
eventABI := evWithABI.EventABI
contractABI := evWithABI.ContractABI

// Check if known contract ABI matches candidate ABI and if not, skip this ABI and try the next one
if knownContractABI != nil && !reflect.DeepEqual(knownContractABI, contractABI) {
l.Trace().
Str("Event signature", eventSig).
Str("Contract address", lo.Address.Hex()).
Msg("ABI doesn't match known ABI for this address; trying next ABI")
continue
}

// Validate indexed parameters count
// Non-indexed parameters are stored in the Data field,
Expand Down Expand Up @@ -1220,7 +1249,7 @@ func (m *Client) decodeContractLogs(l zerolog.Logger, logs []types.Log, allABIs
Str("Signature", evSpec.Sig).
Msg("Unpacking event")

eventsMap, topicsMap, err := decodeEventFromLog(l, *eventABI, *evSpec, d)
eventsMap, topicsMap, err := decodeEventFromLog(l, *contractABI, *evSpec, d)
if err != nil {
l.Error().
Err(err).
Expand Down Expand Up @@ -1256,55 +1285,23 @@ func (m *Client) decodeContractLogs(l zerolog.Logger, logs []types.Log, allABIs
return eventsParsed, nil
}

// func (m *Client) decodeContractLogs(l zerolog.Logger, logs []types.Log, allABIs []*abi.ABI) ([]DecodedTransactionLog, error) {
// l.Trace().Msg("Decoding events")
// var eventsParsed []DecodedTransactionLog
// for _, lo := range logs {
// ABI_LOOP:
// for _, a := range allABIs {
// for _, evSpec := range a.Events {
// if evSpec.ID.Hex() == lo.Topics[0].Hex() {
// d := TransactionLog{lo.Topics, lo.Data}
// l.Trace().Str("Name", evSpec.RawName).Str("Signature", evSpec.Sig).Msg("Unpacking event")
// eventsMap, topicsMap, err := decodeEventFromLog(l, *a, evSpec, d)
// if err != nil {
// return nil, errors.Wrap(err, ErrDecodeLog)
// }
// parsedEvent := decodedLogFromMaps(&DecodedTransactionLog{}, eventsMap, topicsMap)
// decodedTransactionLog, ok := parsedEvent.(*DecodedTransactionLog)
// if ok {
// decodedTransactionLog.Signature = evSpec.Sig
// m.mergeLogMeta(decodedTransactionLog, lo)
// eventsParsed = append(eventsParsed, *decodedTransactionLog)
// l.Trace().Interface("Log", parsedEvent).Msg("Transaction log")
// break ABI_LOOP
// }
// l.Trace().
// Str("Actual type", fmt.Sprintf("%T", decodedTransactionLog)).
// Msg("Failed to cast decoded event to DecodedCommonLog")
// }
// }
// }
// }
// return eventsParsed, nil
// }

type EventWithABI struct {
EventABI *abi.ABI
EventSpec *abi.Event
type eventWithABI struct {
ContractABI *abi.ABI
EventSpec *abi.Event
}

// buildEventSignatureMap precomputes a mapping from event signature to events with their ABIs
func buildEventSignatureMap(allABIs []*abi.ABI) map[string][]*EventWithABI {
sigMap := make(map[string][]*EventWithABI)
func buildEventSignatureMap(allABIs []*abi.ABI) map[string][]*eventWithABI {
sigMap := make(map[string][]*eventWithABI)
for _, a := range allABIs {
for _, ev := range a.Events {
sigMap[ev.ID.Hex()] = append(sigMap[ev.ID.Hex()], &EventWithABI{
EventABI: a,
EventSpec: &ev,
sigMap[ev.ID.Hex()] = append(sigMap[ev.ID.Hex()], &eventWithABI{
ContractABI: a,
EventSpec: &ev,
})
}
}

return sigMap
}

Expand Down
240 changes: 120 additions & 120 deletions seth/client_decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,85 +75,85 @@ func TestSmokeDebugData(t *testing.T) {
}

tests := []test{
// {
// name: "test named inputs/outputs",
// method: "emitNamedInputsOutputs",
// params: []interface{}{big.NewInt(1337), "test"},
// write: true,
// output: seth.DecodedTransaction{
// CommonData: seth.CommonData{
// Input: map[string]interface{}{
// "inputVal1": big.NewInt(1337),
// "inputVal2": "test",
// },
// },
// },
// },
// // TODO: https://docs.soliditylang.org/en/v0.8.19/control-structures.html read and figure out if
// // decoding anynymous + named is heavily used and needed, usually people name params and omit output names
// {
// name: "test anonymous inputs/outputs",
// method: "emitInputsOutputs",
// params: []interface{}{big.NewInt(1337), "test"},
// write: true,
// output: seth.DecodedTransaction{
// CommonData: seth.CommonData{
// Input: map[string]interface{}{
// "inputVal1": big.NewInt(1337),
// "inputVal2": "test",
// },
// },
// },
// },
// {
// name: "test one log no index",
// method: "emitNoIndexEvent",
// write: true,
// output: seth.DecodedTransaction{
// Events: []seth.DecodedTransactionLog{
// {
// DecodedCommonLog: seth.DecodedCommonLog{
// EventData: map[string]interface{}{
// "sender": c.Addresses[0],
// },
// },
// },
// },
// },
// },
// {
// name: "test one log index",
// method: "emitOneIndexEvent",
// write: true,
// output: seth.DecodedTransaction{
// Events: []seth.DecodedTransactionLog{
// {
// DecodedCommonLog: seth.DecodedCommonLog{
// EventData: map[string]interface{}{
// "a": big.NewInt(83),
// },
// },
// },
// },
// },
// },
// {
// name: "test two log index",
// method: "emitTwoIndexEvent",
// write: true,
// output: seth.DecodedTransaction{
// Events: []seth.DecodedTransactionLog{
// {
// DecodedCommonLog: seth.DecodedCommonLog{
// EventData: map[string]interface{}{
// "roundId": big.NewInt(1),
// "startedBy": c.Addresses[0],
// },
// },
// },
// },
// },
// },
{
name: "test named inputs/outputs",
method: "emitNamedInputsOutputs",
params: []interface{}{big.NewInt(1337), "test"},
write: true,
output: seth.DecodedTransaction{
CommonData: seth.CommonData{
Input: map[string]interface{}{
"inputVal1": big.NewInt(1337),
"inputVal2": "test",
},
},
},
},
// TODO: https://docs.soliditylang.org/en/v0.8.19/control-structures.html read and figure out if
// decoding anynymous + named is heavily used and needed, usually people name params and omit output names
{
name: "test anonymous inputs/outputs",
method: "emitInputsOutputs",
params: []interface{}{big.NewInt(1337), "test"},
write: true,
output: seth.DecodedTransaction{
CommonData: seth.CommonData{
Input: map[string]interface{}{
"inputVal1": big.NewInt(1337),
"inputVal2": "test",
},
},
},
},
{
name: "test one log no index",
method: "emitNoIndexEvent",
write: true,
output: seth.DecodedTransaction{
Events: []seth.DecodedTransactionLog{
{
DecodedCommonLog: seth.DecodedCommonLog{
EventData: map[string]interface{}{
"sender": c.Addresses[0],
},
},
},
},
},
},
{
name: "test one log index",
method: "emitOneIndexEvent",
write: true,
output: seth.DecodedTransaction{
Events: []seth.DecodedTransactionLog{
{
DecodedCommonLog: seth.DecodedCommonLog{
EventData: map[string]interface{}{
"a": big.NewInt(83),
},
},
},
},
},
},
{
name: "test two log index",
method: "emitTwoIndexEvent",
write: true,
output: seth.DecodedTransaction{
Events: []seth.DecodedTransactionLog{
{
DecodedCommonLog: seth.DecodedCommonLog{
EventData: map[string]interface{}{
"roundId": big.NewInt(1),
"startedBy": c.Addresses[0],
},
},
},
},
},
},
{
name: "test three log index",
method: "emitThreeIndexEvent",
Expand All @@ -172,47 +172,47 @@ func TestSmokeDebugData(t *testing.T) {
},
},
},
// {
// name: "test log no index string",
// method: "emitNoIndexEventString",
// write: true,
// output: seth.DecodedTransaction{
// Events: []seth.DecodedTransactionLog{
// {
// DecodedCommonLog: seth.DecodedCommonLog{
// EventData: map[string]interface{}{
// "str": "myString",
// },
// },
// },
// },
// },
// },
// // emitNoIndexStructEvent
// {
// name: "test log struct",
// method: "emitNoIndexStructEvent",
// write: true,
// output: seth.DecodedTransaction{
// Events: []seth.DecodedTransactionLog{
// {
// DecodedCommonLog: seth.DecodedCommonLog{
// EventData: map[string]interface{}{
// "a": struct {
// Name string `json:"name"`
// Balance uint64 `json:"balance"`
// DailyLimit *big.Int `json:"dailyLimit"`
// }{
// Name: "John",
// Balance: 5,
// DailyLimit: big.NewInt(10),
// },
// },
// },
// },
// },
// },
// },
{
name: "test log no index string",
method: "emitNoIndexEventString",
write: true,
output: seth.DecodedTransaction{
Events: []seth.DecodedTransactionLog{
{
DecodedCommonLog: seth.DecodedCommonLog{
EventData: map[string]interface{}{
"str": "myString",
},
},
},
},
},
},
// emitNoIndexStructEvent
{
name: "test log struct",
method: "emitNoIndexStructEvent",
write: true,
output: seth.DecodedTransaction{
Events: []seth.DecodedTransactionLog{
{
DecodedCommonLog: seth.DecodedCommonLog{
EventData: map[string]interface{}{
"a": struct {
Name string `json:"name"`
Balance uint64 `json:"balance"`
DailyLimit *big.Int `json:"dailyLimit"`
}{
Name: "John",
Balance: 5,
DailyLimit: big.NewInt(10),
},
},
},
},
},
},
},
// TODO: another case - figure out if indexed strings are used by anyone in events
// https://ethereum.stackexchange.com/questions/6840/indexed-event-with-string-not-getting-logged
}
Expand Down
Loading

0 comments on commit 0a0c4f0

Please sign in to comment.