code.vegaprotocol.io/vega@v0.79.0/core/datasource/external/ethcall/common/spec.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 common 17 18 import ( 19 "encoding/base64" 20 "encoding/hex" 21 "errors" 22 "fmt" 23 "sort" 24 "strings" 25 26 "code.vegaprotocol.io/vega/core/datasource/common" 27 vegapb "code.vegaprotocol.io/vega/protos/vega" 28 29 "google.golang.org/protobuf/types/known/structpb" 30 ) 31 32 var ( 33 ErrCallSpecIsNil = errors.New("ethereum call spec proto is nil") 34 ErrInvalidEthereumAbi = errors.New("is not a valid ethereum abi definition") 35 ErrInvalidCallTrigger = errors.New("ethereum call trigger not valid") 36 ErrInvalidCallArgs = errors.New("ethereum call args not valid") 37 ErrInvalidFilters = errors.New("ethereum call filters not valid") 38 ) 39 40 type Spec struct { 41 Address string 42 AbiJson []byte 43 Method string 44 ArgsJson []string 45 Trigger Trigger 46 RequiredConfirmations uint64 47 Normalisers map[string]string 48 Filters common.SpecFilters 49 SourceChainID uint64 50 } 51 52 func SpecFromProto(proto *vegapb.EthCallSpec) (Spec, error) { 53 if proto == nil { 54 return Spec{}, ErrCallSpecIsNil 55 } 56 57 trigger, err := TriggerFromProto(proto.Trigger) 58 if err != nil { 59 return Spec{}, errors.Join(ErrInvalidCallTrigger, err) 60 } 61 62 filters := common.SpecFiltersFromProto(proto.Filters) 63 64 abiBytes := []byte(proto.Abi) 65 66 jsonArgs := []string{} 67 for _, protoArg := range proto.Args { 68 // special case for string starting with 0x? and are 32bytes long. 69 // this is to match the ethereum bytes32 type 70 // down the line we are trying to call the contract with strings 71 // instead here we want to encode the value into a base64 string 72 // which gets unwrapped into a [32]byte by the ethereum library 73 var isBytes bool 74 var jsonArg []byte 75 stringValue := protoArg.GetStringValue() 76 77 if stringValue != "" && strings.HasPrefix(stringValue, "0x") { 78 bytes, err := hex.DecodeString(stringValue[2:]) 79 if err == nil && len(bytes) == 32 { 80 base64Value := base64.StdEncoding.EncodeToString(bytes) 81 jsonArg = []byte(fmt.Sprintf("\"%v\"", base64Value)) 82 isBytes = true 83 } 84 } 85 86 if !isBytes { 87 jsonArg, err = protoArg.MarshalJSON() 88 if err != nil { 89 return Spec{}, errors.Join(ErrInvalidCallArgs, err) 90 } 91 } 92 jsonArgs = append(jsonArgs, string(jsonArg)) 93 } 94 95 normalisers := map[string]string{} 96 for _, v := range proto.Normalisers { 97 normalisers[v.Name] = v.Expression 98 } 99 100 return Spec{ 101 Address: proto.Address, 102 AbiJson: abiBytes, 103 Method: proto.Method, 104 ArgsJson: jsonArgs, 105 Trigger: trigger, 106 RequiredConfirmations: proto.RequiredConfirmations, 107 Filters: filters, 108 Normalisers: normalisers, 109 SourceChainID: proto.SourceChainId, 110 }, nil 111 } 112 113 func (s Spec) IntoProto() (*vegapb.EthCallSpec, error) { 114 argsPBValue := []*structpb.Value{} 115 for _, arg := range s.ArgsJson { 116 v := structpb.Value{} 117 err := v.UnmarshalJSON([]byte(arg)) 118 if err != nil { 119 return nil, fmt.Errorf("failed to unmarshal arg json '%s': %w", arg, err) 120 } 121 argsPBValue = append(argsPBValue, &v) 122 } 123 124 normalisers := []*vegapb.Normaliser{} 125 for k, v := range s.Normalisers { 126 n := vegapb.Normaliser{ 127 Name: k, 128 Expression: v, 129 } 130 normalisers = append(normalisers, &n) 131 } 132 133 sort.Slice(normalisers, func(i, j int) bool { return normalisers[i].Name < normalisers[j].Name }) 134 135 return &vegapb.EthCallSpec{ 136 Address: s.Address, 137 Abi: string(s.AbiJson), 138 Method: s.Method, 139 Args: argsPBValue, 140 Trigger: s.Trigger.IntoTriggerProto(), 141 RequiredConfirmations: s.RequiredConfirmations, 142 Filters: s.Filters.IntoProto(), 143 Normalisers: normalisers, 144 SourceChainId: s.SourceChainID, 145 }, nil 146 } 147 148 func (s Spec) ToDefinitionProto() (*vegapb.DataSourceDefinition, error) { 149 eth, err := s.IntoProto() 150 if err != nil { 151 return nil, fmt.Errorf("failed to convert to eth oracle proto: %w", err) 152 } 153 154 return &vegapb.DataSourceDefinition{ 155 SourceType: &vegapb.DataSourceDefinition_External{ 156 External: &vegapb.DataSourceDefinitionExternal{ 157 SourceType: &vegapb.DataSourceDefinitionExternal_EthOracle{ 158 EthOracle: eth, 159 }, 160 }, 161 }, 162 }, nil 163 } 164 165 func (s Spec) GetFilters() []*common.SpecFilter { 166 return s.Filters 167 } 168 169 func (s Spec) String() string { 170 return fmt.Sprintf("ethcallspec(%v, %v, %v, %v, %v, %v, %v)", 171 s.Address, s.AbiJson, s.Method, s.ArgsJson, s.Trigger, s.RequiredConfirmations, s.Filters) 172 } 173 174 // Whats the need for this deep clone? 175 func (s Spec) DeepClone() common.DataSourceType { 176 clonedNormalisers := make(map[string]string) 177 for key, value := range s.Normalisers { 178 clonedNormalisers[key] = value 179 } 180 181 return Spec{ 182 Address: s.Address, 183 AbiJson: s.AbiJson, 184 Method: s.Method, 185 ArgsJson: append([]string(nil), s.ArgsJson...), 186 Trigger: s.Trigger, 187 RequiredConfirmations: s.RequiredConfirmations, 188 Filters: append(common.SpecFilters(nil), s.Filters...), 189 Normalisers: clonedNormalisers, 190 SourceChainID: s.SourceChainID, 191 } 192 } 193 194 func (s Spec) IsZero() bool { 195 return s.Address == "" && 196 s.Method == "" && 197 s.Trigger == nil && 198 s.RequiredConfirmations == 0 && 199 len(s.AbiJson) == 0 && 200 len(s.ArgsJson) == 0 && 201 len(s.Filters) == 0 && 202 len(s.Normalisers) == 0 203 }