Skip to content

Commit 3fdd51c

Browse files
authored
Merge pull request #830 from onflow/mpeter/fix-eip-1559-tx-fee-calculations
Fix transaction fee calculations to comply with `EIP-1559`
2 parents a801ed2 + e259aee commit 3fdd51c

6 files changed

Lines changed: 74 additions & 4 deletions

File tree

eth/types/types.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,15 @@ func MarshalReceipt(
536536
"effectiveGasPrice": (*hexutil.Big)(receipt.EffectiveGasPrice),
537537
}
538538

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

models/receipt_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ func Test_DecodeReceipts(t *testing.T) {
1313
_, receipt, _, err := decodeTransactionEvent(cdcEv)
1414
require.NoError(t, err)
1515

16+
assert.Equal(t, BaseFeePerGas, receipt.EffectiveGasPrice)
17+
1618
for i, l := range rec.Logs {
1719
assert.ObjectsAreEqualValues(l, receipt.Logs[i])
1820
for j, tt := range l.Topics {

models/transaction.go

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,58 @@ type TransactionCall struct {
160160
*gethTypes.Transaction
161161
}
162162

163+
func (tc TransactionCall) GasPrice() *big.Int {
164+
// EIP-1559 introduced a new fee model in Ethereum that replaces the
165+
// legacy `GasPrice` with `MaxFeePerGas` and `MaxPriorityFeePerGas`.
166+
// However, many Ethereum tools and wallets (such as MetaMask, Hardhat, etc.)
167+
// still rely on reading `GasPrice`, even if it’s not explicitly set.
168+
//
169+
// When a user submits an EIP-1559 transaction type, `GasPrice` is not
170+
// specified, Ethereum nodes typically return the current `BaseFeePerGas`
171+
// as the effective `GasPrice` for compatibility reasons.
172+
//
173+
// This behavior is mirrored here in Flow EVM Gateway: if `GasPrice` is zero,
174+
// we return the configured `BaseFeePerGas` to satisfy the expectations of
175+
// these tools.
176+
//
177+
// This does NOT affect Flow’s actual transaction fee calculation,
178+
// this is purely for compatibility.
179+
if tc.Transaction.GasPrice().Sign() == 0 {
180+
return BaseFeePerGas
181+
}
182+
return tc.Transaction.GasPrice()
183+
}
184+
185+
func (tc TransactionCall) GasFeeCap() *big.Int {
186+
// `GasFeeCap` represents the `MaxFeePerGas` in EIP-1559, the maximum fee
187+
// a user is willing to pay. Ethereum clients expect a non-zero value when
188+
// calculating effective gas prices for compatibility.
189+
//
190+
// If the user does not provide a value (zero), this method returns the
191+
// configured `BaseFeePerGas` to avoid confusion and comply with expected
192+
// EVM behavior. This ensures Ethereum tooling can still function correctly
193+
// when interacting with Flow EVM.
194+
if tc.Transaction.GasFeeCap().Sign() == 0 {
195+
return BaseFeePerGas
196+
}
197+
return tc.Transaction.GasFeeCap()
198+
}
199+
200+
func (tc TransactionCall) GasTipCap() *big.Int {
201+
// `GasTipCap` represents the `MaxPriorityFeePerGas` in EIP-1559, the optional
202+
// "tip" to incentivize block inclusion. Ethereum expects this value to be
203+
// explicitly defined or it defaults to something reasonable like the base fee.
204+
//
205+
// To satisfy Ethereum clients and maintain expected behavior, when this value
206+
// is zero, Flow EVM returns the configured `BaseFeePerGas` as a safe default.
207+
// This ensures Ethereum tools can continue to compute `EffectiveGasPrice`
208+
// without errors.
209+
if tc.Transaction.GasTipCap().Sign() == 0 {
210+
return BaseFeePerGas
211+
}
212+
return tc.Transaction.GasTipCap()
213+
}
214+
163215
func (tc TransactionCall) Hash() common.Hash {
164216
return tc.Transaction.Hash()
165217
}
@@ -246,7 +298,11 @@ func decodeTransactionEvent(event cadence.Event) (
246298
err,
247299
)
248300
}
249-
receipt.EffectiveGasPrice = gethTx.EffectiveGasTipValue(nil)
301+
if gethTx.GasPrice().Sign() == 0 {
302+
receipt.EffectiveGasPrice = BaseFeePerGas
303+
} else {
304+
receipt.EffectiveGasPrice = gethTx.EffectiveGasTipValue(nil)
305+
}
250306
tx = TransactionCall{Transaction: gethTx}
251307
}
252308

models/transaction_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func Test_DecodeEVMTransaction(t *testing.T) {
123123
assert.Equal(t, big.NewInt(0), decTx.Value())
124124
assert.Equal(t, uint8(0), decTx.Type())
125125
assert.Equal(t, uint64(125_000), decTx.Gas())
126-
assert.Equal(t, big.NewInt(0), decTx.GasPrice())
126+
assert.Equal(t, BaseFeePerGas, decTx.GasPrice())
127127
assert.Equal(t, uint64(0), decTx.BlobGas())
128128
assert.Equal(t, uint64(347), decTx.Size())
129129
}
@@ -223,7 +223,7 @@ func Test_UnmarshalTransaction(t *testing.T) {
223223
assert.Equal(t, big.NewInt(0), decTx.Value())
224224
assert.Equal(t, uint8(0), decTx.Type())
225225
assert.Equal(t, uint64(125_000), decTx.Gas())
226-
assert.Equal(t, big.NewInt(0), decTx.GasPrice())
226+
assert.Equal(t, BaseFeePerGas, decTx.GasPrice())
227227
assert.Equal(t, uint64(0), decTx.BlobGas())
228228
assert.Equal(t, uint64(347), decTx.Size())
229229
})

tests/web3js/eth_batch_retrieval_test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ it('should retrieve batch transactions', async () => {
1919
assert.equal(tx.type, 0, "wrong type")
2020
assert.equal(tx.transactionIndex, i, "wrong index")
2121
assert.isBelow(i, batchSize, "wrong batch size")
22+
assert.equal(tx.gasPrice, 1n)
2223

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

2627
assert.equal(receipt.cumulativeGasUsed, cumulativeGasUsed)
28+
assert.equal(receipt.effectiveGasPrice, 1n)
2729
}
2830
})

tests/web3js/eth_non_interactive_test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ it('should get transaction', async () => {
261261
assert.isNotEmpty(tx.r)
262262
assert.isNotEmpty(tx.s)
263263
assert.equal(tx.transactionIndex, 2)
264+
assert.equal(tx.gasPrice, 1n)
264265

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

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

0 commit comments

Comments
 (0)