diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index fb7206eb19..02d3a54189 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -226,7 +226,12 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, rejectedTxs = append(rejectedTxs, &rejectedTx{i, errMsg}) continue } - msg, err := core.TransactionToMessage(tx, signer, pre.Env.BaseFee, vmContext.ExchangeRates) + // NOTE: we can't provide exchange rates + // for fee-currencies here, since those are dynamically changing + // based on the oracle's exchange rates. + // When a Celo transaction with specified fee-currency is validated with this tool, + // this will thus result in a ErrUnregisteredFeeCurrency error for now. + msg, err := core.TransactionToMessage(tx, signer, pre.Env.BaseFee, vmContext.FeeCurrencyContext.ExchangeRates) if err != nil { log.Warn("rejected tx", "index", i, "hash", tx.Hash(), "error", err) rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()}) diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go index 891b5a597a..9e63988d37 100644 --- a/cmd/evm/internal/t8ntool/transaction.go +++ b/cmd/evm/internal/t8ntool/transaction.go @@ -133,8 +133,14 @@ func Transaction(ctx *cli.Context) error { r.Address = sender } // Check intrinsic gas + // NOTE: we can't provide specific intrinsic gas costs + // for fee-currencies here, since those are written to the + // FeeCurrencyDirectory contract and are chain-specific. + // When a Celo transaction with specified fee-currency is validated with this tool, + // this will thus result in a ErrUnregisteredFeeCurrency error for now. + var feeIntrinsic common.IntrinsicGasCosts if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, - chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int)), chainConfig.IsShanghai(new(big.Int), 0), tx.FeeCurrency()); err != nil { + chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int)), chainConfig.IsShanghai(new(big.Int), 0), tx.FeeCurrency(), feeIntrinsic); err != nil { r.Error = err results = append(results, r) continue diff --git a/common/celo_types.go b/common/celo_types.go index 711dedca42..390cc13138 100644 --- a/common/celo_types.go +++ b/common/celo_types.go @@ -9,6 +9,37 @@ var ( ) type ExchangeRates = map[Address]*big.Rat +type IntrinsicGasCosts = map[Address]uint64 + +type FeeCurrencyContext struct { + ExchangeRates ExchangeRates + IntrinsicGasCosts IntrinsicGasCosts +} + +func MaxAllowedIntrinsicGasCost(i IntrinsicGasCosts, feeCurrency *Address) (uint64, bool) { + intrinsicGas, ok := CurrencyIntrinsicGasCost(i, feeCurrency) + if !ok { + return 0, false + } + // Allow the contract to overshoot 2 times the deducted intrinsic gas + // during execution. + // If the feeCurrency is nil, then the max allowed intrinsic gas cost + // is 0 (i.e. not allowed) for a fee-currency specific EVM call within the STF. + return intrinsicGas * 3, true +} + +func CurrencyIntrinsicGasCost(i IntrinsicGasCosts, feeCurrency *Address) (uint64, bool) { + // the additional intrinsic gas cost for a non fee-currency + // transaction is 0 + if feeCurrency == nil { + return 0, true + } + gasCost, ok := i[*feeCurrency] + if !ok { + return 0, false + } + return gasCost, true +} func CurrencyAllowlist(exchangeRates ExchangeRates) []Address { addrs := make([]Address, len(exchangeRates)) diff --git a/contracts/celo_backend.go b/contracts/celo_backend.go index aa2ec22fa0..86c8cf78ee 100644 --- a/contracts/celo_backend.go +++ b/contracts/celo_backend.go @@ -61,14 +61,19 @@ func (b *CeloBackend) CallContract(ctx context.Context, call ethereum.CallMsg, b // Get a vm.EVM object of you can't use the abi wrapper via the ContractCaller interface // This is usually the case when executing functions that modify state. -func (b *CeloBackend) NewEVM() *vm.EVM { - blockCtx := vm.BlockContext{BlockNumber: new(big.Int), Time: 0, +func (b *CeloBackend) NewEVM(feeCurrencyContext *common.FeeCurrencyContext) *vm.EVM { + blockCtx := vm.BlockContext{ + BlockNumber: new(big.Int), + Time: 0, Transfer: func(state vm.StateDB, from common.Address, to common.Address, value *uint256.Int) { if value.Cmp(common.U2560) != 0 { panic("Non-zero transfers not implemented, yet.") } }, } + if feeCurrencyContext != nil { + blockCtx.FeeCurrencyContext = *feeCurrencyContext + } txCtx := vm.TxContext{} vmConfig := vm.Config{} return vm.NewEVM(blockCtx, txCtx, b.State, b.ChainConfig, vmConfig) diff --git a/contracts/fee_currencies.go b/contracts/fee_currencies.go index 7d35367f90..be60b19222 100644 --- a/contracts/fee_currencies.go +++ b/contracts/fee_currencies.go @@ -1,12 +1,15 @@ package contracts import ( + "errors" "fmt" + "math" "math/big" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/exchange" "github.com/ethereum/go-ethereum/contracts/addresses" "github.com/ethereum/go-ethereum/contracts/celo/abigen" "github.com/ethereum/go-ethereum/core/types" @@ -14,15 +17,6 @@ import ( "github.com/ethereum/go-ethereum/log" ) -const ( - Thousand = 1000 - - // Default intrinsic gas cost of transactions paying for gas in alternative currencies. - // Calculated to estimate 1 balance read, 1 debit, and 4 credit transactions. - IntrinsicGasForAlternativeFeeCurrency uint64 = 50 * Thousand - maxAllowedGasForDebitAndCredit uint64 = 3 * IntrinsicGasForAlternativeFeeCurrency -) - var feeCurrencyABI *abi.ABI func init() { @@ -34,31 +28,42 @@ func init() { } // Returns nil if debit is possible, used in tx pool validation -func TryDebitFees(tx *types.Transaction, from common.Address, backend *CeloBackend) error { +func TryDebitFees(tx *types.Transaction, from common.Address, backend *CeloBackend, feeContext common.FeeCurrencyContext) error { amount := new(big.Int).SetUint64(tx.Gas()) amount.Mul(amount, tx.GasFeeCap()) snapshot := backend.State.Snapshot() - err := DebitFees(backend.NewEVM(), tx.FeeCurrency(), from, amount) + evm := backend.NewEVM(&feeContext) + _, err := DebitFees(evm, tx.FeeCurrency(), from, amount) backend.State.RevertToSnapshot(snapshot) return err } // Debits transaction fees from the transaction sender and stores them in the temporary address -func DebitFees(evm *vm.EVM, feeCurrency *common.Address, address common.Address, amount *big.Int) error { +func DebitFees(evm *vm.EVM, feeCurrency *common.Address, address common.Address, amount *big.Int) (uint64, error) { if amount.Cmp(big.NewInt(0)) == 0 { - return nil + return 0, nil + } + + maxIntrinsicGasCost, ok := common.MaxAllowedIntrinsicGasCost(evm.Context.FeeCurrencyContext.IntrinsicGasCosts, feeCurrency) + if !ok { + return 0, fmt.Errorf("%w: %x", exchange.ErrUnregisteredFeeCurrency, feeCurrency) } leftoverGas, err := evm.CallWithABI( - feeCurrencyABI, "debitGasFees", *feeCurrency, maxAllowedGasForDebitAndCredit, + feeCurrencyABI, "debitGasFees", *feeCurrency, maxIntrinsicGasCost, // debitGasFees(address from, uint256 value) parameters address, amount, ) - gasUsed := maxAllowedGasForDebitAndCredit - leftoverGas - evm.Context.GasUsedForDebit = gasUsed + if errors.Is(err, vm.ErrOutOfGas) { + // This basically is a configuration / contract error, since + // the contract itself used way more gas than was expected (including grace limit) + return 0, fmt.Errorf("surpassed maximum allowed intrinsic gas for fee currency: %w", err) + } + + gasUsed := maxIntrinsicGasCost - leftoverGas log.Trace("DebitFees called", "feeCurrency", *feeCurrency, "gasUsed", gasUsed) - return err + return gasUsed, err } // Credits fees to the respective parties @@ -71,6 +76,7 @@ func CreditFees( feeCurrency *common.Address, txSender, tipReceiver, baseFeeReceiver, l1DataFeeReceiver common.Address, refund, feeTip, baseFee, l1DataFee *big.Int, + gasUsedDebit uint64, ) error { // Our old `creditGasFees` function does not accept an l1DataFee and // the fee currencies do not implement the new interface yet. Since tip @@ -85,8 +91,12 @@ func CreditFees( if tipReceiver.Cmp(common.ZeroAddress) == 0 { tipReceiver = baseFeeReceiver } + maxAllowedGasForDebitAndCredit, ok := common.MaxAllowedIntrinsicGasCost(evm.Context.FeeCurrencyContext.IntrinsicGasCosts, feeCurrency) + if !ok { + return fmt.Errorf("%w: %x", exchange.ErrUnregisteredFeeCurrency, feeCurrency) + } - maxAllowedGasForCredit := maxAllowedGasForDebitAndCredit - evm.Context.GasUsedForDebit + maxAllowedGasForCredit := maxAllowedGasForDebitAndCredit - gasUsedDebit leftoverGas, err := evm.CallWithABI( feeCurrencyABI, "creditGasFees", *feeCurrency, maxAllowedGasForCredit, // function creditGasFees( @@ -101,43 +111,72 @@ func CreditFees( // ) txSender, tipReceiver, common.ZeroAddress, baseFeeReceiver, refund, feeTip, common.Big0, baseFee, ) + if errors.Is(err, vm.ErrOutOfGas) { + // This is a configuration / contract error, since + // the contract itself used way more gas than was expected (including grace limit) + return fmt.Errorf("surpassed maximum allowed intrinsic gas for fee currency: %w", err) + } gasUsed := maxAllowedGasForCredit - leftoverGas log.Trace("CreditFees called", "feeCurrency", *feeCurrency, "gasUsed", gasUsed) - gasUsedForDebitAndCredit := evm.Context.GasUsedForDebit + gasUsed - if gasUsedForDebitAndCredit > IntrinsicGasForAlternativeFeeCurrency { - log.Info("Gas usage for debit+credit exceeds intrinsic gas!", "gasUsed", gasUsedForDebitAndCredit, "intrinsicGas", IntrinsicGasForAlternativeFeeCurrency, "feeCurrency", feeCurrency) + intrinsicGas, ok := common.CurrencyIntrinsicGasCost(evm.Context.FeeCurrencyContext.IntrinsicGasCosts, feeCurrency) + if !ok { + // this will never happen + return fmt.Errorf("%w: %x", exchange.ErrUnregisteredFeeCurrency, feeCurrency) + } + gasUsedForDebitAndCredit := gasUsedDebit + gasUsed + if gasUsedForDebitAndCredit > intrinsicGas { + log.Info("Gas usage for debit+credit exceeds intrinsic gas!", "gasUsed", gasUsedForDebitAndCredit, "intrinsicGas", intrinsicGas, "feeCurrency", feeCurrency) } return err } -// GetExchangeRates returns the exchange rates for all gas currencies from CELO +func GetRegisteredCurrencies(caller *abigen.FeeCurrencyDirectoryCaller) ([]common.Address, error) { + currencies, err := caller.GetCurrencies(&bind.CallOpts{}) + if err != nil { + return currencies, fmt.Errorf("failed to get registered tokens: %w", err) + } + return currencies, nil +} + +// GetExchangeRates returns the exchange rates for the provided gas currencies func GetExchangeRates(caller *CeloBackend) (common.ExchangeRates, error) { - exchangeRates := map[common.Address]*big.Rat{} directory, err := abigen.NewFeeCurrencyDirectoryCaller(addresses.GetAddresses(caller.ChainConfig.ChainID).FeeCurrencyDirectory, caller) if err != nil { return common.ExchangeRates{}, fmt.Errorf("failed to access FeeCurrencyDirectory: %w", err) } - - registeredTokens, err := directory.GetCurrencies(&bind.CallOpts{}) + currencies, err := GetRegisteredCurrencies(directory) if err != nil { - return exchangeRates, fmt.Errorf("Failed to get whitelisted tokens: %w", err) + return common.ExchangeRates{}, err } - for _, tokenAddress := range registeredTokens { - rate, err := directory.GetExchangeRate(&bind.CallOpts{}, tokenAddress) - if err != nil { - log.Error("Failed to get medianRate for gas currency!", "err", err, "tokenAddress", tokenAddress.Hex()) - continue - } - if rate.Numerator.Sign() <= 0 || rate.Denominator.Sign() <= 0 { - log.Error("Bad exchange rate for fee currency", "tokenAddress", tokenAddress.Hex(), "numerator", rate.Numerator, "denominator", rate.Denominator) - continue - } - exchangeRates[tokenAddress] = new(big.Rat).SetFrac(rate.Numerator, rate.Denominator) + return getExchangeRatesForTokens(directory, currencies) +} + +// GetFeeCurrencyContext returns the fee currency block context for all registered gas currencies from CELO +func GetFeeCurrencyContext(caller *CeloBackend) (common.FeeCurrencyContext, error) { + var feeContext common.FeeCurrencyContext + directory, err := abigen.NewFeeCurrencyDirectoryCaller(addresses.GetAddresses(caller.ChainConfig.ChainID).FeeCurrencyDirectory, caller) + if err != nil { + return feeContext, fmt.Errorf("failed to access FeeCurrencyDirectory: %w", err) } - return exchangeRates, nil + currencies, err := GetRegisteredCurrencies(directory) + if err != nil { + return feeContext, err + } + rates, err := getExchangeRatesForTokens(directory, currencies) + if err != nil { + return feeContext, err + } + intrinsicGas, err := getIntrinsicGasForTokens(directory, currencies) + if err != nil { + return feeContext, err + } + return common.FeeCurrencyContext{ + ExchangeRates: rates, + IntrinsicGasCosts: intrinsicGas, + }, nil } // GetBalanceERC20 returns an account's balance on a given ERC20 currency @@ -167,3 +206,41 @@ func GetFeeBalance(backend *CeloBackend, account common.Address, feeCurrency *co } return balance } + +// getIntrinsicGasForTokens returns the intrinsic gas costs for the provided gas currencies from CELO +func getIntrinsicGasForTokens(caller *abigen.FeeCurrencyDirectoryCaller, tokens []common.Address) (common.IntrinsicGasCosts, error) { + gasCosts := common.IntrinsicGasCosts{} + for _, tokenAddress := range tokens { + config, err := caller.GetCurrencyConfig(&bind.CallOpts{}, tokenAddress) + if err != nil { + log.Error("Failed to get intrinsic gas cost for gas currency!", "err", err, "tokenAddress", tokenAddress.Hex()) + continue + } + if !config.IntrinsicGas.IsUint64() { + log.Error("Intrinsic gas cost exceeds MaxUint64 limit, capping at MaxUint64", "err", err, "tokenAddress", tokenAddress.Hex()) + gasCosts[tokenAddress] = math.MaxUint64 + } else { + gasCosts[tokenAddress] = config.IntrinsicGas.Uint64() + } + } + return gasCosts, nil +} + +// getExchangeRatesForTokens returns the exchange rates for the provided gas currencies from CELO +func getExchangeRatesForTokens(caller *abigen.FeeCurrencyDirectoryCaller, tokens []common.Address) (common.ExchangeRates, error) { + exchangeRates := common.ExchangeRates{} + for _, tokenAddress := range tokens { + rate, err := caller.GetExchangeRate(&bind.CallOpts{}, tokenAddress) + if err != nil { + log.Error("Failed to get medianRate for gas currency!", "err", err, "tokenAddress", tokenAddress.Hex()) + continue + } + if rate.Numerator.Sign() <= 0 || rate.Denominator.Sign() <= 0 { + log.Error("Bad exchange rate for fee currency", "tokenAddress", tokenAddress.Hex(), "numerator", rate.Numerator, "denominator", rate.Denominator) + continue + } + exchangeRates[tokenAddress] = new(big.Rat).SetFrac(rate.Numerator, rate.Denominator) + } + + return exchangeRates, nil +} diff --git a/core/bench_test.go b/core/bench_test.go index 0ad9227f5d..132c8736e5 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -90,7 +90,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) { data := make([]byte, nbytes) return func(i int, gen *BlockGen) { toaddr := common.Address{} - gas, _ := IntrinsicGas(data, nil, false, false, false, false, nil) + gas, _ := IntrinsicGas(data, nil, false, false, false, false, nil, common.IntrinsicGasCosts{}) signer := gen.Signer() gasPrice := big.NewInt(0) if gen.header.BaseFee != nil { diff --git a/core/celo_evm.go b/core/celo_evm.go index 38b8b3a5b6..69a5c994dd 100644 --- a/core/celo_evm.go +++ b/core/celo_evm.go @@ -9,8 +9,19 @@ import ( "github.com/ethereum/go-ethereum/params" ) +// XXYXX func setCeloFieldsInBlockContext(blockContext *vm.BlockContext, header *types.Header, config *params.ChainConfig, statedb vm.StateDB) { - blockContext.ExchangeRates = GetExchangeRates(header, config, statedb) + if !config.IsCel2(header.Time) { + return + } + + caller := &contracts.CeloBackend{ChainConfig: config, State: statedb} + + feeCurrencyContext, err := contracts.GetFeeCurrencyContext(caller) + if err != nil { + log.Error("Error fetching exchange rates!", "err", err) + } + blockContext.FeeCurrencyContext = feeCurrencyContext } func GetExchangeRates(header *types.Header, config *params.ChainConfig, statedb vm.StateDB) common.ExchangeRates { @@ -20,10 +31,9 @@ func GetExchangeRates(header *types.Header, config *params.ChainConfig, statedb caller := &contracts.CeloBackend{ChainConfig: config, State: statedb} - // Add fee currency exchange rates - exchangeRates, err := contracts.GetExchangeRates(caller) + feeCurrencyContext, err := contracts.GetFeeCurrencyContext(caller) if err != nil { log.Error("Error fetching exchange rates!", "err", err) } - return exchangeRates + return feeCurrencyContext.ExchangeRates } diff --git a/core/celo_state_transition.go b/core/celo_state_transition.go index 29f789b836..b17500c615 100644 --- a/core/celo_state_transition.go +++ b/core/celo_state_transition.go @@ -49,7 +49,9 @@ func (st *StateTransition) subFees(effectiveFee *big.Int) (err error) { st.state.SubBalance(st.msg.From, effectiveFeeU256, tracing.BalanceDecreaseGasBuy) return nil } else { - return contracts.DebitFees(st.evm, st.msg.FeeCurrency, st.msg.From, effectiveFee) + gasUsedDebit, err := contracts.DebitFees(st.evm, st.msg.FeeCurrency, st.msg.From, effectiveFee) + st.feeCurrencyGasUsed += gasUsedDebit + return err } } @@ -121,9 +123,22 @@ func (st *StateTransition) distributeTxFees() error { } } else { if l1Cost != nil { - l1Cost, _ = exchange.ConvertCeloToCurrency(st.evm.Context.ExchangeRates, feeCurrency, l1Cost) + l1Cost, _ = exchange.ConvertCeloToCurrency(st.evm.Context.FeeCurrencyContext.ExchangeRates, feeCurrency, l1Cost) } - if err := contracts.CreditFees(st.evm, feeCurrency, from, st.evm.Context.Coinbase, feeHandlerAddress, params.OptimismL1FeeRecipient, refund, tipTxFee, baseTxFee, l1Cost); err != nil { + if err := contracts.CreditFees( + st.evm, + feeCurrency, + from, + st.evm.Context.Coinbase, + feeHandlerAddress, + params.OptimismL1FeeRecipient, + refund, + tipTxFee, + baseTxFee, + l1Cost, + st.feeCurrencyGasUsed, + ); err != nil { + err = fmt.Errorf("error crediting fee-currency: %w", err) log.Error("Error crediting", "from", from, "coinbase", st.evm.Context.Coinbase, "feeHandler", feeHandlerAddress, "err", err) return err } @@ -147,7 +162,7 @@ func (st *StateTransition) calculateBaseFee() *big.Int { if st.msg.FeeCurrency != nil { // Existence of the fee currency has been checked in `preCheck` - baseFee, _ = exchange.ConvertCeloToCurrency(st.evm.Context.ExchangeRates, st.msg.FeeCurrency, baseFee) + baseFee, _ = exchange.ConvertCeloToCurrency(st.evm.Context.FeeCurrencyContext.ExchangeRates, st.msg.FeeCurrency, baseFee) } return baseFee diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index a2b44850a0..8f46ca6224 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -60,7 +60,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c return } // Convert the transaction into an executable message and pre-cache its sender - msg, err := TransactionToMessage(tx, signer, header.BaseFee, blockContext.ExchangeRates) + msg, err := TransactionToMessage(tx, signer, header.BaseFee, blockContext.FeeCurrencyContext.ExchangeRates) if err != nil { return // Also invalid block, bail out } diff --git a/core/state_processor.go b/core/state_processor.go index 50ecfd5eb6..55cc364a78 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -92,7 +92,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg // Iterate over and process the individual transactions for i, tx := range block.Transactions() { - msg, err := TransactionToMessage(tx, signer, header.BaseFee, context.ExchangeRates) + msg, err := TransactionToMessage(tx, signer, header.BaseFee, context.FeeCurrencyContext.ExchangeRates) if err != nil { return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } @@ -203,7 +203,7 @@ func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, b alternativeBaseFee := evm.Context.BaseFee if tx.FeeCurrency() != nil { var err error - alternativeBaseFee, err = exchange.ConvertCeloToCurrency(evm.Context.ExchangeRates, tx.FeeCurrency(), evm.Context.BaseFee) + alternativeBaseFee, err = exchange.ConvertCeloToCurrency(evm.Context.FeeCurrencyContext.ExchangeRates, tx.FeeCurrency(), evm.Context.BaseFee) if err != nil { log.Error("Can't calc base fee in currency for receipt", "err", err) } @@ -250,7 +250,7 @@ type ApplyTransactionOpts struct { func ApplyTransactionExtended(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config, extraOpts *ApplyTransactionOpts) (*types.Receipt, error) { // Create a new context to be used in the EVM environment blockContext := NewEVMBlockContext(header, bc, author, config, statedb) - msg, err := TransactionToMessage(tx, types.MakeSigner(config, header.Number, header.Time), header.BaseFee, blockContext.ExchangeRates) + msg, err := TransactionToMessage(tx, types.MakeSigner(config, header.Number, header.Time), header.BaseFee, blockContext.FeeCurrencyContext.ExchangeRates) if err != nil { return nil, err } diff --git a/core/state_transition.go b/core/state_transition.go index b86a734f04..52d95eef85 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -23,7 +23,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/exchange" - "github.com/ethereum/go-ethereum/contracts" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -70,7 +69,7 @@ func (result *ExecutionResult) Revert() []byte { } // IntrinsicGas computes the 'intrinsic gas' for a message with the given data. -func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool, feeCurrency *common.Address) (uint64, error) { +func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool, feeCurrency *common.Address, feeIntrinsicGas common.IntrinsicGasCosts) (uint64, error) { // Set the starting gas for the raw transaction var gas uint64 if isContractCreation && isHomestead { @@ -128,10 +127,14 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, // In this case, however, the user always ends up paying `maxGasForDebitAndCreditTransactions` // keeping it consistent. if feeCurrency != nil { - if (math.MaxUint64 - gas) < contracts.IntrinsicGasForAlternativeFeeCurrency { + intrinsicGasForFeeCurrency, ok := common.CurrencyIntrinsicGasCost(feeIntrinsicGas, feeCurrency) + if !ok { + return 0, fmt.Errorf("%w: %x", exchange.ErrUnregisteredFeeCurrency, feeCurrency) + } + if (math.MaxUint64 - gas) < intrinsicGasForFeeCurrency { return 0, ErrGasUintOverflow } - gas += contracts.IntrinsicGasForAlternativeFeeCurrency + gas += intrinsicGasForFeeCurrency } if accessList != nil { @@ -284,6 +287,8 @@ type StateTransition struct { initialGas uint64 state vm.StateDB evm *vm.EVM + + feeCurrencyGasUsed uint64 } // NewStateTransition initialises and returns a new state transition object. @@ -404,7 +409,7 @@ func (st *StateTransition) preCheck() error { if !st.evm.ChainConfig().IsCel2(st.evm.Context.Time) { return ErrCel2NotEnabled } else { - if !common.IsCurrencyAllowed(st.evm.Context.ExchangeRates, msg.FeeCurrency) { + if !common.IsCurrencyAllowed(st.evm.Context.FeeCurrencyContext.ExchangeRates, msg.FeeCurrency) { log.Trace("fee currency not allowed", "fee currency address", msg.FeeCurrency) return fmt.Errorf("%w: %x", exchange.ErrUnregisteredFeeCurrency, msg.FeeCurrency) } @@ -431,7 +436,7 @@ func (st *StateTransition) preCheck() error { // This will panic if baseFee is nil, but basefee presence is verified // as part of header validation. - baseFeeInFeeCurrency, err := exchange.ConvertCeloToCurrency(st.evm.Context.ExchangeRates, msg.FeeCurrency, st.evm.Context.BaseFee) + baseFeeInFeeCurrency, err := exchange.ConvertCeloToCurrency(st.evm.Context.FeeCurrencyContext.ExchangeRates, msg.FeeCurrency, st.evm.Context.BaseFee) if err != nil { return fmt.Errorf("preCheck: %w", err) } @@ -562,7 +567,16 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { } // Check clauses 4-5, subtract intrinsic gas if everything is correct - gas, err := IntrinsicGas(msg.Data, msg.AccessList, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, msg.FeeCurrency) + gas, err := IntrinsicGas( + msg.Data, + msg.AccessList, + contractCreation, + rules.IsHomestead, + rules.IsIstanbul, + rules.IsShanghai, + msg.FeeCurrency, + st.evm.Context.FeeCurrencyContext.IntrinsicGasCosts, + ) if err != nil { return nil, err } diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 1fab555d02..40b0f4f021 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -326,8 +326,8 @@ type BlobPool struct { lock sync.RWMutex // Mutex protecting the pool during reorg handling // Celo specific - celoBackend *contracts.CeloBackend // For fee currency balances & exchange rate calculation - currentRates common.ExchangeRates // current exchange rates for fee currencies + celoBackend *contracts.CeloBackend // For fee currency balances & exchange rate calculation + feeCurrencyContext common.FeeCurrencyContext } // New creates a new blob transaction pool to gather, sort and filter inbound diff --git a/core/txpool/blobpool/celo_blobpool.go b/core/txpool/blobpool/celo_blobpool.go index b3e4cc8cff..7b453a3ca4 100644 --- a/core/txpool/blobpool/celo_blobpool.go +++ b/core/txpool/blobpool/celo_blobpool.go @@ -10,9 +10,9 @@ func (pool *BlobPool) recreateCeloProperties() { ChainConfig: pool.chain.Config(), State: pool.state, } - currentRates, err := contracts.GetExchangeRates(pool.celoBackend) + currencyContext, err := contracts.GetFeeCurrencyContext(pool.celoBackend) if err != nil { - log.Error("Error trying to get exchange rates in txpool.", "cause", err) + log.Error("Error trying to get fee currency context in txpool.", "cause", err) } - pool.currentRates = currentRates + pool.feeCurrencyContext = currencyContext } diff --git a/core/txpool/celo_validation.go b/core/txpool/celo_validation.go index 23b2741f92..91e2025ff5 100644 --- a/core/txpool/celo_validation.go +++ b/core/txpool/celo_validation.go @@ -1,7 +1,6 @@ package txpool import ( - "errors" "math/big" "github.com/ethereum/go-ethereum/common" @@ -10,8 +9,6 @@ import ( "github.com/ethereum/go-ethereum/params" ) -var NonWhitelistedFeeCurrencyError = errors.New("Fee currency given is not whitelisted at current block") - // AcceptSet is a set of accepted transaction types for a transaction subpool. type AcceptSet = map[uint8]struct{} @@ -51,11 +48,11 @@ func (cvo *CeloValidationOptions) Accepts(txType uint8) bool { // This check is public to allow different transaction pools to check the basic // rules without duplicating code and running the risk of missed updates. func CeloValidateTransaction(tx *types.Transaction, head *types.Header, - signer types.Signer, opts *CeloValidationOptions, rates common.ExchangeRates) error { - if err := ValidateTransaction(tx, head, signer, opts); err != nil { + signer types.Signer, opts *CeloValidationOptions, currencyCtx common.FeeCurrencyContext) error { + if err := ValidateTransaction(tx, head, signer, opts, currencyCtx); err != nil { return err } - if !common.IsCurrencyAllowed(rates, tx.FeeCurrency()) { + if !common.IsCurrencyAllowed(currencyCtx.ExchangeRates, tx.FeeCurrency()) { return exchange.ErrUnregisteredFeeCurrency } diff --git a/core/txpool/legacypool/celo_legacypool.go b/core/txpool/legacypool/celo_legacypool.go index 5dad65c131..a6f4da7a2b 100644 --- a/core/txpool/legacypool/celo_legacypool.go +++ b/core/txpool/legacypool/celo_legacypool.go @@ -12,7 +12,7 @@ import ( // and gasLimit. Returns drops and invalid txs. func (pool *LegacyPool) filter(list *list, addr common.Address, gasLimit uint64) (types.Transactions, types.Transactions) { // CELO: drop all transactions that no longer have a registered currency - dropsAllowlist, invalidsAllowlist := list.FilterAllowlisted(pool.currentRates) + dropsAllowlist, invalidsAllowlist := list.FilterAllowlisted(pool.feeCurrencyContext.ExchangeRates) // Check from which currencies we need to get balances currenciesInList := list.FeeCurrencies() drops, invalids := list.Filter(pool.getBalances(addr, currenciesInList), gasLimit) @@ -34,9 +34,10 @@ func (pool *LegacyPool) recreateCeloProperties() { ChainConfig: pool.chainconfig, State: pool.currentState, } - currentRates, err := contracts.GetExchangeRates(pool.celoBackend) + feeCurrencyContext, err := contracts.GetFeeCurrencyContext(pool.celoBackend) if err != nil { - log.Error("Error trying to get exchange rates in txpool.", "cause", err) + log.Error("Error trying to get fee currency context in txpool.", "cause", err) } - pool.currentRates = currentRates + + pool.feeCurrencyContext = feeCurrencyContext } diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index b64b32ecd0..244de57b0b 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -244,8 +244,8 @@ type LegacyPool struct { l1CostFn txpool.L1CostFunc // To apply L1 costs as rollup, optional field, may be nil. // Celo specific - celoBackend *contracts.CeloBackend // For fee currency balances & exchange rate calculation - currentRates common.ExchangeRates // current exchange rates for fee currencies + celoBackend *contracts.CeloBackend // For fee currency balances & exchange rate calculation + feeCurrencyContext common.FeeCurrencyContext // context for fee currencies } type txpoolResetRequest struct { @@ -667,7 +667,7 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) erro if local { opts.MinTip = new(big.Int) } - if err := txpool.CeloValidateTransaction(tx, pool.currentHead.Load(), pool.signer, opts, pool.currentRates); err != nil { + if err := txpool.CeloValidateTransaction(tx, pool.currentHead.Load(), pool.signer, opts, pool.feeCurrencyContext); err != nil { return err } return nil @@ -730,7 +730,10 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction, local bool) error { log.Error("Transaction sender recovery failed", "err", err) return err } - return contracts.TryDebitFees(tx, from, pool.celoBackend) + //NOTE: we only test the `debitFees` call here. + // If the `creditFees` reverts (or runs out of gas), the transaction will + // not be invalidated here and will be included in the block. + return contracts.TryDebitFees(tx, from, pool.celoBackend, pool.feeCurrencyContext) } return nil } @@ -850,7 +853,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e // Try to replace an existing transaction in the pending pool if list := pool.pending[from]; list != nil && list.Contains(tx.Nonce()) { // Nonce already pending, check if required price bump is met - inserted, old := list.Add(tx, pool.config.PriceBump, pool.l1CostFn, pool.currentRates) + inserted, old := list.Add(tx, pool.config.PriceBump, pool.l1CostFn, pool.feeCurrencyContext.ExchangeRates) if !inserted { pendingDiscardMeter.Mark(1) return false, txpool.ErrReplaceUnderpriced @@ -924,7 +927,7 @@ func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, local if pool.queue[from] == nil { pool.queue[from] = newList(false) } - inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump, pool.l1CostFn, pool.currentRates) + inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump, pool.l1CostFn, pool.feeCurrencyContext.ExchangeRates) if !inserted { // An older transaction was better, discard this queuedDiscardMeter.Mark(1) @@ -978,7 +981,7 @@ func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *typ } list := pool.pending[addr] - inserted, old := list.Add(tx, pool.config.PriceBump, pool.l1CostFn, pool.currentRates) + inserted, old := list.Add(tx, pool.config.PriceBump, pool.l1CostFn, pool.feeCurrencyContext.ExchangeRates) if !inserted { // An older transaction was better, discard this pool.all.Remove(hash) @@ -1380,7 +1383,7 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, if reset.newHead != nil { if pool.chainconfig.IsLondon(new(big.Int).Add(reset.newHead.Number, big.NewInt(1))) { pendingBaseFee := eip1559.CalcBaseFee(pool.chainconfig, reset.newHead, reset.newHead.Time+1) - pool.priced.SetBaseFeeAndRates(pendingBaseFee, pool.currentRates) + pool.priced.SetBaseFeeAndRates(pendingBaseFee, pool.feeCurrencyContext.ExchangeRates) } else { pool.priced.Reheap() } diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 0894342eaa..84b38bf3e4 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -80,7 +80,7 @@ type ValidationFunction func(tx *types.Transaction, head *types.Header, signer t // This check is public to allow different transaction pools to check the basic // rules without duplicating code and running the risk of missed updates. // ONLY TO BE CALLED FROM "CeloValidateTransaction" -func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types.Signer, opts *CeloValidationOptions) error { +func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types.Signer, opts *CeloValidationOptions, currencyCtx common.FeeCurrencyContext) error { // No unauthenticated deposits allowed in the transaction pool. // This is for spam protection, not consensus, // as the external engine-API user authenticates deposits. @@ -139,7 +139,16 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types } // Ensure the transaction has more gas than the bare minimum needed to cover // the transaction metadata - intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, opts.Config.IsIstanbul(head.Number), opts.Config.IsShanghai(head.Number, head.Time), tx.FeeCurrency()) + intrGas, err := core.IntrinsicGas( + tx.Data(), + tx.AccessList(), + tx.To() == nil, + true, + opts.Config.IsIstanbul(head.Number), + opts.Config.IsShanghai(head.Number, head.Time), + tx.FeeCurrency(), + currencyCtx.IntrinsicGasCosts, + ) if err != nil { return err } diff --git a/core/vm/evm.go b/core/vm/evm.go index e7ad70eab8..aeeef6570d 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -87,8 +87,7 @@ type BlockContext struct { Random *common.Hash // Provides information for PREVRANDAO // Celo specific information - ExchangeRates common.ExchangeRates - GasUsedForDebit uint64 + FeeCurrencyContext common.FeeCurrencyContext } // TxContext provides the EVM with information about a transaction. diff --git a/e2e_test/debug-fee-currency/DebugFeeCurrency.sol b/e2e_test/debug-fee-currency/DebugFeeCurrency.sol index e5d6e1ce3a..7f269dd5f2 100644 --- a/e2e_test/debug-fee-currency/DebugFeeCurrency.sol +++ b/e2e_test/debug-fee-currency/DebugFeeCurrency.sol @@ -702,11 +702,14 @@ contract FeeCurrency is ERC20, IFeeCurrency { contract DebugFeeCurrency is ERC20, IFeeCurrency { bool failOnDebit; bool failOnCredit; + bool highGasOnCredit; + mapping(uint256 => uint256) private _dummyMap; - constructor(uint256 initialSupply, bool _failOnDebit, bool _failOnCredit) ERC20("DebugFeeCurrency", "DFC") { + constructor(uint256 initialSupply, bool _failOnDebit, bool _failOnCredit, bool _highGasOnCredit) ERC20("DebugFeeCurrency", "DFC") { _mint(msg.sender, initialSupply); failOnDebit = _failOnDebit; failOnCredit = _failOnCredit; + highGasOnCredit = _highGasOnCredit; } modifier onlyVm() { @@ -727,6 +730,18 @@ contract DebugFeeCurrency is ERC20, IFeeCurrency { for (uint256 i = 0; i < recipients.length; i++) { _mint(recipients[i], amounts[i]); } + + if (highGasOnCredit){ + induceHighGasCost(); + } + } + + function induceHighGasCost() internal view { + // SLOAD for non existing touched_storage_slots + // 2100 * 3000 gas = 630.0000 + for (uint256 i = 0; i < 3000; i++) { + _dummyMap[i]; + } } // Old function signature for backwards compatibility @@ -746,6 +761,10 @@ contract DebugFeeCurrency is ERC20, IFeeCurrency { _mint(from, refund); _mint(feeRecipient, tipTxFee); _mint(communityFund, baseTxFee); + + if (highGasOnCredit){ + induceHighGasCost(); + } } } diff --git a/e2e_test/debug-fee-currency/deploy_and_send_tx.sh b/e2e_test/debug-fee-currency/deploy_and_send_tx.sh index 76470fa53a..1cfd9eec73 100755 --- a/e2e_test/debug-fee-currency/deploy_and_send_tx.sh +++ b/e2e_test/debug-fee-currency/deploy_and_send_tx.sh @@ -3,7 +3,7 @@ set -xeo pipefail export FEE_CURRENCY=$(\ - forge create --root . --contracts . --private-key $ACC_PRIVKEY DebugFeeCurrency.sol:DebugFeeCurrency --constructor-args '100000000000000000000000000' $1 $2 --json\ + forge create --root . --contracts . --private-key $ACC_PRIVKEY DebugFeeCurrency.sol:DebugFeeCurrency --constructor-args '100000000000000000000000000' $1 $2 $3 --json\ | jq .deployedTo -r) cast send --private-key $ACC_PRIVKEY $ORACLE3 'setExchangeRate(address, uint256, uint256)' $FEE_CURRENCY 2ether 1ether diff --git a/e2e_test/js-tests/test_viem_tx.mjs b/e2e_test/js-tests/test_viem_tx.mjs index cfbde85e47..7193f1e8aa 100644 --- a/e2e_test/js-tests/test_viem_tx.mjs +++ b/e2e_test/js-tests/test_viem_tx.mjs @@ -263,10 +263,7 @@ describe("viem send tx", () => { assert.fail("Failed to filter unregistered feeCurrency"); } catch (err) { // TODO: find a better way to check the error type - if ( - err.cause.details == - "Fee currency given is not whitelisted at current block" - ) { + if (err.cause.details.indexOf("unregistered fee-currency address") >= 0) { // Test success } else { throw err; diff --git a/e2e_test/test_fee_currency_fails_intrinsic.sh b/e2e_test/test_fee_currency_fails_intrinsic.sh new file mode 100755 index 0000000000..b34a2b0e74 --- /dev/null +++ b/e2e_test/test_fee_currency_fails_intrinsic.sh @@ -0,0 +1,33 @@ +#!/bin/bash +#shellcheck disable=SC2086 +set -eo pipefail + +source shared.sh +source debug-fee-currency/lib.sh + +# Expect that the creditGasFees failed and is logged by geth +tail -F -n 0 geth.log >debug-fee-currency/geth.intrinsic.log & # start log capture +trap 'kill %%' EXIT # kill bg tail job on exit +( + sleep 0.2 + fee_currency=$(deploy_fee_currency false false true) + + # trigger the first failed call to the CreditFees(), causing the + # currency to get temporarily blocklisted. + # initial tx should not succeed, should have required a replacement transaction. + cip_64_tx $fee_currency 1 true 2 | assert_cip_64_tx false + + sleep 2 + + # since the fee currency is temporarily blocked, + # this should NOT make the transaction execute anymore, + # but invalidate the transaction earlier. + # initial tx should not succeed, should have required a replacement transaction. + cip_64_tx $fee_currency 1 true 2 | assert_cip_64_tx false + + cleanup_fee_currency $fee_currency +) +sleep 0.5 +# although we sent a transaction wih faulty fee-currency twice, +# the EVM call should have been executed only once +if [ "$(grep -Ec "fee-currency EVM execution error, temporarily blocking fee-currency in local txpools .+ surpassed maximum allowed intrinsic gas for CreditFees\(\) in fee-currency" debug-fee-currency/geth.intrinsic.log)" -ne 1 ]; then exit 1; fi diff --git a/e2e_test/test_fee_currency_fails_on_credit.sh b/e2e_test/test_fee_currency_fails_on_credit.sh index 137ea6c227..dbf16dfa2d 100755 --- a/e2e_test/test_fee_currency_fails_on_credit.sh +++ b/e2e_test/test_fee_currency_fails_on_credit.sh @@ -5,8 +5,8 @@ set -eo pipefail source shared.sh # Expect that the creditGasFees failed and is logged by geth -tail -f -n0 geth.log > debug-fee-currency/geth.partial.log & # start log capture -(cd debug-fee-currency && ./deploy_and_send_tx.sh false true) -sleep 0.1 +tail -f -n0 geth.log >debug-fee-currency/geth.partial.log & # start log capture +(cd debug-fee-currency && ./deploy_and_send_tx.sh false true false) +sleep 0.5 kill %1 # stop log capture grep "This DebugFeeCurrency always fails in (old) creditGasFees!" debug-fee-currency/geth.partial.log diff --git a/e2e_test/test_fee_currency_fails_on_debit.sh b/e2e_test/test_fee_currency_fails_on_debit.sh index 0b4e4438f3..105c448e36 100755 --- a/e2e_test/test_fee_currency_fails_on_debit.sh +++ b/e2e_test/test_fee_currency_fails_on_debit.sh @@ -5,6 +5,6 @@ set -eo pipefail source shared.sh # Expect that the debitGasFees fails during tx submission -(cd debug-fee-currency && ./deploy_and_send_tx.sh true false) &> debug-fee-currency/send_tx.log || true -grep "debitGasFees reverted: This DebugFeeCurrency always fails in debitGasFees!" debug-fee-currency/send_tx.log \ - || (cat debug-fee-currency/send_tx.log && false) +(cd debug-fee-currency && ./deploy_and_send_tx.sh true false false) &>debug-fee-currency/send_tx.log || true +grep "debitGasFees reverted: This DebugFeeCurrency always fails in debitGasFees!" debug-fee-currency/send_tx.log || + (cat debug-fee-currency/send_tx.log && false) diff --git a/eth/interop.go b/eth/interop.go index 1b3321963b..c5078edad1 100644 --- a/eth/interop.go +++ b/eth/interop.go @@ -39,7 +39,7 @@ func (s *Ethereum) SimLogs(tx *types.Transaction) ([]*types.Log, error) { signer := types.MakeSigner(chainConfig, header.Number, header.Time) chainCtx := ethapi.NewChainContext(context.Background(), s.APIBackend) blockCtx := core.NewEVMBlockContext(header, chainCtx, &header.Coinbase, chainConfig, state) - message, err := core.TransactionToMessage(tx, signer, header.BaseFee, blockCtx.ExchangeRates) + message, err := core.TransactionToMessage(tx, signer, header.BaseFee, blockCtx.FeeCurrencyContext.ExchangeRates) if err != nil { return nil, fmt.Errorf("cannot convert tx to message for log simulation: %w", err) } diff --git a/eth/state_accessor.go b/eth/state_accessor.go index c01a2f1ce8..e5b957ba3e 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -254,7 +254,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, for idx, tx := range block.Transactions() { context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil, eth.blockchain.Config(), statedb) // Assemble the transaction call message and return if the requested offset - msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee(), context.ExchangeRates) + msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee(), context.FeeCurrencyContext.ExchangeRates) txContext := core.NewEVMTxContext(msg) if idx == txIndex { return tx, context, statedb, release, nil diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 15d54862e0..69551cab30 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -271,7 +271,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed ) // Trace all the transactions contained within for i, tx := range task.block.Transactions() { - msg, _ := core.TransactionToMessage(tx, signer, task.block.BaseFee(), blockCtx.ExchangeRates) + msg, _ := core.TransactionToMessage(tx, signer, task.block.BaseFee(), blockCtx.FeeCurrencyContext.ExchangeRates) txctx := &Context{ BlockHash: task.block.Hash(), BlockNumber: task.block.Number(), @@ -578,7 +578,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config return nil, err } var ( - msg, _ = core.TransactionToMessage(tx, signer, block.BaseFee(), vmctx.ExchangeRates) + msg, _ = core.TransactionToMessage(tx, signer, block.BaseFee(), vmctx.FeeCurrencyContext.ExchangeRates) txContext = core.NewEVMTxContext(msg) vmenv = vm.NewEVM(vmctx, txContext, statedb, chainConfig, vm.Config{}) ) @@ -660,7 +660,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac ) for i, tx := range txs { // Generate the next state snapshot fast without tracing - msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee(), blockCtx.ExchangeRates) + msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee(), blockCtx.FeeCurrencyContext.ExchangeRates) txctx := &Context{ BlockHash: blockHash, BlockNumber: block.Number(), @@ -737,7 +737,7 @@ txloop: } // Generate the next state snapshot fast without tracing - msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee(), blockCtx.ExchangeRates) + msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee(), blockCtx.FeeCurrencyContext.ExchangeRates) statedb.SetTxContext(tx.Hash(), i) vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{}) if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)); err != nil { @@ -824,7 +824,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block for i, tx := range block.Transactions() { // Prepare the transaction for un-traced execution var ( - msg, _ = core.TransactionToMessage(tx, signer, block.BaseFee(), vmctx.ExchangeRates) + msg, _ = core.TransactionToMessage(tx, signer, block.BaseFee(), vmctx.FeeCurrencyContext.ExchangeRates) txContext = core.NewEVMTxContext(msg) vmConf vm.Config dump *os.File @@ -1016,7 +1016,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc return nil, err } var ( - msg = args.ToMessage(vmctx.BaseFee, true, true, vmctx.ExchangeRates) + msg = args.ToMessage(vmctx.BaseFee, true, true, vmctx.FeeCurrencyContext.ExchangeRates) tx = args.ToTransaction(types.LegacyTxType) traceConfig *TraceConfig ) diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index a5ce677582..d102eba5ef 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -250,7 +250,7 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block signer := types.MakeSigner(b.chainConfig, block.Number(), block.Time()) for idx, tx := range block.Transactions() { context := core.NewEVMBlockContext(block.Header(), b.chain, nil, b.chainConfig, statedb) - msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee(), context.ExchangeRates) + msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee(), context.FeeCurrencyContext.ExchangeRates) txContext := core.NewEVMTxContext(msg) if idx == txIndex { return tx, context, statedb, release, nil diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index aa997ae743..7b9d3d6758 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -128,7 +128,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) { if tracer.Hooks != nil { logState = state.NewHookedState(st.StateDB, tracer.Hooks) } - msg, err := core.TransactionToMessage(tx, signer, context.BaseFee, context.ExchangeRates) + msg, err := core.TransactionToMessage(tx, signer, context.BaseFee, context.FeeCurrencyContext.ExchangeRates) if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) } @@ -211,7 +211,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { GasPrice: tx.GasPrice(), } context := test.Context.toBlockContext(test.Genesis) - msg, err := core.TransactionToMessage(tx, signer, context.BaseFee, context.ExchangeRates) + msg, err := core.TransactionToMessage(tx, signer, context.BaseFee, context.FeeCurrencyContext.ExchangeRates) if err != nil { b.Fatalf("failed to prepare transaction for tracing: %v", err) } diff --git a/eth/tracers/internal/tracetest/flat_calltrace_test.go b/eth/tracers/internal/tracetest/flat_calltrace_test.go index 839f5937f6..6f02fd7733 100644 --- a/eth/tracers/internal/tracetest/flat_calltrace_test.go +++ b/eth/tracers/internal/tracetest/flat_calltrace_test.go @@ -93,7 +93,7 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string return fmt.Errorf("failed to create call tracer: %v", err) } - msg, err := core.TransactionToMessage(tx, signer, context.BaseFee, context.ExchangeRates) + msg, err := core.TransactionToMessage(tx, signer, context.BaseFee, context.FeeCurrencyContext.ExchangeRates) if err != nil { return fmt.Errorf("failed to prepare transaction for tracing: %v", err) } diff --git a/eth/tracers/internal/tracetest/prestate_test.go b/eth/tracers/internal/tracetest/prestate_test.go index 269bbda07e..97dd8761d8 100644 --- a/eth/tracers/internal/tracetest/prestate_test.go +++ b/eth/tracers/internal/tracetest/prestate_test.go @@ -102,7 +102,7 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) { t.Fatalf("failed to create call tracer: %v", err) } - msg, err := core.TransactionToMessage(tx, signer, context.BaseFee, context.ExchangeRates) + msg, err := core.TransactionToMessage(tx, signer, context.BaseFee, context.FeeCurrencyContext.ExchangeRates) if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) } diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index 8d0ce16907..2c24017c20 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -90,7 +90,7 @@ func BenchmarkTransactionTrace(b *testing.B) { //EnableReturnData: false, }) evm := vm.NewEVM(context, txContext, state.StateDB, params.AllEthashProtocolChanges, vm.Config{Tracer: tracer.Hooks()}) - msg, err := core.TransactionToMessage(tx, signer, context.BaseFee, context.ExchangeRates) + msg, err := core.TransactionToMessage(tx, signer, context.BaseFee, context.FeeCurrencyContext.ExchangeRates) if err != nil { b.Fatalf("failed to prepare transaction for tracing: %v", err) } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 2cac5d321d..77b12a48e3 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -945,7 +945,7 @@ func applyMessage(ctx context.Context, b Backend, args TransactionArgs, state *s if err := args.CallDefaults(gp.Gas(), blockContext.BaseFee, b.ChainConfig().ChainID); err != nil { return nil, err } - msg := args.ToMessage(header.BaseFee, skipChecks, skipChecks, blockContext.ExchangeRates) + msg := args.ToMessage(header.BaseFee, skipChecks, skipChecks, blockContext.FeeCurrencyContext.ExchangeRates) // Lower the basefee to 0 to avoid breaking EVM // invariants (basefee < feecap). if msg.GasPrice.Sign() == 0 { diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 5ea7fb4f2f..f9f2392255 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -210,7 +210,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, txes[i] = tx tracer.reset(tx.Hash(), uint(i)) // EoA check is always skipped, even in validation mode. - msg := call.ToMessage(header.BaseFee, !sim.validate, true, blockContext.ExchangeRates) + msg := call.ToMessage(header.BaseFee, !sim.validate, true, blockContext.FeeCurrencyContext.ExchangeRates) evm.Reset(core.NewEVMTxContext(msg), tracingStateDB) result, err := applyMessageWithEVM(ctx, evm, msg, timeout, sim.gp) if err != nil { diff --git a/miner/worker.go b/miner/worker.go index 1a428c695e..b1efe0c12f 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -337,7 +337,7 @@ func (miner *Miner) prepareWork(genParams *generateParams, witness bool) (*envir } env.noTxs = genParams.noTxs context := core.NewEVMBlockContext(header, miner.chain, nil, miner.chainConfig, env.state) - env.feeCurrencyAllowlist = common.CurrencyAllowlist(context.ExchangeRates) + env.feeCurrencyAllowlist = common.CurrencyAllowlist(context.FeeCurrencyContext.ExchangeRates) if header.ParentBeaconRoot != nil { vmenv := vm.NewEVM(context, vm.TxContext{}, env.state, miner.chainConfig, vm.Config{}) core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state) diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go index 0a02b442fd..57b1f7db22 100644 --- a/tests/transaction_test_util.go +++ b/tests/transaction_test_util.go @@ -59,7 +59,7 @@ func (tt *TransactionTest) Run(config *params.ChainConfig) error { return nil, nil, err } // Intrinsic gas - requiredGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, isHomestead, isIstanbul, false, nil) + requiredGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, isHomestead, isIstanbul, false, nil, common.IntrinsicGasCosts{}) if err != nil { return nil, nil, err }