Skip to content
11 changes: 11 additions & 0 deletions prdoc/pr_10387.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
title: "pallet-revive: add DebugSetting for bypassing eip-3607 for contracts and precompiles"
doc:
- audience: Runtime Dev
description: |-
Adds a new DebugSetting option which, if enabled, allows transactions coming from contract accounts or precompiles.
This is needed so that test nodes like anvil can send transactions from
contract or precompile accounts, a widely-used feature in tests.

crates:
- name: pallet-revive
bump: major
13 changes: 11 additions & 2 deletions substrate/frame/revive/src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,27 @@ use sp_runtime::RuntimeDebug;
pub struct DebugSettings {
/// Whether to allow unlimited contract size.
allow_unlimited_contract_size: bool,
/// Whether to allow bypassing EIP-3607 (allowing transactions coming from contract or
/// precompile accounts).
bypass_eip_3607: bool,
}

impl DebugSettings {
pub fn new(allow_unlimited_contract_size: bool) -> Self {
Self { allow_unlimited_contract_size }
pub fn new(allow_unlimited_contract_size: bool, bypass_eip_3607: bool) -> Self {
Self { allow_unlimited_contract_size, bypass_eip_3607 }
}

/// Returns true if unlimited contract size is allowed.
pub fn is_unlimited_contract_size_allowed<T: Config>() -> bool {
T::DebugEnabled::get() && DebugSettingsOf::<T>::get().allow_unlimited_contract_size
}

/// Returns true if transactions coming from contract or precompile accounts are allowed
/// (bypassing EIP-3607)
pub fn bypass_eip_3607<T: Config>() -> bool {
T::DebugEnabled::get() && DebugSettingsOf::<T>::get().bypass_eip_3607
}

/// Write the debug settings to storage.
pub fn write_to_storage<T: Config>(&self) {
DebugSettingsOf::<T>::put(self);
Expand Down
3 changes: 3 additions & 0 deletions substrate/frame/revive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2368,6 +2368,9 @@ impl<T: Config> Pallet<T> {
///
/// This enforces EIP-3607.
fn ensure_non_contract_if_signed(origin: &OriginFor<T>) -> DispatchResult {
if DebugSettings::bypass_eip_3607::<T>() {
return Ok(())
}
let Some(address) = origin
.as_system_ref()
.and_then(|o| o.as_signed())
Expand Down
79 changes: 75 additions & 4 deletions substrate/frame/revive/src/tests/pvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ use crate::{
tracing::trace,
weights::WeightInfo,
AccountInfo, AccountInfoOf, BalanceWithDust, Code, Combinator, Config, ContractInfo,
DeletionQueueCounter, Error, ExecConfig, HoldReason, Origin, Pallet, PristineCode,
StorageDeposit, H160,
DebugSettings, DeletionQueueCounter, Error, ExecConfig, HoldReason, Origin, Pallet,
PristineCode, StorageDeposit, H160,
};
use assert_matches::assert_matches;
use codec::Encode;
Expand Down Expand Up @@ -4973,7 +4973,7 @@ fn eip3607_reject_tx_from_contract_or_precompile() {
assert_err!(result, DispatchError::BadOrigin);

let result = builder::eth_call(BOB_ADDR)
.origin(RuntimeOrigin::signed(origin.clone()))
.origin(Origin::EthTransaction(origin.clone()).into())
.build();
assert_err!(result, DispatchError::BadOrigin);

Expand All @@ -4983,7 +4983,7 @@ fn eip3607_reject_tx_from_contract_or_precompile() {
assert_err!(result, DispatchError::BadOrigin);

let result = builder::eth_instantiate_with_code(Default::default())
.origin(RuntimeOrigin::signed(origin.clone()))
.origin(Origin::EthTransaction(origin.clone()).into())
.build();
assert_err!(result, DispatchError::BadOrigin);

Expand Down Expand Up @@ -5011,6 +5011,77 @@ fn eip3607_reject_tx_from_contract_or_precompile() {
});
}

#[test]
fn eip3607_allow_tx_from_contract_or_precompile_if_debug_setting_configured() {
let (binary, code_hash) = compile_module("dummy").unwrap();

let genesis_config = GenesisConfig::<Test> {
debug_settings: Some(DebugSettings::new(false, true)),
..Default::default()
};

ExtBuilder::default()
.genesis_config(Some(genesis_config))
.existential_deposit(200)
.build()
.execute_with(|| {
DebugFlag::set(true);

let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000);

// the origins from which we try to call a dispatchable
let Contract { addr: contract_addr, .. } =
builder::bare_instantiate(Code::Upload(binary.clone())).build_and_unwrap_contract();

assert!(<AccountInfo<Test>>::is_contract(&contract_addr));

let blake2_addr = H160::from_low_u64_be(9);
let system_addr = H160::from_low_u64_be(0x900);
let addresses = [contract_addr, blake2_addr, system_addr];
Comment on lines +5038 to +5040
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess one if enough, but ok

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I took inspiration from the existing eip3607_reject_tx_from_contract_or_precompile test


for address in addresses {
let origin = <Test as Config>::AddressMapper::to_fallback_account_id(&address);

let _ = <Test as Config>::Currency::set_balance(&origin, 10_000_000_000_000);

let result =
builder::call(BOB_ADDR).origin(RuntimeOrigin::signed(origin.clone())).build();
assert_ok!(result);

let result = builder::eth_call(BOB_ADDR)
.origin(Origin::EthTransaction(origin.clone()).into())
.build();
assert_ok!(result);

let result = builder::instantiate(code_hash)
.origin(RuntimeOrigin::signed(origin.clone()))
.build();
assert_ok!(result);

let result = builder::eth_instantiate_with_code(binary.clone())
.origin(Origin::EthTransaction(origin.clone()).into())
.build();
assert_ok!(result);

let result = <Pallet<Test>>::dispatch_as_fallback_account(
RuntimeOrigin::signed(origin.clone()),
Box::new(RuntimeCall::Balances(pallet_balances::Call::transfer_all {
dest: EVE,
keep_alive: false,
})),
);
assert_ok!(result);

let result = <Pallet<Test>>::upload_code(
RuntimeOrigin::signed(origin.clone()),
binary.clone(),
<BalanceOf<Test>>::MAX,
);
assert_ok!(result);
}
});
}

#[test]
fn get_set_storage_key_works() {
let (code, _code_hash) = compile_module("dummy").unwrap();
Expand Down
2 changes: 1 addition & 1 deletion substrate/frame/revive/src/tests/sol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ fn eth_contract_too_large() {

// Initialize genesis config with allow_unlimited_contract_size
let genesis_config = GenesisConfig::<Test> {
debug_settings: Some(DebugSettings::new(allow_unlimited_contract_size)),
debug_settings: Some(DebugSettings::new(allow_unlimited_contract_size, false)),
..Default::default()
};

Expand Down
Loading