Skip to content

Commit 23d90a0

Browse files
revive fix reported gas used (#10148)
Fix `gas_used` calculation introduced in #9418 to use the actual gas instead of just `ref_time`. With these changes we now guarantee that `tx_cost = effective_gas_price * gas`. Note that since we compute gas as `fee / gas_price`, this can lead to rounding errors when the chain uses `SlowAdjustingFeeUpdate` (i.e. the fee is not a multiple of the gas price). The changes in this PR ensure the fee still matches by burning the rounding remainder. This PR also fixes how the actual fee is computed and introduces a new `compute_actual_fee` in `Config::FeeInfo`. The previous fee calculation was skipping the `extension_weight` in the fee calculation. The updated tests ensure that the tx cost reported in the receipt matches the fees deducted from the user account: https://github.com/paritytech/evm-test-suite/blob/460b2c9aa3a3019d3508bb5a34a2498ea86035ff/src/gas.test.ts?plain=1#L31-L61 --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 41bc46f commit 23d90a0

File tree

18 files changed

+653
-449
lines changed

18 files changed

+653
-449
lines changed

.github/workflows/tests-evm.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ jobs:
9999
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
100100
with:
101101
repository: paritytech/evm-test-suite
102-
ref: c2422cace2fb8a4337fc1c704c49e458c8a79d6b
102+
ref: 460b2c9aa3a3019d3508bb5a34a2498ea86035ff
103103
path: evm-test-suite
104104

105105
- uses: denoland/setup-deno@v1

prdoc/pr_10148.prdoc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
title: revive fix reported gas used
2+
doc:
3+
- audience: Runtime Dev
4+
description: |-
5+
Fix `gas_used` calculation introduced in #9418 to use the actual gas instead of just `ref_time`.
6+
7+
With these changes we now guarantee that `tx_cost = effective_gas_price * gas`.
8+
Note that since we compute gas as `fee / gas_price`, this can lead to rounding errors when the chain uses `SlowAdjustingFeeUpdate` (i.e. the fee is not a multiple of the gas price).
9+
The changes in this PR ensure the fee still matches by burning the rounding remainder.
10+
11+
This PR also fixes how the actual fee is computed and introduces a new `compute_actual_fee` in `Config::FeeInfo`.
12+
The previous fee calculation was skipping the `extension_weight` in the fee calculation.
13+
14+
The updated tests ensure that the tx cost reported in the receipt matches the fees deducted from the user account:
15+
16+
https://github.com/paritytech/evm-test-suite/blob/460b2c9aa3a3019d3508bb5a34a2498ea86035ff/src/gas.test.ts?plain=1#L31-L61
17+
crates:
18+
- name: pallet-revive
19+
bump: patch
20+
- name: revive-dev-runtime
21+
bump: patch
22+
- name: pallet-revive-eth-rpc
23+
bump: patch

substrate/frame/revive/rpc/src/receipt_extractor.rs

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
use crate::{
1818
client::{runtime_api::RuntimeApi, SubstrateBlock, SubstrateBlockNumber},
1919
subxt_client::{
20-
self,
2120
revive::{
2221
calls::types::EthTransact,
2322
events::{ContractEmitted, EthExtrinsicRevert},
@@ -36,10 +35,6 @@ use sp_core::keccak_256;
3635
use std::{future::Future, pin::Pin, sync::Arc};
3736
use subxt::{blocks::ExtrinsicDetails, OnlineClient};
3837

39-
type FetchGasPriceFn = Arc<
40-
dyn Fn(H256) -> Pin<Box<dyn Future<Output = Result<U256, ClientError>> + Send>> + Send + Sync,
41-
>;
42-
4338
type FetchReceiptDataFn = Arc<
4439
dyn Fn(H256) -> Pin<Box<dyn Future<Output = Option<Vec<ReceiptGasInfo>>> + Send>> + Send + Sync,
4540
>;
@@ -58,9 +53,6 @@ pub struct ReceiptExtractor {
5853
/// Fetch ethereum block hash.
5954
fetch_eth_block_hash: FetchEthBlockHashFn,
6055

61-
/// Fetch the gas price from the chain.
62-
fetch_gas_price: FetchGasPriceFn,
63-
6456
/// Earliest block number to consider when searching for transaction receipts.
6557
earliest_receipt_block: Option<SubstrateBlockNumber>,
6658

@@ -109,20 +101,6 @@ impl ReceiptExtractor {
109101
Box::pin(fut) as Pin<Box<_>>
110102
});
111103

112-
let api_inner = api.clone();
113-
let fetch_gas_price = Arc::new(move |block_hash| {
114-
let api_inner = api_inner.clone();
115-
116-
let fut = async move {
117-
let runtime_api = api_inner.runtime_api().at(block_hash);
118-
let payload = subxt_client::apis().revive_api().gas_price();
119-
let base_gas_price = runtime_api.call(payload).await?;
120-
Ok(*base_gas_price)
121-
};
122-
123-
Box::pin(fut) as Pin<Box<_>>
124-
});
125-
126104
let api_inner = api.clone();
127105
let fetch_receipt_data = Arc::new(move |block_hash| {
128106
let api_inner = api_inner.clone();
@@ -138,7 +116,6 @@ impl ReceiptExtractor {
138116
Ok(Self {
139117
fetch_receipt_data,
140118
fetch_eth_block_hash,
141-
fetch_gas_price,
142119
earliest_receipt_block,
143120
recover_eth_address: recover_eth_address_fn,
144121
})
@@ -154,13 +131,10 @@ impl ReceiptExtractor {
154131
let eth_block_hash = H256::from(keccak_256(&bytes));
155132
Box::pin(std::future::ready(Some(eth_block_hash))) as Pin<Box<_>>
156133
});
157-
let fetch_gas_price =
158-
Arc::new(|_| Box::pin(std::future::ready(Ok(U256::from(1000)))) as Pin<Box<_>>);
159134

160135
Self {
161136
fetch_receipt_data,
162137
fetch_eth_block_hash,
163-
fetch_gas_price,
164138
earliest_receipt_block: None,
165139
recover_eth_address: Arc::new(|signed_tx: &TransactionSigned| {
166140
signed_tx.recover_eth_address()
@@ -197,11 +171,11 @@ impl ReceiptExtractor {
197171
ClientError::RecoverEthAddressFailed
198172
})?;
199173

200-
let base_gas_price = (self.fetch_gas_price)(substrate_block.hash()).await?;
201-
let tx_info =
202-
GenericTransaction::from_signed(signed_tx.clone(), base_gas_price, Some(from));
203-
204-
let gas_price = tx_info.gas_price.unwrap_or_default();
174+
let tx_info = GenericTransaction::from_signed(
175+
signed_tx.clone(),
176+
receipt_gas_info.effective_gas_price,
177+
Some(from),
178+
);
205179

206180
// get logs from ContractEmitted event
207181
let logs = events
@@ -244,7 +218,7 @@ impl ReceiptExtractor {
244218
from,
245219
logs,
246220
tx_info.to,
247-
gas_price,
221+
receipt_gas_info.effective_gas_price,
248222
U256::from(receipt_gas_info.gas_used),
249223
success,
250224
transaction_hash,

substrate/frame/revive/src/benchmarking.rs

Lines changed: 32 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -287,10 +287,13 @@ mod benchmarks {
287287
c: Linear<0, { 100 * 1024 }>,
288288
i: Linear<0, { limits::CALLDATA_BYTES }>,
289289
d: Linear<0, 1>,
290-
) {
291-
let pallet_account = whitelisted_pallet_account::<T>();
290+
) -> Result<(), BenchmarkError> {
292291
let input = vec![42u8; i as usize];
293292

293+
// Use an `effective_gas_price` that is not a multiple of `T::NativeToEthRatio`
294+
// to hit the code that charge the rounding error so that tx_cost == effective_gas_price *
295+
// gas_used
296+
let effective_gas_price = Pallet::<T>::evm_base_fee() + 1;
294297
let value = Pallet::<T>::min_balance();
295298
let dust = 42u32 * d;
296299
let evm_value =
@@ -303,7 +306,6 @@ mod benchmarks {
303306
let deployer = T::AddressMapper::to_address(&caller);
304307
let nonce = System::<T>::account_nonce(&caller).try_into().unwrap_or_default();
305308
let addr = crate::address::create1(&deployer, nonce);
306-
let account_id = T::AddressMapper::to_fallback_account_id(&addr);
307309

308310
assert!(AccountInfoOf::<T>::get(&deployer).is_none());
309311

@@ -319,33 +321,13 @@ mod benchmarks {
319321
code,
320322
input,
321323
TransactionSigned::default().signed_payload(),
322-
0u32.into(),
324+
effective_gas_price,
323325
0,
324326
);
325327

326-
let deposit =
327-
T::Currency::balance_on_hold(&HoldReason::StorageDepositReserve.into(), &account_id);
328-
// uploading the code reserves some balance in the pallet account
329-
let code_deposit = T::Currency::balance_on_hold(
330-
&HoldReason::CodeUploadDepositReserve.into(),
331-
&pallet_account,
332-
);
333-
let mapping_deposit =
334-
T::Currency::balance_on_hold(&HoldReason::AddressMapping.into(), &caller);
335-
336-
assert_eq!(
337-
<T as Config>::FeeInfo::remaining_txfee(),
338-
caller_funding::<T>() - deposit - code_deposit - Pallet::<T>::min_balance(),
339-
);
340-
assert_eq!(
341-
Pallet::<T>::evm_balance(&deployer),
342-
Pallet::<T>::convert_native_to_evm(
343-
caller_funding::<T>() - Pallet::<T>::min_balance() - value - mapping_deposit,
344-
) - dust,
345-
);
346-
347328
// contract has the full value
348329
assert_eq!(Pallet::<T>::evm_balance(&addr), evm_value);
330+
Ok(())
349331
}
350332

351333
#[benchmark(pov_mode = Measured)]
@@ -451,11 +433,14 @@ mod benchmarks {
451433
// `d`: with or without dust value to transfer
452434
#[benchmark(pov_mode = Measured)]
453435
fn eth_call(d: Linear<0, 1>) -> Result<(), BenchmarkError> {
454-
let pallet_account = whitelisted_pallet_account::<T>();
455436
let data = vec![42u8; 1024];
456437
let instance =
457438
Contract::<T>::with_caller(whitelisted_caller(), VmBinaryModule::dummy(), vec![])?;
458439

440+
// Use an `effective_gas_price` that is not a multiple of `T::NativeToEthRatio`
441+
// to hit the code that charge the rounding error so that tx_cost == effective_gas_price *
442+
// gas_used
443+
let effective_gas_price = Pallet::<T>::evm_base_fee() + 1;
459444
let value = Pallet::<T>::min_balance();
460445
let dust = 42u32 * d;
461446
let evm_value =
@@ -466,7 +451,6 @@ mod benchmarks {
466451
<T as Config>::Currency::issue(caller_funding::<T>()),
467452
);
468453

469-
let caller_addr = T::AddressMapper::to_address(&instance.caller);
470454
let origin = Origin::EthTransaction(instance.caller.clone());
471455
let before = Pallet::<T>::evm_balance(&instance.address);
472456

@@ -478,30 +462,9 @@ mod benchmarks {
478462
Weight::MAX,
479463
data,
480464
TransactionSigned::default().signed_payload(),
481-
0u32.into(),
465+
effective_gas_price,
482466
0,
483467
);
484-
let deposit = T::Currency::balance_on_hold(
485-
&HoldReason::StorageDepositReserve.into(),
486-
&instance.account_id,
487-
);
488-
let code_deposit = T::Currency::balance_on_hold(
489-
&HoldReason::CodeUploadDepositReserve.into(),
490-
&pallet_account,
491-
);
492-
let mapping_deposit =
493-
T::Currency::balance_on_hold(&HoldReason::AddressMapping.into(), &instance.caller);
494-
// value and value transferred via call should be removed from the caller
495-
assert_eq!(
496-
Pallet::<T>::evm_balance(&caller_addr),
497-
Pallet::<T>::convert_native_to_evm(
498-
caller_funding::<T>() -
499-
Pallet::<T>::min_balance() -
500-
Pallet::<T>::min_balance() -
501-
value - deposit - code_deposit -
502-
mapping_deposit,
503-
) - dust,
504-
);
505468

506469
// contract should have received the value
507470
assert_eq!(Pallet::<T>::evm_balance(&instance.address), before + evm_value);
@@ -2741,7 +2704,10 @@ mod benchmarks {
27412704

27422705
// Create input data of fixed size for consistent transaction payloads
27432706
let input_data = vec![0x42u8; fixed_payload_size];
2744-
let gas_used = Weight::from_parts(1_000_000, 1000);
2707+
let receipt_gas_info = ReceiptGasInfo {
2708+
gas_used: U256::from(1_000_000),
2709+
effective_gas_price: Pallet::<T>::evm_base_fee(),
2710+
};
27452711

27462712
for _ in 0..n {
27472713
// Create real signed transaction with fixed-size input data
@@ -2763,7 +2729,7 @@ mod benchmarks {
27632729
block_builder.process_transaction(
27642730
signed_transaction,
27652731
true,
2766-
gas_used,
2732+
receipt_gas_info.clone(),
27672733
encoded_logs,
27682734
bloom,
27692735
);
@@ -2814,7 +2780,10 @@ mod benchmarks {
28142780

28152781
// Create input data of variable size p for realistic transaction payloads
28162782
let input_data = vec![0x42u8; d as usize];
2817-
let gas_used = Weight::from_parts(1_000_000, 1000);
2783+
let receipt_gas_info = ReceiptGasInfo {
2784+
gas_used: U256::from(1_000_000),
2785+
effective_gas_price: Pallet::<T>::evm_base_fee(),
2786+
};
28182787

28192788
for _ in 0..fixed_tx_count {
28202789
// Create real signed transaction with variable-size input data
@@ -2836,7 +2805,7 @@ mod benchmarks {
28362805
block_builder.process_transaction(
28372806
signed_transaction,
28382807
true,
2839-
gas_used,
2808+
receipt_gas_info.clone(),
28402809
encoded_logs,
28412810
bloom,
28422811
);
@@ -2888,7 +2857,10 @@ mod benchmarks {
28882857
input_data.clone(),
28892858
);
28902859

2891-
let gas_used = Weight::from_parts(1_000_000, 1000);
2860+
let receipt_gas_info = ReceiptGasInfo {
2861+
gas_used: U256::from(1_000_000),
2862+
effective_gas_price: Pallet::<T>::evm_base_fee(),
2863+
};
28922864

28932865
// Store transaction
28942866
let _ = block_storage::bench_with_ethereum_context(|| {
@@ -2900,7 +2872,7 @@ mod benchmarks {
29002872
block_builder.process_transaction(
29012873
signed_transaction,
29022874
true,
2903-
gas_used,
2875+
receipt_gas_info.clone(),
29042876
encoded_logs,
29052877
bloom,
29062878
);
@@ -2950,7 +2922,10 @@ mod benchmarks {
29502922
input_data.clone(),
29512923
);
29522924

2953-
let gas_used = Weight::from_parts(1_000_000, 1000);
2925+
let receipt_gas_info = ReceiptGasInfo {
2926+
gas_used: U256::from(1_000_000),
2927+
effective_gas_price: Pallet::<T>::evm_base_fee(),
2928+
};
29542929

29552930
// Store transaction
29562931
let _ = block_storage::bench_with_ethereum_context(|| {
@@ -2962,7 +2937,7 @@ mod benchmarks {
29622937
block_builder.process_transaction(
29632938
signed_transaction,
29642939
true,
2965-
gas_used,
2940+
receipt_gas_info,
29662941
encoded_logs,
29672942
bloom,
29682943
);

substrate/frame/revive/src/evm.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,8 @@ pub use block_hash::ReceiptGasInfo;
3535
/// Ethereum block storage module.
3636
pub(crate) mod block_storage;
3737

38+
/// Transfer with dust functionality.
39+
mod transfer_with_dust;
40+
pub(crate) use transfer_with_dust::*;
41+
3842
type OnChargeTransactionBalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::Balance;

substrate/frame/revive/src/evm/block_hash.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ use sp_core::{H256, U256};
4242
pub struct ReceiptGasInfo {
4343
/// The amount of gas used for this specific transaction alone.
4444
pub gas_used: U256,
45+
46+
/// The effective gas price for this transaction.
47+
pub effective_gas_price: U256,
4548
}
4649

4750
impl Block {

0 commit comments

Comments
 (0)