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
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.

1 change: 1 addition & 0 deletions frame/evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ fp-evm = { version = "3.0.0-dev", path = "../../primitives/evm", default-feature

[dev-dependencies]
pallet-balances = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate", branch = "master" }
pallet-evm-precompile-simple = { path = "./precompile/simple" }

[features]
default = ["std"]
Expand Down
2 changes: 2 additions & 0 deletions frame/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,8 @@ pub mod pallet {
Undefined,
/// EVM reentrancy
Reentrancy,
/// EIP-3607,
TransactionMustComeFromEOA,
}

impl<T> From<InvalidEvmTransactionError> for Error<T> {
Expand Down
35 changes: 32 additions & 3 deletions frame/evm/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

//! Test mock for unit tests and benchmarking

use fp_evm::Precompile;
use frame_support::{
parameter_types,
traits::{ConstU32, FindAuthor},
Expand All @@ -30,7 +31,10 @@ use sp_runtime::{
};
use sp_std::{boxed::Box, prelude::*, str::FromStr};

use crate::{EnsureAddressNever, EnsureAddressRoot, FeeCalculator, IdentityAddressMapping};
use crate::{
EnsureAddressNever, EnsureAddressRoot, FeeCalculator, IdentityAddressMapping, PrecompileHandle,
PrecompileResult, PrecompileSet,
};

type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
Expand Down Expand Up @@ -125,6 +129,7 @@ impl FindAuthor<H160> for FindAuthorTruncated {
parameter_types! {
pub BlockGasLimit: U256 = U256::max_value();
pub WeightPerGas: Weight = Weight::from_ref_time(20_000);
pub MockPrecompiles: MockPrecompileSet = MockPrecompileSet;
}
impl crate::Config for Test {
type FeeCalculator = FixedGasPrice;
Expand All @@ -139,11 +144,35 @@ impl crate::Config for Test {
type Currency = Balances;

type RuntimeEvent = RuntimeEvent;
type PrecompilesType = ();
type PrecompilesValue = ();
type PrecompilesType = MockPrecompileSet;
type PrecompilesValue = MockPrecompiles;
type ChainId = ();
type BlockGasLimit = BlockGasLimit;
type Runner = crate::runner::stack::Runner<Self>;
type OnChargeTransaction = ();
type FindAuthor = FindAuthorTruncated;
}

/// Exemple PrecompileSet with only Identity precompile.
pub struct MockPrecompileSet;

impl PrecompileSet for MockPrecompileSet {
/// Tries to execute a precompile in the precompile set.
/// If the provided address is not a precompile, returns None.
fn execute(&self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
let address = handle.code_address();

if address == H160::from_low_u64_be(1) {
return Some(pallet_evm_precompile_simple::Identity::execute(handle));
}

None
}

/// Check if the given address is a precompile. Should only be called to
/// perform the check while not executing the precompile afterward, since
/// `execute` already performs a check internally.
fn is_precompile(&self, address: H160) -> bool {
address == H160::from_low_u64_be(1)
}
}
16 changes: 15 additions & 1 deletion frame/evm/src/runner/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use evm::{
executor::stack::{Accessed, StackExecutor, StackState as StackStateT, StackSubstateMetadata},
ExitError, ExitReason, Transfer,
};
use fp_evm::{CallInfo, CreateInfo, ExecutionInfo, Log, Vicinity};
use fp_evm::{CallInfo, CreateInfo, ExecutionInfo, Log, PrecompileSet, Vicinity};
use frame_support::traits::{Currency, ExistenceRequirement, Get};
use sp_core::{H160, H256, U256};
use sp_runtime::traits::UniqueSaturatedInto;
Expand Down Expand Up @@ -130,6 +130,20 @@ where
>,
) -> (ExitReason, R),
{
// EIP-3607: https://eips.ethereum.org/EIPS/eip-3607
// Do not allow transactions for which `tx.sender` has any code deployed.
//
// We extend the principle of this EIP to also prevent `tx.sender` to be the address
// of a precompile. While mainnet Ethereum currently only has stateless precompiles,
// projects using Frontier can have stateful precompiles that can manage funds or
// which calls other contracts that expects this precompile address to be trustworthy.
if !<AccountCodes<T>>::get(&source).is_empty() || precompiles.is_precompile(source) {
return Err(RunnerError {
error: Error::<T>::TransactionMustComeFromEOA,
weight,
});
}

let (total_fee_per_gas, _actual_priority_fee_per_gas) =
match (max_fee_per_gas, max_priority_fee_per_gas, is_transactional) {
// Zero max_fee_per_gas for validated transactional calls exist in XCM -> EVM
Expand Down
54 changes: 54 additions & 0 deletions frame/evm/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -557,3 +557,57 @@ fn runner_max_fee_per_gas_gte_max_priority_fee_per_gas() {
assert!(res.is_err());
});
}

#[test]
fn eip3607_transaction_from_contract_should_fail() {
new_test_ext().execute_with(|| {
match <Test as Config>::Runner::call(
// Contract address.
H160::from_str("1000000000000000000000000000000000000001").unwrap(),
H160::from_str("1000000000000000000000000000000000000001").unwrap(),
Vec::new(),
U256::from(1u32),
1000000,
None,
None,
None,
Vec::new(),
false, // non-transactional
true, // must be validated
&<Test as Config>::config().clone(),
) {
Err(RunnerError {
error: Error::TransactionMustComeFromEOA,
..
}) => (),
_ => panic!("Should have failed"),
}
});
}

#[test]
fn eip3607_transaction_from_precompile_should_fail() {
new_test_ext().execute_with(|| {
match <Test as Config>::Runner::call(
// Precompile address.
H160::from_str("0000000000000000000000000000000000000001").unwrap(),
H160::from_str("1000000000000000000000000000000000000001").unwrap(),
Vec::new(),
U256::from(1u32),
1000000,
None,
None,
None,
Vec::new(),
false, // non-transactional
true, // must be validated
&<Test as Config>::config().clone(),
) {
Err(RunnerError {
error: Error::TransactionMustComeFromEOA,
..
}) => (),
_ => panic!("Should have failed"),
}
});
}