aurora_engine_transactions/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2#![forbid(unsafe_code)]
3
4use aurora_engine_types::types::{Address, Wei};
5use aurora_engine_types::{vec, Vec, H160, U256};
6use eip_2930::AccessTuple;
7use rlp::{Decodable, DecoderError, Rlp};
8
9pub mod backwards_compatibility;
10pub mod eip_1559;
11pub mod eip_2930;
12pub mod eip_4844;
13pub mod legacy;
14
15/// Typed Transaction Envelope (see `https://eips.ethereum.org/EIPS/eip-2718`)
16#[derive(Debug, Eq, PartialEq, Clone)]
17pub enum EthTransactionKind {
18    Legacy(legacy::LegacyEthSignedTransaction),
19    Eip2930(eip_2930::SignedTransaction2930),
20    Eip1559(eip_1559::SignedTransaction1559),
21}
22
23impl TryFrom<&[u8]> for EthTransactionKind {
24    type Error = Error;
25
26    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
27        if bytes.is_empty() {
28            Err(Error::EmptyInput)
29        } else if bytes[0] == eip_2930::TYPE_BYTE {
30            Ok(Self::Eip2930(eip_2930::SignedTransaction2930::decode(
31                &Rlp::new(&bytes[1..]),
32            )?))
33        } else if bytes[0] == eip_1559::TYPE_BYTE {
34            Ok(Self::Eip1559(eip_1559::SignedTransaction1559::decode(
35                &Rlp::new(&bytes[1..]),
36            )?))
37        } else if bytes[0] == eip_4844::TYPE_BYTE {
38            Err(Error::UnsupportedTransactionEip4844)
39        } else if bytes[0] <= 0x7f {
40            Err(Error::UnknownTransactionType)
41        } else if bytes[0] == 0xff {
42            Err(Error::ReservedSentinel)
43        } else {
44            let legacy = legacy::LegacyEthSignedTransaction::decode(&Rlp::new(bytes))?;
45            Ok(Self::Legacy(legacy))
46        }
47    }
48}
49
50impl From<&EthTransactionKind> for Vec<u8> {
51    fn from(tx: &EthTransactionKind) -> Self {
52        let mut stream = rlp::RlpStream::new();
53        match &tx {
54            EthTransactionKind::Legacy(tx) => {
55                stream.append(tx);
56            }
57            EthTransactionKind::Eip1559(tx) => {
58                stream.append(&eip_1559::TYPE_BYTE);
59                stream.append(tx);
60            }
61            EthTransactionKind::Eip2930(tx) => {
62                stream.append(&eip_2930::TYPE_BYTE);
63                stream.append(tx);
64            }
65        }
66        stream.out().to_vec()
67    }
68}
69
70/// A normalized Ethereum transaction which can be created from older
71/// transactions.
72pub struct NormalizedEthTransaction {
73    pub address: Address,
74    pub chain_id: Option<u64>,
75    pub nonce: U256,
76    pub gas_limit: U256,
77    pub max_priority_fee_per_gas: U256,
78    pub max_fee_per_gas: U256,
79    pub to: Option<Address>,
80    pub value: Wei,
81    pub data: Vec<u8>,
82    pub access_list: Vec<AccessTuple>,
83}
84
85impl TryFrom<EthTransactionKind> for NormalizedEthTransaction {
86    type Error = Error;
87
88    fn try_from(kind: EthTransactionKind) -> Result<Self, Self::Error> {
89        use EthTransactionKind::{Eip1559, Eip2930, Legacy};
90        Ok(match kind {
91            Legacy(tx) => Self {
92                address: tx.sender()?,
93                chain_id: tx.chain_id(),
94                nonce: tx.transaction.nonce,
95                gas_limit: tx.transaction.gas_limit,
96                max_priority_fee_per_gas: tx.transaction.gas_price,
97                max_fee_per_gas: tx.transaction.gas_price,
98                to: tx.transaction.to,
99                value: tx.transaction.value,
100                data: tx.transaction.data,
101                access_list: vec![],
102            },
103            Eip2930(tx) => Self {
104                address: tx.sender()?,
105                chain_id: Some(tx.transaction.chain_id),
106                nonce: tx.transaction.nonce,
107                gas_limit: tx.transaction.gas_limit,
108                max_priority_fee_per_gas: tx.transaction.gas_price,
109                max_fee_per_gas: tx.transaction.gas_price,
110                to: tx.transaction.to,
111                value: tx.transaction.value,
112                data: tx.transaction.data,
113                access_list: tx.transaction.access_list,
114            },
115            Eip1559(tx) => Self {
116                address: tx.sender()?,
117                chain_id: Some(tx.transaction.chain_id),
118                nonce: tx.transaction.nonce,
119                gas_limit: tx.transaction.gas_limit,
120                max_priority_fee_per_gas: tx.transaction.max_priority_fee_per_gas,
121                max_fee_per_gas: tx.transaction.max_fee_per_gas,
122                to: tx.transaction.to,
123                value: tx.transaction.value,
124                data: tx.transaction.data,
125                access_list: tx.transaction.access_list,
126            },
127        })
128    }
129}
130
131impl NormalizedEthTransaction {
132    #[allow(clippy::naive_bytecount)]
133    pub fn intrinsic_gas(&self, config: &aurora_evm::Config) -> Result<u64, Error> {
134        let is_contract_creation = self.to.is_none();
135
136        let base_gas = if is_contract_creation {
137            config.gas_transaction_create + init_code_cost(config, &self.data)?
138        } else {
139            config.gas_transaction_call
140        };
141
142        let num_zero_bytes = u64::try_from(self.data.iter().filter(|b| **b == 0).count())
143            .map_err(|_e| Error::IntegerConversion)?;
144        let gas_zero_bytes = config
145            .gas_transaction_zero_data
146            .checked_mul(num_zero_bytes)
147            .ok_or(Error::GasOverflow)?;
148
149        let data_len = u64::try_from(self.data.len()).map_err(|_e| Error::IntegerConversion)?;
150        let num_non_zero_bytes = data_len - num_zero_bytes;
151        let gas_non_zero_bytes = config
152            .gas_transaction_non_zero_data
153            .checked_mul(num_non_zero_bytes)
154            .ok_or(Error::GasOverflow)?;
155
156        let access_list_len =
157            u64::try_from(self.access_list.len()).map_err(|_e| Error::IntegerConversion)?;
158        let gas_access_list_address = config
159            .gas_access_list_address
160            .checked_mul(access_list_len)
161            .ok_or(Error::GasOverflow)?;
162
163        let gas_access_list_storage = config
164            .gas_access_list_storage_key
165            .checked_mul(
166                u64::try_from(
167                    self.access_list
168                        .iter()
169                        .map(|a| a.storage_keys.len())
170                        .sum::<usize>(),
171                )
172                .map_err(|_e| Error::IntegerConversion)?,
173            )
174            .ok_or(Error::GasOverflow)?;
175
176        base_gas
177            .checked_add(gas_zero_bytes)
178            .and_then(|gas| gas.checked_add(gas_non_zero_bytes))
179            .and_then(|gas| gas.checked_add(gas_access_list_address))
180            .and_then(|gas| gas.checked_add(gas_access_list_storage))
181            .ok_or(Error::GasOverflow)
182    }
183}
184
185fn init_code_cost(config: &aurora_evm::Config, data: &[u8]) -> Result<u64, Error> {
186    // As per EIP-3860:
187    // > We define initcode_cost(initcode) to equal INITCODE_WORD_COST * ceil(len(initcode) / 32).
188    // where INITCODE_WORD_COST is 2.
189    let init_code_cost = if config.max_initcode_size.is_some() {
190        2 * ((u64::try_from(data.len()).map_err(|_| Error::IntegerConversion)? + 31) / 32)
191    } else {
192        0
193    };
194
195    Ok(init_code_cost)
196}
197
198#[derive(Debug, Clone, Eq, PartialEq)]
199#[cfg_attr(feature = "serde", derive(serde::Serialize))]
200pub enum Error {
201    UnknownTransactionType,
202    EmptyInput,
203    // Per the EIP-2718 spec 0xff is a reserved value
204    ReservedSentinel,
205    InvalidV,
206    EcRecover,
207    GasOverflow,
208    IntegerConversion,
209    #[cfg_attr(feature = "serde", serde(serialize_with = "decoder_err_to_str"))]
210    RlpDecodeError(DecoderError),
211    UnsupportedTransactionEip4844,
212}
213
214#[cfg(feature = "serde")]
215fn decoder_err_to_str<S: serde::Serializer>(err: &DecoderError, ser: S) -> Result<S::Ok, S::Error> {
216    ser.serialize_str(&format!("{err:?}"))
217}
218
219impl Error {
220    #[must_use]
221    pub const fn as_str(&self) -> &str {
222        match self {
223            Self::UnknownTransactionType => "ERR_UNKNOWN_TX_TYPE",
224            Self::EmptyInput => "ERR_EMPTY_TX_INPUT",
225            Self::ReservedSentinel => "ERR_RESERVED_LEADING_TX_BYTE",
226            Self::InvalidV => "ERR_INVALID_V",
227            Self::EcRecover => "ERR_ECRECOVER",
228            Self::GasOverflow => "ERR_GAS_OVERFLOW",
229            Self::IntegerConversion => "ERR_INTEGER_CONVERSION",
230            Self::RlpDecodeError(_) => "ERR_TX_RLP_DECODE",
231            Self::UnsupportedTransactionEip4844 => "ERR_UNSUPPORTED_TX_EIP4844",
232        }
233    }
234}
235
236impl From<DecoderError> for Error {
237    fn from(e: DecoderError) -> Self {
238        Self::RlpDecodeError(e)
239    }
240}
241
242impl AsRef<[u8]> for Error {
243    fn as_ref(&self) -> &[u8] {
244        self.as_str().as_bytes()
245    }
246}
247
248fn rlp_extract_to(rlp: &Rlp<'_>, index: usize) -> Result<Option<Address>, DecoderError> {
249    let value = rlp.at(index)?;
250    if value.is_empty() {
251        if value.is_data() {
252            Ok(None)
253        } else {
254            Err(DecoderError::RlpExpectedToBeData)
255        }
256    } else {
257        let v: H160 = value.as_val()?;
258        let addr = Address::new(v);
259        Ok(Some(addr))
260    }
261}
262
263fn vrs_to_arr(v: u8, r: U256, s: U256) -> [u8; 65] {
264    let mut result = [0u8; 65]; // (r, s, v), typed (uint256, uint256, uint8)
265    result[..32].copy_from_slice(&r.to_big_endian());
266    result[32..64].copy_from_slice(&s.to_big_endian());
267    result[64] = v;
268    result
269}
270
271#[cfg(test)]
272mod tests {
273    use super::{Error, EthTransactionKind};
274    use crate::{eip_1559, eip_2930};
275
276    #[test]
277    fn test_try_parse_empty_input() {
278        assert!(matches!(
279            EthTransactionKind::try_from([].as_ref()),
280            Err(Error::EmptyInput)
281        ));
282
283        // If the first byte is present, then empty bytes will be passed in to
284        // the RLP parsing. Let's also check this is not a problem.
285        assert!(matches!(
286            EthTransactionKind::try_from([eip_1559::TYPE_BYTE].as_ref()),
287            Err(Error::RlpDecodeError(_))
288        ));
289        assert!(matches!(
290            EthTransactionKind::try_from([eip_2930::TYPE_BYTE].as_ref()),
291            Err(Error::RlpDecodeError(_))
292        ));
293        assert!(matches!(
294            EthTransactionKind::try_from([0x80].as_ref()),
295            Err(Error::RlpDecodeError(_))
296        ));
297    }
298}