1use ethereum::{
18 AccessList, AccessListItem, AuthorizationList, EIP1559Transaction, EIP2930Transaction,
19 LegacyTransaction, TransactionAction, TransactionV3,
20};
21use ethereum_types::{H160, H256, U256};
22use frame_support::{traits::ConstU32, BoundedVec};
23use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode};
24use scale_info::TypeInfo;
25use sp_std::vec::Vec;
26
27pub const DEFAULT_PROOF_SIZE: u64 = 256 * 1024;
35
36pub const MAX_ETHEREUM_XCM_INPUT_SIZE: u32 = 2u32.pow(16);
38
39pub trait EnsureProxy<AccountId> {
42 fn ensure_ok(delegator: AccountId, delegatee: AccountId) -> Result<(), &'static str>;
43}
44
45#[derive(Clone, Debug, Eq, PartialEq, Encode, Decode, TypeInfo, DecodeWithMemTracking)]
46pub struct ManualEthereumXcmFee {
48 pub gas_price: Option<U256>,
50 pub max_fee_per_gas: Option<U256>,
53}
54
55#[derive(Clone, Debug, Eq, PartialEq, Encode, Decode, TypeInfo, DecodeWithMemTracking)]
57pub enum EthereumXcmFee {
58 Manual(ManualEthereumXcmFee),
60 Auto,
62}
63
64#[derive(Clone, Debug, Eq, PartialEq, Encode, Decode, TypeInfo, DecodeWithMemTracking)]
66pub enum EthereumXcmTransaction {
67 V1(EthereumXcmTransactionV1),
68 V2(EthereumXcmTransactionV2),
69 V3(EthereumXcmTransactionV3),
70}
71
72pub fn rs_id() -> H256 {
74 H256::from_low_u64_be(1u64)
75}
76
77#[derive(Clone, Debug, Eq, PartialEq, Encode, Decode, TypeInfo, DecodeWithMemTracking)]
78pub struct EthereumXcmTransactionV1 {
79 pub gas_limit: U256,
81 pub fee_payment: EthereumXcmFee,
83 pub action: TransactionAction,
85 pub value: U256,
87 pub input: BoundedVec<u8, ConstU32<MAX_ETHEREUM_XCM_INPUT_SIZE>>,
89 pub access_list: Option<Vec<(H160, Vec<H256>)>>,
91}
92
93#[derive(Clone, Debug, Eq, PartialEq, Encode, Decode, TypeInfo, DecodeWithMemTracking)]
94pub struct EthereumXcmTransactionV2 {
95 pub gas_limit: U256,
97 pub action: TransactionAction,
99 pub value: U256,
101 pub input: BoundedVec<u8, ConstU32<MAX_ETHEREUM_XCM_INPUT_SIZE>>,
103 pub access_list: Option<Vec<(H160, Vec<H256>)>>,
105}
106
107#[derive(Clone, Debug, Eq, PartialEq, Encode, Decode, TypeInfo, DecodeWithMemTracking)]
108pub struct EthereumXcmTransactionV3 {
109 pub gas_limit: U256,
111 pub action: TransactionAction,
113 pub value: U256,
115 pub input: BoundedVec<u8, ConstU32<MAX_ETHEREUM_XCM_INPUT_SIZE>>,
117 pub access_list: Option<Vec<(H160, Vec<H256>)>>,
119 pub authorization_list: Option<AuthorizationList>,
122}
123
124pub trait XcmToEthereum {
125 fn into_transaction(
126 &self,
127 nonce: U256,
128 chain_id: u64,
129 allow_create: bool,
130 ) -> Option<TransactionV3>;
131}
132
133impl XcmToEthereum for EthereumXcmTransaction {
134 fn into_transaction(
135 &self,
136 nonce: U256,
137 chain_id: u64,
138 allow_create: bool,
139 ) -> Option<TransactionV3> {
140 match self {
141 EthereumXcmTransaction::V1(v1_tx) => {
142 v1_tx.into_transaction(nonce, chain_id, allow_create)
143 }
144 EthereumXcmTransaction::V2(v2_tx) => {
145 v2_tx.into_transaction(nonce, chain_id, allow_create)
146 }
147 EthereumXcmTransaction::V3(v3_tx) => {
148 v3_tx.into_transaction(nonce, chain_id, allow_create)
149 }
150 }
151 }
152}
153
154impl XcmToEthereum for EthereumXcmTransactionV1 {
155 fn into_transaction(
156 &self,
157 nonce: U256,
158 chain_id: u64,
159 allow_create: bool,
160 ) -> Option<TransactionV3> {
161 if !allow_create && self.action == TransactionAction::Create {
162 return None;
164 }
165 let from_tuple_to_access_list = |t: &Vec<(H160, Vec<H256>)>| -> AccessList {
166 t.iter()
167 .map(|item| AccessListItem {
168 address: item.0.clone(),
169 storage_keys: item.1.clone(),
170 })
171 .collect::<Vec<AccessListItem>>()
172 };
173
174 let (gas_price, max_fee) = match &self.fee_payment {
175 EthereumXcmFee::Manual(fee_config) => {
176 (fee_config.gas_price, fee_config.max_fee_per_gas)
177 }
178 EthereumXcmFee::Auto => (None, Some(U256::zero())),
179 };
180
181 match (gas_price, max_fee) {
182 (Some(gas_price), None) => {
183 if let Some(ref access_list) = self.access_list {
185 Some(TransactionV3::EIP2930(EIP2930Transaction {
187 chain_id,
188 nonce,
189 gas_price,
190 gas_limit: self.gas_limit,
191 action: self.action,
192 value: self.value,
193 input: self.input.to_vec(),
194 access_list: from_tuple_to_access_list(access_list),
195 signature: ethereum::eip2930::TransactionSignature::new(
196 true,
197 rs_id(),
198 rs_id(),
199 )?,
200 }))
201 } else {
202 Some(TransactionV3::Legacy(LegacyTransaction {
204 nonce,
205 gas_price,
206 gas_limit: self.gas_limit,
207 action: self.action,
208 value: self.value,
209 input: self.input.to_vec(),
210 signature: ethereum::legacy::TransactionSignature::new(
211 42,
212 rs_id(),
213 rs_id(),
214 )?,
215 }))
216 }
217 }
218 (None, Some(max_fee)) => {
219 Some(TransactionV3::EIP1559(EIP1559Transaction {
221 chain_id,
222 nonce,
223 max_fee_per_gas: max_fee,
224 max_priority_fee_per_gas: U256::zero(),
225 gas_limit: self.gas_limit,
226 action: self.action,
227 value: self.value,
228 input: self.input.to_vec(),
229 access_list: if let Some(ref access_list) = self.access_list {
230 from_tuple_to_access_list(access_list)
231 } else {
232 Vec::new()
233 },
234 signature: ethereum::eip1559::TransactionSignature::new(
235 true,
236 rs_id(),
237 rs_id(),
238 )?,
239 }))
240 }
241 _ => None,
242 }
243 }
244}
245
246impl XcmToEthereum for EthereumXcmTransactionV2 {
247 fn into_transaction(
248 &self,
249 nonce: U256,
250 chain_id: u64,
251 allow_create: bool,
252 ) -> Option<TransactionV3> {
253 if !allow_create && self.action == TransactionAction::Create {
254 return None;
256 }
257 let from_tuple_to_access_list = |t: &Vec<(H160, Vec<H256>)>| -> AccessList {
258 t.iter()
259 .map(|item| AccessListItem {
260 address: item.0,
261 storage_keys: item.1.clone(),
262 })
263 .collect::<Vec<AccessListItem>>()
264 };
265
266 Some(TransactionV3::EIP1559(EIP1559Transaction {
268 chain_id,
269 nonce,
270 max_fee_per_gas: U256::zero(),
271 max_priority_fee_per_gas: U256::zero(),
272 gas_limit: self.gas_limit,
273 action: self.action,
274 value: self.value,
275 input: self.input.to_vec(),
276 access_list: if let Some(ref access_list) = self.access_list {
277 from_tuple_to_access_list(access_list)
278 } else {
279 Vec::new()
280 },
281 signature: ethereum::eip1559::TransactionSignature::new(true, rs_id(), rs_id())?,
282 }))
283 }
284}
285
286impl XcmToEthereum for EthereumXcmTransactionV3 {
287 fn into_transaction(
288 &self,
289 nonce: U256,
290 chain_id: u64,
291 allow_create: bool,
292 ) -> Option<TransactionV3> {
293 if !allow_create && self.action == TransactionAction::Create {
294 return None;
296 }
297 let from_tuple_to_access_list = |t: &Vec<(H160, Vec<H256>)>| -> AccessList {
298 t.iter()
299 .map(|item| AccessListItem {
300 address: item.0,
301 storage_keys: item.1.clone(),
302 })
303 .collect::<Vec<AccessListItem>>()
304 };
305
306 Some(TransactionV3::EIP1559(EIP1559Transaction {
308 chain_id,
309 nonce,
310 max_fee_per_gas: U256::zero(),
311 max_priority_fee_per_gas: U256::zero(),
312 gas_limit: self.gas_limit,
313 action: self.action,
314 value: self.value,
315 input: self.input.to_vec(),
316 access_list: if let Some(ref access_list) = self.access_list {
317 from_tuple_to_access_list(access_list)
318 } else {
319 Vec::new()
320 },
321 signature: ethereum::eip1559::TransactionSignature::new(true, rs_id(), rs_id())?,
322 }))
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329 #[test]
330 fn test_into_ethereum_tx_with_auto_fee_v1() {
331 let xcm_transaction = EthereumXcmTransactionV1 {
332 gas_limit: U256::one(),
333 fee_payment: EthereumXcmFee::Auto,
334 action: TransactionAction::Call(H160::default()),
335 value: U256::zero(),
336 input: BoundedVec::<u8, ConstU32<MAX_ETHEREUM_XCM_INPUT_SIZE>>::try_from(vec![1u8])
337 .unwrap(),
338 access_list: None,
339 };
340 let nonce = U256::zero();
341
342 let expected_tx = Some(TransactionV3::EIP1559(EIP1559Transaction {
343 chain_id: 111,
344 nonce,
345 max_fee_per_gas: U256::zero(),
346 max_priority_fee_per_gas: U256::zero(),
347 gas_limit: U256::one(),
348 action: TransactionAction::Call(H160::default()),
349 value: U256::zero(),
350 input: vec![1u8],
351 access_list: vec![],
352 signature: ethereum::eip1559::TransactionSignature::new(
353 true,
354 H256::from_low_u64_be(1u64),
355 H256::from_low_u64_be(1u64),
356 )
357 .unwrap(),
358 }));
359
360 assert_eq!(
361 xcm_transaction.into_transaction(nonce, 111, false),
362 expected_tx
363 );
364 }
365
366 #[test]
367 fn test_legacy_v1() {
368 let xcm_transaction = EthereumXcmTransactionV1 {
369 gas_limit: U256::one(),
370 fee_payment: EthereumXcmFee::Manual(ManualEthereumXcmFee {
371 gas_price: Some(U256::zero()),
372 max_fee_per_gas: None,
373 }),
374 action: TransactionAction::Call(H160::default()),
375 value: U256::zero(),
376 input: BoundedVec::<u8, ConstU32<MAX_ETHEREUM_XCM_INPUT_SIZE>>::try_from(vec![1u8])
377 .unwrap(),
378 access_list: None,
379 };
380 let nonce = U256::zero();
381 let expected_tx = Some(TransactionV3::Legacy(LegacyTransaction {
382 nonce,
383 gas_price: U256::zero(),
384 gas_limit: U256::one(),
385 action: TransactionAction::Call(H160::default()),
386 value: U256::zero(),
387 input: vec![1u8],
388 signature: ethereum::legacy::TransactionSignature::new(42, rs_id(), rs_id()).unwrap(),
389 }));
390
391 assert_eq!(
392 xcm_transaction.into_transaction(nonce, 111, false),
393 expected_tx
394 );
395 }
396 #[test]
397 fn test_eip_2930_v1() {
398 let access_list = Some(vec![(H160::default(), vec![H256::default()])]);
399 let from_tuple_to_access_list = |t: &Vec<(H160, Vec<H256>)>| -> AccessList {
400 t.iter()
401 .map(|item| AccessListItem {
402 address: item.0.clone(),
403 storage_keys: item.1.clone(),
404 })
405 .collect::<Vec<AccessListItem>>()
406 };
407
408 let xcm_transaction = EthereumXcmTransactionV1 {
409 gas_limit: U256::one(),
410 fee_payment: EthereumXcmFee::Manual(ManualEthereumXcmFee {
411 gas_price: Some(U256::zero()),
412 max_fee_per_gas: None,
413 }),
414 action: TransactionAction::Call(H160::default()),
415 value: U256::zero(),
416 input: BoundedVec::<u8, ConstU32<MAX_ETHEREUM_XCM_INPUT_SIZE>>::try_from(vec![1u8])
417 .unwrap(),
418 access_list: access_list.clone(),
419 };
420
421 let nonce = U256::zero();
422 let expected_tx = Some(TransactionV3::EIP2930(EIP2930Transaction {
423 chain_id: 111,
424 nonce,
425 gas_price: U256::zero(),
426 gas_limit: U256::one(),
427 action: TransactionAction::Call(H160::default()),
428 value: U256::zero(),
429 input: vec![1u8],
430 access_list: from_tuple_to_access_list(&access_list.unwrap()),
431 signature: ethereum::eip2930::TransactionSignature::new(
432 true,
433 H256::from_low_u64_be(1u64),
434 H256::from_low_u64_be(1u64),
435 )
436 .unwrap(),
437 }));
438
439 assert_eq!(
440 xcm_transaction.into_transaction(nonce, 111, false),
441 expected_tx
442 );
443 }
444
445 #[test]
446 fn test_eip1559_v2() {
447 let xcm_transaction = EthereumXcmTransactionV2 {
448 gas_limit: U256::one(),
449 action: TransactionAction::Call(H160::default()),
450 value: U256::zero(),
451 input: BoundedVec::<u8, ConstU32<MAX_ETHEREUM_XCM_INPUT_SIZE>>::try_from(vec![1u8])
452 .unwrap(),
453 access_list: None,
454 };
455 let nonce = U256::zero();
456 let expected_tx = Some(TransactionV3::EIP1559(EIP1559Transaction {
457 chain_id: 111,
458 nonce,
459 max_fee_per_gas: U256::zero(),
460 max_priority_fee_per_gas: U256::zero(),
461 gas_limit: U256::one(),
462 action: TransactionAction::Call(H160::default()),
463 value: U256::zero(),
464 input: vec![1u8],
465 access_list: vec![],
466 signature: ethereum::eip1559::TransactionSignature::new(
467 true,
468 H256::from_low_u64_be(1u64),
469 H256::from_low_u64_be(1u64),
470 )
471 .unwrap(),
472 }));
473
474 assert_eq!(
475 xcm_transaction.into_transaction(nonce, 111, false),
476 expected_tx
477 );
478 }
479}