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 sp_core::{MaxEncodedLen, H160, H256, U256};
39
use sp_std::{
40
	convert::{TryFrom, TryInto},
41
	marker::PhantomData,
42
};
43

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

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

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

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

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

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

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

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

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

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

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

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

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

            
132
459
		if pallet_assets::Pallet::<Runtime, Instance>::maybe_total_supply(asset_id.clone())
133
459
			.is_some()
134
		{
135
202
			DiscriminantResult::Some(asset_id, extra_cost)
136
		} else {
137
257
			DiscriminantResult::None(extra_cost)
138
		}
139
459
	}
140

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
276
15
		Ok(())
277
15
	}
278

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

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

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

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

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

            
318
3
		Ok(true)
319
5
	}
320

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

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

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

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

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

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

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

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

            
400
2
		Ok(name)
401
2
	}
402

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

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

            
420
2
		Ok(symbol)
421
2
	}
422

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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