1use crate::TempoInvalidTransaction;
2use alloy_consensus::{EthereumTxEnvelope, TxEip4844, Typed2718, crypto::secp256k1};
3use alloy_evm::{FromRecoveredTx, FromTxWithEncoded, IntoTxEnv};
4use alloy_primitives::{Address, B256, Bytes, TxKind, U256};
5use reth_evm::TransactionEnv;
6use revm::context::{
7 Transaction, TxEnv,
8 either::Either,
9 result::InvalidTransaction,
10 transaction::{
11 AccessList, AccessListItem, RecoveredAuthority, RecoveredAuthorization, SignedAuthorization,
12 },
13};
14use tempo_primitives::{
15 AASigned, TempoSignature, TempoTransaction, TempoTxEnvelope, TxFeeToken,
16 transaction::{
17 Call, RecoveredTempoAuthorization, SignedKeyAuthorization, calc_gas_balance_spending,
18 },
19};
20
21#[derive(Debug, Clone, Default)]
23pub struct TempoBatchCallEnv {
24 pub signature: TempoSignature,
26
27 pub valid_before: Option<u64>,
29
30 pub valid_after: Option<u64>,
32
33 pub aa_calls: Vec<Call>,
35
36 pub tempo_authorization_list: Vec<RecoveredTempoAuthorization>,
41
42 pub nonce_key: U256,
44
45 pub subblock_transaction: bool,
47
48 pub key_authorization: Option<SignedKeyAuthorization>,
50
51 pub signature_hash: B256,
53}
54#[derive(Debug, Clone, Default, derive_more::Deref, derive_more::DerefMut)]
56pub struct TempoTxEnv {
57 #[deref]
59 #[deref_mut]
60 pub inner: TxEnv,
61
62 pub fee_token: Option<Address>,
64
65 pub is_system_tx: bool,
67
68 pub fee_payer: Option<Option<Address>>,
74
75 pub tempo_tx_env: Option<Box<TempoBatchCallEnv>>,
77}
78
79impl TempoTxEnv {
80 pub fn fee_payer(&self) -> Result<Address, TempoInvalidTransaction> {
82 if let Some(fee_payer) = self.fee_payer {
83 fee_payer.ok_or(TempoInvalidTransaction::InvalidFeePayerSignature)
84 } else {
85 Ok(self.caller())
86 }
87 }
88
89 pub fn is_subblock_transaction(&self) -> bool {
91 self.tempo_tx_env
92 .as_ref()
93 .is_some_and(|aa| aa.subblock_transaction)
94 }
95
96 pub fn first_call(&self) -> Option<(&TxKind, &[u8])> {
98 if let Some(aa) = self.tempo_tx_env.as_ref() {
99 aa.aa_calls
100 .first()
101 .map(|call| (&call.to, call.input.as_ref()))
102 } else {
103 Some((&self.inner.kind, &self.inner.data))
104 }
105 }
106
107 pub fn calls(&self) -> impl Iterator<Item = (&TxKind, &[u8])> {
110 if let Some(aa) = self.tempo_tx_env.as_ref() {
111 Either::Left(
112 aa.aa_calls
113 .iter()
114 .map(|call| (&call.to, call.input.as_ref())),
115 )
116 } else {
117 Either::Right(core::iter::once((
118 &self.inner.kind,
119 self.inner.input().as_ref(),
120 )))
121 }
122 }
123}
124
125impl From<TxEnv> for TempoTxEnv {
126 fn from(inner: TxEnv) -> Self {
127 Self {
128 inner,
129 ..Default::default()
130 }
131 }
132}
133
134impl Transaction for TempoTxEnv {
135 type AccessListItem<'a> = &'a AccessListItem;
136 type Authorization<'a> = &'a Either<SignedAuthorization, RecoveredAuthorization>;
137
138 fn tx_type(&self) -> u8 {
139 self.inner.tx_type()
140 }
141
142 fn kind(&self) -> TxKind {
143 self.inner.kind()
144 }
145
146 fn caller(&self) -> Address {
147 self.inner.caller()
148 }
149
150 fn gas_limit(&self) -> u64 {
151 self.inner.gas_limit()
152 }
153
154 fn gas_price(&self) -> u128 {
155 self.inner.gas_price()
156 }
157
158 fn value(&self) -> U256 {
159 self.inner.value()
160 }
161
162 fn nonce(&self) -> u64 {
163 Transaction::nonce(&self.inner)
164 }
165
166 fn chain_id(&self) -> Option<u64> {
167 self.inner.chain_id()
168 }
169
170 fn access_list(&self) -> Option<impl Iterator<Item = Self::AccessListItem<'_>>> {
171 self.inner.access_list()
172 }
173
174 fn max_fee_per_gas(&self) -> u128 {
175 self.inner.max_fee_per_gas()
176 }
177
178 fn max_fee_per_blob_gas(&self) -> u128 {
179 self.inner.max_fee_per_blob_gas()
180 }
181
182 fn authorization_list_len(&self) -> usize {
183 self.inner.authorization_list_len()
184 }
185
186 fn authorization_list(&self) -> impl Iterator<Item = Self::Authorization<'_>> {
187 self.inner.authorization_list()
188 }
189
190 fn input(&self) -> &Bytes {
191 self.inner.input()
192 }
193
194 fn blob_versioned_hashes(&self) -> &[B256] {
195 self.inner.blob_versioned_hashes()
196 }
197
198 fn max_priority_fee_per_gas(&self) -> Option<u128> {
199 self.inner.max_priority_fee_per_gas()
200 }
201
202 fn max_balance_spending(&self) -> Result<U256, InvalidTransaction> {
203 calc_gas_balance_spending(self.gas_limit(), self.max_fee_per_gas())
204 .checked_add(self.value())
205 .ok_or(InvalidTransaction::OverflowPaymentInTransaction)
206 }
207
208 fn effective_balance_spending(
209 &self,
210 base_fee: u128,
211 _blob_price: u128,
212 ) -> Result<U256, InvalidTransaction> {
213 calc_gas_balance_spending(self.gas_limit(), self.effective_gas_price(base_fee))
214 .checked_add(self.value())
215 .ok_or(InvalidTransaction::OverflowPaymentInTransaction)
216 }
217}
218
219impl TransactionEnv for TempoTxEnv {
220 fn set_gas_limit(&mut self, gas_limit: u64) {
221 self.inner.set_gas_limit(gas_limit);
222 }
223
224 fn nonce(&self) -> u64 {
225 Transaction::nonce(&self.inner)
226 }
227
228 fn set_nonce(&mut self, nonce: u64) {
229 self.inner.set_nonce(nonce);
230 }
231
232 fn set_access_list(&mut self, access_list: AccessList) {
233 self.inner.set_access_list(access_list);
234 }
235}
236
237impl IntoTxEnv<Self> for TempoTxEnv {
238 fn into_tx_env(self) -> Self {
239 self
240 }
241}
242
243impl FromRecoveredTx<EthereumTxEnvelope<TxEip4844>> for TempoTxEnv {
244 fn from_recovered_tx(tx: &EthereumTxEnvelope<TxEip4844>, sender: Address) -> Self {
245 TxEnv::from_recovered_tx(tx, sender).into()
246 }
247}
248
249impl FromRecoveredTx<TxFeeToken> for TempoTxEnv {
250 fn from_recovered_tx(tx: &TxFeeToken, caller: Address) -> Self {
251 let TxFeeToken {
252 chain_id,
253 nonce,
254 gas_limit,
255 to,
256 value,
257 input,
258 max_fee_per_gas,
259 max_priority_fee_per_gas,
260 access_list,
261 authorization_list,
262 fee_token,
263 fee_payer_signature,
264 } = tx;
265 Self {
266 inner: TxEnv {
267 tx_type: tx.ty(),
268 caller,
269 gas_limit: *gas_limit,
270 gas_price: *max_fee_per_gas,
271 kind: *to,
272 value: *value,
273 data: input.clone(),
274 nonce: *nonce,
275 chain_id: Some(*chain_id),
276 gas_priority_fee: Some(*max_priority_fee_per_gas),
277 access_list: access_list.clone(),
278 authorization_list: authorization_list
279 .iter()
280 .map(|auth| {
281 Either::Right(RecoveredAuthorization::new_unchecked(
282 auth.inner().clone(),
283 auth.signature()
284 .ok()
285 .and_then(|signature| {
286 secp256k1::recover_signer(&signature, auth.signature_hash())
287 .ok()
288 })
289 .map_or(RecoveredAuthority::Invalid, RecoveredAuthority::Valid),
290 ))
291 })
292 .collect(),
293 ..Default::default()
294 },
295 fee_token: *fee_token,
296 is_system_tx: false,
297 fee_payer: fee_payer_signature.map(|sig| {
298 sig.recover_address_from_prehash(&tx.fee_payer_signature_hash(caller))
299 .ok()
300 }),
301 tempo_tx_env: None, }
303 }
304}
305
306impl FromRecoveredTx<AASigned> for TempoTxEnv {
307 fn from_recovered_tx(aa_signed: &AASigned, caller: Address) -> Self {
308 let tx = aa_signed.tx();
309 let signature = aa_signed.signature();
310
311 if let Some(keychain_sig) = signature.as_keychain() {
314 let _ = keychain_sig.key_id(&aa_signed.signature_hash());
315 }
316
317 let TempoTransaction {
318 chain_id,
319 fee_token,
320 max_priority_fee_per_gas,
321 max_fee_per_gas,
322 gas_limit,
323 calls,
324 access_list,
325 nonce_key,
326 nonce,
327 fee_payer_signature,
328 valid_before,
329 valid_after,
330 key_authorization,
331 tempo_authorization_list,
332 } = tx;
333
334 let (to, value, input) = if let Some(first_call) = calls.first() {
336 (first_call.to, first_call.value, first_call.input.clone())
337 } else {
338 (
339 alloy_primitives::TxKind::Create,
340 alloy_primitives::U256::ZERO,
341 alloy_primitives::Bytes::new(),
342 )
343 };
344
345 Self {
346 inner: TxEnv {
347 tx_type: tx.ty(),
348 caller,
349 gas_limit: *gas_limit,
350 gas_price: *max_fee_per_gas,
351 kind: to,
352 value,
353 data: input,
354 nonce: *nonce, chain_id: Some(*chain_id),
356 gas_priority_fee: Some(*max_priority_fee_per_gas),
357 access_list: access_list.clone(),
358 authorization_list: tempo_authorization_list
360 .iter()
361 .map(|auth| {
362 let authority = auth
363 .recover_authority()
364 .map_or(RecoveredAuthority::Invalid, RecoveredAuthority::Valid);
365 Either::Right(RecoveredAuthorization::new_unchecked(
366 auth.inner().clone(),
367 authority,
368 ))
369 })
370 .collect(),
371 ..Default::default()
372 },
373 fee_token: *fee_token,
374 is_system_tx: false,
375 fee_payer: fee_payer_signature.map(|sig| {
376 sig.recover_address_from_prehash(&tx.fee_payer_signature_hash(caller))
377 .ok()
378 }),
379 tempo_tx_env: Some(Box::new(TempoBatchCallEnv {
381 signature: signature.clone(),
382 valid_before: *valid_before,
383 valid_after: *valid_after,
384 aa_calls: calls.clone(),
385 tempo_authorization_list: tempo_authorization_list
387 .iter()
388 .map(|auth| RecoveredTempoAuthorization::recover(auth.clone()))
389 .collect(),
390 nonce_key: *nonce_key,
391 subblock_transaction: aa_signed.tx().subblock_proposer().is_some(),
392 key_authorization: key_authorization.clone(),
393 signature_hash: aa_signed.signature_hash(),
394 })),
395 }
396 }
397}
398
399impl FromRecoveredTx<TempoTxEnvelope> for TempoTxEnv {
400 fn from_recovered_tx(tx: &TempoTxEnvelope, sender: Address) -> Self {
401 match tx {
402 tx @ TempoTxEnvelope::Legacy(inner) => Self {
403 inner: TxEnv::from_recovered_tx(inner.tx(), sender),
404 fee_token: None,
405 is_system_tx: tx.is_system_tx(),
406 fee_payer: None,
407 tempo_tx_env: None, },
409 TempoTxEnvelope::Eip2930(tx) => TxEnv::from_recovered_tx(tx.tx(), sender).into(),
410 TempoTxEnvelope::Eip1559(tx) => TxEnv::from_recovered_tx(tx.tx(), sender).into(),
411 TempoTxEnvelope::Eip7702(tx) => TxEnv::from_recovered_tx(tx.tx(), sender).into(),
412 TempoTxEnvelope::AA(tx) => Self::from_recovered_tx(tx, sender),
413 TempoTxEnvelope::FeeToken(tx) => Self::from_recovered_tx(tx.tx(), sender),
414 }
415 }
416}
417
418impl FromTxWithEncoded<EthereumTxEnvelope<TxEip4844>> for TempoTxEnv {
419 fn from_encoded_tx(
420 tx: &EthereumTxEnvelope<TxEip4844>,
421 sender: Address,
422 _encoded: Bytes,
423 ) -> Self {
424 Self::from_recovered_tx(tx, sender)
425 }
426}
427
428impl FromTxWithEncoded<TxFeeToken> for TempoTxEnv {
429 fn from_encoded_tx(tx: &TxFeeToken, sender: Address, _encoded: Bytes) -> Self {
430 Self::from_recovered_tx(tx, sender)
431 }
432}
433
434impl FromTxWithEncoded<AASigned> for TempoTxEnv {
435 fn from_encoded_tx(tx: &AASigned, sender: Address, _encoded: Bytes) -> Self {
436 Self::from_recovered_tx(tx, sender)
437 }
438}
439
440impl FromTxWithEncoded<TempoTxEnvelope> for TempoTxEnv {
441 fn from_encoded_tx(tx: &TempoTxEnvelope, sender: Address, _encoded: Bytes) -> Self {
442 Self::from_recovered_tx(tx, sender)
443 }
444}