Skip to content

Commit 1e30822

Browse files
pallet_revive: Only enforce EIP-3607 for dispatchables (#10100)
The EIP states that contract addresses cannot be the origin of a transaction. However, we are enforcing this rule for all contract execution (i.e all public function on the pallet). This is a problem for code that uses `pallet_revive` and explicitly wants to allow this. This PR now only enforces this check for dispatchables so that all the `bare_*` functions are unaffected. As a drive-by a regrouped the functions on the `Pallet` so that the public functions are no longer interleaved with the private ones. This got mixed up when we resolved some merge conflicts. --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 1d75dcc commit 1e30822

3 files changed

Lines changed: 125 additions & 133 deletions

File tree

prdoc/pr_10100.prdoc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
title: 'pallet_revive: Only enforce EIP-3607 for dispatchables'
2+
doc:
3+
- audience: Runtime Dev
4+
description: |-
5+
The EIP states that contract addresses cannot be the origin of a transaction. However, we are enforcing this rule for all contract execution (i.e all public function on the pallet). This is a problem for code that uses `pallet_revive` and explicitly wants to allow this.
6+
7+
This PR now only enforces this check for dispatchables so that all the `bare_*` functions are unaffected.
8+
9+
As a drive-by a regrouped the functions on the `Pallet` so that the public functions are no longer interleaved with the private ones. This got mixed up when we resolved some merge conflicts.
10+
crates:
11+
- name: pallet-revive
12+
bump: patch

substrate/frame/revive/src/lib.rs

Lines changed: 65 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,7 @@ pub mod pallet {
10461046
#[pallet::compact] storage_deposit_limit: BalanceOf<T>,
10471047
data: Vec<u8>,
10481048
) -> DispatchResultWithPostInfo {
1049+
Self::ensure_non_contract_if_signed(&origin)?;
10491050
let mut output = Self::bare_call(
10501051
origin,
10511052
dest,
@@ -1082,6 +1083,7 @@ pub mod pallet {
10821083
data: Vec<u8>,
10831084
salt: Option<[u8; 32]>,
10841085
) -> DispatchResultWithPostInfo {
1086+
Self::ensure_non_contract_if_signed(&origin)?;
10851087
let data_len = data.len() as u32;
10861088
let mut output = Self::bare_instantiate(
10871089
origin,
@@ -1146,6 +1148,7 @@ pub mod pallet {
11461148
data: Vec<u8>,
11471149
salt: Option<[u8; 32]>,
11481150
) -> DispatchResultWithPostInfo {
1151+
Self::ensure_non_contract_if_signed(&origin)?;
11491152
let code_len = code.len() as u32;
11501153
let data_len = data.len() as u32;
11511154
let mut output = Self::bare_instantiate(
@@ -1207,6 +1210,7 @@ pub mod pallet {
12071210
encoded_len: u32,
12081211
) -> DispatchResultWithPostInfo {
12091212
let origin = Self::ensure_eth_origin(origin)?;
1213+
Self::ensure_non_contract_if_signed(&origin)?;
12101214
let mut call = Call::<T>::eth_instantiate_with_code {
12111215
value,
12121216
gas_limit,
@@ -1277,6 +1281,7 @@ pub mod pallet {
12771281
encoded_len: u32,
12781282
) -> DispatchResultWithPostInfo {
12791283
let origin = Self::ensure_eth_origin(origin)?;
1284+
Self::ensure_non_contract_if_signed(&origin)?;
12801285
let mut call = Call::<T>::eth_call {
12811286
dest,
12821287
value,
@@ -1352,6 +1357,7 @@ pub mod pallet {
13521357
code: Vec<u8>,
13531358
#[pallet::compact] storage_deposit_limit: BalanceOf<T>,
13541359
) -> DispatchResult {
1360+
Self::ensure_non_contract_if_signed(&origin)?;
13551361
Self::bare_upload_code(origin, code, storage_deposit_limit).map(|_| ())
13561362
}
13571363

@@ -1413,6 +1419,7 @@ pub mod pallet {
14131419
#[pallet::call_index(7)]
14141420
#[pallet::weight(<T as Config>::WeightInfo::map_account())]
14151421
pub fn map_account(origin: OriginFor<T>) -> DispatchResult {
1422+
Self::ensure_non_contract_if_signed(&origin)?;
14161423
let origin = ensure_signed(origin)?;
14171424
T::AddressMapper::map(&origin)
14181425
}
@@ -1445,6 +1452,7 @@ pub mod pallet {
14451452
origin: OriginFor<T>,
14461453
call: Box<<T as Config>::RuntimeCall>,
14471454
) -> DispatchResultWithPostInfo {
1455+
Self::ensure_non_contract_if_signed(&origin)?;
14481456
let origin = ensure_signed(origin)?;
14491457
let unmapped_account =
14501458
T::AddressMapper::to_fallback_account_id(&T::AddressMapper::to_address(&origin));
@@ -1485,9 +1493,6 @@ impl<T: Config> Pallet<T> {
14851493
data: Vec<u8>,
14861494
exec_config: ExecConfig,
14871495
) -> ContractResult<ExecReturnValue, BalanceOf<T>> {
1488-
if let Err(contract_result) = Self::ensure_non_contract_if_signed(&origin) {
1489-
return contract_result;
1490-
}
14911496
let mut gas_meter = GasMeter::new(gas_limit);
14921497
let mut storage_deposit = Default::default();
14931498

@@ -1544,10 +1549,6 @@ impl<T: Config> Pallet<T> {
15441549
salt: Option<[u8; 32]>,
15451550
exec_config: ExecConfig,
15461551
) -> ContractResult<InstantiateReturnValue, BalanceOf<T>> {
1547-
// Enforce EIP-3607 for top-level signed origins: deny signed contract addresses.
1548-
if let Err(contract_result) = Self::ensure_non_contract_if_signed(&origin) {
1549-
return contract_result;
1550-
}
15511552
let mut gas_meter = GasMeter::new(gas_limit);
15521553
let mut storage_deposit = Default::default();
15531554
let try_instantiate = || {
@@ -2094,6 +2095,39 @@ impl<T: Config> Pallet<T> {
20942095
.map_err(ContractAccessError::StorageWriteFailed)
20952096
}
20962097

2098+
/// Pallet account, used to hold funds for contracts upload deposit.
2099+
pub fn account_id() -> T::AccountId {
2100+
use frame_support::PalletId;
2101+
use sp_runtime::traits::AccountIdConversion;
2102+
PalletId(*b"py/reviv").into_account_truncating()
2103+
}
2104+
2105+
/// The address of the validator that produced the current block.
2106+
pub fn block_author() -> H160 {
2107+
use frame_support::traits::FindAuthor;
2108+
2109+
let digest = <frame_system::Pallet<T>>::digest();
2110+
let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime());
2111+
2112+
T::FindAuthor::find_author(pre_runtime_digests)
2113+
.map(|account_id| T::AddressMapper::to_address(&account_id))
2114+
.unwrap_or_default()
2115+
}
2116+
2117+
/// Returns the code at `address`.
2118+
///
2119+
/// This takes pre-compiles into account.
2120+
pub fn code(address: &H160) -> Vec<u8> {
2121+
use precompiles::{All, Precompiles};
2122+
if let Some(code) = <All<T>>::code(address.as_fixed_bytes()) {
2123+
return code.into()
2124+
}
2125+
AccountInfo::<T>::load_contract(&address)
2126+
.and_then(|contract| <PristineCode<T>>::get(contract.code_hash))
2127+
.map(|code| code.into())
2128+
.unwrap_or_default()
2129+
}
2130+
20972131
/// Uploads new code and returns the Vm binary contract blob and deposit amount collected.
20982132
fn try_upload_pvm_code(
20992133
origin: T::AccountId,
@@ -2131,80 +2165,6 @@ impl<T: Config> Pallet<T> {
21312165
T::FeeInfo::weight_to_fee(&weight, Combinator::Max).into()
21322166
}
21332167

2134-
/// Ensure the origin has no code deplyoyed if it is a signed origin.
2135-
fn ensure_non_contract_if_signed<ReturnValue>(
2136-
origin: &OriginFor<T>,
2137-
) -> Result<(), ContractResult<ReturnValue, BalanceOf<T>>> {
2138-
use crate::exec::is_precompile;
2139-
let Ok(who) = ensure_signed(origin.clone()) else { return Ok(()) };
2140-
let address = <T::AddressMapper as AddressMapper<T>>::to_address(&who);
2141-
2142-
// EIP_1052: precompile can never be used as EOA.
2143-
if is_precompile::<T, ContractBlob<T>>(&address) {
2144-
log::debug!(
2145-
target: crate::LOG_TARGET,
2146-
"EIP-3607: reject externally-signed tx from precompile account {:?}",
2147-
address
2148-
);
2149-
return Err(ContractResult {
2150-
result: Err(DispatchError::BadOrigin),
2151-
gas_consumed: Weight::default(),
2152-
gas_required: Weight::default(),
2153-
storage_deposit: Default::default(),
2154-
});
2155-
}
2156-
2157-
// Deployed code exists when hash is neither zero (no account) nor EMPTY_CODE_HASH
2158-
// (account exists but no code).
2159-
if <AccountInfo<T>>::is_contract(&address) {
2160-
log::debug!(
2161-
target: crate::LOG_TARGET,
2162-
"EIP-3607: reject externally-signed tx from contract account {:?}",
2163-
address
2164-
);
2165-
return Err(ContractResult {
2166-
result: Err(DispatchError::BadOrigin),
2167-
gas_consumed: Weight::default(),
2168-
gas_required: Weight::default(),
2169-
storage_deposit: Default::default(),
2170-
});
2171-
}
2172-
Ok(())
2173-
}
2174-
2175-
/// Pallet account, used to hold funds for contracts upload deposit.
2176-
pub fn account_id() -> T::AccountId {
2177-
use frame_support::PalletId;
2178-
use sp_runtime::traits::AccountIdConversion;
2179-
PalletId(*b"py/reviv").into_account_truncating()
2180-
}
2181-
2182-
/// The address of the validator that produced the current block.
2183-
pub fn block_author() -> H160 {
2184-
use frame_support::traits::FindAuthor;
2185-
2186-
let digest = <frame_system::Pallet<T>>::digest();
2187-
let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime());
2188-
2189-
T::FindAuthor::find_author(pre_runtime_digests)
2190-
.map(|account_id| T::AddressMapper::to_address(&account_id))
2191-
.unwrap_or_default()
2192-
}
2193-
2194-
/// Returns the code at `address`.
2195-
///
2196-
/// This takes pre-compiles into account.
2197-
pub fn code(address: &H160) -> Vec<u8> {
2198-
use precompiles::{All, Precompiles};
2199-
if let Some(code) = <All<T>>::code(address.as_fixed_bytes()) {
2200-
return code.into()
2201-
}
2202-
AccountInfo::<T>::load_contract(&address)
2203-
.and_then(|contract| <PristineCode<T>>::get(contract.code_hash))
2204-
.map(|code| code.into())
2205-
.unwrap_or_default()
2206-
}
2207-
22082168
/// Transfer a deposit from some account to another.
22092169
///
22102170
/// `from` is usually the transaction origin and `to` a contract or
@@ -2331,6 +2291,30 @@ impl<T: Config> Pallet<T> {
23312291
_ => Err(BadOrigin.into()),
23322292
}
23332293
}
2294+
2295+
/// Ensure that the origin is neither a pre-compile nor a contract.
2296+
///
2297+
/// This enforces EIP-3607.
2298+
fn ensure_non_contract_if_signed(origin: &OriginFor<T>) -> DispatchResult {
2299+
let Some(address) = origin
2300+
.as_system_ref()
2301+
.and_then(|o| o.as_signed())
2302+
.map(<T::AddressMapper as AddressMapper<T>>::to_address)
2303+
else {
2304+
return Ok(())
2305+
};
2306+
if exec::is_precompile::<T, ContractBlob<T>>(&address) ||
2307+
<AccountInfo<T>>::is_contract(&address)
2308+
{
2309+
log::debug!(
2310+
target: crate::LOG_TARGET,
2311+
"EIP-3607: reject tx as pre-compile or account exist at {address:?}",
2312+
);
2313+
Err(DispatchError::BadOrigin)
2314+
} else {
2315+
Ok(())
2316+
}
2317+
}
23342318
}
23352319

23362320
/// The address used to call the runtime's pallets dispatchables

substrate/frame/revive/src/tests/pvm.rs

Lines changed: 48 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5039,74 +5039,70 @@ fn storage_deposit_from_hold_works() {
50395039
});
50405040
}
50415041

5042-
/// EIP-3607
5043-
/// Test that a top-level signed transaction that uses a contract address as the signer is rejected.
50445042
#[test]
5045-
fn reject_signed_tx_from_contract_address() {
5043+
fn eip3607_reject_tx_from_contract_or_precompile() {
50465044
let (binary, _code_hash) = compile_module("dummy").unwrap();
50475045

50485046
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
50495047
let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000);
50505048

5051-
let Contract { addr: contract_addr, account_id: contract_account_id, .. } =
5049+
// the origins from which we try to call a dispatchable
5050+
let Contract { addr: contract_addr, .. } =
50525051
builder::bare_instantiate(Code::Upload(binary)).build_and_unwrap_contract();
5052+
assert!(<AccountInfo<Test>>::is_contract(&contract_addr));
5053+
let blake2_addr = H160::from_low_u64_be(9);
5054+
let system_addr = H160::from_low_u64_be(0x900);
5055+
let addresses = [contract_addr, blake2_addr, system_addr];
50535056

5054-
assert!(AccountInfoOf::<Test>::contains_key(&contract_addr));
5057+
// used to test `dispatch_as_fallback_account`
5058+
let call = Box::new(RuntimeCall::Balances(pallet_balances::Call::transfer_all {
5059+
dest: EVE,
5060+
keep_alive: false,
5061+
}));
50555062

5056-
let call_result = builder::bare_call(BOB_ADDR)
5057-
.native_value(1)
5058-
.origin(RuntimeOrigin::signed(contract_account_id.clone()))
5059-
.build();
5060-
assert_err!(call_result.result, DispatchError::BadOrigin);
5063+
for address in addresses.iter() {
5064+
let origin = <Test as Config>::AddressMapper::to_fallback_account_id(address);
50615065

5062-
let instantiate_result = builder::bare_instantiate(Code::Upload(Vec::new()))
5063-
.origin(RuntimeOrigin::signed(contract_account_id))
5064-
.build();
5065-
assert_err!(instantiate_result.result, DispatchError::BadOrigin);
5066-
});
5067-
}
5066+
let result =
5067+
builder::call(BOB_ADDR).origin(RuntimeOrigin::signed(origin.clone())).build();
5068+
assert_err!(result, DispatchError::BadOrigin);
50685069

5069-
#[test]
5070-
fn reject_signed_tx_from_primitive_precompile_address() {
5071-
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
5072-
// blake2f precompile address
5073-
let precompile_addr = H160::from_low_u64_be(9);
5074-
let fake_account = <Test as Config>::AddressMapper::to_account_id(&precompile_addr);
5075-
let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000);
5076-
let _ = <Test as Config>::Currency::set_balance(&fake_account, 1_000_000);
5070+
let result = builder::eth_call(BOB_ADDR)
5071+
.origin(RuntimeOrigin::signed(origin.clone()))
5072+
.build();
5073+
assert_err!(result, DispatchError::BadOrigin);
50775074

5078-
let call_result = builder::bare_call(BOB_ADDR)
5079-
.native_value(1)
5080-
.origin(RuntimeOrigin::signed(fake_account.clone()))
5081-
.build();
5082-
assert_err!(call_result.result, DispatchError::BadOrigin);
5075+
let result = builder::instantiate(Default::default())
5076+
.origin(RuntimeOrigin::signed(origin.clone()))
5077+
.build();
5078+
assert_err!(result, DispatchError::BadOrigin);
50835079

5084-
let instantiate_result = builder::bare_instantiate(Code::Upload(Vec::new()))
5085-
.origin(RuntimeOrigin::signed(fake_account))
5086-
.build();
5087-
assert_err!(instantiate_result.result, DispatchError::BadOrigin);
5088-
});
5089-
}
5080+
let result = builder::eth_instantiate_with_code(Default::default())
5081+
.origin(RuntimeOrigin::signed(origin.clone()))
5082+
.build();
5083+
assert_err!(result, DispatchError::BadOrigin);
50905084

5091-
#[test]
5092-
fn reject_signed_tx_from_builtin_precompile_address() {
5093-
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
5094-
// system precompile address
5095-
let precompile_addr = H160::from_low_u64_be(0x900);
5096-
let fake_account = <Test as Config>::AddressMapper::to_account_id(&precompile_addr);
5097-
let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000);
5098-
let _ = <Test as Config>::Currency::set_balance(&fake_account, 1_000_000);
5085+
let result = builder::instantiate_with_code(Default::default())
5086+
.origin(RuntimeOrigin::signed(origin.clone()))
5087+
.build();
5088+
assert_err!(result, DispatchError::BadOrigin);
50995089

5100-
let call_result = builder::bare_call(BOB_ADDR)
5101-
.native_value(1)
5102-
.origin(RuntimeOrigin::signed(fake_account.clone()))
5103-
.build();
5104-
assert_err!(call_result.result, DispatchError::BadOrigin);
5090+
let result = <Pallet<Test>>::upload_code(
5091+
RuntimeOrigin::signed(origin.clone()),
5092+
Default::default(),
5093+
<BalanceOf<Test>>::MAX,
5094+
);
5095+
assert_err!(result, DispatchError::BadOrigin);
51055096

5106-
let instantiate_result = builder::bare_instantiate(Code::Upload(Vec::new()))
5107-
.origin(RuntimeOrigin::signed(fake_account))
5108-
.build();
5109-
assert_err!(instantiate_result.result, DispatchError::BadOrigin);
5097+
let result = <Pallet<Test>>::map_account(RuntimeOrigin::signed(origin.clone()));
5098+
assert_err!(result, DispatchError::BadOrigin);
5099+
5100+
let result = <Pallet<Test>>::dispatch_as_fallback_account(
5101+
RuntimeOrigin::signed(origin.clone()),
5102+
call.clone(),
5103+
);
5104+
assert_err!(result, DispatchError::BadOrigin);
5105+
}
51105106
});
51115107
}
51125108

0 commit comments

Comments
 (0)