diff --git a/core/types/celo_receipt_test.go b/core/types/celo_receipt_test.go index 297e8504ae..3b48ceab98 100644 --- a/core/types/celo_receipt_test.go +++ b/core/types/celo_receipt_test.go @@ -6,7 +6,9 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/uint256" "github.com/stretchr/testify/require" ) @@ -101,3 +103,127 @@ func checkStorageRLPEncodeDecodeConsistency(r *ReceiptForStorage, t *testing.T) require.EqualValues(t, r, &r2) } + +// Tests that the effective gas price is correctly derived for different transaction types, in different scenarios. +func TestReceiptEffectiveGasPriceDerivation(t *testing.T) { + gasPrice := big.NewInt(1000) + gasFeeCap := big.NewInt(800) + gasTipCap := big.NewInt(100) + // Receipt base fee is the base fee encoded in the receipt which will be set post cel2 for CeloDynamicFeeTxV2 types. + receiptBaseFee := big.NewInt(50) + + t.Run("LegacyTx", func(t *testing.T) { + testNonDynamic(t, NewTransaction(0, common.Address{}, big.NewInt(0), 0, gasPrice, nil), gasPrice) + }) + t.Run("AccessListTx", func(t *testing.T) { + testNonDynamic(t, NewTx(&AccessListTx{GasPrice: gasPrice}), gasPrice) + }) + t.Run("DynamicFeeTx", func(t *testing.T) { + tx := NewTx(&DynamicFeeTx{GasFeeCap: gasFeeCap, GasTipCap: gasTipCap}) + testDynamic(t, tx, nil) + }) + t.Run("BlobTx", func(t *testing.T) { + tx := NewTx(&BlobTx{GasFeeCap: uint256.MustFromBig(gasFeeCap), GasTipCap: uint256.MustFromBig(gasTipCap)}) + testDynamic(t, tx, nil) + }) + t.Run("CeloDynamicFeeTx", func(t *testing.T) { + tx := NewTx(&CeloDynamicFeeTx{GasFeeCap: gasFeeCap, GasTipCap: gasTipCap}) + testDynamic(t, tx, nil) + tx = NewTx(&CeloDynamicFeeTx{GasFeeCap: gasFeeCap, GasTipCap: gasTipCap, FeeCurrency: &common.Address{}}) + testDynamicWithFeeCurrency(t, tx, nil) + }) + t.Run("CeloDynamicFeeTxV2", func(t *testing.T) { + tx := NewTx(&CeloDynamicFeeTxV2{GasFeeCap: gasFeeCap, GasTipCap: gasTipCap}) + testDynamic(t, tx, nil) + testDynamic(t, tx, receiptBaseFee) + tx = NewTx(&CeloDynamicFeeTxV2{GasFeeCap: gasFeeCap, GasTipCap: gasTipCap, FeeCurrency: &common.Address{}}) + testDynamicWithFeeCurrency(t, tx, nil) + testDynamicWithFeeCurrency(t, tx, receiptBaseFee) + }) + t.Run("CeloDenominatedTx", func(t *testing.T) { + tx := NewTx(&CeloDenominatedTx{GasFeeCap: gasFeeCap, GasTipCap: gasTipCap}) + testDynamic(t, tx, nil) + tx = NewTx(&CeloDenominatedTx{GasFeeCap: gasFeeCap, GasTipCap: gasTipCap, FeeCurrency: &common.Address{}}) + testDynamicWithFeeCurrency(t, tx, nil) + }) +} + +func testNonDynamic(t *testing.T, tx *Transaction, receiptBaseFee *big.Int) { + // Non dynamic txs should always have the gas price defined in the tx. + config := params.TestChainConfig + config.GingerbreadBlock = big.NewInt(1) + config.LondonBlock = big.NewInt(3) + preGingerbreadBlock := uint64(0) + postGingerbreadBlock := uint64(2) + + receipts := []*Receipt{{BaseFee: receiptBaseFee}} + txs := []*Transaction{tx} + + // Pre-gingerbread + err := Receipts(receipts).DeriveFields(config, blockHash, preGingerbreadBlock, blockTime, nil, nil, txs) + require.NoError(t, err) + require.Equal(t, tx.GasPrice(), receipts[0].EffectiveGasPrice) + + // Post-gingerbread + err = Receipts(receipts).DeriveFields(config, blockHash, postGingerbreadBlock, blockTime, baseFee, nil, txs) + require.NoError(t, err) + require.Equal(t, tx.GasPrice(), receipts[0].EffectiveGasPrice) +} + +// Dynamic txs with no fee currency should have nil for the effective gas price pre-gingerbread and the correct +// effective gas price post-gingerbread, if the receipt base fee is set then the post-gingerbread effective gas price +// should be calculated with that. +func testDynamic(t *testing.T, tx *Transaction, receiptBaseFee *big.Int) { + config := params.TestChainConfig + config.GingerbreadBlock = big.NewInt(1) + config.LondonBlock = big.NewInt(3) + preGingerbreadBlock := uint64(0) + postGingerbreadBlock := uint64(2) + receipts := []*Receipt{{BaseFee: receiptBaseFee}} + txs := []*Transaction{tx} + + // Pre-gingerbread + err := Receipts(receipts).DeriveFields(config, blockHash, preGingerbreadBlock, blockTime, nil, nil, txs) + require.NoError(t, err) + var nilBigInt *big.Int + require.Equal(t, nilBigInt, receipts[0].EffectiveGasPrice) + + // Post-gingerbread + err = Receipts(receipts).DeriveFields(config, blockHash, postGingerbreadBlock, blockTime, baseFee, nil, txs) + require.NoError(t, err) + if receiptBaseFee != nil { + require.Equal(t, tx.inner.effectiveGasPrice(new(big.Int), receiptBaseFee), receipts[0].EffectiveGasPrice) + } else { + require.Equal(t, tx.inner.effectiveGasPrice(new(big.Int), baseFee), receipts[0].EffectiveGasPrice) + } +} + +// Dynamic txs with a fee currency set should have nil for the effective gas price pre and post gingerbread, unless +// the receiptBaseFee is set, in which case the post-gingerbread effective gas price should be calculated with the +// receiptBaseFee. +func testDynamicWithFeeCurrency(t *testing.T, tx *Transaction, receiptBaseFee *big.Int) { + config := params.TestChainConfig + config.GingerbreadBlock = big.NewInt(1) + config.LondonBlock = big.NewInt(3) + preGingerbreadBlock := uint64(0) + postGingerbreadBlock := uint64(2) + receipts := []*Receipt{{BaseFee: receiptBaseFee}} + txs := []*Transaction{tx} + + // Pre-gingerbread + err := Receipts(receipts).DeriveFields(config, blockHash, preGingerbreadBlock, blockTime, nil, nil, txs) + require.NoError(t, err) + var nilBigInt *big.Int + require.Equal(t, nilBigInt, receipts[0].EffectiveGasPrice) + + // Post-gingerbread + err = Receipts(receipts).DeriveFields(config, blockHash, postGingerbreadBlock, blockTime, baseFee, nil, txs) + require.NoError(t, err) + if receiptBaseFee != nil { + require.Equal(t, tx.inner.effectiveGasPrice(new(big.Int), receiptBaseFee), receipts[0].EffectiveGasPrice) + } else if tx.Type() == CeloDenominatedTxType { + require.Equal(t, tx.inner.effectiveGasPrice(new(big.Int), baseFee), receipts[0].EffectiveGasPrice) + } else { + require.Equal(t, nilBigInt, receipts[0].EffectiveGasPrice) + } +} diff --git a/core/types/receipt.go b/core/types/receipt.go index 6c46d55265..19593bd32f 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -610,16 +610,27 @@ func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, nu rs[i].Type = txs[i].Type() rs[i].TxHash = txs[i].Hash() - // Pre-gingerbred the base fee was stored in state, but we don't try to recover it here, since A) we don't have - // access to the objects required to get the state and B) retrieving the base fee is quite code heavy and we - // don't want to bring that code across from the celo L1 to op-geth. In the celo L1 we would return a nil base - // fee if the state was not available, so that is what we do here. - if config.IsGingerbread(new(big.Int).SetUint64(number)) { - // The post transition CeloDynamicFeeV2Txs set the baseFee in the receipt - if rs[i].BaseFee == nil { - rs[i].EffectiveGasPrice = txs[i].inner.effectiveGasPrice(new(big.Int), baseFee) - } else { - rs[i].EffectiveGasPrice = txs[i].inner.effectiveGasPrice(new(big.Int), rs[i].BaseFee) + switch rs[i].Type { + case LegacyTxType, AccessListTxType: + // These are the non dynamic tx types so we can simply set effective gas price to gas price. + rs[i].EffectiveGasPrice = txs[i].inner.effectiveGasPrice(new(big.Int), baseFee) + default: + // Pre-gingerbred the base fee was stored in state, but we don't try to recover it here, since A) we don't + // have access to the objects required to get the state and B) retrieving the base fee is quite code heavy + // and we don't want to bring that code across from the celo L1 to op-geth. In the celo L1 we would return a + // nil base fee if the state was not available, so that is what we do here. + // + // We also check for the London hardfork here, in order to not break tests from upstream that have not + // configured the gingerbread block, since the london hardfork introduced dynamic fee transactions. + if config.IsGingerbread(new(big.Int).SetUint64(number)) || config.IsLondon(new(big.Int).SetUint64(number)) { + // The post transition CeloDynamicFeeV2Txs set the baseFee in the receipt, so if we have it use it. + // Otherwise we can set the effectiveGasPrice only if the transaction does not specify a fee currency, + // since we would need state to discover the true base fee. + if rs[i].BaseFee != nil { + rs[i].EffectiveGasPrice = txs[i].inner.effectiveGasPrice(new(big.Int), rs[i].BaseFee) + } else if txs[i].FeeCurrency() == nil || txs[i].Type() == CeloDenominatedTxType { + rs[i].EffectiveGasPrice = txs[i].inner.effectiveGasPrice(new(big.Int), baseFee) + } } }