Skip to content

Commit 263622f

Browse files
accounts/abi/bind/backend, internal/ethapi: recap gas limit with balance (ethereum#21043)
* accounts/abi/bind/backend, internal/ethapi: recap gas limit with balance * accounts, internal: address comment and fix lint * accounts, internal: extend log message * tiny nits to format hexutil.Big and nil properly Co-authored-by: Péter Szilágyi <[email protected]>
1 parent e29e4c2 commit 263622f

File tree

3 files changed

+120
-4
lines changed

3 files changed

+120
-4
lines changed

accounts/abi/bind/backends/simulated.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
"github.com/ethereum/go-ethereum/eth/filters"
4040
"github.com/ethereum/go-ethereum/ethdb"
4141
"github.com/ethereum/go-ethereum/event"
42+
"github.com/ethereum/go-ethereum/log"
4243
"github.com/ethereum/go-ethereum/params"
4344
"github.com/ethereum/go-ethereum/rpc"
4445
)
@@ -401,6 +402,27 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
401402
} else {
402403
hi = b.pendingBlock.GasLimit()
403404
}
405+
// Recap the highest gas allowance with account's balance.
406+
if call.GasPrice != nil && call.GasPrice.Uint64() != 0 {
407+
balance := b.pendingState.GetBalance(call.From) // from can't be nil
408+
available := new(big.Int).Set(balance)
409+
if call.Value != nil {
410+
if call.Value.Cmp(available) >= 0 {
411+
return 0, errors.New("insufficient funds for transfer")
412+
}
413+
available.Sub(available, call.Value)
414+
}
415+
allowance := new(big.Int).Div(available, call.GasPrice)
416+
if hi > allowance.Uint64() {
417+
transfer := call.Value
418+
if transfer == nil {
419+
transfer = new(big.Int)
420+
}
421+
log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance,
422+
"sent", transfer, "gasprice", call.GasPrice, "fundable", allowance)
423+
hi = allowance.Uint64()
424+
}
425+
}
404426
cap = hi
405427

406428
// Create a helper to check if a gas allowance results in an executable transaction

accounts/abi/bind/backends/simulated_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,73 @@ func TestSimulatedBackend_EstimateGas(t *testing.T) {
466466
}
467467
}
468468

469+
func TestSimulatedBackend_EstimateGasWithPrice(t *testing.T) {
470+
key, _ := crypto.GenerateKey()
471+
addr := crypto.PubkeyToAddress(key.PublicKey)
472+
473+
sim := NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether*2 + 2e17)}}, 10000000)
474+
defer sim.Close()
475+
476+
receipant := common.HexToAddress("deadbeef")
477+
var cases = []struct {
478+
name string
479+
message ethereum.CallMsg
480+
expect uint64
481+
expectError error
482+
}{
483+
{"EstimateWithoutPrice", ethereum.CallMsg{
484+
From: addr,
485+
To: &receipant,
486+
Gas: 0,
487+
GasPrice: big.NewInt(0),
488+
Value: big.NewInt(1000),
489+
Data: nil,
490+
}, 21000, nil},
491+
492+
{"EstimateWithPrice", ethereum.CallMsg{
493+
From: addr,
494+
To: &receipant,
495+
Gas: 0,
496+
GasPrice: big.NewInt(1000),
497+
Value: big.NewInt(1000),
498+
Data: nil,
499+
}, 21000, nil},
500+
501+
{"EstimateWithVeryHighPrice", ethereum.CallMsg{
502+
From: addr,
503+
To: &receipant,
504+
Gas: 0,
505+
GasPrice: big.NewInt(1e14), // gascost = 2.1ether
506+
Value: big.NewInt(1e17), // the remaining balance for fee is 2.1ether
507+
Data: nil,
508+
}, 21000, nil},
509+
510+
{"EstimateWithSuperhighPrice", ethereum.CallMsg{
511+
From: addr,
512+
To: &receipant,
513+
Gas: 0,
514+
GasPrice: big.NewInt(2e14), // gascost = 4.2ether
515+
Value: big.NewInt(1000),
516+
Data: nil,
517+
}, 21000, errors.New("gas required exceeds allowance (10999)")}, // 10999=(2.2ether-1000wei)/(2e14)
518+
}
519+
for _, c := range cases {
520+
got, err := sim.EstimateGas(context.Background(), c.message)
521+
if c.expectError != nil {
522+
if err == nil {
523+
t.Fatalf("Expect error, got nil")
524+
}
525+
if c.expectError.Error() != err.Error() {
526+
t.Fatalf("Expect error, want %v, got %v", c.expectError, err)
527+
}
528+
continue
529+
}
530+
if got != c.expect {
531+
t.Fatalf("Gas estimation mismatch, want %d, got %d", c.expect, got)
532+
}
533+
}
534+
}
535+
469536
func TestSimulatedBackend_HeaderByHash(t *testing.T) {
470537
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
471538

internal/ethapi/api.go

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,11 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
906906
hi uint64
907907
cap uint64
908908
)
909+
// Use zero address if sender unspecified.
910+
if args.From == nil {
911+
args.From = new(common.Address)
912+
}
913+
// Determine the highest gas limit can be used during the estimation.
909914
if args.Gas != nil && uint64(*args.Gas) >= params.TxGas {
910915
hi = uint64(*args.Gas)
911916
} else {
@@ -916,16 +921,38 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
916921
}
917922
hi = block.GasLimit()
918923
}
924+
// Recap the highest gas limit with account's available balance.
925+
if args.GasPrice != nil && args.GasPrice.ToInt().Uint64() != 0 {
926+
state, _, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
927+
if err != nil {
928+
return 0, err
929+
}
930+
balance := state.GetBalance(*args.From) // from can't be nil
931+
available := new(big.Int).Set(balance)
932+
if args.Value != nil {
933+
if args.Value.ToInt().Cmp(available) >= 0 {
934+
return 0, errors.New("insufficient funds for transfer")
935+
}
936+
available.Sub(available, args.Value.ToInt())
937+
}
938+
allowance := new(big.Int).Div(available, args.GasPrice.ToInt())
939+
if hi > allowance.Uint64() {
940+
transfer := args.Value
941+
if transfer == nil {
942+
transfer = new(hexutil.Big)
943+
}
944+
log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance,
945+
"sent", transfer.ToInt(), "gasprice", args.GasPrice.ToInt(), "fundable", allowance)
946+
hi = allowance.Uint64()
947+
}
948+
}
949+
// Recap the highest gas allowance with specified gascap.
919950
if gasCap != nil && hi > gasCap.Uint64() {
920951
log.Warn("Caller gas above allowance, capping", "requested", hi, "cap", gasCap)
921952
hi = gasCap.Uint64()
922953
}
923954
cap = hi
924955

925-
// Use zero address if sender unspecified.
926-
if args.From == nil {
927-
args.From = new(common.Address)
928-
}
929956
// Create a helper to check if a gas allowance results in an executable transaction
930957
executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
931958
args.Gas = (*hexutil.Uint64)(&gas)

0 commit comments

Comments
 (0)