1extern crate alloc;
17
18use crate::{AssetId, Error, Pallet};
19use alloc::format;
20use ethereum_types::{BigEndianHash, H160, H256, U256};
21use fp_evm::{ExitReason, ExitSucceed};
22use frame_support::ensure;
23use frame_support::pallet_prelude::Weight;
24use pallet_evm::{GasWeightMapping, Runner};
25use precompile_utils::prelude::*;
26use precompile_utils::solidity::codec::{Address, BoundedString};
27use precompile_utils::solidity::Codec;
28use precompile_utils_macro::keccak256;
29use sp_runtime::traits::ConstU32;
30use sp_runtime::{DispatchError, SaturatedConversion};
31use sp_std::vec::Vec;
32use xcm::latest::Error as XcmError;
33
34const ERC20_CALL_MAX_CALLDATA_SIZE: usize = 4 + 32 + 32; const ERC20_CREATE_MAX_CALLDATA_SIZE: usize = 16 * 1024; const ERC20_CREATE_GAS_LIMIT: u64 = 3_600_000; pub(crate) const ERC20_BURN_FROM_GAS_LIMIT: u64 = 160_000; pub(crate) const ERC20_MINT_INTO_GAS_LIMIT: u64 = 160_000; const ERC20_PAUSE_GAS_LIMIT: u64 = 160_000; pub(crate) const ERC20_TRANSFER_GAS_LIMIT: u64 = 160_000; pub(crate) const ERC20_APPROVE_GAS_LIMIT: u64 = 160_000; const ERC20_UNPAUSE_GAS_LIMIT: u64 = 160_000; pub(crate) const ERC20_BALANCE_OF_GAS_LIMIT: u64 = 160_000; #[derive(Debug, PartialEq)]
48pub enum EvmError {
49 BurnFromFail(String),
50 BalanceOfFail(String),
51 ContractReturnInvalidValue,
52 DispatchError(DispatchError),
53 EvmCallFail(String),
54 MintIntoFail(String),
55 TransferFail(String),
56}
57
58impl From<DispatchError> for EvmError {
59 fn from(e: DispatchError) -> Self {
60 Self::DispatchError(e)
61 }
62}
63
64impl From<EvmError> for XcmError {
65 fn from(error: EvmError) -> XcmError {
66 match error {
67 EvmError::BurnFromFail(err) => {
68 log::debug!("BurnFromFail error: {:?}", err);
69 XcmError::FailedToTransactAsset("Erc20 contract call burnFrom fail")
70 }
71 EvmError::BalanceOfFail(err) => {
72 log::debug!("BalanceOfFail error: {:?}", err);
73 XcmError::FailedToTransactAsset("Erc20 contract call balanceOf fail")
74 }
75 EvmError::ContractReturnInvalidValue => {
76 XcmError::FailedToTransactAsset("Erc20 contract return invalid value")
77 }
78 EvmError::DispatchError(err) => {
79 log::debug!("dispatch error: {:?}", err);
80 Self::FailedToTransactAsset("storage layer error")
81 }
82 EvmError::EvmCallFail(err) => {
83 log::debug!("EvmCallFail error: {:?}", err);
84 XcmError::FailedToTransactAsset("Fail to call erc20 contract")
85 }
86 EvmError::MintIntoFail(err) => {
87 log::debug!("MintIntoFail error: {:?}", err);
88 XcmError::FailedToTransactAsset("Erc20 contract call mintInto fail+")
89 }
90 EvmError::TransferFail(err) => {
91 log::debug!("TransferFail error: {:?}", err);
92 XcmError::FailedToTransactAsset("Erc20 contract call transfer fail")
93 }
94 }
95 }
96}
97
98#[derive(Codec)]
99#[cfg_attr(test, derive(Debug))]
100struct ForeignErc20ConstructorArgs {
101 owner: Address,
102 decimals: u8,
103 symbol: BoundedString<ConstU32<64>>,
104 token_name: BoundedString<ConstU32<256>>,
105}
106
107pub(crate) struct EvmCaller<T: crate::Config>(core::marker::PhantomData<T>);
108
109impl<T: crate::Config> EvmCaller<T> {
110 pub(crate) fn erc20_create(
112 asset_id: AssetId,
113 decimals: u8,
114 symbol: &str,
115 token_name: &str,
116 ) -> Result<H160, Error<T>> {
117 let mut init = Vec::with_capacity(ERC20_CREATE_MAX_CALLDATA_SIZE);
119 init.extend_from_slice(include_bytes!("../resources/foreign_erc20_initcode.bin"));
120
121 let args = ForeignErc20ConstructorArgs {
123 owner: Pallet::<T>::account_id().into(),
124 decimals,
125 symbol: symbol.into(),
126 token_name: token_name.into(),
127 };
128 let encoded_args = precompile_utils::solidity::codec::Writer::new()
129 .write(args)
130 .build();
131 init.extend_from_slice(&encoded_args[32..]);
133
134 let contract_adress = Pallet::<T>::contract_address_from_asset_id(asset_id);
135
136 let exec_info = T::EvmRunner::create_force_address(
137 Pallet::<T>::account_id(),
138 init,
139 U256::default(),
140 ERC20_CREATE_GAS_LIMIT,
141 None,
142 None,
143 None,
144 Default::default(),
145 Default::default(),
146 false,
147 false,
148 None,
149 None,
150 &<T as pallet_evm::Config>::config(),
151 contract_adress,
152 )
153 .map_err(|err| {
154 log::debug!("erc20_create (error): {:?}", err.error.into());
155 Error::<T>::Erc20ContractCreationFail
156 })?;
157
158 ensure!(
159 matches!(
160 exec_info.exit_reason,
161 ExitReason::Succeed(ExitSucceed::Returned | ExitSucceed::Stopped)
162 ),
163 Error::Erc20ContractCreationFail
164 );
165
166 Ok(contract_adress)
167 }
168
169 pub(crate) fn erc20_mint_into(
170 erc20_contract_address: H160,
171 beneficiary: H160,
172 amount: U256,
173 ) -> Result<(), EvmError> {
174 let mut input = Vec::with_capacity(ERC20_CALL_MAX_CALLDATA_SIZE);
175 input.extend_from_slice(&keccak256!("mintInto(address,uint256)")[..4]);
177 input.extend_from_slice(H256::from(beneficiary).as_bytes());
179 input.extend_from_slice(H256::from_uint(&amount).as_bytes());
181
182 let weight_limit: Weight =
183 T::GasWeightMapping::gas_to_weight(ERC20_MINT_INTO_GAS_LIMIT, true);
184
185 let exec_info = T::EvmRunner::call(
186 Pallet::<T>::account_id(),
187 erc20_contract_address,
188 input,
189 U256::default(),
190 ERC20_MINT_INTO_GAS_LIMIT,
191 None,
192 None,
193 None,
194 Default::default(),
195 Default::default(),
196 false,
197 false,
198 Some(weight_limit),
199 Some(0),
200 &<T as pallet_evm::Config>::config(),
201 )
202 .map_err(|err| EvmError::MintIntoFail(format!("{:?}", err.error.into())))?;
203
204 ensure!(
205 matches!(
206 exec_info.exit_reason,
207 ExitReason::Succeed(ExitSucceed::Returned | ExitSucceed::Stopped)
208 ),
209 {
210 let err = error_on_execution_failure(&exec_info.exit_reason, &exec_info.value);
211 EvmError::MintIntoFail(err)
212 }
213 );
214
215 Ok(())
216 }
217
218 pub(crate) fn erc20_transfer(
219 erc20_contract_address: H160,
220 from: H160,
221 to: H160,
222 amount: U256,
223 ) -> Result<(), EvmError> {
224 let mut input = Vec::with_capacity(ERC20_CALL_MAX_CALLDATA_SIZE);
225 input.extend_from_slice(&keccak256!("transfer(address,uint256)")[..4]);
227 input.extend_from_slice(H256::from(to).as_bytes());
229 input.extend_from_slice(H256::from_uint(&amount).as_bytes());
231
232 let weight_limit: Weight =
233 T::GasWeightMapping::gas_to_weight(ERC20_TRANSFER_GAS_LIMIT, true);
234
235 let exec_info = T::EvmRunner::call(
236 from,
237 erc20_contract_address,
238 input,
239 U256::default(),
240 ERC20_TRANSFER_GAS_LIMIT,
241 None,
242 None,
243 None,
244 Default::default(),
245 Default::default(),
246 false,
247 false,
248 Some(weight_limit),
249 Some(0),
250 &<T as pallet_evm::Config>::config(),
251 )
252 .map_err(|err| EvmError::TransferFail(format!("{:?}", err.error.into())))?;
253
254 ensure!(
255 matches!(
256 exec_info.exit_reason,
257 ExitReason::Succeed(ExitSucceed::Returned | ExitSucceed::Stopped)
258 ),
259 {
260 let err = error_on_execution_failure(&exec_info.exit_reason, &exec_info.value);
261 EvmError::TransferFail(err)
262 }
263 );
264
265 let bytes: [u8; 32] = U256::from(1).to_big_endian();
267
268 ensure!(
270 !exec_info.value.is_empty() && exec_info.value == bytes,
271 EvmError::ContractReturnInvalidValue
272 );
273
274 Ok(())
275 }
276
277 pub(crate) fn erc20_approve(
278 erc20_contract_address: H160,
279 owner: H160,
280 spender: H160,
281 amount: U256,
282 ) -> Result<(), EvmError> {
283 let mut input = Vec::with_capacity(ERC20_CALL_MAX_CALLDATA_SIZE);
284 input.extend_from_slice(&keccak256!("approve(address,uint256)")[..4]);
286 input.extend_from_slice(H256::from(spender).as_bytes());
288 input.extend_from_slice(H256::from_uint(&amount).as_bytes());
290 let weight_limit: Weight =
291 T::GasWeightMapping::gas_to_weight(ERC20_APPROVE_GAS_LIMIT, true);
292
293 let exec_info = T::EvmRunner::call(
294 owner,
295 erc20_contract_address,
296 input,
297 U256::default(),
298 ERC20_APPROVE_GAS_LIMIT,
299 None,
300 None,
301 None,
302 Default::default(),
303 Default::default(),
304 false,
305 false,
306 Some(weight_limit),
307 Some(0),
308 &<T as pallet_evm::Config>::config(),
309 )
310 .map_err(|err| EvmError::EvmCallFail(format!("{:?}", err.error.into())))?;
311
312 ensure!(
313 matches!(
314 exec_info.exit_reason,
315 ExitReason::Succeed(ExitSucceed::Returned | ExitSucceed::Stopped)
316 ),
317 {
318 let err = error_on_execution_failure(&exec_info.exit_reason, &exec_info.value);
319 EvmError::EvmCallFail(err)
320 }
321 );
322
323 Ok(())
324 }
325
326 pub(crate) fn erc20_burn_from(
327 erc20_contract_address: H160,
328 who: H160,
329 amount: U256,
330 ) -> Result<(), EvmError> {
331 let mut input = Vec::with_capacity(ERC20_CALL_MAX_CALLDATA_SIZE);
332 input.extend_from_slice(&keccak256!("burnFrom(address,uint256)")[..4]);
334 input.extend_from_slice(H256::from(who).as_bytes());
336 input.extend_from_slice(H256::from_uint(&amount).as_bytes());
338
339 let weight_limit: Weight =
340 T::GasWeightMapping::gas_to_weight(ERC20_BURN_FROM_GAS_LIMIT, true);
341
342 let exec_info = T::EvmRunner::call(
343 Pallet::<T>::account_id(),
344 erc20_contract_address,
345 input,
346 U256::default(),
347 ERC20_BURN_FROM_GAS_LIMIT,
348 None,
349 None,
350 None,
351 Default::default(),
352 Default::default(),
353 false,
354 false,
355 Some(weight_limit),
356 Some(0),
357 &<T as pallet_evm::Config>::config(),
358 )
359 .map_err(|err| EvmError::EvmCallFail(format!("{:?}", err.error.into())))?;
360
361 ensure!(
362 matches!(
363 exec_info.exit_reason,
364 ExitReason::Succeed(ExitSucceed::Returned | ExitSucceed::Stopped)
365 ),
366 {
367 let err = error_on_execution_failure(&exec_info.exit_reason, &exec_info.value);
368 EvmError::BurnFromFail(err)
369 }
370 );
371
372 Ok(())
373 }
374
375 pub(crate) fn erc20_pause(asset_id: AssetId) -> Result<(), Error<T>> {
377 let mut input = Vec::with_capacity(ERC20_CALL_MAX_CALLDATA_SIZE);
378 input.extend_from_slice(&keccak256!("pause()")[..4]);
380
381 let weight_limit: Weight = T::GasWeightMapping::gas_to_weight(ERC20_PAUSE_GAS_LIMIT, true);
382
383 let exec_info = T::EvmRunner::call(
384 Pallet::<T>::account_id(),
385 Pallet::<T>::contract_address_from_asset_id(asset_id),
386 input,
387 U256::default(),
388 ERC20_PAUSE_GAS_LIMIT,
389 None,
390 None,
391 None,
392 Default::default(),
393 Default::default(),
394 false,
395 false,
396 Some(weight_limit),
397 Some(0),
398 &<T as pallet_evm::Config>::config(),
399 )
400 .map_err(|err| {
401 log::debug!("erc20_pause (error): {:?}", err.error.into());
402 Error::<T>::EvmInternalError
403 })?;
404
405 ensure!(
406 matches!(
407 exec_info.exit_reason,
408 ExitReason::Succeed(ExitSucceed::Returned | ExitSucceed::Stopped)
409 ),
410 {
411 let err = error_on_execution_failure(&exec_info.exit_reason, &exec_info.value);
412 log::debug!("erc20_pause (error): {:?}", err);
413 Error::<T>::EvmCallPauseFail
414 }
415 );
416
417 Ok(())
418 }
419
420 pub(crate) fn erc20_unpause(asset_id: AssetId) -> Result<(), Error<T>> {
422 let mut input = Vec::with_capacity(ERC20_CALL_MAX_CALLDATA_SIZE);
423 input.extend_from_slice(&keccak256!("unpause()")[..4]);
425
426 let weight_limit: Weight =
427 T::GasWeightMapping::gas_to_weight(ERC20_UNPAUSE_GAS_LIMIT, true);
428
429 let exec_info = T::EvmRunner::call(
430 Pallet::<T>::account_id(),
431 Pallet::<T>::contract_address_from_asset_id(asset_id),
432 input,
433 U256::default(),
434 ERC20_UNPAUSE_GAS_LIMIT,
435 None,
436 None,
437 None,
438 Default::default(),
439 Default::default(),
440 false,
441 false,
442 Some(weight_limit),
443 Some(0),
444 &<T as pallet_evm::Config>::config(),
445 )
446 .map_err(|err| {
447 log::debug!("erc20_unpause (error): {:?}", err.error.into());
448 Error::<T>::EvmInternalError
449 })?;
450
451 ensure!(
452 matches!(
453 exec_info.exit_reason,
454 ExitReason::Succeed(ExitSucceed::Returned | ExitSucceed::Stopped)
455 ),
456 {
457 let err = error_on_execution_failure(&exec_info.exit_reason, &exec_info.value);
458 log::debug!("erc20_unpause (error): {:?}", err);
459 Error::<T>::EvmCallUnpauseFail
460 }
461 );
462
463 Ok(())
464 }
465
466 pub(crate) fn erc20_balance_of(asset_id: AssetId, account: H160) -> Result<U256, EvmError> {
468 let mut input = Vec::with_capacity(ERC20_CALL_MAX_CALLDATA_SIZE);
469 input.extend_from_slice(&keccak256!("balanceOf(address)")[..4]);
471 input.extend_from_slice(H256::from(account).as_bytes());
473
474 let exec_info = T::EvmRunner::call(
475 Pallet::<T>::account_id(),
476 Pallet::<T>::contract_address_from_asset_id(asset_id),
477 input,
478 U256::default(),
479 ERC20_BALANCE_OF_GAS_LIMIT,
480 None,
481 None,
482 None,
483 Default::default(),
484 Default::default(),
485 false,
486 false,
487 None,
488 None,
489 &<T as pallet_evm::Config>::config(),
490 )
491 .map_err(|err| EvmError::EvmCallFail(format!("{:?}", err.error.into())))?;
492
493 ensure!(
494 matches!(
495 exec_info.exit_reason,
496 ExitReason::Succeed(ExitSucceed::Returned | ExitSucceed::Stopped)
497 ),
498 {
499 let err = error_on_execution_failure(&exec_info.exit_reason, &exec_info.value);
500 EvmError::BalanceOfFail(err)
501 }
502 );
503
504 let balance = U256::from_big_endian(&exec_info.value);
505 Ok(balance)
506 }
507}
508
509fn error_on_execution_failure(reason: &ExitReason, data: &[u8]) -> String {
510 match reason {
511 ExitReason::Succeed(_) => alloc::string::String::new(),
512 ExitReason::Error(err) => format!("evm error: {err:?}"),
513 ExitReason::Fatal(err) => format!("evm fatal: {err:?}"),
514 ExitReason::Revert(_) => extract_revert_message(data),
515 }
516}
517
518fn extract_revert_message(data: &[u8]) -> alloc::string::String {
521 const LEN_START: usize = 36;
522 const MESSAGE_START: usize = 68;
523 const BASE_MESSAGE: &str = "VM Exception while processing transaction: revert";
524 if data.len() <= MESSAGE_START {
526 return BASE_MESSAGE.into();
527 }
528 let message_len =
530 U256::from_big_endian(&data[LEN_START..MESSAGE_START]).saturated_into::<usize>();
531 let message_end = MESSAGE_START.saturating_add(message_len);
532 if data.len() < message_end {
534 return BASE_MESSAGE.into();
535 }
536 let body = &data[MESSAGE_START..message_end];
538 match core::str::from_utf8(body) {
539 Ok(reason) => format!("{BASE_MESSAGE} {reason}"),
540 Err(_) => BASE_MESSAGE.into(),
541 }
542}