fix: Eth API: accept input data in call arguments under field 'input' (#11505)

The correct name for this field is 'input' according to the Ethereum specs [0].
However, for the longest time, clients have been using 'data' and servers have been
lenient to accept both, preferring 'input' over 'data' when both appear.

Our lack of support for 'input' had gone unnoticed until go-ethereum decided
to adjust their ethclient implementation to issue eth_call and eth_estimateGas
requests with the 'input' field instead of 'data' [1]. This suddenly broke apps
using this client against Lotus' Eth API.

[0]: https://github.com/ethereum/execution-apis/blob/main/src/schemas/transaction.yaml#L33-L35
[1]: ethereum/go-ethereum#28078

Co-authored-by: raulk <raul.kripalani@gmail.com>
This commit is contained in:
jonathan@vulcanize.io 2024-04-04 16:35:48 +00:00
parent 1735a6f308
commit e2712a9157
2 changed files with 45 additions and 4 deletions

View File

@ -229,13 +229,25 @@ type EthCall struct {
} }
func (c *EthCall) UnmarshalJSON(b []byte) error { func (c *EthCall) UnmarshalJSON(b []byte) error {
type TempEthCall EthCall type EthCallRaw EthCall // Avoid a recursive call.
var params TempEthCall type EthCallDecode struct {
// The field should be "input" by spec, but many clients use "data" so we support
// both, but prefer "input".
Input *EthBytes `json:"input"`
EthCallRaw
}
var params EthCallDecode
if err := json.Unmarshal(b, &params); err != nil { if err := json.Unmarshal(b, &params); err != nil {
return err return err
} }
*c = EthCall(params)
// If input is specified, prefer it.
if params.Input != nil {
params.Data = *params.Input
}
*c = EthCall(params.EthCallRaw)
return nil return nil
} }

View File

@ -194,11 +194,40 @@ func TestMaskedIDInF4(t *testing.T) {
} }
func TestUnmarshalEthCall(t *testing.T) { func TestUnmarshalEthCall(t *testing.T) {
data := `{"from":"0x4D6D86b31a112a05A473c4aE84afaF873f632325","to":"0xFe01CC39f5Ae8553D6914DBb9dC27D219fa22D7f","gas":"0x5","gasPrice":"0x6","value":"0x123","data":""}` data := `{"from":"0x4D6D86b31a112a05A473c4aE84afaF873f632325","to":"0xFe01CC39f5Ae8553D6914DBb9dC27D219fa22D7f","gas":"0x5","gasPrice":"0x6","value":"0x123","data":"0xFF"}`
var c EthCall var c EthCall
err := c.UnmarshalJSON([]byte(data)) err := c.UnmarshalJSON([]byte(data))
require.Nil(t, err) require.Nil(t, err)
require.EqualValues(t, []byte{0xff}, c.Data)
}
func TestUnmarshalEthCallInput(t *testing.T) {
data := `{"from":"0x4D6D86b31a112a05A473c4aE84afaF873f632325","to":"0xFe01CC39f5Ae8553D6914DBb9dC27D219fa22D7f","gas":"0x5","gasPrice":"0x6","value":"0x123","input":"0xFF"}`
var c EthCall
err := c.UnmarshalJSON([]byte(data))
require.Nil(t, err)
require.EqualValues(t, []byte{0xff}, c.Data)
}
func TestUnmarshalEthCallInputAndData(t *testing.T) {
data := `{"from":"0x4D6D86b31a112a05A473c4aE84afaF873f632325","to":"0xFe01CC39f5Ae8553D6914DBb9dC27D219fa22D7f","gas":"0x5","gasPrice":"0x6","value":"0x123","data":"0xFE","input":"0xFF"}`
var c EthCall
err := c.UnmarshalJSON([]byte(data))
require.Nil(t, err)
require.EqualValues(t, []byte{0xff}, c.Data)
}
func TestUnmarshalEthCallInputAndDataEmpty(t *testing.T) {
// Even if the input is empty, it should be used when specified.
data := `{"from":"0x4D6D86b31a112a05A473c4aE84afaF873f632325","to":"0xFe01CC39f5Ae8553D6914DBb9dC27D219fa22D7f","gas":"0x5","gasPrice":"0x6","value":"0x123","data":"0xFE","input":""}`
var c EthCall
err := c.UnmarshalJSON([]byte(data))
require.Nil(t, err)
require.EqualValues(t, []byte{}, c.Data)
} }
func TestUnmarshalEthBytes(t *testing.T) { func TestUnmarshalEthBytes(t *testing.T) {