Skip to content

Commit

Permalink
Skip account balance check for eth_call (#1949)
Browse files Browse the repository at this point in the history
  • Loading branch information
begmaroman authored Oct 4, 2023
1 parent ad1cf7b commit b455131
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 19 deletions.
106 changes: 94 additions & 12 deletions e2e-polybft/e2e/jsonrpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import (
"math/big"
"testing"

"github.com/stretchr/testify/require"
"github.com/umbracle/ethgo"
"github.com/umbracle/ethgo/jsonrpc"
"github.com/umbracle/ethgo/wallet"

"github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi"
"github.com/0xPolygon/polygon-edge/e2e-polybft/framework"
"github.com/0xPolygon/polygon-edge/helper/hex"
"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"
)

var (
Expand All @@ -31,7 +32,8 @@ func TestE2E_JsonRPC(t *testing.T) {

cluster.WaitForReady(t)

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

// Test eth_call with override in state diff
t.Run("eth_call state override", func(t *testing.T) {
Expand All @@ -41,7 +43,7 @@ func TestE2E_JsonRPC(t *testing.T) {

target := deployTxn.Receipt().ContractAddress

input := abi.MustNewMethod("function getValue() public returns (uint256)").ID()
input := contractsapi.TestSimple.Abi.GetMethod("getValue").ID()

resp, err := client.Call(&ethgo.CallMsg{To: &target, Data: input}, ethgo.Latest)
require.NoError(t, err)
Expand All @@ -62,11 +64,72 @@ func TestE2E_JsonRPC(t *testing.T) {
require.Equal(t, "0x0300000000000000000000000000000000000000000000000000000000000000", resp)
})

// Test eth_call with zero account balance
t.Run("eth_call with zero-balance account", 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 := contractsapi.TestSimple.Abi.GetMethod("getValue").ID()

acctZeroBalance, err := wallet.GenerateKey()
require.NoError(t, err)

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

t.Run("eth_estimateGas", 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 := contractsapi.TestSimple.Abi.GetMethod("getValue").ID()

estimatedGas, err := client.EstimateGas(&ethgo.CallMsg{
From: acct.Address(),
To: &target,
Data: input,
})
require.NoError(t, err)
require.Equal(t, uint64(0x56a3), estimatedGas)
})

t.Run("eth_estimateGas by zero-balance account", 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 := contractsapi.TestSimple.Abi.GetMethod("getValue").ID()

acctZeroBalance, err := wallet.GenerateKey()
require.NoError(t, err)

resp, err := client.EstimateGas(&ethgo.CallMsg{
From: acctZeroBalance.Address(),
To: &target,
Data: input,
})
require.NoError(t, err)
require.Equal(t, uint64(0x56a3), resp)
})

t.Run("eth_getBalance", func(t *testing.T) {
key1, err := wallet.GenerateKey()
require.NoError(t, err)

// Test. return zero if the account does not exists
// Test. return zero if the account does not exist
balance1, err := client.GetBalance(key1.Address(), ethgo.Latest)
require.NoError(t, err)
require.Equal(t, balance1, big.NewInt(0))
Expand All @@ -86,13 +149,12 @@ func TestE2E_JsonRPC(t *testing.T) {
require.NoError(t, err)

toAddr := key1.Address()
msg := &ethgo.CallMsg{

estimatedGas, err := client.EstimateGas(&ethgo.CallMsg{
From: acct.Address(),
To: &toAddr,
Value: newBalance,
}

estimatedGas, err := client.EstimateGas(msg)
})
require.NoError(t, err)
txPrice := gasPrice * estimatedGas
// subtract gasPrice * estimatedGas from the balance and transfer the rest to the other account
Expand Down Expand Up @@ -163,7 +225,7 @@ func TestE2E_JsonRPC(t *testing.T) {
require.NoError(t, err)
require.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000000", resp.String())

setValueFn := abi.MustNewMethod("function setValue(uint256 _val) public")
setValueFn := contractsapi.TestSimple.Abi.GetMethod("setValue")

newVal := big.NewInt(1)

Expand Down Expand Up @@ -305,4 +367,24 @@ func TestE2E_JsonRPC(t *testing.T) {
// Test. The dynamic 'from' field is populated
require.NotEqual(t, ethTxn.From, ethgo.ZeroAddress)
})

t.Run("debug_traceTransaction", func(t *testing.T) {
key1, err := wallet.GenerateKey()
require.NoError(t, err)

// Test. We should be able to query the transaction by its hash
txn := cluster.Transfer(t, acct, types.Address(key1.Address()), one)
require.NoError(t, txn.Wait())
require.True(t, txn.Succeed())

txReceipt := txn.Receipt()

// Use a wrapper function from "jsonrpc" package when the config is introduced.
var trace *jsonrpc.TransactionTrace
err = jsonRPC.Call("debug_traceTransaction", &trace, txReceipt.TransactionHash, map[string]interface{}{
"tracer": "callTracer",
})
require.NoError(t, err)
require.Equal(t, txReceipt.GasUsed, trace.Gas)
})
}
2 changes: 1 addition & 1 deletion jsonrpc/eth_blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,7 @@ func (m *mockBlockStore) GetAvgGasPrice() *big.Int {
return big.NewInt(m.averageGasPrice)
}

func (m *mockBlockStore) ApplyTxn(header *types.Header, txn *types.Transaction, overrides types.StateOverride) (*runtime.ExecutionResult, error) {
func (m *mockBlockStore) ApplyTxn(_ *types.Header, _ *types.Transaction, _ types.StateOverride, _ bool) (*runtime.ExecutionResult, error) {
return &runtime.ExecutionResult{
Err: m.ethCallError,
ReturnValue: m.returnValue,
Expand Down
11 changes: 8 additions & 3 deletions jsonrpc/eth_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,12 @@ type ethBlockchainStore interface {
GetAvgGasPrice() *big.Int

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

// GetSyncProgression retrieves the current sync progression, if any
GetSyncProgression() *progress.Progression
Expand Down Expand Up @@ -510,7 +515,7 @@ func (e *Eth) Call(arg *txnArgs, filter BlockNumberOrHash, apiOverride *stateOve
}

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

transaction.Gas = gas

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

if result != nil {
data = []byte(hex.EncodeToString(result.ReturnValue))
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 @@ -870,7 +870,7 @@ func (m *mockSpecialStore) GetForksInTime(blockNumber uint64) chain.ForksInTime
return chain.ForksInTime{}
}

func (m *mockSpecialStore) ApplyTxn(header *types.Header, txn *types.Transaction, overrides types.StateOverride) (*runtime.ExecutionResult, error) {
func (m *mockSpecialStore) ApplyTxn(header *types.Header, txn *types.Transaction, _ types.StateOverride, _ bool) (*runtime.ExecutionResult, error) {
if m.applyTxnHook != nil {
return m.applyTxnHook(header, txn)
}
Expand Down
3 changes: 3 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,7 @@ func (j *jsonRPCHub) ApplyTxn(
header *types.Header,
txn *types.Transaction,
override types.StateOverride,
nonPayable bool,
) (result *runtime.ExecutionResult, err error) {
blockCreator, err := j.GetConsensus().GetBlockCreator(header)
if err != nil {
Expand All @@ -746,6 +747,8 @@ func (j *jsonRPCHub) ApplyTxn(
}
}

transition.SetNonPayable(nonPayable)

result, err = transition.Apply(txn)

return
Expand Down
13 changes: 11 additions & 2 deletions state/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,11 @@ func (t *Transition) SetCodeDirectly(addr types.Address, code []byte) error {
return nil
}

// SetNonPayable deactivates the check of tx cost against tx executor balance.
func (t *Transition) SetNonPayable(nonPayable bool) {
t.ctx.NonPayable = nonPayable
}

// SetTracer sets tracer to the context in order to enable it
func (t *Transition) SetTracer(tracer tracer.Tracer) {
t.ctx.Tracer = tracer
Expand Down Expand Up @@ -1117,8 +1122,12 @@ func checkAndProcessTx(msg *types.Transaction, t *Transition) error {
}

// 3. caller has enough balance to cover transaction
if err := t.subGasLimitPrice(msg); err != nil {
return NewTransitionApplicationError(err, true)
// Skip this check if the given flag is provided.
// It happens for eth_call and for other operations that do not change the state.
if !t.ctx.NonPayable {
if err := t.subGasLimitPrice(msg); err != nil {
return NewTransitionApplicationError(err, true)
}
}

return nil
Expand Down
4 changes: 4 additions & 0 deletions state/runtime/evm/evm_fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ func (m *mockHostF) SetState(addr types.Address, key types.Hash, value types.Has
return
}

func (m *mockHostF) SetNonPayable(nonPayable bool) {
return
}

func (m *mockHostF) GetBalance(addr types.Address) *big.Int {
if b, ok := m.balances[addr]; !ok {
m.balances[addr] = big.NewInt(0)
Expand Down
4 changes: 4 additions & 0 deletions state/runtime/evm/evm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ func (m *mockHost) SetStorage(
panic("Not implemented in tests") //nolint:gocritic
}

func (m *mockHost) SetNonPayable(bool) {
panic("Not implemented in tests") //nolint:gocritic
}

func (m *mockHost) GetBalance(addr types.Address) *big.Int {
panic("Not implemented in tests") //nolint:gocritic
}
Expand Down
4 changes: 4 additions & 0 deletions state/runtime/precompiled/native_transfer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ func (d dummyHost) SetStorage(addr types.Address, key types.Hash, value types.Ha
return runtime.StorageAdded
}

func (d dummyHost) SetNonPayable(nonPayable bool) {
d.t.Fatalf("SetNonPayable is not implemented")
}

func (d dummyHost) GetBalance(addr types.Address) *big.Int {
balance, exists := d.balances[addr]
if !exists {
Expand Down
2 changes: 2 additions & 0 deletions state/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type TxContext struct {
ChainID int64
Difficulty types.Hash
Tracer tracer.Tracer
NonPayable bool
BaseFee *big.Int
BurnContract types.Address
}
Expand Down Expand Up @@ -64,6 +65,7 @@ type Host interface {
GetStorage(addr types.Address, key types.Hash) types.Hash
SetStorage(addr types.Address, key types.Hash, value types.Hash, config *chain.ForksInTime) StorageStatus
SetState(addr types.Address, key types.Hash, value types.Hash)
SetNonPayable(nonPayable bool)
GetBalance(addr types.Address) *big.Int
GetCodeSize(addr types.Address) int
GetCodeHash(addr types.Address) types.Hash
Expand Down

0 comments on commit b455131

Please sign in to comment.