1use eth_valkyoth_codec::{
2 DecodeError, checked_len_add, encode_rlp_integer, encode_rlp_list_header, encode_rlp_scalar,
3 encoded_rlp_integer_len, encoded_rlp_list_header_len, encoded_rlp_scalar_len,
4};
5use eth_valkyoth_primitives::Address;
6
7use super::access_list::ACCESS_LIST_TRANSACTION_TYPE;
8use super::blob::BLOB_TRANSACTION_TYPE;
9use super::dynamic_fee::DYNAMIC_FEE_TRANSACTION_TYPE;
10use super::{
11 AccessListTransactionTo, BlobVersionedHashes, UnvalidatedAccessListTransaction,
12 UnvalidatedBlobTransaction, UnvalidatedDynamicFeeTransaction, UnvalidatedLegacyTransaction,
13};
14use crate::transaction::LegacyTransactionTo;
15
16mod error;
17#[cfg(test)]
18mod tests;
19
20pub use error::{TransactionEncodeError, TransactionEncodeErrorCategory};
21
22const U64_BYTES: usize = 8;
23const U256_BYTES: usize = 32;
24
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
27pub enum UnvalidatedTransaction<'a> {
28 Legacy(UnvalidatedLegacyTransaction<'a>),
30 AccessList(UnvalidatedAccessListTransaction<'a>),
32 DynamicFee(UnvalidatedDynamicFeeTransaction<'a>),
34 Blob(UnvalidatedBlobTransaction<'a>),
36}
37
38pub fn encoded_transaction_len(
40 transaction: UnvalidatedTransaction<'_>,
41) -> Result<usize, TransactionEncodeError> {
42 match transaction {
43 UnvalidatedTransaction::Legacy(tx) => encoded_legacy_transaction_len(&tx),
44 UnvalidatedTransaction::AccessList(tx) => encoded_access_list_transaction_len(&tx),
45 UnvalidatedTransaction::DynamicFee(tx) => encoded_dynamic_fee_transaction_len(&tx),
46 UnvalidatedTransaction::Blob(tx) => encoded_blob_transaction_len(&tx),
47 }
48}
49
50pub fn encode_transaction(
57 transaction: UnvalidatedTransaction<'_>,
58 output: &mut [u8],
59) -> Result<usize, TransactionEncodeError> {
60 match transaction {
61 UnvalidatedTransaction::Legacy(tx) => encode_legacy_transaction(&tx, output),
62 UnvalidatedTransaction::AccessList(tx) => encode_access_list_transaction(&tx, output),
63 UnvalidatedTransaction::DynamicFee(tx) => encode_dynamic_fee_transaction(&tx, output),
64 UnvalidatedTransaction::Blob(tx) => encode_blob_transaction(&tx, output),
65 }
66}
67
68pub fn encoded_legacy_transaction_len(
70 transaction: &UnvalidatedLegacyTransaction<'_>,
71) -> Result<usize, TransactionEncodeError> {
72 encoded_list_envelope_len(legacy_payload_len(transaction)?).map_err(Into::into)
73}
74
75pub fn encode_legacy_transaction(
77 transaction: &UnvalidatedLegacyTransaction<'_>,
78 output: &mut [u8],
79) -> Result<usize, TransactionEncodeError> {
80 let payload_len = legacy_payload_len(transaction)?;
81 encode_list_envelope(payload_len, output, |fields| {
82 write_u64(transaction.nonce.get(), fields)?;
83 write_wei(transaction.gas_price.to_be_bytes(), fields)?;
84 write_u64(transaction.gas_limit.get(), fields)?;
85 write_legacy_to(transaction.to, fields)?;
86 write_wei(transaction.value.to_be_bytes(), fields)?;
87 write_scalar(transaction.input, fields)?;
88 write_u256(transaction.v, fields)?;
89 write_u256(transaction.r, fields)?;
90 write_u256(transaction.s, fields)
91 })
92 .map_err(Into::into)
93}
94
95pub fn encoded_access_list_transaction_len(
97 transaction: &UnvalidatedAccessListTransaction<'_>,
98) -> Result<usize, TransactionEncodeError> {
99 encoded_typed_envelope_len(access_list_payload_len(transaction)?).map_err(Into::into)
100}
101
102pub fn encode_access_list_transaction(
104 transaction: &UnvalidatedAccessListTransaction<'_>,
105 output: &mut [u8],
106) -> Result<usize, TransactionEncodeError> {
107 let payload_len = access_list_payload_len(transaction)?;
108 encode_typed_envelope(
109 ACCESS_LIST_TRANSACTION_TYPE,
110 payload_len,
111 output,
112 |fields| {
113 write_u64(transaction.chain_id.get(), fields)?;
114 write_u64(transaction.nonce.get(), fields)?;
115 write_wei(transaction.gas_price.to_be_bytes(), fields)?;
116 write_u64(transaction.gas_limit.get(), fields)?;
117 write_transaction_to(transaction.to, fields)?;
118 write_wei(transaction.value.to_be_bytes(), fields)?;
119 write_scalar(transaction.input, fields)?;
120 write_access_list(transaction.access_list, fields)?;
121 write_u64(u64::from(transaction.y_parity.get()), fields)?;
122 write_u256(transaction.r, fields)?;
123 write_u256(transaction.s, fields)
124 },
125 )
126 .map_err(Into::into)
127}
128
129pub fn encoded_dynamic_fee_transaction_len(
131 transaction: &UnvalidatedDynamicFeeTransaction<'_>,
132) -> Result<usize, TransactionEncodeError> {
133 encoded_typed_envelope_len(dynamic_fee_payload_len(transaction)?).map_err(Into::into)
134}
135
136pub fn encode_dynamic_fee_transaction(
138 transaction: &UnvalidatedDynamicFeeTransaction<'_>,
139 output: &mut [u8],
140) -> Result<usize, TransactionEncodeError> {
141 let payload_len = dynamic_fee_payload_len(transaction)?;
142 encode_typed_envelope(
143 DYNAMIC_FEE_TRANSACTION_TYPE,
144 payload_len,
145 output,
146 |fields| {
147 write_u64(transaction.chain_id.get(), fields)?;
148 write_u64(transaction.nonce.get(), fields)?;
149 write_wei(transaction.max_priority_fee_per_gas.to_be_bytes(), fields)?;
150 write_wei(transaction.max_fee_per_gas.to_be_bytes(), fields)?;
151 write_u64(transaction.gas_limit.get(), fields)?;
152 write_transaction_to(transaction.to, fields)?;
153 write_wei(transaction.value.to_be_bytes(), fields)?;
154 write_scalar(transaction.input, fields)?;
155 write_access_list(transaction.access_list, fields)?;
156 write_u64(u64::from(transaction.y_parity.get()), fields)?;
157 write_u256(transaction.r, fields)?;
158 write_u256(transaction.s, fields)
159 },
160 )
161 .map_err(Into::into)
162}
163
164pub fn encoded_blob_transaction_len(
166 transaction: &UnvalidatedBlobTransaction<'_>,
167) -> Result<usize, TransactionEncodeError> {
168 encoded_typed_envelope_len(blob_payload_len(transaction)?).map_err(Into::into)
169}
170
171pub fn encode_blob_transaction(
173 transaction: &UnvalidatedBlobTransaction<'_>,
174 output: &mut [u8],
175) -> Result<usize, TransactionEncodeError> {
176 let payload_len = blob_payload_len(transaction)?;
177 encode_typed_envelope(BLOB_TRANSACTION_TYPE, payload_len, output, |fields| {
178 write_u64(transaction.chain_id.get(), fields)?;
179 write_u64(transaction.nonce.get(), fields)?;
180 write_wei(transaction.max_priority_fee_per_gas.to_be_bytes(), fields)?;
181 write_wei(transaction.max_fee_per_gas.to_be_bytes(), fields)?;
182 write_u64(transaction.gas_limit.get(), fields)?;
183 write_address(transaction.to, fields)?;
184 write_wei(transaction.value.to_be_bytes(), fields)?;
185 write_scalar(transaction.input, fields)?;
186 write_access_list(transaction.access_list, fields)?;
187 write_wei(transaction.max_fee_per_blob_gas.to_be_bytes(), fields)?;
188 write_blob_hashes(transaction.blob_versioned_hashes, fields)?;
189 write_u64(u64::from(transaction.y_parity.get()), fields)?;
190 write_u256(transaction.r, fields)?;
191 write_u256(transaction.s, fields)
192 })
193 .map_err(Into::into)
194}
195
196fn legacy_payload_len(tx: &UnvalidatedLegacyTransaction<'_>) -> Result<usize, DecodeError> {
197 sum_lengths(&[
198 encoded_u64_len(tx.nonce.get())?,
199 encoded_u256_len(tx.gas_price.to_be_bytes())?,
200 encoded_u64_len(tx.gas_limit.get())?,
201 encoded_legacy_to_len(tx.to)?,
202 encoded_u256_len(tx.value.to_be_bytes())?,
203 encoded_rlp_scalar_len(tx.input)?,
204 encoded_u256_len(tx.v)?,
205 encoded_u256_len(tx.r)?,
206 encoded_u256_len(tx.s)?,
207 ])
208}
209
210fn access_list_payload_len(
211 tx: &UnvalidatedAccessListTransaction<'_>,
212) -> Result<usize, DecodeError> {
213 sum_lengths(&[
214 encoded_u64_len(tx.chain_id.get())?,
215 encoded_u64_len(tx.nonce.get())?,
216 encoded_u256_len(tx.gas_price.to_be_bytes())?,
217 encoded_u64_len(tx.gas_limit.get())?,
218 encoded_access_list_to_len(tx.to)?,
219 encoded_u256_len(tx.value.to_be_bytes())?,
220 encoded_rlp_scalar_len(tx.input)?,
221 tx.access_list.encoded_rlp_len(),
222 encoded_u64_len(u64::from(tx.y_parity.get()))?,
223 encoded_u256_len(tx.r)?,
224 encoded_u256_len(tx.s)?,
225 ])
226}
227
228fn dynamic_fee_payload_len(
229 tx: &UnvalidatedDynamicFeeTransaction<'_>,
230) -> Result<usize, DecodeError> {
231 sum_lengths(&[
232 encoded_u64_len(tx.chain_id.get())?,
233 encoded_u64_len(tx.nonce.get())?,
234 encoded_u256_len(tx.max_priority_fee_per_gas.to_be_bytes())?,
235 encoded_u256_len(tx.max_fee_per_gas.to_be_bytes())?,
236 encoded_u64_len(tx.gas_limit.get())?,
237 encoded_access_list_to_len(tx.to)?,
238 encoded_u256_len(tx.value.to_be_bytes())?,
239 encoded_rlp_scalar_len(tx.input)?,
240 tx.access_list.encoded_rlp_len(),
241 encoded_u64_len(u64::from(tx.y_parity.get()))?,
242 encoded_u256_len(tx.r)?,
243 encoded_u256_len(tx.s)?,
244 ])
245}
246
247fn blob_payload_len(tx: &UnvalidatedBlobTransaction<'_>) -> Result<usize, DecodeError> {
248 sum_lengths(&[
249 encoded_u64_len(tx.chain_id.get())?,
250 encoded_u64_len(tx.nonce.get())?,
251 encoded_u256_len(tx.max_priority_fee_per_gas.to_be_bytes())?,
252 encoded_u256_len(tx.max_fee_per_gas.to_be_bytes())?,
253 encoded_u64_len(tx.gas_limit.get())?,
254 encoded_rlp_scalar_len(&tx.to.to_bytes())?,
255 encoded_u256_len(tx.value.to_be_bytes())?,
256 encoded_rlp_scalar_len(tx.input)?,
257 tx.access_list.encoded_rlp_len(),
258 encoded_u256_len(tx.max_fee_per_blob_gas.to_be_bytes())?,
259 tx.blob_versioned_hashes.encoded_rlp_len(),
260 encoded_u64_len(u64::from(tx.y_parity.get()))?,
261 encoded_u256_len(tx.r)?,
262 encoded_u256_len(tx.s)?,
263 ])
264}
265
266fn encoded_list_envelope_len(payload_len: usize) -> Result<usize, DecodeError> {
267 checked_len_add(encoded_rlp_list_header_len(payload_len)?, payload_len)
268}
269
270fn encoded_typed_envelope_len(payload_len: usize) -> Result<usize, DecodeError> {
271 checked_len_add(1, encoded_list_envelope_len(payload_len)?)
272}
273
274fn encode_list_envelope(
275 payload_len: usize,
276 output: &mut [u8],
277 write_fields: impl FnOnce(&mut FieldWriter<'_>) -> Result<(), DecodeError>,
278) -> Result<usize, DecodeError> {
279 let total_len = encoded_list_envelope_len(payload_len)?;
280 if output.len() < total_len {
281 return Err(DecodeError::OffsetOutOfBounds);
282 }
283 let header_len = encoded_rlp_list_header_len(payload_len)?;
284 let fields = output
285 .get_mut(header_len..total_len)
286 .ok_or(DecodeError::OffsetOutOfBounds)?;
287 let mut writer = FieldWriter::new(fields);
288 write_fields(&mut writer)?;
289 writer.finish(payload_len)?;
290 encode_rlp_list_header(payload_len, output)?;
291 Ok(total_len)
292}
293
294fn encode_typed_envelope(
295 transaction_type: u8,
296 payload_len: usize,
297 output: &mut [u8],
298 write_fields: impl FnOnce(&mut FieldWriter<'_>) -> Result<(), DecodeError>,
299) -> Result<usize, DecodeError> {
300 let payload_total_len = encoded_list_envelope_len(payload_len)?;
301 let total_len = checked_len_add(1, payload_total_len)?;
302 if output.len() < total_len {
303 return Err(DecodeError::OffsetOutOfBounds);
304 }
305 let payload_output = output
306 .get_mut(1..total_len)
307 .ok_or(DecodeError::OffsetOutOfBounds)?;
308 encode_list_envelope(payload_len, payload_output, write_fields)?;
309 let type_byte = output.first_mut().ok_or(DecodeError::OffsetOutOfBounds)?;
310 *type_byte = transaction_type;
311 Ok(total_len)
312}
313
314fn encoded_u64_len(value: u64) -> Result<usize, DecodeError> {
315 let bytes = value.to_be_bytes();
316 encoded_rlp_integer_len(trim_u64_payload(&bytes))
317}
318
319fn encoded_u256_len(bytes: [u8; U256_BYTES]) -> Result<usize, DecodeError> {
320 encoded_rlp_integer_len(trim_u256_payload(&bytes))
321}
322
323fn encoded_legacy_to_len(to: LegacyTransactionTo) -> Result<usize, DecodeError> {
324 match to {
325 LegacyTransactionTo::Create => encoded_rlp_scalar_len(&[]),
326 LegacyTransactionTo::Call(address) => encoded_rlp_scalar_len(&address.to_bytes()),
327 }
328}
329
330fn encoded_access_list_to_len(to: AccessListTransactionTo) -> Result<usize, DecodeError> {
331 match to {
332 AccessListTransactionTo::Create => encoded_rlp_scalar_len(&[]),
333 AccessListTransactionTo::Call(address) => encoded_rlp_scalar_len(&address.to_bytes()),
334 }
335}
336
337fn sum_lengths(lengths: &[usize]) -> Result<usize, DecodeError> {
338 let mut total = 0usize;
339 for length in lengths {
340 total = checked_len_add(total, *length)?;
341 }
342 Ok(total)
343}
344
345struct FieldWriter<'a> {
346 output: &'a mut [u8],
347 cursor: usize,
348}
349
350impl<'a> FieldWriter<'a> {
351 fn new(output: &'a mut [u8]) -> Self {
352 Self { output, cursor: 0 }
353 }
354
355 fn write_with(
356 &mut self,
357 write: impl FnOnce(&mut [u8]) -> Result<usize, DecodeError>,
358 ) -> Result<(), DecodeError> {
359 let target = self
360 .output
361 .get_mut(self.cursor..)
362 .ok_or(DecodeError::OffsetOutOfBounds)?;
363 let written = write(target)?;
364 self.cursor = checked_len_add(self.cursor, written)?;
365 Ok(())
366 }
367
368 fn finish(self, expected_len: usize) -> Result<(), DecodeError> {
369 if self.cursor == expected_len {
370 Ok(())
371 } else {
372 Err(DecodeError::DecoderOverread)
373 }
374 }
375}
376
377fn write_u64(value: u64, writer: &mut FieldWriter<'_>) -> Result<(), DecodeError> {
378 let bytes = value.to_be_bytes();
379 writer.write_with(|output| encode_rlp_integer(trim_u64_payload(&bytes), output))
380}
381
382fn write_u256(bytes: [u8; U256_BYTES], writer: &mut FieldWriter<'_>) -> Result<(), DecodeError> {
383 writer.write_with(|output| encode_rlp_integer(trim_u256_payload(&bytes), output))
384}
385
386fn write_wei(bytes: [u8; U256_BYTES], writer: &mut FieldWriter<'_>) -> Result<(), DecodeError> {
387 write_u256(bytes, writer)
388}
389
390fn write_scalar(payload: &[u8], writer: &mut FieldWriter<'_>) -> Result<(), DecodeError> {
391 writer.write_with(|output| encode_rlp_scalar(payload, output))
392}
393
394fn write_address(address: Address, writer: &mut FieldWriter<'_>) -> Result<(), DecodeError> {
395 write_scalar(&address.to_bytes(), writer)
396}
397
398fn write_legacy_to(
399 to: LegacyTransactionTo,
400 writer: &mut FieldWriter<'_>,
401) -> Result<(), DecodeError> {
402 match to {
403 LegacyTransactionTo::Create => write_scalar(&[], writer),
404 LegacyTransactionTo::Call(address) => write_address(address, writer),
405 }
406}
407
408fn write_transaction_to(
409 to: AccessListTransactionTo,
410 writer: &mut FieldWriter<'_>,
411) -> Result<(), DecodeError> {
412 match to {
413 AccessListTransactionTo::Create => write_scalar(&[], writer),
414 AccessListTransactionTo::Call(address) => write_address(address, writer),
415 }
416}
417
418fn write_access_list(
419 access_list: super::AccessList<'_>,
420 writer: &mut FieldWriter<'_>,
421) -> Result<(), DecodeError> {
422 writer.write_with(|output| access_list.encode_rlp(output))
423}
424
425fn write_blob_hashes(
426 hashes: BlobVersionedHashes<'_>,
427 writer: &mut FieldWriter<'_>,
428) -> Result<(), DecodeError> {
429 writer.write_with(|output| hashes.encode_rlp(output))
430}
431
432fn trim_u64_payload(bytes: &[u8; U64_BYTES]) -> &[u8] {
433 let start = bytes
434 .iter()
435 .position(|byte| *byte != 0)
436 .unwrap_or(U64_BYTES);
437 bytes.get(start..).unwrap_or(&[])
438}
439
440fn trim_u256_payload(bytes: &[u8; U256_BYTES]) -> &[u8] {
441 let start = bytes
442 .iter()
443 .position(|byte| *byte != 0)
444 .unwrap_or(U256_BYTES);
445 bytes.get(start..).unwrap_or(&[])
446}