github.com/amazechain/amc@v0.1.3/internal/api/filters/filter_query.go (about)

     1  package filters
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"github.com/amazechain/amc/common/hexutil"
     8  	"github.com/amazechain/amc/common/types"
     9  	mvm_common "github.com/amazechain/amc/internal/avm/common"
    10  	mvm_types "github.com/amazechain/amc/internal/avm/types"
    11  	"github.com/amazechain/amc/modules/rpc/jsonrpc"
    12  	"math/big"
    13  )
    14  
    15  // FilterCriteria contains options for contract log filtering.
    16  type FilterCriteria struct {
    17  	BlockHash types.Hash      // used by eth_getLogs, return logs only from block with this hash
    18  	FromBlock *big.Int        // beginning of the queried range, nil means genesis block
    19  	ToBlock   *big.Int        // end of the rtypes nil means latest block
    20  	Addresses []types.Address // restricts matches to events created by specific contracts
    21  
    22  	// The Topic list restricts matches to particular event topics. Each event has a list
    23  	// of topics. Topics matches a prefix of that list. An empty element slice matches any
    24  	// topic. Non-empty elements represent an alternative that matches any of the
    25  	// contained topics.
    26  	//
    27  	// Examples:
    28  	// {} or nil          matches any topic list
    29  	// {{A}}              matches topic A in first position
    30  	// {{}, {B}}          matches any topic in first position AND B in second position
    31  	// {{A}, {B}}         matches topic A in first position AND B in second position
    32  	// {{A, B}, {C, D}}   matches topic (A OR B) in first position AND (C OR D) in second position
    33  	Topics [][]types.Hash
    34  }
    35  
    36  // UnmarshalJSON sets *args fields with given data.
    37  func (args *FilterCriteria) UnmarshalJSON(data []byte) error {
    38  	type input struct {
    39  		BlockHash *mvm_common.Hash     `json:"blockHash"`
    40  		FromBlock *jsonrpc.BlockNumber `json:"fromBlock"`
    41  		ToBlock   *jsonrpc.BlockNumber `json:"toBlock"`
    42  		Addresses interface{}          `json:"address"`
    43  		Topics    []interface{}        `json:"topics"`
    44  	}
    45  
    46  	var raw input
    47  	if err := json.Unmarshal(data, &raw); err != nil {
    48  		return err
    49  	}
    50  
    51  	if raw.BlockHash != nil {
    52  		if raw.FromBlock != nil || raw.ToBlock != nil {
    53  			// BlockHash is mutually exclusive with FromBlock/ToBlock criteria
    54  			return fmt.Errorf("cannot specify both BlockHash and FromBlock/ToBlock, choose one or the other")
    55  		}
    56  		args.BlockHash = mvm_types.ToAmcHash(*raw.BlockHash)
    57  	} else {
    58  		if raw.FromBlock != nil {
    59  			args.FromBlock = big.NewInt(raw.FromBlock.Int64())
    60  		}
    61  
    62  		if raw.ToBlock != nil {
    63  			args.ToBlock = big.NewInt(raw.ToBlock.Int64())
    64  		}
    65  	}
    66  
    67  	args.Addresses = []types.Address{}
    68  
    69  	if raw.Addresses != nil {
    70  		// raw.Address can contain a single address or an array of addresses
    71  		switch rawAddr := raw.Addresses.(type) {
    72  		case []interface{}:
    73  			for i, addr := range rawAddr {
    74  				if strAddr, ok := addr.(string); ok {
    75  					addr, err := decodeAddress(strAddr)
    76  					if err != nil {
    77  						return fmt.Errorf("invalid address at index %d: %v", i, err)
    78  					}
    79  					args.Addresses = append(args.Addresses, *mvm_types.ToAmcAddress(&addr))
    80  				} else {
    81  					return fmt.Errorf("non-string address at index %d", i)
    82  				}
    83  			}
    84  		case string:
    85  			addr, err := decodeAddress(rawAddr)
    86  			if err != nil {
    87  				return fmt.Errorf("invalid address: %v", err)
    88  			}
    89  			args.Addresses = []types.Address{*mvm_types.ToAmcAddress(&addr)}
    90  		default:
    91  			return errors.New("invalid addresses in query")
    92  		}
    93  	}
    94  
    95  	// topics is an array consisting of strings and/or arrays of strings.
    96  	// JSON null values are converted to types.Hash{} and ignored by the filter manager.
    97  	if len(raw.Topics) > 0 {
    98  		args.Topics = make([][]types.Hash, len(raw.Topics))
    99  		for i, t := range raw.Topics {
   100  			switch topic := t.(type) {
   101  			case nil:
   102  				// ignore topic when matching logs
   103  
   104  			case string:
   105  				// match specific topic
   106  				top, err := decodeTopic(topic)
   107  				if err != nil {
   108  					return err
   109  				}
   110  				args.Topics[i] = []types.Hash{mvm_types.ToAmcHash(top)}
   111  
   112  			case []interface{}:
   113  				// or case e.g. [null, "topic0", "topic1"]
   114  				for _, rawTopic := range topic {
   115  					if rawTopic == nil {
   116  						// null component, match all
   117  						args.Topics[i] = nil
   118  						break
   119  					}
   120  					if topic, ok := rawTopic.(string); ok {
   121  						parsed, err := decodeTopic(topic)
   122  						if err != nil {
   123  							return err
   124  						}
   125  						args.Topics[i] = append(args.Topics[i], mvm_types.ToAmcHash(parsed))
   126  					} else {
   127  						return fmt.Errorf("invalid topic(s)")
   128  					}
   129  				}
   130  			default:
   131  				return fmt.Errorf("invalid topic(s)")
   132  			}
   133  		}
   134  	}
   135  
   136  	return nil
   137  }
   138  
   139  func decodeAddress(s string) (mvm_common.Address, error) {
   140  	b, err := hexutil.Decode(s)
   141  	if err == nil && len(b) != mvm_common.AddressLength {
   142  		err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for address", len(b), mvm_common.AddressLength)
   143  	}
   144  	return mvm_common.BytesToAddress(b), err
   145  }
   146  
   147  func decodeTopic(s string) (mvm_common.Hash, error) {
   148  	b, err := hexutil.Decode(s)
   149  	if err == nil && len(b) != mvm_common.HashLength {
   150  		err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for topic", len(b), mvm_common.HashLength)
   151  	}
   152  	return mvm_common.BytesToHash(b), err
   153  }