Skip to content

Commit d7d371c

Browse files
authored
ethcoder: typed data marshaljson (#155)
1 parent 7f8d429 commit d7d371c

File tree

3 files changed

+456
-235
lines changed

3 files changed

+456
-235
lines changed

ethcoder/typed_data.go

Lines changed: 0 additions & 233 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ package ethcoder
22

33
import (
44
"bytes"
5-
"encoding/json"
65
"fmt"
76
"math/big"
8-
"slices"
97
"sort"
108
"strings"
119

@@ -269,234 +267,3 @@ func (t *TypedData) EncodeDigest() ([]byte, error) {
269267
}
270268
return digest, nil
271269
}
272-
273-
func TypedDataFromJSON(typedDataJSON string) (*TypedData, error) {
274-
var typedData TypedData
275-
err := json.Unmarshal([]byte(typedDataJSON), &typedData)
276-
if err != nil {
277-
return nil, err
278-
}
279-
return &typedData, nil
280-
}
281-
282-
func (t *TypedData) UnmarshalJSON(data []byte) error {
283-
// Intermediary structure to decode message field
284-
type TypedDataRaw struct {
285-
Types TypedDataTypes `json:"types"`
286-
PrimaryType string `json:"primaryType"`
287-
Domain struct {
288-
Name string `json:"name,omitempty"`
289-
Version string `json:"version,omitempty"`
290-
ChainID interface{} `json:"chainId,omitempty"`
291-
VerifyingContract *common.Address `json:"verifyingContract,omitempty"`
292-
Salt *common.Hash `json:"salt,omitempty"`
293-
} `json:"domain"`
294-
Message map[string]interface{} `json:"message"`
295-
}
296-
297-
// Json decoder with json.Number support, so that we can decode big.Int values
298-
dec := json.NewDecoder(bytes.NewReader(data))
299-
dec.UseNumber()
300-
301-
var raw TypedDataRaw
302-
if err := dec.Decode(&raw); err != nil {
303-
return err
304-
}
305-
306-
// Ensure the "EIP712Domain" type is defined. In case its not defined
307-
// we will add it to the types map
308-
_, ok := raw.Types["EIP712Domain"]
309-
if !ok {
310-
raw.Types["EIP712Domain"] = []TypedDataArgument{}
311-
if raw.Domain.Name != "" {
312-
raw.Types["EIP712Domain"] = append(raw.Types["EIP712Domain"], TypedDataArgument{Name: "name", Type: "string"})
313-
}
314-
if raw.Domain.Version != "" {
315-
raw.Types["EIP712Domain"] = append(raw.Types["EIP712Domain"], TypedDataArgument{Name: "version", Type: "string"})
316-
}
317-
if raw.Domain.ChainID != nil {
318-
raw.Types["EIP712Domain"] = append(raw.Types["EIP712Domain"], TypedDataArgument{Name: "chainId", Type: "uint256"})
319-
}
320-
if raw.Domain.VerifyingContract != nil {
321-
raw.Types["EIP712Domain"] = append(raw.Types["EIP712Domain"], TypedDataArgument{Name: "verifyingContract", Type: "address"})
322-
}
323-
if raw.Domain.Salt != nil {
324-
raw.Types["EIP712Domain"] = append(raw.Types["EIP712Domain"], TypedDataArgument{Name: "salt", Type: "bytes32"})
325-
}
326-
}
327-
328-
// Ensure primary type is defined
329-
if raw.PrimaryType == "" {
330-
// detect primary type if its unspecified
331-
primaryType, err := typedDataDetectPrimaryType(raw.Types.Map(), raw.Message)
332-
if err != nil {
333-
return err
334-
}
335-
raw.PrimaryType = primaryType
336-
}
337-
_, ok = raw.Types[raw.PrimaryType]
338-
if !ok {
339-
return fmt.Errorf("primary type '%s' is not defined", raw.PrimaryType)
340-
}
341-
342-
// Decode the domain, which is mostly decooded except the chainId is an interface{} type
343-
// because the value may be a number of a hex encoded number. We want it in a big.Int.
344-
domain := TypedDataDomain{
345-
Name: raw.Domain.Name,
346-
Version: raw.Domain.Version,
347-
ChainID: nil,
348-
VerifyingContract: raw.Domain.VerifyingContract,
349-
Salt: raw.Domain.Salt,
350-
}
351-
if raw.Domain.ChainID != nil {
352-
chainID := big.NewInt(0)
353-
if val, ok := raw.Domain.ChainID.(float64); ok {
354-
chainID.SetInt64(int64(val))
355-
} else if val, ok := raw.Domain.ChainID.(json.Number); ok {
356-
chainID.SetString(val.String(), 10)
357-
} else if val, ok := raw.Domain.ChainID.(string); ok {
358-
if strings.HasPrefix(val, "0x") {
359-
chainID.SetString(val[2:], 16)
360-
} else {
361-
chainID.SetString(val, 10)
362-
}
363-
}
364-
domain.ChainID = chainID
365-
}
366-
367-
// Decode the raw message into Go runtime types
368-
message, err := typedDataDecodeRawMessageMap(raw.Types.Map(), raw.PrimaryType, raw.Message)
369-
if err != nil {
370-
return err
371-
}
372-
373-
t.Types = raw.Types
374-
t.PrimaryType = raw.PrimaryType
375-
t.Domain = domain
376-
377-
m, ok := message.(map[string]interface{})
378-
if !ok {
379-
return fmt.Errorf("resulting message is not a map")
380-
}
381-
t.Message = m
382-
383-
return nil
384-
}
385-
386-
func typedDataDetectPrimaryType(typesMap map[string]map[string]string, message map[string]interface{}) (string, error) {
387-
// If there are only two types, and one is the EIP712Domain, then the other is the primary type
388-
if len(typesMap) == 2 {
389-
_, ok := typesMap["EIP712Domain"]
390-
if ok {
391-
for typ := range typesMap {
392-
if typ == "EIP712Domain" {
393-
continue
394-
}
395-
return typ, nil
396-
}
397-
}
398-
}
399-
400-
// Otherwise search for the primary type by looking for the first type that has a message field keys
401-
messageKeys := []string{}
402-
for k := range message {
403-
messageKeys = append(messageKeys, k)
404-
}
405-
sort.Strings(messageKeys)
406-
407-
for typ := range typesMap {
408-
if typ == "EIP712Domain" {
409-
continue
410-
}
411-
if len(typesMap[typ]) != len(messageKeys) {
412-
continue
413-
}
414-
415-
typKeys := []string{}
416-
for k := range typesMap[typ] {
417-
typKeys = append(typKeys, k)
418-
}
419-
sort.Strings(typKeys)
420-
421-
if !slices.Equal(messageKeys, typKeys) {
422-
continue
423-
}
424-
return typ, nil
425-
}
426-
427-
return "", fmt.Errorf("no primary type found")
428-
}
429-
430-
func typedDataDecodeRawMessageMap(typesMap map[string]map[string]string, primaryType string, data interface{}) (interface{}, error) {
431-
// Handle array types
432-
if arr, ok := data.([]interface{}); ok {
433-
results := make([]interface{}, len(arr))
434-
for i, item := range arr {
435-
decoded, err := typedDataDecodeRawMessageMap(typesMap, primaryType, item)
436-
if err != nil {
437-
return nil, err
438-
}
439-
results[i] = decoded
440-
}
441-
return results, nil
442-
}
443-
444-
// Handle primitive directly
445-
message, ok := data.(map[string]interface{})
446-
if !ok {
447-
return typedDataDecodePrimitiveValue(primaryType, data)
448-
}
449-
450-
currentType, ok := typesMap[primaryType]
451-
if !ok {
452-
return nil, fmt.Errorf("type %s is not defined", primaryType)
453-
}
454-
455-
processedMessage := make(map[string]interface{})
456-
for k, v := range message {
457-
typ, ok := currentType[k]
458-
if !ok {
459-
return nil, fmt.Errorf("message field '%s' is missing type definition on '%s'", k, primaryType)
460-
}
461-
462-
// Extract base type and check if it's an array
463-
baseType := typ
464-
isArray := false
465-
if idx := strings.Index(typ, "["); idx != -1 {
466-
baseType = typ[:idx]
467-
isArray = true
468-
}
469-
470-
// Process value based on whether it's a custom or primitive type
471-
if _, isCustomType := typesMap[baseType]; isCustomType {
472-
decoded, err := typedDataDecodeRawMessageMap(typesMap, baseType, v)
473-
if err != nil {
474-
return nil, err
475-
}
476-
processedMessage[k] = decoded
477-
} else {
478-
var decoded interface{}
479-
var err error
480-
if isArray {
481-
decoded, err = typedDataDecodeRawMessageMap(typesMap, baseType, v)
482-
} else {
483-
decoded, err = typedDataDecodePrimitiveValue(baseType, v)
484-
}
485-
if err != nil {
486-
return nil, fmt.Errorf("failed to decode field '%s': %w", k, err)
487-
}
488-
processedMessage[k] = decoded
489-
}
490-
}
491-
492-
return processedMessage, nil
493-
}
494-
495-
func typedDataDecodePrimitiveValue(typ string, value interface{}) (interface{}, error) {
496-
val := fmt.Sprintf("%v", value)
497-
out, err := ABIUnmarshalStringValuesAny([]string{typ}, []any{val})
498-
if err != nil {
499-
return nil, fmt.Errorf("typedDataDecodePrimitiveValue: %w", err)
500-
}
501-
return out[0], nil
502-
}

0 commit comments

Comments
 (0)