aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--accounts/abi/bind/backends/simulated.go37
-rw-r--r--internal/ethapi/api.go39
2 files changed, 52 insertions, 24 deletions
diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go
index e1d5745ca..09288d401 100644
--- a/accounts/abi/bind/backends/simulated.go
+++ b/accounts/abi/bind/backends/simulated.go
@@ -41,6 +41,7 @@ import (
var _ bind.ContractBackend = (*SimulatedBackend)(nil)
var errBlockNumberUnsupported = errors.New("SimulatedBackend cannot access blocks other than the latest block")
+var errGasEstimationFailed = errors.New("gas required exceeds allowance or always failing transaction")
// SimulatedBackend implements bind.ContractBackend, simulating a blockchain in
// the background. Its main purpose is to allow easily testing contract bindings.
@@ -203,32 +204,46 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
b.mu.Lock()
defer b.mu.Unlock()
- // Binary search the gas requirement, as it may be higher than the amount used
+ // Determine the lowest and highest possible gas limits to binary search in between
var (
- lo uint64 = params.TxGas - 1
- hi uint64
+ lo uint64 = params.TxGas - 1
+ hi uint64
+ cap uint64
)
if call.Gas != nil && call.Gas.Uint64() >= params.TxGas {
hi = call.Gas.Uint64()
} else {
hi = b.pendingBlock.GasLimit().Uint64()
}
- for lo+1 < hi {
- // Take a guess at the gas, and check transaction validity
- mid := (hi + lo) / 2
- call.Gas = new(big.Int).SetUint64(mid)
+ cap = hi
+
+ // Create a helper to check if a gas allowance results in an executable transaction
+ executable := func(gas uint64) bool {
+ call.Gas = new(big.Int).SetUint64(gas)
snapshot := b.pendingState.Snapshot()
_, _, failed, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState)
b.pendingState.RevertToSnapshot(snapshot)
- // If the transaction became invalid or execution failed, raise the gas limit
if err != nil || failed {
+ return false
+ }
+ return true
+ }
+ // Execute the binary search and hone in on an executable gas limit
+ for lo+1 < hi {
+ mid := (hi + lo) / 2
+ if !executable(mid) {
lo = mid
- continue
+ } else {
+ hi = mid
+ }
+ }
+ // Reject the transaction as invalid if it still fails at the highest allowance
+ if hi == cap {
+ if !executable(hi) {
+ return nil, errGasEstimationFailed
}
- // Otherwise assume the transaction succeeded, lower the gas limit
- hi = mid
}
return new(big.Int).SetUint64(hi), nil
}
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index 1ffb5a180..59a29d722 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -649,12 +649,14 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNr r
return (hexutil.Bytes)(result), err
}
-// EstimateGas returns an estimate of the amount of gas needed to execute the given transaction.
+// EstimateGas returns an estimate of the amount of gas needed to execute the
+// given transaction against the current pending block.
func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (*hexutil.Big, error) {
- // Binary search the gas requirement, as it may be higher than the amount used
+ // Determine the lowest and highest possible gas limits to binary search in between
var (
- lo uint64 = params.TxGas - 1
- hi uint64
+ lo uint64 = params.TxGas - 1
+ hi uint64
+ cap uint64
)
if (*big.Int)(&args.Gas).Uint64() >= params.TxGas {
hi = (*big.Int)(&args.Gas).Uint64()
@@ -666,20 +668,31 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (*
}
hi = block.GasLimit().Uint64()
}
- for lo+1 < hi {
- // Take a guess at the gas, and check transaction validity
- mid := (hi + lo) / 2
- (*big.Int)(&args.Gas).SetUint64(mid)
+ cap = hi
+ // Create a helper to check if a gas allowance results in an executable transaction
+ executable := func(gas uint64) bool {
+ (*big.Int)(&args.Gas).SetUint64(gas)
_, _, failed, err := s.doCall(ctx, args, rpc.PendingBlockNumber, vm.Config{})
-
- // If the transaction became invalid or execution failed, raise the gas limit
if err != nil || failed {
+ return false
+ }
+ return true
+ }
+ // Execute the binary search and hone in on an executable gas limit
+ for lo+1 < hi {
+ mid := (hi + lo) / 2
+ if !executable(mid) {
lo = mid
- continue
+ } else {
+ hi = mid
+ }
+ }
+ // Reject the transaction as invalid if it still fails at the highest allowance
+ if hi == cap {
+ if !executable(hi) {
+ return nil, fmt.Errorf("gas required exceeds allowance or always failing transaction")
}
- // Otherwise assume the transaction succeeded, lower the gas limit
- hi = mid
}
return (*hexutil.Big)(new(big.Int).SetUint64(hi)), nil
}