Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion eth/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,15 @@ func MarshalReceipt(
"effectiveGasPrice": (*hexutil.Big)(receipt.EffectiveGasPrice),
}

if _, ok := tx.(models.DirectCall); ok {
// Dynamically fallback to `BaseFeePerGas` when computing `EffectiveGasPrice`
// to fix historical gasPrice = 0 issues.
// This avoids the need to re-index the entire chain for previously stored
// transactions. For any transaction that had a `0` gas price, regardless
// whether they were COA interactions or regular EVM, we
// set the `effectiveGasPrice` to the value of `BaseFeePerGas`,
// which is the minimum amount of gas price required by any
// transaction, in order to comply with EIP-1559.
if receipt.EffectiveGasPrice.Sign() == 0 {
fields["effectiveGasPrice"] = (*hexutil.Big)(models.BaseFeePerGas)
}

Expand Down
2 changes: 2 additions & 0 deletions models/receipt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ func Test_DecodeReceipts(t *testing.T) {
_, receipt, _, err := decodeTransactionEvent(cdcEv)
require.NoError(t, err)

assert.Equal(t, BaseFeePerGas, receipt.EffectiveGasPrice)

for i, l := range rec.Logs {
assert.ObjectsAreEqualValues(l, receipt.Logs[i])
for j, tt := range l.Topics {
Expand Down
58 changes: 57 additions & 1 deletion models/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,58 @@ type TransactionCall struct {
*gethTypes.Transaction
}

func (tc TransactionCall) GasPrice() *big.Int {
// EIP-1559 introduced a new fee model in Ethereum that replaces the
// legacy `GasPrice` with `MaxFeePerGas` and `MaxPriorityFeePerGas`.
// However, many Ethereum tools and wallets (such as MetaMask, Hardhat, etc.)
// still rely on reading `GasPrice`, even if it’s not explicitly set.
//
// When a user submits an EIP-1559 transaction type, `GasPrice` is not
// specified, Ethereum nodes typically return the current `BaseFeePerGas`
// as the effective `GasPrice` for compatibility reasons.
//
// This behavior is mirrored here in Flow EVM Gateway: if `GasPrice` is zero,
// we return the configured `BaseFeePerGas` to satisfy the expectations of
// these tools.
//
// This does NOT affect Flow’s actual transaction fee calculation,
// this is purely for compatibility.
if tc.Transaction.GasPrice().Sign() == 0 {
return BaseFeePerGas
}
return tc.Transaction.GasPrice()
}

func (tc TransactionCall) GasFeeCap() *big.Int {
// `GasFeeCap` represents the `MaxFeePerGas` in EIP-1559, the maximum fee
// a user is willing to pay. Ethereum clients expect a non-zero value when
// calculating effective gas prices for compatibility.
//
// If the user does not provide a value (zero), this method returns the
// configured `BaseFeePerGas` to avoid confusion and comply with expected
// EVM behavior. This ensures Ethereum tooling can still function correctly
// when interacting with Flow EVM.
if tc.Transaction.GasFeeCap().Sign() == 0 {
return BaseFeePerGas
}
return tc.Transaction.GasFeeCap()
}

func (tc TransactionCall) GasTipCap() *big.Int {
// `GasTipCap` represents the `MaxPriorityFeePerGas` in EIP-1559, the optional
// "tip" to incentivize block inclusion. Ethereum expects this value to be
// explicitly defined or it defaults to something reasonable like the base fee.
//
// To satisfy Ethereum clients and maintain expected behavior, when this value
// is zero, Flow EVM returns the configured `BaseFeePerGas` as a safe default.
// This ensures Ethereum tools can continue to compute `EffectiveGasPrice`
// without errors.
if tc.Transaction.GasTipCap().Sign() == 0 {
return BaseFeePerGas
}
return tc.Transaction.GasTipCap()
}

func (tc TransactionCall) Hash() common.Hash {
return tc.Transaction.Hash()
}
Expand Down Expand Up @@ -246,7 +298,11 @@ func decodeTransactionEvent(event cadence.Event) (
err,
)
}
receipt.EffectiveGasPrice = gethTx.EffectiveGasTipValue(nil)
if gethTx.GasPrice().Sign() == 0 {
receipt.EffectiveGasPrice = BaseFeePerGas
} else {
receipt.EffectiveGasPrice = gethTx.EffectiveGasTipValue(nil)
}
tx = TransactionCall{Transaction: gethTx}
}

Expand Down
4 changes: 2 additions & 2 deletions models/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func Test_DecodeEVMTransaction(t *testing.T) {
assert.Equal(t, big.NewInt(0), decTx.Value())
assert.Equal(t, uint8(0), decTx.Type())
assert.Equal(t, uint64(125_000), decTx.Gas())
assert.Equal(t, big.NewInt(0), decTx.GasPrice())
assert.Equal(t, BaseFeePerGas, decTx.GasPrice())
assert.Equal(t, uint64(0), decTx.BlobGas())
assert.Equal(t, uint64(347), decTx.Size())
}
Expand Down Expand Up @@ -223,7 +223,7 @@ func Test_UnmarshalTransaction(t *testing.T) {
assert.Equal(t, big.NewInt(0), decTx.Value())
assert.Equal(t, uint8(0), decTx.Type())
assert.Equal(t, uint64(125_000), decTx.Gas())
assert.Equal(t, big.NewInt(0), decTx.GasPrice())
assert.Equal(t, BaseFeePerGas, decTx.GasPrice())
assert.Equal(t, uint64(0), decTx.BlobGas())
assert.Equal(t, uint64(347), decTx.Size())
})
Expand Down
2 changes: 2 additions & 0 deletions tests/web3js/eth_batch_retrieval_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ it('should retrieve batch transactions', async () => {
assert.equal(tx.type, 0, "wrong type")
assert.equal(tx.transactionIndex, i, "wrong index")
assert.isBelow(i, batchSize, "wrong batch size")
assert.equal(tx.gasPrice, 1n)

let receipt = await web3.eth.getTransactionReceipt(tx.hash)
cumulativeGasUsed += receipt.gasUsed

assert.equal(receipt.cumulativeGasUsed, cumulativeGasUsed)
assert.equal(receipt.effectiveGasPrice, 1n)
}
})
2 changes: 2 additions & 0 deletions tests/web3js/eth_non_interactive_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ it('should get transaction', async () => {
assert.isNotEmpty(tx.r)
assert.isNotEmpty(tx.s)
assert.equal(tx.transactionIndex, 2)
assert.equal(tx.gasPrice, 1n)

let rcp = await web3.eth.getTransactionReceipt(tx.hash)
assert.isNotEmpty(rcp)
Expand All @@ -272,6 +273,7 @@ it('should get transaction', async () => {
assert.equal(rcp.cumulativeGasUsed, 744655n)
assert.equal(rcp.transactionHash, tx.hash)
assert.equal(rcp.status, conf.successStatus)
assert.equal(rcp.effectiveGasPrice, 1n)
})

it('should return null for non-existing blocks and receipts', async () => {
Expand Down