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
199659
	fn default() -> Self {
91
199659
		Self(PhantomData)
92
199659
	}
93
}
94

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

            
101
589
#[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
296
	fn discriminant(address: H160, gas: u64) -> DiscriminantResult<AssetIdOf<Runtime, Instance>> {
122
296
		let extra_cost = RuntimeHelper::<Runtime>::db_read_gas_cost();
123
296
		if gas < extra_cost {
124
			return DiscriminantResult::OutOfGas;
125
296
		}
126
296

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
277
15
		Ok(())
278
15
	}
279

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

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

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

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

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

            
319
3
		Ok(true)
320
5
	}
321

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

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

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

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

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

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

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

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

            
401
2
		Ok(name)
402
2
	}
403

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

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

            
421
2
		Ok(symbol)
422
2
	}
423

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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