Skip to content

Commit

Permalink
Fix receipt effectiveGasPrice calculation (#218)
Browse files Browse the repository at this point in the history
* Fix receipt effectiveGasPrice calculation

For non dynamic txs we can always use the gas price.

For dynamic txs we need the base fee to be able to calculate the
effectiveGasPrice.

Pre gingerbread we don't have it, because we have
no way to get it from the state, and most node's won't have the state.

Post gingerbread, we have the base fee on the block so we can calculate
the effectiveGasPrice for dynamic txs as long as they don't have a
custom fee currency, if they do we can't calculate the effectiveGasPrice
because we have no way to get the exchange rate from the state.

* Use inner tx function to calcluate receipt effectiveGasPrice

* Add tests for effective gas price receipt derivation

* Remove unused config variable

* Add test case for BlobTx

* Add condition to support celo denominated txs

* Convert inner comments to docstrings

* Fix test for CeloDenominatedTx type

* Add support for chain config without gingerbred

This is to allow upstream tests to function correctly.
  • Loading branch information
piersy authored Sep 17, 2024
1 parent 377ebf2 commit bbf17c7
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 10 deletions.
126 changes: 126 additions & 0 deletions core/types/celo_receipt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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)
}
}
31 changes: 21 additions & 10 deletions core/types/receipt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

Expand Down

0 comments on commit bbf17c7

Please sign in to comment.