Skip to content

Commit 7491a7b

Browse files
JukLee0iragzliudan
authored andcommitted
all: improve EstimateGas API (ethereum#20830)
1 parent 21b0524 commit 7491a7b

File tree

16 files changed

+404
-121
lines changed

16 files changed

+404
-121
lines changed

accounts/abi/abi_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package abi
1919
import (
2020
"bytes"
2121
"encoding/hex"
22+
"errors"
2223
"fmt"
2324
"log"
2425
"math/big"
@@ -713,3 +714,34 @@ func TestABI_MethodById(t *testing.T) {
713714
}
714715

715716
}
717+
718+
func TestUnpackRevert(t *testing.T) {
719+
t.Parallel()
720+
721+
var cases = []struct {
722+
input string
723+
expect string
724+
expectErr error
725+
}{
726+
{"", "", errors.New("invalid data for unpacking")},
727+
{"08c379a1", "", errors.New("invalid data for unpacking")},
728+
{"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000", "revert reason", nil},
729+
}
730+
for index, c := range cases {
731+
t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) {
732+
got, err := UnpackRevert(common.Hex2Bytes(c.input))
733+
if c.expectErr != nil {
734+
if err == nil {
735+
t.Fatalf("Expected non-nil error")
736+
}
737+
if err.Error() != c.expectErr.Error() {
738+
t.Fatalf("Expected error mismatch, want %v, got %v", c.expectErr, err)
739+
}
740+
return
741+
}
742+
if c.expect != got {
743+
t.Fatalf("Output mismatch, want %v, got %v", c.expect, got)
744+
}
745+
})
746+
}
747+
}

accounts/abi/bind/backends/simulated.go

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/XinFinOrg/XDPoSChain/XDCx"
3030
"github.com/XinFinOrg/XDPoSChain/XDCxlending"
3131
"github.com/XinFinOrg/XDPoSChain/accounts"
32+
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
3233
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
3334
"github.com/XinFinOrg/XDPoSChain/accounts/keystore"
3435
"github.com/XinFinOrg/XDPoSChain/common"
@@ -54,7 +55,6 @@ import (
5455
var _ bind.ContractBackend = (*SimulatedBackend)(nil)
5556

5657
var errBlockNumberUnsupported = errors.New("SimulatedBackend cannot access blocks other than the latest block")
57-
var errGasEstimationFailed = errors.New("gas required exceeds allowance or always failing transaction")
5858

5959
// SimulatedBackend implements bind.ContractBackend, simulating a blockchain in
6060
// the background. Its main purpose is to allow easily testing contract bindings.
@@ -161,6 +161,12 @@ func NewSimulatedBackend(alloc core.GenesisAlloc) *SimulatedBackend {
161161
return backend
162162
}
163163

164+
// Close terminates the underlying blockchain's update loop.
165+
func (b *SimulatedBackend) Close() error {
166+
b.blockchain.Stop()
167+
return nil
168+
}
169+
164170
// Commit imports all the pending transactions as a single block and starts a
165171
// fresh new state.
166172
func (b *SimulatedBackend) Commit() {
@@ -290,8 +296,11 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call XDPoSChain.Cal
290296
if err != nil {
291297
return nil, err
292298
}
293-
rval, _, _, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state)
294-
return rval, err
299+
res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state)
300+
if err != nil {
301+
return nil, err
302+
}
303+
return res.Return(), nil
295304
}
296305

297306
// PendingCallContract executes a contract call on the pending state.
@@ -300,8 +309,11 @@ func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call XDPoSCh
300309
defer b.mu.Unlock()
301310
defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot())
302311

303-
rval, _, _, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState)
304-
return rval, err
312+
res, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState)
313+
if err != nil {
314+
return nil, err
315+
}
316+
return res.Return(), nil
305317
}
306318

307319
// PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving
@@ -348,42 +360,70 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call XDPoSChain.Call
348360
cap = hi
349361

350362
// Create a helper to check if a gas allowance results in an executable transaction
351-
executable := func(gas uint64) bool {
363+
executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
352364
call.Gas = gas
353365

354366
snapshot := b.pendingState.Snapshot()
355-
_, _, failed, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState)
367+
res, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState)
356368
b.pendingState.RevertToSnapshot(snapshot)
357369

358-
if err != nil || failed {
359-
return false
370+
if err != nil {
371+
if err == core.ErrIntrinsicGas {
372+
return true, nil, nil // Special case, raise gas limit
373+
}
374+
return true, nil, err // Bail out
360375
}
361-
return true
376+
return res.Failed(), res, nil
362377
}
363378
// Execute the binary search and hone in on an executable gas limit
364379
for lo+1 < hi {
365380
mid := (hi + lo) / 2
366-
if !executable(mid) {
381+
failed, _, err := executable(mid)
382+
383+
// If the error is not nil(consensus error), it means the provided message
384+
// call or transaction will never be accepted no matter how much gas it is
385+
// assigned. Return the error directly, don't struggle any more
386+
if err != nil {
387+
return 0, err
388+
}
389+
if failed {
367390
lo = mid
368391
} else {
369392
hi = mid
370393
}
371394
}
372395
// Reject the transaction as invalid if it still fails at the highest allowance
373396
if hi == cap {
374-
if !executable(hi) {
375-
return 0, errGasEstimationFailed
397+
failed, result, err := executable(hi)
398+
if err != nil {
399+
return 0, err
400+
}
401+
if failed {
402+
if result != nil && result.Err != vm.ErrOutOfGas {
403+
errMsg := fmt.Sprintf("always failing transaction (%v)", result.Err)
404+
if len(result.Revert()) > 0 {
405+
ret, err := abi.UnpackRevert(result.Revert())
406+
if err != nil {
407+
errMsg += fmt.Sprintf(" (%#x)", result.Revert())
408+
} else {
409+
errMsg += fmt.Sprintf(" (%s)", ret)
410+
}
411+
}
412+
return 0, errors.New(errMsg)
413+
}
414+
// Otherwise, the specified gas cap is too low
415+
return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap)
376416
}
377417
}
378418
return hi, nil
379419
}
380420

381421
// callContract implements common code between normal and pending contract calls.
382422
// state is modified during execution, make sure to copy it if necessary.
383-
func (b *SimulatedBackend) callContract(ctx context.Context, call XDPoSChain.CallMsg, block *types.Block, statedb *state.StateDB) (ret []byte, usedGas uint64, failed bool, err error) {
423+
func (b *SimulatedBackend) callContract(ctx context.Context, call XDPoSChain.CallMsg, block *types.Block, statedb *state.StateDB) (*core.ExecutionResult, error) {
384424
// Gas prices post 1559 need to be initialized
385425
if call.GasPrice != nil && (call.GasFeeCap != nil || call.GasTipCap != nil) {
386-
return nil, 0, false, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
426+
return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
387427
}
388428
head := b.blockchain.CurrentHeader()
389429
if !b.blockchain.Config().IsEIP1559(head.Number) {
@@ -438,8 +478,8 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call XDPoSChain.Cal
438478
vmenv := vm.NewEVM(evmContext, txContext, statedb, nil, b.config, vm.Config{NoBaseFee: true})
439479
gaspool := new(core.GasPool).AddGas(math.MaxUint64)
440480
owner := common.Address{}
441-
ret, usedGas, failed, err, _ = core.NewStateTransition(vmenv, msg, gaspool).TransitionDb(owner)
442-
return
481+
res, err, _ := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb(owner)
482+
return res, err
443483
}
444484

445485
// SendTransaction updates the pending block to include the given transaction.
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright 2019 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package backends
18+
19+
import (
20+
"context"
21+
"errors"
22+
"math/big"
23+
"strings"
24+
"testing"
25+
26+
"github.com/XinFinOrg/XDPoSChain"
27+
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
28+
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
29+
"github.com/XinFinOrg/XDPoSChain/common"
30+
"github.com/XinFinOrg/XDPoSChain/core"
31+
"github.com/XinFinOrg/XDPoSChain/crypto"
32+
"github.com/XinFinOrg/XDPoSChain/params"
33+
)
34+
35+
func TestSimulatedBackend_EstimateGas(t *testing.T) {
36+
/*
37+
pragma solidity ^0.6.4;
38+
contract GasEstimation {
39+
function PureRevert() public { revert(); }
40+
function Revert() public { revert("revert reason");}
41+
function OOG() public { for (uint i = 0; ; i++) {}}
42+
function Assert() public { assert(false);}
43+
function Valid() public {}
44+
}*/
45+
const contractAbi = "[{\"inputs\":[],\"name\":\"Assert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OOG\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PureRevert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Revert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Valid\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]"
46+
const contractBin = "0x60806040523480156100115760006000fd5b50610017565b61016e806100266000396000f3fe60806040523480156100115760006000fd5b506004361061005c5760003560e01c806350f6fe3414610062578063aa8b1d301461006c578063b9b046f914610076578063d8b9839114610080578063e09fface1461008a5761005c565b60006000fd5b61006a610094565b005b6100746100ad565b005b61007e6100b5565b005b6100886100c2565b005b610092610135565b005b6000600090505b5b808060010191505061009b565b505b565b60006000fd5b565b600015156100bf57fe5b5b565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600d8152602001807f72657665727420726561736f6e0000000000000000000000000000000000000081526020015060200191505060405180910390fd5b565b5b56fea2646970667358221220345bbcbb1a5ecf22b53a78eaebf95f8ee0eceff6d10d4b9643495084d2ec934a64736f6c63430006040033"
47+
48+
key, _ := crypto.GenerateKey()
49+
addr := crypto.PubkeyToAddress(key.PublicKey)
50+
opts := bind.NewKeyedTransactor(key)
51+
52+
sim := NewXDCSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether)}}, 10000000, &params.ChainConfig{
53+
ConstantinopleBlock: big.NewInt(0),
54+
XDPoS: &params.XDPoSConfig{
55+
Epoch: 900,
56+
SkipV1Validation: true,
57+
V2: &params.V2{
58+
SwitchBlock: big.NewInt(900),
59+
CurrentConfig: params.UnitTestV2Configs[0],
60+
},
61+
},
62+
})
63+
64+
defer sim.Close()
65+
66+
parsed, _ := abi.JSON(strings.NewReader(contractAbi))
67+
contractAddr, _, _, _ := bind.DeployContract(opts, parsed, common.FromHex(contractBin), sim)
68+
sim.Commit()
69+
70+
var cases = []struct {
71+
name string
72+
message XDPoSChain.CallMsg
73+
expect uint64
74+
expectError error
75+
}{
76+
{"plain transfer(valid)", XDPoSChain.CallMsg{
77+
From: addr,
78+
To: &addr,
79+
Gas: 0,
80+
GasPrice: big.NewInt(0),
81+
Value: big.NewInt(1),
82+
Data: nil,
83+
}, params.TxGas, nil},
84+
85+
{"plain transfer(invalid)", XDPoSChain.CallMsg{
86+
From: addr,
87+
To: &contractAddr,
88+
Gas: 0,
89+
GasPrice: big.NewInt(0),
90+
Value: big.NewInt(1),
91+
Data: nil,
92+
}, 0, errors.New("always failing transaction (execution reverted)")},
93+
94+
{"Revert", XDPoSChain.CallMsg{
95+
From: addr,
96+
To: &contractAddr,
97+
Gas: 0,
98+
GasPrice: big.NewInt(0),
99+
Value: nil,
100+
Data: common.Hex2Bytes("d8b98391"),
101+
}, 0, errors.New("always failing transaction (execution reverted) (revert reason)")},
102+
103+
{"PureRevert", XDPoSChain.CallMsg{
104+
From: addr,
105+
To: &contractAddr,
106+
Gas: 0,
107+
GasPrice: big.NewInt(0),
108+
Value: nil,
109+
Data: common.Hex2Bytes("aa8b1d30"),
110+
}, 0, errors.New("always failing transaction (execution reverted)")},
111+
112+
{"OOG", XDPoSChain.CallMsg{
113+
From: addr,
114+
To: &contractAddr,
115+
Gas: 100000,
116+
GasPrice: big.NewInt(0),
117+
Value: nil,
118+
Data: common.Hex2Bytes("50f6fe34"),
119+
}, 0, errors.New("gas required exceeds allowance (100000)")},
120+
121+
{"Assert", XDPoSChain.CallMsg{
122+
From: addr,
123+
To: &contractAddr,
124+
Gas: 100000,
125+
GasPrice: big.NewInt(0),
126+
Value: nil,
127+
Data: common.Hex2Bytes("b9b046f9"),
128+
}, 0, errors.New("always failing transaction (invalid opcode: INVALID)")},
129+
130+
{"Valid", XDPoSChain.CallMsg{
131+
From: addr,
132+
To: &contractAddr,
133+
Gas: 100000,
134+
GasPrice: big.NewInt(0),
135+
Value: nil,
136+
Data: common.Hex2Bytes("e09fface"),
137+
}, 21483, nil},
138+
}
139+
for _, c := range cases {
140+
got, err := sim.EstimateGas(context.Background(), c.message)
141+
if c.expectError != nil {
142+
if err == nil {
143+
t.Fatalf("Expect error, got nil")
144+
}
145+
if c.expectError.Error() != err.Error() {
146+
t.Fatalf("Expect error, want %v, got %v", c.expectError, err)
147+
}
148+
continue
149+
}
150+
if got != c.expect {
151+
t.Fatalf("Gas estimation mismatch, want %d, got %d", c.expect, got)
152+
}
153+
}
154+
}

core/blockchain.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,6 @@ var (
8282
blockReorgDropMeter = metrics.NewRegisteredMeter("chain/reorg/drop", nil)
8383

8484
CheckpointCh = make(chan int)
85-
86-
ErrNoGenesis = errors.New("Genesis not found in chain")
8785
)
8886

8987
const (

core/error.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,18 @@ var (
2929
// ErrBlacklistedHash is returned if a block to import is on the blacklist.
3030
ErrBlacklistedHash = errors.New("blacklisted hash")
3131

32+
// ErrNoGenesis is returned when there is no Genesis Block.
33+
ErrNoGenesis = errors.New("genesis not found in chain")
34+
)
35+
36+
// List of evm-call-message pre-checking errors. All state transtion messages will
37+
// be pre-checked before execution. If any invalidation detected, the corresponding
38+
// error should be returned which is defined here.
39+
//
40+
// - If the pre-checking happens in the miner, then the transaction won't be packed.
41+
// - If the pre-checking happens in the block processing procedure, then a "BAD BLOCk"
42+
// error should be emitted.
43+
var (
3244
// ErrNonceTooLow is returned if the nonce of a transaction is lower than the
3345
// one present in the local chain.
3446
ErrNonceTooLow = errors.New("nonce too low")
@@ -45,6 +57,10 @@ var (
4557
// by a transaction is higher than what's left in the block.
4658
ErrGasLimitReached = errors.New("gas limit reached")
4759

60+
// ErrInsufficientFundsForTransfer is returned if the transaction sender doesn't
61+
// have enough funds for transfer(topmost call only).
62+
ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer")
63+
4864
// ErrMaxInitCodeSizeExceeded is returned if creation transaction provides the init code bigger
4965
// than init code size limit.
5066
ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded")

0 commit comments

Comments
 (0)