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