1
// Copyright 2019-2025 PureStake Inc.
2
// This file is part of Moonbeam.
3

            
4
// Moonbeam is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Moonbeam is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16

            
17
#![cfg_attr(not(feature = "std"), no_std)]
18

            
19
use account::SYSTEM_ACCOUNT_SIZE;
20
use evm::ExitReason;
21
use fp_evm::{Context, PrecompileFailure, PrecompileHandle, Transfer};
22
use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo};
23
use pallet_balances::Call as BalancesCall;
24
use pallet_evm::AddressMapping;
25
use pallet_proxy::Call as ProxyCall;
26
use pallet_proxy::Pallet as ProxyPallet;
27
use precompile_utils::precompile_set::{self, AddressType, SelectorFilter};
28
use precompile_utils::prelude::*;
29
use sp_core::{Get, H160, U256};
30
use sp_runtime::{
31
	codec::Decode,
32
	traits::{ConstU32, Dispatchable, StaticLookup, Zero},
33
};
34
use sp_std::marker::PhantomData;
35

            
36
#[cfg(test)]
37
pub mod mock;
38
#[cfg(test)]
39
mod tests;
40

            
41
#[derive(Debug)]
42
pub struct OnlyIsProxy<Runtime>(PhantomData<Runtime>);
43

            
44
impl<Runtime> SelectorFilter for OnlyIsProxy<Runtime>
45
where
46
	Runtime:
47
		pallet_proxy::Config + pallet_evm::Config + frame_system::Config + pallet_balances::Config,
48
	<<Runtime as pallet_proxy::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
49
		From<Option<Runtime::AccountId>>,
50
	<Runtime as pallet_proxy::Config>::ProxyType: Decode + EvmProxyCallFilter,
51
	<Runtime as frame_system::Config>::RuntimeCall:
52
		Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
53
	<<Runtime as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
54
		From<Option<Runtime::AccountId>>,
55
	<Runtime as frame_system::Config>::RuntimeCall:
56
		From<ProxyCall<Runtime>> + From<BalancesCall<Runtime>>,
57
	<Runtime as pallet_balances::Config<()>>::Balance: TryFrom<U256> + Into<U256>,
58
	<Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
59
{
60
	fn is_allowed(_caller: H160, selector: Option<u32>) -> bool {
61
		match selector {
62
			None => false,
63
			Some(selector) => {
64
				ProxyPrecompileCall::<Runtime>::is_proxy_selectors().contains(&selector)
65
			}
66
		}
67
	}
68

            
69
	fn description() -> String {
70
		"Allowed for all callers only for selector 'is_proxy'".into()
71
	}
72
}
73

            
74
#[derive(Debug)]
75
pub struct OnlyIsProxyAndProxy<Runtime>(PhantomData<Runtime>);
76

            
77
impl<Runtime> SelectorFilter for OnlyIsProxyAndProxy<Runtime>
78
where
79
	Runtime:
80
		pallet_proxy::Config + pallet_evm::Config + frame_system::Config + pallet_balances::Config,
81
	<<Runtime as pallet_proxy::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
82
		From<Option<Runtime::AccountId>>,
83
	<Runtime as pallet_proxy::Config>::ProxyType: Decode + EvmProxyCallFilter,
84
	<Runtime as frame_system::Config>::RuntimeCall:
85
		Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
86
	<<Runtime as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
87
		From<Option<Runtime::AccountId>>,
88
	<Runtime as frame_system::Config>::RuntimeCall:
89
		From<ProxyCall<Runtime>> + From<BalancesCall<Runtime>>,
90
	<Runtime as pallet_balances::Config<()>>::Balance: TryFrom<U256> + Into<U256>,
91
	<Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
92
{
93
45
	fn is_allowed(_caller: H160, selector: Option<u32>) -> bool {
94
45
		match selector {
95
4
			None => false,
96
41
			Some(selector) => {
97
41
				ProxyPrecompileCall::<Runtime>::is_proxy_selectors().contains(&selector)
98
34
					|| ProxyPrecompileCall::<Runtime>::proxy_selectors().contains(&selector)
99
26
					|| ProxyPrecompileCall::<Runtime>::proxy_force_type_selectors()
100
26
						.contains(&selector)
101
			}
102
		}
103
45
	}
104

            
105
	fn description() -> String {
106
		"Allowed for all callers only for selectors 'is_proxy', 'proxy', 'proxy_force_type'".into()
107
	}
108
}
109

            
110
pub const CALL_DATA_LIMIT: u32 = 2u32.pow(16);
111

            
112
type GetCallDataLimit = ConstU32<CALL_DATA_LIMIT>;
113

            
114
pub struct EvmSubCall {
115
	pub to: Address,
116
	pub value: U256,
117
	pub call_data: BoundedBytes<ConstU32<CALL_DATA_LIMIT>>,
118
}
119

            
120
/// A trait to filter if an evm subcall is allowed to be executed by a proxy account.
121
/// This trait should be implemented by the `ProxyType` type configured in pallet proxy.
122
pub trait EvmProxyCallFilter: Sized + Send + Sync {
123
	/// If returns `false`, then the subcall will not be executed and the evm transaction will
124
	/// revert with error message "CallFiltered".
125
	fn is_evm_proxy_call_allowed(
126
		&self,
127
		_call: &EvmSubCall,
128
		_recipient_has_code: bool,
129
		_gas: u64,
130
	) -> EvmResult<bool> {
131
		Ok(false)
132
	}
133
}
134

            
135
/// A precompile to wrap the functionality from pallet-proxy.
136
pub struct ProxyPrecompile<Runtime>(PhantomData<Runtime>);
137

            
138
214
#[precompile_utils::precompile]
139
impl<Runtime> ProxyPrecompile<Runtime>
140
where
141
	Runtime:
142
		pallet_proxy::Config + pallet_evm::Config + frame_system::Config + pallet_balances::Config,
143
	<<Runtime as pallet_proxy::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
144
		From<Option<Runtime::AccountId>>,
145
	<Runtime as pallet_proxy::Config>::ProxyType: Decode + EvmProxyCallFilter,
146
	<Runtime as frame_system::Config>::RuntimeCall:
147
		Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
148
	<<Runtime as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
149
		From<Option<Runtime::AccountId>>,
150
	<Runtime as frame_system::Config>::RuntimeCall:
151
		From<ProxyCall<Runtime>> + From<BalancesCall<Runtime>>,
152
	<Runtime as pallet_balances::Config<()>>::Balance: TryFrom<U256> + Into<U256>,
153
	<Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
154
{
155
	/// Register a proxy account for the sender that is able to make calls on its behalf.
156
	/// The dispatch origin for this call must be Signed.
157
	///
158
	/// Parameters:
159
	/// * delegate: The account that the caller would like to make a proxy.
160
	/// * proxy_type: The permissions allowed for this proxy account.
161
	/// * delay: The announcement period required of the initial proxy. Will generally be zero.
162
	#[precompile::public("addProxy(address,uint8,uint32)")]
163
8
	fn add_proxy(
164
8
		handle: &mut impl PrecompileHandle,
165
8
		delegate: Address,
166
8
		proxy_type: u8,
167
8
		delay: u32,
168
8
	) -> EvmResult {
169
8
		let delegate = Runtime::AddressMapping::into_account_id(delegate.into());
170
8
		let proxy_type = Runtime::ProxyType::decode(&mut proxy_type.to_le_bytes().as_slice())
171
8
			.map_err(|_| {
172
1
				RevertReason::custom("Failed decoding value to ProxyType").in_field("proxyType")
173
8
			})?;
174
7
		let delay = delay.into();
175
7

            
176
7
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
177
7

            
178
7
		// Disallow re-adding proxy via precompile to prevent re-entrancy.
179
7
		// See: https://github.com/PureStake/sr-moonbeam/issues/30
180
7
		// Note: It is also assumed that EVM calls are only allowed through `Origin::Root` and
181
7
		// filtered via CallFilter
182
7
		// Proxies:
183
7
		// Twox64Concat(8) + AccountId(20) + BoundedVec(ProxyDefinition * MaxProxies) + Balance(16)
184
7
		handle.record_db_read::<Runtime>(
185
7
			28 + (29 * (<Runtime as pallet_proxy::Config>::MaxProxies::get() as usize)) + 16,
186
7
		)?;
187
7
		if ProxyPallet::<Runtime>::proxies(origin.clone())
188
7
			.0
189
7
			.iter()
190
7
			.any(|pd| pd.delegate == delegate)
191
		{
192
4
			return Err(revert("Cannot add more than one proxy"));
193
3
		}
194
3

            
195
3
		let delegate: <Runtime::Lookup as StaticLookup>::Source =
196
3
			Runtime::Lookup::unlookup(delegate.clone());
197
3
		let call: ProxyCall<Runtime> = ProxyCall::<Runtime>::add_proxy {
198
3
			delegate,
199
3
			proxy_type,
200
3
			delay,
201
3
		}
202
3
		.into();
203
3

            
204
3
		<RuntimeHelper<Runtime>>::try_dispatch(handle, Some(origin).into(), call, 0)?;
205

            
206
3
		Ok(())
207
8
	}
208

            
209
	/// Unregister a proxy account for the sender.
210
	/// The dispatch origin for this call must be Signed.
211
	///
212
	/// Parameters:
213
	/// * delegate: The account that the caller would like to remove as a proxy.
214
	/// * proxy_type: The permissions currently enabled for the removed proxy account.
215
	/// * delay: The announcement period required of the initial proxy. Will generally be zero.
216
	#[precompile::public("removeProxy(address,uint8,uint32)")]
217
3
	fn remove_proxy(
218
3
		handle: &mut impl PrecompileHandle,
219
3
		delegate: Address,
220
3
		proxy_type: u8,
221
3
		delay: u32,
222
3
	) -> EvmResult {
223
3
		let delegate = Runtime::AddressMapping::into_account_id(delegate.into());
224
3
		let proxy_type = Runtime::ProxyType::decode(&mut proxy_type.to_le_bytes().as_slice())
225
3
			.map_err(|_| {
226
1
				RevertReason::custom("Failed decoding value to ProxyType").in_field("proxyType")
227
3
			})?;
228
2
		let delay = delay.into();
229
2

            
230
2
		let delegate: <Runtime::Lookup as StaticLookup>::Source =
231
2
			Runtime::Lookup::unlookup(delegate.clone());
232
2
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
233
2
		let call: ProxyCall<Runtime> = ProxyCall::<Runtime>::remove_proxy {
234
2
			delegate,
235
2
			proxy_type,
236
2
			delay,
237
2
		}
238
2
		.into();
239
2

            
240
2
		<RuntimeHelper<Runtime>>::try_dispatch(handle, Some(origin).into(), call, 0)?;
241

            
242
1
		Ok(())
243
3
	}
244

            
245
	/// Unregister all proxy accounts for the sender.
246
	/// The dispatch origin for this call must be Signed.
247
	/// WARNING: This may be called on accounts created by anonymous, however if done, then the
248
	/// unreserved fees will be inaccessible. All access to this account will be lost.
249
	#[precompile::public("removeProxies()")]
250
2
	fn remove_proxies(handle: &mut impl PrecompileHandle) -> EvmResult {
251
2
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
252
2
		let call: ProxyCall<Runtime> = ProxyCall::<Runtime>::remove_proxies {}.into();
253
2

            
254
2
		<RuntimeHelper<Runtime>>::try_dispatch(handle, Some(origin).into(), call, 0)?;
255

            
256
2
		Ok(())
257
2
	}
258

            
259
	/// Dispatch the given subcall (`call_to`, `call_data`) from an account that the sender is
260
	/// authorised for through `add_proxy`.
261
	///
262
	/// Parameters:
263
	/// - `real`: The account that the proxy will make a call on behalf of.
264
	/// - `call_to`: Recipient of the call to be made by the `real` account.
265
	/// - `call_data`: Data of the call to be made by the `real` account.
266
	#[precompile::public("proxy(address,address,bytes)")]
267
	#[precompile::payable]
268
5
	fn proxy(
269
5
		handle: &mut impl PrecompileHandle,
270
5
		real: Address,
271
5
		call_to: Address,
272
5
		call_data: BoundedBytes<GetCallDataLimit>,
273
5
	) -> EvmResult {
274
5
		let evm_subcall = EvmSubCall {
275
5
			to: call_to,
276
5
			value: handle.context().apparent_value,
277
5
			call_data,
278
5
		};
279
5

            
280
5
		Self::inner_proxy(handle, real, None, evm_subcall)
281
5
	}
282

            
283
	/// Dispatch the given subcall (`call_to`, `call_data`) from an account that the sender is
284
	/// authorised for through `add_proxy`.
285
	///
286
	/// Parameters:
287
	/// - `real`: The account that the proxy will make a call on behalf of.
288
	/// - `force_proxy_type`: Specify the exact proxy type to be used and checked for this call.
289
	/// - `call_to`: Recipient of the call to be made by the `real` account.
290
	/// - `call_data`: Data of the call to be made by the `real` account.
291
	#[precompile::public("proxyForceType(address,uint8,address,bytes)")]
292
	#[precompile::public("proxy_force_type(address,uint8,address,bytes)")]
293
	#[precompile::payable]
294
1
	fn proxy_force_type(
295
1
		handle: &mut impl PrecompileHandle,
296
1
		real: Address,
297
1
		force_proxy_type: u8,
298
1
		call_to: Address,
299
1
		call_data: BoundedBytes<GetCallDataLimit>,
300
1
	) -> EvmResult {
301
1
		let proxy_type = Runtime::ProxyType::decode(&mut force_proxy_type.to_le_bytes().as_slice())
302
1
			.map_err(|_| {
303
1
				RevertReason::custom("Failed decoding value to ProxyType")
304
1
					.in_field("forceProxyType")
305
1
			})?;
306

            
307
		let evm_subcall = EvmSubCall {
308
			to: call_to,
309
			value: handle.context().apparent_value,
310
			call_data,
311
		};
312

            
313
		Self::inner_proxy(handle, real, Some(proxy_type), evm_subcall)
314
1
	}
315

            
316
	/// Checks if the caller has an account proxied with a given proxy type
317
	///
318
	/// Parameters:
319
	/// * delegate: The account that the caller has maybe proxied
320
	/// * proxyType: The permissions allowed for the proxy
321
	/// * delay: The announcement period required of the initial proxy. Will generally be zero.
322
	#[precompile::public("isProxy(address,address,uint8,uint32)")]
323
	#[precompile::view]
324
5
	fn is_proxy(
325
5
		handle: &mut impl PrecompileHandle,
326
5
		real: Address,
327
5
		delegate: Address,
328
5
		proxy_type: u8,
329
5
		delay: u32,
330
5
	) -> EvmResult<bool> {
331
5
		let delegate = Runtime::AddressMapping::into_account_id(delegate.into());
332
5
		let proxy_type = Runtime::ProxyType::decode(&mut proxy_type.to_le_bytes().as_slice())
333
5
			.map_err(|_| {
334
				RevertReason::custom("Failed decoding value to ProxyType").in_field("proxyType")
335
5
			})?;
336
5
		let delay = delay.into();
337
5

            
338
5
		let real = Runtime::AddressMapping::into_account_id(real.into());
339
5

            
340
5
		// Proxies:
341
5
		// Twox64Concat(8) + AccountId(20) + BoundedVec(ProxyDefinition * MaxProxies) + Balance(16)
342
5
		handle.record_db_read::<Runtime>(
343
5
			28 + (29 * (<Runtime as pallet_proxy::Config>::MaxProxies::get() as usize)) + 16,
344
5
		)?;
345
5
		let is_proxy = ProxyPallet::<Runtime>::proxies(real)
346
5
			.0
347
5
			.iter()
348
5
			.any(|pd| pd.delegate == delegate && pd.proxy_type == proxy_type && pd.delay == delay);
349
5

            
350
5
		Ok(is_proxy)
351
5
	}
352

            
353
5
	fn inner_proxy(
354
5
		handle: &mut impl PrecompileHandle,
355
5
		real: Address,
356
5
		force_proxy_type: Option<<Runtime as pallet_proxy::Config>::ProxyType>,
357
5
		evm_subcall: EvmSubCall,
358
5
	) -> EvmResult {
359
5
		// Check that we only perform proxy calls on behalf of externally owned accounts
360
5
		let AddressType::EOA = precompile_set::get_address_type::<Runtime>(handle, real.into())?
361
		else {
362
1
			return Err(revert("real address must be EOA"));
363
		};
364

            
365
		// Read proxy
366
4
		let real_account_id = Runtime::AddressMapping::into_account_id(real.into());
367
4
		let who = Runtime::AddressMapping::into_account_id(handle.context().caller);
368
4
		// Proxies:
369
4
		// Twox64Concat(8) + AccountId(20) + BoundedVec(ProxyDefinition * MaxProxies) + Balance(16)
370
4
		handle.record_db_read::<Runtime>(
371
4
			28 + (29 * (<Runtime as pallet_proxy::Config>::MaxProxies::get() as usize)) + 16,
372
4
		)?;
373
2
		let def =
374
4
			pallet_proxy::Pallet::<Runtime>::find_proxy(&real_account_id, &who, force_proxy_type)
375
4
				.map_err(|_| RevertReason::custom("Not proxy"))?;
376
2
		frame_support::ensure!(def.delay.is_zero(), revert("Unannounced"));
377

            
378
		// Read subcall recipient code
379
		// AccountCodes: Blake2128(16) + H160(20) + Vec(5)
380
		// decode_len reads the first 5 bytes to find the payload len under this key
381
2
		handle.record_db_read::<Runtime>(41)?;
382
2
		let recipient_has_code =
383
2
			pallet_evm::AccountCodes::<Runtime>::decode_len(evm_subcall.to.0).unwrap_or(0) > 0;
384
2

            
385
2
		// Apply proxy type filter
386
2
		frame_support::ensure!(
387
2
			def.proxy_type.is_evm_proxy_call_allowed(
388
2
				&evm_subcall,
389
2
				recipient_has_code,
390
2
				handle.remaining_gas()
391
2
			)?,
392
1
			revert("CallFiltered")
393
		);
394

            
395
		let EvmSubCall {
396
1
			to,
397
1
			value,
398
1
			call_data,
399
1
		} = evm_subcall;
400
1
		let address = to.0;
401
1

            
402
1
		let sub_context = Context {
403
1
			caller: real.0,
404
1
			address: address.clone(),
405
1
			apparent_value: value,
406
1
		};
407

            
408
1
		let transfer = if value.is_zero() {
409
1
			None
410
		} else {
411
			let contract_address: Runtime::AccountId =
412
				Runtime::AddressMapping::into_account_id(handle.context().address);
413

            
414
			// Send back funds received by the precompile.
415
			RuntimeHelper::<Runtime>::try_dispatch(
416
				handle,
417
				Some(contract_address).into(),
418
				pallet_balances::Call::<Runtime>::transfer_allow_death {
419
					dest: Runtime::Lookup::unlookup(who),
420
					value: {
421
						let balance: <Runtime as pallet_balances::Config<()>>::Balance =
422
							value.try_into().map_err(|_| PrecompileFailure::Revert {
423
								exit_status: fp_evm::ExitRevert::Reverted,
424
								output: sp_std::vec::Vec::new(),
425
							})?;
426
						balance
427
					},
428
				},
429
				SYSTEM_ACCOUNT_SIZE,
430
			)?;
431

            
432
			Some(Transfer {
433
				source: sub_context.caller,
434
				target: address.clone(),
435
				value,
436
			})
437
		};
438

            
439
1
		let (reason, output) = handle.call(
440
1
			address,
441
1
			transfer,
442
1
			call_data.into(),
443
1
			Some(handle.remaining_gas()),
444
1
			false,
445
1
			&sub_context,
446
1
		);
447
1

            
448
1
		// Return subcall result
449
1
		match reason {
450
			ExitReason::Fatal(exit_status) => Err(PrecompileFailure::Fatal { exit_status }),
451
			ExitReason::Revert(exit_status) => Err(PrecompileFailure::Revert {
452
				exit_status,
453
				output,
454
			}),
455
			ExitReason::Error(exit_status) => Err(PrecompileFailure::Error { exit_status }),
456
1
			ExitReason::Succeed(_) => Ok(()),
457
		}
458
5
	}
459
}