1use crate::{TempoBlockEnv, TempoTxEnv, instructions};
2use alloy_evm::{Database, precompiles::PrecompilesMap};
3use alloy_primitives::{Log, U256};
4use revm::{
5 Context, Inspector,
6 context::{CfgEnv, ContextError, Evm, FrameStack},
7 handler::{
8 EthFrame, EvmTr, FrameInitOrResult, FrameTr, ItemOrResult, instructions::EthInstructions,
9 },
10 inspector::InspectorEvmTr,
11 interpreter::interpreter::EthInterpreter,
12};
13use tempo_chainspec::hardfork::TempoHardfork;
14
15pub type TempoContext<DB> = Context<TempoBlockEnv, TempoTxEnv, CfgEnv<TempoHardfork>, DB>;
17
18#[derive(Debug, derive_more::Deref, derive_more::DerefMut)]
20#[expect(clippy::type_complexity)]
21pub struct TempoEvm<DB: Database, I> {
22 #[deref]
24 #[deref_mut]
25 pub inner: Evm<
26 TempoContext<DB>,
27 I,
28 EthInstructions<EthInterpreter, TempoContext<DB>>,
29 PrecompilesMap,
30 EthFrame<EthInterpreter>,
31 >,
32 pub logs: Vec<Log>,
34 pub(crate) collected_fee: U256,
36 pub(crate) initial_gas: u64,
40}
41
42impl<DB: Database, I> TempoEvm<DB, I> {
43 pub fn new(ctx: TempoContext<DB>, inspector: I) -> Self {
45 let precompiles = tempo_precompiles::tempo_precompiles(&ctx.cfg);
46
47 Self::new_inner(Evm {
48 instruction: instructions::tempo_instructions(ctx.cfg.spec),
49 ctx,
50 inspector,
51 precompiles,
52 frame_stack: FrameStack::new(),
53 })
54 }
55
56 #[inline]
58 #[expect(clippy::type_complexity)]
59 fn new_inner(
60 inner: Evm<
61 TempoContext<DB>,
62 I,
63 EthInstructions<EthInterpreter, TempoContext<DB>>,
64 PrecompilesMap,
65 EthFrame<EthInterpreter>,
66 >,
67 ) -> Self {
68 Self {
69 inner,
70 logs: Vec::new(),
71 collected_fee: U256::ZERO,
72 initial_gas: 0,
73 }
74 }
75}
76
77impl<DB: Database, I> TempoEvm<DB, I> {
78 pub fn with_inspector<OINSP>(self, inspector: OINSP) -> TempoEvm<DB, OINSP> {
80 TempoEvm::new_inner(self.inner.with_inspector(inspector))
81 }
82
83 pub fn with_precompiles(self, precompiles: PrecompilesMap) -> Self {
85 Self::new_inner(self.inner.with_precompiles(precompiles))
86 }
87
88 pub fn into_inspector(self) -> I {
90 self.inner.into_inspector()
91 }
92
93 #[inline]
95 pub fn take_logs(&mut self) -> Vec<Log> {
96 std::mem::take(&mut self.logs)
97 }
98}
99
100impl<DB, I> EvmTr for TempoEvm<DB, I>
101where
102 DB: Database,
103{
104 type Context = TempoContext<DB>;
105 type Instructions = EthInstructions<EthInterpreter, TempoContext<DB>>;
106 type Precompiles = PrecompilesMap;
107 type Frame = EthFrame<EthInterpreter>;
108
109 fn all(
110 &self,
111 ) -> (
112 &Self::Context,
113 &Self::Instructions,
114 &Self::Precompiles,
115 &FrameStack<Self::Frame>,
116 ) {
117 self.inner.all()
118 }
119
120 fn all_mut(
121 &mut self,
122 ) -> (
123 &mut Self::Context,
124 &mut Self::Instructions,
125 &mut Self::Precompiles,
126 &mut FrameStack<Self::Frame>,
127 ) {
128 self.inner.all_mut()
129 }
130
131 fn frame_stack(&mut self) -> &mut FrameStack<Self::Frame> {
132 &mut self.inner.frame_stack
133 }
134
135 fn frame_init(
136 &mut self,
137 frame_input: <Self::Frame as FrameTr>::FrameInit,
138 ) -> Result<
139 ItemOrResult<&mut Self::Frame, <Self::Frame as FrameTr>::FrameResult>,
140 ContextError<DB::Error>,
141 > {
142 self.inner.frame_init(frame_input)
143 }
144
145 fn frame_run(&mut self) -> Result<FrameInitOrResult<Self::Frame>, ContextError<DB::Error>> {
146 self.inner.frame_run()
147 }
148
149 fn frame_return_result(
150 &mut self,
151 result: <Self::Frame as FrameTr>::FrameResult,
152 ) -> Result<Option<<Self::Frame as FrameTr>::FrameResult>, ContextError<DB::Error>> {
153 self.inner.frame_return_result(result)
154 }
155}
156
157impl<DB, I> InspectorEvmTr for TempoEvm<DB, I>
158where
159 DB: Database,
160 I: Inspector<TempoContext<DB>>,
161{
162 type Inspector = I;
163
164 fn all_inspector(
165 &self,
166 ) -> (
167 &Self::Context,
168 &Self::Instructions,
169 &Self::Precompiles,
170 &FrameStack<Self::Frame>,
171 &Self::Inspector,
172 ) {
173 self.inner.all_inspector()
174 }
175
176 fn all_mut_inspector(
177 &mut self,
178 ) -> (
179 &mut Self::Context,
180 &mut Self::Instructions,
181 &mut Self::Precompiles,
182 &mut FrameStack<Self::Frame>,
183 &mut Self::Inspector,
184 ) {
185 self.inner.all_mut_inspector()
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use crate::gas_params::tempo_gas_params;
192 use alloy_eips::eip7702::Authorization;
193 use alloy_evm::FromRecoveredTx;
194 use alloy_primitives::{Address, Bytes, Log, TxKind, U256, bytes};
195 use alloy_sol_types::SolCall;
196 use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
197 use p256::{
198 ecdsa::{SigningKey, signature::hazmat::PrehashSigner},
199 elliptic_curve::rand_core::OsRng,
200 };
201 use reth_evm::EvmInternals;
202 use revm::{
203 Context, DatabaseRef, ExecuteCommitEvm, ExecuteEvm, InspectEvm, MainContext,
204 bytecode::opcode,
205 context::{
206 CfgEnv, ContextTr, TxEnv,
207 result::{ExecutionResult, HaltReason},
208 },
209 database::{CacheDB, EmptyDB},
210 handler::system_call::SystemCallEvm,
211 inspector::{CountInspector, InspectSystemCallEvm},
212 state::{AccountInfo, Bytecode},
213 };
214 use sha2::{Digest, Sha256};
215 use tempo_chainspec::hardfork::TempoHardfork;
216 use tempo_precompiles::{
217 AuthorizedKey, NONCE_PRECOMPILE_ADDRESS, PATH_USD_ADDRESS,
218 nonce::NonceManager,
219 storage::{Handler, StorageCtx, evm::EvmPrecompileStorageProvider},
220 test_util::TIP20Setup,
221 tip20::{ITIP20, TIP20Token},
222 };
223 use tempo_primitives::{
224 TempoTransaction,
225 transaction::{
226 KeyAuthorization, KeychainSignature, SignatureType, TempoSignedAuthorization,
227 tempo_transaction::Call,
228 tt_signature::{
229 PrimitiveSignature, TempoSignature, WebAuthnSignature, derive_p256_address,
230 normalize_p256_s,
231 },
232 },
233 };
234
235 use crate::{TempoBlockEnv, TempoEvm, TempoHaltReason, TempoInvalidTransaction, TempoTxEnv};
236
237 const DEFAULT_BALANCE: u128 = 1_000_000_000_000_000_000;
241
242 const IDENTITY_PRECOMPILE: Address = Address::new([
244 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x04,
245 ]);
246
247 fn create_evm() -> TempoEvm<CacheDB<EmptyDB>, ()> {
251 let db = CacheDB::new(EmptyDB::new());
252 let ctx = Context::mainnet()
253 .with_db(db)
254 .with_block(Default::default())
255 .with_cfg(Default::default())
256 .with_tx(Default::default());
257 TempoEvm::new(ctx, ())
258 }
259
260 fn create_evm_with_timestamp(timestamp: u64) -> TempoEvm<CacheDB<EmptyDB>, ()> {
262 let db = CacheDB::new(EmptyDB::new());
263 let mut block = TempoBlockEnv::default();
264 block.inner.timestamp = U256::from(timestamp);
265
266 let ctx = Context::mainnet()
267 .with_db(db)
268 .with_block(block)
269 .with_cfg(Default::default())
270 .with_tx(Default::default());
271
272 TempoEvm::new(ctx, ())
273 }
274
275 fn fund_account(evm: &mut TempoEvm<CacheDB<EmptyDB>, ()>, address: Address) {
277 evm.ctx.db_mut().insert_account_info(
278 address,
279 AccountInfo {
280 balance: U256::from(DEFAULT_BALANCE),
281 ..Default::default()
282 },
283 );
284 }
285
286 fn create_funded_evm(address: Address) -> TempoEvm<CacheDB<EmptyDB>, ()> {
288 let mut evm = create_evm();
289 fund_account(&mut evm, address);
290 evm
291 }
292
293 fn create_funded_evm_t1(address: Address) -> TempoEvm<CacheDB<EmptyDB>, ()> {
296 let db = CacheDB::new(EmptyDB::new());
297 let mut cfg = CfgEnv::<TempoHardfork>::default();
298 cfg.spec = TempoHardfork::T1C;
299 cfg.gas_params = tempo_gas_params(TempoHardfork::T1C);
301
302 let ctx = Context::mainnet()
303 .with_db(db)
304 .with_block(Default::default())
305 .with_cfg(cfg)
306 .with_tx(Default::default());
307
308 let mut evm = TempoEvm::new(ctx, ());
309 fund_account(&mut evm, address);
310 evm
311 }
312
313 fn create_funded_evm_with_timestamp(
315 address: Address,
316 timestamp: u64,
317 ) -> TempoEvm<CacheDB<EmptyDB>, ()> {
318 let mut evm = create_evm_with_timestamp(timestamp);
319 fund_account(&mut evm, address);
320 evm
321 }
322
323 fn create_funded_evm_t1_with_timestamp(
325 address: Address,
326 timestamp: u64,
327 ) -> TempoEvm<CacheDB<EmptyDB>, ()> {
328 let db = CacheDB::new(EmptyDB::new());
329 let mut cfg = CfgEnv::<TempoHardfork>::default();
330 cfg.spec = TempoHardfork::T1;
331 cfg.gas_params = tempo_gas_params(TempoHardfork::T1);
332
333 let mut block = TempoBlockEnv::default();
334 block.inner.timestamp = U256::from(timestamp);
335
336 let ctx = Context::mainnet()
337 .with_db(db)
338 .with_block(block)
339 .with_cfg(cfg)
340 .with_tx(Default::default());
341
342 let mut evm = TempoEvm::new(ctx, ());
343 fund_account(&mut evm, address);
344 evm
345 }
346
347 fn create_evm_with_inspector<I>(inspector: I) -> TempoEvm<CacheDB<EmptyDB>, I> {
349 let db = CacheDB::new(EmptyDB::new());
350 let ctx = Context::mainnet()
351 .with_db(db)
352 .with_block(Default::default())
353 .with_cfg(Default::default())
354 .with_tx(Default::default());
355 TempoEvm::new(ctx, inspector)
356 }
357
358 struct P256KeyPair {
360 signing_key: SigningKey,
361 pub_key_x: alloy_primitives::B256,
362 pub_key_y: alloy_primitives::B256,
363 address: Address,
364 }
365
366 impl P256KeyPair {
367 fn random() -> Self {
369 let signing_key = SigningKey::random(&mut OsRng);
370 let verifying_key = signing_key.verifying_key();
371 let encoded_point = verifying_key.to_encoded_point(false);
372 let pub_key_x = alloy_primitives::B256::from_slice(encoded_point.x().unwrap().as_ref());
373 let pub_key_y = alloy_primitives::B256::from_slice(encoded_point.y().unwrap().as_ref());
374 let address = derive_p256_address(&pub_key_x, &pub_key_y);
375
376 Self {
377 signing_key,
378 pub_key_x,
379 pub_key_y,
380 address,
381 }
382 }
383
384 fn sign_webauthn(&self, challenge: &[u8]) -> eyre::Result<WebAuthnSignature> {
386 let mut authenticator_data = vec![0u8; 37];
388 authenticator_data[0..32].copy_from_slice(&[0xAA; 32]); authenticator_data[32] = 0x01; authenticator_data[33..37].copy_from_slice(&[0, 0, 0, 0]); let challenge_b64url = URL_SAFE_NO_PAD.encode(challenge);
394 let client_data_json = format!(
395 r#"{{"type":"webauthn.get","challenge":"{challenge_b64url}","origin":"https://example.com","crossOrigin":false}}"#
396 );
397
398 let client_data_hash = Sha256::digest(client_data_json.as_bytes());
400 let mut final_hasher = Sha256::new();
401 final_hasher.update(&authenticator_data);
402 final_hasher.update(client_data_hash);
403 let message_hash = final_hasher.finalize();
404
405 let signature: p256::ecdsa::Signature = self.signing_key.sign_prehash(&message_hash)?;
407 let sig_bytes = signature.to_bytes();
408
409 let mut webauthn_data = Vec::new();
411 webauthn_data.extend_from_slice(&authenticator_data);
412 webauthn_data.extend_from_slice(client_data_json.as_bytes());
413
414 Ok(WebAuthnSignature {
415 webauthn_data: Bytes::from(webauthn_data),
416 r: alloy_primitives::B256::from_slice(&sig_bytes[0..32]),
417 s: normalize_p256_s(&sig_bytes[32..64]),
418 pub_key_x: self.pub_key_x,
419 pub_key_y: self.pub_key_y,
420 })
421 }
422
423 fn create_signed_authorization(
425 &self,
426 delegate_address: Address,
427 ) -> eyre::Result<TempoSignedAuthorization> {
428 let auth = Authorization {
429 chain_id: U256::from(1),
430 address: delegate_address,
431 nonce: 0,
432 };
433
434 let mut sig_buf = Vec::new();
435 sig_buf.push(tempo_primitives::transaction::tt_authorization::MAGIC);
436 alloy_rlp::Encodable::encode(&auth, &mut sig_buf);
437 let auth_sig_hash = alloy_primitives::keccak256(&sig_buf);
438
439 let webauthn_sig = self.sign_webauthn(auth_sig_hash.as_slice())?;
440 let aa_sig = TempoSignature::Primitive(PrimitiveSignature::WebAuthn(webauthn_sig));
441
442 Ok(TempoSignedAuthorization::new_unchecked(auth, aa_sig))
443 }
444
445 fn sign_tx(&self, tx: TempoTransaction) -> eyre::Result<tempo_primitives::AASigned> {
447 let webauthn_sig = self.sign_webauthn(tx.signature_hash().as_slice())?;
448 Ok(
449 tx.into_signed(TempoSignature::Primitive(PrimitiveSignature::WebAuthn(
450 webauthn_sig,
451 ))),
452 )
453 }
454
455 fn sign_tx_keychain(
457 &self,
458 tx: TempoTransaction,
459 ) -> eyre::Result<tempo_primitives::AASigned> {
460 let sig_hash = tx.signature_hash();
462 let effective_hash = alloy_primitives::keccak256(
463 [&[0x04], sig_hash.as_slice(), self.address.as_slice()].concat(),
464 );
465 let webauthn_sig = self.sign_webauthn(effective_hash.as_slice())?;
466 let keychain_sig =
467 KeychainSignature::new(self.address, PrimitiveSignature::WebAuthn(webauthn_sig));
468 Ok(tx.into_signed(TempoSignature::Keychain(keychain_sig)))
469 }
470 }
471
472 struct TxBuilder {
474 calls: Vec<Call>,
475 nonce: u64,
476 nonce_key: U256,
477 gas_limit: u64,
478 max_fee_per_gas: u128,
479 max_priority_fee_per_gas: u128,
480 valid_before: Option<u64>,
481 valid_after: Option<u64>,
482 authorization_list: Vec<TempoSignedAuthorization>,
483 key_authorization: Option<tempo_primitives::transaction::SignedKeyAuthorization>,
484 }
485
486 impl Default for TxBuilder {
487 fn default() -> Self {
488 Self {
489 calls: vec![],
490 nonce: 0,
491 nonce_key: U256::ZERO,
492 gas_limit: 1_000_000,
493 max_fee_per_gas: 0,
494 max_priority_fee_per_gas: 0,
495 valid_before: Some(u64::MAX),
496 valid_after: None,
497 authorization_list: vec![],
498 key_authorization: None,
499 }
500 }
501 }
502
503 impl TxBuilder {
504 fn new() -> Self {
505 Self::default()
506 }
507
508 fn call_identity(mut self, input: &[u8]) -> Self {
510 self.calls.push(Call {
511 to: TxKind::Call(IDENTITY_PRECOMPILE),
512 value: U256::ZERO,
513 input: Bytes::from(input.to_vec()),
514 });
515 self
516 }
517
518 fn call(mut self, to: Address, input: &[u8]) -> Self {
520 self.calls.push(Call {
521 to: TxKind::Call(to),
522 value: U256::ZERO,
523 input: Bytes::from(input.to_vec()),
524 });
525 self
526 }
527
528 fn create(mut self, initcode: &[u8]) -> Self {
530 self.calls.push(Call {
531 to: TxKind::Create,
532 value: U256::ZERO,
533 input: Bytes::from(initcode.to_vec()),
534 });
535 self
536 }
537
538 fn call_with_value(mut self, to: Address, input: &[u8], value: U256) -> Self {
540 self.calls.push(Call {
541 to: TxKind::Call(to),
542 value,
543 input: Bytes::from(input.to_vec()),
544 });
545 self
546 }
547
548 fn nonce(mut self, nonce: u64) -> Self {
549 self.nonce = nonce;
550 self
551 }
552
553 fn nonce_key(mut self, nonce_key: U256) -> Self {
554 self.nonce_key = nonce_key;
555 self
556 }
557
558 fn gas_limit(mut self, gas_limit: u64) -> Self {
559 self.gas_limit = gas_limit;
560 self
561 }
562
563 fn with_max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self {
564 self.max_fee_per_gas = max_fee_per_gas;
565 self
566 }
567
568 fn with_max_priority_fee_per_gas(mut self, max_priority_fee_per_gas: u128) -> Self {
569 self.max_priority_fee_per_gas = max_priority_fee_per_gas;
570 self
571 }
572
573 fn valid_before(mut self, valid_before: Option<u64>) -> Self {
574 self.valid_before = valid_before;
575 self
576 }
577
578 fn valid_after(mut self, valid_after: Option<u64>) -> Self {
579 self.valid_after = valid_after;
580 self
581 }
582
583 fn authorization(mut self, auth: TempoSignedAuthorization) -> Self {
584 self.authorization_list.push(auth);
585 self
586 }
587
588 fn key_authorization(
589 mut self,
590 key_auth: tempo_primitives::transaction::SignedKeyAuthorization,
591 ) -> Self {
592 self.key_authorization = Some(key_auth);
593 self
594 }
595
596 fn build(self) -> TempoTransaction {
597 TempoTransaction {
598 chain_id: 1,
599 fee_token: None,
600 max_priority_fee_per_gas: self.max_priority_fee_per_gas,
601 max_fee_per_gas: self.max_fee_per_gas,
602 gas_limit: self.gas_limit,
603 calls: self.calls,
604 access_list: Default::default(),
605 nonce_key: self.nonce_key,
606 nonce: self.nonce,
607 fee_payer_signature: None,
608 valid_before: self.valid_before,
609 valid_after: self.valid_after,
610 key_authorization: self.key_authorization,
611 tempo_authorization_list: self.authorization_list,
612 }
613 }
614 }
615
616 #[test_case::test_case(TempoHardfork::T1)]
619 #[test_case::test_case(TempoHardfork::T1C)]
620 fn test_access_millis_timestamp(spec: TempoHardfork) -> eyre::Result<()> {
621 let db = CacheDB::new(EmptyDB::new());
622
623 let mut ctx = Context::mainnet()
624 .with_db(db)
625 .with_block(TempoBlockEnv::default())
626 .with_cfg(CfgEnv::<TempoHardfork>::default())
627 .with_tx(Default::default());
628
629 ctx.cfg.spec = spec;
630 ctx.block.timestamp = U256::from(1000);
631 ctx.block.timestamp_millis_part = 100;
632
633 let mut tempo_evm = TempoEvm::new(ctx, ());
634 let ctx = &mut tempo_evm.ctx;
635
636 let internals = EvmInternals::new(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx);
637 let mut storage = EvmPrecompileStorageProvider::new_max_gas(internals, &ctx.cfg);
638
639 _ = StorageCtx::enter(&mut storage, || TIP20Setup::path_usd(Address::ZERO).apply())?;
640 drop(storage);
641
642 let contract = Address::random();
643
644 ctx.db_mut().insert_account_info(
646 contract,
647 AccountInfo {
648 code: Some(Bytecode::new_raw(bytes!("0x4F5F5260205FF3"))),
650 ..Default::default()
651 },
652 );
653
654 let tx_env = TxEnv {
655 kind: contract.into(),
656 ..Default::default()
657 };
658 let result = tempo_evm.transact_one(tx_env.into())?;
659
660 if !spec.is_t1c() {
661 assert!(result.is_success());
662 assert_eq!(
663 U256::from_be_slice(result.output().unwrap()),
664 U256::from(1000100)
665 );
666 } else {
667 assert!(matches!(
668 result,
669 ExecutionResult::Halt {
670 reason: TempoHaltReason::Ethereum(HaltReason::OpcodeNotFound),
671 ..
672 }
673 ));
674 }
675
676 Ok(())
677 }
678
679 #[test]
680 fn test_inspector_calls() -> eyre::Result<()> {
681 let caller = Address::repeat_byte(0x01);
683 let contract = Address::repeat_byte(0x42);
684
685 let input_bytes = ITIP20::setSupplyCapCall {
686 newSupplyCap: U256::from(100),
687 }
688 .abi_encode();
689
690 let mut bytecode_bytes = vec![];
693
694 for (i, &byte) in input_bytes.iter().enumerate() {
695 bytecode_bytes.extend_from_slice(&[
696 opcode::PUSH1,
697 byte,
698 opcode::PUSH1,
699 i as u8,
700 opcode::MSTORE8,
701 ]);
702 }
703
704 bytecode_bytes.extend_from_slice(&[
707 opcode::PUSH1,
708 0x00, opcode::PUSH1,
710 0x00, opcode::PUSH1,
712 0x24, opcode::PUSH1,
714 0x00, opcode::PUSH1,
716 0x00, ]);
718
719 bytecode_bytes.push(opcode::PUSH20);
721 bytecode_bytes.extend_from_slice(PATH_USD_ADDRESS.as_slice());
722
723 bytecode_bytes.extend_from_slice(&[
724 opcode::PUSH2,
725 0xFF,
726 0xFF, opcode::CALL,
728 opcode::POP, opcode::STOP,
730 ]);
731
732 let bytecode = Bytecode::new_raw(bytecode_bytes.into());
733
734 let mut evm = create_evm_with_inspector(CountInspector::new());
736 {
738 let ctx = &mut evm.ctx;
739 let internals =
740 EvmInternals::new(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx);
741
742 let mut storage = EvmPrecompileStorageProvider::new_max_gas(internals, &ctx.cfg);
743 StorageCtx::enter(&mut storage, || {
744 TIP20Setup::path_usd(caller)
745 .with_issuer(caller)
746 .with_admin(contract) .apply()
748 })?;
749 }
750
751 evm.ctx.db_mut().insert_account_info(
753 contract,
754 AccountInfo {
755 code: Some(bytecode),
756 ..Default::default()
757 },
758 );
759
760 let tx_env = TxEnv {
762 caller,
763 kind: TxKind::Call(contract),
764 gas_limit: 1_000_000,
765 ..Default::default()
766 };
767 let result = evm
768 .inspect_tx(tx_env.into())
769 .expect("execution should succeed");
770
771 assert!(result.result.is_success());
772
773 assert_eq!(result.result.logs().len(), 3);
775 assert_eq!(result.result.logs()[0].address, PATH_USD_ADDRESS);
777
778 let inspector = &evm.inspector;
780
781 assert_eq!(inspector.get_count(opcode::CALL), 1);
783
784 assert_eq!(inspector.get_count(opcode::STOP), 1);
785
786 assert_eq!(inspector.log_count(), 1);
788
789 assert_eq!(inspector.call_count(), 2);
791
792 assert_eq!(inspector.call_end_count(), 2);
794
795 let key_pair = P256KeyPair::random();
799 let tempo_caller = key_pair.address;
800
801 let signed_auth = key_pair.create_signed_authorization(Address::repeat_byte(0x42))?;
803
804 let tx = TxBuilder::new()
806 .call_identity(&[0x01, 0x02])
807 .call_identity(&[0x03, 0x04])
808 .call_identity(&[0x05, 0x06])
809 .authorization(signed_auth)
810 .build();
811
812 let signed_tx = key_pair.sign_tx(tx)?;
813 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, tempo_caller);
814
815 let mut multi_evm = create_evm_with_inspector(CountInspector::new());
817 multi_evm.ctx.db_mut().insert_account_info(
818 tempo_caller,
819 AccountInfo {
820 balance: U256::from(DEFAULT_BALANCE),
821 ..Default::default()
822 },
823 );
824
825 let multi_result = multi_evm.inspect_tx(tx_env)?;
827 assert!(multi_result.result.is_success(),);
828
829 let multi_inspector = &multi_evm.inspector;
831
832 assert_eq!(multi_inspector.call_count(), 3,);
835 assert_eq!(multi_inspector.call_end_count(), 3,);
836
837 Ok(())
838 }
839
840 #[test]
841 fn test_tempo_tx_initial_gas() -> eyre::Result<()> {
842 let key_pair = P256KeyPair::random();
843 let caller = key_pair.address;
844
845 let mut evm = create_funded_evm(caller);
847 evm.block.basefee = 100_000_000_000;
848
849 let block = TempoBlockEnv::default();
851 let ctx = &mut evm.ctx;
852 let internals = EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
853 let mut provider =
854 EvmPrecompileStorageProvider::new_max_gas(internals, &Default::default());
855
856 StorageCtx::enter(&mut provider, || {
857 TIP20Setup::path_usd(caller)
858 .with_issuer(caller)
859 .with_mint(caller, U256::from(100_000))
860 .apply()
861 })?;
862
863 drop(provider);
864
865 let tx1 = TxBuilder::new()
867 .call_identity(&[])
868 .gas_limit(300_000)
869 .with_max_fee_per_gas(200_000_000_000)
870 .with_max_priority_fee_per_gas(0)
871 .build();
872
873 let signed_tx1 = key_pair.sign_tx(tx1)?;
874 let tx_env1 = TempoTxEnv::from_recovered_tx(&signed_tx1, caller);
875
876 let ctx = &mut evm.ctx;
877 let internals = EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
878 let mut provider =
879 EvmPrecompileStorageProvider::new_max_gas(internals, &Default::default());
880
881 let slot = StorageCtx::enter(&mut provider, || {
882 TIP20Token::from_address(PATH_USD_ADDRESS)?.balances[caller].read()
883 })?;
884 drop(provider);
885
886 assert_eq!(slot, U256::from(100_000));
887
888 let result1 = evm.transact_commit(tx_env1)?;
889 assert!(result1.is_success());
890 assert_eq!(result1.gas_used(), 28_671);
891
892 let ctx = &mut evm.ctx;
893 let internals = EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
894 let mut provider =
895 EvmPrecompileStorageProvider::new_max_gas(internals, &Default::default());
896
897 let slot = StorageCtx::enter(&mut provider, || {
898 TIP20Token::from_address(PATH_USD_ADDRESS)?.balances[caller].read()
899 })?;
900 drop(provider);
901
902 assert_eq!(slot, U256::from(97_132));
903
904 let tx2 = TxBuilder::new()
906 .call_identity(&[])
907 .call_identity(&[])
908 .nonce(1)
909 .gas_limit(35_000)
910 .with_max_fee_per_gas(200_000_000_000)
911 .with_max_priority_fee_per_gas(0)
912 .build();
913
914 let signed_tx2 = key_pair.sign_tx(tx2)?;
915 let tx_env2 = TempoTxEnv::from_recovered_tx(&signed_tx2, caller);
916
917 let result2 = evm.transact_commit(tx_env2)?;
918 assert!(result2.is_success());
919 assert_eq!(result2.gas_used(), 31_286);
920
921 let ctx = &mut evm.ctx;
922 let internals = EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
923 let mut provider =
924 EvmPrecompileStorageProvider::new_max_gas(internals, &Default::default());
925
926 let slot = StorageCtx::enter(&mut provider, || {
927 TIP20Token::from_address(PATH_USD_ADDRESS)?.balances[caller].read()
928 })?;
929 drop(provider);
930
931 assert_eq!(slot, U256::from(94_003));
932
933 Ok(())
934 }
935
936 #[test]
941 fn test_tempo_tx() -> eyre::Result<()> {
942 let key_pair = P256KeyPair::random();
943 let caller = key_pair.address;
944
945 let signed_auth = key_pair.create_signed_authorization(Address::repeat_byte(0x42))?;
947
948 let tx = TxBuilder::new()
950 .call_identity(&[0x01, 0x02, 0x03, 0x04])
951 .call_identity(&[0xAA, 0xBB, 0xCC, 0xDD])
952 .authorization(signed_auth.clone())
953 .build();
954
955 let signed_tx = key_pair.sign_tx(tx)?;
956 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
957
958 assert!(tx_env.tempo_tx_env.is_some(),);
960 let tempo_env = tx_env.tempo_tx_env.as_ref().unwrap();
961 assert_eq!(tempo_env.tempo_authorization_list.len(), 1);
962 assert_eq!(tempo_env.aa_calls.len(), 2);
963
964 let mut evm = create_funded_evm_t1(caller);
966
967 let result = evm.transact_commit(tx_env)?;
969 assert!(result.is_success());
970
971 let key_auth = KeyAuthorization {
973 chain_id: 1,
974 key_type: SignatureType::WebAuthn,
975 key_id: caller,
976 expiry: None,
977 limits: None,
978 };
979 let key_auth_webauthn_sig = key_pair.sign_webauthn(key_auth.signature_hash().as_slice())?;
980 let signed_key_auth =
981 key_auth.into_signed(PrimitiveSignature::WebAuthn(key_auth_webauthn_sig));
982
983 let tx2 = TxBuilder::new()
985 .call_identity(&[0x01, 0x02, 0x03, 0x04])
986 .call_identity(&[0xAA, 0xBB, 0xCC, 0xDD])
987 .authorization(signed_auth)
988 .nonce(1)
989 .gas_limit(1_000_000)
990 .key_authorization(signed_key_auth)
991 .build();
992
993 let signed_tx = key_pair.sign_tx_keychain(tx2)?;
994 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
995
996 let tempo_env_keychain = tx_env
998 .tempo_tx_env
999 .as_ref()
1000 .expect("Transaction should have tempo_tx_env");
1001 let keychain_sig = tempo_env_keychain
1002 .signature
1003 .as_keychain()
1004 .expect("Signature should be a KeychainSignature");
1005
1006 assert_eq!(keychain_sig.user_address, caller,);
1009
1010 assert!(matches!(
1012 keychain_sig.signature,
1013 PrimitiveSignature::WebAuthn(_)
1014 ));
1015
1016 let recovered_key_id = keychain_sig
1018 .key_id(&tempo_env_keychain.signature_hash)
1019 .expect("Key ID recovery should succeed");
1020 assert_eq!(recovered_key_id, caller,);
1021
1022 let result = evm.transact_commit(tx_env)?;
1024 assert!(result.is_success());
1025
1026 let tx_fail = TxBuilder::new()
1028 .call(PATH_USD_ADDRESS, &[0x01, 0x02]) .nonce(2)
1030 .build();
1031
1032 let signed_tx_fail = key_pair.sign_tx_keychain(tx_fail)?;
1033 let tx_env_fail = TempoTxEnv::from_recovered_tx(&signed_tx_fail, caller);
1034
1035 let result_fail = evm.transact(tx_env_fail)?;
1036 assert!(!result_fail.result.is_success());
1037
1038 let nonce_key_2d = U256::from(42);
1040
1041 let tx_2d = TxBuilder::new()
1042 .call_identity(&[0x2D, 0x2D, 0x2D, 0x2D])
1043 .nonce_key(nonce_key_2d)
1044 .build();
1045
1046 let signed_tx_2d = key_pair.sign_tx_keychain(tx_2d)?;
1047 let tx_env_2d = TempoTxEnv::from_recovered_tx(&signed_tx_2d, caller);
1048
1049 assert!(tx_env_2d.tempo_tx_env.is_some());
1050 assert_eq!(
1051 tx_env_2d.tempo_tx_env.as_ref().unwrap().nonce_key,
1052 nonce_key_2d
1053 );
1054
1055 let result_2d = evm.transact_commit(tx_env_2d)?;
1056 assert!(result_2d.is_success());
1057
1058 let nonce_slot = NonceManager::new().nonces[caller][nonce_key_2d].slot();
1060 let stored_nonce = evm
1061 .ctx
1062 .db()
1063 .storage_ref(NONCE_PRECOMPILE_ADDRESS, nonce_slot)
1064 .unwrap_or_default();
1065 assert_eq!(stored_nonce, U256::from(1));
1066
1067 let tx_2d_2 = TxBuilder::new()
1069 .call_identity(&[0x2E, 0x2E, 0x2E, 0x2E])
1070 .nonce_key(nonce_key_2d)
1071 .nonce(1)
1072 .build();
1073
1074 let signed_tx_2d_2 = key_pair.sign_tx_keychain(tx_2d_2)?;
1075 let tx_env_2d_2 = TempoTxEnv::from_recovered_tx(&signed_tx_2d_2, caller);
1076
1077 let result_2d_2 = evm.transact_commit(tx_env_2d_2)?;
1078 assert!(result_2d_2.is_success());
1079
1080 let stored_nonce_2 = evm
1082 .ctx
1083 .db()
1084 .storage_ref(NONCE_PRECOMPILE_ADDRESS, nonce_slot)
1085 .unwrap_or_default();
1086 assert_eq!(stored_nonce_2, U256::from(2));
1087
1088 Ok(())
1089 }
1090
1091 #[test]
1094 fn test_tempo_tx_time_window() -> eyre::Result<()> {
1095 let key_pair = P256KeyPair::random();
1096 let caller = key_pair.address;
1097
1098 let signed_auth = key_pair.create_signed_authorization(Address::repeat_byte(0x42))?;
1100
1101 let create_signed_tx = |valid_after: Option<u64>, valid_before: Option<u64>| {
1103 let tx = TxBuilder::new()
1104 .call_identity(&[0x01, 0x02, 0x03, 0x04])
1105 .authorization(signed_auth.clone())
1106 .valid_after(valid_after)
1107 .valid_before(valid_before)
1108 .build();
1109 key_pair.sign_tx(tx)
1110 };
1111
1112 {
1114 let mut evm = create_funded_evm_with_timestamp(caller, 100);
1115 let signed_tx = create_signed_tx(Some(200), None)?;
1116 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1117
1118 let result = evm.transact(tx_env);
1119 assert!(result.is_err());
1120 let err = result.unwrap_err();
1121 assert!(
1122 matches!(
1123 err,
1124 revm::context::result::EVMError::Transaction(
1125 TempoInvalidTransaction::ValidAfter {
1126 current: 100,
1127 valid_after: 200
1128 }
1129 )
1130 ),
1131 "Expected ValidAfter error, got: {err:?}"
1132 );
1133 }
1134
1135 {
1137 let mut evm = create_funded_evm_with_timestamp(caller, 200);
1138 let signed_tx = create_signed_tx(None, Some(200))?;
1139 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1140
1141 let result = evm.transact(tx_env);
1142 assert!(result.is_err());
1143 let err = result.unwrap_err();
1144 assert!(
1145 matches!(
1146 err,
1147 revm::context::result::EVMError::Transaction(
1148 TempoInvalidTransaction::ValidBefore {
1149 current: 200,
1150 valid_before: 200
1151 }
1152 )
1153 ),
1154 "Expected ValidBefore error, got: {err:?}"
1155 );
1156 }
1157
1158 {
1160 let mut evm = create_funded_evm_with_timestamp(caller, 300);
1161 let signed_tx = create_signed_tx(None, Some(200))?;
1162 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1163
1164 let result = evm.transact(tx_env);
1165 assert!(result.is_err());
1166 let err = result.unwrap_err();
1167 assert!(
1168 matches!(
1169 err,
1170 revm::context::result::EVMError::Transaction(
1171 TempoInvalidTransaction::ValidBefore {
1172 current: 300,
1173 valid_before: 200
1174 }
1175 )
1176 ),
1177 "Expected ValidBefore error, got: {err:?}"
1178 );
1179 }
1180
1181 {
1183 let mut evm = create_funded_evm_with_timestamp(caller, 200);
1184 let signed_tx = create_signed_tx(Some(200), None)?;
1185 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1186
1187 let result = evm.transact(tx_env)?;
1188 assert!(result.result.is_success());
1189 }
1190
1191 {
1193 let mut evm = create_funded_evm_with_timestamp(caller, 150);
1194 let signed_tx = create_signed_tx(Some(100), Some(200))?;
1195 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1196
1197 let result = evm.transact(tx_env)?;
1198 assert!(result.result.is_success());
1199 }
1200
1201 {
1203 let mut evm = create_funded_evm_with_timestamp(caller, 50);
1204 let signed_tx = create_signed_tx(Some(100), Some(200))?;
1205 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1206
1207 let result = evm.transact(tx_env);
1208 assert!(result.is_err());
1209 let err = result.unwrap_err();
1210 assert!(
1211 matches!(
1212 err,
1213 revm::context::result::EVMError::Transaction(
1214 TempoInvalidTransaction::ValidAfter {
1215 current: 50,
1216 valid_after: 100
1217 }
1218 )
1219 ),
1220 "Expected ValidAfter error, got: {err:?}"
1221 );
1222 }
1223
1224 {
1226 let mut evm = create_funded_evm_with_timestamp(caller, 200);
1227 let signed_tx = create_signed_tx(Some(100), Some(200))?;
1228 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1229
1230 let result = evm.transact(tx_env);
1231 assert!(result.is_err());
1232 let err = result.unwrap_err();
1233 assert!(
1234 matches!(
1235 err,
1236 revm::context::result::EVMError::Transaction(
1237 TempoInvalidTransaction::ValidBefore {
1238 current: 200,
1239 valid_before: 200
1240 }
1241 )
1242 ),
1243 "Expected ValidBefore error, got: {err:?}"
1244 );
1245 }
1246
1247 Ok(())
1248 }
1249
1250 #[test]
1253 fn test_tempo_tx_create_first_call() -> eyre::Result<()> {
1254 let key_pair = P256KeyPair::random();
1255 let caller = key_pair.address;
1256
1257 let initcode = vec![0x60, 0x00, 0x60, 0x00, 0xF3];
1259
1260 let tx = TxBuilder::new()
1262 .create(&initcode)
1263 .call_identity(&[0x01, 0x02])
1264 .gas_limit(200_000)
1265 .build();
1266
1267 let signed_tx = key_pair.sign_tx(tx)?;
1268 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1269
1270 let mut evm = create_funded_evm(caller);
1272 let result = evm.transact_commit(tx_env)?;
1273
1274 assert!(result.is_success(), "CREATE as first call should succeed");
1275
1276 Ok(())
1277 }
1278
1279 #[test]
1282 fn test_tempo_tx_create_second_call_fails() -> eyre::Result<()> {
1283 let key_pair = P256KeyPair::random();
1284 let caller = key_pair.address;
1285
1286 let initcode = vec![0x60, 0x00, 0x60, 0x00, 0xF3];
1288
1289 let tx = TxBuilder::new()
1291 .call_identity(&[0x01, 0x02])
1292 .create(&initcode)
1293 .gas_limit(200_000)
1294 .build();
1295
1296 let signed_tx = key_pair.sign_tx(tx)?;
1297 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1298
1299 let mut evm = create_funded_evm(caller);
1301 let result = evm.transact(tx_env);
1302
1303 assert!(result.is_err(), "CREATE as second call should fail");
1304 let err = result.unwrap_err();
1305 assert!(
1306 matches!(
1307 err,
1308 revm::context::result::EVMError::Transaction(
1309 TempoInvalidTransaction::CallsValidation(msg)
1310 ) if msg.contains("first call")
1311 ),
1312 "Expected CallsValidation error about 'first call', got: {err:?}"
1313 );
1314
1315 Ok(())
1316 }
1317
1318 #[test]
1324 fn test_validate_aa_initial_tx_gas_errors() -> eyre::Result<()> {
1325 use revm::{context::result::EVMError, handler::Handler};
1326
1327 use crate::handler::TempoEvmHandler;
1328
1329 let key_pair = P256KeyPair::random();
1330 let caller = key_pair.address;
1331
1332 let create_evm_with_tx =
1334 |tx: TempoTransaction| -> eyre::Result<TempoEvm<CacheDB<EmptyDB>, ()>> {
1335 let signed_tx = key_pair.sign_tx(tx)?;
1336 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1337 let mut evm = create_funded_evm(caller);
1338 evm.ctx.tx = tx_env;
1339 Ok(evm)
1340 };
1341
1342 let handler = TempoEvmHandler::default();
1343
1344 {
1346 let oversized_initcode = vec![0x60; 50_000];
1348
1349 let mut evm = create_evm_with_tx(
1350 TxBuilder::new()
1351 .create(&oversized_initcode)
1352 .gas_limit(10_000_000)
1353 .build(),
1354 )?;
1355
1356 let result = handler.validate_initial_tx_gas(&mut evm);
1357 assert!(
1358 matches!(
1359 result,
1360 Err(EVMError::Transaction(
1361 TempoInvalidTransaction::EthInvalidTransaction(
1362 revm::context::result::InvalidTransaction::CreateInitCodeSizeLimit
1363 )
1364 ))
1365 ),
1366 "Expected CreateInitCodeSizeLimit error, got: {result:?}"
1367 );
1368 }
1369
1370 {
1372 let mut evm = create_evm_with_tx(
1373 TxBuilder::new()
1374 .call_with_value(IDENTITY_PRECOMPILE, &[0x01, 0x02], U256::from(1000))
1375 .build(),
1376 )?;
1377
1378 let result = handler.validate_initial_tx_gas(&mut evm);
1379 assert!(
1380 matches!(
1381 result,
1382 Err(EVMError::Transaction(
1383 TempoInvalidTransaction::ValueTransferNotAllowedInAATx
1384 ))
1385 ),
1386 "Expected ValueTransferNotAllowedInAATx error, got: {result:?}"
1387 );
1388 }
1389
1390 {
1392 let mut evm = create_evm_with_tx(
1393 TxBuilder::new()
1394 .call_identity(&[0x01, 0x02, 0x03, 0x04])
1395 .gas_limit(1000) .build(),
1397 )?;
1398
1399 let result = handler.validate_initial_tx_gas(&mut evm);
1400 assert!(
1401 matches!(
1402 result,
1403 Err(EVMError::Transaction(
1404 TempoInvalidTransaction::InsufficientGasForIntrinsicCost {
1405 gas_limit: 1000,
1406 intrinsic_gas
1407 }
1408 )) if intrinsic_gas > 1000
1409 ),
1410 "Expected InsufficientGasForIntrinsicCost error, got: {result:?}"
1411 );
1412 }
1413
1414 {
1416 let large_calldata = vec![0x42; 1000]; let mut evm = create_evm_with_tx(
1419 TxBuilder::new()
1420 .call_identity(&large_calldata)
1421 .gas_limit(31_000) .build(),
1423 )?;
1424
1425 let result = handler.validate_initial_tx_gas(&mut evm);
1426
1427 assert!(
1429 matches!(
1430 result,
1431 Err(EVMError::Transaction(
1432 TempoInvalidTransaction::InsufficientGasForIntrinsicCost {
1433 gas_limit: 31_000,
1434 intrinsic_gas
1435 }
1436 )) if intrinsic_gas > 31_000
1437 ),
1438 "Expected InsufficientGasForIntrinsicCost (floor gas), got: {result:?}"
1439 );
1440 }
1441
1442 {
1445 let large_calldata = vec![0x42; 1000];
1446
1447 let mut evm = create_evm_with_tx(
1448 TxBuilder::new()
1449 .call_identity(&large_calldata)
1450 .gas_limit(1_000_000) .build(),
1452 )?;
1453
1454 let result = handler.validate_initial_tx_gas(&mut evm);
1455 assert!(
1456 result.is_ok(),
1457 "Expected success with sufficient gas, got: {result:?}"
1458 );
1459
1460 let gas = result.unwrap();
1461 assert!(
1463 gas.floor_gas > gas.initial_gas,
1464 "Expected floor_gas ({}) > initial_gas ({}) for large calldata",
1465 gas.floor_gas,
1466 gas.initial_gas
1467 );
1468 }
1469
1470 {
1472 let mut evm = create_evm_with_tx(
1473 TxBuilder::new()
1474 .call_identity(&[0x01, 0x02, 0x03, 0x04])
1475 .gas_limit(1_000_000)
1476 .build(),
1477 )?;
1478
1479 let result = handler.validate_initial_tx_gas(&mut evm);
1480 assert!(result.is_ok(), "Expected success, got: {result:?}");
1481
1482 let gas = result.unwrap();
1483 assert!(
1484 gas.initial_gas >= 21_000,
1485 "Initial gas should be at least 21k base"
1486 );
1487 }
1488
1489 Ok(())
1490 }
1491
1492 #[test]
1496 fn test_tempo_evm_with_inspector_preserves_fields() {
1497 let evm = create_evm();
1498
1499 let evm_with_inspector = evm.with_inspector(CountInspector::new());
1501
1502 assert_eq!(
1504 evm_with_inspector.initial_gas, 0,
1505 "initial_gas should be 0 after with_inspector"
1506 );
1507 }
1508
1509 #[test]
1511 fn test_tempo_evm_take_logs() {
1512 let mut evm = create_evm();
1513
1514 evm.logs.push(Log::new_unchecked(
1516 Address::repeat_byte(0x01),
1517 vec![],
1518 Bytes::new(),
1519 ));
1520 evm.logs.push(Log::new_unchecked(
1521 Address::repeat_byte(0x02),
1522 vec![],
1523 Bytes::new(),
1524 ));
1525
1526 assert_eq!(evm.logs.len(), 2);
1527
1528 let taken_logs = evm.take_logs();
1530
1531 assert_eq!(taken_logs.len(), 2, "Should return 2 logs");
1532 assert!(evm.logs.is_empty(), "Logs should be cleared after take");
1533 }
1534
1535 #[test]
1539 fn test_aa_tx_gas_baseline_identity_call() -> eyre::Result<()> {
1540 let key_pair = P256KeyPair::random();
1541 let caller = key_pair.address;
1542
1543 let mut evm = create_funded_evm_t1(caller);
1544
1545 let tx = TxBuilder::new()
1548 .call_identity(&[0x01, 0x02, 0x03, 0x04])
1549 .gas_limit(500_000)
1550 .build();
1551
1552 let signed_tx = key_pair.sign_tx(tx)?;
1553 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1554
1555 let result = evm.transact_commit(tx_env)?;
1556 assert!(result.is_success());
1557
1558 let gas_used = result.gas_used();
1560 assert_eq!(
1561 gas_used, 278738,
1562 "T1 baseline identity call gas should be exact"
1563 );
1564
1565 Ok(())
1566 }
1567
1568 #[test]
1572 fn test_aa_tx_gas_sstore_new_slot() -> eyre::Result<()> {
1573 let key_pair = P256KeyPair::random();
1574 let caller = key_pair.address;
1575 let contract = Address::repeat_byte(0x55);
1576
1577 let mut evm = create_funded_evm_t1(caller);
1578
1579 let sstore_bytecode = Bytecode::new_raw(bytes!("60426000555B00"));
1583 evm.ctx.db_mut().insert_account_info(
1584 contract,
1585 AccountInfo {
1586 code: Some(sstore_bytecode),
1587 ..Default::default()
1588 },
1589 );
1590
1591 let tx = TxBuilder::new()
1593 .call(contract, &[])
1594 .gas_limit(600_000)
1595 .build();
1596
1597 let signed_tx = key_pair.sign_tx(tx)?;
1598 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1599
1600 let result = evm.transact_commit(tx_env)?;
1601 assert!(result.is_success(), "SSTORE transaction should succeed");
1602
1603 let gas_used = result.gas_used();
1605 assert_eq!(
1606 gas_used, 530863,
1607 "T1 SSTORE to new slot gas should be exact"
1608 );
1609
1610 Ok(())
1611 }
1612
1613 #[test]
1617 fn test_aa_tx_gas_sstore_warm_slot() -> eyre::Result<()> {
1618 let key_pair = P256KeyPair::random();
1619 let caller = key_pair.address;
1620 let contract = Address::repeat_byte(0x56);
1621
1622 let mut evm = create_funded_evm_t1(caller);
1623
1624 let sstore_bytecode = Bytecode::new_raw(bytes!("60426000555B00"));
1627 evm.ctx.db_mut().insert_account_info(
1628 contract,
1629 AccountInfo {
1630 code: Some(sstore_bytecode),
1631 ..Default::default()
1632 },
1633 );
1634
1635 evm.ctx
1637 .db_mut()
1638 .insert_account_storage(contract, U256::ZERO, U256::from(1))
1639 .unwrap();
1640
1641 let tx = TxBuilder::new()
1643 .call(contract, &[])
1644 .gas_limit(500_000)
1645 .build();
1646
1647 let signed_tx = key_pair.sign_tx(tx)?;
1648 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1649
1650 let result = evm.transact_commit(tx_env)?;
1651 assert!(
1652 result.is_success(),
1653 "SSTORE to existing slot should succeed"
1654 );
1655
1656 let gas_used = result.gas_used();
1659 assert_eq!(
1660 gas_used, 283663,
1661 "T1 SSTORE to existing slot gas should be exact"
1662 );
1663
1664 Ok(())
1665 }
1666
1667 #[test]
1670 fn test_aa_tx_gas_multiple_sstores() -> eyre::Result<()> {
1671 let key_pair = P256KeyPair::random();
1672 let caller = key_pair.address;
1673 let contract = Address::repeat_byte(0x57);
1674
1675 let mut evm = create_funded_evm_t1(caller);
1676
1677 let multi_sstore_bytecode = Bytecode::new_raw(bytes!("601160005560226001555B00"));
1682 evm.ctx.db_mut().insert_account_info(
1683 contract,
1684 AccountInfo {
1685 code: Some(multi_sstore_bytecode),
1686 ..Default::default()
1687 },
1688 );
1689
1690 let tx = TxBuilder::new()
1692 .call(contract, &[])
1693 .gas_limit(1_000_000)
1694 .build();
1695
1696 let signed_tx = key_pair.sign_tx(tx)?;
1697 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1698
1699 let result = evm.transact_commit(tx_env)?;
1700 assert!(
1701 result.is_success(),
1702 "Multiple SSTORE transaction should succeed"
1703 );
1704
1705 let gas_used = result.gas_used();
1707 assert_eq!(gas_used, 783069, "T1 multiple SSTOREs gas should be exact");
1708
1709 Ok(())
1710 }
1711
1712 #[test]
1716 fn test_aa_tx_gas_create_contract() -> eyre::Result<()> {
1717 let key_pair = P256KeyPair::random();
1718 let caller = key_pair.address;
1719
1720 let mut evm = create_funded_evm_t1(caller);
1721
1722 let initcode = vec![0x60, 0x00, 0x60, 0x00, 0xF3];
1724
1725 let tx = TxBuilder::new()
1727 .create(&initcode)
1728 .gas_limit(1_000_000)
1729 .build();
1730
1731 let signed_tx = key_pair.sign_tx(tx)?;
1732 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1733
1734 let result = evm.transact_commit(tx_env)?;
1735 assert!(result.is_success(), "CREATE transaction should succeed");
1736
1737 let gas_used = result.gas_used();
1739 assert_eq!(gas_used, 778720, "T1 CREATE contract gas should be exact");
1740
1741 Ok(())
1742 }
1743
1744 #[test]
1748 fn test_aa_tx_gas_create_with_2d_nonce() -> eyre::Result<()> {
1749 let key_pair = P256KeyPair::random();
1750 let caller = key_pair.address;
1751
1752 let mut evm = create_funded_evm_t1(caller);
1753
1754 let initcode = vec![0x60, 0x00, 0x60, 0x00, 0xF3];
1756 let nonce_key_2d = U256::from(42);
1757
1758 let tx1 = TxBuilder::new()
1761 .create(&initcode)
1762 .nonce_key(nonce_key_2d)
1763 .gas_limit(2_000_000)
1764 .build();
1765
1766 assert_eq!(
1768 evm.ctx
1769 .db()
1770 .basic_ref(caller)
1771 .ok()
1772 .flatten()
1773 .map(|a| a.nonce)
1774 .unwrap_or(0),
1775 0,
1776 "Caller account nonce should be 0 before first tx"
1777 );
1778
1779 let signed_tx1 = key_pair.sign_tx(tx1)?;
1780 let tx_env1 = TempoTxEnv::from_recovered_tx(&signed_tx1, caller);
1781
1782 let result1 = evm.transact_commit(tx_env1)?;
1783 assert!(result1.is_success(), "CREATE with 2D nonce should succeed");
1784
1785 assert_eq!(
1787 result1.gas_used(),
1788 1028720,
1789 "T1 CREATE with 2D nonce (caller.nonce=0) gas should be exact"
1790 );
1791
1792 let nonce_key_2d_2 = U256::from(43);
1797 let tx2 = TxBuilder::new()
1798 .create(&initcode)
1799 .nonce_key(nonce_key_2d_2)
1800 .nonce(0) .gas_limit(2_000_000)
1802 .build();
1803
1804 let signed_tx2 = key_pair.sign_tx(tx2)?;
1805 let tx_env2 = TempoTxEnv::from_recovered_tx(&signed_tx2, caller);
1806
1807 let result2 = evm.transact_commit(tx_env2)?;
1808 assert!(
1809 result2.is_success(),
1810 "Second CREATE with 2D nonce should succeed"
1811 );
1812
1813 assert_eq!(
1815 result2.gas_used(),
1816 778720,
1817 "T1 CREATE with 2D nonce (caller.nonce=1) gas should be exact"
1818 );
1819
1820 let gas_difference = result1.gas_used() - result2.gas_used();
1822 assert_eq!(
1823 gas_difference, 250_000,
1824 "Gas difference should be exactly new_account_cost (250,000), got {gas_difference:?}",
1825 );
1826
1827 Ok(())
1828 }
1829
1830 #[test]
1833 fn test_aa_tx_gas_create_with_expiring_nonce() -> eyre::Result<()> {
1834 use tempo_primitives::transaction::TEMPO_EXPIRING_NONCE_KEY;
1835
1836 let key_pair = P256KeyPair::random();
1837 let caller = key_pair.address;
1838 let initcode = vec![0x60, 0x00, 0x60, 0x00, 0xF3]; let timestamp = 1000u64;
1840 let valid_before = timestamp + 30;
1841
1842 let mut evm1 = create_funded_evm_t1_with_timestamp(caller, timestamp);
1844 let tx1 = TxBuilder::new()
1845 .create(&initcode)
1846 .nonce_key(TEMPO_EXPIRING_NONCE_KEY)
1847 .valid_before(Some(valid_before))
1848 .gas_limit(2_000_000)
1849 .build();
1850 let result1 = evm1.transact_commit(TempoTxEnv::from_recovered_tx(
1851 &key_pair.sign_tx(tx1)?,
1852 caller,
1853 ))?;
1854 assert!(result1.is_success());
1855 let gas_nonce_zero = result1.gas_used();
1856
1857 let mut evm2 = create_funded_evm_t1_with_timestamp(caller, timestamp);
1859 evm2.ctx.db_mut().insert_account_info(
1860 caller,
1861 AccountInfo {
1862 balance: U256::from(DEFAULT_BALANCE),
1863 nonce: 1,
1864 ..Default::default()
1865 },
1866 );
1867 let tx2 = TxBuilder::new()
1868 .create(&initcode)
1869 .nonce_key(TEMPO_EXPIRING_NONCE_KEY)
1870 .valid_before(Some(valid_before))
1871 .gas_limit(2_000_000)
1872 .build();
1873 let result2 = evm2.transact_commit(TempoTxEnv::from_recovered_tx(
1874 &key_pair.sign_tx(tx2)?,
1875 caller,
1876 ))?;
1877 assert!(result2.is_success());
1878 let gas_nonce_one = result2.gas_used();
1879
1880 assert_eq!(
1882 gas_nonce_zero - gas_nonce_one,
1883 250_000,
1884 "new_account_cost not charged"
1885 );
1886
1887 Ok(())
1888 }
1889
1890 #[test]
1893 fn test_aa_tx_gas_single_vs_multiple_calls() -> eyre::Result<()> {
1894 let key_pair = P256KeyPair::random();
1895 let caller = key_pair.address;
1896
1897 let mut evm1 = create_funded_evm_t1(caller);
1900 let tx1 = TxBuilder::new()
1901 .call_identity(&[0x01, 0x02, 0x03, 0x04])
1902 .gas_limit(500_000)
1903 .build();
1904
1905 let signed_tx1 = key_pair.sign_tx(tx1)?;
1906 let tx_env1 = TempoTxEnv::from_recovered_tx(&signed_tx1, caller);
1907 let result1 = evm1.transact_commit(tx_env1)?;
1908 assert!(result1.is_success());
1909 let gas_single = result1.gas_used();
1910
1911 let mut evm2 = create_funded_evm_t1(caller);
1914 let tx2 = TxBuilder::new()
1915 .call_identity(&[0x01, 0x02, 0x03, 0x04])
1916 .call_identity(&[0x05, 0x06, 0x07, 0x08])
1917 .call_identity(&[0x09, 0x0A, 0x0B, 0x0C])
1918 .gas_limit(500_000)
1919 .build();
1920
1921 let signed_tx2 = key_pair.sign_tx(tx2)?;
1922 let tx_env2 = TempoTxEnv::from_recovered_tx(&signed_tx2, caller);
1923 let result2 = evm2.transact_commit(tx_env2)?;
1924 assert!(result2.is_success());
1925 let gas_triple = result2.gas_used();
1926
1927 assert_eq!(gas_single, 278738, "T1 single call gas should be exact");
1929 assert_eq!(gas_triple, 284102, "T1 triple call gas should be exact");
1930 assert!(
1931 gas_triple > gas_single,
1932 "3 calls should cost more than 1 call"
1933 );
1934 assert!(
1935 gas_triple < gas_single * 3,
1936 "3 calls should cost less than 3x single call (base costs shared)"
1937 );
1938
1939 Ok(())
1940 }
1941
1942 #[test]
1945 fn test_aa_tx_gas_sload_cold_vs_warm() -> eyre::Result<()> {
1946 let key_pair = P256KeyPair::random();
1947 let caller = key_pair.address;
1948 let contract = Address::repeat_byte(0x58);
1949
1950 let mut evm = create_funded_evm_t1(caller);
1951
1952 let sload_bytecode = Bytecode::new_raw(bytes!("6000545060005450"));
1957 evm.ctx.db_mut().insert_account_info(
1958 contract,
1959 AccountInfo {
1960 code: Some(sload_bytecode),
1961 ..Default::default()
1962 },
1963 );
1964
1965 evm.ctx
1967 .db_mut()
1968 .insert_account_storage(contract, U256::ZERO, U256::from(0x1234))
1969 .unwrap();
1970
1971 let tx = TxBuilder::new()
1973 .call(contract, &[])
1974 .gas_limit(500_000)
1975 .build();
1976
1977 let signed_tx = key_pair.sign_tx(tx)?;
1978 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1979
1980 let result = evm.transact_commit(tx_env)?;
1981 assert!(result.is_success(), "SLOAD transaction should succeed");
1982
1983 let gas_used = result.gas_used();
1985 assert_eq!(gas_used, 280866, "T1 SLOAD cold/warm gas should be exact");
1986
1987 Ok(())
1988 }
1989
1990 #[test]
1995 fn test_system_call_and_inspector() -> eyre::Result<()> {
1996 let caller = Address::repeat_byte(0x01);
1997 let contract = Address::repeat_byte(0x42);
1998
1999 let bytecode = Bytecode::new_raw(bytes!("444360006000F3"));
2002
2003 {
2005 let mut evm = create_evm();
2006 evm.ctx.db_mut().insert_account_info(
2007 contract,
2008 AccountInfo {
2009 code: Some(bytecode.clone()),
2010 ..Default::default()
2011 },
2012 );
2013
2014 let result = evm.system_call_one_with_caller(caller, contract, Bytes::new())?;
2015 assert!(result.is_success());
2016 }
2017
2018 {
2020 let mut evm = create_evm_with_inspector(CountInspector::new());
2021 evm.ctx.db_mut().insert_account_info(
2022 contract,
2023 AccountInfo {
2024 code: Some(bytecode),
2025 ..Default::default()
2026 },
2027 );
2028
2029 let result = evm.inspect_one_system_call_with_caller(caller, contract, Bytes::new())?;
2031 assert!(result.is_success());
2032
2033 assert!(evm.inspector.call_count() > 0,);
2035
2036 evm.set_inspector(CountInspector::new());
2038
2039 assert_eq!(evm.inspector.call_count(), 0,);
2041
2042 let result = evm.inspect_one_system_call_with_caller(caller, contract, Bytes::new())?;
2044 assert!(result.is_success());
2045 assert!(evm.inspector.call_count() > 0);
2046 }
2047
2048 Ok(())
2049 }
2050
2051 #[test]
2061 fn test_key_authorization_t1() -> eyre::Result<()> {
2062 use tempo_precompiles::account_keychain::AccountKeychain;
2063
2064 let key_pair = P256KeyPair::random();
2065 let caller = key_pair.address;
2066
2067 let mut evm = create_funded_evm_t1(caller);
2069
2070 let block = TempoBlockEnv::default();
2072 {
2073 let ctx = &mut evm.ctx;
2074 let internals = EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
2075 let mut provider = EvmPrecompileStorageProvider::new_max_gas(internals, &ctx.cfg);
2076
2077 StorageCtx::enter(&mut provider, || {
2078 TIP20Setup::path_usd(caller)
2079 .with_issuer(caller)
2080 .with_mint(caller, U256::from(10_000_000))
2081 .apply()
2082 })?;
2083 }
2084
2085 let access_key = P256KeyPair::random();
2089 let key_auth = KeyAuthorization {
2090 chain_id: 1,
2091 key_type: SignatureType::WebAuthn,
2092 key_id: access_key.address,
2093 expiry: None,
2094 limits: None,
2095 };
2096 let key_auth_sig = key_pair.sign_webauthn(key_auth.signature_hash().as_slice())?;
2097 let signed_key_auth = key_auth.into_signed(PrimitiveSignature::WebAuthn(key_auth_sig));
2098
2099 {
2101 let ctx = &mut evm.ctx;
2102 let internals = EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
2103 let mut provider = EvmPrecompileStorageProvider::new_max_gas(internals, &ctx.cfg);
2104
2105 let key_exists = StorageCtx::enter(&mut provider, || {
2106 let keychain = AccountKeychain::default();
2107 keychain.keys[caller][access_key.address].read()
2108 })?;
2109 assert_eq!(
2110 key_exists.expiry, 0,
2111 "Key should not exist before transaction"
2112 );
2113 }
2114
2115 let signed_auth = key_pair.create_signed_authorization(Address::repeat_byte(0x42))?;
2116
2117 let tx_low_gas = TxBuilder::new()
2119 .call_identity(&[0x01])
2120 .authorization(signed_auth)
2121 .key_authorization(signed_key_auth)
2122 .gas_limit(589_000)
2123 .build();
2124
2125 let signed_tx_low = key_pair.sign_tx(tx_low_gas)?;
2126 let tx_env_low = TempoTxEnv::from_recovered_tx(&signed_tx_low, caller);
2127
2128 let result_low = evm.transact_commit(tx_env_low);
2130
2131 let nonce_incremented = match &result_low {
2134 Ok(result) => {
2135 assert_eq!(result.gas_used(), 589_000, "Gas used should be gas limit");
2136 assert!(
2137 !result.is_success(),
2138 "Transaction with insufficient gas should fail"
2139 );
2140 true }
2142 Err(e) => {
2143 assert!(
2145 matches!(
2146 e,
2147 revm::context::result::EVMError::Transaction(
2148 TempoInvalidTransaction::InsufficientGasForIntrinsicCost { .. }
2149 )
2150 ),
2151 "Expected InsufficientGasForIntrinsicCost, got: {e:?}"
2152 );
2153 false }
2155 };
2156
2157 {
2160 let ctx = &mut evm.ctx;
2161 let internals = EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
2162 let mut provider = EvmPrecompileStorageProvider::new_max_gas(internals, &ctx.cfg);
2163
2164 let key_after_fail = StorageCtx::enter(&mut provider, || {
2165 let keychain = AccountKeychain::default();
2166 keychain.keys[caller][access_key.address].read()
2167 })?;
2168
2169 assert_eq!(
2170 key_after_fail,
2171 AuthorizedKey::default(),
2172 "Key should NOT be authorized when transaction fails due to insufficient gas"
2173 );
2174 }
2175
2176 let access_key2 = P256KeyPair::random();
2180 let key_auth2 = KeyAuthorization {
2181 chain_id: 1,
2182 key_type: SignatureType::WebAuthn,
2183 key_id: access_key2.address,
2184 expiry: None, limits: None, };
2187 let key_auth_sig2 = key_pair.sign_webauthn(key_auth2.signature_hash().as_slice())?;
2188 let signed_key_auth2 = key_auth2.into_signed(PrimitiveSignature::WebAuthn(key_auth_sig2));
2189
2190 let signed_auth2 = key_pair.create_signed_authorization(Address::repeat_byte(0x43))?;
2191
2192 let next_nonce = if nonce_incremented { 1 } else { 0 };
2194 let tx = TxBuilder::new()
2195 .call_identity(&[0x01])
2196 .authorization(signed_auth2)
2197 .key_authorization(signed_key_auth2)
2198 .nonce(next_nonce)
2199 .gas_limit(1_000_000)
2200 .build();
2201
2202 let signed_tx = key_pair.sign_tx(tx)?;
2203 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
2204
2205 let result = evm.transact_commit(tx_env)?;
2206 assert!(result.is_success(), "Transaction should succeed");
2207
2208 {
2210 let ctx = &mut evm.ctx;
2211 let internals = EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
2212 let mut provider = EvmPrecompileStorageProvider::new_max_gas(internals, &ctx.cfg);
2213
2214 let key_after_success = StorageCtx::enter(&mut provider, || {
2215 let keychain = AccountKeychain::default();
2216 keychain.keys[caller][access_key2.address].read()
2217 })?;
2218
2219 assert_eq!(
2220 key_after_success.expiry,
2221 u64::MAX,
2222 "Key should be authorized after successful transaction"
2223 );
2224 }
2225
2226 Ok(())
2227 }
2228
2229 #[test]
2244 fn test_create_nonce_replay_regression() -> eyre::Result<()> {
2245 use tempo_precompiles::account_keychain::AccountKeychain;
2246
2247 fn run_create_with_key_auth(
2250 spec: TempoHardfork,
2251 gas_limit: u64,
2252 ) -> eyre::Result<(u64, u64)> {
2253 let key_pair = P256KeyPair::random();
2254 let caller = key_pair.address;
2255
2256 let db = CacheDB::new(EmptyDB::new());
2257 let mut cfg = CfgEnv::<TempoHardfork>::default();
2258 cfg.spec = spec;
2259 cfg.gas_params = tempo_gas_params(spec);
2260
2261 let ctx = Context::mainnet()
2262 .with_db(db)
2263 .with_block(Default::default())
2264 .with_cfg(cfg)
2265 .with_tx(Default::default());
2266
2267 let mut evm = TempoEvm::new(ctx, ());
2268 fund_account(&mut evm, caller);
2269
2270 let block = TempoBlockEnv::default();
2271 {
2272 let ctx = &mut evm.ctx;
2273 let internals =
2274 EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
2275 let mut provider =
2279 EvmPrecompileStorageProvider::new_max_gas(internals, &Default::default());
2280 StorageCtx::enter(&mut provider, || {
2281 TIP20Setup::path_usd(caller)
2282 .with_issuer(caller)
2283 .with_mint(caller, U256::from(100_000_000))
2284 .apply()
2285 })?;
2286 }
2287
2288 let access_key = P256KeyPair::random();
2289 let key_auth = KeyAuthorization {
2290 chain_id: 1,
2291 key_type: SignatureType::WebAuthn,
2292 key_id: access_key.address,
2293 expiry: None,
2294 limits: None,
2295 };
2296 let key_auth_sig = key_pair.sign_webauthn(key_auth.signature_hash().as_slice())?;
2297 let signed_key_auth = key_auth.into_signed(PrimitiveSignature::WebAuthn(key_auth_sig));
2298
2299 let tx = TxBuilder::new()
2300 .create(&[0x60, 0x00, 0x60, 0x00, 0xF3])
2301 .key_authorization(signed_key_auth)
2302 .gas_limit(gas_limit)
2303 .build();
2304
2305 let signed_tx = key_pair.sign_tx(tx)?;
2306 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
2307 let _result = evm.transact_commit(tx_env);
2308
2309 let nonce = evm
2310 .ctx
2311 .db()
2312 .basic_ref(caller)
2313 .ok()
2314 .flatten()
2315 .map(|a| a.nonce)
2316 .unwrap_or(0);
2317
2318 let key_expiry = {
2319 let ctx = &mut evm.ctx;
2320 let internals =
2321 EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
2322 let mut provider =
2323 EvmPrecompileStorageProvider::new_max_gas(internals, &Default::default());
2324 let key = StorageCtx::enter(&mut provider, || {
2325 AccountKeychain::default().keys[caller][access_key.address].read()
2326 })?;
2327 key.expiry
2328 };
2329
2330 Ok((nonce, key_expiry))
2331 }
2332
2333 let (t1_nonce, t1_key_expiry) = run_create_with_key_auth(TempoHardfork::T1, 780_000)?;
2338 assert_eq!(
2339 t1_nonce, 0,
2340 "T1 bug: nonce must NOT be bumped when keychain OOGs"
2341 );
2342 assert_eq!(
2343 t1_key_expiry, 0,
2344 "T1 bug: key must NOT be authorized when keychain OOGs"
2345 );
2346
2347 let (t1b_nonce, t1b_key_expiry) = run_create_with_key_auth(TempoHardfork::T1B, 1_050_000)?;
2353 assert_eq!(
2354 t1b_nonce, 1,
2355 "T1B fix: nonce must be bumped after CREATE+KeyAuth"
2356 );
2357 assert_eq!(t1b_key_expiry, u64::MAX, "T1B fix: key must be authorized");
2358
2359 Ok(())
2360 }
2361
2362 #[test]
2373 fn test_double_charge_key_authorization_regression() -> eyre::Result<()> {
2374 fn run_call_with_key_auth(spec: TempoHardfork) -> eyre::Result<u64> {
2376 let key_pair = P256KeyPair::random();
2377 let caller = key_pair.address;
2378
2379 let db = CacheDB::new(EmptyDB::new());
2380 let mut cfg = CfgEnv::<TempoHardfork>::default();
2381 cfg.spec = spec;
2382 cfg.gas_params = tempo_gas_params(spec);
2383
2384 let ctx = Context::mainnet()
2385 .with_db(db)
2386 .with_block(Default::default())
2387 .with_cfg(cfg)
2388 .with_tx(Default::default());
2389
2390 let mut evm = TempoEvm::new(ctx, ());
2391 fund_account(&mut evm, caller);
2392
2393 let block = TempoBlockEnv::default();
2394 {
2395 let ctx = &mut evm.ctx;
2396 let internals =
2397 EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
2398 let mut provider =
2399 EvmPrecompileStorageProvider::new_max_gas(internals, &Default::default());
2400 StorageCtx::enter(&mut provider, || {
2401 TIP20Setup::path_usd(caller)
2402 .with_issuer(caller)
2403 .with_mint(caller, U256::from(100_000_000))
2404 .apply()
2405 })?;
2406 }
2407
2408 let access_key = P256KeyPair::random();
2409 let key_auth = KeyAuthorization {
2410 chain_id: 1,
2411 key_type: SignatureType::Secp256k1,
2412 key_id: access_key.address,
2413 expiry: None,
2414 limits: None,
2415 };
2416 let key_auth_sig = key_pair.sign_webauthn(key_auth.signature_hash().as_slice())?;
2417 let signed_key_auth = key_auth.into_signed(PrimitiveSignature::WebAuthn(key_auth_sig));
2418
2419 let tx = TxBuilder::new()
2420 .call_identity(&[])
2421 .key_authorization(signed_key_auth)
2422 .gas_limit(2_000_000)
2423 .build();
2424
2425 let signed_tx = key_pair.sign_tx(tx)?;
2426 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
2427 let result = evm.transact_commit(tx_env)?;
2428 assert!(result.is_success());
2429 Ok(result.gas_used())
2430 }
2431
2432 let t1_gas = run_call_with_key_auth(TempoHardfork::T1)?;
2433 let t1b_gas = run_call_with_key_auth(TempoHardfork::T1B)?;
2434
2435 assert!(
2438 t1_gas > 500_000,
2439 "T1 bug: should double-charge (got {t1_gas}, expected >500k)"
2440 );
2441
2442 assert!(
2446 t1b_gas < t1_gas,
2447 "T1B fix: gas ({t1b_gas}) must be less than T1 double-charge ({t1_gas})"
2448 );
2449
2450 Ok(())
2451 }
2452}