code.vegaprotocol.io/vega@v0.79.0/core/datasource/external/ethcall/result.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package ethcall 17 18 import ( 19 "bytes" 20 "context" 21 "encoding/json" 22 "fmt" 23 "strconv" 24 "text/scanner" 25 26 ethcallcommon "code.vegaprotocol.io/vega/core/datasource/external/ethcall/common" 27 28 "github.com/PaesslerAG/gval" 29 "github.com/PaesslerAG/jsonpath" 30 ) 31 32 type Result struct { 33 Bytes []byte 34 Values []any 35 Normalised map[string]string 36 PassesFilters bool 37 } 38 39 func NewResult(spec ethcallcommon.Spec, bytes []byte) (Result, error) { 40 call, err := NewCall(spec) 41 if err != nil { 42 return Result{}, fmt.Errorf("failed to create result: %w", err) 43 } 44 45 return newResult(call, bytes) 46 } 47 48 func newResult(call Call, bytes []byte) (Result, error) { 49 values, err := call.abi.Unpack(call.method, bytes) 50 if err != nil { 51 return Result{}, fmt.Errorf("failed to unpack contract call result: %w", err) 52 } 53 54 normalised, err := normaliseValues(values, call.spec.Normalisers) 55 if err != nil { 56 return Result{}, fmt.Errorf("failed to normalise contract call result: %w", err) 57 } 58 59 passesFilters, err := call.filters.Match(normalised) 60 if err != nil { 61 return Result{}, fmt.Errorf("error evaluating filters: %w", err) 62 } 63 64 return Result{ 65 Bytes: bytes, 66 Values: values, 67 Normalised: normalised, 68 PassesFilters: passesFilters, 69 }, nil 70 } 71 72 func normaliseValues(values []any, rules map[string]string) (map[string]string, error) { 73 // The data in 'values' is relatively well typed, after being unpacked by the ABI. 74 // For example, a uint256 will be a big.Int, structs returned from the contract call 75 // will be anonymous go structs rather than a string map etc.. In order to fish data 76 // out with jsonpath it need to be simple lists, maps, strings and numbers; so we 77 // serialise to json and then deserialise to an into an []interface{}. 78 valuesJson, err := json.Marshal(values) 79 if err != nil { 80 return nil, fmt.Errorf("unable to serialse values: %v", err) 81 } 82 83 valuesSimple := []interface{}{} 84 d := json.NewDecoder(bytes.NewBuffer(valuesJson)) 85 // Keep numbers as a json.Number, which holds the original string representation 86 // otherwise all numbers get cast to float64 which is no good for e.g uint256. 87 d.UseNumber() 88 err = d.Decode(&valuesSimple) 89 if err != nil { 90 return nil, fmt.Errorf("unable to deserialse values: %v", err) 91 } 92 93 res := make(map[string]string) 94 95 for key, path := range rules { 96 value, err := myJSONPathGet(path, valuesSimple) 97 if err != nil { 98 return nil, fmt.Errorf("unable to normalise key %v: %v", key, err) 99 } 100 switch v := value.(type) { 101 case json.Number: 102 res[key] = v.String() 103 case int64: 104 // all of the numbers in the json from the ethereum call result will be 105 // json.Number and handled above; this case is just for the corner case 106 // where someone specifies a number as a static value in the json path itself 107 res[key] = strconv.FormatInt(v, 10) 108 case string: 109 res[key] = v 110 case bool: 111 res[key] = strconv.FormatBool(v) 112 default: 113 return nil, fmt.Errorf("unable to normalise key %v of type %T", key, value) 114 } 115 } 116 117 return res, nil 118 } 119 120 // myJSONPathGet works exactly like jsonpath.Get(path, values), except that any numbers found 121 // are returned as int64 rather than float64 which is the default. 122 // ** in the path expression itself, not the json being queried ** 123 // Evaluation will fail if numbers in the path are not integers. 124 func myJSONPathGet(path string, values []interface{}) (interface{}, error) { 125 baselang := jsonpath.Language() 126 mylang := gval.PrefixExtension(scanner.Int, parseNumberAsInt64) 127 lang := gval.NewLanguage(baselang, mylang) 128 129 eval, err := lang.NewEvaluable(path) 130 if err != nil { 131 return nil, err 132 } 133 value, err := eval(context.Background(), values) 134 return value, err 135 } 136 137 func parseNumberAsInt64(c context.Context, p *gval.Parser) (gval.Evaluable, error) { 138 n, err := strconv.ParseInt(p.TokenText(), 10, 64) 139 if err != nil { 140 return nil, err 141 } 142 return p.Const(n), nil 143 }