accounts/abi/bind/backend, internal/ethapi: recap gas limit with balance

This commit is contained in:
rjl493456442 2020-05-08 11:22:49 +08:00
parent b0b65d017f
commit d3b553df82
3 changed files with 107 additions and 4 deletions

View file

@ -401,6 +401,21 @@ 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 := new(big.Int)
balance.Set(b.pendingState.GetBalance(call.From)) // from can't be nil
if call.Value != nil {
if call.Value.Cmp(balance) >= 0 {
return 0, errors.New("insufficient funds for transfer")
}
balance.Sub(balance, call.Value)
}
allowance := new(big.Int).Div(balance, call.GasPrice)
if hi > allowance.Uint64() {
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

View file

@ -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)

View file

@ -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,32 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
} }
hi = block.GasLimit() hi = block.GasLimit()
} }
// Recap the highest gas limit with account's avaliable balance.
if args.GasPrice != nil && args.GasPrice.ToInt().Uint64() != 0 {
state, _, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if err != nil {
return 0, err
}
balance := new(big.Int)
balance.Set(state.GetBalance(*args.From)) // from can't be nil
if args.Value != nil {
if args.Value.ToInt().Cmp(balance) >= 0 {
return 0, errors.New("insufficient funds for transfer")
}
balance.Sub(balance, args.Value.ToInt())
}
allowance := new(big.Int).Div(balance, args.GasPrice.ToInt())
if hi > allowance.Uint64() {
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)