Skip to content

Commit a531fc6

Browse files
committed
Add SponsorTransaction type
1 parent 15215c8 commit a531fc6

5 files changed

Lines changed: 140 additions & 1 deletion

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ alloy-consensus = { version = "1.0.37", default-features = false }
111111
alloy-genesis = { version = "1.0.37", default-features = false }
112112
alloy-rpc-types-txpool = { version = "1.0.37", default-features = false }
113113
alloy-sol-types = { version = "1.3.1", default-features = false }
114+
alloy-rlp = { version = "0.3.12", default-features = false }
114115

115116
revm-inspector = { version = "10.0.1" }
116117
# Core dependencies

crates/common/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ description = "Common utilities and constants for ev-reth"
1212
# Core dependencies
1313
serde = { workspace = true, features = ["derive"] }
1414
tracing.workspace = true
15+
alloy-consensus.workspace = true
16+
alloy-eips.workspace = true
17+
alloy-primitives.workspace = true
18+
alloy-rlp.workspace = true
1519

1620
[lints]
1721
workspace = true

crates/common/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! Common utilities and constants for ev-reth
22
33
pub mod constants;
4+
pub mod sponsor_transaction;
45

56
pub use constants::*;
7+
pub use sponsor_transaction::{SponsorTransaction, FEE_PAYER_SIGNATURE_MAGIC_BYTE, SPONSOR_TX_TYPE_ID};
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
use alloy_consensus::crypto::{secp256k1, RecoveryError};
2+
use alloy_eips::Typed2718;
3+
use alloy_primitives::{keccak256, Address, B256, ChainId, Signature};
4+
use alloy_rlp::{BufMut, Decodable, Encodable};
5+
use serde::{Deserialize, Serialize};
6+
7+
/// Sponsor transaction type byte (0x76).
8+
pub const SPONSOR_TX_TYPE_ID: u8 = 0x76;
9+
10+
/// Magic byte for fee payer signature hashing.
11+
pub const FEE_PAYER_SIGNATURE_MAGIC_BYTE: u8 = 0x78;
12+
13+
/// Sponsor transaction with fee payer commitment.
14+
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
15+
#[serde(rename_all = "camelCase")]
16+
pub struct SponsorTransaction {
17+
/// EIP-155 replay protection.
18+
pub chain_id: ChainId,
19+
/// Token used to pay fees.
20+
pub fee_token: Address,
21+
/// Fee payer signature over the sponsorship payload.
22+
pub fee_payer_signature: Signature,
23+
}
24+
25+
impl SponsorTransaction {
26+
/// Returns the transaction type byte.
27+
pub const fn tx_type() -> u8 {
28+
SPONSOR_TX_TYPE_ID
29+
}
30+
31+
/// Hash signed by the fee payer to sponsor this transaction.
32+
pub fn fee_payer_signature_hash(&self) -> B256 {
33+
let payload_length = self.chain_id.length() + self.fee_token.length();
34+
let mut buf = Vec::with_capacity(1 + rlp_header(payload_length).length_with_payload());
35+
36+
buf.put_u8(FEE_PAYER_SIGNATURE_MAGIC_BYTE);
37+
rlp_header(payload_length).encode(&mut buf);
38+
self.chain_id.encode(&mut buf);
39+
self.fee_token.encode(&mut buf);
40+
41+
keccak256(&buf)
42+
}
43+
44+
/// Recovers the fee payer address from the signature.
45+
pub fn recover_fee_payer(&self) -> Result<Address, RecoveryError> {
46+
secp256k1::recover_signer(&self.fee_payer_signature, self.fee_payer_signature_hash())
47+
}
48+
49+
fn rlp_encoded_fields_length(&self) -> usize {
50+
self.chain_id.length()
51+
+ self.fee_token.length()
52+
+ {
53+
let payload_length =
54+
self.fee_payer_signature.rlp_rs_len() + self.fee_payer_signature.v().length();
55+
rlp_header(payload_length).length_with_payload()
56+
}
57+
}
58+
59+
fn rlp_encode_fields(&self, out: &mut dyn BufMut) {
60+
self.chain_id.encode(out);
61+
self.fee_token.encode(out);
62+
63+
let payload_length =
64+
self.fee_payer_signature.rlp_rs_len() + self.fee_payer_signature.v().length();
65+
rlp_header(payload_length).encode(out);
66+
self.fee_payer_signature
67+
.write_rlp_vrs(out, self.fee_payer_signature.v());
68+
}
69+
}
70+
71+
impl Typed2718 for SponsorTransaction {
72+
fn ty(&self) -> u8 {
73+
Self::tx_type()
74+
}
75+
}
76+
77+
impl Encodable for SponsorTransaction {
78+
fn encode(&self, out: &mut dyn BufMut) {
79+
let payload_length = self.rlp_encoded_fields_length();
80+
rlp_header(payload_length).encode(out);
81+
self.rlp_encode_fields(out);
82+
}
83+
84+
fn length(&self) -> usize {
85+
let payload_length = self.rlp_encoded_fields_length();
86+
rlp_header(payload_length).length_with_payload()
87+
}
88+
}
89+
90+
impl Decodable for SponsorTransaction {
91+
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
92+
let header = alloy_rlp::Header::decode(buf)?;
93+
if !header.list {
94+
return Err(alloy_rlp::Error::UnexpectedString);
95+
}
96+
let remaining = buf.len();
97+
if header.payload_length > remaining {
98+
return Err(alloy_rlp::Error::InputTooShort);
99+
}
100+
101+
let chain_id = Decodable::decode(buf)?;
102+
let fee_token = Decodable::decode(buf)?;
103+
104+
let signature_header = alloy_rlp::Header::decode(buf)?;
105+
if buf.len() < signature_header.payload_length {
106+
return Err(alloy_rlp::Error::InputTooShort);
107+
}
108+
if !signature_header.list {
109+
return Err(alloy_rlp::Error::UnexpectedString);
110+
}
111+
let fee_payer_signature = Signature::decode_rlp_vrs(buf, bool::decode)?;
112+
113+
if buf.len() + header.payload_length != remaining {
114+
return Err(alloy_rlp::Error::UnexpectedLength);
115+
}
116+
117+
Ok(Self {
118+
chain_id,
119+
fee_token,
120+
fee_payer_signature,
121+
})
122+
}
123+
}
124+
125+
#[inline]
126+
fn rlp_header(payload_length: usize) -> alloy_rlp::Header {
127+
alloy_rlp::Header {
128+
list: true,
129+
payload_length,
130+
}
131+
}

docs/adr/ADR-0003-typed-transactions-sponsorship.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,9 @@ pub enum EvRethTxEnvelope {
8181
Eip1559(Signed<TxEip1559>),
8282
#[envelope(ty = 3)]
8383
Eip4844(Signed<TxEip4844>),
84+
/// EvReth sponsorship transaction (type 0x76)
8485
#[envelope(ty = 0x76, typed = SponsorTransaction)]
85-
Sponsor(SponsorSigned),
86+
Sponsorship(SponsorSigned),
8687
}
8788

8889
pub struct SponsorTransaction {

0 commit comments

Comments
 (0)