Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

241 changes: 181 additions & 60 deletions frame/ethereum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ use frame_support::{
weights::Weight,
};
use frame_system::{pallet_prelude::OriginFor, CheckWeight, WeightInfo};
use pallet_evm::{BlockHashMapping, FeeCalculator, GasWeightMapping, Runner};
use pallet_evm::{BlockHashMapping, CurrentLogs, FeeCalculator, GasWeightMapping, Runner};
use sp_runtime::{
generic::DigestItem,
traits::{DispatchInfoOf, Dispatchable, One, Saturating, UniqueSaturatedInto, Zero},
Expand Down Expand Up @@ -342,6 +342,10 @@ pub mod pallet {
#[pallet::getter(fn block_hash)]
pub(super) type BlockHash<T: Config> = StorageMap<_, Twox64Concat, U256, H256, ValueQuery>;

/// Injected transactions should have unique nonce, here we store current
#[pallet::storage]
pub(super) type InjectedNonce<T: Config> = StorageValue<_, U256, ValueQuery>;

#[pallet::genesis_config]
#[derive(Default)]
pub struct GenesisConfig {}
Expand Down Expand Up @@ -408,7 +412,7 @@ impl<T: Config> Pallet<T> {
}
};
cumulative_gas_used = used_gas;
Self::logs_bloom(logs, &mut logs_bloom);
Self::logs_bloom(logs.iter(), &mut logs_bloom);
}

let ommers = Vec::<ethereum::Header>::new();
Expand Down Expand Up @@ -462,10 +466,10 @@ impl<T: Config> Pallet<T> {
}
}

fn logs_bloom(logs: Vec<Log>, bloom: &mut Bloom) {
fn logs_bloom<'a>(logs: impl IntoIterator<Item = &'a Log>, bloom: &'a mut Bloom) {
for log in logs {
bloom.accrue(BloomInput::Raw(&log.address[..]));
for topic in log.topics {
for topic in &log.topics {
bloom.accrue(BloomInput::Raw(&topic[..]));
}
}
Expand Down Expand Up @@ -549,68 +553,74 @@ impl<T: Config> Pallet<T> {
let transaction_index = pending.len() as u32;

let (reason, status, used_gas, dest, extra_data) = match info {
CallOrCreateInfo::Call(info) => (
info.exit_reason.clone(),
TransactionStatus {
transaction_hash,
transaction_index,
from: source,
to,
contract_address: None,
logs: info.logs.clone(),
logs_bloom: {
let mut bloom: Bloom = Bloom::default();
Self::logs_bloom(info.logs, &mut bloom);
bloom
CallOrCreateInfo::Call(info) => {
let logs = <CurrentLogs<T>>::take();
(
info.exit_reason.clone(),
TransactionStatus {
transaction_hash,
transaction_index,
from: source,
to,
contract_address: None,
logs_bloom: {
let mut bloom: Bloom = Bloom::default();
Self::logs_bloom(logs.iter(), &mut bloom);
bloom
},
logs,
},
},
info.used_gas,
to,
match info.exit_reason {
ExitReason::Revert(_) => {
const LEN_START: usize = 36;
const MESSAGE_START: usize = 68;

let data = info.value;
let data_len = data.len();
if data_len > MESSAGE_START {
let message_len = U256::from(&data[LEN_START..MESSAGE_START])
.saturated_into::<usize>();
let message_end = MESSAGE_START.saturating_add(
message_len.min(T::ExtraDataLength::get() as usize),
);

if data_len >= message_end {
data[MESSAGE_START..message_end].to_vec()
info.used_gas,
to,
match info.exit_reason {
ExitReason::Revert(_) => {
const LEN_START: usize = 36;
const MESSAGE_START: usize = 68;

let data = info.value;
let data_len = data.len();
if data_len > MESSAGE_START {
let message_len = U256::from(&data[LEN_START..MESSAGE_START])
.saturated_into::<usize>();
let message_end = MESSAGE_START.saturating_add(
message_len.min(T::ExtraDataLength::get() as usize),
);

if data_len >= message_end {
data[MESSAGE_START..message_end].to_vec()
} else {
data
}
} else {
data
}
} else {
data
}
}
_ => vec![],
},
),
CallOrCreateInfo::Create(info) => (
info.exit_reason,
TransactionStatus {
transaction_hash,
transaction_index,
from: source,
to,
contract_address: Some(info.value),
logs: info.logs.clone(),
logs_bloom: {
let mut bloom: Bloom = Bloom::default();
Self::logs_bloom(info.logs, &mut bloom);
bloom
_ => vec![],
},
},
info.used_gas,
Some(info.value),
Vec::new(),
),
)
}
CallOrCreateInfo::Create(info) => {
let logs = <CurrentLogs<T>>::take();
(
info.exit_reason,
TransactionStatus {
transaction_hash,
transaction_index,
from: source,
to,
contract_address: Some(info.value),
logs_bloom: {
let mut bloom: Bloom = Bloom::default();
Self::logs_bloom(logs.iter(), &mut bloom);
bloom
},
logs,
},
info.used_gas,
Some(info.value),
Vec::new(),
)
}
};

let receipt = {
Expand Down Expand Up @@ -670,6 +680,69 @@ impl<T: Config> Pallet<T> {
})
}

pub fn flush_injected_transaction() {
use ethereum::{
EIP658ReceiptData, EnvelopedEncodable, TransactionSignature, TransactionV0,
};

assert!(
fp_consensus::find_pre_log(&frame_system::Pallet::<T>::digest()).is_err(),
"this method is supposed to be called only from other pallets",
);

let logs = <CurrentLogs<T>>::take();
if logs.is_empty() {
return;
}

let nonce = <InjectedNonce<T>>::get()
.checked_add(1u32.into())
.expect("u256 should be enough");
<InjectedNonce<T>>::set(nonce);

let transaction = Transaction::Legacy(TransactionV0 {
nonce,
gas_price: 0.into(),
gas_limit: 0.into(),
action: TransactionAction::Call(H160([0; 20])),
value: 0.into(),
// zero selector, this transaction always has same sender, so all data should be acquired from logs
input: Vec::from([0, 0, 0, 0]),
// if v is not 27 - then we need to pass some other validity checks
signature: TransactionSignature::new(27, H256([0x88; 32]), H256([0x88; 32])).unwrap(),
});

let transaction_hash = H256::from_slice(
sp_io::hashing::keccak_256(&EnvelopedEncodable::encode(&transaction)).as_slice(),
);
let transaction_index = <Pending<T>>::get().len() as u32;

let logs_bloom = {
let mut bloom: Bloom = Bloom::default();
Self::logs_bloom(&logs, &mut bloom);
bloom
};

let status = TransactionStatus {
transaction_hash,
transaction_index,
from: H160::default(),
to: None,
contract_address: None,
logs_bloom,
logs: logs.clone(),
};

let receipt = Receipt::Legacy(EIP658ReceiptData {
status_code: 1,
used_gas: 0u32.into(),
logs_bloom,
logs,
});

<Pending<T>>::append((transaction, status, receipt));
}

/// Get current block hash
pub fn current_block_hash() -> Option<H256> {
Self::current_block().map(|block| block.header.hash())
Expand Down Expand Up @@ -969,3 +1042,51 @@ impl From<InvalidEvmTransactionError> for InvalidTransactionWrapper {
}
}
}

#[derive(TypeInfo, PartialEq, Eq, Clone, Debug, Encode, Decode, Default)]
pub struct FakeTransactionFinalizer<T>(PhantomData<T>);

impl<T> FakeTransactionFinalizer<T> {
pub fn new() -> Self {
Self(Default::default())
}
}

impl<T: Config + TypeInfo + core::fmt::Debug + Send + Sync> sp_runtime::traits::SignedExtension
for FakeTransactionFinalizer<T>
{
const IDENTIFIER: &'static str = "FakeTransactionFinalizer";

type AccountId = T::AccountId;

type Call = T::RuntimeCall;

type AdditionalSigned = ();

type Pre = ();

fn additional_signed(&self) -> Result<Self::AdditionalSigned, TransactionValidityError> {
Ok(())
}

fn pre_dispatch(
self,
_who: &Self::AccountId,
_call: &Self::Call,
_info: &DispatchInfoOf<Self::Call>,
_len: usize,
) -> Result<Self::Pre, TransactionValidityError> {
Ok(())
}

fn post_dispatch(
_pre: Option<Self::Pre>,
_info: &DispatchInfoOf<Self::Call>,
_post_info: &sp_runtime::traits::PostDispatchInfoOf<Self::Call>,
_len: usize,
_result: &sp_runtime::DispatchResult,
) -> Result<(), TransactionValidityError> {
<Pallet<T>>::flush_injected_transaction();
Ok(())
}
}
27 changes: 27 additions & 0 deletions frame/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ pub mod pallet {
}
}

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_finalize(_: T::BlockNumber) {
assert_eq!(<CurrentLogs<T>>::get().len(), 0, "fake transaction finalizer is not initialized, as some logs was left after block is finished");
}
}

#[pallet::call]
impl<T: Config> Pallet<T> {
/// Withdraw balance from EVM into currency/balances pallet.
Expand Down Expand Up @@ -510,6 +517,11 @@ pub mod pallet {
#[pallet::getter(fn account_storages)]
pub type AccountStorages<T: Config> =
StorageDoubleMap<_, Blake2_128Concat, H160, Blake2_128Concat, H256, H256, ValueQuery>;

/// Written on log, reset after transaction
/// Should be empty between transactions
#[pallet::storage]
pub type CurrentLogs<T: Config> = StorageValue<_, Vec<Log>, ValueQuery>;
}

/// Type alias for currency balance.
Expand Down Expand Up @@ -733,6 +745,21 @@ impl<T: Config> Pallet<T> {
<AccountCodes<T>>::insert(address, code);
}

/// Add log to be injected in either real or fake ethereum transaction
pub fn deposit_log(log: Log) {
log::trace!(
target: "evm",
"Inserting mirrored log for {:?}, topics ({}) {:?}, data ({}): {:?}]",
log.address,
log.topics.len(),
log.topics,
log.data.len(),
log.data
);
<CurrentLogs<T>>::append(log);
// Log event is not emitted here, as these logs belong to pallets, which will emit pallet-specific logs on substrate side by themselves
}

/// Get the account basic in EVM format.
pub fn account_basic(address: &H160) -> (Account, frame_support::weights::Weight) {
let account_id = T::AddressMapping::into_account_id(*address);
Expand Down
Loading