8000 Add common EstimateGas by 2dvorak · Pull Request #1878 · klaytn/klaytn · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
This repository was archived by the owner on Aug 19, 2024. It is now read-only.

Add common EstimateGas #1878

Merged
merged 2 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion accounts/abi/bind/backends/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func (b *BlockchainContractCaller) CallContract(ctx context.Context, call klaytn
return nil, err
}
if len(res.Revert()) > 0 {
return nil, newRevertError(res)
return nil, blockchain.NewRevertError(res)
}
return res.Return(), res.Unwrap()
}
Expand Down
110 changes: 9 additions & 101 deletions accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,13 @@ import (
"time"

"github.com/klaytn/klaytn"
"github.com/klaytn/klaytn/accounts/abi"
"github.com/klaytn/klaytn/accounts/abi/bind"
"github.com/klaytn/klaytn/blockchain"
"github.com/klaytn/klaytn/blockchain/bloombits"
"github.com/klaytn/klaytn/blockchain/state"
"github.com/klaytn/klaytn/blockchain/types"
"github.com/klaytn/klaytn/blockchain/vm"
"github.com/klaytn/klaytn/common"
"github.com/klaytn/klaytn/common/hexutil"
"github.com/klaytn/klaytn/common/math"
"github.com/klaytn/klaytn/consensus/gxhash"
"github.com/klaytn/klaytn/event"
Expand Down Expand Up @@ -355,36 +353,6 @@ func (b *SimulatedBackend) PendingCodeAt(_ context.Context, contract common.Addr
return b.pendingState.GetCode(contract), nil
}

func newRevertError(result *blockchain.ExecutionResult) *revertError {
reason, errUnpack := abi.UnpackRevert(result.Revert())
err := errors.New("execution reverted")
if errUnpack == nil {
err = fmt.Errorf("execution reverted: %v", reason)
}
return &revertError{
error: err,
reason: hexutil.Encode(result.Revert()),
}
}

// revertError is an API error that encompassas an EVM revertal with JSON error
// code and a binary data blob.
type revertError struct {
error
reason string // revert reason hex encoded
}

// ErrorCode returns the JSON error code for a revertal.
// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal
func (e *revertError) ErrorCode() int {
return 3
}

// ErrorData returns the hex encoded revert reason.
func (e *revertError) ErrorData() interface{} {
return e.reason
}

// CallContract executes a contract call.
func (b *SimulatedBackend) CallContract(ctx context.Context, call klaytn.CallMsg, blockNumber *big.Int) ([]byte, error) {
b.mu.Lock()
Expand All @@ -403,7 +371,7 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call klaytn.CallMsg
}
// If the result contains a revert reason, try to unpack and return it.
if len(res.Revert()) > 0 {
return nil, newRevertError(res)
return nil, blockchain.NewRevertError(res)
}
return res.Return(), res.Unwrap()
}
Expand All @@ -420,7 +388,7 @@ func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call klaytn.
}
// If the result contains a revert reason, try to unpack and return it.
if len(res.Revert()) > 0 {
return nil, newRevertError(res)
return nil, blockchain.NewRevertError(res)
}
return res.Return(), res.Unwrap()
}
Expand All @@ -446,40 +414,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call klaytn.CallMsg)
b.mu.Lock()
defer b.mu.Unlock()

// Determine the lowest and highest possible gas limits to binary search in between
var (
lo uint64 = params.TxGas - 1
hi uint64
cap uint64
)
if call.Gas >= params.TxGas {
hi = call.Gas
} else {
hi = params.UpperGasLimit
}

// Recap the highest gas allowance with account's balance.
if call.GasPrice != nil && call.GasPrice.BitLen() != 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 allowance.IsUint64() && hi > allowance.Uint64() {
transfer := call.Value
if transfer == nil {
transfer = new(big.Int)
}
bind.Logger.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance,
"sent", transfer, "gasprice", call.GasPrice, "fundable", allowance)
hi = allowance.Uint64()
}
}
cap = hi
balance := b.pendingState.GetBalance(call.From) // from can't be nil

// Create a helper to check if a gas allowance results in an executable transaction
executable := func(gas uint64) (bool, *blockchain.ExecutionResult, error) {
Expand All @@ -498,40 +433,13 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call klaytn.CallMsg)
}
return res.Failed(), res, nil
}
// Execute the binary search and hone in on an executable gas limit
for lo+1 < hi {
mid := (hi + lo) / 2
failed, _, err := executable(mid)
// If the error is not nil(when not a vm error), it means the provided message
// call or transaction will never be accepted no matter how much gas it is
// assigned. Return the error directly, don't struggle any more
if err != nil {
return 0, err
}
if failed {
lo = mid
} else {
hi = mid
}
}
// Reject the transaction as invalid if it still fails at the highest allowance
if hi == cap {
failed, result, err := executable(hi)
if err != nil {
return 0, err
}
if failed {
if result != nil && result.VmExecutionStatus != types.ReceiptStatusErrOutOfGas {
if len(result.Revert()) > 0 {
return 0, newRevertError(result)
}
return 0, result.Unwrap()
}
// Otherwise, the specified gas cap is too low
return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap)
}

estimated, err := blockchain.DoEstimateGas(ctx, call.Gas, 0, call.Value, call.GasPrice, balance, executable)
if err != nil {
return 0, err
} else {
return uint64(estimated), nil
}
return hi, nil
}

// callContract implements common code between normal and pending contract calls.
Expand Down
4 changes: 2 additions & 2 deletions accounts/abi/bind/backends/simulated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ func TestSimulatedBackend_EstimateGas(t *testing.T) {
t.Fatalf("Expect error, want %v, got %v", c.expectError, err)
}
if c.expectData != nil {
if err, ok := err.(*revertError); !ok {
if err, ok := err.(*blockchain.RevertError); !ok {
t.Fatalf("Expect revert error, got %T", err)
} else if !reflect.DeepEqual(err.ErrorData(), c.expectData) {
t.Fatalf("Error data mismatch, want %v, got %v", c.expectData, err.ErrorData())
Expand Down Expand Up @@ -1023,7 +1023,7 @@ func TestSimulatedBackend_CallContractRevert(t *testing.T) {
t.Errorf("result from %v was not nil: %v", key, res)
}
if val != nil {
rerr, ok := err.(*revertError)
rerr, ok := err.(*blockchain.RevertError)
if !ok {
t.Errorf("expect revert error")
}
Expand Down
92 changes: 12 additions & 80 deletions api/api_ethereum.go
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ func (api *EthereumAPI) Call(ctx context.Context, args EthTransactionArgs, block
}

if len(result.Revert()) > 0 {
return nil, newRevertError(result)
return nil, blockchain.NewRevertError(result)
}
return result.Return(), result.Unwrap()
}
Expand Down Expand Up @@ -1620,68 +1620,31 @@ func EthDoCall(ctx context.Context, b Backend, args EthTransactionArgs, blockNrO
}

func EthDoEstimateGas(ctx context.Context, b Backend, args EthTransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap uint64) (hexutil.Uint64, error) {
// Binary search the gas requirement, as it may be higher than the amount used
var (
lo uint64 = params.TxGas - 1
hi uint64 = params.UpperGasLimit
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 {
// Ethereum set hi as gas ceiling of the block but,
// there is no actual gas limit in Klaytn, so we set it as params.UpperGasLimit.
hi = params.UpperGasLimit

var gasLimit uint64 = 0
if args.Gas != nil {
gasLimit = uint64(*args.Gas)
}

// Normalize the max fee per gas the call is willing to spend.
var feeCap *big.Int
var feeCap *big.Int = common.Big0
if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) {
return 0, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
} else if args.GasPrice != nil {
feeCap = args.GasPrice.ToInt()
} else if args.MaxFeePerGas != nil {
feeCap = args.MaxFeePerGas.ToInt()
} else {
feeCap = common.Big0
}
// recap the highest gas limit with account's available balance.
if feeCap.BitLen() != 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, feeCap)

// If the allowance is larger than maximum uint64, skip checking
if allowance.IsUint64() && hi > allowance.Uint64() {
transfer := args.Value
if transfer == nil {
transfer = new(hexutil.Big)
}
logger.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance,
"sent", transfer.ToInt(), "maxFeePerGas", feeCap, "fundable", allowance)
hi = allowance.Uint64()
}
}
// Recap the highest gas allowance with specified gascap.
if gasCap != 0 && hi > gasCap {
logger.Warn("Caller gas above allowance, capping", "requested", hi, "cap", gasCap)
hi = gasCap
state, _, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if err != nil {
return 0, err
}
cap = hi
balance := state.GetBalance(*args.From) // from can't be nil

executable := func(gas uint64) ( AC5E bool, *blockchain.ExecutionResult, error) {
args.Gas = (*hexutil.Uint64)(&gas)
Expand All @@ -1697,38 +1660,7 @@ func EthDoEstimateGas(ctx context.Context, b Backend, args EthTransactionArgs, b
return result.Failed(), result, nil
}

// Execute the binary search and hone in on an executable gas limit
for lo+1 < hi {
mid := (hi + lo) / 2
failed, _, err := executable(mid)
if err != nil {
return 0, err
}

if failed {
lo = mid
} else {
hi = mid
}
}
// Reject the transaction as invalid if it still fails at the highest allowance
if hi == cap {
failed, result, err := executable(hi)
if err != nil {
return 0, err
}
if failed {
if result != nil {
if len(result.Revert()) > 0 && result.VmExecutionStatus != types.ReceiptStatusErrOutOfGas {
return 0, newRevertError(result)
}
return 0, result.Unwrap()
}
// Otherwise, the specified gas cap is too low
return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap)
}
}
return hexutil.Uint64(hi), nil
return blockchain.DoEstimateGas(ctx, gasLimit, gasCap, args.Value.ToInt(), feeCap, balance, executable)
}

// checkTxFee is an internal function used to check whether the fee of
Expand Down
Loading
0