From f1fb53c28c8c70da18a2e0405647a861d94a2171 Mon Sep 17 00:00:00 2001 From: Maximilian Langenfeld <15726643+ezdac@users.noreply.github.com> Date: Mon, 5 Aug 2024 17:56:57 +0200 Subject: [PATCH] Read IntrinsicGas from FeeCurrencyDirectory (#178) * Add FeeCurrencyContext object to BlockContext This replaces the `BlockContext.ExchangeRates` with a wrapper object `BlockContext.FeeCurrencyContext`. This enables easier addition of fee-currency related information to the block context and passing around that information. The exchange-rates are now part of the `FeeCurrencyContext` * Add intrinsic gas retrieval from FeeCurrencyDirectory * Use intrinsic gas from FeeCurrencyDirectory in STF * Add test for intrinsic gas too high in fee currency * Add empty fee-currency context to tools and tests * Add minor code improvements Co-authored-by: Paul Lange * Add comment for nil fee-currency code path * Add fee-currency-context to CeloBackend.NewEVM constructor --------- Co-authored-by: Paul Lange --- cmd/evm/internal/t8ntool/execution.go | 7 +- cmd/evm/internal/t8ntool/transaction.go | 8 +- common/celo_types.go | 31 ++++ contracts/celo_backend.go | 9 +- contracts/fee_currencies.go | 153 +++++++++++++----- core/bench_test.go | 2 +- core/celo_evm.go | 18 ++- core/state_prefetcher.go | 2 +- core/state_processor.go | 6 +- core/state_processor_test.go | 4 +- core/state_transition.go | 52 ++++-- core/txpool/blobpool/blobpool.go | 6 +- core/txpool/blobpool/celo_blobpool.go | 6 +- core/txpool/celo_validation.go | 9 +- core/txpool/legacypool/celo_legacypool.go | 9 +- core/txpool/legacypool/legacypool.go | 19 ++- core/txpool/validation.go | 13 +- core/vm/evm.go | 3 +- .../debug-fee-currency/DebugFeeCurrency.sol | 21 ++- .../debug-fee-currency/deploy_and_send_tx.sh | 2 +- e2e_test/js-tests/test_viem_tx.mjs | 5 +- e2e_test/test_fee_currency_fails_intrinsic.sh | 13 ++ e2e_test/test_fee_currency_fails_on_credit.sh | 6 +- e2e_test/test_fee_currency_fails_on_debit.sh | 6 +- eth/state_accessor.go | 2 +- eth/tracers/api.go | 12 +- eth/tracers/api_test.go | 2 +- .../internal/tracetest/calltrace_test.go | 4 +- .../internal/tracetest/flat_calltrace_test.go | 2 +- .../internal/tracetest/prestate_test.go | 2 +- eth/tracers/tracers_test.go | 2 +- internal/ethapi/api.go | 2 +- internal/ethapi/simulate.go | 2 +- miner/worker.go | 2 +- tests/transaction_test_util.go | 2 +- 35 files changed, 323 insertions(+), 121 deletions(-) create mode 100755 e2e_test/test_fee_currency_fails_intrinsic.sh diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 5094db931d..7bce224ba0 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -219,7 +219,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 2eb85ee8d7..ca199bbe0c 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -83,7 +83,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) { return func(i int, gen *BlockGen) { toaddr := common.Address{} data := make([]byte, nbytes) - 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/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 c839e26ddf..4cef69536c 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -86,7 +86,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) } @@ -186,7 +186,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) } @@ -225,7 +225,7 @@ func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, b func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*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_processor_test.go b/core/state_processor_test.go index a7c2f2b2ac..097f87b014 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -431,12 +431,12 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr var ( code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`) - intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, true, true, true, true, nil) + intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, true, true, true, true, nil, nil) // A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness // will not contain that copied data. // Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985 codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`) - intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, true, true, true, true, nil) + intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, true, true, true, true, nil, nil) ) func TestProcessVerkle(t *testing.T) { diff --git a/core/state_transition.go b/core/state_transition.go index 2bbf97372b..af048d1fc5 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -72,7 +72,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 { @@ -130,10 +130,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 { @@ -279,6 +283,8 @@ type StateTransition struct { initialGas uint64 state vm.StateDB evm *vm.EVM + + feeCurrencyGasUsed uint64 } // NewStateTransition initialises and returns a new state transition object. @@ -389,7 +395,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 } } @@ -439,7 +447,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) } @@ -466,7 +474,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) } @@ -590,7 +598,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 } @@ -789,9 +806,22 @@ func (st *StateTransition) distributeTxFees() error { } } else { if l1Cost != nil { - l1Cost, _ = exchange.ConvertCeloToCurrency(st.evm.Context.ExchangeRates, feeCurrency, l1Cost) - } - if err := contracts.CreditFees(st.evm, feeCurrency, from, st.evm.Context.Coinbase, feeHandlerAddress, params.OptimismL1FeeRecipient, refund, tipTxFee, baseTxFee, l1Cost); err != nil { + 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, + 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 } @@ -815,7 +845,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/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 241e417cd7..e94a6d69c7 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -318,8 +318,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 @@ -1094,7 +1094,7 @@ func (p *BlobPool) validateTx(tx *types.Transaction) error { MaxSize: txMaxSize, MinTip: p.gasTip.ToBig(), } - if err := txpool.CeloValidateTransaction(tx, p.head, p.signer, baseOpts, p.currentRates); err != nil { + if err := txpool.CeloValidateTransaction(tx, p.head, p.signer, baseOpts, p.feeCurrencyContext); err != nil { return err } // Ensure the transaction adheres to the stateful pool filters (nonce, balance) 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 1e07f18be6..8787a19125 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -243,8 +243,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 { @@ -653,7 +653,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 @@ -716,7 +716,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 } @@ -836,7 +839,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 @@ -910,7 +913,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) @@ -964,7 +967,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) @@ -1360,7 +1363,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 9d24a53842..d173d4ce6c 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -75,7 +75,7 @@ type ValidationOptions struct { // 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. @@ -134,7 +134,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 0e16bbd6e3..f35dd5a2e0 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..68213b5b7d --- /dev/null +++ b/e2e_test/test_fee_currency_fails_intrinsic.sh @@ -0,0 +1,13 @@ +#!/bin/bash +#shellcheck disable=SC2086 +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.intrinsic.log & # start log capture +(cd debug-fee-currency && ./deploy_and_send_tx.sh false false true) +sleep 0.5 +kill %1 # stop log capture +grep "error crediting fee-currency: surpassed maximum allowed intrinsic gas for fee currency: out of gas" debug-fee-currency/geth.intrinsic.log +# echo $(grep "send_tx hash:" debug-fee-currency/send_tx.intrinsic.log) 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/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 9f2affb1c1..c4aa851234 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{}) ) @@ -658,7 +658,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(), @@ -735,7 +735,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 { @@ -822,7 +822,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 @@ -1014,7 +1014,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 9ea37de61b..af8017b0c0 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -127,7 +127,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) { t.Fatalf("failed to create call tracer: %v", err) } state.StateDB.SetLogger(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) } @@ -219,7 +219,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { Difficulty: (*big.Int)(test.Context.Difficulty), GasLimit: uint64(test.Context.GasLimit), } - 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 f47cf5abb9..125b85fa09 100644 --- a/eth/tracers/internal/tracetest/flat_calltrace_test.go +++ b/eth/tracers/internal/tracetest/flat_calltrace_test.go @@ -94,7 +94,7 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string } state.StateDB.SetLogger(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 { 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 3affcacb7e..5824adb4ea 100644 --- a/eth/tracers/internal/tracetest/prestate_test.go +++ b/eth/tracers/internal/tracetest/prestate_test.go @@ -104,7 +104,7 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) { } state.StateDB.SetLogger(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) } diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index e0476e4c8e..4204136deb 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 ae02a39e03..e5d036b3a9 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1285,7 +1285,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 32459245f1..f7d9be34d5 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -207,7 +207,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), sim.state) result, err := applyMessageWithEVM(ctx, evm, msg, sim.state, timeout, sim.gp) if err != nil { diff --git a/miner/worker.go b/miner/worker.go index 2312ff555a..2ea7c89349 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -293,7 +293,7 @@ func (miner *Miner) prepareWork(genParams *generateParams, witness bool) (*envir return nil, err } 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 }