1use core::marker::PhantomData;
20use ethereum_standards::IERC20;
21use frame_support::traits::{fungible::Inspect, OriginTrait};
22use frame_system::pallet_prelude::OriginFor;
23use pallet_revive::{
24 precompiles::alloy::{
25 primitives::{Address, U256 as EU256},
26 sol_types::SolCall,
27 },
28 AddressMapper, ContractResult, ExecConfig, MomentOf, TransactionLimits,
29};
30use sp_core::{Get, H160, H256, U256};
31use sp_runtime::Weight;
32use xcm::latest::prelude::*;
33use xcm_executor::{
34 traits::{ConvertLocation, Error as MatchError, MatchesFungibles, TransactAsset},
35 AssetsInHolding,
36};
37
38type BalanceOf<T> = <<T as pallet_revive::Config>::Currency as Inspect<
39 <T as frame_system::Config>::AccountId,
40>>::Balance;
41
42pub struct ERC20Transactor<
44 T,
45 Matcher,
46 AccountIdConverter,
47 WeightLimit,
48 StorageDepositLimit,
49 AccountId,
50 TransfersCheckingAccount,
51>(
52 PhantomData<(
53 T,
54 Matcher,
55 AccountIdConverter,
56 WeightLimit,
57 StorageDepositLimit,
58 AccountId,
59 TransfersCheckingAccount,
60 )>,
61);
62
63impl<
64 AccountId: Eq + Clone,
65 T: pallet_revive::Config<AccountId = AccountId>,
66 AccountIdConverter: ConvertLocation<AccountId>,
67 Matcher: MatchesFungibles<H160, u128>,
68 WeightLimit: Get<Weight>,
69 StorageDepositLimit: Get<BalanceOf<T>>,
70 TransfersCheckingAccount: Get<AccountId>,
71 > TransactAsset
72 for ERC20Transactor<
73 T,
74 Matcher,
75 AccountIdConverter,
76 WeightLimit,
77 StorageDepositLimit,
78 AccountId,
79 TransfersCheckingAccount,
80 >
81where
82 BalanceOf<T>: Into<U256> + TryFrom<U256>,
83 MomentOf<T>: Into<U256>,
84 T::Hash: frame_support::traits::IsType<H256>,
85{
86 fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {
87 Err(XcmError::Unimplemented)
89 }
90
91 fn check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) {
92 }
94
95 fn can_check_out(_destination: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {
96 Err(XcmError::Unimplemented)
98 }
99
100 fn check_out(_destination: &Location, _what: &Asset, _context: &XcmContext) {
101 }
103
104 fn withdraw_asset_with_surplus(
105 what: &Asset,
106 who: &Location,
107 _context: Option<&XcmContext>,
108 ) -> Result<(AssetsInHolding, Weight), XcmError> {
109 tracing::trace!(
110 target: "xcm::transactor::erc20::withdraw",
111 ?what, ?who,
112 );
113 let (asset_id, amount) = Matcher::matches_fungibles(what)?;
114 let who = AccountIdConverter::convert_location(who)
115 .ok_or(MatchError::AccountIdConversionFailed)?;
116 let checking_account_eth = T::AddressMapper::to_address(&TransfersCheckingAccount::get());
118 let checking_address = Address::from(Into::<[u8; 20]>::into(checking_account_eth));
119 let weight_limit = WeightLimit::get();
120 let data =
123 IERC20::transferCall { to: checking_address, value: EU256::from(amount) }.abi_encode();
124 let ContractResult { result, weight_consumed, storage_deposit, .. } =
125 pallet_revive::Pallet::<T>::bare_call(
126 OriginFor::<T>::signed(who.clone()),
127 asset_id,
128 U256::zero(),
129 TransactionLimits::WeightAndDeposit {
130 weight_limit,
131 deposit_limit: StorageDepositLimit::get(),
132 },
133 data,
134 ExecConfig::new_substrate_tx(),
135 );
136 let surplus = weight_limit.saturating_sub(weight_consumed);
138 tracing::trace!(target: "xcm::transactor::erc20::withdraw", ?weight_consumed, ?surplus, ?storage_deposit);
139 if let Ok(return_value) = result {
140 tracing::trace!(target: "xcm::transactor::erc20::withdraw", ?return_value, "Return value by withdraw_asset");
141 if return_value.did_revert() {
142 tracing::debug!(target: "xcm::transactor::erc20::withdraw", "ERC20 contract reverted");
143 Err(XcmError::FailedToTransactAsset("ERC20 contract reverted"))
144 } else {
145 let is_success = IERC20::transferCall::abi_decode_returns_validate(&return_value.data).map_err(|error| {
146 tracing::debug!(target: "xcm::transactor::erc20::withdraw", ?error, "ERC20 contract result couldn't decode");
147 XcmError::FailedToTransactAsset("ERC20 contract result couldn't decode")
148 })?;
149 if is_success {
150 tracing::trace!(target: "xcm::transactor::erc20::withdraw", "ERC20 contract was successful");
151 Ok((what.clone().into(), surplus))
152 } else {
153 tracing::debug!(target: "xcm::transactor::erc20::withdraw", "contract transfer failed");
154 Err(XcmError::FailedToTransactAsset("ERC20 contract transfer failed"))
155 }
156 }
157 } else {
158 tracing::debug!(target: "xcm::transactor::erc20::withdraw", ?result, "Error");
159 Err(XcmError::FailedToTransactAsset("ERC20 contract execution errored"))
163 }
164 }
165
166 fn deposit_asset_with_surplus(
167 what: &Asset,
168 who: &Location,
169 _context: Option<&XcmContext>,
170 ) -> Result<Weight, XcmError> {
171 tracing::trace!(
172 target: "xcm::transactor::erc20::deposit",
173 ?what, ?who,
174 );
175 let (asset_id, amount) = Matcher::matches_fungibles(what)?;
176 let who = AccountIdConverter::convert_location(who)
177 .ok_or(MatchError::AccountIdConversionFailed)?;
178 let eth_address = T::AddressMapper::to_address(&who);
180 let address = Address::from(Into::<[u8; 20]>::into(eth_address));
181 let data = IERC20::transferCall { to: address, value: EU256::from(amount) }.abi_encode();
184 let weight_limit = WeightLimit::get();
185 let ContractResult { result, weight_consumed, storage_deposit, .. } =
186 pallet_revive::Pallet::<T>::bare_call(
187 OriginFor::<T>::signed(TransfersCheckingAccount::get()),
188 asset_id,
189 U256::zero(),
190 TransactionLimits::WeightAndDeposit {
191 weight_limit,
192 deposit_limit: StorageDepositLimit::get(),
193 },
194 data,
195 ExecConfig::new_substrate_tx(),
196 );
197 let surplus = weight_limit.saturating_sub(weight_consumed);
199 tracing::trace!(target: "xcm::transactor::erc20::deposit", ?weight_consumed, ?surplus, ?storage_deposit);
200 if let Ok(return_value) = result {
201 tracing::trace!(target: "xcm::transactor::erc20::deposit", ?return_value, "Return value");
202 if return_value.did_revert() {
203 tracing::debug!(target: "xcm::transactor::erc20::deposit", "Contract reverted");
204 Err(XcmError::FailedToTransactAsset("ERC20 contract reverted"))
205 } else {
206 let is_success = IERC20::transferCall::abi_decode_returns_validate(&return_value.data).map_err(|error| {
207 tracing::debug!(target: "xcm::transactor::erc20::deposit", ?error, "ERC20 contract result couldn't decode");
208 XcmError::FailedToTransactAsset("ERC20 contract result couldn't decode")
209 })?;
210 if is_success {
211 tracing::trace!(target: "xcm::transactor::erc20::deposit", "ERC20 contract was successful");
212 Ok(surplus)
213 } else {
214 tracing::debug!(target: "xcm::transactor::erc20::deposit", "contract transfer failed");
215 Err(XcmError::FailedToTransactAsset("ERC20 contract transfer failed"))
216 }
217 }
218 } else {
219 tracing::debug!(target: "xcm::transactor::erc20::deposit", ?result, "Error");
220 Err(XcmError::FailedToTransactAsset("ERC20 contract execution errored"))
224 }
225 }
226}