1
// Copyright 2024 Moonbeam Foundation.
2
// This file is part of Moonbeam.
3

            
4
// Moonbeam is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Moonbeam is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16

            
17
use crate::{AssetId, Error, Pallet};
18
use ethereum_types::{BigEndianHash, H160, H256, U256};
19
use fp_evm::{ExitReason, ExitSucceed};
20
use frame_support::ensure;
21
use frame_support::pallet_prelude::Weight;
22
use pallet_evm::{GasWeightMapping, Runner};
23
use precompile_utils::prelude::*;
24
use precompile_utils::solidity::codec::{Address, BoundedString};
25
use precompile_utils::solidity::Codec;
26
use precompile_utils_macro::keccak256;
27
use sp_runtime::traits::ConstU32;
28
use sp_runtime::DispatchError;
29
use sp_std::vec::Vec;
30
use xcm::latest::Error as XcmError;
31

            
32
const ERC20_CALL_MAX_CALLDATA_SIZE: usize = 4 + 32 + 32; // selector + address + uint256
33
const ERC20_CREATE_MAX_CALLDATA_SIZE: usize = 16 * 1024; // 16Ko
34

            
35
// Hardcoded gas limits (from manual binary search)
36
const ERC20_CREATE_GAS_LIMIT: u64 = 3_410_000; // highest failure: 3_406_000
37
pub(crate) const ERC20_BURN_FROM_GAS_LIMIT: u64 = 155_000; // highest failure: 154_000
38
pub(crate) const ERC20_MINT_INTO_GAS_LIMIT: u64 = 155_000; // highest failure: 154_000
39
const ERC20_PAUSE_GAS_LIMIT: u64 = 150_500; // highest failure: 150_500
40
pub(crate) const ERC20_TRANSFER_GAS_LIMIT: u64 = 155_000; // highest failure: 154_000
41
pub(crate) const ERC20_APPROVE_GAS_LIMIT: u64 = 154_000; // highest failure: 153_000
42
const ERC20_UNPAUSE_GAS_LIMIT: u64 = 151_000; // highest failure: 149_500
43

            
44
pub enum EvmError {
45
	BurnFromFail,
46
	ContractReturnInvalidValue,
47
	DispatchError(DispatchError),
48
	EvmCallFail,
49
	MintIntoFail,
50
	TransferFail,
51
}
52

            
53
impl From<DispatchError> for EvmError {
54
	fn from(e: DispatchError) -> Self {
55
		Self::DispatchError(e)
56
	}
57
}
58

            
59
impl From<EvmError> for XcmError {
60
	fn from(error: EvmError) -> XcmError {
61
		match error {
62
			EvmError::BurnFromFail => {
63
				XcmError::FailedToTransactAsset("Erc20 contract call burnFrom fail")
64
			}
65
			EvmError::ContractReturnInvalidValue => {
66
				XcmError::FailedToTransactAsset("Erc20 contract return invalid value")
67
			}
68
			EvmError::DispatchError(err) => {
69
				log::debug!("dispatch error: {:?}", err);
70
				Self::FailedToTransactAsset("storage layer error")
71
			}
72
			EvmError::EvmCallFail => XcmError::FailedToTransactAsset("Fail to call erc20 contract"),
73
			EvmError::MintIntoFail => {
74
				XcmError::FailedToTransactAsset("Erc20 contract call mintInto fail")
75
			}
76
			EvmError::TransferFail => {
77
				XcmError::FailedToTransactAsset("Erc20 contract call transfer fail")
78
			}
79
		}
80
	}
81
}
82

            
83
#[derive(Codec)]
84
#[cfg_attr(test, derive(Debug))]
85
struct ForeignErc20ConstructorArgs {
86
	owner: Address,
87
	decimals: u8,
88
	symbol: BoundedString<ConstU32<64>>,
89
	token_name: BoundedString<ConstU32<256>>,
90
}
91

            
92
pub(crate) struct EvmCaller<T: crate::Config>(core::marker::PhantomData<T>);
93

            
94
impl<T: crate::Config> EvmCaller<T> {
95
	/// Deploy foreign asset erc20 contract
96
25
	pub(crate) fn erc20_create(
97
25
		asset_id: AssetId,
98
25
		decimals: u8,
99
25
		symbol: &str,
100
25
		token_name: &str,
101
25
	) -> Result<H160, Error<T>> {
102
25
		// Get init code
103
25
		let mut init = Vec::with_capacity(ERC20_CREATE_MAX_CALLDATA_SIZE);
104
25
		init.extend_from_slice(include_bytes!("../resources/foreign_erc20_initcode.bin"));
105
25

            
106
25
		// Add constructor parameters
107
25
		let args = ForeignErc20ConstructorArgs {
108
25
			owner: Pallet::<T>::account_id().into(),
109
25
			decimals,
110
25
			symbol: symbol.into(),
111
25
			token_name: token_name.into(),
112
25
		};
113
25
		let encoded_args = precompile_utils::solidity::codec::Writer::new()
114
25
			.write(args)
115
25
			.build();
116
25
		// Skip size of constructor args (32 bytes)
117
25
		init.extend_from_slice(&encoded_args[32..]);
118
25

            
119
25
		let contract_adress = Pallet::<T>::contract_address_from_asset_id(asset_id);
120

            
121
25
		let exec_info = T::EvmRunner::create_force_address(
122
25
			Pallet::<T>::account_id(),
123
25
			init,
124
25
			U256::default(),
125
25
			ERC20_CREATE_GAS_LIMIT,
126
25
			None,
127
25
			None,
128
25
			None,
129
25
			Default::default(),
130
25
			false,
131
25
			false,
132
25
			None,
133
25
			None,
134
25
			&<T as pallet_evm::Config>::config(),
135
25
			contract_adress,
136
25
		)
137
25
		.map_err(|_| Error::Erc20ContractCreationFail)?;
138

            
139
25
		ensure!(
140
			matches!(
141
25
				exec_info.exit_reason,
142
				ExitReason::Succeed(ExitSucceed::Returned | ExitSucceed::Stopped)
143
			),
144
			Error::Erc20ContractCreationFail
145
		);
146

            
147
25
		Ok(contract_adress)
148
25
	}
149

            
150
13
	pub(crate) fn erc20_mint_into(
151
13
		erc20_contract_address: H160,
152
13
		beneficiary: H160,
153
13
		amount: U256,
154
13
	) -> Result<(), EvmError> {
155
13
		let mut input = Vec::with_capacity(ERC20_CALL_MAX_CALLDATA_SIZE);
156
13
		// Selector
157
13
		input.extend_from_slice(&keccak256!("mintInto(address,uint256)")[..4]);
158
13
		// append beneficiary address
159
13
		input.extend_from_slice(H256::from(beneficiary).as_bytes());
160
13
		// append amount to be minted
161
13
		input.extend_from_slice(H256::from_uint(&amount).as_bytes());
162
13

            
163
13
		let weight_limit: Weight =
164
13
			T::GasWeightMapping::gas_to_weight(ERC20_MINT_INTO_GAS_LIMIT, true);
165

            
166
13
		let exec_info = T::EvmRunner::call(
167
13
			Pallet::<T>::account_id(),
168
13
			erc20_contract_address,
169
13
			input,
170
13
			U256::default(),
171
13
			ERC20_MINT_INTO_GAS_LIMIT,
172
13
			None,
173
13
			None,
174
13
			None,
175
13
			Default::default(),
176
13
			false,
177
13
			false,
178
13
			Some(weight_limit),
179
13
			Some(0),
180
13
			&<T as pallet_evm::Config>::config(),
181
13
		)
182
13
		.map_err(|_| EvmError::EvmCallFail)?;
183

            
184
13
		ensure!(
185
			matches!(
186
13
				exec_info.exit_reason,
187
				ExitReason::Succeed(ExitSucceed::Returned | ExitSucceed::Stopped)
188
			),
189
			EvmError::MintIntoFail
190
		);
191

            
192
13
		Ok(())
193
13
	}
194

            
195
	pub(crate) fn erc20_transfer(
196
		erc20_contract_address: H160,
197
		from: H160,
198
		to: H160,
199
		amount: U256,
200
	) -> Result<(), EvmError> {
201
		let mut input = Vec::with_capacity(ERC20_CALL_MAX_CALLDATA_SIZE);
202
		// Selector
203
		input.extend_from_slice(&keccak256!("transfer(address,uint256)")[..4]);
204
		// append receiver address
205
		input.extend_from_slice(H256::from(to).as_bytes());
206
		// append amount to be transferred
207
		input.extend_from_slice(H256::from_uint(&amount).as_bytes());
208

            
209
		let weight_limit: Weight =
210
			T::GasWeightMapping::gas_to_weight(ERC20_TRANSFER_GAS_LIMIT, true);
211

            
212
		let exec_info = T::EvmRunner::call(
213
			from,
214
			erc20_contract_address,
215
			input,
216
			U256::default(),
217
			ERC20_TRANSFER_GAS_LIMIT,
218
			None,
219
			None,
220
			None,
221
			Default::default(),
222
			false,
223
			false,
224
			Some(weight_limit),
225
			Some(0),
226
			&<T as pallet_evm::Config>::config(),
227
		)
228
		.map_err(|_| EvmError::EvmCallFail)?;
229

            
230
		ensure!(
231
			matches!(
232
				exec_info.exit_reason,
233
				ExitReason::Succeed(ExitSucceed::Returned | ExitSucceed::Stopped)
234
			),
235
			EvmError::TransferFail
236
		);
237

            
238
		// return value is true.
239
		let mut bytes = [0u8; 32];
240
		U256::from(1).to_big_endian(&mut bytes);
241

            
242
		// Check return value to make sure not calling on empty contracts.
243
		ensure!(
244
			!exec_info.value.is_empty() && exec_info.value == bytes,
245
			EvmError::ContractReturnInvalidValue
246
		);
247

            
248
		Ok(())
249
	}
250

            
251
13
	pub(crate) fn erc20_approve(
252
13
		erc20_contract_address: H160,
253
13
		owner: H160,
254
13
		spender: H160,
255
13
		amount: U256,
256
13
	) -> Result<(), EvmError> {
257
13
		let mut input = Vec::with_capacity(ERC20_CALL_MAX_CALLDATA_SIZE);
258
13
		// Selector
259
13
		input.extend_from_slice(&keccak256!("approve(address,uint256)")[..4]);
260
13
		// append spender address
261
13
		input.extend_from_slice(H256::from(spender).as_bytes());
262
13
		// append amount to be approved
263
13
		input.extend_from_slice(H256::from_uint(&amount).as_bytes());
264
13
		let weight_limit: Weight =
265
13
			T::GasWeightMapping::gas_to_weight(ERC20_APPROVE_GAS_LIMIT, true);
266

            
267
13
		let exec_info = T::EvmRunner::call(
268
13
			owner,
269
13
			erc20_contract_address,
270
13
			input,
271
13
			U256::default(),
272
13
			ERC20_APPROVE_GAS_LIMIT,
273
13
			None,
274
13
			None,
275
13
			None,
276
13
			Default::default(),
277
13
			false,
278
13
			false,
279
13
			Some(weight_limit),
280
13
			Some(0),
281
13
			&<T as pallet_evm::Config>::config(),
282
13
		)
283
13
		.map_err(|_| EvmError::EvmCallFail)?;
284

            
285
13
		ensure!(
286
			matches!(
287
13
				exec_info.exit_reason,
288
				ExitReason::Succeed(ExitSucceed::Returned | ExitSucceed::Stopped)
289
			),
290
			EvmError::EvmCallFail
291
		);
292

            
293
13
		Ok(())
294
13
	}
295

            
296
18
	pub(crate) fn erc20_burn_from(
297
18
		erc20_contract_address: H160,
298
18
		who: H160,
299
18
		amount: U256,
300
18
	) -> Result<(), EvmError> {
301
18
		let mut input = Vec::with_capacity(ERC20_CALL_MAX_CALLDATA_SIZE);
302
18
		// Selector
303
18
		input.extend_from_slice(&keccak256!("burnFrom(address,uint256)")[..4]);
304
18
		// append who address
305
18
		input.extend_from_slice(H256::from(who).as_bytes());
306
18
		// append amount to be burn
307
18
		input.extend_from_slice(H256::from_uint(&amount).as_bytes());
308
18

            
309
18
		let weight_limit: Weight =
310
18
			T::GasWeightMapping::gas_to_weight(ERC20_BURN_FROM_GAS_LIMIT, true);
311

            
312
18
		let exec_info = T::EvmRunner::call(
313
18
			Pallet::<T>::account_id(),
314
18
			erc20_contract_address,
315
18
			input,
316
18
			U256::default(),
317
18
			ERC20_BURN_FROM_GAS_LIMIT,
318
18
			None,
319
18
			None,
320
18
			None,
321
18
			Default::default(),
322
18
			false,
323
18
			false,
324
18
			Some(weight_limit),
325
18
			Some(0),
326
18
			&<T as pallet_evm::Config>::config(),
327
18
		)
328
18
		.map_err(|_| EvmError::EvmCallFail)?;
329

            
330
18
		ensure!(
331
			matches!(
332
18
				exec_info.exit_reason,
333
				ExitReason::Succeed(ExitSucceed::Returned | ExitSucceed::Stopped)
334
			),
335
			EvmError::BurnFromFail
336
		);
337

            
338
18
		Ok(())
339
18
	}
340

            
341
	// Call contract selector "pause"
342
2
	pub(crate) fn erc20_pause(asset_id: AssetId) -> Result<(), Error<T>> {
343
2
		let mut input = Vec::with_capacity(ERC20_CALL_MAX_CALLDATA_SIZE);
344
2
		// Selector
345
2
		input.extend_from_slice(&keccak256!("pause()")[..4]);
346
2

            
347
2
		let weight_limit: Weight = T::GasWeightMapping::gas_to_weight(ERC20_PAUSE_GAS_LIMIT, true);
348

            
349
2
		let exec_info = T::EvmRunner::call(
350
2
			Pallet::<T>::account_id(),
351
2
			Pallet::<T>::contract_address_from_asset_id(asset_id),
352
2
			input,
353
2
			U256::default(),
354
2
			ERC20_PAUSE_GAS_LIMIT,
355
2
			None,
356
2
			None,
357
2
			None,
358
2
			Default::default(),
359
2
			false,
360
2
			false,
361
2
			Some(weight_limit),
362
2
			Some(0),
363
2
			&<T as pallet_evm::Config>::config(),
364
2
		)
365
2
		.map_err(|_| Error::<T>::EvmInternalError)?;
366

            
367
2
		ensure!(
368
			matches!(
369
2
				exec_info.exit_reason,
370
				ExitReason::Succeed(ExitSucceed::Returned | ExitSucceed::Stopped)
371
			),
372
			Error::<T>::EvmCallPauseFail
373
		);
374

            
375
2
		Ok(())
376
2
	}
377

            
378
	// Call contract selector "unpause"
379
2
	pub(crate) fn erc20_unpause(asset_id: AssetId) -> Result<(), Error<T>> {
380
2
		let mut input = Vec::with_capacity(ERC20_CALL_MAX_CALLDATA_SIZE);
381
2
		// Selector
382
2
		input.extend_from_slice(&keccak256!("unpause()")[..4]);
383
2

            
384
2
		let weight_limit: Weight =
385
2
			T::GasWeightMapping::gas_to_weight(ERC20_UNPAUSE_GAS_LIMIT, true);
386

            
387
2
		let exec_info = T::EvmRunner::call(
388
2
			Pallet::<T>::account_id(),
389
2
			Pallet::<T>::contract_address_from_asset_id(asset_id),
390
2
			input,
391
2
			U256::default(),
392
2
			ERC20_UNPAUSE_GAS_LIMIT,
393
2
			None,
394
2
			None,
395
2
			None,
396
2
			Default::default(),
397
2
			false,
398
2
			false,
399
2
			Some(weight_limit),
400
2
			Some(0),
401
2
			&<T as pallet_evm::Config>::config(),
402
2
		)
403
2
		.map_err(|_| Error::<T>::EvmInternalError)?;
404

            
405
2
		ensure!(
406
			matches!(
407
2
				exec_info.exit_reason,
408
				ExitReason::Succeed(ExitSucceed::Returned | ExitSucceed::Stopped)
409
			),
410
			Error::<T>::EvmCallUnpauseFail
411
		);
412

            
413
2
		Ok(())
414
2
	}
415
}