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
//! Precompile to interact with pallet_balances instances using the ERC20 interface standard.
18

            
19
#![cfg_attr(not(feature = "std"), no_std)]
20

            
21
use account::SYSTEM_ACCOUNT_SIZE;
22
use fp_evm::PrecompileHandle;
23
use frame_support::{
24
	dispatch::{GetDispatchInfo, PostDispatchInfo},
25
	sp_runtime::traits::{Bounded, CheckedSub, Dispatchable, StaticLookup},
26
	storage::types::{StorageDoubleMap, StorageMap, ValueQuery},
27
	traits::StorageInstance,
28
	Blake2_128Concat,
29
};
30
use pallet_balances::pallet::{
31
	Instance1, Instance10, Instance11, Instance12, Instance13, Instance14, Instance15, Instance16,
32
	Instance2, Instance3, Instance4, Instance5, Instance6, Instance7, Instance8, Instance9,
33
};
34
use pallet_evm::AddressMapping;
35
use precompile_utils::prelude::*;
36
use sp_core::{H160, H256, U256};
37
use sp_std::{
38
	convert::{TryFrom, TryInto},
39
	marker::PhantomData,
40
};
41

            
42
mod eip2612;
43
use eip2612::Eip2612;
44

            
45
#[cfg(test)]
46
mod mock;
47
#[cfg(test)]
48
mod tests;
49

            
50
/// Solidity selector of the Transfer log, which is the Keccak of the Log signature.
51
pub const SELECTOR_LOG_TRANSFER: [u8; 32] = keccak256!("Transfer(address,address,uint256)");
52

            
53
/// Solidity selector of the Approval log, which is the Keccak of the Log signature.
54
pub const SELECTOR_LOG_APPROVAL: [u8; 32] = keccak256!("Approval(address,address,uint256)");
55

            
56
/// Solidity selector of the Deposit log, which is the Keccak of the Log signature.
57
pub const SELECTOR_LOG_DEPOSIT: [u8; 32] = keccak256!("Deposit(address,uint256)");
58

            
59
/// Solidity selector of the Withdraw log, which is the Keccak of the Log signature.
60
pub const SELECTOR_LOG_WITHDRAWAL: [u8; 32] = keccak256!("Withdrawal(address,uint256)");
61

            
62
/// Associates pallet Instance to a prefix used for the Approves storage.
63
/// This trait is implemented for () and the 16 substrate Instance.
64
pub trait InstanceToPrefix {
65
	/// Prefix used for the Approves storage.
66
	type ApprovesPrefix: StorageInstance;
67

            
68
	/// Prefix used for the Approves storage.
69
	type NoncesPrefix: StorageInstance;
70
}
71

            
72
// We use a macro to implement the trait for () and the 16 substrate Instance.
73
macro_rules! impl_prefix {
74
	($instance:ident, $name:literal) => {
75
		// Using `paste!` we generate a dedicated module to avoid collisions
76
		// between each instance `Approves` struct.
77
		paste::paste! {
78
			mod [<_impl_prefix_ $instance:snake>] {
79
				use super::*;
80

            
81
				pub struct Approves;
82

            
83
				impl StorageInstance for Approves {
84
					const STORAGE_PREFIX: &'static str = "Approves";
85

            
86
17
					fn pallet_prefix() -> &'static str {
87
17
						$name
88
17
					}
89
				}
90

            
91
				pub struct Nonces;
92

            
93
				impl StorageInstance for Nonces {
94
					const STORAGE_PREFIX: &'static str = "Nonces";
95

            
96
14
					fn pallet_prefix() -> &'static str {
97
14
						$name
98
14
					}
99
				}
100

            
101
				impl InstanceToPrefix for $instance {
102
					type ApprovesPrefix = Approves;
103
					type NoncesPrefix = Nonces;
104
				}
105
			}
106
		}
107
	};
108
}
109

            
110
// Since the macro expect a `ident` to be used with `paste!` we cannot provide `()` directly.
111
type Instance0 = ();
112

            
113
impl_prefix!(Instance0, "Erc20Instance0Balances");
114
impl_prefix!(Instance1, "Erc20Instance1Balances");
115
impl_prefix!(Instance2, "Erc20Instance2Balances");
116
impl_prefix!(Instance3, "Erc20Instance3Balances");
117
impl_prefix!(Instance4, "Erc20Instance4Balances");
118
impl_prefix!(Instance5, "Erc20Instance5Balances");
119
impl_prefix!(Instance6, "Erc20Instance6Balances");
120
impl_prefix!(Instance7, "Erc20Instance7Balances");
121
impl_prefix!(Instance8, "Erc20Instance8Balances");
122
impl_prefix!(Instance9, "Erc20Instance9Balances");
123
impl_prefix!(Instance10, "Erc20Instance10Balances");
124
impl_prefix!(Instance11, "Erc20Instance11Balances");
125
impl_prefix!(Instance12, "Erc20Instance12Balances");
126
impl_prefix!(Instance13, "Erc20Instance13Balances");
127
impl_prefix!(Instance14, "Erc20Instance14Balances");
128
impl_prefix!(Instance15, "Erc20Instance15Balances");
129
impl_prefix!(Instance16, "Erc20Instance16Balances");
130

            
131
/// Alias for the Balance type for the provided Runtime and Instance.
132
pub type BalanceOf<Runtime, Instance = ()> =
133
	<Runtime as pallet_balances::Config<Instance>>::Balance;
134

            
135
/// Storage type used to store approvals, since `pallet_balances` doesn't
136
/// handle this behavior.
137
/// (Owner => Allowed => Amount)
138
pub type ApprovesStorage<Runtime, Instance> = StorageDoubleMap<
139
	<Instance as InstanceToPrefix>::ApprovesPrefix,
140
	Blake2_128Concat,
141
	<Runtime as frame_system::Config>::AccountId,
142
	Blake2_128Concat,
143
	<Runtime as frame_system::Config>::AccountId,
144
	BalanceOf<Runtime, Instance>,
145
>;
146

            
147
/// Storage type used to store EIP2612 nonces.
148
pub type NoncesStorage<Instance> = StorageMap<
149
	<Instance as InstanceToPrefix>::NoncesPrefix,
150
	// Owner
151
	Blake2_128Concat,
152
	H160,
153
	// Nonce
154
	U256,
155
	ValueQuery,
156
>;
157

            
158
/// Metadata of an ERC20 token.
159
pub trait Erc20Metadata {
160
	/// Returns the name of the token.
161
	fn name() -> &'static str;
162

            
163
	/// Returns the symbol of the token.
164
	fn symbol() -> &'static str;
165

            
166
	/// Returns the decimals places of the token.
167
	fn decimals() -> u8;
168

            
169
	/// Must return `true` only if it represents the main native currency of
170
	/// the network. It must be the currency used in `pallet_evm`.
171
	fn is_native_currency() -> bool;
172
}
173

            
174
/// Precompile exposing a pallet_balance as an ERC20.
175
/// Multiple precompiles can support instances of pallet_balance.
176
/// The precompile uses an additional storage to store approvals.
177
pub struct Erc20BalancesPrecompile<Runtime, Metadata: Erc20Metadata, Instance: 'static = ()>(
178
	PhantomData<(Runtime, Metadata, Instance)>,
179
);
180

            
181
462
#[precompile_utils::precompile]
182
impl<Runtime, Metadata, Instance> Erc20BalancesPrecompile<Runtime, Metadata, Instance>
183
where
184
	Runtime: pallet_balances::Config<Instance> + pallet_evm::Config,
185
	Runtime::RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
186
	Runtime::RuntimeCall: From<pallet_balances::Call<Runtime, Instance>>,
187
	<Runtime::RuntimeCall as Dispatchable>::RuntimeOrigin: From<Option<Runtime::AccountId>>,
188
	BalanceOf<Runtime, Instance>: TryFrom<U256> + Into<U256>,
189
	Metadata: Erc20Metadata,
190
	Instance: InstanceToPrefix + 'static,
191
	<Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
192
{
193
	#[precompile::public("totalSupply()")]
194
	#[precompile::view]
195
2
	fn total_supply(handle: &mut impl PrecompileHandle) -> EvmResult<U256> {
196
2
		// TotalIssuance: Balance(16)
197
2
		handle.record_db_read::<Runtime>(16)?;
198

            
199
2
		Ok(pallet_balances::Pallet::<Runtime, Instance>::total_issuance().into())
200
2
	}
201

            
202
	#[precompile::public("balanceOf(address)")]
203
	#[precompile::view]
204
24
	fn balance_of(handle: &mut impl PrecompileHandle, owner: Address) -> EvmResult<U256> {
205
24
		// frame_system::Account:
206
24
		// Blake2128(16) + AccountId(20) + AccountInfo ((4 * 4) + AccountData(16 * 4))
207
24
		handle.record_db_read::<Runtime>(116)?;
208

            
209
24
		let owner: H160 = owner.into();
210
24
		let owner: Runtime::AccountId = Runtime::AddressMapping::into_account_id(owner);
211
24

            
212
24
		Ok(pallet_balances::Pallet::<Runtime, Instance>::usable_balance(&owner).into())
213
24
	}
214

            
215
	#[precompile::public("allowance(address,address)")]
216
	#[precompile::view]
217
8
	fn allowance(
218
8
		handle: &mut impl PrecompileHandle,
219
8
		owner: Address,
220
8
		spender: Address,
221
8
	) -> EvmResult<U256> {
222
8
		// frame_system::ApprovesStorage:
223
8
		// (2 * (Blake2128(16) + AccountId(20)) + Balanceof(16)
224
8
		handle.record_db_read::<Runtime>(88)?;
225

            
226
8
		let owner: H160 = owner.into();
227
8
		let spender: H160 = spender.into();
228
8

            
229
8
		let owner: Runtime::AccountId = Runtime::AddressMapping::into_account_id(owner);
230
8
		let spender: Runtime::AccountId = Runtime::AddressMapping::into_account_id(spender);
231
8

            
232
8
		Ok(ApprovesStorage::<Runtime, Instance>::get(owner, spender)
233
8
			.unwrap_or_default()
234
8
			.into())
235
8
	}
236

            
237
	#[precompile::public("approve(address,uint256)")]
238
5
	fn approve(
239
5
		handle: &mut impl PrecompileHandle,
240
5
		spender: Address,
241
5
		value: U256,
242
5
	) -> EvmResult<bool> {
243
5
		handle.record_cost(RuntimeHelper::<Runtime>::db_write_gas_cost())?;
244
5
		handle.record_log_costs_manual(3, 32)?;
245

            
246
5
		let spender: H160 = spender.into();
247
5

            
248
5
		// Write into storage.
249
5
		{
250
5
			let caller: Runtime::AccountId =
251
5
				Runtime::AddressMapping::into_account_id(handle.context().caller);
252
5
			let spender: Runtime::AccountId = Runtime::AddressMapping::into_account_id(spender);
253
5
			// Amount saturate if too high.
254
5
			let value = Self::u256_to_amount(value).unwrap_or_else(|_| Bounded::max_value());
255
5

            
256
5
			ApprovesStorage::<Runtime, Instance>::insert(caller, spender, value);
257
5
		}
258
5

            
259
5
		log3(
260
5
			handle.context().address,
261
5
			SELECTOR_LOG_APPROVAL,
262
5
			handle.context().caller,
263
5
			spender,
264
5
			solidity::encode_event_data(value),
265
5
		)
266
5
		.record(handle)?;
267

            
268
		// Build output.
269
5
		Ok(true)
270
5
	}
271

            
272
	#[precompile::public("transfer(address,uint256)")]
273
2
	fn transfer(handle: &mut impl PrecompileHandle, to: Address, value: U256) -> EvmResult<bool> {
274
2
		handle.record_log_costs_manual(3, 32)?;
275

            
276
2
		let to: H160 = to.into();
277
2

            
278
2
		// Build call with origin.
279
2
		{
280
2
			let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
281
2
			let to = Runtime::AddressMapping::into_account_id(to);
282
2
			let value = Self::u256_to_amount(value).in_field("value")?;
283

            
284
			// Dispatch call (if enough gas).
285
2
			RuntimeHelper::<Runtime>::try_dispatch(
286
2
				handle,
287
2
				Some(origin).into(),
288
2
				pallet_balances::Call::<Runtime, Instance>::transfer_allow_death {
289
2
					dest: Runtime::Lookup::unlookup(to),
290
2
					value,
291
2
				},
292
2
				SYSTEM_ACCOUNT_SIZE,
293
2
			)?;
294
		}
295

            
296
1
		log3(
297
1
			handle.context().address,
298
1
			SELECTOR_LOG_TRANSFER,
299
1
			handle.context().caller,
300
1
			to,
301
1
			solidity::encode_event_data(value),
302
1
		)
303
1
		.record(handle)?;
304

            
305
1
		Ok(true)
306
2
	}
307

            
308
	#[precompile::public("transferFrom(address,address,uint256)")]
309
3
	fn transfer_from(
310
3
		handle: &mut impl PrecompileHandle,
311
3
		from: Address,
312
3
		to: Address,
313
3
		value: U256,
314
3
	) -> EvmResult<bool> {
315
3
		// frame_system::ApprovesStorage:
316
3
		// (2 * (Blake2128(16) + AccountId(20)) + Balanceof(16)
317
3
		handle.record_db_read::<Runtime>(88)?;
318
3
		handle.record_cost(RuntimeHelper::<Runtime>::db_write_gas_cost())?;
319
3
		handle.record_log_costs_manual(3, 32)?;
320

            
321
3
		let from: H160 = from.into();
322
3
		let to: H160 = to.into();
323
3

            
324
3
		{
325
3
			let caller: Runtime::AccountId =
326
3
				Runtime::AddressMapping::into_account_id(handle.context().caller);
327
3
			let from: Runtime::AccountId = Runtime::AddressMapping::into_account_id(from);
328
3
			let to: Runtime::AccountId = Runtime::AddressMapping::into_account_id(to);
329
3
			let value = Self::u256_to_amount(value).in_field("value")?;
330

            
331
			// If caller is "from", it can spend as much as it wants.
332
3
			if caller != from {
333
2
				ApprovesStorage::<Runtime, Instance>::mutate(from.clone(), caller, |entry| {
334
					// Get current allowed value, exit if None.
335
2
					let allowed = entry.ok_or(revert("spender not allowed"))?;
336

            
337
					// Remove "value" from allowed, exit if underflow.
338
2
					let allowed = allowed
339
2
						.checked_sub(&value)
340
2
						.ok_or_else(|| revert("trying to spend more than allowed"))?;
341

            
342
					// Update allowed value.
343
1
					*entry = Some(allowed);
344
1

            
345
1
					EvmResult::Ok(())
346
2
				})?;
347
1
			}
348

            
349
			// Build call with origin. Here origin is the "from"/owner field.
350
			// Dispatch call (if enough gas).
351
2
			RuntimeHelper::<Runtime>::try_dispatch(
352
2
				handle,
353
2
				Some(from).into(),
354
2
				pallet_balances::Call::<Runtime, Instance>::transfer_allow_death {
355
2
					dest: Runtime::Lookup::unlookup(to),
356
2
					value,
357
2
				},
358
2
				SYSTEM_ACCOUNT_SIZE,
359
2
			)?;
360
		}
361

            
362
2
		log3(
363
2
			handle.context().address,
364
2
			SELECTOR_LOG_TRANSFER,
365
2
			from,
366
2
			to,
367
2
			solidity::encode_event_data(value),
368
2
		)
369
2
		.record(handle)?;
370

            
371
2
		Ok(true)
372
3
	}
373

            
374
	#[precompile::public("name()")]
375
	#[precompile::view]
376
2
	fn name(_handle: &mut impl PrecompileHandle) -> EvmResult<UnboundedBytes> {
377
2
		Ok(Metadata::name().into())
378
2
	}
379

            
380
	#[precompile::public("symbol()")]
381
	#[precompile::view]
382
2
	fn symbol(_handle: &mut impl PrecompileHandle) -> EvmResult<UnboundedBytes> {
383
2
		Ok(Metadata::symbol().into())
384
2
	}
385

            
386
	#[precompile::public("decimals()")]
387
	#[precompile::view]
388
2
	fn decimals(_handle: &mut impl PrecompileHandle) -> EvmResult<u8> {
389
2
		Ok(Metadata::decimals())
390
2
	}
391

            
392
	#[precompile::public("deposit()")]
393
	#[precompile::fallback]
394
	#[precompile::payable]
395
8
	fn deposit(handle: &mut impl PrecompileHandle) -> EvmResult {
396
8
		// Deposit only makes sense for the native currency.
397
8
		if !Metadata::is_native_currency() {
398
			return Err(RevertReason::UnknownSelector.into());
399
8
		}
400
8

            
401
8
		let caller: Runtime::AccountId =
402
8
			Runtime::AddressMapping::into_account_id(handle.context().caller);
403
8
		let precompile = Runtime::AddressMapping::into_account_id(handle.context().address);
404
8
		let amount = Self::u256_to_amount(handle.context().apparent_value)?;
405

            
406
8
		if amount.into() == U256::from(0u32) {
407
4
			return Err(revert("deposited amount must be non-zero"));
408
4
		}
409
4

            
410
4
		handle.record_log_costs_manual(2, 32)?;
411

            
412
		// Send back funds received by the precompile.
413
4
		RuntimeHelper::<Runtime>::try_dispatch(
414
4
			handle,
415
4
			Some(precompile).into(),
416
4
			pallet_balances::Call::<Runtime, Instance>::transfer_allow_death {
417
4
				dest: Runtime::Lookup::unlookup(caller),
418
4
				value: amount,
419
4
			},
420
4
			SYSTEM_ACCOUNT_SIZE,
421
4
		)?;
422

            
423
3
		log2(
424
3
			handle.context().address,
425
3
			SELECTOR_LOG_DEPOSIT,
426
3
			handle.context().caller,
427
3
			solidity::encode_event_data(handle.context().apparent_value),
428
3
		)
429
3
		.record(handle)?;
430

            
431
3
		Ok(())
432
8
	}
433

            
434
	#[precompile::public("withdraw(uint256)")]
435
2
	fn withdraw(handle: &mut impl PrecompileHandle, value: U256) -> EvmResult {
436
2
		// Withdraw only makes sense for the native currency.
437
2
		if !Metadata::is_native_currency() {
438
			return Err(RevertReason::UnknownSelector.into());
439
2
		}
440
2

            
441
2
		handle.record_log_costs_manual(2, 32)?;
442

            
443
2
		let account_amount: U256 = {
444
2
			let owner: Runtime::AccountId =
445
2
				Runtime::AddressMapping::into_account_id(handle.context().caller);
446
2
			pallet_balances::Pallet::<Runtime, Instance>::usable_balance(&owner).into()
447
2
		};
448
2

            
449
2
		if value > account_amount {
450
1
			return Err(revert("Trying to withdraw more than owned"));
451
1
		}
452
1

            
453
1
		log2(
454
1
			handle.context().address,
455
1
			SELECTOR_LOG_WITHDRAWAL,
456
1
			handle.context().caller,
457
1
			solidity::encode_event_data(value),
458
1
		)
459
1
		.record(handle)?;
460

            
461
1
		Ok(())
462
2
	}
463

            
464
	#[precompile::public("permit(address,address,uint256,uint256,uint8,bytes32,bytes32)")]
465
	#[allow(clippy::too_many_arguments)]
466
5
	fn eip2612_permit(
467
5
		handle: &mut impl PrecompileHandle,
468
5
		owner: Address,
469
5
		spender: Address,
470
5
		value: U256,
471
5
		deadline: U256,
472
5
		v: u8,
473
5
		r: H256,
474
5
		s: H256,
475
5
	) -> EvmResult {
476
5
		<Eip2612<Runtime, Metadata, Instance>>::permit(
477
5
			handle, owner, spender, value, deadline, v, r, s,
478
5
		)
479
5
	}
480

            
481
	#[precompile::public("nonces(address)")]
482
	#[precompile::view]
483
8
	fn eip2612_nonces(handle: &mut impl PrecompileHandle, owner: Address) -> EvmResult<U256> {
484
8
		<Eip2612<Runtime, Metadata, Instance>>::nonces(handle, owner)
485
8
	}
486

            
487
	#[precompile::public("DOMAIN_SEPARATOR()")]
488
	#[precompile::view]
489
1
	fn eip2612_domain_separator(handle: &mut impl PrecompileHandle) -> EvmResult<H256> {
490
1
		<Eip2612<Runtime, Metadata, Instance>>::domain_separator(handle)
491
1
	}
492

            
493
20
	fn u256_to_amount(value: U256) -> MayRevert<BalanceOf<Runtime, Instance>> {
494
20
		value
495
20
			.try_into()
496
20
			.map_err(|_| RevertReason::value_is_too_large("balance type").into())
497
20
	}
498
}