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  }