Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/enveloped.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,25 @@ pub trait EnvelopedEncodable {
out
}

/// Returns the length of the encoded transaction.
///
/// This is the EIP-2718 encoded length: type_id (1 byte for typed txs) + RLP payload.
/// Matches geth's `tx.Size()` and reth/alloy's `encoded_length()`.
fn encoded_len(&self) -> usize {
let type_id_len = if self.type_id().is_some() { 1 } else { 0 };
type_id_len + self.payload_len()
}

/// Type Id of the transaction.
fn type_id(&self) -> Option<u8>;

/// Encode inner payload.
fn encode_payload(&self) -> BytesMut;

/// Returns the length of the RLP-encoded payload without the type byte.
fn payload_len(&self) -> usize {
self.encode_payload().len()
}
}

/// Decodable typed transactions.
Expand Down
33 changes: 33 additions & 0 deletions src/transaction/eip1559.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use ethereum_types::{H256, U256};
use rlp::{DecoderError, Rlp, RlpStream};
use sha3::{Digest, Keccak256};

use super::rlp_len::{rlp_h256_as_u256_len, rlp_list_len, RlpEncodableLen};
use crate::Bytes;

pub use super::eip2930::{AccessList, TransactionAction, TransactionSignature};
Expand Down Expand Up @@ -52,6 +53,23 @@ impl EIP1559Transaction {
access_list: self.access_list,
}
}

/// Non-allocating RLP-encoded length of this signed transaction.
pub fn rlp_len(&self) -> usize {
let payload = self.chain_id.rlp_len()
+ self.nonce.rlp_len()
+ self.max_priority_fee_per_gas.rlp_len()
+ self.max_fee_per_gas.rlp_len()
+ self.gas_limit.rlp_len()
+ self.action.rlp_len()
+ self.value.rlp_len()
+ self.input.rlp_len()
+ self.access_list.rlp_len()
+ self.signature.odd_y_parity().rlp_len()
+ rlp_h256_as_u256_len(self.signature.r())
+ rlp_h256_as_u256_len(self.signature.s());
rlp_list_len(payload)
}
}

impl rlp::Encodable for EIP1559Transaction {
Expand Down Expand Up @@ -120,6 +138,21 @@ impl EIP1559TransactionMessage {
out[1..].copy_from_slice(&encoded);
H256::from_slice(Keccak256::digest(&out).as_ref())
}

/// Returns the RLP-encoded length of this unsigned message without
/// allocating.
pub fn encoded_len(&self) -> usize {
let payload = self.chain_id.rlp_len()
+ self.nonce.rlp_len()
+ self.max_priority_fee_per_gas.rlp_len()
+ self.max_fee_per_gas.rlp_len()
+ self.gas_limit.rlp_len()
+ self.action.rlp_len()
+ self.value.rlp_len()
+ self.input.rlp_len()
+ self.access_list.rlp_len();
rlp_list_len(payload)
}
}

impl rlp::Encodable for EIP1559TransactionMessage {
Expand Down
56 changes: 52 additions & 4 deletions src/transaction/eip2930.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use ethereum_types::{Address, H256, U256};
use rlp::{DecoderError, Rlp, RlpStream};
use sha3::{Digest, Keccak256};

use super::rlp_len::{rlp_h256_as_u256_len, rlp_list_len, RlpEncodableLen};
use super::signature;
use crate::Bytes;

Expand Down Expand Up @@ -94,10 +95,8 @@ impl<'de> serde::Deserialize<'de> for TransactionSignature {
D: serde::de::Deserializer<'de>,
{
let unchecked = MalleableTransactionSignature::deserialize(deserializer)?;
Ok(
TransactionSignature::new(unchecked.odd_y_parity, unchecked.r, unchecked.s)
.ok_or(serde::de::Error::custom("invalid signature"))?,
)
TransactionSignature::new(unchecked.odd_y_parity, unchecked.r, unchecked.s)
.ok_or(serde::de::Error::custom("invalid signature"))
}
}

Expand Down Expand Up @@ -134,6 +133,25 @@ impl rlp::Decodable for AccessListItem {
}
}

impl AccessListItem {
/// Non-allocating RLP-encoded length.
///
/// Layout: `RLP_LIST(address‖RLP_LIST(storage_keys…))`
/// * address (H160) is always 21 bytes (1-byte prefix + 20 data).
/// * each storage key (H256) is always 33 bytes (1-byte prefix + 32 data).
pub fn rlp_len(&self) -> usize {
let payload = self.address.rlp_len() + self.storage_keys.rlp_len();
rlp_list_len(payload)
}
}

impl RlpEncodableLen for [AccessListItem] {
fn rlp_len(&self) -> usize {
let items_len: usize = self.iter().map(|item| item.rlp_len()).sum();
rlp_list_len(items_len)
}
}

pub type AccessList = Vec<AccessListItem>;

#[derive(Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -180,6 +198,22 @@ impl EIP2930Transaction {
access_list: self.access_list,
}
}

/// Non-allocating RLP-encoded length of this signed transaction.
pub fn rlp_len(&self) -> usize {
let payload = self.chain_id.rlp_len()
+ self.nonce.rlp_len()
+ self.gas_price.rlp_len()
+ self.gas_limit.rlp_len()
+ self.action.rlp_len()
+ self.value.rlp_len()
+ self.input.rlp_len()
+ self.access_list.rlp_len()
+ self.signature.odd_y_parity().rlp_len()
+ rlp_h256_as_u256_len(self.signature.r())
+ rlp_h256_as_u256_len(self.signature.s());
rlp_list_len(payload)
}
}

impl rlp::Encodable for EIP2930Transaction {
Expand Down Expand Up @@ -245,6 +279,20 @@ impl EIP2930TransactionMessage {
out[1..].copy_from_slice(&encoded);
H256::from_slice(Keccak256::digest(&out).as_ref())
}

/// Returns the RLP-encoded length of this unsigned message without
/// allocating.
pub fn encoded_len(&self) -> usize {
let payload = self.chain_id.rlp_len()
+ self.nonce.rlp_len()
+ self.gas_price.rlp_len()
+ self.gas_limit.rlp_len()
+ self.action.rlp_len()
+ self.value.rlp_len()
+ self.input.rlp_len()
+ self.access_list.rlp_len();
rlp_list_len(payload)
}
}

impl rlp::Encodable for EIP2930TransactionMessage {
Expand Down
53 changes: 53 additions & 0 deletions src/transaction/eip7702.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use k256::ecdsa::{RecoveryId, Signature, VerifyingKey};
use rlp::{DecoderError, Rlp, RlpStream};
use sha3::{Digest, Keccak256};

use super::rlp_len::{rlp_h256_as_u256_len, rlp_list_len, RlpEncodableLen};
use crate::Bytes;

pub use super::eip2930::{
Expand Down Expand Up @@ -161,6 +162,24 @@ impl AuthorizationListItem {
Err(AuthorizationError::InvalidPublicKey)
}
}

/// Non-allocating RLP-encoded length of this authorization item.
pub fn rlp_len(&self) -> usize {
let payload = self.chain_id.rlp_len()
+ self.address.rlp_len()
+ self.nonce.rlp_len()
+ self.signature.odd_y_parity.rlp_len()
+ rlp_h256_as_u256_len(&self.signature.r)
+ rlp_h256_as_u256_len(&self.signature.s);
rlp_list_len(payload)
}
}

impl RlpEncodableLen for [AuthorizationListItem] {
fn rlp_len(&self) -> usize {
let items_len: usize = self.iter().map(|item| item.rlp_len()).sum();
rlp_list_len(items_len)
}
}

pub type AuthorizationList = Vec<AuthorizationListItem>;
Expand Down Expand Up @@ -213,6 +232,24 @@ impl EIP7702Transaction {
authorization_list: self.authorization_list,
}
}

/// Non-allocating RLP-encoded length of this signed transaction.
pub fn rlp_len(&self) -> usize {
let payload = self.chain_id.rlp_len()
+ self.nonce.rlp_len()
+ self.max_priority_fee_per_gas.rlp_len()
+ self.max_fee_per_gas.rlp_len()
+ self.gas_limit.rlp_len()
+ self.destination.rlp_len()
+ self.value.rlp_len()
+ self.data.rlp_len()
+ self.access_list.rlp_len()
+ self.authorization_list.rlp_len()
+ self.signature.odd_y_parity().rlp_len()
+ rlp_h256_as_u256_len(self.signature.r())
+ rlp_h256_as_u256_len(self.signature.s());
rlp_list_len(payload)
}
}

impl rlp::Encodable for EIP7702Transaction {
Expand Down Expand Up @@ -284,6 +321,22 @@ impl EIP7702TransactionMessage {
out[1..].copy_from_slice(&encoded);
H256::from_slice(Keccak256::digest(&out).as_ref())
}

/// Returns the RLP-encoded length of this unsigned message without
/// allocating.
pub fn encoded_len(&self) -> usize {
let payload = self.chain_id.rlp_len()
+ self.nonce.rlp_len()
+ self.max_priority_fee_per_gas.rlp_len()
+ self.max_fee_per_gas.rlp_len()
+ self.gas_limit.rlp_len()
+ self.destination.rlp_len()
+ self.value.rlp_len()
+ self.data.rlp_len()
+ self.access_list.rlp_len()
+ self.authorization_list.rlp_len();
rlp_list_len(payload)
}
}

impl rlp::Encodable for EIP7702TransactionMessage {
Expand Down
53 changes: 49 additions & 4 deletions src/transaction/legacy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use ethereum_types::{H160, H256, U256};
use rlp::{DecoderError, Rlp, RlpStream};
use sha3::{Digest, Keccak256};

use super::rlp_len::{rlp_h256_as_u256_len, rlp_list_len, RlpEncodableLen};
use super::signature;
use crate::Bytes;

Expand Down Expand Up @@ -48,6 +49,20 @@ impl rlp::Decodable for TransactionAction {
}
}

impl TransactionAction {
/// Non-allocating RLP-encoded length.
///
/// `Call(address)` encodes as a 20-byte string (1-byte prefix + 20 data
/// bytes); `Create` encodes as the empty string (`0x80`, 1 byte).
#[inline]
pub fn rlp_len(&self) -> usize {
match self {
Self::Call(_) => 1 + 20,
Self::Create => 1,
}
}
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "with-scale",
Expand Down Expand Up @@ -183,10 +198,8 @@ impl<'de> serde::Deserialize<'de> for TransactionSignature {
{
let unchecked = TransactionSignatureUnchecked::deserialize(deserializer)?;

Ok(
TransactionSignature::new(unchecked.v, unchecked.r, unchecked.s)
.ok_or(serde::de::Error::custom("invalid signature"))?,
)
TransactionSignature::new(unchecked.v, unchecked.r, unchecked.s)
.ok_or(serde::de::Error::custom("invalid signature"))
}
}

Expand Down Expand Up @@ -227,6 +240,20 @@ impl LegacyTransaction {
chain_id: self.signature.chain_id(),
}
}

/// Non-allocating RLP-encoded length of this signed transaction.
pub fn rlp_len(&self) -> usize {
let payload = self.nonce.rlp_len()
+ self.gas_price.rlp_len()
+ self.gas_limit.rlp_len()
+ self.action.rlp_len()
+ self.value.rlp_len()
+ self.input.rlp_len()
+ self.signature.v().rlp_len()
+ rlp_h256_as_u256_len(self.signature.r())
+ rlp_h256_as_u256_len(self.signature.s());
rlp_list_len(payload)
}
}

impl rlp::Encodable for LegacyTransaction {
Expand Down Expand Up @@ -283,6 +310,24 @@ impl LegacyTransactionMessage {
pub fn hash(&self) -> H256 {
H256::from_slice(Keccak256::digest(rlp::encode(self)).as_ref())
}

/// Returns the RLP-encoded length of this unsigned message without
/// allocating.
pub fn encoded_len(&self) -> usize {
let common = self.nonce.rlp_len()
+ self.gas_price.rlp_len()
+ self.gas_limit.rlp_len()
+ self.action.rlp_len()
+ self.value.rlp_len()
+ self.input.rlp_len();
let payload = if let Some(chain_id) = self.chain_id {
// EIP-155: 6 common fields + chain_id + two zero-bytes
common + chain_id.rlp_len() + 1 + 1
} else {
common
};
rlp_list_len(payload)
}
}

impl rlp::Encodable for LegacyTransactionMessage {
Expand Down
Loading
Loading