forked from cerc-io/plugeth
accounts/abi/bind/backend, internal/ethapi: recap gas limit with balance (#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 <peterke@gmail.com>
This commit is contained in:
parent
e29e4c2376
commit
263622f44f
@ -39,6 +39,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/eth/filters"
|
"github.com/ethereum/go-ethereum/eth/filters"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
)
|
)
|
||||||
@ -401,6 +402,27 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
|
|||||||
} else {
|
} else {
|
||||||
hi = b.pendingBlock.GasLimit()
|
hi = b.pendingBlock.GasLimit()
|
||||||
}
|
}
|
||||||
|
// Recap the highest gas allowance with account's balance.
|
||||||
|
if call.GasPrice != nil && call.GasPrice.Uint64() != 0 {
|
||||||
|
balance := b.pendingState.GetBalance(call.From) // from can't be nil
|
||||||
|
available := new(big.Int).Set(balance)
|
||||||
|
if call.Value != nil {
|
||||||
|
if call.Value.Cmp(available) >= 0 {
|
||||||
|
return 0, errors.New("insufficient funds for transfer")
|
||||||
|
}
|
||||||
|
available.Sub(available, call.Value)
|
||||||
|
}
|
||||||
|
allowance := new(big.Int).Div(available, call.GasPrice)
|
||||||
|
if hi > allowance.Uint64() {
|
||||||
|
transfer := call.Value
|
||||||
|
if transfer == nil {
|
||||||
|
transfer = new(big.Int)
|
||||||
|
}
|
||||||
|
log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance,
|
||||||
|
"sent", transfer, "gasprice", call.GasPrice, "fundable", allowance)
|
||||||
|
hi = allowance.Uint64()
|
||||||
|
}
|
||||||
|
}
|
||||||
cap = hi
|
cap = hi
|
||||||
|
|
||||||
// Create a helper to check if a gas allowance results in an executable transaction
|
// Create a helper to check if a gas allowance results in an executable transaction
|
||||||
|
@ -466,6 +466,73 @@ func TestSimulatedBackend_EstimateGas(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSimulatedBackend_EstimateGasWithPrice(t *testing.T) {
|
||||||
|
key, _ := crypto.GenerateKey()
|
||||||
|
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
|
||||||
|
sim := NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether*2 + 2e17)}}, 10000000)
|
||||||
|
defer sim.Close()
|
||||||
|
|
||||||
|
receipant := common.HexToAddress("deadbeef")
|
||||||
|
var cases = []struct {
|
||||||
|
name string
|
||||||
|
message ethereum.CallMsg
|
||||||
|
expect uint64
|
||||||
|
expectError error
|
||||||
|
}{
|
||||||
|
{"EstimateWithoutPrice", ethereum.CallMsg{
|
||||||
|
From: addr,
|
||||||
|
To: &receipant,
|
||||||
|
Gas: 0,
|
||||||
|
GasPrice: big.NewInt(0),
|
||||||
|
Value: big.NewInt(1000),
|
||||||
|
Data: nil,
|
||||||
|
}, 21000, nil},
|
||||||
|
|
||||||
|
{"EstimateWithPrice", ethereum.CallMsg{
|
||||||
|
From: addr,
|
||||||
|
To: &receipant,
|
||||||
|
Gas: 0,
|
||||||
|
GasPrice: big.NewInt(1000),
|
||||||
|
Value: big.NewInt(1000),
|
||||||
|
Data: nil,
|
||||||
|
}, 21000, nil},
|
||||||
|
|
||||||
|
{"EstimateWithVeryHighPrice", ethereum.CallMsg{
|
||||||
|
From: addr,
|
||||||
|
To: &receipant,
|
||||||
|
Gas: 0,
|
||||||
|
GasPrice: big.NewInt(1e14), // gascost = 2.1ether
|
||||||
|
Value: big.NewInt(1e17), // the remaining balance for fee is 2.1ether
|
||||||
|
Data: nil,
|
||||||
|
}, 21000, nil},
|
||||||
|
|
||||||
|
{"EstimateWithSuperhighPrice", ethereum.CallMsg{
|
||||||
|
From: addr,
|
||||||
|
To: &receipant,
|
||||||
|
Gas: 0,
|
||||||
|
GasPrice: big.NewInt(2e14), // gascost = 4.2ether
|
||||||
|
Value: big.NewInt(1000),
|
||||||
|
Data: nil,
|
||||||
|
}, 21000, errors.New("gas required exceeds allowance (10999)")}, // 10999=(2.2ether-1000wei)/(2e14)
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
got, err := sim.EstimateGas(context.Background(), c.message)
|
||||||
|
if c.expectError != nil {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expect error, got nil")
|
||||||
|
}
|
||||||
|
if c.expectError.Error() != err.Error() {
|
||||||
|
t.Fatalf("Expect error, want %v, got %v", c.expectError, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got != c.expect {
|
||||||
|
t.Fatalf("Gas estimation mismatch, want %d, got %d", c.expect, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSimulatedBackend_HeaderByHash(t *testing.T) {
|
func TestSimulatedBackend_HeaderByHash(t *testing.T) {
|
||||||
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
testAddr := crypto.PubkeyToAddress(testKey.PublicKey)
|
||||||
|
|
||||||
|
@ -906,6 +906,11 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
|
|||||||
hi uint64
|
hi uint64
|
||||||
cap uint64
|
cap uint64
|
||||||
)
|
)
|
||||||
|
// Use zero address if sender unspecified.
|
||||||
|
if args.From == nil {
|
||||||
|
args.From = new(common.Address)
|
||||||
|
}
|
||||||
|
// Determine the highest gas limit can be used during the estimation.
|
||||||
if args.Gas != nil && uint64(*args.Gas) >= params.TxGas {
|
if args.Gas != nil && uint64(*args.Gas) >= params.TxGas {
|
||||||
hi = uint64(*args.Gas)
|
hi = uint64(*args.Gas)
|
||||||
} else {
|
} else {
|
||||||
@ -916,16 +921,38 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
|
|||||||
}
|
}
|
||||||
hi = block.GasLimit()
|
hi = block.GasLimit()
|
||||||
}
|
}
|
||||||
|
// Recap the highest gas limit with account's available balance.
|
||||||
|
if args.GasPrice != nil && args.GasPrice.ToInt().Uint64() != 0 {
|
||||||
|
state, _, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
balance := state.GetBalance(*args.From) // from can't be nil
|
||||||
|
available := new(big.Int).Set(balance)
|
||||||
|
if args.Value != nil {
|
||||||
|
if args.Value.ToInt().Cmp(available) >= 0 {
|
||||||
|
return 0, errors.New("insufficient funds for transfer")
|
||||||
|
}
|
||||||
|
available.Sub(available, args.Value.ToInt())
|
||||||
|
}
|
||||||
|
allowance := new(big.Int).Div(available, args.GasPrice.ToInt())
|
||||||
|
if hi > allowance.Uint64() {
|
||||||
|
transfer := args.Value
|
||||||
|
if transfer == nil {
|
||||||
|
transfer = new(hexutil.Big)
|
||||||
|
}
|
||||||
|
log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance,
|
||||||
|
"sent", transfer.ToInt(), "gasprice", args.GasPrice.ToInt(), "fundable", allowance)
|
||||||
|
hi = allowance.Uint64()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Recap the highest gas allowance with specified gascap.
|
||||||
if gasCap != nil && hi > gasCap.Uint64() {
|
if gasCap != nil && hi > gasCap.Uint64() {
|
||||||
log.Warn("Caller gas above allowance, capping", "requested", hi, "cap", gasCap)
|
log.Warn("Caller gas above allowance, capping", "requested", hi, "cap", gasCap)
|
||||||
hi = gasCap.Uint64()
|
hi = gasCap.Uint64()
|
||||||
}
|
}
|
||||||
cap = hi
|
cap = hi
|
||||||
|
|
||||||
// Use zero address if sender unspecified.
|
|
||||||
if args.From == nil {
|
|
||||||
args.From = new(common.Address)
|
|
||||||
}
|
|
||||||
// Create a helper to check if a gas allowance results in an executable transaction
|
// Create a helper to check if a gas allowance results in an executable transaction
|
||||||
executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
|
executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
|
||||||
args.Gas = (*hexutil.Uint64)(&gas)
|
args.Gas = (*hexutil.Uint64)(&gas)
|
||||||
|
Loading…
Reference in New Issue
Block a user