1#![cfg_attr(not(feature = "std"), no_std)]
38
39use fp_evm::{ExitError, PrecompileHandle};
40use frame_support::{
41 dispatch::{GetDispatchInfo, PostDispatchInfo},
42 traits::{
43 fungibles::{
44 approvals::Inspect as ApprovalInspect, metadata::Inspect as MetadataInspect, Inspect,
45 },
46 OriginTrait,
47 },
48 DefaultNoBound,
49};
50use pallet_evm::AddressMapping;
51use precompile_utils::prelude::*;
52use sp_runtime::traits::{Bounded, Dispatchable, StaticLookup};
53
54use sp_core::{Get, MaxEncodedLen, H160, U256};
55use sp_std::{
56 convert::{TryFrom, TryInto},
57 marker::PhantomData,
58};
59
60#[cfg(test)]
61mod mock;
62#[cfg(test)]
63mod tests;
64
65pub const SELECTOR_LOG_TRANSFER: [u8; 32] = keccak256!("Transfer(address,address,uint256)");
67
68pub const SELECTOR_LOG_APPROVAL: [u8; 32] = keccak256!("Approval(address,address,uint256)");
70
71pub type BalanceOf<Runtime, Instance = ()> = <Runtime as pallet_assets::Config<Instance>>::Balance;
73
74pub type AssetIdOf<Runtime, Instance = ()> = <Runtime as pallet_assets::Config<Instance>>::AssetId;
76
77pub trait AddressToAssetId<AssetId> {
80 fn address_to_asset_id(address: H160) -> Option<AssetId>;
82
83 fn asset_id_to_address(asset_id: AssetId) -> H160;
85}
86
87#[derive(Clone, DefaultNoBound)]
102pub struct Erc20AssetsPrecompileSet<Runtime, Instance: 'static = ()>(
103 PhantomData<(Runtime, Instance)>,
104);
105
106impl<Runtime, Instance> Erc20AssetsPrecompileSet<Runtime, Instance> {
107 pub fn new() -> Self {
108 Self(PhantomData)
109 }
110}
111
112#[precompile_utils::precompile]
113#[precompile::precompile_set]
114#[precompile::test_concrete_types(mock::Runtime, ())]
115impl<Runtime, Instance> Erc20AssetsPrecompileSet<Runtime, Instance>
116where
117 Instance: 'static,
118 Runtime: pallet_assets::Config<Instance> + pallet_evm::Config + frame_system::Config,
119 Runtime::RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
120 Runtime::RuntimeCall: From<pallet_assets::Call<Runtime, Instance>>,
121 <Runtime::RuntimeCall as Dispatchable>::RuntimeOrigin: From<Option<Runtime::AccountId>>,
122 BalanceOf<Runtime, Instance>: TryFrom<U256> + Into<U256> + solidity::Codec,
123 Runtime: AddressToAssetId<AssetIdOf<Runtime, Instance>>,
124 <<Runtime as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin: OriginTrait,
125 AssetIdOf<Runtime, Instance>: Copy,
126 <Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
127{
128 #[precompile::discriminant]
131 fn discriminant(address: H160, gas: u64) -> DiscriminantResult<AssetIdOf<Runtime, Instance>> {
132 let extra_cost = RuntimeHelper::<Runtime>::db_read_gas_cost();
133 if gas < extra_cost {
134 return DiscriminantResult::OutOfGas;
135 }
136
137 let asset_id = match Runtime::address_to_asset_id(address) {
138 Some(asset_id) => asset_id,
139 None => return DiscriminantResult::None(extra_cost),
140 };
141
142 if pallet_assets::Pallet::<Runtime, Instance>::maybe_total_supply(asset_id).is_some() {
143 DiscriminantResult::Some(asset_id, extra_cost)
144 } else {
145 DiscriminantResult::None(extra_cost)
146 }
147 }
148
149 #[precompile::public("totalSupply()")]
150 #[precompile::view]
151 fn total_supply(
152 asset_id: AssetIdOf<Runtime, Instance>,
153 handle: &mut impl PrecompileHandle,
154 ) -> EvmResult<U256> {
155 handle.record_db_read::<Runtime>(223)?;
159
160 Ok(pallet_assets::Pallet::<Runtime, Instance>::total_issuance(asset_id).into())
161 }
162
163 #[precompile::public("balanceOf(address)")]
164 #[precompile::view]
165 fn balance_of(
166 asset_id: AssetIdOf<Runtime, Instance>,
167 handle: &mut impl PrecompileHandle,
168 who: Address,
169 ) -> EvmResult<U256> {
170 handle.record_db_read::<Runtime>(
174 99 + <Runtime as pallet_assets::Config<Instance>>::Extra::max_encoded_len(),
175 )?;
176
177 let who: H160 = who.into();
178
179 let amount: U256 = {
181 let who: Runtime::AccountId = Runtime::AddressMapping::into_account_id(who);
182 pallet_assets::Pallet::<Runtime, Instance>::balance(asset_id, &who).into()
183 };
184
185 Ok(amount)
187 }
188
189 #[precompile::public("allowance(address,address)")]
190 #[precompile::view]
191 fn allowance(
192 asset_id: AssetIdOf<Runtime, Instance>,
193 handle: &mut impl PrecompileHandle,
194 owner: Address,
195 spender: Address,
196 ) -> EvmResult<U256> {
197 handle.record_db_read::<Runtime>(148)?;
201
202 let owner: H160 = owner.into();
203 let spender: H160 = spender.into();
204
205 let amount: U256 = {
207 let owner: Runtime::AccountId = Runtime::AddressMapping::into_account_id(owner);
208 let spender: Runtime::AccountId = Runtime::AddressMapping::into_account_id(spender);
209
210 pallet_assets::Pallet::<Runtime, Instance>::allowance(asset_id, &owner, &spender).into()
212 };
213
214 Ok(amount)
216 }
217
218 #[precompile::public("approve(address,uint256)")]
219 fn approve(
220 asset_id: AssetIdOf<Runtime, Instance>,
221 handle: &mut impl PrecompileHandle,
222 spender: Address,
223 value: U256,
224 ) -> EvmResult<bool> {
225 handle.record_log_costs_manual(3, 32)?;
226
227 let spender: H160 = spender.into();
228
229 Self::approve_inner(asset_id, handle, handle.context().caller, spender, value)?;
230
231 log3(
232 handle.context().address,
233 SELECTOR_LOG_APPROVAL,
234 handle.context().caller,
235 spender,
236 solidity::encode_event_data(value),
237 )
238 .record(handle)?;
239
240 Ok(true)
242 }
243
244 fn approve_inner(
245 asset_id: AssetIdOf<Runtime, Instance>,
246 handle: &mut impl PrecompileHandle,
247 owner: H160,
248 spender: H160,
249 value: U256,
250 ) -> EvmResult {
251 let owner = Runtime::AddressMapping::into_account_id(owner);
252 let spender: Runtime::AccountId = Runtime::AddressMapping::into_account_id(spender);
253 let amount: BalanceOf<Runtime, Instance> =
255 value.try_into().unwrap_or_else(|_| Bounded::max_value());
256
257 handle.record_db_read::<Runtime>(148)?;
260
261 if pallet_assets::Pallet::<Runtime, Instance>::allowance(asset_id, &owner, &spender)
263 != 0u32.into()
264 {
265 RuntimeHelper::<Runtime>::try_dispatch(
266 handle,
267 Some(owner.clone()).into(),
268 pallet_assets::Call::<Runtime, Instance>::cancel_approval {
269 id: asset_id.into(),
270 delegate: Runtime::Lookup::unlookup(spender.clone()),
271 },
272 0,
273 )?;
274 }
275 RuntimeHelper::<Runtime>::try_dispatch(
277 handle,
278 Some(owner).into(),
279 pallet_assets::Call::<Runtime, Instance>::approve_transfer {
280 id: asset_id.into(),
281 delegate: Runtime::Lookup::unlookup(spender),
282 amount,
283 },
284 0,
285 )?;
286
287 Ok(())
288 }
289
290 #[precompile::public("transfer(address,uint256)")]
291 fn transfer(
292 asset_id: AssetIdOf<Runtime, Instance>,
293 handle: &mut impl PrecompileHandle,
294 to: Address,
295 value: U256,
296 ) -> EvmResult<bool> {
297 handle.record_log_costs_manual(3, 32)?;
298
299 let to: H160 = to.into();
300 let value = Self::u256_to_amount(value).in_field("value")?;
301
302 {
304 let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
305 let to = Runtime::AddressMapping::into_account_id(to);
306
307 RuntimeHelper::<Runtime>::try_dispatch(
309 handle,
310 Some(origin).into(),
311 pallet_assets::Call::<Runtime, Instance>::transfer {
312 id: asset_id.into(),
313 target: Runtime::Lookup::unlookup(to),
314 amount: value,
315 },
316 0,
317 )?;
318 }
319
320 log3(
321 handle.context().address,
322 SELECTOR_LOG_TRANSFER,
323 handle.context().caller,
324 to,
325 solidity::encode_event_data(value),
326 )
327 .record(handle)?;
328
329 Ok(true)
330 }
331
332 #[precompile::public("transferFrom(address,address,uint256)")]
333 fn transfer_from(
334 asset_id: AssetIdOf<Runtime, Instance>,
335 handle: &mut impl PrecompileHandle,
336 from: Address,
337 to: Address,
338 value: U256,
339 ) -> EvmResult<bool> {
340 handle.record_log_costs_manual(3, 32)?;
341
342 let from: H160 = from.into();
343 let to: H160 = to.into();
344 let value = Self::u256_to_amount(value).in_field("value")?;
345
346 {
347 let caller: Runtime::AccountId =
348 Runtime::AddressMapping::into_account_id(handle.context().caller);
349 let from: Runtime::AccountId = Runtime::AddressMapping::into_account_id(from);
350 let to: Runtime::AccountId = Runtime::AddressMapping::into_account_id(to);
351
352 if caller != from {
354 RuntimeHelper::<Runtime>::try_dispatch(
356 handle,
357 Some(caller).into(),
358 pallet_assets::Call::<Runtime, Instance>::transfer_approved {
359 id: asset_id.into(),
360 owner: Runtime::Lookup::unlookup(from),
361 destination: Runtime::Lookup::unlookup(to),
362 amount: value,
363 },
364 0,
365 )?;
366 } else {
367 RuntimeHelper::<Runtime>::try_dispatch(
369 handle,
370 Some(from).into(),
371 pallet_assets::Call::<Runtime, Instance>::transfer {
372 id: asset_id.into(),
373 target: Runtime::Lookup::unlookup(to),
374 amount: value,
375 },
376 0,
377 )?;
378 }
379 }
380
381 log3(
382 handle.context().address,
383 SELECTOR_LOG_TRANSFER,
384 from,
385 to,
386 solidity::encode_event_data(value),
387 )
388 .record(handle)?;
389
390 Ok(true)
392 }
393
394 #[precompile::public("name()")]
395 #[precompile::view]
396 fn name(
397 asset_id: AssetIdOf<Runtime, Instance>,
398 handle: &mut impl PrecompileHandle,
399 ) -> EvmResult<UnboundedBytes> {
400 handle.record_db_read::<Runtime>(
404 50 + (2 * <Runtime as pallet_assets::Config<Instance>>::StringLimit::get()) as usize,
405 )?;
406
407 let name = pallet_assets::Pallet::<Runtime, Instance>::name(asset_id)
408 .as_slice()
409 .into();
410
411 Ok(name)
412 }
413
414 #[precompile::public("symbol()")]
415 #[precompile::view]
416 fn symbol(
417 asset_id: AssetIdOf<Runtime, Instance>,
418 handle: &mut impl PrecompileHandle,
419 ) -> EvmResult<UnboundedBytes> {
420 handle.record_db_read::<Runtime>(
424 50 + (2 * <Runtime as pallet_assets::Config<Instance>>::StringLimit::get()) as usize,
425 )?;
426
427 let symbol = pallet_assets::Pallet::<Runtime, Instance>::symbol(asset_id)
428 .as_slice()
429 .into();
430
431 Ok(symbol)
432 }
433
434 #[precompile::public("decimals()")]
435 #[precompile::view]
436 fn decimals(
437 asset_id: AssetIdOf<Runtime, Instance>,
438 handle: &mut impl PrecompileHandle,
439 ) -> EvmResult<u8> {
440 handle.record_db_read::<Runtime>(
444 50 + (2 * <Runtime as pallet_assets::Config<Instance>>::StringLimit::get()) as usize,
445 )?;
446
447 Ok(pallet_assets::Pallet::<Runtime, Instance>::decimals(
448 asset_id,
449 ))
450 }
451
452 #[precompile::public("minimumBalance()")]
453 #[precompile::view]
454 fn minimum_balance(
455 asset_id: AssetIdOf<Runtime, Instance>,
456 handle: &mut impl PrecompileHandle,
457 ) -> EvmResult<U256> {
458 handle.record_db_read::<Runtime>(207)?;
462
463 Ok(pallet_assets::Pallet::<Runtime, Instance>::minimum_balance(asset_id).into())
464 }
465
466 #[precompile::public("mint(address,uint256)")]
467 fn mint(
468 asset_id: AssetIdOf<Runtime, Instance>,
469 handle: &mut impl PrecompileHandle,
470 to: Address,
471 value: U256,
472 ) -> EvmResult<bool> {
473 handle.record_log_costs_manual(3, 32)?;
474
475 let to: H160 = to.into();
476 let value = Self::u256_to_amount(value).in_field("value")?;
477
478 {
480 let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
481 let to = Runtime::AddressMapping::into_account_id(to);
482
483 RuntimeHelper::<Runtime>::try_dispatch(
485 handle,
486 Some(origin).into(),
487 pallet_assets::Call::<Runtime, Instance>::mint {
488 id: asset_id.into(),
489 beneficiary: Runtime::Lookup::unlookup(to),
490 amount: value,
491 },
492 0,
493 )?;
494 }
495
496 log3(
497 handle.context().address,
498 SELECTOR_LOG_TRANSFER,
499 H160::default(),
500 to,
501 solidity::encode_event_data(value),
502 )
503 .record(handle)?;
504
505 Ok(true)
506 }
507
508 #[precompile::public("burn(address,uint256)")]
509 fn burn(
510 asset_id: AssetIdOf<Runtime, Instance>,
511 handle: &mut impl PrecompileHandle,
512 from: Address,
513 value: U256,
514 ) -> EvmResult<bool> {
515 handle.record_log_costs_manual(3, 32)?;
516
517 let from: H160 = from.into();
518 let value = Self::u256_to_amount(value).in_field("value")?;
519
520 {
522 let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
523 let from = Runtime::AddressMapping::into_account_id(from);
524
525 RuntimeHelper::<Runtime>::try_dispatch(
527 handle,
528 Some(origin).into(),
529 pallet_assets::Call::<Runtime, Instance>::burn {
530 id: asset_id.into(),
531 who: Runtime::Lookup::unlookup(from),
532 amount: value,
533 },
534 0,
535 )?;
536 }
537
538 log3(
539 handle.context().address,
540 SELECTOR_LOG_TRANSFER,
541 from,
542 H160::default(),
543 solidity::encode_event_data(value),
544 )
545 .record(handle)?;
546
547 Ok(true)
548 }
549
550 fn u256_to_amount(value: U256) -> MayRevert<BalanceOf<Runtime, Instance>> {
551 value
552 .try_into()
553 .map_err(|_| RevertReason::value_is_too_large("balance type").into())
554 }
555}