blockbook/bchain/coins/eth/dataparser.go
2023-02-01 17:58:37 +01:00

295 lines
6.6 KiB
Go

package eth
import (
"bytes"
"encoding/hex"
"math/big"
"runtime/debug"
"strconv"
"strings"
"unicode"
"unicode/utf8"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/golang/glog"
"github.com/trezor/blockbook/bchain"
)
func parseSimpleNumericProperty(data string) *big.Int {
if has0xPrefix(data) {
data = data[2:]
}
if len(data) > 64 {
data = data[:64]
}
if len(data) == 64 {
var n big.Int
_, ok := n.SetString(data, 16)
if ok {
return &n
}
}
return nil
}
func parseSimpleStringProperty(data string) string {
if has0xPrefix(data) {
data = data[2:]
}
if len(data) > 128 {
n := parseSimpleNumericProperty(data[64:128])
if n != nil {
l := n.Uint64()
if l > 0 && 2*int(l) <= len(data)-128 {
b, err := hex.DecodeString(data[128 : 128+2*l])
if err == nil {
return string(b)
}
}
}
}
// allow string properties as UTF-8 data
b, err := hex.DecodeString(data)
if err == nil {
i := bytes.Index(b, []byte{0})
if i > 32 {
i = 32
}
if i > 0 {
b = b[:i]
}
if utf8.Valid(b) {
return string(b)
}
}
return ""
}
func decamel(s string) string {
var b bytes.Buffer
splittable := false
for i, v := range s {
if i == 0 {
b.WriteRune(unicode.ToUpper(v))
} else {
if splittable && unicode.IsUpper(v) {
b.WriteByte(' ')
}
b.WriteRune(v)
splittable = unicode.IsLower(v) || unicode.IsNumber(v)
}
}
return b.String()
}
func GetSignatureFromData(data string) uint32 {
if has0xPrefix(data) {
data = data[2:]
}
if len(data) < 8 {
return 0
}
sig, err := strconv.ParseUint(data[:8], 16, 32)
if err != nil {
return 0
}
return uint32(sig)
}
const ErrorTy byte = 255
func processParam(data string, index int, t *abi.Type, processed []bool) ([]string, int, bool) {
var retval []string
d := index << 6
if d+64 > len(data) {
return nil, 0, false
}
block := data[d : d+64]
switch t.T {
// static types
case abi.IntTy, abi.UintTy, abi.BoolTy:
var n big.Int
_, ok := n.SetString(block, 16)
if !ok {
return nil, 0, false
}
if t.T == abi.BoolTy {
if n.Int64() != 0 {
retval = []string{"true"}
} else {
retval = []string{"false"}
}
} else {
retval = []string{n.String()}
}
processed[index] = true
index++
case abi.AddressTy:
b, err := hex.DecodeString(block[24:])
if err != nil {
return nil, 0, false
}
retval = []string{EIP55Address(b)}
processed[index] = true
index++
case abi.FixedBytesTy:
retval = []string{"0x" + block[:t.Size<<1]}
processed[index] = true
index++
case abi.ArrayTy:
for i := 0; i < t.Size; i++ {
var r []string
var ok bool
r, index, ok = processParam(data, index, t.Elem, processed)
if !ok {
return nil, 0, false
}
retval = append(retval, r...)
}
// dynamic types
case abi.StringTy, abi.BytesTy, abi.SliceTy:
// get offset of dynamic type
offset, err := strconv.ParseInt(block, 16, 64)
if err != nil {
return nil, 0, false
}
processed[index] = true
index++
offset <<= 1
d = int(offset)
dynIndex := d >> 6
if d+64 > len(data) || d < 0 {
return nil, 0, false
}
// get element count of dynamic type
c, err := strconv.ParseInt(data[d:d+64], 16, 64)
count := int(c)
if err != nil {
return nil, 0, false
}
processed[dynIndex] = true
dynIndex++
if t.T == abi.StringTy || t.T == abi.BytesTy {
d += 64
de := d + (count << 1)
if de > len(data) {
return nil, 0, false
}
if count == 0 {
retval = []string{""}
} else {
block = data[d:de]
if t.T == abi.StringTy {
b, err := hex.DecodeString(block)
if err != nil {
return nil, 0, false
}
retval = []string{string(b)}
} else {
retval = []string{"0x" + block}
}
count = ((count - 1) >> 5) + 1
for i := 0; i < count; i++ {
processed[dynIndex] = true
dynIndex++
}
}
} else {
for i := 0; i < count; i++ {
var r []string
var ok bool
r, dynIndex, ok = processParam(data, dynIndex, t.Elem, processed)
if !ok {
return nil, 0, false
}
retval = append(retval, r...)
}
}
// types not processed
case abi.HashTy, abi.FixedPointTy, abi.FunctionTy, abi.TupleTy:
fallthrough
default:
return nil, 0, false
}
return retval, index, true
}
func tryParseParams(data string, params []string, parsedParams []abi.Type) []bchain.EthereumParsedInputParam {
processed := make([]bool, len(data)/64)
parsed := make([]bchain.EthereumParsedInputParam, len(params))
index := 0
var values []string
var ok bool
for i := range params {
t := &parsedParams[i]
values, index, ok = processParam(data, index, t, processed)
if !ok {
return nil
}
parsed[i] = bchain.EthereumParsedInputParam{Type: params[i], Values: values}
}
// all data must be processed, otherwise wrong signature
for _, p := range processed {
if !p {
return nil
}
}
return parsed
}
// ParseInputData tries to parse transaction input data from known FourByteSignatures
// as there may be multiple signatures for the same four bytes, it tries to match the input to the known parameters
// it does not parse tuples for now
func ParseInputData(signatures *[]bchain.FourByteSignature, data string) *bchain.EthereumParsedInputData {
if len(data) <= 2 { // data is empty or 0x
return &bchain.EthereumParsedInputData{Name: "Transfer"}
}
if len(data) < 10 || (len(data)-10)%64 != 0 {
return nil
}
parsed := bchain.EthereumParsedInputData{
MethodId: data[:10],
}
defer func() {
if r := recover(); r != nil {
glog.Error("ParseInputData recovered from panic: ", r, ", ", data, ",signatures ", signatures)
debug.PrintStack()
}
}()
if signatures != nil {
data = data[10:]
for i := range *signatures {
s := &(*signatures)[i]
// if not yet done, set DecamelName and Function and parse parameter types from string to abi.Type
// the signatures are stored in cache
if s.DecamelName == "" {
s.DecamelName = decamel(s.Name)
s.Function = s.Name + "(" + strings.Join(s.Parameters, ", ") + ")"
s.ParsedParameters = make([]abi.Type, len(s.Parameters))
for j := range s.Parameters {
var t abi.Type
if len(s.Parameters[j]) > 0 && s.Parameters[j][0] == '(' {
// Tuple type is not supported for now
t = abi.Type{T: abi.TupleTy}
} else {
var err error
t, err = abi.NewType(s.Parameters[j], "", nil)
if err != nil {
t = abi.Type{T: ErrorTy}
}
}
s.ParsedParameters[j] = t
}
}
parsedParams := tryParseParams(data, s.Parameters, s.ParsedParameters)
if parsedParams != nil {
parsed.Name = s.DecamelName
parsed.Function = s.Function
parsed.Params = parsedParams
break
}
}
}
return &parsed
}