1#![cfg_attr(not(feature = "std"), no_std)]
18
19use account::SYSTEM_ACCOUNT_SIZE;
20use evm::ExitReason;
21use fp_evm::{
22 Context, PrecompileFailure, PrecompileHandle, Transfer, ACCOUNT_CODES_METADATA_PROOF_SIZE,
23};
24use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo};
25use pallet_balances::Call as BalancesCall;
26use pallet_evm::AddressMapping;
27use pallet_proxy::Call as ProxyCall;
28use pallet_proxy::Pallet as ProxyPallet;
29use precompile_utils::precompile_set::{self, AddressType, SelectorFilter};
30use precompile_utils::prelude::*;
31use sp_core::{Get, H160, U256};
32use sp_runtime::{
33 codec::Decode,
34 traits::{ConstU32, Dispatchable, StaticLookup, Zero},
35 SaturatedConversion,
36};
37use sp_std::marker::PhantomData;
38
39#[cfg(test)]
40pub mod mock;
41#[cfg(test)]
42mod tests;
43
44#[derive(Debug)]
45pub struct OnlyIsProxy<Runtime>(PhantomData<Runtime>);
46
47impl<Runtime> SelectorFilter for OnlyIsProxy<Runtime>
48where
49 Runtime:
50 pallet_proxy::Config + pallet_evm::Config + frame_system::Config + pallet_balances::Config,
51 <<Runtime as pallet_proxy::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
52 From<Option<Runtime::AccountId>>,
53 <Runtime as pallet_proxy::Config>::ProxyType: Decode + EvmProxyCallFilter,
54 <Runtime as frame_system::Config>::RuntimeCall:
55 Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
56 <<Runtime as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
57 From<Option<Runtime::AccountId>>,
58 <Runtime as frame_system::Config>::RuntimeCall:
59 From<ProxyCall<Runtime>> + From<BalancesCall<Runtime>>,
60 <Runtime as pallet_balances::Config<()>>::Balance: TryFrom<U256> + Into<U256>,
61 <Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
62{
63 fn is_allowed(_caller: H160, selector: Option<u32>) -> bool {
64 match selector {
65 None => false,
66 Some(selector) => {
67 ProxyPrecompileCall::<Runtime>::is_proxy_selectors().contains(&selector)
68 }
69 }
70 }
71
72 fn description() -> String {
73 "Allowed for all callers only for selector 'is_proxy'".into()
74 }
75}
76
77#[derive(Debug)]
78pub struct OnlyIsProxyAndProxy<Runtime>(PhantomData<Runtime>);
79
80impl<Runtime> SelectorFilter for OnlyIsProxyAndProxy<Runtime>
81where
82 Runtime:
83 pallet_proxy::Config + pallet_evm::Config + frame_system::Config + pallet_balances::Config,
84 <<Runtime as pallet_proxy::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
85 From<Option<Runtime::AccountId>>,
86 <Runtime as pallet_proxy::Config>::ProxyType: Decode + EvmProxyCallFilter,
87 <Runtime as frame_system::Config>::RuntimeCall:
88 Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
89 <<Runtime as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
90 From<Option<Runtime::AccountId>>,
91 <Runtime as frame_system::Config>::RuntimeCall:
92 From<ProxyCall<Runtime>> + From<BalancesCall<Runtime>>,
93 <Runtime as pallet_balances::Config<()>>::Balance: TryFrom<U256> + Into<U256>,
94 <Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
95{
96 fn is_allowed(_caller: H160, selector: Option<u32>) -> bool {
97 match selector {
98 None => false,
99 Some(selector) => {
100 ProxyPrecompileCall::<Runtime>::is_proxy_selectors().contains(&selector)
101 || ProxyPrecompileCall::<Runtime>::proxy_selectors().contains(&selector)
102 || ProxyPrecompileCall::<Runtime>::proxy_force_type_selectors()
103 .contains(&selector)
104 }
105 }
106 }
107
108 fn description() -> String {
109 "Allowed for all callers only for selectors 'is_proxy', 'proxy', 'proxy_force_type'".into()
110 }
111}
112
113pub const CALL_DATA_LIMIT: u32 = 2u32.pow(16);
114
115type GetCallDataLimit = ConstU32<CALL_DATA_LIMIT>;
116
117pub struct EvmSubCall {
118 pub to: Address,
119 pub value: U256,
120 pub call_data: BoundedBytes<ConstU32<CALL_DATA_LIMIT>>,
121}
122
123pub trait EvmProxyCallFilter: Sized + Send + Sync {
126 fn is_evm_proxy_call_allowed(
129 &self,
130 _call: &EvmSubCall,
131 _recipient_has_code: bool,
132 _gas: u64,
133 ) -> EvmResult<bool> {
134 Ok(false)
135 }
136}
137
138pub struct ProxyPrecompile<Runtime>(PhantomData<Runtime>);
140
141#[precompile_utils::precompile]
142impl<Runtime> ProxyPrecompile<Runtime>
143where
144 Runtime:
145 pallet_proxy::Config + pallet_evm::Config + frame_system::Config + pallet_balances::Config,
146 <<Runtime as pallet_proxy::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
147 From<Option<Runtime::AccountId>>,
148 <Runtime as pallet_proxy::Config>::ProxyType: Decode + EvmProxyCallFilter,
149 <Runtime as frame_system::Config>::RuntimeCall:
150 Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
151 <<Runtime as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
152 From<Option<Runtime::AccountId>>,
153 <Runtime as frame_system::Config>::RuntimeCall:
154 From<ProxyCall<Runtime>> + From<BalancesCall<Runtime>>,
155 <Runtime as pallet_balances::Config<()>>::Balance: TryFrom<U256> + Into<U256>,
156 <Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
157{
158 #[precompile::public("addProxy(address,uint8,uint32)")]
166 fn add_proxy(
167 handle: &mut impl PrecompileHandle,
168 delegate: Address,
169 proxy_type: u8,
170 delay: u32,
171 ) -> EvmResult {
172 let delegate = Runtime::AddressMapping::into_account_id(delegate.into());
173 let proxy_type = Runtime::ProxyType::decode(&mut proxy_type.to_le_bytes().as_slice())
174 .map_err(|_| {
175 RevertReason::custom("Failed decoding value to ProxyType").in_field("proxyType")
176 })?;
177 let delay = delay.into();
178
179 let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
180
181 handle.record_db_read::<Runtime>(
188 28 + (29 * (<Runtime as pallet_proxy::Config>::MaxProxies::get() as usize)) + 16,
189 )?;
190 if ProxyPallet::<Runtime>::proxies(origin.clone())
191 .0
192 .iter()
193 .any(|pd| pd.delegate == delegate)
194 {
195 return Err(revert("Cannot add more than one proxy"));
196 }
197
198 let delegate: <Runtime::Lookup as StaticLookup>::Source =
199 Runtime::Lookup::unlookup(delegate.clone());
200 let call: ProxyCall<Runtime> = ProxyCall::<Runtime>::add_proxy {
201 delegate,
202 proxy_type,
203 delay,
204 }
205 .into();
206
207 <RuntimeHelper<Runtime>>::try_dispatch(handle, Some(origin).into(), call, 0)?;
208
209 Ok(())
210 }
211
212 #[precompile::public("removeProxy(address,uint8,uint32)")]
220 fn remove_proxy(
221 handle: &mut impl PrecompileHandle,
222 delegate: Address,
223 proxy_type: u8,
224 delay: u32,
225 ) -> EvmResult {
226 let delegate = Runtime::AddressMapping::into_account_id(delegate.into());
227 let proxy_type = Runtime::ProxyType::decode(&mut proxy_type.to_le_bytes().as_slice())
228 .map_err(|_| {
229 RevertReason::custom("Failed decoding value to ProxyType").in_field("proxyType")
230 })?;
231 let delay = delay.into();
232
233 let delegate: <Runtime::Lookup as StaticLookup>::Source =
234 Runtime::Lookup::unlookup(delegate.clone());
235 let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
236 let call: ProxyCall<Runtime> = ProxyCall::<Runtime>::remove_proxy {
237 delegate,
238 proxy_type,
239 delay,
240 }
241 .into();
242
243 <RuntimeHelper<Runtime>>::try_dispatch(handle, Some(origin).into(), call, 0)?;
244
245 Ok(())
246 }
247
248 #[precompile::public("removeProxies()")]
253 fn remove_proxies(handle: &mut impl PrecompileHandle) -> EvmResult {
254 let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
255 let call: ProxyCall<Runtime> = ProxyCall::<Runtime>::remove_proxies {}.into();
256
257 <RuntimeHelper<Runtime>>::try_dispatch(handle, Some(origin).into(), call, 0)?;
258
259 Ok(())
260 }
261
262 #[precompile::public("proxy(address,address,bytes)")]
270 #[precompile::payable]
271 fn proxy(
272 handle: &mut impl PrecompileHandle,
273 real: Address,
274 call_to: Address,
275 call_data: BoundedBytes<GetCallDataLimit>,
276 ) -> EvmResult {
277 let evm_subcall = EvmSubCall {
278 to: call_to,
279 value: handle.context().apparent_value,
280 call_data,
281 };
282
283 Self::inner_proxy(handle, real, None, evm_subcall)
284 }
285
286 #[precompile::public("proxyForceType(address,uint8,address,bytes)")]
295 #[precompile::public("proxy_force_type(address,uint8,address,bytes)")]
296 #[precompile::payable]
297 fn proxy_force_type(
298 handle: &mut impl PrecompileHandle,
299 real: Address,
300 force_proxy_type: u8,
301 call_to: Address,
302 call_data: BoundedBytes<GetCallDataLimit>,
303 ) -> EvmResult {
304 let proxy_type = Runtime::ProxyType::decode(&mut force_proxy_type.to_le_bytes().as_slice())
305 .map_err(|_| {
306 RevertReason::custom("Failed decoding value to ProxyType")
307 .in_field("forceProxyType")
308 })?;
309
310 let evm_subcall = EvmSubCall {
311 to: call_to,
312 value: handle.context().apparent_value,
313 call_data,
314 };
315
316 Self::inner_proxy(handle, real, Some(proxy_type), evm_subcall)
317 }
318
319 #[precompile::public("isProxy(address,address,uint8,uint32)")]
326 #[precompile::view]
327 fn is_proxy(
328 handle: &mut impl PrecompileHandle,
329 real: Address,
330 delegate: Address,
331 proxy_type: u8,
332 delay: u32,
333 ) -> EvmResult<bool> {
334 let delegate = Runtime::AddressMapping::into_account_id(delegate.into());
335 let proxy_type = Runtime::ProxyType::decode(&mut proxy_type.to_le_bytes().as_slice())
336 .map_err(|_| {
337 RevertReason::custom("Failed decoding value to ProxyType").in_field("proxyType")
338 })?;
339 let delay = delay.into();
340
341 let real = Runtime::AddressMapping::into_account_id(real.into());
342
343 handle.record_db_read::<Runtime>(
346 28 + (29 * (<Runtime as pallet_proxy::Config>::MaxProxies::get() as usize)) + 16,
347 )?;
348 let is_proxy = ProxyPallet::<Runtime>::proxies(real)
349 .0
350 .iter()
351 .any(|pd| pd.delegate == delegate && pd.proxy_type == proxy_type && pd.delay == delay);
352
353 Ok(is_proxy)
354 }
355
356 fn inner_proxy(
357 handle: &mut impl PrecompileHandle,
358 real: Address,
359 force_proxy_type: Option<<Runtime as pallet_proxy::Config>::ProxyType>,
360 evm_subcall: EvmSubCall,
361 ) -> EvmResult {
362 let AddressType::EOA = precompile_set::get_address_type::<Runtime>(handle, real.into())?
364 else {
365 return Err(revert("real address must be EOA"));
366 };
367
368 let real_account_id = Runtime::AddressMapping::into_account_id(real.into());
370 let who = Runtime::AddressMapping::into_account_id(handle.context().caller);
371 handle.record_db_read::<Runtime>(
374 28 + (29 * (<Runtime as pallet_proxy::Config>::MaxProxies::get() as usize)) + 16,
375 )?;
376 let def =
377 pallet_proxy::Pallet::<Runtime>::find_proxy(&real_account_id, &who, force_proxy_type)
378 .map_err(|_| RevertReason::custom("Not proxy"))?;
379 frame_support::ensure!(def.delay.is_zero(), revert("Unannounced"));
380
381 handle.record_db_read::<Runtime>(ACCOUNT_CODES_METADATA_PROOF_SIZE.saturated_into())?;
384 let recipient_has_code =
385 pallet_evm::AccountCodesMetadata::<Runtime>::get(evm_subcall.to.0).is_some();
386
387 frame_support::ensure!(
389 def.proxy_type.is_evm_proxy_call_allowed(
390 &evm_subcall,
391 recipient_has_code,
392 handle.remaining_gas()
393 )?,
394 revert("CallFiltered")
395 );
396
397 let EvmSubCall {
398 to,
399 value,
400 call_data,
401 } = evm_subcall;
402 let address = to.0;
403
404 let sub_context = Context {
405 caller: real.0,
406 address: address.clone(),
407 apparent_value: value,
408 };
409
410 let transfer = if value.is_zero() {
411 None
412 } else {
413 let contract_address: Runtime::AccountId =
414 Runtime::AddressMapping::into_account_id(handle.context().address);
415
416 RuntimeHelper::<Runtime>::try_dispatch(
418 handle,
419 Some(contract_address).into(),
420 pallet_balances::Call::<Runtime>::transfer_allow_death {
421 dest: Runtime::Lookup::unlookup(who),
422 value: {
423 let balance: <Runtime as pallet_balances::Config<()>>::Balance =
424 value.try_into().map_err(|_| PrecompileFailure::Revert {
425 exit_status: fp_evm::ExitRevert::Reverted,
426 output: sp_std::vec::Vec::new(),
427 })?;
428 balance
429 },
430 },
431 SYSTEM_ACCOUNT_SIZE,
432 )?;
433
434 Some(Transfer {
435 source: sub_context.caller,
436 target: address.clone(),
437 value,
438 })
439 };
440
441 let (reason, output) = handle.call(
442 address,
443 transfer,
444 call_data.into(),
445 Some(handle.remaining_gas()),
446 false,
447 &sub_context,
448 );
449
450 match reason {
452 ExitReason::Fatal(exit_status) => Err(PrecompileFailure::Fatal { exit_status }),
453 ExitReason::Revert(exit_status) => Err(PrecompileFailure::Revert {
454 exit_status,
455 output,
456 }),
457 ExitReason::Error(exit_status) => Err(PrecompileFailure::Error { exit_status }),
458 ExitReason::Succeed(_) => Ok(()),
459 }
460 }
461}