github.com/lmittmann/w3@v0.20.0/event.go (about) 1 package w3 2 3 import ( 4 "fmt" 5 6 "github.com/ethereum/go-ethereum/accounts/abi" 7 "github.com/ethereum/go-ethereum/common" 8 "github.com/ethereum/go-ethereum/core/types" 9 "github.com/ethereum/go-ethereum/crypto" 10 _abi "github.com/lmittmann/w3/internal/abi" 11 ) 12 13 // Event represents a Smart Contract event decoder. 14 type Event struct { 15 Signature string // Event signature 16 Topic0 common.Hash // Hash of event signature (Topic 0) 17 Args abi.Arguments // Arguments 18 19 indexedArgs abi.Arguments // Subset of Args that are indexed 20 } 21 22 // NewEvent returns a new Smart Contract event log decoder from the given 23 // Solidity event signature. 24 // 25 // The optional tuples parameter accepts struct definitions that can be 26 // referenced by name in the signature instead of using inline tuple 27 // definitions. This enables cleaner, more readable function signatures when 28 // working with complex tuple types. 29 // 30 // An error is returned if the signature parsing fails. 31 func NewEvent(signature string, tuples ...any) (*Event, error) { 32 name, args, err := _abi.ParseWithName(signature, tuples...) 33 if err != nil { 34 return nil, fmt.Errorf("%w: %v", ErrInvalidABI, err) 35 } 36 if name == "" { 37 return nil, fmt.Errorf("%w: missing event name", ErrInvalidABI) 38 } 39 40 indexedArgs := make(abi.Arguments, 0) 41 for _, arg := range args { 42 if arg.Indexed { 43 arg.Indexed = false 44 indexedArgs = append(indexedArgs, arg) 45 } 46 } 47 48 sig := args.SignatureWithName(name) 49 return &Event{ 50 Signature: sig, 51 Topic0: crypto.Keccak256Hash([]byte(sig)), 52 Args: abi.Arguments(args), 53 indexedArgs: indexedArgs, 54 }, nil 55 } 56 57 // MustNewEvent is like [NewEvent] but panics if the signature parsing fails. 58 func MustNewEvent(signature string, tuples ...any) *Event { 59 event, err := NewEvent(signature, tuples...) 60 if err != nil { 61 panic(err) 62 } 63 return event 64 } 65 66 // DecodeArgs decodes the topics and data of the given log to the given args. 67 func (e *Event) DecodeArgs(log *types.Log, args ...any) error { 68 if len(log.Topics) <= 0 || e.Topic0 != log.Topics[0] { 69 return fmt.Errorf("w3: topic0 mismatch") 70 } 71 72 if len(e.Args) != len(args) { 73 return fmt.Errorf("%w: expected %d arguments, got %d", ErrArgumentMismatch, len(e.Args), len(args)) 74 } 75 if len(e.indexedArgs) != len(log.Topics)-1 { 76 return fmt.Errorf("%w: expected %d indexed arguments, got %d", ErrArgumentMismatch, len(e.indexedArgs), len(log.Topics)-1) 77 } 78 79 indexedArgs := make([]any, 0, len(e.indexedArgs)) 80 nonIndexedArgs := make([]any, 0, len(e.Args)-len(e.indexedArgs)) 81 for i, arg := range e.Args { 82 if arg.Indexed { 83 indexedArgs = append(indexedArgs, args[i]) 84 } else { 85 nonIndexedArgs = append(nonIndexedArgs, args[i]) 86 } 87 } 88 89 // decode indexed args 90 for i, arg := range indexedArgs { 91 if err := (_abi.Arguments{e.indexedArgs[i]}).Decode(log.Topics[i+1][:], arg); err != nil { 92 return err 93 } 94 } 95 96 // decode non-indexed args 97 if err := _abi.Arguments(e.Args).Decode(log.Data, nonIndexedArgs...); err != nil { 98 return err 99 } 100 return nil 101 }