github.com/klaytn/klaytn@v1.12.1/accounts/abi/topics.go (about)

     1  // Modifications Copyright 2018 The klaytn Authors
     2  // Copyright 2018 The go-ethereum Authors
     3  // This file is part of the go-ethereum library.
     4  //
     5  // The go-ethereum library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Lesser General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // The go-ethereum library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU Lesser General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public License
    16  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  // This file is derived from accounts/abi/topics.go (2018/06/04).
    19  // Modified and improved for the klaytn development.
    20  
    21  package abi
    22  
    23  import (
    24  	"encoding/binary"
    25  	"errors"
    26  	"fmt"
    27  	"math/big"
    28  	"reflect"
    29  
    30  	"github.com/klaytn/klaytn/common"
    31  	"github.com/klaytn/klaytn/crypto"
    32  )
    33  
    34  // MakeTopics converts a filter query argument list into a filter topic set.
    35  func MakeTopics(query ...[]interface{}) ([][]common.Hash, error) {
    36  	topics := make([][]common.Hash, len(query))
    37  	for i, filter := range query {
    38  		for _, rule := range filter {
    39  			var topic common.Hash
    40  
    41  			// Try to generate the topic based on simple types
    42  			switch rule := rule.(type) {
    43  			case common.Hash:
    44  				copy(topic[:], rule[:])
    45  			case common.Address:
    46  				copy(topic[common.HashLength-common.AddressLength:], rule[:])
    47  			case *big.Int:
    48  				blob := rule.Bytes()
    49  				copy(topic[common.HashLength-len(blob):], blob)
    50  			case bool:
    51  				if rule {
    52  					topic[common.HashLength-1] = 1
    53  				}
    54  			case int8:
    55  				copy(topic[:], genIntType(int64(rule), 1))
    56  			case int16:
    57  				copy(topic[:], genIntType(int64(rule), 2))
    58  			case int32:
    59  				copy(topic[:], genIntType(int64(rule), 4))
    60  			case int64:
    61  				copy(topic[:], genIntType(rule, 8))
    62  			case uint8:
    63  				blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
    64  				copy(topic[common.HashLength-len(blob):], blob)
    65  			case uint16:
    66  				blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
    67  				copy(topic[common.HashLength-len(blob):], blob)
    68  			case uint32:
    69  				blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
    70  				copy(topic[common.HashLength-len(blob):], blob)
    71  			case uint64:
    72  				blob := new(big.Int).SetUint64(rule).Bytes()
    73  				copy(topic[common.HashLength-len(blob):], blob)
    74  			case string:
    75  				hash := crypto.Keccak256Hash([]byte(rule))
    76  				copy(topic[:], hash[:])
    77  			case []byte:
    78  				hash := crypto.Keccak256Hash(rule)
    79  				copy(topic[:], hash[:])
    80  
    81  			default:
    82  				// todo(rjl493456442) according solidity documentation, indexed event
    83  				// parameters that are not value types i.e. arrays and structs are not
    84  				// stored directly but instead a keccak256-hash of an encoding is stored.
    85  				//
    86  				// We only convert stringS and bytes to hash, still need to deal with
    87  				// array(both fixed-size and dynamic-size) and struct.
    88  
    89  				// Attempt to generate the topic from funky types
    90  				val := reflect.ValueOf(rule)
    91  				switch {
    92  				// static byte array
    93  				case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8:
    94  					reflect.Copy(reflect.ValueOf(topic[:val.Len()]), val)
    95  				default:
    96  					return nil, fmt.Errorf("unsupported indexed type: %T", rule)
    97  				}
    98  			}
    99  			topics[i] = append(topics[i], topic)
   100  		}
   101  	}
   102  	return topics, nil
   103  }
   104  
   105  func genIntType(rule int64, size uint) []byte {
   106  	var topic [common.HashLength]byte
   107  	if rule < 0 {
   108  		// if a rule is negative, we need to put it into two's complement.
   109  		// extended to common.Hashlength bytes.
   110  		topic = [common.HashLength]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}
   111  	}
   112  	for i := uint(0); i < size; i++ {
   113  		topic[common.HashLength-i-1] = byte(rule >> (i * 8))
   114  	}
   115  	return topic[:]
   116  }
   117  
   118  // ParseTopics converts the indexed topic fields into actual log field values.
   119  func ParseTopics(out interface{}, fields Arguments, topics []common.Hash) error {
   120  	return parseTopicWithSetter(fields, topics,
   121  		func(arg Argument, reconstr interface{}) {
   122  			field := reflect.ValueOf(out).Elem().FieldByName(ToCamelCase(arg.Name))
   123  			field.Set(reflect.ValueOf(reconstr))
   124  		})
   125  }
   126  
   127  // ParseTopicsIntoMap converts the indexed topic field-value pairs into map key-value pairs
   128  func ParseTopicsIntoMap(out map[string]interface{}, fields Arguments, topics []common.Hash) error {
   129  	return parseTopicWithSetter(fields, topics,
   130  		func(arg Argument, reconstr interface{}) {
   131  			out[arg.Name] = reconstr
   132  		})
   133  }
   134  
   135  // parseTopicWithSetter converts the indexed topic field-value pairs and stores them using the
   136  // provided set function.
   137  //
   138  // Note, dynamic types cannot be reconstructed since they get mapped to Keccak256
   139  // hashes as the topic value!
   140  func parseTopicWithSetter(fields Arguments, topics []common.Hash, setter func(Argument, interface{})) error {
   141  	// Sanity check that the fields and topics match up
   142  	if len(fields) != len(topics) {
   143  		return errors.New("topic/field count mismatch")
   144  	}
   145  	// Iterate over all the fields and reconstruct them from topics
   146  	for i, arg := range fields {
   147  		if !arg.Indexed {
   148  			return errors.New("non-indexed field in topic reconstruction")
   149  		}
   150  		var reconstr interface{}
   151  		switch arg.Type.T {
   152  		case TupleTy:
   153  			return errors.New("tuple type in topic reconstruction")
   154  		case StringTy, BytesTy, SliceTy, ArrayTy:
   155  			// Array types (including strings and bytes) have their keccak256 hashes stored in the topic- not a hash
   156  			// whose bytes can be decoded to the actual value- so the best we can do is retrieve that hash
   157  			reconstr = topics[i]
   158  		case FunctionTy:
   159  			if garbage := binary.BigEndian.Uint64(topics[i][0:8]); garbage != 0 {
   160  				return fmt.Errorf("bind: got improperly encoded function type, got %v", topics[i].Bytes())
   161  			}
   162  			var tmp [24]byte
   163  			copy(tmp[:], topics[i][8:32])
   164  			reconstr = tmp
   165  		default:
   166  			var err error
   167  			reconstr, err = toGoType(0, arg.Type, topics[i].Bytes())
   168  			if err != nil {
   169  				return err
   170  			}
   171  		}
   172  		// Use the setter function to store the value
   173  		setter(arg, reconstr)
   174  	}
   175  
   176  	return nil
   177  }