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 manueal binary search)
36
const ERC20_CREATE_GAS_LIMIT: u64 = 3_367_000; // highest failure: 3_366_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_000; // highest failure: 149_500
40
pub(crate) const ERC20_TRANSFER_GAS_LIMIT: u64 = 155_000; // highest failure: 154_000
41
const ERC20_UNPAUSE_GAS_LIMIT: u64 = 150_000; // highest failure: 149_500
42

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

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

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

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

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

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

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

            
118
10
		let contract_adress = Pallet::<T>::contract_address_from_asset_id(asset_id);
119

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

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

            
146
10
		Ok(contract_adress)
147
10
	}
148

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

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

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

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

            
191
4
		Ok(())
192
4
	}
193

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

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

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

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

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

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

            
247
		Ok(())
248
	}
249

            
250
18
	pub(crate) fn erc20_burn_from(
251
18
		erc20_contract_address: H160,
252
18
		who: H160,
253
18
		amount: U256,
254
18
	) -> Result<(), EvmError> {
255
18
		let mut input = Vec::with_capacity(ERC20_CALL_MAX_CALLDATA_SIZE);
256
18
		// Selector
257
18
		input.extend_from_slice(&keccak256!("burnFrom(address,uint256)")[..4]);
258
18
		// append who address
259
18
		input.extend_from_slice(H256::from(who).as_bytes());
260
18
		// append amount to be burn
261
18
		input.extend_from_slice(H256::from_uint(&amount).as_bytes());
262
18

            
263
18
		let weight_limit: Weight =
264
18
			T::GasWeightMapping::gas_to_weight(ERC20_BURN_FROM_GAS_LIMIT, true);
265

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

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

            
292
18
		Ok(())
293
18
	}
294

            
295
	// Call contract selector "pause"
296
2
	pub(crate) fn erc20_pause(asset_id: AssetId) -> Result<(), Error<T>> {
297
2
		let mut input = Vec::with_capacity(ERC20_CALL_MAX_CALLDATA_SIZE);
298
2
		// Selector
299
2
		input.extend_from_slice(&keccak256!("pause()")[..4]);
300
2

            
301
2
		let weight_limit: Weight = T::GasWeightMapping::gas_to_weight(ERC20_PAUSE_GAS_LIMIT, true);
302

            
303
2
		let exec_info = T::EvmRunner::call(
304
2
			Pallet::<T>::account_id(),
305
2
			Pallet::<T>::contract_address_from_asset_id(asset_id),
306
2
			input,
307
2
			U256::default(),
308
2
			ERC20_PAUSE_GAS_LIMIT,
309
2
			None,
310
2
			None,
311
2
			None,
312
2
			Default::default(),
313
2
			false,
314
2
			false,
315
2
			Some(weight_limit),
316
2
			Some(0),
317
2
			&<T as pallet_evm::Config>::config(),
318
2
		)
319
2
		.map_err(|_| Error::<T>::EvmInternalError)?;
320

            
321
2
		ensure!(
322
			matches!(
323
2
				exec_info.exit_reason,
324
				ExitReason::Succeed(ExitSucceed::Returned | ExitSucceed::Stopped)
325
			),
326
			Error::<T>::EvmCallPauseFail
327
		);
328

            
329
2
		Ok(())
330
2
	}
331

            
332
	// Call contract selector "unpause"
333
2
	pub(crate) fn erc20_unpause(asset_id: AssetId) -> Result<(), Error<T>> {
334
2
		let mut input = Vec::with_capacity(ERC20_CALL_MAX_CALLDATA_SIZE);
335
2
		// Selector
336
2
		input.extend_from_slice(&keccak256!("unpause()")[..4]);
337
2

            
338
2
		let weight_limit: Weight =
339
2
			T::GasWeightMapping::gas_to_weight(ERC20_UNPAUSE_GAS_LIMIT, true);
340

            
341
2
		let exec_info = T::EvmRunner::call(
342
2
			Pallet::<T>::account_id(),
343
2
			Pallet::<T>::contract_address_from_asset_id(asset_id),
344
2
			input,
345
2
			U256::default(),
346
2
			ERC20_UNPAUSE_GAS_LIMIT,
347
2
			None,
348
2
			None,
349
2
			None,
350
2
			Default::default(),
351
2
			false,
352
2
			false,
353
2
			Some(weight_limit),
354
2
			Some(0),
355
2
			&<T as pallet_evm::Config>::config(),
356
2
		)
357
2
		.map_err(|_| Error::<T>::EvmInternalError)?;
358

            
359
2
		ensure!(
360
			matches!(
361
2
				exec_info.exit_reason,
362
				ExitReason::Succeed(ExitSucceed::Returned | ExitSucceed::Stopped)
363
			),
364
			Error::<T>::EvmCallUnpauseFail
365
		);
366

            
367
2
		Ok(())
368
2
	}
369
}