1#![cfg_attr(not(feature = "std"), no_std)]
20
21extern crate alloc;
22
23use alloc::vec::Vec;
24use core::marker::PhantomData;
25use ethereum_standards::{
26 IERC20,
27 IERC20::{IERC20Calls, IERC20Events},
28};
29use frame_support::traits::fungibles::metadata::Inspect as MetadataInspect;
30use pallet_assets::{weights::WeightInfo as _, AssetsCallback, Call, Config, TransferFlags};
31use pallet_revive::precompiles::{
32 alloy::{
33 self,
34 primitives::IntoLogData,
35 sol_types::{Revert, SolCall},
36 },
37 AddressMapper, AddressMatcher, Error, Ext, Precompile, RuntimeCosts, H160, H256,
38};
39use sp_runtime::traits::{UniqueSaturatedInto, Zero};
40
41pub mod foreign_assets;
42pub mod migration;
43pub mod permit;
44pub mod weights;
45pub use weights::WeightInfo;
46
47#[cfg(feature = "runtime-benchmarks")]
48pub(crate) mod benchmarking;
49
50#[cfg(test)]
51mod foreign_assets_tests;
52#[cfg(test)]
53mod migration_tests;
54#[cfg(test)]
55mod mock;
56#[cfg(test)]
57mod permit_precompile_tests;
58#[cfg(test)]
59mod permit_tests;
60#[cfg(test)]
61mod test_helpers;
62#[cfg(test)]
63mod tests;
64
65pub use foreign_assets::{pallet, pallet::Config as ForeignAssetsConfig, ForeignAssetId};
66pub use migration::MigrateForeignAssetPrecompileMappings;
67pub use permit::pallet::Config as PermitConfig;
68
69pub trait AssetIdExtractor {
71 type AssetId;
72 fn asset_id_from_address(address: &[u8; 20]) -> Result<Self::AssetId, Error>;
74 fn asset_index(id: &Self::AssetId) -> Option<u32>;
76}
77
78pub trait AssetPrecompileConfig {
80 const MATCHER: AddressMatcher;
82
83 type AssetIdExtractor: AssetIdExtractor;
85}
86
87pub struct InlineAssetIdExtractor;
89
90impl AssetIdExtractor for InlineAssetIdExtractor {
91 type AssetId = u32;
92 fn asset_id_from_address(addr: &[u8; 20]) -> Result<Self::AssetId, Error> {
93 let bytes: [u8; 4] = addr[0..4].try_into().expect("slice is 4 bytes; qed");
94 let index = u32::from_be_bytes(bytes);
95 Ok(index)
96 }
97 fn asset_index(id: &Self::AssetId) -> Option<u32> {
98 Some(*id)
99 }
100}
101
102pub struct InlineIdConfig<const PREFIX: u16>;
104
105impl<const P: u16> AssetPrecompileConfig for InlineIdConfig<P> {
106 const MATCHER: AddressMatcher = AddressMatcher::Prefix(core::num::NonZero::new(P).unwrap());
107 type AssetIdExtractor = InlineAssetIdExtractor;
108}
109
110pub struct ForeignAssetIdExtractor<Runtime, Instance = ()> {
113 _phantom: PhantomData<(Runtime, Instance)>,
114}
115
116impl<Runtime, Instance: 'static> AssetIdExtractor for ForeignAssetIdExtractor<Runtime, Instance>
117where
118 Runtime: pallet_assets::Config<Instance>
119 + pallet::Config<ForeignAssetId = <Runtime as pallet_assets::Config<Instance>>::AssetId>
120 + pallet_revive::Config,
121{
122 type AssetId = <Runtime as pallet_assets::Config<Instance>>::AssetId;
123 fn asset_id_from_address(addr: &[u8; 20]) -> Result<Self::AssetId, Error> {
124 let bytes: [u8; 4] = addr[0..4].try_into().expect("slice is 4 bytes; qed");
125 let index = u32::from_be_bytes(bytes);
126 pallet::Pallet::<Runtime>::asset_id_of(index)
127 .ok_or(Error::Revert(Revert { reason: "Invalid foreign asset id".into() }))
128 }
129 fn asset_index(id: &Self::AssetId) -> Option<u32> {
130 pallet::Pallet::<Runtime>::asset_index_of(id)
131 }
132}
133
134pub struct ForeignIdConfig<const PREFIX: u16, Runtime, Instance = ()> {
136 _phantom: PhantomData<(Runtime, Instance)>,
137}
138
139impl<const P: u16, Runtime, Instance: 'static> AssetPrecompileConfig
140 for ForeignIdConfig<P, Runtime, Instance>
141where
142 Runtime: pallet_assets::Config<Instance>
143 + pallet::Config<ForeignAssetId = <Runtime as pallet_assets::Config<Instance>>::AssetId>
144 + pallet_revive::Config,
145{
146 const MATCHER: AddressMatcher = AddressMatcher::Prefix(core::num::NonZero::new(P).unwrap());
147 type AssetIdExtractor = ForeignAssetIdExtractor<Runtime, Instance>;
148}
149
150pub struct ERC20<Runtime, PrecompileConfig, Instance = ()> {
161 _phantom: PhantomData<(Runtime, PrecompileConfig, Instance)>,
162}
163
164impl<Runtime, PrecompileConfig, Instance: 'static> Precompile
165 for ERC20<Runtime, PrecompileConfig, Instance>
166where
167 PrecompileConfig: AssetPrecompileConfig,
168 Runtime: crate::Config<Instance> + pallet_revive::Config + permit::Config,
169 <<PrecompileConfig as AssetPrecompileConfig>::AssetIdExtractor as AssetIdExtractor>::AssetId:
170 Into<<Runtime as Config<Instance>>::AssetId>,
171 Call<Runtime, Instance>: Into<<Runtime as pallet_revive::Config>::RuntimeCall>,
172 alloy::primitives::U256: TryInto<<Runtime as Config<Instance>>::Balance>,
173 alloy::primitives::U256: TryFrom<<Runtime as Config<Instance>>::Balance>,
174{
175 type T = Runtime;
176 type Interface = IERC20::IERC20Calls;
177 const MATCHER: AddressMatcher = PrecompileConfig::MATCHER;
178 const HAS_CONTRACT_INFO: bool = false;
179
180 fn call(
181 address: &[u8; 20],
182 input: &Self::Interface,
183 env: &mut impl Ext<T = Self::T>,
184 ) -> Result<Vec<u8>, Error> {
185 frame_support::ensure!(
186 !env.is_delegate_call(),
187 pallet_revive::Error::<Self::T>::PrecompileDelegateDenied,
188 );
189
190 let asset_id = PrecompileConfig::AssetIdExtractor::asset_id_from_address(address)?.into();
191 let contract_addr = H160::from(*address);
192
193 match input {
194 IERC20Calls::transfer(_) |
196 IERC20Calls::approve(_) |
197 IERC20Calls::transferFrom(_) |
198 IERC20Calls::permit(_)
199 if env.is_read_only() =>
200 {
201 Err(Error::Error(pallet_revive::Error::<Self::T>::StateChangeDenied.into()))
202 },
203
204 IERC20Calls::transfer(call) => Self::transfer(asset_id, contract_addr, call, env),
206 IERC20Calls::totalSupply(_) => Self::total_supply(asset_id, env),
207 IERC20Calls::balanceOf(call) => Self::balance_of(asset_id, call, env),
208 IERC20Calls::allowance(call) => Self::allowance(asset_id, call, env),
209 IERC20Calls::approve(call) => Self::approve(asset_id, call, env),
210 IERC20Calls::transferFrom(call) => {
211 Self::transfer_from(asset_id, contract_addr, call, env)
212 },
213
214 IERC20Calls::permit(call) => Self::permit(asset_id, contract_addr, call, env),
216 IERC20Calls::nonces(call) => Self::nonces(contract_addr, call, env),
217 IERC20Calls::DOMAIN_SEPARATOR(_) => {
218 Self::domain_separator(asset_id, contract_addr, env)
219 },
220
221 IERC20Calls::name(_) => Self::name(asset_id, env),
223 IERC20Calls::symbol(_) => Self::symbol(asset_id, env),
224 IERC20Calls::decimals(_) => Self::decimals(asset_id, env),
225 }
226 }
227}
228
229pub struct Erc20TransferLogsCallback<Runtime, PrecompileConfig, Instance = ()> {
245 _phantom: PhantomData<(Runtime, PrecompileConfig, Instance)>,
246}
247
248impl<Runtime, PrecompileConfig, Instance>
249 AssetsCallback<
250 <Runtime as Config<Instance>>::AssetId,
251 <Runtime as frame_system::Config>::AccountId,
252 <Runtime as Config<Instance>>::Balance,
253 > for Erc20TransferLogsCallback<Runtime, PrecompileConfig, Instance>
254where
255 PrecompileConfig: AssetPrecompileConfig,
256 Runtime: Config<Instance> + pallet_revive::Config,
257 <Runtime as Config<Instance>>::AssetId:
258 Clone + Into<<PrecompileConfig::AssetIdExtractor as AssetIdExtractor>::AssetId>,
259 alloy::primitives::U256: TryFrom<<Runtime as Config<Instance>>::Balance>,
260 Instance: 'static,
261{
262 fn issued(
263 id: &<Runtime as Config<Instance>>::AssetId,
264 owner: &<Runtime as frame_system::Config>::AccountId,
265 amount: <Runtime as Config<Instance>>::Balance,
266 ) {
267 Self::emit(id, alloy::primitives::Address::ZERO, Self::evm_address(owner), amount);
268 }
269
270 fn transferred(
271 id: &<Runtime as Config<Instance>>::AssetId,
272 from: &<Runtime as frame_system::Config>::AccountId,
273 to: &<Runtime as frame_system::Config>::AccountId,
274 amount: <Runtime as Config<Instance>>::Balance,
275 ) {
276 Self::emit(id, Self::evm_address(from), Self::evm_address(to), amount);
277 }
278
279 fn burned(
280 id: &<Runtime as Config<Instance>>::AssetId,
281 owner: &<Runtime as frame_system::Config>::AccountId,
282 amount: <Runtime as Config<Instance>>::Balance,
283 ) {
284 Self::emit(id, Self::evm_address(owner), alloy::primitives::Address::ZERO, amount);
285 }
286
287 fn deposited(
288 id: &<Runtime as Config<Instance>>::AssetId,
289 who: &<Runtime as frame_system::Config>::AccountId,
290 amount: <Runtime as Config<Instance>>::Balance,
291 ) {
292 Self::emit(id, alloy::primitives::Address::ZERO, Self::evm_address(who), amount);
293 }
294
295 fn withdrawn(
296 id: &<Runtime as Config<Instance>>::AssetId,
297 who: &<Runtime as frame_system::Config>::AccountId,
298 amount: <Runtime as Config<Instance>>::Balance,
299 ) {
300 Self::emit(id, Self::evm_address(who), alloy::primitives::Address::ZERO, amount);
301 }
302}
303
304impl<Runtime, PrecompileConfig, Instance>
305 Erc20TransferLogsCallback<Runtime, PrecompileConfig, Instance>
306where
307 PrecompileConfig: AssetPrecompileConfig,
308 Runtime: Config<Instance> + pallet_revive::Config,
309 <Runtime as Config<Instance>>::AssetId:
310 Clone + Into<<PrecompileConfig::AssetIdExtractor as AssetIdExtractor>::AssetId>,
311 alloy::primitives::U256: TryFrom<<Runtime as Config<Instance>>::Balance>,
312 Instance: 'static,
313{
314 fn token_address(id: &<Runtime as Config<Instance>>::AssetId) -> Option<H160> {
317 let index = PrecompileConfig::AssetIdExtractor::asset_index(&id.clone().into())?;
318 let mut address = PrecompileConfig::MATCHER.base_address();
319 address[..4].copy_from_slice(&index.to_be_bytes());
320 Some(H160(address))
321 }
322
323 fn evm_address(
324 account: &<Runtime as frame_system::Config>::AccountId,
325 ) -> alloy::primitives::Address {
326 <Runtime as pallet_revive::Config>::AddressMapper::to_address(account).0.into()
327 }
328
329 fn emit(
330 id: &<Runtime as Config<Instance>>::AssetId,
331 from: alloy::primitives::Address,
332 to: alloy::primitives::Address,
333 amount: <Runtime as Config<Instance>>::Balance,
334 ) {
335 let Ok(value) = alloy::primitives::U256::try_from(amount) else {
336 frame_support::defensive!(
337 "asset balance exceeds U256; Transfer log dropped (unreachable for balance types up to 256 bit)",
338 (id, from, to, amount)
339 );
340 return;
341 };
342 let Some(token) = Self::token_address(id) else {
343 log::error!(
344 target: frame_support::LOG_TARGET,
345 "asset has no precompile address mapping; Transfer log dropped (a foreign asset predating the index-map migration): {:?}",
346 (id, from, to, amount)
347 );
348 return;
349 };
350 emit_transfer_log::<Runtime>(token, from, to, value);
351 }
352}
353
354fn emit_transfer_log<Runtime: pallet_revive::Config>(
359 token: H160,
360 from: alloy::primitives::Address,
361 to: alloy::primitives::Address,
362 value: alloy::primitives::U256,
363) {
364 let (topics, data) = IERC20Events::Transfer(IERC20::Transfer { from, to, value })
365 .into_log_data()
366 .split();
367 let topics = topics.into_iter().map(|t| H256(t.0)).collect::<Vec<_>>();
368 let topics = pallet_revive::ContractLogTopics::try_from(topics)
369 .expect("Transfer has 3 topics, within NUM_EVENT_TOPICS of 4; qed");
370 let data = pallet_revive::ContractLogData::try_from(data.to_vec())
371 .expect("Transfer data is a single 32 byte word, within EVENT_BYTES of 64 KiB; qed");
372 pallet_revive::Pallet::<Runtime>::emit_contract_log_outside_frame(token, topics, data);
373}
374
375const TRANSFER_LOG_TOPICS: u32 = 3;
378const TRANSFER_LOG_DATA_LEN: u32 = 32;
379
380const ERR_INVALID_CALLER: &str = "Invalid caller";
381const ERR_BALANCE_CONVERSION_FAILED: &str = "Balance conversion failed";
382
383impl<Runtime, PrecompileConfig, Instance: 'static> ERC20<Runtime, PrecompileConfig, Instance>
384where
385 PrecompileConfig: AssetPrecompileConfig,
386 Runtime: crate::Config<Instance> + pallet_revive::Config + permit::Config,
387 <<PrecompileConfig as AssetPrecompileConfig>::AssetIdExtractor as AssetIdExtractor>::AssetId:
388 Into<<Runtime as Config<Instance>>::AssetId>,
389 Call<Runtime, Instance>: Into<<Runtime as pallet_revive::Config>::RuntimeCall>,
390 alloy::primitives::U256: TryInto<<Runtime as Config<Instance>>::Balance>,
391 alloy::primitives::U256: TryFrom<<Runtime as Config<Instance>>::Balance>,
392{
393 fn caller(env: &mut impl Ext<T = Runtime>) -> Result<H160, Error> {
395 env.caller()
396 .account_id()
397 .map(<Runtime as pallet_revive::Config>::AddressMapper::to_address)
398 .map_err(|_| Error::Revert(Revert { reason: ERR_INVALID_CALLER.into() }))
399 }
400
401 fn to_balance(
403 value: alloy::primitives::U256,
404 ) -> Result<<Runtime as Config<Instance>>::Balance, Error> {
405 value
406 .try_into()
407 .map_err(|_| Error::Revert(Revert { reason: ERR_BALANCE_CONVERSION_FAILED.into() }))
408 }
409
410 fn to_u256(
413 value: <Runtime as Config<Instance>>::Balance,
414 ) -> Result<alloy::primitives::U256, Error> {
415 alloy::primitives::U256::try_from(value)
416 .map_err(|_| Error::Revert(Revert { reason: ERR_BALANCE_CONVERSION_FAILED.into() }))
417 }
418
419 fn deposit_event(env: &mut impl Ext<T = Runtime>, event: IERC20Events) -> Result<(), Error> {
421 let (topics, data) = event.into_log_data().split();
422 let topics = topics.into_iter().map(|v| H256(v.0)).collect::<Vec<_>>();
423 env.frame_meter_mut().charge_weight_token(RuntimeCosts::DepositEvent {
424 num_topic: topics.len() as u32,
425 len: data.len() as u32,
426 })?;
427 env.deposit_event(topics, data.to_vec());
428 Ok(())
429 }
430
431 fn transfer(
436 asset_id: <Runtime as Config<Instance>>::AssetId,
437 contract_addr: H160,
438 call: &IERC20::transferCall,
439 env: &mut impl Ext<T = Runtime>,
440 ) -> Result<Vec<u8>, Error> {
441 env.charge(<Runtime as Config<Instance>>::WeightInfo::transfer())?;
446 env.frame_meter_mut().charge_weight_token(RuntimeCosts::DepositEvent {
447 num_topic: TRANSFER_LOG_TOPICS,
448 len: TRANSFER_LOG_DATA_LEN,
449 })?;
450
451 let from = Self::caller(env)?;
452 let from_account = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&from);
453 let dest = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(
454 &call.to.into_array().into(),
455 );
456 let amount = Self::to_balance(call.value)?;
457
458 let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false };
459 pallet_assets::Pallet::<Runtime, Instance>::do_transfer(
460 asset_id,
461 &from_account,
462 &dest,
463 amount,
464 None,
465 f,
466 )?;
467
468 if call.value.is_zero() {
473 emit_transfer_log::<Runtime>(contract_addr, from.0.into(), call.to, call.value);
474 }
475
476 Ok(IERC20::transferCall::abi_encode_returns(&true))
477 }
478
479 fn total_supply(
481 asset_id: <Runtime as Config<Instance>>::AssetId,
482 env: &mut impl Ext<T = Runtime>,
483 ) -> Result<Vec<u8>, Error> {
484 use frame_support::traits::fungibles::Inspect;
485 env.charge(<Runtime as Config<Instance>>::WeightInfo::total_issuance())?;
486
487 let value =
488 Self::to_u256(pallet_assets::Pallet::<Runtime, Instance>::total_issuance(asset_id))?;
489 Ok(IERC20::totalSupplyCall::abi_encode_returns(&value))
490 }
491
492 fn balance_of(
498 asset_id: <Runtime as Config<Instance>>::AssetId,
499 call: &IERC20::balanceOfCall,
500 env: &mut impl Ext<T = Runtime>,
501 ) -> Result<Vec<u8>, Error> {
502 use frame_support::traits::fungibles::Inspect;
503
504 env.charge(<Runtime as Config<Instance>>::WeightInfo::balance())?;
505 let account = call.account.into_array().into();
506 let account = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&account);
507 let value = Self::to_u256(pallet_assets::Pallet::<Runtime, Instance>::total_balance(
508 asset_id, &account,
509 ))?;
510 Ok(IERC20::balanceOfCall::abi_encode_returns(&value))
511 }
512
513 fn allowance(
515 asset_id: <Runtime as Config<Instance>>::AssetId,
516 call: &IERC20::allowanceCall,
517 env: &mut impl Ext<T = Runtime>,
518 ) -> Result<Vec<u8>, Error> {
519 env.charge(<Runtime as Config<Instance>>::WeightInfo::allowance())?;
520 use frame_support::traits::fungibles::approvals::Inspect;
521 let owner = call.owner.into_array().into();
522 let owner = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&owner);
523
524 let spender = call.spender.into_array().into();
525 let spender = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&spender);
526 let value = Self::to_u256(pallet_assets::Pallet::<Runtime, Instance>::allowance(
527 asset_id, &owner, &spender,
528 ))?;
529
530 Ok(IERC20::allowanceCall::abi_encode_returns(&value))
531 }
532
533 fn approve(
543 asset_id: <Runtime as Config<Instance>>::AssetId,
544 call: &IERC20::approveCall,
545 env: &mut impl Ext<T = Runtime>,
546 ) -> Result<Vec<u8>, Error> {
547 use frame_support::traits::fungibles::approvals::Inspect as ApprovalsInspect;
548
549 let worst_case = <Runtime as Config<Instance>>::WeightInfo::allowance()
551 .saturating_add(<Runtime as Config<Instance>>::WeightInfo::cancel_approval())
552 .saturating_add(<Runtime as Config<Instance>>::WeightInfo::approve_transfer());
553 let charged = env.charge(worst_case)?;
554
555 let owner = Self::caller(env)?;
556 let owner_account =
557 <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&owner);
558 let spender: H160 = call.spender.into_array().into();
559 let spender_account = env.to_account_id(&spender);
560 let new_amount: <Runtime as Config<Instance>>::Balance = call.value.unique_saturated_into();
563
564 let current = pallet_assets::Pallet::<Runtime, Instance>::allowance(
565 asset_id.clone(),
566 &owner_account,
567 &spender_account,
568 );
569
570 let actual_weight;
571 if new_amount.is_zero() {
572 if !current.is_zero() {
573 pallet_assets::Pallet::<Runtime, Instance>::do_cancel_approval(
576 &asset_id,
577 &owner_account,
578 &spender_account,
579 )?;
580 actual_weight = <Runtime as Config<Instance>>::WeightInfo::allowance()
581 .saturating_add(<Runtime as Config<Instance>>::WeightInfo::cancel_approval());
582 } else {
583 actual_weight = <Runtime as Config<Instance>>::WeightInfo::allowance();
585 }
586 } else {
587 if !current.is_zero() {
593 pallet_assets::Pallet::<Runtime, Instance>::do_cancel_approval(
594 &asset_id,
595 &owner_account,
596 &spender_account,
597 )?;
598 actual_weight = worst_case;
599 } else {
600 actual_weight = <Runtime as Config<Instance>>::WeightInfo::allowance()
601 .saturating_add(<Runtime as Config<Instance>>::WeightInfo::approve_transfer());
602 }
603 pallet_assets::Pallet::<Runtime, Instance>::do_approve_transfer(
604 asset_id,
605 &owner_account,
606 &spender_account,
607 new_amount,
608 )?;
609 }
610 env.adjust_gas(charged, actual_weight);
611
612 Self::deposit_event(
613 env,
614 IERC20Events::Approval(IERC20::Approval {
615 owner: owner.0.into(),
616 spender: call.spender,
617 value: call.value,
618 }),
619 )?;
620
621 Ok(IERC20::approveCall::abi_encode_returns(&true))
622 }
623
624 fn transfer_from(
629 asset_id: <Runtime as Config<Instance>>::AssetId,
630 contract_addr: H160,
631 call: &IERC20::transferFromCall,
632 env: &mut impl Ext<T = Runtime>,
633 ) -> Result<Vec<u8>, Error> {
634 env.charge(<Runtime as Config<Instance>>::WeightInfo::transfer_approved())?;
637 env.frame_meter_mut().charge_weight_token(RuntimeCosts::DepositEvent {
638 num_topic: TRANSFER_LOG_TOPICS,
639 len: TRANSFER_LOG_DATA_LEN,
640 })?;
641 let spender = Self::caller(env)?;
642 let spender = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&spender);
643
644 let from = call.from.into_array().into();
645 let from = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&from);
646
647 let to = call.to.into_array().into();
648 let to = <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&to);
649
650 let approval_amount = Self::to_balance(call.value)?;
651 pallet_assets::Pallet::<Runtime, Instance>::do_transfer_approved(
652 asset_id,
653 &from,
654 &spender,
655 &to,
656 approval_amount,
657 )?;
658
659 if call.value.is_zero() {
663 emit_transfer_log::<Runtime>(contract_addr, call.from, call.to, call.value);
664 }
665
666 Ok(IERC20::transferFromCall::abi_encode_returns(&true))
667 }
668
669 pub(crate) fn permit(
677 asset_id: <Runtime as Config<Instance>>::AssetId,
678 verifying_contract: H160,
679 call: &IERC20::permitCall,
680 env: &mut impl Ext<T = Runtime>,
681 ) -> Result<Vec<u8>, Error> {
682 let use_permit_weight = <Runtime as permit::Config>::WeightInfo::use_permit();
686 let worst_case = use_permit_weight
687 .saturating_add(<Runtime as Config<Instance>>::WeightInfo::allowance())
688 .saturating_add(<Runtime as Config<Instance>>::WeightInfo::cancel_approval())
689 .saturating_add(<Runtime as Config<Instance>>::WeightInfo::approve_transfer());
690 let charged = env.charge(worst_case)?;
691
692 let owner_h160: H160 = call.owner.into_array().into();
693 let spender_h160: H160 = call.spender.into_array().into();
694
695 let value_bytes: [u8; 32] = call.value.to_be_bytes();
697 let deadline_bytes: [u8; 32] = call.deadline.to_be_bytes();
698 let r_bytes: [u8; 32] = call.r.0;
699 let s_bytes: [u8; 32] = call.s.0;
700
701 let transaction_outcome = frame_support::storage::with_transaction(|| {
702 let result = (|| {
703 permit::Pallet::<Runtime>::use_permit(
705 &verifying_contract,
706 &pallet_assets::Pallet::<Runtime, Instance>::name(asset_id.clone()),
707 &owner_h160,
708 &spender_h160,
709 &value_bytes,
710 &deadline_bytes,
711 call.v,
712 &r_bytes,
713 &s_bytes,
714 )
715 .map_err(|e| {
716 let msg = match e {
717 permit::pallet::Error::PermitExpired => "Permit expired",
718 permit::pallet::Error::InvalidSignature => "Invalid signature",
719 permit::pallet::Error::SignerMismatch => "Signer does not match owner",
720 permit::pallet::Error::SignatureSValueTooHigh => {
721 "Signature s value too high (malleability)"
722 },
723 permit::pallet::Error::InvalidVValue => "Invalid signature v value",
724 permit::pallet::Error::NonceOverflow => "Nonce overflow",
725 permit::pallet::Error::InvalidOwner => "Invalid owner address",
726 permit::pallet::Error::InvalidSpender => "Invalid spender address",
727 };
728 Error::Revert(Revert { reason: msg.into() })
729 })?;
730
731 use frame_support::traits::fungibles::approvals::Inspect as ApprovalsInspect;
734 let owner_account =
735 <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&owner_h160);
736 let spender_account =
737 <Runtime as pallet_revive::Config>::AddressMapper::to_account_id(&spender_h160);
738
739 let new_amount: <Runtime as Config<Instance>>::Balance =
741 call.value.unique_saturated_into();
742 let current = pallet_assets::Pallet::<Runtime, Instance>::allowance(
743 asset_id.clone(),
744 &owner_account,
745 &spender_account,
746 );
747
748 let actual_weight;
749 if new_amount.is_zero() {
750 if !current.is_zero() {
751 pallet_assets::Pallet::<Runtime, Instance>::do_cancel_approval(
754 &asset_id,
755 &owner_account,
756 &spender_account,
757 )?;
758 actual_weight = use_permit_weight
759 .saturating_add(<Runtime as Config<Instance>>::WeightInfo::allowance())
760 .saturating_add(
761 <Runtime as Config<Instance>>::WeightInfo::cancel_approval(),
762 );
763 } else {
764 actual_weight = use_permit_weight
766 .saturating_add(<Runtime as Config<Instance>>::WeightInfo::allowance());
767 }
768 } else {
769 if !current.is_zero() {
770 pallet_assets::Pallet::<Runtime, Instance>::do_cancel_approval(
772 &asset_id,
773 &owner_account,
774 &spender_account,
775 )?;
776 actual_weight = worst_case;
777 } else {
778 actual_weight = use_permit_weight
780 .saturating_add(<Runtime as Config<Instance>>::WeightInfo::allowance())
781 .saturating_add(
782 <Runtime as Config<Instance>>::WeightInfo::approve_transfer(),
783 );
784 }
785 pallet_assets::Pallet::<Runtime, Instance>::do_approve_transfer(
786 asset_id,
787 &owner_account,
788 &spender_account,
789 new_amount,
790 )?;
791 }
792
793 Self::deposit_event(
795 env,
796 IERC20Events::Approval(IERC20::Approval {
797 owner: call.owner,
798 spender: call.spender,
799 value: call.value,
800 }),
801 )?;
802 Ok::<_, Error>(actual_weight)
803 })();
804 match result {
805 Ok(actual_weight) => {
806 frame_support::storage::TransactionOutcome::Commit(Ok(actual_weight))
807 },
808 Err(e) => {
809 log::trace!(target: frame_support::LOG_TARGET, "Call to permit failed: {e:?}");
810 frame_support::storage::TransactionOutcome::Rollback(Err(e))
811 },
812 }
813 });
814
815 match transaction_outcome {
817 Ok(actual_weight) => {
818 env.adjust_gas(charged, actual_weight);
819 Ok(Vec::new())
820 },
821 Err(e) => Err(e),
822 }
823 }
824
825 fn nonces(
827 verifying_contract: H160,
828 call: &IERC20::noncesCall,
829 env: &mut impl Ext<T = Runtime>,
830 ) -> Result<Vec<u8>, Error> {
831 env.charge(<Runtime as permit::Config>::WeightInfo::nonces())?;
832
833 let owner_h160: H160 = call.owner.into_array().into();
834 let nonce = permit::Pallet::<Runtime>::nonce(&verifying_contract, &owner_h160);
835
836 let nonce_bytes = nonce.to_big_endian();
838 let nonce_alloy = alloy::primitives::U256::from_be_bytes(nonce_bytes);
839
840 Ok(IERC20::noncesCall::abi_encode_returns(&nonce_alloy))
841 }
842
843 fn domain_separator(
845 asset_id: <Runtime as Config<Instance>>::AssetId,
846 verifying_contract: H160,
847 env: &mut impl Ext<T = Runtime>,
848 ) -> Result<Vec<u8>, Error> {
849 env.charge(<Runtime as permit::Config>::WeightInfo::domain_separator())?;
850
851 let token_name = pallet_assets::Pallet::<Runtime, Instance>::name(asset_id);
853
854 let separator =
855 permit::Pallet::<Runtime>::compute_domain_separator(&verifying_contract, &token_name);
856 let separator_alloy: alloy::primitives::FixedBytes<32> = separator.0.into();
857
858 Ok(IERC20::DOMAIN_SEPARATORCall::abi_encode_returns(&separator_alloy))
859 }
860
861 fn name(
863 asset_id: <Runtime as Config<Instance>>::AssetId,
864 env: &mut impl Ext<T = Runtime>,
865 ) -> Result<Vec<u8>, Error> {
866 env.charge(<Runtime as Config<Instance>>::WeightInfo::get_metadata())?;
867
868 let metadata = pallet_assets::Pallet::<Runtime, Instance>::get_metadata(asset_id)
869 .ok_or(Error::Revert(Revert { reason: "Metadata not found".into() }))?;
870
871 let name = alloc::string::String::from_utf8(metadata.name.to_vec())
872 .map_err(|_| Error::Revert(Revert { reason: "Invalid UTF-8 in name".into() }))?;
873
874 Ok(IERC20::nameCall::abi_encode_returns(&name))
875 }
876
877 fn symbol(
879 asset_id: <Runtime as Config<Instance>>::AssetId,
880 env: &mut impl Ext<T = Runtime>,
881 ) -> Result<Vec<u8>, Error> {
882 env.charge(<Runtime as Config<Instance>>::WeightInfo::get_metadata())?;
883
884 let metadata = pallet_assets::Pallet::<Runtime, Instance>::get_metadata(asset_id)
885 .ok_or(Error::Revert(Revert { reason: "Metadata not found".into() }))?;
886
887 let symbol = alloc::string::String::from_utf8(metadata.symbol.to_vec())
888 .map_err(|_| Error::Revert(Revert { reason: "Invalid UTF-8 in symbol".into() }))?;
889
890 Ok(IERC20::symbolCall::abi_encode_returns(&symbol))
891 }
892
893 fn decimals(
895 asset_id: <Runtime as Config<Instance>>::AssetId,
896 env: &mut impl Ext<T = Runtime>,
897 ) -> Result<Vec<u8>, Error> {
898 env.charge(<Runtime as Config<Instance>>::WeightInfo::get_metadata())?;
899
900 let metadata = pallet_assets::Pallet::<Runtime, Instance>::get_metadata(asset_id)
901 .ok_or(Error::Revert(Revert { reason: "Metadata not found".into() }))?;
902
903 Ok(IERC20::decimalsCall::abi_encode_returns(&metadata.decimals))
904 }
905}