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  }