1
// Copyright 2019-2022 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
{
59
	fn is_allowed(_caller: H160, selector: Option<u32>) -> bool {
60
		match selector {
61
			None => false,
62
			Some(selector) => {
63
				ProxyPrecompileCall::<Runtime>::is_proxy_selectors().contains(&selector)
64
			}
65
		}
66
	}
67

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

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

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

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

            
108
pub const CALL_DATA_LIMIT: u32 = 2u32.pow(16);
109

            
110
type GetCallDataLimit = ConstU32<CALL_DATA_LIMIT>;
111

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

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

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

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

            
173
7
		let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
174
7

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

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

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

            
203
3
		Ok(())
204
8
	}
205

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

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

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

            
239
1
		Ok(())
240
3
	}
241

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

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

            
253
2
		Ok(())
254
2
	}
255

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

            
277
5
		Self::inner_proxy(handle, real, None, evm_subcall)
278
5
	}
279

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

            
304
		let evm_subcall = EvmSubCall {
305
			to: call_to,
306
			value: handle.context().apparent_value,
307
			call_data,
308
		};
309

            
310
		Self::inner_proxy(handle, real, Some(proxy_type), evm_subcall)
311
1
	}
312

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

            
335
5
		let real = Runtime::AddressMapping::into_account_id(real.into());
336
5

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

            
347
5
		Ok(is_proxy)
348
5
	}
349

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

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

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

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

            
392
		let EvmSubCall {
393
1
			to,
394
1
			value,
395
1
			call_data,
396
1
		} = evm_subcall;
397
1
		let address = to.0;
398
1

            
399
1
		let sub_context = Context {
400
1
			caller: real.0,
401
1
			address: address.clone(),
402
1
			apparent_value: value,
403
1
		};
404

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

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

            
429
			Some(Transfer {
430
				source: sub_context.caller,
431
				target: address.clone(),
432
				value,
433
			})
434
		};
435

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

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