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