Skip to content

Commit

Permalink
Add eth_call override (#1337)
Browse files Browse the repository at this point in the history
* Add eth_call override

* Pass lint

* Apply feedback

---------

Co-authored-by: Victor Castell <victor@victorcastell.com>
  • Loading branch information
ferranbt and Victor Castell authored Apr 11, 2023
1 parent f6cc66a commit 36772f4
Show file tree
Hide file tree
Showing 16 changed files with 287 additions and 10 deletions.
6 changes: 6 additions & 0 deletions consensus/polybft/contractsapi/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ var (
testContracts embed.FS
TestWriteBlockMetadata *artifact.Artifact
RootERC20 *artifact.Artifact
TestSimple *artifact.Artifact
RootERC721 *artifact.Artifact
RootERC1155 *artifact.Artifact
)
Expand Down Expand Up @@ -172,6 +173,11 @@ func init() {
if err != nil {
log.Fatal(err)
}

TestSimple, err = artifact.DecodeArtifact(readTestContractContent("TestSimple.json"))
if err != nil {
log.Fatal(err)
}
}

func readTestContractContent(contractFileName string) []byte {
Expand Down
24 changes: 24 additions & 0 deletions consensus/polybft/contractsapi/test-contracts/TestSimple.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"_format": "hh-sol-artifact-1",
"contractName": "TestSimple",
"sourceName": "contracts/TestSimple.sol",
"abi": [
{
"inputs": [],
"name": "getValue",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
],
"bytecode": "0x608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80632096525514602d575b600080fd5b60336047565b604051603e91906067565b60405180910390f35b60008054905090565b6000819050919050565b6061816050565b82525050565b6000602082019050607a6000830184605a565b9291505056fea26469706673582212203adde2f97bef70ed52dd170d2965805f1ed0cd3eaa229edc27180041091175ac64736f6c63430008110033",
"deployedBytecode": "0x6080604052348015600f57600080fd5b506004361060285760003560e01c80632096525514602d575b600080fd5b60336047565b604051603e91906067565b60405180910390f35b60008054905090565b6000819050919050565b6061816050565b82525050565b6000602082019050607a6000830184605a565b9291505056fea26469706673582212203adde2f97bef70ed52dd170d2965805f1ed0cd3eaa229edc27180041091175ac64736f6c63430008110033",
"linkReferences": {},
"deployedLinkReferences": {}
}
10 changes: 10 additions & 0 deletions consensus/polybft/contractsapi/test-contracts/TestSimple.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

contract TestSimple {
uint256 val;

function getValue() public view returns (uint256) {
return val;
}
}
55 changes: 55 additions & 0 deletions e2e-polybft/e2e/jsonrpc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package e2e

import (
"testing"

"github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi"
"github.com/0xPolygon/polygon-edge/e2e-polybft/framework"
"github.com/0xPolygon/polygon-edge/types"
"github.com/stretchr/testify/require"
"github.com/umbracle/ethgo"
"github.com/umbracle/ethgo/abi"
"github.com/umbracle/ethgo/wallet"
)

func TestE2E_JsonRPC(t *testing.T) {
acct, _ := wallet.GenerateKey()

cluster := framework.NewTestCluster(t, 3,
framework.WithPremine(types.Address(acct.Address())),
)
defer cluster.Stop()

cluster.WaitForReady(t)

client := cluster.Servers[0].JSONRPC().Eth()

// Test eth_call with override in state diff
t.Run("eth_call state override", func(t *testing.T) {
deployTxn := cluster.Deploy(t, acct, contractsapi.TestSimple.Bytecode)
require.NoError(t, deployTxn.Wait())
require.True(t, deployTxn.Succeed())

target := deployTxn.Receipt().ContractAddress

input := abi.MustNewMethod("function getValue() public returns (uint256)").ID()

resp, err := client.Call(&ethgo.CallMsg{To: &target, Data: input}, ethgo.Latest)
require.NoError(t, err)
require.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000000", resp)

override := &ethgo.StateOverride{
target: ethgo.OverrideAccount{
StateDiff: &map[ethgo.Hash]ethgo.Hash{
// storage slot 0 stores the 'val' uint256 value
{0x0}: {0x3},
},
},
}

resp, err = client.Call(&ethgo.CallMsg{To: &target, Data: input}, ethgo.Latest, override)
require.NoError(t, err)

require.Equal(t, "0x0300000000000000000000000000000000000000000000000000000000000000", resp)
})
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ require (
github.com/klauspost/compress v1.15.5 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mitchellh/mapstructure v1.5.0
github.com/umbracle/ethgo v0.1.4-0.20230126112511-6a4d02533af6
github.com/umbracle/ethgo v0.1.4-0.20230326234627-15b1df435098
github.com/valyala/fastjson v1.6.3 // indirect
go.uber.org/zap v1.22.0 // indirect
golang.org/x/sys v0.4.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,8 @@ github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2n
github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
github.com/umbracle/ethgo v0.1.4-0.20230126112511-6a4d02533af6 h1:WqlyYNdrBECgDwDIEMxa4mLUSH/FfPdAuOnniUqNpJs=
github.com/umbracle/ethgo v0.1.4-0.20230126112511-6a4d02533af6/go.mod h1:8QIHEG/YfGnW4I5AND2Znl9W0LU3tXR9IGqgmSieiGo=
github.com/umbracle/ethgo v0.1.4-0.20230326234627-15b1df435098 h1:OXYAHR6AKpV2A/BjQNECXod9rR8r5XOT2QCakD2r0YQ=
github.com/umbracle/ethgo v0.1.4-0.20230326234627-15b1df435098/go.mod h1:bjxSp984qsxCStKoKjqz5fgugi4uWj6+/tFkSEpjk3A=
github.com/umbracle/fastrlp v0.0.0-20220527094140-59d5dd30e722 h1:10Nbw6cACsnQm7r34zlpJky+IzxVLRk6MKTS2d3Vp0E=
github.com/umbracle/fastrlp v0.0.0-20220527094140-59d5dd30e722/go.mod h1:c8J0h9aULj2i3umrfyestM6jCq0LK0U6ly6bWy96nd4=
github.com/umbracle/go-eth-bn256 v0.0.0-20230125114011-47cb310d9b0b h1:5/xofhZiOG0I9DQXqDSPxqYObk6QI7mBGMJI+ngyIgc=
Expand Down
6 changes: 3 additions & 3 deletions jsonrpc/eth_blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ func TestEth_Call(t *testing.T) {
Nonce: argUintPtr(0),
}

res, err := eth.Call(contractCall, BlockNumberOrHash{})
res, err := eth.Call(contractCall, BlockNumberOrHash{}, nil)

assert.Error(t, err)
assert.Contains(t, err.Error(), store.ethCallError.Error())
Expand All @@ -328,7 +328,7 @@ func TestEth_Call(t *testing.T) {
Nonce: argUintPtr(0),
}

res, err := eth.Call(contractCall, BlockNumberOrHash{})
res, err := eth.Call(contractCall, BlockNumberOrHash{}, nil)

assert.NoError(t, err)
assert.NotNil(t, res)
Expand Down Expand Up @@ -517,7 +517,7 @@ func (m *mockBlockStore) GetAvgGasPrice() *big.Int {
return big.NewInt(m.averageGasPrice)
}

func (m *mockBlockStore) ApplyTxn(header *types.Header, txn *types.Transaction) (*runtime.ExecutionResult, error) {
func (m *mockBlockStore) ApplyTxn(header *types.Header, txn *types.Transaction, overrides types.StateOverride) (*runtime.ExecutionResult, error) {
return &runtime.ExecutionResult{Err: m.ethCallError}, nil
}

Expand Down
53 changes: 49 additions & 4 deletions jsonrpc/eth_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ type ethBlockchainStore interface {
GetAvgGasPrice() *big.Int

// ApplyTxn applies a transaction object to the blockchain
ApplyTxn(header *types.Header, txn *types.Transaction) (*runtime.ExecutionResult, error)
ApplyTxn(header *types.Header, txn *types.Transaction, override types.StateOverride) (*runtime.ExecutionResult, error)

// GetSyncProgression retrieves the current sync progression, if any
GetSyncProgression() *progress.Progression
Expand Down Expand Up @@ -388,8 +388,45 @@ func (e *Eth) GasPrice() (interface{}, error) {
return argUint64(common.Max(e.priceLimit, avgGasPrice)), nil
}

type overrideAccount struct {
Nonce *argUint64 `json:"nonce"`
Code *argBytes `json:"code"`
Balance *argUint64 `json:"balance"`
State *map[types.Hash]types.Hash `json:"state"`
StateDiff *map[types.Hash]types.Hash `json:"stateDiff"`
}

func (o *overrideAccount) ToType() types.OverrideAccount {
res := types.OverrideAccount{}

if o.Nonce != nil {
res.Nonce = (*uint64)(o.Nonce)
}

if o.Code != nil {
res.Code = *o.Code
}

if o.Balance != nil {
res.Balance = new(big.Int).SetUint64(*(*uint64)(o.Balance))
}

if o.State != nil {
res.State = *o.State
}

if o.StateDiff != nil {
res.StateDiff = *o.StateDiff
}

return res
}

// StateOverride is the collection of overridden accounts.
type stateOverride map[types.Address]overrideAccount

// Call executes a smart contract call using the transaction object data
func (e *Eth) Call(arg *txnArgs, filter BlockNumberOrHash) (interface{}, error) {
func (e *Eth) Call(arg *txnArgs, filter BlockNumberOrHash, apiOverride *stateOverride) (interface{}, error) {
header, err := GetHeaderFromBlockNumberOrHash(filter, e.store)
if err != nil {
return nil, err
Expand All @@ -404,8 +441,16 @@ func (e *Eth) Call(arg *txnArgs, filter BlockNumberOrHash) (interface{}, error)
transaction.Gas = header.GasLimit
}

var override types.StateOverride
if apiOverride != nil {
override = types.StateOverride{}
for addr, o := range *apiOverride {
override[addr] = o.ToType()
}
}

// The return value of the execution is saved in the transition (returnValue field)
result, err := e.store.ApplyTxn(header, transaction)
result, err := e.store.ApplyTxn(header, transaction, override)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -540,7 +585,7 @@ func (e *Eth) EstimateGas(arg *txnArgs, rawNum *BlockNumber) (interface{}, error
txn := transaction.Copy()
txn.Gas = gas

result, applyErr := e.store.ApplyTxn(header, txn)
result, applyErr := e.store.ApplyTxn(header, txn, nil)

if applyErr != nil {
// Check the application error.
Expand Down
2 changes: 1 addition & 1 deletion jsonrpc/eth_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -840,7 +840,7 @@ func (m *mockSpecialStore) GetForksInTime(blockNumber uint64) chain.ForksInTime
return chain.ForksInTime{}
}

func (m *mockSpecialStore) ApplyTxn(header *types.Header, txn *types.Transaction) (*runtime.ExecutionResult, error) {
func (m *mockSpecialStore) ApplyTxn(header *types.Header, txn *types.Transaction, overrides types.StateOverride) (*runtime.ExecutionResult, error) {
if m.applyTxnHook != nil {
return m.applyTxnHook(header, txn)
}
Expand Down
7 changes: 7 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,7 @@ func (j *jsonRPCHub) GetCode(root types.Hash, addr types.Address) ([]byte, error
func (j *jsonRPCHub) ApplyTxn(
header *types.Header,
txn *types.Transaction,
override types.StateOverride,
) (result *runtime.ExecutionResult, err error) {
blockCreator, err := j.GetConsensus().GetBlockCreator(header)
if err != nil {
Expand All @@ -688,6 +689,12 @@ func (j *jsonRPCHub) ApplyTxn(
return
}

if override != nil {
if err = transition.WithStateOverride(override); err != nil {
return
}
}

result, err = transition.Apply(txn)

return
Expand Down
30 changes: 30 additions & 0 deletions state/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,36 @@ func NewTransition(config chain.ForksInTime, snap Snapshot, radix *Txn) *Transit
}
}

func (t *Transition) WithStateOverride(override types.StateOverride) error {
for addr, o := range override {
if o.State != nil && o.StateDiff != nil {
return fmt.Errorf("cannot override both state and state diff")
}

if o.Nonce != nil {
t.state.SetNonce(addr, *o.Nonce)
}

if o.Balance != nil {
t.state.SetBalance(addr, o.Balance)
}

if o.Code != nil {
t.state.SetCode(addr, o.Code)
}

if o.State != nil {
t.state.SetFullStorage(addr, o.State)
}

for k, v := range o.StateDiff {
t.state.SetState(addr, k, v)
}
}

return nil
}

func (t *Transition) TotalGas() uint64 {
return t.totalGas
}
Expand Down
62 changes: 62 additions & 0 deletions state/executor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package state

import (
"math/big"
"testing"

"github.com/0xPolygon/polygon-edge/chain"
"github.com/0xPolygon/polygon-edge/types"
"github.com/stretchr/testify/require"
)

func TestOverride(t *testing.T) {
t.Parallel()
state := newStateWithPreState(map[types.Address]*PreState{
{0x0}: {
Nonce: 1,
Balance: 1,
State: map[types.Hash]types.Hash{
types.ZeroHash: {0x1},
},
},
{0x1}: {
State: map[types.Hash]types.Hash{
types.ZeroHash: {0x1},
},
},
})

nonce := uint64(2)
balance := big.NewInt(2)
code := []byte{0x1}

tt := NewTransition(chain.ForksInTime{}, state, newTxn(state))

require.Empty(t, tt.state.GetCode(types.ZeroAddress))

err := tt.WithStateOverride(types.StateOverride{
{0x0}: types.OverrideAccount{
Nonce: &nonce,
Balance: balance,
Code: code,
StateDiff: map[types.Hash]types.Hash{
types.ZeroHash: {0x2},
},
},
{0x1}: types.OverrideAccount{
State: map[types.Hash]types.Hash{
{0x1}: {0x1},
},
},
})
require.NoError(t, err)

require.Equal(t, nonce, tt.state.GetNonce(types.ZeroAddress))
require.Equal(t, balance, tt.state.GetBalance(types.ZeroAddress))
require.Equal(t, code, tt.state.GetCode(types.ZeroAddress))
require.Equal(t, types.Hash{0x2}, tt.state.GetState(types.ZeroAddress, types.ZeroHash))

// state is fully replaced
require.Equal(t, types.Hash{0x0}, tt.state.GetState(types.Address{0x1}, types.ZeroHash))
require.Equal(t, types.Hash{0x1}, tt.state.GetState(types.Address{0x1}, types.Hash{0x1}))
}
5 changes: 5 additions & 0 deletions state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ type StateObject struct {
Deleted bool
DirtyCode bool
Txn *iradix.Txn

// withFakeStorage signals whether the state object
// is using the override full state
withFakeStorage bool
}

func (s *StateObject) Empty() bool {
Expand All @@ -129,6 +133,7 @@ func (s *StateObject) Copy() *StateObject {
ss.Deleted = s.Deleted
ss.DirtyCode = s.DirtyCode
ss.Code = s.Code
ss.withFakeStorage = s.withFakeStorage

if s.Txn != nil {
ss.Txn = s.Txn.CommitOnly().Txn()
Expand Down
Loading

0 comments on commit 36772f4

Please sign in to comment.