diff --git a/CHANGELOG.md b/CHANGELOG.md index 1726ce4f..82576ec8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # 0.1.4 (Unreleased) +- feat: Add override to `eth_call` request [[GH-240](https://github.com/umbracle/ethgo/issues/240)] - fix: Recovery of typed transactions [[GH-238](https://github.com/umbracle/ethgo/issues/238)] - fix: Parse `nonce` and `mixHash` on `Block` [[GH-228](https://github.com/umbracle/ethgo/issues/228)] - feat: `abi` decodes function string in multilines [[GH-212](https://github.com/umbracle/ethgo/issues/212)] diff --git a/jsonrpc/eth.go b/jsonrpc/eth.go index cbdf2be9..af6bf7f8 100644 --- a/jsonrpc/eth.go +++ b/jsonrpc/eth.go @@ -171,9 +171,14 @@ func (e *Eth) GasPrice() (uint64, error) { } // Call executes a new message call immediately without creating a transaction on the block chain. -func (e *Eth) Call(msg *ethgo.CallMsg, block ethgo.BlockNumber) (string, error) { +func (e *Eth) Call(msg *ethgo.CallMsg, block ethgo.BlockNumber, override ...*ethgo.StateOverride) (string, error) { + var cleanOverride *ethgo.StateOverride + if len(override) == 1 && override[0] != nil { + cleanOverride = override[0] + } + var out string - if err := e.c.Call("eth_call", &out, msg, block.String()); err != nil { + if err := e.c.Call("eth_call", &out, msg, block.String(), cleanOverride); err != nil { return "", err } return out, nil diff --git a/jsonrpc/eth_test.go b/jsonrpc/eth_test.go index 4daa96ee..bf8f6737 100644 --- a/jsonrpc/eth_test.go +++ b/jsonrpc/eth_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/umbracle/ethgo" + "github.com/umbracle/ethgo/abi" "github.com/umbracle/ethgo/testutil" ) @@ -245,6 +246,54 @@ func TestEthChainID(t *testing.T) { }) } +func TestEthCall(t *testing.T) { + s := testutil.NewTestServer(t) + + c, _ := NewClient(s.HTTPAddr()) + cc := &testutil.Contract{} + + // add global variables + cc.AddCallback(func() string { + return "uint256 val = 1;" + }) + + // add setter method + cc.AddCallback(func() string { + return `function getValue() public returns (uint256) { + return val; + }` + }) + + _, addr, err := s.DeployContract(cc) + require.NoError(t, err) + + input := abi.MustNewMethod("function getValue() public returns (uint256)").ID() + + resp, err := c.Eth().Call(ðgo.CallMsg{To: &addr, Data: input}, ethgo.Latest) + require.NoError(t, err) + + require.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000001", resp) + + nonce := uint64(1) + + // override the state + override := ðgo.StateOverride{ + addr: ethgo.OverrideAccount{ + Nonce: &nonce, + Balance: big.NewInt(1), + StateDiff: &map[ethgo.Hash]ethgo.Hash{ + // storage slot 0 stores the 'val' uint256 value + {0x0}: {0x3}, + }, + }, + } + + resp, err = c.Eth().Call(ðgo.CallMsg{To: &addr, Data: input}, ethgo.Latest, override) + require.NoError(t, err) + + require.Equal(t, "0x0300000000000000000000000000000000000000000000000000000000000000", resp) +} + func TestEthGetNonce(t *testing.T) { s := testutil.NewTestServer(t) diff --git a/structs.go b/structs.go index c7dc5e77..f8a24c13 100644 --- a/structs.go +++ b/structs.go @@ -392,3 +392,13 @@ func completeHex(str string, num int) []byte { } return []byte("0x" + str) } + +type OverrideAccount struct { + Nonce *uint64 + Code *[]byte + Balance *big.Int + State *map[Hash]Hash + StateDiff *map[Hash]Hash +} + +type StateOverride map[Address]OverrideAccount diff --git a/structs_marshal.go b/structs_marshal.go index aa9c7228..e768b11a 100644 --- a/structs_marshal.go +++ b/structs_marshal.go @@ -254,3 +254,41 @@ func (l *LogFilter) MarshalJSON() ([]byte, error) { defaultArena.Put(a) return res, nil } + +func (s StateOverride) MarshalJSON() ([]byte, error) { + a := defaultArena.Get() + + o := a.NewObject() + for addr, obj := range s { + oo := a.NewObject() + if obj.Nonce != nil { + oo.Set("nonce", a.NewString(fmt.Sprintf("0x%x", *obj.Nonce))) + } + if obj.Balance != nil { + oo.Set("balance", a.NewString(fmt.Sprintf("0x%x", obj.Balance))) + } + if obj.Code != nil { + oo.Set("code", a.NewString("0x"+hex.EncodeToString(*obj.Code))) + } + if obj.State != nil { + ooo := a.NewObject() + for k, v := range *obj.State { + ooo.Set(k.String(), a.NewString(v.String())) + } + oo.Set("state", ooo) + } + if obj.StateDiff != nil { + ooo := a.NewObject() + for k, v := range *obj.StateDiff { + ooo.Set(k.String(), a.NewString(v.String())) + } + oo.Set("stateDiff", ooo) + } + o.Set(addr.String(), oo) + } + + res := o.MarshalTo(nil) + defaultArena.Put(a) + + return res, nil +} diff --git a/structs_marshal_test.go b/structs_marshal_test.go index 70c201b1..a6760c61 100644 --- a/structs_marshal_test.go +++ b/structs_marshal_test.go @@ -2,9 +2,11 @@ package ethgo import ( "encoding/json" + "math/big" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func generateHashPtr(input string) *Hash { @@ -94,3 +96,28 @@ func TestLogFilter_MarshalJSON(t *testing.T) { }) } } + +func TestMarshal_StateOverride(t *testing.T) { + nonce := uint64(1) + code := []byte{0x1} + + o := StateOverride{ + {0x0}: OverrideAccount{ + Nonce: &nonce, + Balance: big.NewInt(1), + Code: &code, + State: &map[Hash]Hash{ + {0x1}: {0x1}, + }, + StateDiff: &map[Hash]Hash{ + {0x1}: {0x1}, + }, + }, + } + + res, err := o.MarshalJSON() + require.NoError(t, err) + + expected := `{"0x0000000000000000000000000000000000000000":{"nonce":"0x1","balance":"0x1","code":"0x01","state":{"0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000"},"stateDiff":{"0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000"}}}` + require.Equal(t, expected, string(res)) +}