mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-08 16:01:36 +00:00
core: apply fixes for 8037
This commit is contained in:
parent
a91cb20ae9
commit
0cb107dfd6
4 changed files with 66 additions and 38 deletions
|
|
@ -322,7 +322,7 @@ func (st *stateTransition) to() common.Address {
|
||||||
return *st.msg.To
|
return *st.msg.To
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *stateTransition) buyGas() error {
|
func (st *stateTransition) buyGas() (uint64, error) {
|
||||||
mgval := new(big.Int).SetUint64(st.msg.GasLimit)
|
mgval := new(big.Int).SetUint64(st.msg.GasLimit)
|
||||||
mgval.Mul(mgval, st.msg.GasPrice)
|
mgval.Mul(mgval, st.msg.GasPrice)
|
||||||
balanceCheck := new(big.Int).Set(mgval)
|
balanceCheck := new(big.Int).Set(mgval)
|
||||||
|
|
@ -346,13 +346,10 @@ func (st *stateTransition) buyGas() error {
|
||||||
}
|
}
|
||||||
balanceCheckU256, overflow := uint256.FromBig(balanceCheck)
|
balanceCheckU256, overflow := uint256.FromBig(balanceCheck)
|
||||||
if overflow {
|
if overflow {
|
||||||
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
|
return 0, fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
|
||||||
}
|
}
|
||||||
if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 {
|
if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 {
|
||||||
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
|
return 0, 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 {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil {
|
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil {
|
||||||
|
|
@ -368,23 +365,23 @@ func (st *stateTransition) buyGas() error {
|
||||||
st.gasRemaining = st.initialBudget.Copy()
|
st.gasRemaining = st.initialBudget.Copy()
|
||||||
mgvalU256, _ := uint256.FromBig(mgval)
|
mgvalU256, _ := uint256.FromBig(mgval)
|
||||||
st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy)
|
st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy)
|
||||||
return nil
|
return st.msg.GasLimit, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *stateTransition) preCheck() error {
|
func (st *stateTransition) preCheck() (uint64, error) {
|
||||||
// Only check transactions that are not fake
|
// Only check transactions that are not fake
|
||||||
msg := st.msg
|
msg := st.msg
|
||||||
if !msg.SkipNonceChecks {
|
if !msg.SkipNonceChecks {
|
||||||
// Make sure this transaction's nonce is correct.
|
// Make sure this transaction's nonce is correct.
|
||||||
stNonce := st.state.GetNonce(msg.From)
|
stNonce := st.state.GetNonce(msg.From)
|
||||||
if msgNonce := msg.Nonce; stNonce < msgNonce {
|
if msgNonce := msg.Nonce; stNonce < msgNonce {
|
||||||
return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooHigh,
|
return 0, fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooHigh,
|
||||||
msg.From.Hex(), msgNonce, stNonce)
|
msg.From.Hex(), msgNonce, stNonce)
|
||||||
} else if stNonce > msgNonce {
|
} else if stNonce > msgNonce {
|
||||||
return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooLow,
|
return 0, fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooLow,
|
||||||
msg.From.Hex(), msgNonce, stNonce)
|
msg.From.Hex(), msgNonce, stNonce)
|
||||||
} else if stNonce+1 < stNonce {
|
} else if stNonce+1 < stNonce {
|
||||||
return fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax,
|
return 0, fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax,
|
||||||
msg.From.Hex(), stNonce)
|
msg.From.Hex(), stNonce)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -393,13 +390,13 @@ func (st *stateTransition) preCheck() error {
|
||||||
if !msg.SkipTransactionChecks {
|
if !msg.SkipTransactionChecks {
|
||||||
// Verify tx gas limit does not exceed EIP-7825 cap.
|
// Verify tx gas limit does not exceed EIP-7825 cap.
|
||||||
if !isAmsterdam && isOsaka && msg.GasLimit > params.MaxTxGas {
|
if !isAmsterdam && isOsaka && msg.GasLimit > params.MaxTxGas {
|
||||||
return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit)
|
return 0, fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit)
|
||||||
}
|
}
|
||||||
// Make sure the sender is an EOA
|
// Make sure the sender is an EOA
|
||||||
code := st.state.GetCode(msg.From)
|
code := st.state.GetCode(msg.From)
|
||||||
_, delegated := types.ParseDelegation(code)
|
_, delegated := types.ParseDelegation(code)
|
||||||
if len(code) > 0 && !delegated {
|
if len(code) > 0 && !delegated {
|
||||||
return fmt.Errorf("%w: address %v, len(code): %d", ErrSenderNoEOA, msg.From.Hex(), len(code))
|
return 0, fmt.Errorf("%w: address %v, len(code): %d", ErrSenderNoEOA, msg.From.Hex(), len(code))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Make sure that transaction gasFeeCap is greater than the baseFee (post london)
|
// Make sure that transaction gasFeeCap is greater than the baseFee (post london)
|
||||||
|
|
@ -408,21 +405,21 @@ func (st *stateTransition) preCheck() error {
|
||||||
skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0
|
skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0
|
||||||
if !skipCheck {
|
if !skipCheck {
|
||||||
if l := msg.GasFeeCap.BitLen(); l > 256 {
|
if l := msg.GasFeeCap.BitLen(); l > 256 {
|
||||||
return fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
|
return 0, fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
|
||||||
msg.From.Hex(), l)
|
msg.From.Hex(), l)
|
||||||
}
|
}
|
||||||
if l := msg.GasTipCap.BitLen(); l > 256 {
|
if l := msg.GasTipCap.BitLen(); l > 256 {
|
||||||
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh,
|
return 0, fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh,
|
||||||
msg.From.Hex(), l)
|
msg.From.Hex(), l)
|
||||||
}
|
}
|
||||||
if msg.GasFeeCap.Cmp(msg.GasTipCap) < 0 {
|
if msg.GasFeeCap.Cmp(msg.GasTipCap) < 0 {
|
||||||
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap,
|
return 0, fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap,
|
||||||
msg.From.Hex(), msg.GasTipCap, msg.GasFeeCap)
|
msg.From.Hex(), msg.GasTipCap, msg.GasFeeCap)
|
||||||
}
|
}
|
||||||
// This will panic if baseFee is nil, but basefee presence is verified
|
// This will panic if baseFee is nil, but basefee presence is verified
|
||||||
// as part of header validation.
|
// as part of header validation.
|
||||||
if msg.GasFeeCap.Cmp(st.evm.Context.BaseFee) < 0 {
|
if msg.GasFeeCap.Cmp(st.evm.Context.BaseFee) < 0 {
|
||||||
return fmt.Errorf("%w: address %v, maxFeePerGas: %s, baseFee: %s", ErrFeeCapTooLow,
|
return 0, fmt.Errorf("%w: address %v, maxFeePerGas: %s, baseFee: %s", ErrFeeCapTooLow,
|
||||||
msg.From.Hex(), msg.GasFeeCap, st.evm.Context.BaseFee)
|
msg.From.Hex(), msg.GasFeeCap, st.evm.Context.BaseFee)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -433,17 +430,17 @@ func (st *stateTransition) preCheck() error {
|
||||||
// has it as a non-nillable value, so any msg derived from blob transaction has it non-nil.
|
// has it as a non-nillable value, so any msg derived from blob transaction has it non-nil.
|
||||||
// However, messages created through RPC (eth_call) don't have this restriction.
|
// However, messages created through RPC (eth_call) don't have this restriction.
|
||||||
if msg.To == nil {
|
if msg.To == nil {
|
||||||
return ErrBlobTxCreate
|
return 0, ErrBlobTxCreate
|
||||||
}
|
}
|
||||||
if len(msg.BlobHashes) == 0 {
|
if len(msg.BlobHashes) == 0 {
|
||||||
return ErrMissingBlobHashes
|
return 0, ErrMissingBlobHashes
|
||||||
}
|
}
|
||||||
if isOsaka && len(msg.BlobHashes) > params.BlobTxMaxBlobs {
|
if isOsaka && len(msg.BlobHashes) > params.BlobTxMaxBlobs {
|
||||||
return ErrTooManyBlobs
|
return 0, ErrTooManyBlobs
|
||||||
}
|
}
|
||||||
for i, hash := range msg.BlobHashes {
|
for i, hash := range msg.BlobHashes {
|
||||||
if !kzg4844.IsValidVersionedHash(hash[:]) {
|
if !kzg4844.IsValidVersionedHash(hash[:]) {
|
||||||
return fmt.Errorf("blob %d has invalid hash version", i)
|
return 0, fmt.Errorf("blob %d has invalid hash version", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -456,7 +453,7 @@ func (st *stateTransition) preCheck() error {
|
||||||
// This will panic if blobBaseFee is nil, but blobBaseFee presence
|
// This will panic if blobBaseFee is nil, but blobBaseFee presence
|
||||||
// is verified as part of header validation.
|
// is verified as part of header validation.
|
||||||
if msg.BlobGasFeeCap.Cmp(st.evm.Context.BlobBaseFee) < 0 {
|
if msg.BlobGasFeeCap.Cmp(st.evm.Context.BlobBaseFee) < 0 {
|
||||||
return fmt.Errorf("%w: address %v blobGasFeeCap: %v, blobBaseFee: %v", ErrBlobFeeCapTooLow,
|
return 0, fmt.Errorf("%w: address %v blobGasFeeCap: %v, blobBaseFee: %v", ErrBlobFeeCapTooLow,
|
||||||
msg.From.Hex(), msg.BlobGasFeeCap, st.evm.Context.BlobBaseFee)
|
msg.From.Hex(), msg.BlobGasFeeCap, st.evm.Context.BlobBaseFee)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -465,10 +462,10 @@ func (st *stateTransition) preCheck() error {
|
||||||
// Check that EIP-7702 authorization list signatures are well formed.
|
// Check that EIP-7702 authorization list signatures are well formed.
|
||||||
if msg.SetCodeAuthorizations != nil {
|
if msg.SetCodeAuthorizations != nil {
|
||||||
if msg.To == nil {
|
if msg.To == nil {
|
||||||
return fmt.Errorf("%w (sender %v)", ErrSetCodeTxCreate, msg.From)
|
return 0, fmt.Errorf("%w (sender %v)", ErrSetCodeTxCreate, msg.From)
|
||||||
}
|
}
|
||||||
if len(msg.SetCodeAuthorizations) == 0 {
|
if len(msg.SetCodeAuthorizations) == 0 {
|
||||||
return fmt.Errorf("%w (sender %v)", ErrEmptyAuthList, msg.From)
|
return 0, fmt.Errorf("%w (sender %v)", ErrEmptyAuthList, msg.From)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return st.buyGas()
|
return st.buyGas()
|
||||||
|
|
@ -496,7 +493,8 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
// 6. caller has enough balance to cover asset transfer for **topmost** call
|
// 6. caller has enough balance to cover asset transfer for **topmost** call
|
||||||
|
|
||||||
// Check clauses 1-3, buy gas if everything is correct
|
// Check clauses 1-3, buy gas if everything is correct
|
||||||
if err := st.preCheck(); err != nil {
|
gas, err := st.preCheck()
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -506,12 +504,33 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
contractCreation = msg.To == nil
|
contractCreation = msg.To == nil
|
||||||
floorDataGas uint64
|
floorDataGas uint64
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if !rules.IsAmsterdam {
|
||||||
|
if err := st.gp.SubGas(gas); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check clauses 4-5, subtract intrinsic gas if everything is correct
|
// Check clauses 4-5, subtract intrinsic gas if everything is correct
|
||||||
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules, st.evm.Context.CostPerGasByte)
|
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules, st.evm.Context.CostPerGasByte)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regular gas check for block inclusion post-amsterdam includes state gas.
|
||||||
|
if rules.IsAmsterdam {
|
||||||
|
subGasAmount := msg.GasLimit
|
||||||
|
if subGasAmount > cost.StateGas {
|
||||||
|
subGasAmount -= cost.StateGas
|
||||||
|
} else {
|
||||||
|
subGasAmount = 0
|
||||||
|
}
|
||||||
|
subGasAmount = min(subGasAmount, params.MaxTxGas)
|
||||||
|
if err := st.gp.SubGas(subGasAmount); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Compute the floor data cost (EIP-7623), needed for both Prague and Amsterdam validation.
|
// Compute the floor data cost (EIP-7623), needed for both Prague and Amsterdam validation.
|
||||||
if rules.IsPrague {
|
if rules.IsPrague {
|
||||||
floorDataGas, err = FloorDataGas(rules, msg.Data, msg.AccessList)
|
floorDataGas, err = FloorDataGas(rules, msg.Data, msg.AccessList)
|
||||||
|
|
|
||||||
|
|
@ -141,22 +141,21 @@ func (c *Contract) UseGas(cost GasCosts, logger *tracing.Hooks, reason tracing.G
|
||||||
|
|
||||||
// RefundGas refunds gas to the contract.
|
// RefundGas refunds gas to the contract.
|
||||||
func (c *Contract) RefundGas(err error, initialRegularGasUsed uint64, gas GasBudget, gasUsed GasUsed, logger *tracing.Hooks, reason tracing.GasChangeReason) {
|
func (c *Contract) RefundGas(err error, initialRegularGasUsed uint64, gas GasBudget, gasUsed GasUsed, logger *tracing.Hooks, reason tracing.GasChangeReason) {
|
||||||
// If the preceding call errored, return the child's state-gas reservoir
|
|
||||||
// and any net state-gas consumption to the parent. A negative
|
|
||||||
// gasUsed.StateGas (an unabsorbed SSTORE 0→x→0 refund) is an inflation
|
|
||||||
// to undo: the matching state mutation was reverted with the child, so
|
|
||||||
// the refund must not leak back to the parent.
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if gasUsed.StateGas >= 0 {
|
if gasUsed.StateGas > 0 {
|
||||||
gas.StateGas += uint64(gasUsed.StateGas)
|
gas.StateGas += uint64(gasUsed.StateGas)
|
||||||
} else if undo := uint64(-gasUsed.StateGas); gas.StateGas >= undo {
|
|
||||||
gas.StateGas -= undo
|
|
||||||
} else {
|
|
||||||
gas.StateGas = 0
|
|
||||||
}
|
}
|
||||||
gasUsed.StateGas = 0
|
gasUsed.StateGas = 0
|
||||||
|
if gas.StateGasRefund > 0 {
|
||||||
|
if gas.StateGas >= gas.StateGasRefund {
|
||||||
|
gas.StateGas -= gas.StateGasRefund
|
||||||
|
} else {
|
||||||
|
gas.StateGas = 0
|
||||||
|
}
|
||||||
|
gas.StateGasRefund = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if gas.RegularGas == 0 && gas.StateGas == 0 && gasUsed.StateGas == 0 && gasUsed.RegularGas == 0 {
|
if gas.RegularGas == 0 && gas.StateGas == 0 && gasUsed.StateGas == 0 && gasUsed.RegularGas == 0 && gas.StateGasRefund == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored {
|
if logger != nil && logger.OnGasChange != nil && reason != tracing.GasChangeIgnored {
|
||||||
|
|
@ -164,6 +163,9 @@ func (c *Contract) RefundGas(err error, initialRegularGasUsed uint64, gas GasBud
|
||||||
}
|
}
|
||||||
c.Gas.RegularGas += gas.RegularGas
|
c.Gas.RegularGas += gas.RegularGas
|
||||||
c.Gas.StateGas = gas.StateGas
|
c.Gas.StateGas = gas.StateGas
|
||||||
|
// Propagate the child's applied inline refund so a later ancestor
|
||||||
|
// error can still undo the reservoir inflation.
|
||||||
|
c.Gas.StateGasRefund += gas.StateGasRefund
|
||||||
c.GasUsed.StateGas += gasUsed.StateGas
|
c.GasUsed.StateGas += gasUsed.StateGas
|
||||||
c.GasUsed.RegularGas = initialRegularGasUsed + gasUsed.RegularGas
|
c.GasUsed.RegularGas = initialRegularGasUsed + gasUsed.RegularGas
|
||||||
}
|
}
|
||||||
|
|
@ -172,6 +174,7 @@ func (c *Contract) RefundGas(err error, initialRegularGasUsed uint64, gas GasBud
|
||||||
func (c *Contract) RefundCreateStateGas(refund uint64) {
|
func (c *Contract) RefundCreateStateGas(refund uint64) {
|
||||||
if refund > 0 {
|
if refund > 0 {
|
||||||
c.Gas.StateGas += refund
|
c.Gas.StateGas += refund
|
||||||
|
c.Gas.StateGasRefund += refund
|
||||||
c.GasUsed.StateGas -= int64(refund)
|
c.GasUsed.StateGas -= int64(refund)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -699,9 +699,11 @@ func gasSStore8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
|
||||||
if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1)
|
if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1)
|
||||||
// EIP-8037 point (2): refund state gas directly to the reservoir
|
// EIP-8037 point (2): refund state gas directly to the reservoir
|
||||||
// at the SSTORE restoration point (0→x→0 in same tx); not to the
|
// at the SSTORE restoration point (0→x→0 in same tx); not to the
|
||||||
// refund counter, which is capped at gas_used/5.
|
// refund counter, which is capped at gas_used/5. StateGasRefund
|
||||||
|
// tracks the reservoir inflation so a frame error undoes it.
|
||||||
stateRefund := params.StorageCreationSize * evm.Context.CostPerGasByte
|
stateRefund := params.StorageCreationSize * evm.Context.CostPerGasByte
|
||||||
contract.Gas.StateGas += stateRefund
|
contract.Gas.StateGas += stateRefund
|
||||||
|
contract.Gas.StateGasRefund += stateRefund
|
||||||
contract.GasUsed.StateGas -= int64(stateRefund)
|
contract.GasUsed.StateGas -= int64(stateRefund)
|
||||||
// Regular portion of the refund still goes through the refund counter.
|
// Regular portion of the refund still goes through the refund counter.
|
||||||
evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929)
|
evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ package vm
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// GasUsed is the per-frame accumulator for gas consumption.
|
// GasUsed is the per-frame accumulator for gas consumption.
|
||||||
// StateGas is signed because of 0 -> X -> 0 SSTORE refunds.
|
// StateGas is signed, because it can be negative in a 0 -> x -> 0 scenario.
|
||||||
type GasUsed struct {
|
type GasUsed struct {
|
||||||
RegularGas uint64
|
RegularGas uint64
|
||||||
StateGas int64
|
StateGas int64
|
||||||
|
|
@ -55,6 +55,10 @@ func (g GasCosts) String() string {
|
||||||
type GasBudget struct {
|
type GasBudget struct {
|
||||||
RegularGas uint64 // The leftover gas for execution and state gas usage
|
RegularGas uint64 // The leftover gas for execution and state gas usage
|
||||||
StateGas uint64 // The state gas reservoir
|
StateGas uint64 // The state gas reservoir
|
||||||
|
|
||||||
|
// Tracks the gas refunds in this call frame. Needed so we can
|
||||||
|
// revert the refunds if the call frame reverts.
|
||||||
|
StateGasRefund uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGasBudgetReg creates a GasBudget with the given initial regular gas allowance.
|
// NewGasBudgetReg creates a GasBudget with the given initial regular gas allowance.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue