Skip to content

Commit

Permalink
[R4R]-[fee]feat: support estimateGas with or without setting gasPrice
Browse files Browse the repository at this point in the history
  • Loading branch information
Tri-stone committed Nov 8, 2023
1 parent ef00dd1 commit 1c93ab4
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 18 deletions.
25 changes: 17 additions & 8 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ type RunMode uint8
const (
CommitMode RunMode = iota
GasEstimationMode
GasEstimationWithSkipCheckBalanceMode
EthcallMode
)

Expand Down Expand Up @@ -273,13 +274,13 @@ func (st *StateTransition) buyGas() (*big.Int, error) {
mgval := new(big.Int).SetUint64(st.msg.GasLimit)
mgval = mgval.Mul(mgval, st.msg.GasPrice)
var l1Cost *big.Int
if st.msg.RunMode == GasEstimationMode {
if st.msg.RunMode == GasEstimationMode || st.msg.RunMode == GasEstimationWithSkipCheckBalanceMode {
st.CalculateRollupGasDataFromMessage()
}
if st.evm.Context.L1CostFunc != nil && st.msg.RunMode != EthcallMode {
l1Cost = st.evm.Context.L1CostFunc(st.evm.Context.BlockNumber.Uint64(), st.evm.Context.Time, st.msg.RollupDataGas, st.msg.IsDepositTx)
}
if l1Cost != nil && st.msg.RunMode == GasEstimationMode {
if l1Cost != nil && (st.msg.RunMode == GasEstimationMode || st.msg.RunMode == GasEstimationWithSkipCheckBalanceMode) {
mgval = mgval.Add(mgval, l1Cost)
}
balanceCheck := mgval
Expand All @@ -291,9 +292,10 @@ func (st *StateTransition) buyGas() (*big.Int, error) {
balanceCheck.Add(balanceCheck, l1Cost)
}
}

if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 {
return nil, fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
if st.msg.RunMode != GasEstimationWithSkipCheckBalanceMode && st.msg.RunMode != EthcallMode {
if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 {
return nil, fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
}
}

if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
Expand All @@ -302,7 +304,9 @@ func (st *StateTransition) buyGas() (*big.Int, error) {
st.gasRemaining += st.msg.GasLimit

st.initialGas = st.msg.GasLimit
st.state.SubBalance(st.msg.From, mgval)
if st.msg.RunMode != GasEstimationWithSkipCheckBalanceMode && st.msg.RunMode != EthcallMode {
st.state.SubBalance(st.msg.From, mgval)
}
return l1Cost, nil
}

Expand Down Expand Up @@ -468,8 +472,8 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) {
st.gasRemaining -= gas

var l1Gas uint64
if !st.msg.IsDepositTx && !st.msg.IsSystemTx {
if st.msg.GasPrice.Cmp(common.Big0) > 0 {
if !st.msg.IsDepositTx && !st.msg.IsSystemTx && st.msg.RunMode != GasEstimationWithSkipCheckBalanceMode {
if st.msg.GasPrice.Cmp(common.Big0) > 0 && l1Cost != nil {
l1Gas = new(big.Int).Div(l1Cost, st.msg.GasPrice).Uint64()
if st.msg.GasLimit < l1Gas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, l1Gas)
Expand Down Expand Up @@ -580,6 +584,11 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) {
}

func (st *StateTransition) refundGas(refundQuotient, tokenRatio uint64) {
if st.msg.RunMode == GasEstimationWithSkipCheckBalanceMode || st.msg.RunMode == EthcallMode {
st.gasRemaining = st.gasRemaining * tokenRatio
st.gp.AddGas(st.gasRemaining)
return
}
// Apply refund counter, capped to a refund quotient
refund := st.gasUsed() / refundQuotient
if refund > st.state.GetRefund() {
Expand Down
2 changes: 1 addition & 1 deletion eth/tracers/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -979,7 +979,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
config.BlockOverrides.Apply(&vmctx)
}
// Execute the trace
msg, err := args.ToMessage(api.backend.RPCGasCap(), block.BaseFee(), core.EthcallMode)
msg, err := args.ToMessage(api.backend.RPCGasCap(), block.BaseFee(), core.EthcallMode, args.GasPrice)
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions graphql/graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -1070,7 +1070,7 @@ func (b *Block) Call(ctx context.Context, args struct {
return nil, err
}
}
result, err := ethapi.DoCall(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, b.r.backend.RPCEVMTimeout(), b.r.backend.RPCGasCap(), core.EthcallMode)
result, err := ethapi.DoCall(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, b.r.backend.RPCEVMTimeout(), b.r.backend.RPCGasCap(), core.EthcallMode, args.Data.GasPrice)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1140,7 +1140,7 @@ func (p *Pending) Call(ctx context.Context, args struct {
Data ethapi.TransactionArgs
}) (*CallResult, error) {
pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
result, err := ethapi.DoCall(ctx, p.r.backend, args.Data, pendingBlockNr, nil, p.r.backend.RPCEVMTimeout(), p.r.backend.RPCGasCap(), core.EthcallMode)
result, err := ethapi.DoCall(ctx, p.r.backend, args.Data, pendingBlockNr, nil, p.r.backend.RPCEVMTimeout(), p.r.backend.RPCGasCap(), core.EthcallMode, args.Data.GasPrice)
if err != nil {
return nil, err
}
Expand Down
60 changes: 54 additions & 6 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1043,7 +1043,7 @@ func (diff *BlockOverrides) Apply(blockCtx *vm.BlockContext) {
}
}

func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64, runMode core.RunMode) (*core.ExecutionResult, error) {
func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64, runMode core.RunMode, gasPriceForEstimate *hexutil.Big) (*core.ExecutionResult, error) {
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())

state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
Expand All @@ -1066,7 +1066,7 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash
defer cancel()

// Get a new instance of the EVM.
msg, err := args.ToMessage(globalGasCap, header.BaseFee, runMode)
msg, err := args.ToMessage(globalGasCap, header.BaseFee, runMode, gasPriceForEstimate)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1153,7 +1153,7 @@ func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrO
}
}

result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap(), core.EthcallMode)
result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap(), core.EthcallMode, args.GasPrice)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1200,6 +1200,23 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
} else {
feeCap = common.Big0
}

runMode := core.GasEstimationMode
if args.GasPrice == nil && args.MaxFeePerGas == nil && args.MaxPriorityFeePerGas == nil {
runMode = core.GasEstimationWithSkipCheckBalanceMode
}

// Normalize the gasPrice used for estimateGas
gasPriceForEstimateGas, err := b.SuggestGasTipCap(ctx)
if err != nil {
return 0, errors.New("failed to get suggestGasTipCap")
}
if head := b.CurrentHeader(); head.BaseFee != nil {
gasPriceForEstimateGas.Add(gasPriceForEstimateGas, head.BaseFee)
}

log.Info("DoEstimateGas", "feeCap.BitLen", feeCap.BitLen(), "feeCap", feeCap.String())

// Recap the highest gas limit with account's available balance.
if feeCap.BitLen() != 0 {
state, _, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
Expand Down Expand Up @@ -1232,13 +1249,21 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
log.Warn("Caller gas above allowance, capping", "requested", hi, "cap", gasCap)
hi = gasCap
}
// Recap the highest gas allowance with suggested gasPrice.
if runMode == core.GasEstimationMode {
gasCap, err = calculateGasWithAllowance(ctx, b, args, blockNrOrHash, gasPriceForEstimateGas, gasCap)
if err != nil {
return 0, err
}
hi = gasCap
}
cap = hi

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

result, err := DoCall(ctx, b, args, blockNrOrHash, nil, 0, gasCap, core.GasEstimationMode)
result, err := DoCall(ctx, b, args, blockNrOrHash, nil, 0, gasCap, runMode, (*hexutil.Big)(gasPriceForEstimateGas))
if err != nil {
if errors.Is(err, core.ErrIntrinsicGas) {
return true, nil, nil // Special case, raise gas limit
Expand Down Expand Up @@ -1281,7 +1306,30 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap)
}
}
return hexutil.Uint64(hi), nil
return hexutil.Uint64(hi) * 150 / 100, nil
}

func calculateGasWithAllowance(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, gasPriceForEstimate *big.Int, gasCap uint64) (uint64, error) {
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, core.ErrInsufficientFundsForTransfer
}
available.Sub(available, args.Value.ToInt())
}

allowanceGas := new(big.Int).Div(available, gasPriceForEstimate)

if allowanceGas.Uint64() < gasCap {
return allowanceGas.Uint64(), nil
}

return gasCap, nil
}

// EstimateGas returns an estimate of the amount of gas needed to execute the
Expand Down Expand Up @@ -1652,7 +1700,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
statedb := db.Copy()
// Set the accesslist to the last al
args.AccessList = &accessList
msg, err := args.ToMessage(b.RPCGasCap(), header.BaseFee, core.EthcallMode)
msg, err := args.ToMessage(b.RPCGasCap(), header.BaseFee, core.EthcallMode, args.GasPrice)
if err != nil {
return nil, 0, nil, err
}
Expand Down
9 changes: 8 additions & 1 deletion internal/ethapi/transaction_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ func (args *TransactionArgs) setLondonFeeDefaults(ctx context.Context, head *typ
// ToMessage converts the transaction arguments to the Message type used by the
// core evm. This method is used in calls and traces that do not require a real
// live transaction.
func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int, runMode core.RunMode) (*core.Message, error) {
func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int, runMode core.RunMode, gasPriceForEstimate *hexutil.Big) (*core.Message, error) {
// Reject invalid combinations of pre- and post-1559 fee styles
if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) {
return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
Expand Down Expand Up @@ -255,6 +255,13 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int, ru
}
}
}

// use suggested gasPrice for estimateGas to calculate gasUsed
if runMode == core.GasEstimationMode {
gasPrice = gasPriceForEstimate.ToInt()
gasFeeCap = gasPriceForEstimate.ToInt()
}

value := new(big.Int)
if args.Value != nil {
value = args.Value.ToInt()
Expand Down

0 comments on commit 1c93ab4

Please sign in to comment.