Skip to main content

tempo_revm/
evm.rs

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
15/// The Tempo EVM context type.
16pub type TempoContext<DB> = Context<TempoBlockEnv, TempoTxEnv, CfgEnv<TempoHardfork>, DB>;
17
18/// TempoEvm extends the Evm with Tempo specific types and logic.
19#[derive(Debug, derive_more::Deref, derive_more::DerefMut)]
20#[expect(clippy::type_complexity)]
21pub struct TempoEvm<DB: Database, I> {
22    /// Inner EVM type.
23    #[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    /// Preserved logs from the last transaction
33    pub logs: Vec<Log>,
34    /// The fee collected in `collectFeePreTx` call.
35    pub(crate) collected_fee: U256,
36    /// Initial gas cost. Used for key_authorization validation in collectFeePreTx.
37    ///
38    /// Additional initial gas cost is added for authorization_key setting in pre execution.
39    pub(crate) initial_gas: u64,
40}
41
42impl<DB: Database, I> TempoEvm<DB, I> {
43    /// Create a new Tempo EVM.
44    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    /// Inner helper function to create a new Tempo EVM with empty logs.
57    #[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    /// Consumed self and returns a new Evm type with given Inspector.
79    pub fn with_inspector<OINSP>(self, inspector: OINSP) -> TempoEvm<DB, OINSP> {
80        TempoEvm::new_inner(self.inner.with_inspector(inspector))
81    }
82
83    /// Consumes self and returns a new Evm type with given Precompiles.
84    pub fn with_precompiles(self, precompiles: PrecompilesMap) -> Self {
85        Self::new_inner(self.inner.with_precompiles(precompiles))
86    }
87
88    /// Consumes self and returns the inner Inspector.
89    pub fn into_inspector(self) -> I {
90        self.inner.into_inspector()
91    }
92
93    /// Take logs from the EVM.
94    #[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    // ==================== Test Constants ====================
238
239    /// Default balance for funded accounts (1 ETH)
240    const DEFAULT_BALANCE: u128 = 1_000_000_000_000_000_000;
241
242    /// Identity precompile address (0x04)
243    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    // ==================== Test Utility Functions ====================
248
249    /// Create an empty EVM instance with default settings and no inspector.
250    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    /// Create an EVM instance with a specific block timestamp.
261    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    /// Fund an account with the default balance (1 ETH).
276    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    /// Create an EVM with a funded account at the given address.
287    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    /// Create an EVM with T1C hardfork enabled and a funded account.
294    /// This applies TIP-1000 gas params via `tempo_gas_params()`.
295    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        // Apply TIP-1000 gas params for T1C hardfork
300        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    /// Create an EVM with a specific timestamp and a funded account.
314    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    /// Create an EVM with T1 hardfork, a specific timestamp, and a funded account.
324    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    /// Create an EVM instance with a custom inspector.
348    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    /// Helper struct for managing P256 key pairs in tests.
359    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        /// Generate a new random P256 key pair.
368        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        /// Create a WebAuthn signature for the given challenge.
385        fn sign_webauthn(&self, challenge: &[u8]) -> eyre::Result<WebAuthnSignature> {
386            // Create authenticator data
387            let mut authenticator_data = vec![0u8; 37];
388            authenticator_data[0..32].copy_from_slice(&[0xAA; 32]); // rpIdHash
389            authenticator_data[32] = 0x01; // UP flag set
390            authenticator_data[33..37].copy_from_slice(&[0, 0, 0, 0]); // signCount
391
392            // Create client data JSON
393            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            // Compute message hash
399            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            // Sign
406            let signature: p256::ecdsa::Signature = self.signing_key.sign_prehash(&message_hash)?;
407            let sig_bytes = signature.to_bytes();
408
409            // Construct WebAuthn data
410            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        /// Create a signed EIP-7702 authorization for the given delegate address.
424        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        /// Sign a transaction and return it ready for execution.
446        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        /// Sign a transaction with KeychainSignature wrapper (V2).
456        fn sign_tx_keychain(
457            &self,
458            tx: TempoTransaction,
459        ) -> eyre::Result<tempo_primitives::AASigned> {
460            // V2: sign keccak256(0x04 || sig_hash || user_address)
461            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    /// Builder for creating test transactions with sensible defaults.
473    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        /// Add a call to the identity precompile with the given input.
509        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        /// Add a call to a specific address.
519        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        /// Add a create call with the given initcode.
529        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        /// Add a call with a specific value transfer.
539        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    // ==================== End Test Utility Functions ====================
617
618    #[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        // Create a simple contract that returns output of the opcode.
645        ctx.db_mut().insert_account_info(
646            contract,
647            AccountInfo {
648                // MILLISTIMESTAMP PUSH0 MSTORE PUSH1 0x20 PUSH0 RETURN
649                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        // This test calls TIP20 setSupplyCap which emits a SupplyCapUpdate log event
682        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        // Create bytecode that calls setSupplyCap(uint256 newSupplyCap) on PATH_USD
691        // it is 36 bytes long
692        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        // CALL to PATH_USD precompile
705        // CALL(gas, addr, value, argsOffset, argsSize, retOffset, retSize)
706        bytecode_bytes.extend_from_slice(&[
707            opcode::PUSH1,
708            0x00, // retSize
709            opcode::PUSH1,
710            0x00, // retOffset
711            opcode::PUSH1,
712            0x24, // argsSize (4 + 32 = 36 = 0x24)
713            opcode::PUSH1,
714            0x00, // argsOffset
715            opcode::PUSH1,
716            0x00, // value = 0
717        ]);
718
719        // PUSH20 PATH_USD_ADDRESS
720        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, // gas
727            opcode::CALL,
728            opcode::POP, // pop success/failure
729            opcode::STOP,
730        ]);
731
732        let bytecode = Bytecode::new_raw(bytecode_bytes.into());
733
734        // Set up EVM with TIP20 infrastructure
735        let mut evm = create_evm_with_inspector(CountInspector::new());
736        // Set up TIP20 using the storage context pattern
737        {
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) // Grant admin role to contract so it can call setSupplyCap
747                    .apply()
748            })?;
749        }
750
751        // Deploy the contract bytecode
752        evm.ctx.db_mut().insert_account_info(
753            contract,
754            AccountInfo {
755                code: Some(bytecode),
756                ..Default::default()
757            },
758        );
759
760        // Execute a call to the contract
761        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        // Verify that a SupplyCapUpdate log was emitted by the TIP20 precompile
774        assert_eq!(result.result.logs().len(), 3);
775        // Log should be from TIP20_FACTORY
776        assert_eq!(result.result.logs()[0].address, PATH_USD_ADDRESS);
777
778        // Get the inspector and verify counts
779        let inspector = &evm.inspector;
780
781        // Verify CALL opcode was executed (the call to PATH_USD)
782        assert_eq!(inspector.get_count(opcode::CALL), 1);
783
784        assert_eq!(inspector.get_count(opcode::STOP), 1);
785
786        // Verify log count
787        assert_eq!(inspector.log_count(), 1);
788
789        // Verify call count (initial tx + CALL to PATH_USD)
790        assert_eq!(inspector.call_count(), 2);
791
792        // Should have 2 call ends
793        assert_eq!(inspector.call_end_count(), 2);
794
795        // ==================== Multi-call Tempo transaction test ====================
796        // Test inspector with a Tempo transaction that has multiple calls
797
798        let key_pair = P256KeyPair::random();
799        let tempo_caller = key_pair.address;
800
801        // Create signed authorization for Tempo tx
802        let signed_auth = key_pair.create_signed_authorization(Address::repeat_byte(0x42))?;
803
804        // Create a transaction with 3 calls to identity precompile
805        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        // Create a new EVM with fresh inspector for multi-call test
816        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        // Execute the multi-call transaction with inspector
826        let multi_result = multi_evm.inspect_tx(tx_env)?;
827        assert!(multi_result.result.is_success(),);
828
829        // Verify inspector tracked all 3 calls
830        let multi_inspector = &multi_evm.inspector;
831
832        // Multi-call Tempo transactions execute each call as a separate frame
833        // call_count = 3 (one for each identity precompile call)
834        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        // Create EVM
846        let mut evm = create_funded_evm(caller);
847        evm.block.basefee = 100_000_000_000;
848
849        // Set up TIP20 first (required for fee token validation)
850        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        // First tx: single call
866        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        // Second tx: two calls
905        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 creating and executing a Tempo transaction with:
937    /// - WebAuthn signature
938    /// - Authorization list (aa_auth_list)
939    /// - Two calls to the identity precompile (0x04)
940    #[test]
941    fn test_tempo_tx() -> eyre::Result<()> {
942        let key_pair = P256KeyPair::random();
943        let caller = key_pair.address;
944
945        // Create signed authorization
946        let signed_auth = key_pair.create_signed_authorization(Address::repeat_byte(0x42))?;
947
948        // Create and sign transaction with two calls to identity precompile
949        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        // Verify transaction has AA auth list
959        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        // Create EVM with T1C (required for V2 keychain signatures) and execute transaction
965        let mut evm = create_funded_evm_t1(caller);
966
967        // Execute the transaction and commit state changes
968        let result = evm.transact_commit(tx_env)?;
969        assert!(result.is_success());
970
971        // Test with KeychainSignature using key_authorization to provision the access key
972        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        // Create transaction with incremented nonce and key_authorization
984        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        // Explicitly test tempo_tx_env.signature.as_keychain()
997        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        // Validate KeychainSignature properties
1007        // KeychainSignature user_address should match the caller
1008        assert_eq!(keychain_sig.user_address, caller,);
1009
1010        // Verify the inner signature is WebAuthn
1011        assert!(matches!(
1012            keychain_sig.signature,
1013            PrimitiveSignature::WebAuthn(_)
1014        ));
1015
1016        // Verify key_id recovery works correctly using the transaction signature hash
1017        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        // Execute the transaction with keychain signature and commit state changes
1023        let result = evm.transact_commit(tx_env)?;
1024        assert!(result.is_success());
1025
1026        // Test a transaction with a failing call to TIP20 contract with wrong input
1027        let tx_fail = TxBuilder::new()
1028            .call(PATH_USD_ADDRESS, &[0x01, 0x02]) // Too short for TIP20
1029            .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        // Test 2D nonce transaction (nonce_key > 0)
1039        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        // Verify 2D nonce was incremented
1059        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        // Test second 2D nonce transaction
1068        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        // Verify nonce incremented again
1081        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 that Tempo transaction time window validation works correctly.
1092    /// Tests `valid_after` and `valid_before` fields against block timestamp.
1093    #[test]
1094    fn test_tempo_tx_time_window() -> eyre::Result<()> {
1095        let key_pair = P256KeyPair::random();
1096        let caller = key_pair.address;
1097
1098        // Create signed authorization
1099        let signed_auth = key_pair.create_signed_authorization(Address::repeat_byte(0x42))?;
1100
1101        // Helper to create and sign a transaction with time window parameters
1102        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        // Test case 1: Transaction fails when block_timestamp < valid_after
1113        {
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        // Test case 2: Transaction fails when block_timestamp >= valid_before
1136        {
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        // Test case 3: Transaction fails when block_timestamp > valid_before
1159        {
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        // Test case 4: Transaction succeeds when exactly at valid_after boundary
1182        {
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        // Test case 5: Transaction succeeds when within time window
1192        {
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        // Test case 6: Transaction fails when block_timestamp < valid_after in a window
1202        {
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        // Test case 7: Transaction fails when block_timestamp >= valid_before in a window
1225        {
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 executing a Tempo transaction where the first call is a Create kind.
1251    /// This should succeed as CREATE is allowed as the first call.
1252    #[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        // Simple contract that just returns: PUSH1 0x00 PUSH1 0x00 RETURN
1258        let initcode = vec![0x60, 0x00, 0x60, 0x00, 0xF3];
1259
1260        // Create transaction with CREATE as first call (no authorization list)
1261        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        // Create EVM and execute
1271        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 that a Tempo transaction fails when CREATE is the second call.
1280    /// CREATE must be the first call if used.
1281    #[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        // Simple initcode
1287        let initcode = vec![0x60, 0x00, 0x60, 0x00, 0xF3];
1288
1289        // Create transaction with a regular call first, then CREATE second
1290        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        // Create EVM and execute - should fail validation
1300        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 validate_aa_initial_tx_gas error cases.
1319    /// Tests all error paths in the AA initial transaction gas validation:
1320    /// - CreateInitCodeSizeLimit: when initcode exceeds max size
1321    /// - ValueTransferNotAllowedInAATx: when a call has non-zero value
1322    /// - InsufficientGasForIntrinsicCost: when gas_limit < intrinsic_gas
1323    #[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        // Helper to create EVM with signed transaction
1333        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        // Test 1: CreateInitCodeSizeLimit - initcode exceeds max size
1345        {
1346            // Default max initcode size is 49152 bytes (2 * MAX_CODE_SIZE)
1347            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        // Test 2: ValueTransferNotAllowedInAATx - call has non-zero value
1371        {
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        // Test 3: InsufficientGasForIntrinsicCost - gas_limit < intrinsic_gas
1391        {
1392            let mut evm = create_evm_with_tx(
1393                TxBuilder::new()
1394                    .call_identity(&[0x01, 0x02, 0x03, 0x04])
1395                    .gas_limit(1000) // Way too low, intrinsic cost is at least 21000
1396                    .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        // Test 4: InsufficientGasForIntrinsicCost - gas_limit < floor_gas (EIP-7623)
1415        {
1416            let large_calldata = vec![0x42; 1000]; // 1000 non-zero bytes = 1000 tokens
1417
1418            let mut evm = create_evm_with_tx(
1419                TxBuilder::new()
1420                    .call_identity(&large_calldata)
1421                    .gas_limit(31_000) // Above initial_gas (~30600) but below floor_gas (~32500)
1422                    .build(),
1423            )?;
1424
1425            let result = handler.validate_initial_tx_gas(&mut evm);
1426
1427            // Should fail because gas_limit < floor_gas
1428            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        // Test 5: Success when gas_limit >= both initial_gas and floor_gas
1443        // Verifies floor_gas > initial_gas for large calldata (EIP-7623 scenario)
1444        {
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) // Plenty of gas for both initial and floor
1451                    .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            // Verify floor_gas > initial_gas for this calldata (EIP-7623 scenario)
1462            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        // Test 6: Success case - sufficient gas provided (small calldata)
1471        {
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    // ==================== TIP-1000 EVM Configuration Tests ====================
1493
1494    /// Test that TempoEvm preserves initial fields when using with_inspector.
1495    #[test]
1496    fn test_tempo_evm_with_inspector_preserves_fields() {
1497        let evm = create_evm();
1498
1499        // Use with_inspector to get a new EVM with CountInspector
1500        let evm_with_inspector = evm.with_inspector(CountInspector::new());
1501
1502        // Verify fields are still initialized correctly
1503        assert_eq!(
1504            evm_with_inspector.initial_gas, 0,
1505            "initial_gas should be 0 after with_inspector"
1506        );
1507    }
1508
1509    /// Test that take_logs clears logs and returns them.
1510    #[test]
1511    fn test_tempo_evm_take_logs() {
1512        let mut evm = create_evm();
1513
1514        // Manually add some logs
1515        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        // Take logs
1529        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 AA transaction gas usage for simple identity precompile call.
1536    /// This establishes a baseline for gas comparison.
1537    /// Uses T1 hardfork for TIP-1000 gas costs.
1538    #[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        // Simple call to identity precompile
1546        // T1 adds 250k for new account creation (nonce == 0)
1547        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        // With T1 TIP-1000: new account cost (250k) + base intrinsic (21k) + WebAuthn (~3.4k) + calldata
1559        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 AA transaction gas usage with SSTORE to a new storage slot.
1569    /// This tests TIP-1000's increased SSTORE cost (250,000 gas for new slot).
1570    /// Uses T1 hardfork for TIP-1000 gas costs.
1571    #[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        // Deploy contract that does SSTORE to slot 0:
1580        // PUSH1 0x42 PUSH1 0x00 SSTORE STOP
1581        // This stores value 0x42 at slot 0
1582        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        // T1 costs: new account (250k) + SSTORE new slot (250k) + base costs
1592        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        // With TIP-1000: new account (250k) + SSTORE to new slot (250k) + base costs
1604        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 AA transaction gas usage with SSTORE to an existing storage slot (warm).
1614    /// Warm SSTORE should be much cheaper than cold SSTORE to a new slot.
1615    /// Uses T1 hardfork for TIP-1000 gas costs.
1616    #[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        // Deploy contract that does SSTORE to slot 0:
1625        // PUSH1 0x42 PUSH1 0x00 SSTORE STOP
1626        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        // Pre-populate storage slot 0 with a non-zero value
1636        evm.ctx
1637            .db_mut()
1638            .insert_account_storage(contract, U256::ZERO, U256::from(1))
1639            .unwrap();
1640
1641        // T1 costs: new account (250k) + SSTORE reset (not new slot) + base costs
1642        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        // SSTORE to existing non-zero slot (reset) doesn't trigger the 250k new slot cost
1657        // But still has new account cost (250k) + cold SLOAD (2100) + warm SSTORE reset (~2900)
1658        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 AA transaction gas comparison: multiple SSTORE operations.
1668    /// Uses T1 hardfork for TIP-1000 gas costs.
1669    #[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        // Deploy contract that does 2 SSTOREs to different slots:
1678        // PUSH1 0x11 PUSH1 0x00 SSTORE  (store 0x11 at slot 0)
1679        // PUSH1 0x22 PUSH1 0x01 SSTORE  (store 0x22 at slot 1)
1680        // STOP
1681        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        // T1 costs: new account (250k) + 2 SSTORE new slots (2 * 250k) + base costs
1691        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        // With TIP-1000: new account (250k) + 2 SSTOREs to new slots (2 * 250k) = 750k + base
1706        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 AA transaction gas for contract creation (CREATE).
1713    /// TIP-1000 increases TX create cost to 500,000 and new account cost to 250,000.
1714    /// Uses T1 hardfork for TIP-1000 gas costs.
1715    #[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        // Simple initcode: PUSH1 0x00 PUSH1 0x00 RETURN (deploys empty contract)
1723        let initcode = vec![0x60, 0x00, 0x60, 0x00, 0xF3];
1724
1725        // T1 costs: CREATE cost (500k) + new account for sender (250k) + new account for contract (250k)
1726        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        // With TIP-1000: CREATE cost (500k) + new account for sender (250k) + base costs
1738        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 AA transaction gas for CREATE with 2D nonce (nonce_key != 0).
1745    /// When caller account nonce is 0, an additional 250k gas is charged for account creation.
1746    /// Uses T1 hardfork for TIP-1000 gas costs.
1747    #[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        // Simple initcode: PUSH1 0x00 PUSH1 0x00 RETURN (deploys empty contract)
1755        let initcode = vec![0x60, 0x00, 0x60, 0x00, 0xF3];
1756        let nonce_key_2d = U256::from(42);
1757
1758        // Test 1: CREATE tx with 2D nonce, caller account nonce = 0
1759        // Should include: CREATE cost (500k) + new account for sender (250k) + 2D nonce sender creation (250k)
1760        let tx1 = TxBuilder::new()
1761            .create(&initcode)
1762            .nonce_key(nonce_key_2d)
1763            .gas_limit(2_000_000)
1764            .build();
1765
1766        // Verify that account nonce is 0 before transaction
1767        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        // With TIP-1000: CREATE cost (500k) + new account (250k) + 2D nonce sender creation (250k) + base
1786        assert_eq!(
1787            result1.gas_used(),
1788            1028720,
1789            "T1 CREATE with 2D nonce (caller.nonce=0) gas should be exact"
1790        );
1791
1792        // Test 2: Second CREATE tx with 2D nonce (different nonce_key)
1793        // Caller account nonce is now 1, so no extra 250k for caller account creation
1794        // Should include: CREATE cost (500k) + new account for sender (250k from nonce==0 check)
1795        // but NOT the extra 250k for 2D nonce caller creation since account.nonce != 0
1796        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) // 2D nonce = 0 (new key, starts at 0)
1801            .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        // With TIP-1000: CREATE cost (500k) + new account (250k) + base (no extra 250k since caller.nonce != 0)
1814        assert_eq!(
1815            result2.gas_used(),
1816            778720,
1817            "T1 CREATE with 2D nonce (caller.nonce=1) gas should be exact"
1818        );
1819
1820        // Verify the gas difference is exactly 250,000 (new_account_cost)
1821        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 that CREATE with expiring nonce charges 250k new_account_cost when caller.nonce == 0.
1831    /// This validates the fix for audit issue #182.
1832    #[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]; // PUSH0 PUSH0 RETURN
1839        let timestamp = 1000u64;
1840        let valid_before = timestamp + 30;
1841
1842        // CREATE with caller.nonce == 0 (should charge extra 250k)
1843        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        // CREATE with caller.nonce == 1 (no extra 250k)
1858        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        // The fix adds 250k when caller.nonce == 0 for CREATE with non-zero nonce_key
1881        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 gas comparison between single call and multiple calls.
1891    /// Uses T1 hardfork for TIP-1000 gas costs.
1892    #[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        // Test 1: Single call
1898        // T1 costs: new account (250k) + base costs
1899        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        // Test 2: Three calls
1912        // T1 costs: new account (250k) + 3 calls overhead
1913        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        // Three calls should cost more than single call
1928        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 AA transaction gas with SLOAD operation (cold vs warm access).
1943    /// Uses T1 hardfork for TIP-1000 gas costs.
1944    #[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        // Deploy contract that does 2 SLOADs from the same slot:
1953        // PUSH1 0x00 SLOAD POP  (cold SLOAD from slot 0)
1954        // PUSH1 0x00 SLOAD POP  (warm SLOAD from slot 0)
1955        // STOP
1956        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        // Pre-populate storage
1966        evm.ctx
1967            .db_mut()
1968            .insert_account_storage(contract, U256::ZERO, U256::from(0x1234))
1969            .unwrap();
1970
1971        // T1 costs: new account (250k) + SLOAD costs + base costs
1972        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        // T1 costs: new account (250k) + cold SLOAD (2100) + warm SLOAD (100) + cold account (~2.6k)
1984        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    // ==================== End TIP-1000 Tests ====================
1991
1992    /// Test system call functions and inspector management.
1993    /// Tests `system_call_one_with_caller`, `inspect_one_system_call_with_caller`, and `set_inspector`.
1994    #[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        // Deploy a simple contract that returns success
2000        // DIFFICULTY NUMBER PUSH1 0x00 PUSH1 0x00 RETURN (returns empty data)
2001        let bytecode = Bytecode::new_raw(bytes!("444360006000F3"));
2002
2003        // Test system_call_one_with_caller (no inspector needed)
2004        {
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        // Test set_inspector and inspect_one_system_call_with_caller
2019        {
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            // Test inspect_one_system_call_with_caller
2030            let result = evm.inspect_one_system_call_with_caller(caller, contract, Bytes::new())?;
2031            assert!(result.is_success());
2032
2033            // Verify inspector was called
2034            assert!(evm.inspector.call_count() > 0,);
2035
2036            // Test set_inspector - replace with a fresh CountInspector
2037            evm.set_inspector(CountInspector::new());
2038
2039            // Verify the new inspector starts fresh
2040            assert_eq!(evm.inspector.call_count(), 0,);
2041
2042            // Run another system call and verify new inspector records it
2043            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 that key_authorization works correctly with T1 hardfork.
2052    ///
2053    /// This test verifies the key_authorization flow works in the T1 EVM.
2054    /// It ensures that:
2055    /// 1. Keys are NOT authorized when transaction fails due to insufficient gas
2056    /// 2. Keys ARE authorized when transaction succeeds with sufficient gas
2057    ///
2058    /// Related fix: The handler creates a checkpoint before key_authorization
2059    /// precompile execution and reverts it on OOG. This ensures storage consistency.
2060    #[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        // Create a T1 EVM (the fix only applies to T1)
2068        let mut evm = create_funded_evm_t1(caller);
2069
2070        // Set up TIP20 for fee payment
2071        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        // ==================== Test 1: INSUFFICIENT gas ====================
2086        // First, try with insufficient gas - key should NOT be authorized
2087
2088        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        // Verify key does NOT exist before the transaction
2100        {
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        // Insufficient gas - will cause OOG during key_authorization processing
2118        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        // Execute the transaction - it should fail due to insufficient gas
2129        let result_low = evm.transact_commit(tx_env_low);
2130
2131        // Transaction should fail (either rejected or OOG).
2132        // Track whether the nonce was incremented (committed OOG vs validation rejection).
2133        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 // OOG: tx committed, nonce incremented
2141            }
2142            Err(e) => {
2143                // Transaction rejected during validation - must be InsufficientGasForIntrinsicCost
2144                assert!(
2145                    matches!(
2146                        e,
2147                        revm::context::result::EVMError::Transaction(
2148                            TempoInvalidTransaction::InsufficientGasForIntrinsicCost { .. }
2149                        )
2150                    ),
2151                    "Expected InsufficientGasForIntrinsicCost, got: {e:?}"
2152                );
2153                false // Validation rejection: nonce NOT incremented
2154            }
2155        };
2156
2157        // CRITICAL: Verify the key was NOT authorized
2158        // This tests that storage changes are properly reverted on failure
2159        {
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        // ==================== Test 2: SUFFICIENT gas ====================
2177        // Now try with sufficient gas - key should be authorized
2178
2179        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, // Never expires (u64::MAX)
2185            limits: None, // No spending limits
2186        };
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        // Execute transaction with sufficient gas
2193        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        // Verify the key was authorized
2209        {
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    /// Regression: CREATE nonce replay vulnerability — demonstrates the T1
2230    /// bug and verifies the T1B fix.
2231    ///
2232    /// **The bug (T1):** An AA CREATE transaction with a KeyAuthorization runs
2233    /// `authorize_key` in a gas-metered precompile call. TIP-1000 SSTORE costs
2234    /// (250k) easily exceed the remaining gas after intrinsic deduction, causing
2235    /// OutOfGas. The handler then sets `evm.initial_gas = u64::MAX`, which
2236    /// short-circuits execution before `make_create_frame` bumps the protocol
2237    /// nonce. The nonce stays at 0, making the signed transaction replayable.
2238    ///
2239    /// **The fix (T1B):** The precompile runs with `gas_limit = u64::MAX`,
2240    /// eliminating the OOG path. Gas is accounted for solely in intrinsic gas.
2241    /// The CREATE frame is always constructed, the nonce is always bumped, and
2242    /// replay is impossible.
2243    #[test]
2244    fn test_create_nonce_replay_regression() -> eyre::Result<()> {
2245        use tempo_precompiles::account_keychain::AccountKeychain;
2246
2247        /// Run a CREATE+KeyAuth transaction on the given hardfork and return
2248        /// (caller_nonce_after, key_expiry).
2249        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                // Use default cfg for TIP20 setup — the test infrastructure's
2276                // `is_initialized` check uses an unsafe `as_hashmap()` cast that
2277                // only works with default gas params.
2278                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        // --- T1: demonstrate the bug ---
2334        // T1 intrinsic gas for this tx is ~560k (21k base + 500k CREATE + 35k
2335        // KeyAuth heuristic). Gas limit 780k leaves ~220k for the precompile,
2336        // which is below the 250k SSTORE cost → OOG → nonce NOT bumped.
2337        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        // --- T1B: verify the fix ---
2348        // T1B intrinsic gas is ~1.04M (21k base + 500k CREATE + 260k KeyAuth
2349        // + calldata + sig). Gas limit 1.05M is just enough to pass intrinsic
2350        // validation. The precompile runs with unlimited gas, so the nonce is
2351        // always bumped.
2352        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    /// Regression: double gas charging for KeyAuthorization — demonstrates the
2363    /// T1 bug and verifies the T1B fix.
2364    ///
2365    /// **The bug (T1):** The handler charges both a heuristic intrinsic gas
2366    /// estimate AND the metered precompile gas (`evm.initial_gas += gas_used`),
2367    /// resulting in a double charge. With TIP-1000 SSTORE at 250k, a simple
2368    /// KeyAuthorization (0 limits) costs ~530k on T1 instead of ~280k.
2369    ///
2370    /// **The fix (T1B):** Only the intrinsic gas is charged; the precompile runs
2371    /// with unlimited gas and its cost is NOT added to `initial_gas` afterward.
2372    #[test]
2373    fn test_double_charge_key_authorization_regression() -> eyre::Result<()> {
2374        /// Run a CALL+KeyAuth transaction and return gas_used.
2375        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        // T1 double-charges: intrinsic heuristic (~35k) + metered precompile
2436        // (~250k SSTORE) on top of base tx gas, resulting in >500k.
2437        assert!(
2438            t1_gas > 500_000,
2439            "T1 bug: should double-charge (got {t1_gas}, expected >500k)"
2440        );
2441
2442        // T1B charges only once via accurate intrinsic gas (~255k for
2443        // sig+sload+sstore) + base tx. Total ~541k, well below the ~790k
2444        // that double-charging would produce.
2445        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}