primitives/transaction/
eth_transaction.rs

1use super::AuthorizationListItem;
2use crate::{
3    bytes::Bytes, transaction::AccessList, AccessListItem, Action,
4    AuthorizationList, SignedTransaction, Transaction,
5    TransactionWithSignature, TransactionWithSignatureSerializePart,
6};
7use cfx_types::{AddressWithSpace, H256, U256};
8use cfxkey::Address;
9use rlp::{Encodable, RlpStream};
10use rlp_derive::{RlpDecodable, RlpEncodable};
11use serde_derive::{Deserialize, Serialize};
12
13impl Eip155Transaction {
14    /// Fake sign phantom transactions.
15    // The signature is part of the hash input. This implementation
16    // ensures that phantom transactions whose fields are identical
17    // will have different hashes.
18    pub fn fake_sign_phantom(
19        self, from: AddressWithSpace,
20    ) -> SignedTransaction {
21        SignedTransaction {
22            transaction: TransactionWithSignature {
23                transaction: TransactionWithSignatureSerializePart {
24                    unsigned: Transaction::Ethereum(
25                        EthereumTransaction::Eip155(self),
26                    ),
27                    // we use sender address for `r` and `s` so that
28                    // phantom transactions with matching
29                    // fields from different senders
30                    // will have different hashes
31                    r: U256::from(from.address.as_ref()),
32                    s: U256::from(from.address.as_ref()),
33                    v: 0,
34                },
35                hash: H256::zero(),
36                rlp_size: None,
37            }
38            .compute_hash(),
39            sender: from.address,
40            public: None,
41        }
42    }
43
44    /// Fake sign call requests in `eth_call`.
45    // `fake_sign_phantom` will use zero signature when the sender is the
46    // zero address, and that will fail basic signature verification.
47    pub fn fake_sign_rpc(self, from: AddressWithSpace) -> SignedTransaction {
48        SignedTransaction {
49            transaction: TransactionWithSignature {
50                transaction: TransactionWithSignatureSerializePart {
51                    unsigned: Transaction::Ethereum(
52                        EthereumTransaction::Eip155(self),
53                    ),
54                    r: U256::one(),
55                    s: U256::one(),
56                    v: 0,
57                },
58                hash: H256::zero(),
59                rlp_size: None,
60            }
61            .compute_hash(),
62            sender: from.address,
63            public: None,
64        }
65    }
66}
67
68#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
69pub struct Eip155Transaction {
70    /// Nonce.
71    pub nonce: U256,
72    /// Gas price.
73    pub gas_price: U256,
74    /// Gas paid up front for transaction execution.
75    pub gas: U256,
76    /// Action, can be either call or contract create.
77    pub action: Action,
78    /// Transferred value.
79    pub value: U256,
80    /// The chain id of the transaction
81    pub chain_id: Option<u32>,
82    /// Transaction data.
83    pub data: Bytes,
84}
85
86impl Encodable for Eip155Transaction {
87    fn rlp_append(&self, s: &mut RlpStream) {
88        match self.chain_id {
89            Some(chain_id) => {
90                s.begin_list(9);
91                s.append(&self.nonce);
92                s.append(&self.gas_price);
93                s.append(&self.gas);
94                s.append(&self.action);
95                s.append(&self.value);
96                s.append(&self.data);
97                s.append(&chain_id);
98                s.append(&0u8);
99                s.append(&0u8);
100            }
101            None => {
102                s.begin_list(6);
103                s.append(&self.nonce);
104                s.append(&self.gas_price);
105                s.append(&self.gas);
106                s.append(&self.action);
107                s.append(&self.value);
108                s.append(&self.data);
109            }
110        }
111    }
112}
113
114#[derive(
115    Default,
116    Debug,
117    Clone,
118    PartialEq,
119    Eq,
120    Serialize,
121    Deserialize,
122    RlpEncodable,
123    RlpDecodable,
124)]
125pub struct Eip2930Transaction {
126    pub chain_id: u32,
127    pub nonce: U256,
128    pub gas_price: U256,
129    pub gas: U256,
130    pub action: Action,
131    pub value: U256,
132    pub data: Bytes,
133    pub access_list: Vec<AccessListItem>,
134}
135
136#[derive(
137    Default,
138    Debug,
139    Clone,
140    PartialEq,
141    Eq,
142    Serialize,
143    Deserialize,
144    RlpEncodable,
145    RlpDecodable,
146)]
147pub struct Eip1559Transaction {
148    pub chain_id: u32,
149    pub nonce: U256,
150    pub max_priority_fee_per_gas: U256,
151    pub max_fee_per_gas: U256,
152    pub gas: U256,
153    pub action: Action,
154    pub value: U256,
155    pub data: Bytes,
156    pub access_list: Vec<AccessListItem>,
157}
158
159#[derive(
160    Default,
161    Debug,
162    Clone,
163    PartialEq,
164    Eq,
165    Serialize,
166    Deserialize,
167    RlpEncodable,
168    RlpDecodable,
169)]
170pub struct Eip7702Transaction {
171    pub chain_id: u32,
172    pub nonce: U256,
173    pub max_priority_fee_per_gas: U256,
174    pub max_fee_per_gas: U256,
175    pub gas: U256,
176    pub destination: Address,
177    pub value: U256,
178    pub data: Bytes,
179    pub access_list: Vec<AccessListItem>,
180    pub authorization_list: Vec<AuthorizationListItem>,
181}
182
183#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
184pub enum EthereumTransaction {
185    Eip155(Eip155Transaction),
186    Eip1559(Eip1559Transaction),
187    Eip2930(Eip2930Transaction),
188    Eip7702(Eip7702Transaction),
189}
190use EthereumTransaction::*;
191
192impl EthereumTransaction {
193    pub fn fake_sign_rpc(self, from: AddressWithSpace) -> SignedTransaction {
194        SignedTransaction {
195            transaction: TransactionWithSignature {
196                transaction: TransactionWithSignatureSerializePart {
197                    unsigned: Transaction::Ethereum(self),
198                    r: U256::one(),
199                    s: U256::one(),
200                    v: 0,
201                },
202                hash: H256::zero(),
203                rlp_size: None,
204            }
205            .compute_hash(),
206            sender: from.address,
207            public: None,
208        }
209    }
210}
211
212macro_rules! eth_access_common_ref {
213    ($field:ident, $ty:ty) => {
214        pub fn $field(&self) -> &$ty {
215            match self {
216                EthereumTransaction::Eip155(tx) => &tx.$field,
217                EthereumTransaction::Eip2930(tx) => &tx.$field,
218                EthereumTransaction::Eip1559(tx) => &tx.$field,
219                EthereumTransaction::Eip7702(tx) => &tx.$field,
220            }
221        }
222    };
223}
224
225impl EthereumTransaction {
226    eth_access_common_ref!(gas, U256);
227
228    eth_access_common_ref!(data, Bytes);
229
230    eth_access_common_ref!(nonce, U256);
231
232    eth_access_common_ref!(value, U256);
233
234    pub fn action(&self) -> Action {
235        match self {
236            Eip155(tx) => tx.action,
237            Eip1559(tx) => tx.action,
238            Eip2930(tx) => tx.action,
239            Eip7702(tx) => Action::Call(tx.destination),
240        }
241    }
242
243    pub fn gas_price(&self) -> &U256 {
244        match self {
245            Eip155(tx) => &tx.gas_price,
246            Eip1559(tx) => &tx.max_fee_per_gas,
247            Eip2930(tx) => &tx.gas_price,
248            Eip7702(tx) => &tx.max_fee_per_gas,
249        }
250    }
251
252    pub fn max_priority_gas_price(&self) -> &U256 {
253        match self {
254            Eip155(tx) => &tx.gas_price,
255            Eip1559(tx) => &tx.max_priority_fee_per_gas,
256            Eip2930(tx) => &tx.gas_price,
257            Eip7702(tx) => &tx.max_priority_fee_per_gas,
258        }
259    }
260
261    pub fn chain_id(&self) -> Option<u32> {
262        match self {
263            Eip155(tx) => tx.chain_id,
264            Eip1559(tx) => Some(tx.chain_id),
265            Eip2930(tx) => Some(tx.chain_id),
266            Eip7702(tx) => Some(tx.chain_id),
267        }
268    }
269
270    pub fn nonce_mut(&mut self) -> &mut U256 {
271        match self {
272            Eip155(tx) => &mut tx.nonce,
273            Eip2930(tx) => &mut tx.nonce,
274            Eip1559(tx) => &mut tx.nonce,
275            Eip7702(tx) => &mut tx.nonce,
276        }
277    }
278
279    pub fn data_mut(&mut self) -> &mut Vec<u8> {
280        match self {
281            Eip155(tx) => &mut tx.data,
282            Eip2930(tx) => &mut tx.data,
283            Eip1559(tx) => &mut tx.data,
284            Eip7702(tx) => &mut tx.data,
285        }
286    }
287
288    pub fn access_list(&self) -> Option<&AccessList> {
289        match self {
290            Eip155(_tx) => None,
291            Eip2930(tx) => Some(&tx.access_list),
292            Eip1559(tx) => Some(&tx.access_list),
293            Eip7702(tx) => Some(&tx.access_list),
294        }
295    }
296
297    pub fn authorization_list(&self) -> Option<&AuthorizationList> {
298        if let Eip7702(tx) = self {
299            Some(&tx.authorization_list)
300        } else {
301            None
302        }
303    }
304}
305
306/// Replay protection logic for v part of transaction's signature
307pub mod eip155_signature {
308    /// Adds chain id into v
309    pub fn add_chain_replay_protection(v: u8, chain_id: Option<u64>) -> u64 {
310        v as u64
311            + if let Some(n) = chain_id {
312                35 + n * 2
313            } else {
314                27
315            }
316    }
317
318    /// Returns refined v
319    /// 0 if `v` would have been 27 under "Electrum" notation, 1 if 28 or 4 if
320    /// invalid.
321    pub fn extract_standard_v(v: u64) -> u8 {
322        match v {
323            v if v == 27 => 0,
324            v if v == 28 => 1,
325            v if v >= 35 => ((v - 1) % 2) as u8,
326            _ => 4,
327        }
328    }
329
330    pub fn extract_chain_id_from_legacy_v(v: u64) -> Option<u64> {
331        if v >= 35 {
332            Some((v - 35) / 2 as u64)
333        } else {
334            None
335        }
336    }
337}