1use alloc::boxed::Box;
20use core::marker::PhantomData;
21use ethereum_standards::IERC20;
22use frame_support::{
23 defensive_assert,
24 traits::{
25 fungible::Inspect,
26 tokens::imbalance::{
27 ImbalanceAccounting, UnsafeConstructorDestructor, UnsafeManualAccounting,
28 },
29 OriginTrait,
30 },
31};
32use frame_system::pallet_prelude::OriginFor;
33use pallet_revive::{
34 precompiles::alloy::{
35 primitives::{Address, U256 as EU256},
36 sol_types::SolCall,
37 },
38 AddressMapper, ContractResult, ExecConfig, MomentOf, TransactionLimits,
39};
40use sp_core::{Get, H160, H256, U256};
41use sp_runtime::Weight;
42use xcm::latest::prelude::*;
43use xcm_executor::{
44 traits::{ConvertLocation, Error as MatchError, MatchesFungibles, TransactAsset},
45 AssetsInHolding,
46};
47
48type BalanceOf<T> = <<T as pallet_revive::Config>::Currency as Inspect<
49 <T as frame_system::Config>::AccountId,
50>>::Balance;
51
52pub struct ERC20Transactor<
54 T,
55 Matcher,
56 AccountIdConverter,
57 WeightLimit,
58 StorageDepositLimit,
59 AccountId,
60 TransfersCheckingAccount,
61>(
62 PhantomData<(
63 T,
64 Matcher,
65 AccountIdConverter,
66 WeightLimit,
67 StorageDepositLimit,
68 AccountId,
69 TransfersCheckingAccount,
70 )>,
71);
72
73struct Erc20Credit(u128);
80impl UnsafeConstructorDestructor<u128> for Erc20Credit {
81 fn unsafe_clone(&self) -> Box<dyn ImbalanceAccounting<u128>> {
82 Box::new(Erc20Credit(self.0))
83 }
84 fn forget_imbalance(&mut self) -> u128 {
85 let amount = self.0;
86 self.0 = 0;
87 amount
88 }
89}
90
91impl UnsafeManualAccounting<u128> for Erc20Credit {
92 fn saturating_subsume(&mut self, mut other: Box<dyn ImbalanceAccounting<u128>>) {
93 let amount = other.forget_imbalance();
94 self.0 = self.0.saturating_add(amount);
95 }
96}
97
98impl ImbalanceAccounting<u128> for Erc20Credit {
99 fn amount(&self) -> u128 {
100 self.0
101 }
102 fn saturating_take(&mut self, amount: u128) -> Box<dyn ImbalanceAccounting<u128>> {
103 let new = self.0.min(amount);
104 self.0 = self.0 - new;
105 Box::new(Erc20Credit(new))
106 }
107}
108
109impl<
110 AccountId: Eq + Clone,
111 T: pallet_revive::Config<AccountId = AccountId>,
112 AccountIdConverter: ConvertLocation<AccountId>,
113 Matcher: MatchesFungibles<H160, u128>,
114 WeightLimit: Get<Weight>,
115 StorageDepositLimit: Get<BalanceOf<T>>,
116 TransfersCheckingAccount: Get<AccountId>,
117 > TransactAsset
118 for ERC20Transactor<
119 T,
120 Matcher,
121 AccountIdConverter,
122 WeightLimit,
123 StorageDepositLimit,
124 AccountId,
125 TransfersCheckingAccount,
126 >
127where
128 BalanceOf<T>: Into<U256> + TryFrom<U256>,
129 MomentOf<T>: Into<U256>,
130 T::Hash: frame_support::traits::IsType<H256>,
131{
132 fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {
133 Err(XcmError::Unimplemented)
135 }
136
137 fn check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) {
138 }
140
141 fn can_check_out(_destination: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult {
142 Err(XcmError::Unimplemented)
144 }
145
146 fn check_out(_destination: &Location, _what: &Asset, _context: &XcmContext) {
147 }
149
150 fn withdraw_asset_with_surplus(
151 what: &Asset,
152 who: &Location,
153 _context: Option<&XcmContext>,
154 ) -> Result<(AssetsInHolding, Weight), XcmError> {
155 tracing::trace!(
156 target: "xcm::transactor::erc20::withdraw",
157 ?what, ?who,
158 );
159 let (asset_id, amount) = Matcher::matches_fungibles(what)?;
160 let who = AccountIdConverter::convert_location(who)
161 .ok_or(MatchError::AccountIdConversionFailed)?;
162 let checking_account_eth = T::AddressMapper::to_address(&TransfersCheckingAccount::get());
164 let checking_address = Address::from(Into::<[u8; 20]>::into(checking_account_eth));
165 let weight_limit = WeightLimit::get();
166 let data =
169 IERC20::transferCall { to: checking_address, value: EU256::from(amount) }.abi_encode();
170 let ContractResult { result, weight_consumed, storage_deposit, .. } =
171 pallet_revive::Pallet::<T>::bare_call(
172 OriginFor::<T>::signed(who.clone()),
173 asset_id,
174 U256::zero(),
175 TransactionLimits::WeightAndDeposit {
176 weight_limit,
177 deposit_limit: StorageDepositLimit::get(),
178 },
179 data,
180 &ExecConfig::new_substrate_tx(),
181 );
182 let surplus = weight_limit.saturating_sub(weight_consumed);
184 tracing::trace!(target: "xcm::transactor::erc20::withdraw", ?weight_consumed, ?surplus, ?storage_deposit);
185 if let Ok(return_value) = result {
186 tracing::trace!(target: "xcm::transactor::erc20::withdraw", ?return_value, "Return value by withdraw_asset");
187 if return_value.did_revert() {
188 tracing::debug!(target: "xcm::transactor::erc20::withdraw", "ERC20 contract reverted");
189 Err(XcmError::FailedToTransactAsset("ERC20 contract reverted"))
190 } else {
191 let is_success = IERC20::transferCall::abi_decode_returns_validate(&return_value.data).map_err(|error| {
192 tracing::debug!(target: "xcm::transactor::erc20::withdraw", ?error, "ERC20 contract result couldn't decode");
193 XcmError::FailedToTransactAsset("ERC20 contract result couldn't decode")
194 })?;
195 if is_success {
196 tracing::trace!(target: "xcm::transactor::erc20::withdraw", "ERC20 contract was successful");
197 Ok((
198 AssetsInHolding::new_from_fungible_credit(
199 what.id.clone(),
200 Box::new(Erc20Credit(amount)),
201 ),
202 surplus,
203 ))
204 } else {
205 tracing::debug!(target: "xcm::transactor::erc20::withdraw", "contract transfer failed");
206 Err(XcmError::FailedToTransactAsset("ERC20 contract transfer failed"))
207 }
208 }
209 } else {
210 tracing::debug!(target: "xcm::transactor::erc20::withdraw", ?result, "Error");
211 Err(XcmError::FailedToTransactAsset("ERC20 contract execution errored"))
215 }
216 }
217
218 fn deposit_asset_with_surplus(
226 what: AssetsInHolding,
227 who: &Location,
228 _context: Option<&XcmContext>,
229 ) -> Result<Weight, (AssetsInHolding, XcmError)> {
230 tracing::trace!(
231 target: "xcm::transactor::erc20::deposit",
232 ?what, ?who,
233 );
234 defensive_assert!(what.len() == 1, "Trying to deposit more than one asset!");
235 let maybe = what
237 .fungible_assets_iter()
238 .next()
239 .and_then(|asset| Matcher::matches_fungibles(&asset).ok());
240 let (asset_contract_id, amount) = match maybe {
241 Some(inner) => inner,
242 None => return Err((what, MatchError::AssetNotHandled.into())),
243 };
244 let who = match AccountIdConverter::convert_location(who) {
245 Some(inner) => inner,
246 None => return Err((what, MatchError::AccountIdConversionFailed.into())),
247 };
248 let eth_address = T::AddressMapper::to_address(&who);
250 let address = Address::from(Into::<[u8; 20]>::into(eth_address));
251 let data = IERC20::transferCall { to: address, value: EU256::from(amount) }.abi_encode();
254 let weight_limit = WeightLimit::get();
255 let ContractResult { result, weight_consumed, storage_deposit, .. } =
256 pallet_revive::Pallet::<T>::bare_call(
257 OriginFor::<T>::signed(TransfersCheckingAccount::get()),
258 asset_contract_id,
259 U256::zero(),
260 TransactionLimits::WeightAndDeposit {
261 weight_limit,
262 deposit_limit: StorageDepositLimit::get(),
263 },
264 data,
265 &ExecConfig::new_substrate_tx(),
266 );
267 let surplus = weight_limit.saturating_sub(weight_consumed);
269 tracing::trace!(target: "xcm::transactor::erc20::deposit", ?weight_consumed, ?surplus, ?storage_deposit);
270 if let Ok(return_value) = result {
271 tracing::trace!(target: "xcm::transactor::erc20::deposit", ?return_value, "Return value");
272 if return_value.did_revert() {
273 tracing::debug!(target: "xcm::transactor::erc20::deposit", "Contract reverted");
274 Err((what, XcmError::FailedToTransactAsset("ERC20 contract reverted")))
275 } else {
276 match IERC20::transferCall::abi_decode_returns_validate(&return_value.data) {
277 Ok(true) => {
278 tracing::trace!(target: "xcm::transactor::erc20::deposit", "ERC20 contract was successful");
279 Ok(surplus)
280 },
281 Ok(false) => {
282 tracing::debug!(target: "xcm::transactor::erc20::deposit", "contract transfer failed");
283 Err((
284 what,
285 XcmError::FailedToTransactAsset("ERC20 contract transfer failed"),
286 ))
287 },
288 Err(error) => {
289 tracing::debug!(target: "xcm::transactor::erc20::deposit", ?error, "ERC20 contract result couldn't decode");
290 Err((
291 what,
292 XcmError::FailedToTransactAsset(
293 "ERC20 contract result couldn't decode",
294 ),
295 ))
296 },
297 }
298 }
299 } else {
300 tracing::debug!(target: "xcm::transactor::erc20::deposit", ?result, "Error");
301 Err((what, XcmError::FailedToTransactAsset("ERC20 contract execution errored")))
305 }
306 }
307}