From d3b553df82b73e1ef17c83ff78398a63edf0c2b0 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Fri, 8 May 2020 11:22:49 +0800 Subject: [PATCH 1/4] accounts/abi/bind/backend, internal/ethapi: recap gas limit with balance --- accounts/abi/bind/backends/simulated.go | 15 +++++ accounts/abi/bind/backends/simulated_test.go | 67 ++++++++++++++++++++ internal/ethapi/api.go | 29 +++++++-- 3 files changed, 107 insertions(+), 4 deletions(-) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 83d0db05b921..238ec4f8d3d9 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -401,6 +401,21 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs } else { 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 // Create a helper to check if a gas allowance results in an executable transaction diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go index a28f99aeaac5..a55b4460aa8a 100644 --- a/accounts/abi/bind/backends/simulated_test.go +++ b/accounts/abi/bind/backends/simulated_test.go @@ -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) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index febfcb22425d..71534e0045ce 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -906,6 +906,11 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash hi 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 { hi = uint64(*args.Gas) } else { @@ -916,16 +921,32 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash } 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() { log.Warn("Caller gas above allowance, capping", "requested", hi, "cap", gasCap) hi = gasCap.Uint64() } 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 executable := func(gas uint64) (bool, *core.ExecutionResult, error) { args.Gas = (*hexutil.Uint64)(&gas) From 1dbb69c6923c7d106823d7f6c5e1b734057d4221 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Fri, 8 May 2020 19:32:05 +0800 Subject: [PATCH 2/4] accounts, internal: address comment and fix lint --- accounts/abi/bind/backends/simulated.go | 2 ++ internal/ethapi/api.go | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 238ec4f8d3d9..19af6f6c34cd 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -39,6 +39,7 @@ import ( "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" ) @@ -414,6 +415,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs allowance := new(big.Int).Div(balance, call.GasPrice) if hi > allowance.Uint64() { hi = allowance.Uint64() + log.Warn("Gas estimation capped by limited funds", "original", hi, "fundable", allowance) } } cap = hi diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 71534e0045ce..779678c4f597 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -921,7 +921,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash } hi = block.GasLimit() } - // Recap the highest gas limit with account's avaliable balance. + // 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 { @@ -938,6 +938,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash allowance := new(big.Int).Div(balance, args.GasPrice.ToInt()) if hi > allowance.Uint64() { hi = allowance.Uint64() + log.Warn("Gas estimation capped by limited funds", "original", hi, "fundable", allowance) } } // Recap the highest gas allowance with specified gascap. From ec6a8df98809c444cdeffd418bd43b94f6d9958a Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Mon, 11 May 2020 15:09:54 +0800 Subject: [PATCH 3/4] accounts, internal: extend log message --- accounts/abi/bind/backends/simulated.go | 13 +++++++------ internal/ethapi/api.go | 13 +++++++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 19af6f6c34cd..e1d8fcd782ca 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -404,18 +404,19 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs } // 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 + 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(balance) >= 0 { + if call.Value.Cmp(available) >= 0 { return 0, errors.New("insufficient funds for transfer") } - balance.Sub(balance, call.Value) + available.Sub(available, call.Value) } - allowance := new(big.Int).Div(balance, call.GasPrice) + allowance := new(big.Int).Div(available, call.GasPrice) if hi > allowance.Uint64() { + log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance, + "sent", call.Value, "gasprice", call.GasPrice, "fundable", allowance) hi = allowance.Uint64() - log.Warn("Gas estimation capped by limited funds", "original", hi, "fundable", allowance) } } cap = hi diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 779678c4f597..195da54fcd74 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -927,18 +927,19 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash if err != nil { return 0, err } - balance := new(big.Int) - balance.Set(state.GetBalance(*args.From)) // from can't be nil + 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(balance) >= 0 { + if args.Value.ToInt().Cmp(available) >= 0 { return 0, errors.New("insufficient funds for transfer") } - balance.Sub(balance, args.Value.ToInt()) + available.Sub(available, args.Value.ToInt()) } - allowance := new(big.Int).Div(balance, args.GasPrice.ToInt()) + allowance := new(big.Int).Div(available, args.GasPrice.ToInt()) if hi > allowance.Uint64() { + log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance, + "sent", args.Value, "gasprice", args.GasPrice, "fundable", allowance) hi = allowance.Uint64() - log.Warn("Gas estimation capped by limited funds", "original", hi, "fundable", allowance) } } // Recap the highest gas allowance with specified gascap. From 10d605a1b43e1ce8a4a3205e9e9d26dfa5dccc92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 11 May 2020 11:06:51 +0300 Subject: [PATCH 4/4] tiny nits to format hexutil.Big and nil properly --- accounts/abi/bind/backends/simulated.go | 6 +++++- internal/ethapi/api.go | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index e1d8fcd782ca..cfbe2914d51e 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -414,8 +414,12 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs } 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", call.Value, "gasprice", call.GasPrice, "fundable", allowance) + "sent", transfer, "gasprice", call.GasPrice, "fundable", allowance) hi = allowance.Uint64() } } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 195da54fcd74..45240aa2cfb7 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -937,8 +937,12 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash } 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", args.Value, "gasprice", args.GasPrice, "fundable", allowance) + "sent", transfer.ToInt(), "gasprice", args.GasPrice.ToInt(), "fundable", allowance) hi = allowance.Uint64() } }