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 core::fmt::Display;
21
use fp_evm::{ExitError, PrecompileHandle};
22
use frame_support::traits::fungibles::Inspect;
23
use frame_support::traits::fungibles::{
24
	approvals::Inspect as ApprovalInspect, metadata::Inspect as MetadataInspect,
25
	roles::Inspect as RolesInspect,
26
};
27
use frame_support::traits::{Get, OriginTrait};
28
use frame_support::{
29
	dispatch::{GetDispatchInfo, PostDispatchInfo},
30
	sp_runtime::traits::StaticLookup,
31
};
32
use moonkit_xcm_primitives::AccountIdAssetIdConversion;
33
use pallet_evm::AddressMapping;
34
use precompile_utils::prelude::*;
35
use sp_runtime::traits::{Bounded, Dispatchable};
36
use sp_std::vec::Vec;
37

            
38
use pallet_moonbeam_lazy_migrations::is_migrating_foreign_assets;
39
use sp_core::{MaxEncodedLen, H160, H256, U256};
40
use sp_std::{
41
	convert::{TryFrom, TryInto},
42
	marker::PhantomData,
43
};
44

            
45
mod eip2612;
46
use eip2612::Eip2612;
47

            
48
#[cfg(test)]
49
mod mock;
50
#[cfg(test)]
51
mod tests;
52

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

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

            
59
/// Alias for the Balance type for the provided Runtime and Instance.
60
pub type BalanceOf<Runtime, Instance = ()> = <Runtime as pallet_assets::Config<Instance>>::Balance;
61

            
62
/// Alias for the Asset Id type for the provided Runtime and Instance.
63
pub type AssetIdOf<Runtime, Instance = ()> = <Runtime as pallet_assets::Config<Instance>>::AssetId;
64

            
65
/// The following distribution has been decided for the precompiles
66
/// 0-1023: Ethereum Mainnet Precompiles
67
/// 1024-2047 Precompiles that are not in Ethereum Mainnet but are neither Moonbeam specific
68
/// 2048-4095 Moonbeam specific precompiles
69
/// Asset precompiles can only fall between
70
///     0xFFFFFFFF00000000000000000000000000000000 - 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
71
/// The precompile for AssetId X, where X is a u128 (i.e.16 bytes), if 0XFFFFFFFF + Bytes(AssetId)
72
/// In order to route the address to Erc20AssetsPrecompile<R>, we first check whether the AssetId
73
/// exists in pallet-assets
74
/// We cannot do this right now, so instead we check whether the total supply is zero. If so, we
75
/// do not route to the precompiles
76

            
77
/// This means that every address that starts with 0xFFFFFFFF will go through an additional db read,
78
/// but the probability for this to happen is 2^-32 for random addresses
79
pub struct Erc20AssetsPrecompileSet<Runtime, Instance: 'static = ()>(
80
	PhantomData<(Runtime, Instance)>,
81
);
82

            
83
impl<R, V> Clone for Erc20AssetsPrecompileSet<R, V> {
84
	fn clone(&self) -> Self {
85
		Self(PhantomData)
86
	}
87
}
88

            
89
impl<R, V> Default for Erc20AssetsPrecompileSet<R, V> {
90
181800
	fn default() -> Self {
91
181800
		Self(PhantomData)
92
181800
	}
93
}
94

            
95
impl<Runtime, Instance> Erc20AssetsPrecompileSet<Runtime, Instance> {
96
	pub fn new() -> Self {
97
		Self(PhantomData)
98
	}
99
}
100

            
101
675
#[precompile_utils::precompile]
102
#[precompile::precompile_set]
103
#[precompile::test_concrete_types(mock::Runtime, pallet_assets::Instance1)]
104
impl<Runtime, Instance> Erc20AssetsPrecompileSet<Runtime, Instance>
105
where
106
	Instance: eip2612::InstanceToPrefix + 'static,
107
	Runtime: pallet_assets::Config<Instance> + pallet_evm::Config + frame_system::Config,
108
	Runtime::RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
109
	Runtime::RuntimeCall: From<pallet_assets::Call<Runtime, Instance>>,
110
	<Runtime::RuntimeCall as Dispatchable>::RuntimeOrigin: From<Option<Runtime::AccountId>>,
111
	BalanceOf<Runtime, Instance>: TryFrom<U256> + Into<U256> + solidity::Codec,
112
	Runtime: AccountIdAssetIdConversion<Runtime::AccountId, AssetIdOf<Runtime, Instance>>,
113
	<<Runtime as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin: OriginTrait,
114
	AssetIdOf<Runtime, Instance>: Display,
115
	Runtime::AccountId: Into<H160>,
116
	<Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
117
{
118
	/// PrecompileSet discriminant. Allows to knows if the address maps to an asset id,
119
	/// and if this is the case which one.
120
	#[precompile::discriminant]
121
420
	fn discriminant(address: H160, gas: u64) -> DiscriminantResult<AssetIdOf<Runtime, Instance>> {
122
420
		let extra_cost = RuntimeHelper::<Runtime>::db_read_gas_cost();
123
420
		if gas < extra_cost {
124
			return DiscriminantResult::OutOfGas;
125
420
		}
126
420

            
127
420
		let account_id = Runtime::AddressMapping::into_account_id(address);
128
420
		let asset_id = match Runtime::account_to_asset_id(account_id) {
129
420
			Some((_, asset_id)) => asset_id,
130
			None => return DiscriminantResult::None(extra_cost),
131
		};
132

            
133
420
		if pallet_assets::Pallet::<Runtime, Instance>::maybe_total_supply(asset_id.clone())
134
420
			.is_some()
135
202
			&& !is_migrating_foreign_assets()
136
		{
137
202
			DiscriminantResult::Some(asset_id, extra_cost)
138
		} else {
139
218
			DiscriminantResult::None(extra_cost)
140
		}
141
420
	}
142

            
143
	#[precompile::public("totalSupply()")]
144
	#[precompile::view]
145
4
	fn total_supply(
146
4
		asset_id: AssetIdOf<Runtime, Instance>,
147
4
		handle: &mut impl PrecompileHandle,
148
4
	) -> EvmResult<U256> {
149
4
		// Storage item: Asset:
150
4
		// Blake2_128(16) + AssetId(16) + AssetDetails((4 * AccountId(20)) + (3 * Balance(16)) + 15)
151
4
		handle.record_db_read::<Runtime>(175)?;
152

            
153
4
		Ok(pallet_assets::Pallet::<Runtime, Instance>::total_issuance(asset_id).into())
154
4
	}
155

            
156
	#[precompile::public("balanceOf(address)")]
157
	#[precompile::view]
158
17
	fn balance_of(
159
17
		asset_id: AssetIdOf<Runtime, Instance>,
160
17
		handle: &mut impl PrecompileHandle,
161
17
		who: Address,
162
17
	) -> EvmResult<U256> {
163
17
		// Storage item: Account:
164
17
		// Blake2_128(16) + AssetId(16) + Blake2_128(16) + AccountId(20) + AssetAccount(19 + Extra)
165
17
		handle.record_db_read::<Runtime>(
166
17
			87 + <Runtime as pallet_assets::Config<Instance>>::Extra::max_encoded_len(),
167
17
		)?;
168

            
169
17
		let who: H160 = who.into();
170
17

            
171
17
		// Fetch info.
172
17
		let amount: U256 = {
173
17
			let who: Runtime::AccountId = Runtime::AddressMapping::into_account_id(who);
174
17
			pallet_assets::Pallet::<Runtime, Instance>::balance(asset_id, &who).into()
175
17
		};
176
17

            
177
17
		// Build output.
178
17
		Ok(amount)
179
17
	}
180

            
181
	#[precompile::public("allowance(address,address)")]
182
	#[precompile::view]
183
8
	fn allowance(
184
8
		asset_id: AssetIdOf<Runtime, Instance>,
185
8
		handle: &mut impl PrecompileHandle,
186
8
		owner: Address,
187
8
		spender: Address,
188
8
	) -> EvmResult<U256> {
189
8
		// Storage item: Approvals:
190
8
		// Blake2_128(16) + AssetId(16) + (2 * Blake2_128(16) + AccountId(20)) + Approval(32)
191
8
		handle.record_db_read::<Runtime>(136)?;
192

            
193
8
		let owner: H160 = owner.into();
194
8
		let spender: H160 = spender.into();
195
8

            
196
8
		// Fetch info.
197
8
		let amount: U256 = {
198
8
			let owner: Runtime::AccountId = Runtime::AddressMapping::into_account_id(owner);
199
8
			let spender: Runtime::AccountId = Runtime::AddressMapping::into_account_id(spender);
200
8

            
201
8
			// Fetch info.
202
8
			pallet_assets::Pallet::<Runtime, Instance>::allowance(asset_id, &owner, &spender).into()
203
8
		};
204
8

            
205
8
		// Build output.
206
8
		Ok(amount)
207
8
	}
208

            
209
	#[precompile::public("approve(address,uint256)")]
210
12
	fn approve(
211
12
		asset_id: AssetIdOf<Runtime, Instance>,
212
12
		handle: &mut impl PrecompileHandle,
213
12
		spender: Address,
214
12
		value: U256,
215
12
	) -> EvmResult<bool> {
216
12
		handle.record_log_costs_manual(3, 32)?;
217

            
218
12
		let spender: H160 = spender.into();
219
12

            
220
12
		Self::approve_inner(asset_id, handle, handle.context().caller, spender, value)?;
221

            
222
12
		log3(
223
12
			handle.context().address,
224
12
			SELECTOR_LOG_APPROVAL,
225
12
			handle.context().caller,
226
12
			spender,
227
12
			solidity::encode_event_data(value),
228
12
		)
229
12
		.record(handle)?;
230

            
231
		// Build output.
232
12
		Ok(true)
233
12
	}
234

            
235
15
	fn approve_inner(
236
15
		asset_id: AssetIdOf<Runtime, Instance>,
237
15
		handle: &mut impl PrecompileHandle,
238
15
		owner: H160,
239
15
		spender: H160,
240
15
		value: U256,
241
15
	) -> EvmResult {
242
15
		let owner = Runtime::AddressMapping::into_account_id(owner);
243
15
		let spender: Runtime::AccountId = Runtime::AddressMapping::into_account_id(spender);
244
15
		// Amount saturate if too high.
245
15
		let amount: BalanceOf<Runtime, Instance> =
246
15
			value.try_into().unwrap_or_else(|_| Bounded::max_value());
247
15

            
248
15
		// Storage item: Approvals:
249
15
		// Blake2_128(16) + AssetId(16) + (2 * Blake2_128(16) + AccountId(20)) + Approval(32)
250
15
		handle.record_db_read::<Runtime>(136)?;
251

            
252
		// If previous approval exists, we need to clean it
253
15
		if pallet_assets::Pallet::<Runtime, Instance>::allowance(asset_id.clone(), &owner, &spender)
254
15
			!= 0u32.into()
255
		{
256
3
			RuntimeHelper::<Runtime>::try_dispatch(
257
3
				handle,
258
3
				Some(owner.clone()).into(),
259
3
				pallet_assets::Call::<Runtime, Instance>::cancel_approval {
260
3
					id: asset_id.clone().into(),
261
3
					delegate: Runtime::Lookup::unlookup(spender.clone()),
262
3
				},
263
3
				0,
264
3
			)?;
265
12
		}
266
		// Dispatch call (if enough gas).
267
15
		RuntimeHelper::<Runtime>::try_dispatch(
268
15
			handle,
269
15
			Some(owner).into(),
270
15
			pallet_assets::Call::<Runtime, Instance>::approve_transfer {
271
15
				id: asset_id.into(),
272
15
				delegate: Runtime::Lookup::unlookup(spender),
273
15
				amount,
274
15
			},
275
15
			0,
276
15
		)?;
277

            
278
15
		Ok(())
279
15
	}
280

            
281
	#[precompile::public("transfer(address,uint256)")]
282
5
	fn transfer(
283
5
		asset_id: AssetIdOf<Runtime, Instance>,
284
5
		handle: &mut impl PrecompileHandle,
285
5
		to: Address,
286
5
		value: U256,
287
5
	) -> EvmResult<bool> {
288
5
		handle.record_log_costs_manual(3, 32)?;
289

            
290
5
		let to: H160 = to.into();
291
5
		let value = Self::u256_to_amount(value).in_field("value")?;
292

            
293
		// Build call with origin.
294
		{
295
4
			let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
296
4
			let to = Runtime::AddressMapping::into_account_id(to);
297
4

            
298
4
			// Dispatch call (if enough gas).
299
4
			RuntimeHelper::<Runtime>::try_dispatch(
300
4
				handle,
301
4
				Some(origin).into(),
302
4
				pallet_assets::Call::<Runtime, Instance>::transfer {
303
4
					id: asset_id.into(),
304
4
					target: Runtime::Lookup::unlookup(to),
305
4
					amount: value,
306
4
				},
307
4
				SYSTEM_ACCOUNT_SIZE,
308
4
			)?;
309
		}
310

            
311
3
		log3(
312
3
			handle.context().address,
313
3
			SELECTOR_LOG_TRANSFER,
314
3
			handle.context().caller,
315
3
			to,
316
3
			solidity::encode_event_data(value),
317
3
		)
318
3
		.record(handle)?;
319

            
320
3
		Ok(true)
321
5
	}
322

            
323
	#[precompile::public("transferFrom(address,address,uint256)")]
324
7
	fn transfer_from(
325
7
		asset_id: AssetIdOf<Runtime, Instance>,
326
7
		handle: &mut impl PrecompileHandle,
327
7
		from: Address,
328
7
		to: Address,
329
7
		value: U256,
330
7
	) -> EvmResult<bool> {
331
7
		handle.record_log_costs_manual(3, 32)?;
332

            
333
7
		let from: H160 = from.into();
334
7
		let to: H160 = to.into();
335
7
		let value = Self::u256_to_amount(value).in_field("value")?;
336

            
337
		{
338
6
			let caller: Runtime::AccountId =
339
6
				Runtime::AddressMapping::into_account_id(handle.context().caller);
340
6
			let from: Runtime::AccountId = Runtime::AddressMapping::into_account_id(from);
341
6
			let to: Runtime::AccountId = Runtime::AddressMapping::into_account_id(to);
342
6

            
343
6
			// If caller is "from", it can spend as much as it wants from its own balance.
344
6
			if caller != from {
345
				// Dispatch call (if enough gas).
346
5
				RuntimeHelper::<Runtime>::try_dispatch(
347
5
					handle,
348
5
					Some(caller).into(),
349
5
					pallet_assets::Call::<Runtime, Instance>::transfer_approved {
350
5
						id: asset_id.into(),
351
5
						owner: Runtime::Lookup::unlookup(from),
352
5
						destination: Runtime::Lookup::unlookup(to),
353
5
						amount: value,
354
5
					},
355
5
					SYSTEM_ACCOUNT_SIZE,
356
5
				)?;
357
			} else {
358
				// Dispatch call (if enough gas).
359
1
				RuntimeHelper::<Runtime>::try_dispatch(
360
1
					handle,
361
1
					Some(from).into(),
362
1
					pallet_assets::Call::<Runtime, Instance>::transfer {
363
1
						id: asset_id.into(),
364
1
						target: Runtime::Lookup::unlookup(to),
365
1
						amount: value,
366
1
					},
367
1
					SYSTEM_ACCOUNT_SIZE,
368
1
				)?;
369
			}
370
		}
371

            
372
4
		log3(
373
4
			handle.context().address,
374
4
			SELECTOR_LOG_TRANSFER,
375
4
			from,
376
4
			to,
377
4
			solidity::encode_event_data(value),
378
4
		)
379
4
		.record(handle)?;
380

            
381
		// Build output.
382
4
		Ok(true)
383
7
	}
384

            
385
	#[precompile::public("name()")]
386
	#[precompile::view]
387
2
	fn name(
388
2
		asset_id: AssetIdOf<Runtime, Instance>,
389
2
		handle: &mut impl PrecompileHandle,
390
2
	) -> EvmResult<UnboundedBytes> {
391
2
		// Storage item: Metadata:
392
2
		// Blake2_128(16) + AssetId(16) + AssetMetadata[deposit(16) + name(StringLimit)
393
2
		// + symbol(StringLimit) + decimals(1) + is_frozen(1)]
394
2
		handle.record_db_read::<Runtime>(
395
2
			50 + (2 * <Runtime as pallet_assets::Config<Instance>>::StringLimit::get()) as usize,
396
2
		)?;
397

            
398
2
		let name = pallet_assets::Pallet::<Runtime, Instance>::name(asset_id)
399
2
			.as_slice()
400
2
			.into();
401
2

            
402
2
		Ok(name)
403
2
	}
404

            
405
	#[precompile::public("symbol()")]
406
	#[precompile::view]
407
2
	fn symbol(
408
2
		asset_id: AssetIdOf<Runtime, Instance>,
409
2
		handle: &mut impl PrecompileHandle,
410
2
	) -> EvmResult<UnboundedBytes> {
411
2
		// Storage item: Metadata:
412
2
		// Blake2_128(16) + AssetId(16) + AssetMetadata[deposit(16) + name(StringLimit)
413
2
		// + symbol(StringLimit) + decimals(1) + is_frozen(1)]
414
2
		handle.record_db_read::<Runtime>(
415
2
			50 + (2 * <Runtime as pallet_assets::Config<Instance>>::StringLimit::get()) as usize,
416
2
		)?;
417

            
418
2
		let symbol = pallet_assets::Pallet::<Runtime, Instance>::symbol(asset_id)
419
2
			.as_slice()
420
2
			.into();
421
2

            
422
2
		Ok(symbol)
423
2
	}
424

            
425
	#[precompile::public("decimals()")]
426
	#[precompile::view]
427
2
	fn decimals(
428
2
		asset_id: AssetIdOf<Runtime, Instance>,
429
2
		handle: &mut impl PrecompileHandle,
430
2
	) -> EvmResult<u8> {
431
2
		// Storage item: Metadata:
432
2
		// Blake2_128(16) + AssetId(16) + AssetMetadata[deposit(16) + name(StringLimit)
433
2
		// + symbol(StringLimit) + decimals(1) + is_frozen(1)]
434
2
		handle.record_db_read::<Runtime>(
435
2
			50 + (2 * <Runtime as pallet_assets::Config<Instance>>::StringLimit::get()) as usize,
436
2
		)?;
437

            
438
2
		Ok(pallet_assets::Pallet::<Runtime, Instance>::decimals(
439
2
			asset_id,
440
2
		))
441
2
	}
442

            
443
	#[precompile::public("owner()")]
444
	#[precompile::view]
445
1
	fn owner(
446
1
		asset_id: AssetIdOf<Runtime, Instance>,
447
1
		handle: &mut impl PrecompileHandle,
448
1
	) -> EvmResult<Address> {
449
1
		// Storage item: Asset:
450
1
		// Blake2_128(16) + AssetId(16) + AssetDetails((4 * AccountId(20)) + (3 * Balance(16)) + 15)
451
1
		handle.record_db_read::<Runtime>(175)?;
452

            
453
1
		let owner: H160 = pallet_assets::Pallet::<Runtime, Instance>::owner(asset_id)
454
1
			.ok_or(revert("No owner set"))?
455
1
			.into();
456
1

            
457
1
		Ok(Address(owner))
458
1
	}
459

            
460
	#[precompile::public("issuer()")]
461
	#[precompile::view]
462
1
	fn issuer(
463
1
		asset_id: AssetIdOf<Runtime, Instance>,
464
1
		handle: &mut impl PrecompileHandle,
465
1
	) -> EvmResult<Address> {
466
1
		// Storage item: Asset:
467
1
		// Blake2_128(16) + AssetId(16) + AssetDetails((4 * AccountId(20)) + (3 * Balance(16)) + 15)
468
1
		handle.record_db_read::<Runtime>(175)?;
469

            
470
1
		let issuer: H160 = pallet_assets::Pallet::<Runtime, Instance>::issuer(asset_id)
471
1
			.ok_or(revert("No issuer set"))?
472
1
			.into();
473
1

            
474
1
		Ok(Address(issuer))
475
1
	}
476

            
477
	#[precompile::public("admin()")]
478
	#[precompile::view]
479
1
	fn admin(
480
1
		asset_id: AssetIdOf<Runtime, Instance>,
481
1
		handle: &mut impl PrecompileHandle,
482
1
	) -> EvmResult<Address> {
483
1
		// Storage item: Asset:
484
1
		// Blake2_128(16) + AssetId(16) + AssetDetails((4 * AccountId(20)) + (3 * Balance(16)) + 15)
485
1
		handle.record_db_read::<Runtime>(175)?;
486

            
487
1
		let admin: H160 = pallet_assets::Pallet::<Runtime, Instance>::admin(asset_id)
488
1
			.ok_or(revert("No admin set"))?
489
1
			.into();
490
1

            
491
1
		Ok(Address(admin))
492
1
	}
493

            
494
	#[precompile::public("freezer()")]
495
	#[precompile::view]
496
1
	fn freezer(
497
1
		asset_id: AssetIdOf<Runtime, Instance>,
498
1
		handle: &mut impl PrecompileHandle,
499
1
	) -> EvmResult<Address> {
500
1
		// Storage item: Asset:
501
1
		// Blake2_128(16) + AssetId(16) + AssetDetails((4 * AccountId(20)) + (3 * Balance(16)) + 15)
502
1
		handle.record_db_read::<Runtime>(175)?;
503

            
504
1
		let freezer: H160 = pallet_assets::Pallet::<Runtime, Instance>::freezer(asset_id)
505
1
			.ok_or(revert("No freezer set"))?
506
1
			.into();
507
1

            
508
1
		Ok(Address(freezer))
509
1
	}
510

            
511
	#[precompile::public("permit(address,address,uint256,uint256,uint8,bytes32,bytes32)")]
512
	#[allow(clippy::too_many_arguments)]
513
6
	fn eip2612_permit(
514
6
		asset_id: AssetIdOf<Runtime, Instance>,
515
6
		handle: &mut impl PrecompileHandle,
516
6
		owner: Address,
517
6
		spender: Address,
518
6
		value: U256,
519
6
		deadline: U256,
520
6
		v: u8,
521
6
		r: H256,
522
6
		s: H256,
523
6
	) -> EvmResult {
524
6
		<Eip2612<Runtime, Instance>>::permit(
525
6
			asset_id, handle, owner, spender, value, deadline, v, r, s,
526
6
		)
527
6
	}
528

            
529
	#[precompile::public("nonces(address)")]
530
	#[precompile::view]
531
10
	fn eip2612_nonces(
532
10
		asset_id: AssetIdOf<Runtime, Instance>,
533
10
		handle: &mut impl PrecompileHandle,
534
10
		owner: Address,
535
10
	) -> EvmResult<U256> {
536
10
		<Eip2612<Runtime, Instance>>::nonces(asset_id, handle, owner)
537
10
	}
538

            
539
	#[precompile::public("DOMAIN_SEPARATOR()")]
540
	#[precompile::view]
541
1
	fn eip2612_domain_separator(
542
1
		asset_id: AssetIdOf<Runtime, Instance>,
543
1
		handle: &mut impl PrecompileHandle,
544
1
	) -> EvmResult<H256> {
545
1
		<Eip2612<Runtime, Instance>>::domain_separator(asset_id, handle)
546
1
	}
547

            
548
12
	fn u256_to_amount(value: U256) -> MayRevert<BalanceOf<Runtime, Instance>> {
549
12
		value
550
12
			.try_into()
551
12
			.map_err(|_| RevertReason::value_is_too_large("balance type").into())
552
12
	}
553
}