use crate::{
dispatch_result,
evm::{
block_hash::{AccumulateReceipt, EthereumBlockBuilder, LogsBloom},
burn_with_dust,
fees::InfoT,
},
limits,
sp_runtime::traits::{One, Zero},
weights::WeightInfo,
AccountIdOf, BalanceOf, BalanceWithDust, BlockHash, BlockNumberFor, Config, ContractResult,
Error, EthBlockBuilderIR, EthereumBlock, Event, ExecReturnValue, Pallet, ReceiptGasInfo,
ReceiptInfoData, StorageDeposit, Weight, H160, H256, LOG_TARGET,
};
use alloc::vec::Vec;
use environmental::environmental;
use frame_support::{
dispatch::DispatchInfo,
pallet_prelude::{DispatchError, DispatchResultWithPostInfo},
storage::with_transaction,
};
use sp_core::U256;
use sp_runtime::{Saturating, TransactionOutcome};
pub const BLOCK_HASH_COUNT: u32 = 256;
environmental!(receipt: AccumulateReceipt);
pub(crate) struct EthereumCallResult {
pub receipt_gas_info: ReceiptGasInfo,
pub result: DispatchResultWithPostInfo,
}
impl EthereumCallResult {
pub(crate) fn new<T: Config>(
signer: AccountIdOf<T>,
mut output: ContractResult<ExecReturnValue, BalanceOf<T>>,
base_call_weight: Weight,
encoded_len: u32,
info: &DispatchInfo,
effective_gas_price: U256,
) -> Self {
let effective_gas_price = effective_gas_price.max(Pallet::<T>::evm_base_fee());
if let Ok(retval) = &output.result {
if retval.did_revert() {
output.result = Err(<Error<T>>::ContractReverted.into());
}
}
if output.result.is_ok() {
output
.weight_consumed
.saturating_reduce(T::WeightInfo::deposit_eth_extrinsic_revert_event())
}
let result = dispatch_result(output.result, output.weight_consumed, base_call_weight);
let native_fee = T::FeeInfo::compute_actual_fee(encoded_len, &info, &result);
let result = T::FeeInfo::ensure_not_overdrawn(native_fee, result);
let fee = Pallet::<T>::convert_native_to_evm(match output.storage_deposit {
StorageDeposit::Refund(refund) => native_fee.saturating_sub(refund),
StorageDeposit::Charge(amount) => native_fee.saturating_add(amount),
});
let (mut gas_used, rest) = fee.div_mod(effective_gas_price);
if !rest.is_zero() {
gas_used = gas_used.saturating_add(1_u32.into());
}
let tx_cost = gas_used.saturating_mul(effective_gas_price);
if tx_cost > fee {
let round_up_fee = BalanceWithDust::<BalanceOf<T>>::from_value::<T>(tx_cost - fee)
.expect("value fits into BalanceOf<T>; qed");
log::debug!(target: LOG_TARGET, "Collecting round_up fee from {signer:?}: {round_up_fee:?}");
let _ = burn_with_dust::<T>(&signer, round_up_fee)
.inspect_err(|e| log::debug!(target: LOG_TARGET, "Failed to collect round up fee {round_up_fee:?} from {signer:?}: {e:?}"));
}
Self { receipt_gas_info: ReceiptGasInfo { gas_used, effective_gas_price }, result }
}
}
pub fn capture_ethereum_log(contract: &H160, data: &[u8], topics: &[H256]) {
receipt::with(|receipt| {
receipt.add_log(contract, data, topics);
});
}
pub fn get_receipt_details() -> Option<(Vec<u8>, LogsBloom)> {
receipt::with(|receipt| {
let encoding = core::mem::take(&mut receipt.encoding);
let bloom = core::mem::take(&mut receipt.bloom);
(encoding, bloom)
})
}
#[cfg(feature = "runtime-benchmarks")]
pub fn bench_with_ethereum_context<R>(f: impl FnOnce() -> R) -> R {
receipt::using(&mut AccumulateReceipt::new(), f)
}
pub fn with_ethereum_context<T: Config>(
transaction_encoded: Vec<u8>,
call: impl FnOnce() -> EthereumCallResult,
) -> DispatchResultWithPostInfo {
receipt::using(&mut AccumulateReceipt::new(), || {
let (err, receipt_gas_info, post_info) =
with_transaction(|| -> TransactionOutcome<Result<_, DispatchError>> {
let EthereumCallResult { receipt_gas_info, result } = call();
match result {
Ok(post_info) =>
TransactionOutcome::Commit(Ok((None, receipt_gas_info, post_info))),
Err(err) => TransactionOutcome::Rollback(Ok((
Some(err.error),
receipt_gas_info,
err.post_info,
))),
}
})?;
if let Some(dispatch_error) = err {
deposit_eth_extrinsic_revert_event::<T>(dispatch_error);
crate::block_storage::process_transaction::<T>(
transaction_encoded,
false,
receipt_gas_info,
);
Ok(post_info)
} else {
#[cfg(feature = "runtime-benchmarks")]
deposit_eth_extrinsic_revert_event::<T>(crate::Error::<T>::BenchmarkingError.into());
crate::block_storage::process_transaction::<T>(
transaction_encoded,
true,
receipt_gas_info,
);
Ok(post_info)
}
})
}
fn deposit_eth_extrinsic_revert_event<T: Config>(dispatch_error: DispatchError) {
Pallet::<T>::deposit_event(Event::<T>::EthExtrinsicRevert { dispatch_error });
}
pub fn on_initialize<T: Config>() {
ReceiptInfoData::<T>::kill();
EthereumBlock::<T>::kill();
}
pub fn on_finalize_build_eth_block<T: Config>(block_number: BlockNumberFor<T>) {
let block_builder_ir = EthBlockBuilderIR::<T>::get();
EthBlockBuilderIR::<T>::kill();
let (block, receipt_data) =
EthereumBlockBuilder::<T>::from_ir(block_builder_ir).build_block(block_number);
BlockHash::<T>::insert(block_number, block.hash);
let block_hash_count = BLOCK_HASH_COUNT;
let to_remove = block_number.saturating_sub(block_hash_count.into()).saturating_sub(One::one());
if !Zero::is_zero(&to_remove) {
<BlockHash<T>>::remove(to_remove);
}
EthereumBlock::<T>::put(block);
ReceiptInfoData::<T>::put(receipt_data);
}
pub fn process_transaction<T: Config>(
transaction_encoded: Vec<u8>,
success: bool,
receipt_gas_info: ReceiptGasInfo,
) {
let (encoded_logs, bloom) = get_receipt_details().unwrap_or_default();
let block_builder_ir = EthBlockBuilderIR::<T>::get();
let mut block_builder = EthereumBlockBuilder::<T>::from_ir(block_builder_ir);
block_builder.process_transaction(
transaction_encoded,
success,
receipt_gas_info,
encoded_logs,
bloom,
);
EthBlockBuilderIR::<T>::put(block_builder.to_ir());
}
pub fn block_builder_bytes_usage(max_events_size: u32) -> u32 {
const MEMORY_COEFFICIENT: u32 = 3;
let receipts_hash_builder = max_events_size;
let transactions_hash_builder =
limits::MAX_TRANSACTION_PAYLOAD_SIZE.saturating_mul(MEMORY_COEFFICIENT);
receipts_hash_builder.saturating_add(transactions_hash_builder)
}