From 7f8d4294db89318e4c902091cd0aa64656b399e1 Mon Sep 17 00:00:00 2001 From: Peter Kieltyka Date: Thu, 19 Dec 2024 09:36:05 -0500 Subject: [PATCH] ethcoder: eip712 additional test cases (#154) --- ethcoder/abi_helpers.go | 8 +- ethcoder/abi_test.go | 28 ++++++ ethcoder/typed_data.go | 43 ++++++++-- ethcoder/typed_data_test.go | 164 +++++++++++++++++++++++++++++++++++- 4 files changed, 234 insertions(+), 9 deletions(-) diff --git a/ethcoder/abi_helpers.go b/ethcoder/abi_helpers.go index 81e737b..0fdd691 100644 --- a/ethcoder/abi_helpers.go +++ b/ethcoder/abi_helpers.go @@ -414,8 +414,14 @@ func ABIUnmarshalStringValues(argTypes []string, stringValues []string) ([]any, return nil, fmt.Errorf("ethcoder: value at position %d is invalid. invalid number type '%s'", i, typ) } + base := 10 + if strings.HasPrefix(s, "0x") { + base = 16 + s = s[2:] + } + num := big.NewInt(0) - num, ok := num.SetString(s, 10) + num, ok := num.SetString(s, base) if !ok { return nil, fmt.Errorf("ethcoder: value at position %d is invalid. expecting number. unable to set value of '%s'", i, s) } diff --git a/ethcoder/abi_test.go b/ethcoder/abi_test.go index c1ac2a2..edb492a 100644 --- a/ethcoder/abi_test.go +++ b/ethcoder/abi_test.go @@ -226,6 +226,20 @@ func TestABIUnmarshalStringValuesAny(t *testing.T) { assert.Equal(t, int64(2), v2.Int64()) } + { + values, err := ABIUnmarshalStringValuesAny([]string{"address", "uint256"}, []any{"0x6615e4e985bf0d137196897dfa182dbd7127f54f", "0x123456"}) + assert.NoError(t, err) + assert.Len(t, values, 2) + + v1, ok := values[0].(common.Address) + assert.True(t, ok) + assert.Equal(t, "0x6615e4e985BF0D137196897Dfa182dBD7127f54f", v1.String()) + + v2, ok := values[1].(*big.Int) + assert.True(t, ok) + assert.Equal(t, int64(1193046), v2.Int64()) + } + { values, err := ABIUnmarshalStringValuesAny([]string{"address", "bytes8"}, []any{"0x6615e4e985bf0d137196897dfa182dbd7127f54f", "0xaabbccddaabbccdd"}) assert.NoError(t, err) @@ -360,6 +374,20 @@ func TestABIUnmarshalStringValues(t *testing.T) { assert.Equal(t, int64(2), v2.Int64()) } + { + values, err := ABIUnmarshalStringValues([]string{"address", "uint256"}, []string{"0x6615e4e985bf0d137196897dfa182dbd7127f54f", "0x123456"}) + assert.NoError(t, err) + assert.Len(t, values, 2) + + v1, ok := values[0].(common.Address) + assert.True(t, ok) + assert.Equal(t, "0x6615e4e985BF0D137196897Dfa182dBD7127f54f", v1.String()) + + v2, ok := values[1].(*big.Int) + assert.True(t, ok) + assert.Equal(t, int64(1193046), v2.Int64()) + } + { values, err := ABIUnmarshalStringValues([]string{"address", "bytes8"}, []string{"0x6615e4e985bf0d137196897dfa182dbd7127f54f", "0xaabbccddaabbccdd"}) assert.NoError(t, err) diff --git a/ethcoder/typed_data.go b/ethcoder/typed_data.go index 9551b55..f57c014 100644 --- a/ethcoder/typed_data.go +++ b/ethcoder/typed_data.go @@ -282,10 +282,16 @@ func TypedDataFromJSON(typedDataJSON string) (*TypedData, error) { func (t *TypedData) UnmarshalJSON(data []byte) error { // Intermediary structure to decode message field type TypedDataRaw struct { - Types TypedDataTypes `json:"types"` - PrimaryType string `json:"primaryType"` - Domain TypedDataDomain `json:"domain"` - Message map[string]interface{} `json:"message"` + Types TypedDataTypes `json:"types"` + PrimaryType string `json:"primaryType"` + Domain struct { + Name string `json:"name,omitempty"` + Version string `json:"version,omitempty"` + ChainID interface{} `json:"chainId,omitempty"` + VerifyingContract *common.Address `json:"verifyingContract,omitempty"` + Salt *common.Hash `json:"salt,omitempty"` + } `json:"domain"` + Message map[string]interface{} `json:"message"` } // Json decoder with json.Number support, so that we can decode big.Int values @@ -333,6 +339,31 @@ func (t *TypedData) UnmarshalJSON(data []byte) error { return fmt.Errorf("primary type '%s' is not defined", raw.PrimaryType) } + // Decode the domain, which is mostly decooded except the chainId is an interface{} type + // because the value may be a number of a hex encoded number. We want it in a big.Int. + domain := TypedDataDomain{ + Name: raw.Domain.Name, + Version: raw.Domain.Version, + ChainID: nil, + VerifyingContract: raw.Domain.VerifyingContract, + Salt: raw.Domain.Salt, + } + if raw.Domain.ChainID != nil { + chainID := big.NewInt(0) + if val, ok := raw.Domain.ChainID.(float64); ok { + chainID.SetInt64(int64(val)) + } else if val, ok := raw.Domain.ChainID.(json.Number); ok { + chainID.SetString(val.String(), 10) + } else if val, ok := raw.Domain.ChainID.(string); ok { + if strings.HasPrefix(val, "0x") { + chainID.SetString(val[2:], 16) + } else { + chainID.SetString(val, 10) + } + } + domain.ChainID = chainID + } + // Decode the raw message into Go runtime types message, err := typedDataDecodeRawMessageMap(raw.Types.Map(), raw.PrimaryType, raw.Message) if err != nil { @@ -341,7 +372,7 @@ func (t *TypedData) UnmarshalJSON(data []byte) error { t.Types = raw.Types t.PrimaryType = raw.PrimaryType - t.Domain = raw.Domain + t.Domain = domain m, ok := message.(map[string]interface{}) if !ok { @@ -465,7 +496,7 @@ func typedDataDecodePrimitiveValue(typ string, value interface{}) (interface{}, val := fmt.Sprintf("%v", value) out, err := ABIUnmarshalStringValuesAny([]string{typ}, []any{val}) if err != nil { - return nil, err + return nil, fmt.Errorf("typedDataDecodePrimitiveValue: %w", err) } return out[0], nil } diff --git a/ethcoder/typed_data_test.go b/ethcoder/typed_data_test.go index 75c2a32..59dab73 100644 --- a/ethcoder/typed_data_test.go +++ b/ethcoder/typed_data_test.go @@ -417,7 +417,7 @@ func TestTypedDataFromJSONPart4(t *testing.T) { require.True(t, valid) } -func TypedDataFromJSONPart5(t *testing.T) { +func TestTypedDataFromJSONPart5(t *testing.T) { typedDataJson := `{ "types": { "EIP712Domain": [ @@ -443,7 +443,7 @@ func TypedDataFromJSONPart5(t *testing.T) { }, "message": { "message": "Test message", - "value": "0x634abebe1d4da48b00000000000000000cde63753dad4f0f42f79ebef71ee924, + "value": "0x634abebe1d4da48b00000000000000000cde63753dad4f0f42f79ebef71ee924", "from": "0xc0ffee254729296a45a3885639AC7E10F9d54979", "to": "0xc0ffee254729296a45a3885639AC7E10F9d54979" } @@ -454,3 +454,163 @@ func TypedDataFromJSONPart5(t *testing.T) { require.Equal(t, typedData.Domain.ChainID.Int64(), int64(15)) } + +func TestTypedDataFromJSONPart6(t *testing.T) { + typedDataJson := `{ + "domain": { + "name": "Seaport", + "version": "1.5", + "chainId": 80002, + "verifyingContract": "0x00000000000000adc04c56bf30ac9d3c0aaf14dc" + }, + "message": { + "conduitKey": "0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e000000000000000000000000", + "consideration": [ + { + "endAmount": "1", + "identifierOrCriteria": "1", + "itemType": 3, + "recipient": "0x033ccc543501e462a2d50b579845709ff21f2eb6", + "startAmount": "1", + "token": "0xb7d432df27ab7b2a1be636bd945e6cb63bc84feb" + } + ], + "counter": "0", + "endTime": 1735219168, + "offer": [ + { + "endAmount": "1", + "identifierOrCriteria": "0", + "itemType": 1, + "startAmount": "1", + "token": "0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582" + } + ], + "offerer": "0x033ccc543501e462a2d50b579845709ff21f2eb6", + "orderType": 1, + "salt": "0x634abebe1d4da48b0000000000000000f6dad44ce6d8c81dcbf213906d353f0c", + "startTime": 1734614365, + "zone": "0x0000000000000000000000000000000000000000", + "zoneHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "primaryType": "OrderComponents", + "types": { + "EIP712Domain": [ + { + "name": "name", + "type": "string" + }, + { + "name": "version", + "type": "string" + }, + { + "name": "chainId", + "type": "uint256" + }, + { + "name": "verifyingContract", + "type": "address" + } + ], + "ConsiderationItem": [ + { + "name": "itemType", + "type": "uint8" + }, + { + "name": "token", + "type": "address" + }, + { + "name": "identifierOrCriteria", + "type": "uint256" + }, + { + "name": "startAmount", + "type": "uint256" + }, + { + "name": "endAmount", + "type": "uint256" + }, + { + "name": "recipient", + "type": "address" + } + ], + "OfferItem": [ + { + "name": "itemType", + "type": "uint8" + }, + { + "name": "token", + "type": "address" + }, + { + "name": "identifierOrCriteria", + "type": "uint256" + }, + { + "name": "startAmount", + "type": "uint256" + }, + { + "name": "endAmount", + "type": "uint256" + } + ], + "OrderComponents": [ + { + "name": "offerer", + "type": "address" + }, + { + "name": "zone", + "type": "address" + }, + { + "name": "offer", + "type": "OfferItem[]" + }, + { + "name": "consideration", + "type": "ConsiderationItem[]" + }, + { + "name": "orderType", + "type": "uint8" + }, + { + "name": "startTime", + "type": "uint256" + }, + { + "name": "endTime", + "type": "uint256" + }, + { + "name": "zoneHash", + "type": "bytes32" + }, + { + "name": "salt", + "type": "uint256" + }, + { + "name": "conduitKey", + "type": "bytes32" + }, + { + "name": "counter", + "type": "uint256" + } + ] + } + }` + + typedData, err := ethcoder.TypedDataFromJSON(typedDataJson) + require.NoError(t, err) + require.NotNil(t, typedData) +}