aurora_engine_transactions/
lib.rs1#![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#[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
70pub 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 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 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]; 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 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}