diff --git a/prdoc/pr_10387.prdoc b/prdoc/pr_10387.prdoc new file mode 100644 index 0000000000000..f3d2a1afcbb38 --- /dev/null +++ b/prdoc/pr_10387.prdoc @@ -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 diff --git a/substrate/frame/revive/src/debug.rs b/substrate/frame/revive/src/debug.rs index 82cea3e8c308e..2f031a67db11d 100644 --- a/substrate/frame/revive/src/debug.rs +++ b/substrate/frame/revive/src/debug.rs @@ -38,11 +38,14 @@ 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. @@ -50,6 +53,12 @@ impl DebugSettings { T::DebugEnabled::get() && DebugSettingsOf::::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() -> bool { + T::DebugEnabled::get() && DebugSettingsOf::::get().bypass_eip_3607 + } + /// Write the debug settings to storage. pub fn write_to_storage(&self) { DebugSettingsOf::::put(self); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 3b6f884711f7a..eacf07111e5de 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -2368,6 +2368,9 @@ impl Pallet { /// /// This enforces EIP-3607. fn ensure_non_contract_if_signed(origin: &OriginFor) -> DispatchResult { + if DebugSettings::bypass_eip_3607::() { + return Ok(()) + } let Some(address) = origin .as_system_ref() .and_then(|o| o.as_signed()) diff --git a/substrate/frame/revive/src/tests/pvm.rs b/substrate/frame/revive/src/tests/pvm.rs index dabe8930d7b85..5361efdd9f17f 100644 --- a/substrate/frame/revive/src/tests/pvm.rs +++ b/substrate/frame/revive/src/tests/pvm.rs @@ -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; @@ -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); @@ -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); @@ -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:: { + 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 _ = ::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!(>::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]; + + for address in addresses { + let origin = ::AddressMapper::to_fallback_account_id(&address); + + let _ = ::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 = >::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 = >::upload_code( + RuntimeOrigin::signed(origin.clone()), + binary.clone(), + >::MAX, + ); + assert_ok!(result); + } + }); +} + #[test] fn get_set_storage_key_works() { let (code, _code_hash) = compile_module("dummy").unwrap(); diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index 93de26d0ee9d5..d04c749719db1 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -180,7 +180,7 @@ fn eth_contract_too_large() { // Initialize genesis config with allow_unlimited_contract_size let genesis_config = GenesisConfig:: { - debug_settings: Some(DebugSettings::new(allow_unlimited_contract_size)), + debug_settings: Some(DebugSettings::new(allow_unlimited_contract_size, false)), ..Default::default() };