1use crate::{TempoBlockEnv, TempoTxEnv, instructions};
2use alloy_evm::{Database, precompiles::PrecompilesMap};
3use alloy_primitives::{Address, U256};
4use revm::{
5 Context, Inspector,
6 context::{Cfg, CfgEnv, ContextError, Evm, FrameStack},
7 handler::{
8 EthFrame, EvmTr, FrameInitOrResult, FrameTr, ItemOrResult, instructions::EthInstructions,
9 },
10 inspector::InspectorEvmTr,
11 interpreter::{InitialAndFloorGas, interpreter::EthInterpreter},
12};
13use tempo_chainspec::hardfork::TempoHardfork;
14
15pub type TempoContext<DB> = Context<TempoBlockEnv, TempoTxEnv, CfgEnv<TempoHardfork>, DB>;
17
18#[derive(Debug, derive_more::Deref, derive_more::DerefMut)]
20#[expect(clippy::type_complexity)]
21pub struct TempoEvm<DB: Database, I> {
22 #[deref]
24 #[deref_mut]
25 pub inner: Evm<
26 TempoContext<DB>,
27 I,
28 EthInstructions<EthInterpreter, TempoContext<DB>>,
29 PrecompilesMap,
30 EthFrame<EthInterpreter>,
31 >,
32 pub(crate) collected_fee: U256,
34 pub(crate) fee_token: Option<Address>,
36 pub(crate) key_expiry: Option<u64>,
39 pub skip_valid_after_check: bool,
44 pub skip_liquidity_check: bool,
49}
50
51impl<DB: Database, I> TempoEvm<DB, I> {
52 pub fn new(ctx: TempoContext<DB>, inspector: I) -> Self {
54 let precompiles = tempo_precompiles::tempo_precompiles(&ctx.cfg);
55
56 Self::new_inner(Evm {
57 instruction: instructions::tempo_instructions(ctx.cfg.spec),
58 ctx,
59 inspector,
60 precompiles,
61 frame_stack: FrameStack::new(),
62 })
63 }
64
65 #[inline]
67 #[expect(clippy::type_complexity)]
68 fn new_inner(
69 inner: Evm<
70 TempoContext<DB>,
71 I,
72 EthInstructions<EthInterpreter, TempoContext<DB>>,
73 PrecompilesMap,
74 EthFrame<EthInterpreter>,
75 >,
76 ) -> Self {
77 Self {
78 inner,
79 collected_fee: U256::ZERO,
80 fee_token: None,
81 key_expiry: None,
82 skip_valid_after_check: false,
83 skip_liquidity_check: false,
84 }
85 }
86
87 pub(crate) fn initial_gas_and_reservoir(
89 &self,
90 init_and_floor_gas: &InitialAndFloorGas,
91 ) -> (u64, u64) {
92 if !self.cfg.spec.is_t0() && init_and_floor_gas.initial_total_gas > self.tx.gas_limit {
96 (u64::MAX, 0)
97 } else {
98 init_and_floor_gas.initial_gas_and_reservoir(
99 self.tx.gas_limit,
100 self.cfg.tx_gas_limit_cap(),
101 self.cfg.is_amsterdam_eip8037_enabled(),
102 )
103 }
104 }
105}
106
107impl<DB: Database, I> TempoEvm<DB, I> {
108 pub fn with_inspector<OINSP>(self, inspector: OINSP) -> TempoEvm<DB, OINSP> {
110 TempoEvm::new_inner(self.inner.with_inspector(inspector))
111 }
112
113 pub fn with_precompiles(self, precompiles: PrecompilesMap) -> Self {
115 Self::new_inner(self.inner.with_precompiles(precompiles))
116 }
117
118 pub fn into_inspector(self) -> I {
120 self.inner.into_inspector()
121 }
122
123 pub fn clear(&mut self) {
125 self.fee_token = None;
126 self.key_expiry = None;
127 }
128}
129
130impl<DB, I> EvmTr for TempoEvm<DB, I>
131where
132 DB: Database,
133{
134 type Context = TempoContext<DB>;
135 type Instructions = EthInstructions<EthInterpreter, TempoContext<DB>>;
136 type Precompiles = PrecompilesMap;
137 type Frame = EthFrame<EthInterpreter>;
138
139 fn all(
140 &self,
141 ) -> (
142 &Self::Context,
143 &Self::Instructions,
144 &Self::Precompiles,
145 &FrameStack<Self::Frame>,
146 ) {
147 self.inner.all()
148 }
149
150 fn all_mut(
151 &mut self,
152 ) -> (
153 &mut Self::Context,
154 &mut Self::Instructions,
155 &mut Self::Precompiles,
156 &mut FrameStack<Self::Frame>,
157 ) {
158 self.inner.all_mut()
159 }
160
161 fn frame_stack(&mut self) -> &mut FrameStack<Self::Frame> {
162 &mut self.inner.frame_stack
163 }
164
165 fn frame_init(
166 &mut self,
167 frame_input: <Self::Frame as FrameTr>::FrameInit,
168 ) -> Result<
169 ItemOrResult<&mut Self::Frame, <Self::Frame as FrameTr>::FrameResult>,
170 ContextError<DB::Error>,
171 > {
172 self.inner.frame_init(frame_input)
173 }
174
175 fn frame_run(&mut self) -> Result<FrameInitOrResult<Self::Frame>, ContextError<DB::Error>> {
176 self.inner.frame_run()
177 }
178
179 fn frame_return_result(
180 &mut self,
181 result: <Self::Frame as FrameTr>::FrameResult,
182 ) -> Result<Option<<Self::Frame as FrameTr>::FrameResult>, ContextError<DB::Error>> {
183 self.inner.frame_return_result(result)
184 }
185}
186
187impl<DB, I> InspectorEvmTr for TempoEvm<DB, I>
188where
189 DB: Database,
190 I: Inspector<TempoContext<DB>>,
191{
192 type Inspector = I;
193
194 fn all_inspector(
195 &self,
196 ) -> (
197 &Self::Context,
198 &Self::Instructions,
199 &Self::Precompiles,
200 &FrameStack<Self::Frame>,
201 &Self::Inspector,
202 ) {
203 self.inner.all_inspector()
204 }
205
206 fn all_mut_inspector(
207 &mut self,
208 ) -> (
209 &mut Self::Context,
210 &mut Self::Instructions,
211 &mut Self::Precompiles,
212 &mut FrameStack<Self::Frame>,
213 &mut Self::Inspector,
214 ) {
215 self.inner.all_mut_inspector()
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use crate::gas_params::tempo_gas_params;
222 use alloy_eips::eip7702::Authorization;
223 use alloy_evm::FromRecoveredTx;
224 use alloy_primitives::{Address, Bytes, TxKind, U256, bytes, hex};
225 use alloy_sol_types::SolCall;
226 use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
227 use p256::{
228 ecdsa::{SigningKey, signature::hazmat::PrehashSigner},
229 elliptic_curve::rand_core::OsRng,
230 };
231 use reth_evm::EvmInternals;
232 use revm::{
233 Context, DatabaseRef, ExecuteCommitEvm, ExecuteEvm, InspectEvm, MainContext,
234 bytecode::opcode,
235 context::{
236 CfgEnv, ContextTr, TxEnv,
237 result::{ExecutionResult, HaltReason},
238 },
239 database::{CacheDB, EmptyDB},
240 handler::system_call::SystemCallEvm,
241 inspector::{CountInspector, InspectSystemCallEvm},
242 state::{AccountInfo, Bytecode},
243 };
244 use sha2::{Digest, Sha256};
245 use tempo_chainspec::hardfork::TempoHardfork;
246 use tempo_precompiles::{
247 AuthorizedKey, NONCE_PRECOMPILE_ADDRESS, PATH_USD_ADDRESS,
248 nonce::NonceManager,
249 storage::{Handler, StorageCtx, evm::EvmPrecompileStorageProvider},
250 test_util::TIP20Setup,
251 tip20::{ITIP20, TIP20Token},
252 };
253 use tempo_primitives::{
254 TempoTransaction,
255 transaction::{
256 KeyAuthorization, KeychainSignature, SignatureType, TempoSignedAuthorization,
257 tempo_transaction::Call,
258 tt_signature::{
259 PrimitiveSignature, TempoSignature, WebAuthnSignature, derive_p256_address,
260 normalize_p256_s,
261 },
262 },
263 };
264
265 use crate::{TempoBlockEnv, TempoEvm, TempoHaltReason, TempoInvalidTransaction, TempoTxEnv};
266 use revm::context::result::InvalidTransaction;
267
268 const DEFAULT_BALANCE: u128 = 1_000_000_000_000_000_000;
272
273 const IDENTITY_PRECOMPILE: Address = Address::new([
275 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x04,
276 ]);
277
278 fn create_evm() -> TempoEvm<CacheDB<EmptyDB>, ()> {
282 let db = CacheDB::new(EmptyDB::new());
283 let ctx = Context::mainnet()
284 .with_db(db)
285 .with_block(Default::default())
286 .with_cfg(Default::default())
287 .with_tx(Default::default());
288 TempoEvm::new(ctx, ())
289 }
290
291 fn create_evm_with_timestamp(timestamp: u64) -> TempoEvm<CacheDB<EmptyDB>, ()> {
293 let db = CacheDB::new(EmptyDB::new());
294 let mut block = TempoBlockEnv::default();
295 block.inner.timestamp = U256::from(timestamp);
296
297 let ctx = Context::mainnet()
298 .with_db(db)
299 .with_block(block)
300 .with_cfg(Default::default())
301 .with_tx(Default::default());
302
303 TempoEvm::new(ctx, ())
304 }
305
306 fn fund_account(evm: &mut TempoEvm<CacheDB<EmptyDB>, ()>, address: Address) {
308 evm.ctx.db_mut().insert_account_info(
309 address,
310 AccountInfo {
311 balance: U256::from(DEFAULT_BALANCE),
312 ..Default::default()
313 },
314 );
315 }
316
317 fn create_funded_evm(address: Address) -> TempoEvm<CacheDB<EmptyDB>, ()> {
319 let mut evm = create_evm();
320 fund_account(&mut evm, address);
321 evm
322 }
323
324 fn create_funded_evm_t1(address: Address) -> TempoEvm<CacheDB<EmptyDB>, ()> {
327 let db = CacheDB::new(EmptyDB::new());
328 let mut cfg = CfgEnv::<TempoHardfork>::default();
329 cfg.spec = TempoHardfork::T1C;
330 cfg.gas_params = tempo_gas_params(TempoHardfork::T1C);
332
333 let ctx = Context::mainnet()
334 .with_db(db)
335 .with_block(Default::default())
336 .with_cfg(cfg)
337 .with_tx(Default::default());
338
339 let mut evm = TempoEvm::new(ctx, ());
340 fund_account(&mut evm, address);
341 evm
342 }
343
344 fn create_funded_evm_t3(address: Address) -> TempoEvm<CacheDB<EmptyDB>, ()> {
346 let db = CacheDB::new(EmptyDB::new());
347 let mut cfg = CfgEnv::<TempoHardfork>::default();
348 cfg.spec = TempoHardfork::T3;
349 cfg.gas_params = tempo_gas_params(TempoHardfork::T3);
350
351 let ctx = Context::mainnet()
352 .with_db(db)
353 .with_block(Default::default())
354 .with_cfg(cfg)
355 .with_tx(Default::default());
356
357 let mut evm = TempoEvm::new(ctx, ());
358 fund_account(&mut evm, address);
359 evm
360 }
361
362 fn create_funded_evm_t4(address: Address) -> TempoEvm<CacheDB<EmptyDB>, ()> {
364 let db = CacheDB::new(EmptyDB::new());
365 let mut cfg = CfgEnv::<TempoHardfork>::default();
366 cfg.spec = TempoHardfork::T4;
367 cfg.gas_params = tempo_gas_params(TempoHardfork::T4);
368 cfg.enable_amsterdam_eip8037 = true;
369
370 let ctx = Context::mainnet()
371 .with_db(db)
372 .with_block(Default::default())
373 .with_cfg(cfg)
374 .with_tx(Default::default());
375
376 let mut evm = TempoEvm::new(ctx, ());
377 fund_account(&mut evm, address);
378 evm
379 }
380
381 fn create_funded_evm_with_timestamp(
383 address: Address,
384 timestamp: u64,
385 ) -> TempoEvm<CacheDB<EmptyDB>, ()> {
386 let mut evm = create_evm_with_timestamp(timestamp);
387 fund_account(&mut evm, address);
388 evm
389 }
390
391 fn create_funded_evm_t1_with_timestamp(
393 address: Address,
394 timestamp: u64,
395 ) -> TempoEvm<CacheDB<EmptyDB>, ()> {
396 let db = CacheDB::new(EmptyDB::new());
397 let mut cfg = CfgEnv::<TempoHardfork>::default();
398 cfg.spec = TempoHardfork::T1;
399 cfg.gas_params = tempo_gas_params(TempoHardfork::T1);
400
401 let mut block = TempoBlockEnv::default();
402 block.inner.timestamp = U256::from(timestamp);
403
404 let ctx = Context::mainnet()
405 .with_db(db)
406 .with_block(block)
407 .with_cfg(cfg)
408 .with_tx(Default::default());
409
410 let mut evm = TempoEvm::new(ctx, ());
411 fund_account(&mut evm, address);
412 evm
413 }
414
415 fn create_evm_with_inspector<I>(inspector: I) -> TempoEvm<CacheDB<EmptyDB>, I> {
417 let db = CacheDB::new(EmptyDB::new());
418 let ctx = Context::mainnet()
419 .with_db(db)
420 .with_block(Default::default())
421 .with_cfg(Default::default())
422 .with_tx(Default::default());
423 TempoEvm::new(ctx, inspector)
424 }
425
426 struct P256KeyPair {
428 signing_key: SigningKey,
429 pub_key_x: alloy_primitives::B256,
430 pub_key_y: alloy_primitives::B256,
431 address: Address,
432 }
433
434 impl P256KeyPair {
435 fn random() -> Self {
437 let signing_key = SigningKey::random(&mut OsRng);
438 let verifying_key = signing_key.verifying_key();
439 let encoded_point = verifying_key.to_encoded_point(false);
440 let pub_key_x = alloy_primitives::B256::from_slice(encoded_point.x().unwrap().as_ref());
441 let pub_key_y = alloy_primitives::B256::from_slice(encoded_point.y().unwrap().as_ref());
442 let address = derive_p256_address(&pub_key_x, &pub_key_y);
443
444 Self {
445 signing_key,
446 pub_key_x,
447 pub_key_y,
448 address,
449 }
450 }
451
452 fn sign_webauthn(&self, challenge: &[u8]) -> eyre::Result<WebAuthnSignature> {
454 let mut authenticator_data = vec![0u8; 37];
456 authenticator_data[0..32].copy_from_slice(&[0xAA; 32]); authenticator_data[32] = 0x01; authenticator_data[33..37].copy_from_slice(&[0, 0, 0, 0]); let challenge_b64url = URL_SAFE_NO_PAD.encode(challenge);
462 let client_data_json = format!(
463 r#"{{"type":"webauthn.get","challenge":"{challenge_b64url}","origin":"https://example.com","crossOrigin":false}}"#
464 );
465
466 let client_data_hash = Sha256::digest(client_data_json.as_bytes());
468 let mut final_hasher = Sha256::new();
469 final_hasher.update(&authenticator_data);
470 final_hasher.update(client_data_hash);
471 let message_hash = final_hasher.finalize();
472
473 let signature: p256::ecdsa::Signature = self.signing_key.sign_prehash(&message_hash)?;
475 let sig_bytes = signature.to_bytes();
476
477 let mut webauthn_data = Vec::new();
479 webauthn_data.extend_from_slice(&authenticator_data);
480 webauthn_data.extend_from_slice(client_data_json.as_bytes());
481
482 Ok(WebAuthnSignature {
483 webauthn_data: Bytes::from(webauthn_data),
484 r: alloy_primitives::B256::from_slice(&sig_bytes[0..32]),
485 s: normalize_p256_s(&sig_bytes[32..64]).map_err(|e| eyre::eyre!(e))?,
486 pub_key_x: self.pub_key_x,
487 pub_key_y: self.pub_key_y,
488 })
489 }
490
491 fn create_signed_authorization(
493 &self,
494 delegate_address: Address,
495 ) -> eyre::Result<TempoSignedAuthorization> {
496 let auth = Authorization {
497 chain_id: U256::from(1),
498 address: delegate_address,
499 nonce: 0,
500 };
501
502 let mut sig_buf = Vec::new();
503 sig_buf.push(tempo_primitives::transaction::tt_authorization::MAGIC);
504 alloy_rlp::Encodable::encode(&auth, &mut sig_buf);
505 let auth_sig_hash = alloy_primitives::keccak256(&sig_buf);
506
507 let webauthn_sig = self.sign_webauthn(auth_sig_hash.as_slice())?;
508 let aa_sig = TempoSignature::Primitive(PrimitiveSignature::WebAuthn(webauthn_sig));
509
510 Ok(TempoSignedAuthorization::new_unchecked(auth, aa_sig))
511 }
512
513 fn sign_tx(&self, tx: TempoTransaction) -> eyre::Result<tempo_primitives::AASigned> {
515 let webauthn_sig = self.sign_webauthn(tx.signature_hash().as_slice())?;
516 Ok(
517 tx.into_signed(TempoSignature::Primitive(PrimitiveSignature::WebAuthn(
518 webauthn_sig,
519 ))),
520 )
521 }
522
523 fn sign_tx_keychain(
525 &self,
526 tx: TempoTransaction,
527 ) -> eyre::Result<tempo_primitives::AASigned> {
528 let sig_hash = tx.signature_hash();
530 let effective_hash = alloy_primitives::keccak256(
531 [&[0x04], sig_hash.as_slice(), self.address.as_slice()].concat(),
532 );
533 let webauthn_sig = self.sign_webauthn(effective_hash.as_slice())?;
534 let keychain_sig =
535 KeychainSignature::new(self.address, PrimitiveSignature::WebAuthn(webauthn_sig));
536 Ok(tx.into_signed(TempoSignature::Keychain(keychain_sig)))
537 }
538 }
539
540 struct TxBuilder {
542 calls: Vec<Call>,
543 nonce: u64,
544 nonce_key: U256,
545 gas_limit: u64,
546 max_fee_per_gas: u128,
547 max_priority_fee_per_gas: u128,
548 valid_before: Option<u64>,
549 valid_after: Option<u64>,
550 authorization_list: Vec<TempoSignedAuthorization>,
551 key_authorization: Option<tempo_primitives::transaction::SignedKeyAuthorization>,
552 }
553
554 impl Default for TxBuilder {
555 fn default() -> Self {
556 Self {
557 calls: vec![],
558 nonce: 0,
559 nonce_key: U256::ZERO,
560 gas_limit: 1_000_000,
561 max_fee_per_gas: 0,
562 max_priority_fee_per_gas: 0,
563 valid_before: Some(u64::MAX),
564 valid_after: None,
565 authorization_list: vec![],
566 key_authorization: None,
567 }
568 }
569 }
570
571 impl TxBuilder {
572 fn new() -> Self {
573 Self::default()
574 }
575
576 fn call_identity(mut self, input: &[u8]) -> Self {
578 self.calls.push(Call {
579 to: TxKind::Call(IDENTITY_PRECOMPILE),
580 value: U256::ZERO,
581 input: Bytes::from(input.to_vec()),
582 });
583 self
584 }
585
586 fn call(mut self, to: Address, input: &[u8]) -> Self {
588 self.calls.push(Call {
589 to: TxKind::Call(to),
590 value: U256::ZERO,
591 input: Bytes::from(input.to_vec()),
592 });
593 self
594 }
595
596 fn create(mut self, initcode: &[u8]) -> Self {
598 self.calls.push(Call {
599 to: TxKind::Create,
600 value: U256::ZERO,
601 input: Bytes::from(initcode.to_vec()),
602 });
603 self
604 }
605
606 fn call_with_value(mut self, to: Address, input: &[u8], value: U256) -> Self {
608 self.calls.push(Call {
609 to: TxKind::Call(to),
610 value,
611 input: Bytes::from(input.to_vec()),
612 });
613 self
614 }
615
616 fn nonce(mut self, nonce: u64) -> Self {
617 self.nonce = nonce;
618 self
619 }
620
621 fn nonce_key(mut self, nonce_key: U256) -> Self {
622 self.nonce_key = nonce_key;
623 self
624 }
625
626 fn gas_limit(mut self, gas_limit: u64) -> Self {
627 self.gas_limit = gas_limit;
628 self
629 }
630
631 fn with_max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self {
632 self.max_fee_per_gas = max_fee_per_gas;
633 self
634 }
635
636 fn with_max_priority_fee_per_gas(mut self, max_priority_fee_per_gas: u128) -> Self {
637 self.max_priority_fee_per_gas = max_priority_fee_per_gas;
638 self
639 }
640
641 fn valid_before(mut self, valid_before: Option<u64>) -> Self {
642 self.valid_before = valid_before;
643 self
644 }
645
646 fn valid_after(mut self, valid_after: Option<u64>) -> Self {
647 self.valid_after = valid_after;
648 self
649 }
650
651 fn authorization(mut self, auth: TempoSignedAuthorization) -> Self {
652 self.authorization_list.push(auth);
653 self
654 }
655
656 fn key_authorization(
657 mut self,
658 key_auth: tempo_primitives::transaction::SignedKeyAuthorization,
659 ) -> Self {
660 self.key_authorization = Some(key_auth);
661 self
662 }
663
664 fn build(self) -> TempoTransaction {
665 TempoTransaction {
666 chain_id: 1,
667 fee_token: None,
668 max_priority_fee_per_gas: self.max_priority_fee_per_gas,
669 max_fee_per_gas: self.max_fee_per_gas,
670 gas_limit: self.gas_limit,
671 calls: self.calls,
672 access_list: Default::default(),
673 nonce_key: self.nonce_key,
674 nonce: self.nonce,
675 fee_payer_signature: None,
676 valid_before: self.valid_before.and_then(core::num::NonZeroU64::new),
677 valid_after: self.valid_after.and_then(core::num::NonZeroU64::new),
678 key_authorization: self.key_authorization,
679 tempo_authorization_list: self.authorization_list,
680 }
681 }
682 }
683
684 #[test_case::test_case(TempoHardfork::T1)]
687 #[test_case::test_case(TempoHardfork::T1C)]
688 fn test_access_millis_timestamp(spec: TempoHardfork) -> eyre::Result<()> {
689 let db = CacheDB::new(EmptyDB::new());
690
691 let mut ctx = Context::mainnet()
692 .with_db(db)
693 .with_block(TempoBlockEnv::default())
694 .with_cfg(CfgEnv::<TempoHardfork>::default())
695 .with_tx(Default::default());
696
697 ctx.cfg.spec = spec;
698 ctx.block.timestamp = U256::from(1000);
699 ctx.block.timestamp_millis_part = 100;
700
701 let mut tempo_evm = TempoEvm::new(ctx, ());
702 let ctx = &mut tempo_evm.ctx;
703
704 let internals = EvmInternals::new(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx);
705 let mut storage = EvmPrecompileStorageProvider::new_max_gas(internals, &ctx.cfg);
706
707 _ = StorageCtx::enter(&mut storage, || TIP20Setup::path_usd(Address::ZERO).apply())?;
708 drop(storage);
709
710 let contract = Address::random();
711
712 ctx.db_mut().insert_account_info(
714 contract,
715 AccountInfo {
716 code: Some(Bytecode::new_raw(bytes!("0x4F5F5260205FF3"))),
718 ..Default::default()
719 },
720 );
721
722 let tx_env = TxEnv {
723 kind: contract.into(),
724 ..Default::default()
725 };
726 let result = tempo_evm.transact_one(tx_env.into())?;
727
728 if !spec.is_t1c() {
729 assert!(result.is_success());
730 assert_eq!(
731 U256::from_be_slice(result.output().unwrap()),
732 U256::from(1000100)
733 );
734 } else {
735 assert!(matches!(
736 result,
737 ExecutionResult::Halt {
738 reason: TempoHaltReason::Ethereum(HaltReason::OpcodeNotFound),
739 ..
740 }
741 ));
742 }
743
744 Ok(())
745 }
746
747 #[test]
748 fn test_inspector_calls() -> eyre::Result<()> {
749 let caller = Address::repeat_byte(0x01);
751 let contract = Address::repeat_byte(0x42);
752
753 let input_bytes = ITIP20::setSupplyCapCall {
754 newSupplyCap: U256::from(100),
755 }
756 .abi_encode();
757
758 let mut bytecode_bytes = vec![];
761
762 for (i, &byte) in input_bytes.iter().enumerate() {
763 bytecode_bytes.extend_from_slice(&[
764 opcode::PUSH1,
765 byte,
766 opcode::PUSH1,
767 i as u8,
768 opcode::MSTORE8,
769 ]);
770 }
771
772 bytecode_bytes.extend_from_slice(&[
775 opcode::PUSH1,
776 0x00, opcode::PUSH1,
778 0x00, opcode::PUSH1,
780 0x24, opcode::PUSH1,
782 0x00, opcode::PUSH1,
784 0x00, ]);
786
787 bytecode_bytes.push(opcode::PUSH20);
789 bytecode_bytes.extend_from_slice(PATH_USD_ADDRESS.as_slice());
790
791 bytecode_bytes.extend_from_slice(&[
792 opcode::PUSH2,
793 0xFF,
794 0xFF, opcode::CALL,
796 opcode::POP, opcode::STOP,
798 ]);
799
800 let bytecode = Bytecode::new_raw(bytecode_bytes.into());
801
802 let mut evm = create_evm_with_inspector(CountInspector::new());
804 {
806 let ctx = &mut evm.ctx;
807 let internals =
808 EvmInternals::new(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx);
809
810 let mut storage = EvmPrecompileStorageProvider::new_max_gas(internals, &ctx.cfg);
811 StorageCtx::enter(&mut storage, || {
812 TIP20Setup::path_usd(caller)
813 .with_issuer(caller)
814 .with_admin(contract) .apply()
816 })?;
817 }
818
819 evm.ctx.db_mut().insert_account_info(
821 contract,
822 AccountInfo {
823 code: Some(bytecode),
824 ..Default::default()
825 },
826 );
827
828 let tx_env = TxEnv {
830 caller,
831 kind: TxKind::Call(contract),
832 gas_limit: 1_000_000,
833 ..Default::default()
834 };
835 let result = evm
836 .inspect_tx(tx_env.into())
837 .expect("execution should succeed");
838
839 assert!(result.result.is_success());
840
841 assert_eq!(result.result.logs().len(), 3);
843 assert_eq!(result.result.logs()[0].address, PATH_USD_ADDRESS);
845
846 let inspector = &evm.inspector;
848
849 assert_eq!(inspector.get_count(opcode::CALL), 1);
851
852 assert_eq!(inspector.get_count(opcode::STOP), 1);
853
854 assert_eq!(inspector.log_count(), 1);
856
857 assert_eq!(inspector.call_count(), 2);
859
860 assert_eq!(inspector.call_end_count(), 2);
862
863 let key_pair = P256KeyPair::random();
867 let tempo_caller = key_pair.address;
868
869 let signed_auth = key_pair.create_signed_authorization(Address::repeat_byte(0x42))?;
871
872 let tx = TxBuilder::new()
874 .call_identity(&[0x01, 0x02])
875 .call_identity(&[0x03, 0x04])
876 .call_identity(&[0x05, 0x06])
877 .authorization(signed_auth)
878 .build();
879
880 let signed_tx = key_pair.sign_tx(tx)?;
881 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, tempo_caller);
882
883 let mut multi_evm = create_evm_with_inspector(CountInspector::new());
885 multi_evm.ctx.db_mut().insert_account_info(
886 tempo_caller,
887 AccountInfo {
888 balance: U256::from(DEFAULT_BALANCE),
889 ..Default::default()
890 },
891 );
892
893 let multi_result = multi_evm.inspect_tx(tx_env)?;
895 assert!(multi_result.result.is_success(),);
896
897 let multi_inspector = &multi_evm.inspector;
899
900 assert_eq!(multi_inspector.call_count(), 3,);
903 assert_eq!(multi_inspector.call_end_count(), 3,);
904
905 Ok(())
906 }
907
908 #[test]
909 fn test_tempo_tx_initial_gas() -> eyre::Result<()> {
910 let key_pair = P256KeyPair::random();
911 let caller = key_pair.address;
912
913 let mut evm = create_funded_evm(caller);
915 evm.block.basefee = 100_000_000_000;
916
917 let block = TempoBlockEnv::default();
919 let ctx = &mut evm.ctx;
920 let internals = EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
921 let mut provider =
922 EvmPrecompileStorageProvider::new_max_gas(internals, &Default::default());
923
924 StorageCtx::enter(&mut provider, || {
925 TIP20Setup::path_usd(caller)
926 .with_issuer(caller)
927 .with_mint(caller, U256::from(100_000))
928 .apply()
929 })?;
930
931 drop(provider);
932
933 let tx1 = TxBuilder::new()
935 .call_identity(&[])
936 .gas_limit(300_000)
937 .with_max_fee_per_gas(200_000_000_000)
938 .with_max_priority_fee_per_gas(0)
939 .build();
940
941 let signed_tx1 = key_pair.sign_tx(tx1)?;
942 let tx_env1 = TempoTxEnv::from_recovered_tx(&signed_tx1, caller);
943
944 let ctx = &mut evm.ctx;
945 let internals = EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
946 let mut provider =
947 EvmPrecompileStorageProvider::new_max_gas(internals, &Default::default());
948
949 let slot = StorageCtx::enter(&mut provider, || {
950 TIP20Token::from_address(PATH_USD_ADDRESS)?.balances[caller].read()
951 })?;
952 drop(provider);
953
954 assert_eq!(slot, U256::from(100_000));
955
956 let result1 = evm.transact_commit(tx_env1)?;
957 assert!(result1.is_success());
958 assert_eq!(result1.tx_gas_used(), 28_671);
959
960 let ctx = &mut evm.ctx;
961 let internals = EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
962 let mut provider =
963 EvmPrecompileStorageProvider::new_max_gas(internals, &Default::default());
964
965 let slot = StorageCtx::enter(&mut provider, || {
966 TIP20Token::from_address(PATH_USD_ADDRESS)?.balances[caller].read()
967 })?;
968 drop(provider);
969
970 assert_eq!(slot, U256::from(97_132));
971
972 let tx2 = TxBuilder::new()
974 .call_identity(&[])
975 .call_identity(&[])
976 .nonce(1)
977 .gas_limit(35_000)
978 .with_max_fee_per_gas(200_000_000_000)
979 .with_max_priority_fee_per_gas(0)
980 .build();
981
982 let signed_tx2 = key_pair.sign_tx(tx2)?;
983 let tx_env2 = TempoTxEnv::from_recovered_tx(&signed_tx2, caller);
984
985 let result2 = evm.transact_commit(tx_env2)?;
986 assert!(result2.is_success());
987 assert_eq!(result2.tx_gas_used(), 31_286);
988
989 let ctx = &mut evm.ctx;
990 let internals = EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
991 let mut provider =
992 EvmPrecompileStorageProvider::new_max_gas(internals, &Default::default());
993
994 let slot = StorageCtx::enter(&mut provider, || {
995 TIP20Token::from_address(PATH_USD_ADDRESS)?.balances[caller].read()
996 })?;
997 drop(provider);
998
999 assert_eq!(slot, U256::from(94_003));
1000
1001 Ok(())
1002 }
1003
1004 #[test]
1009 fn test_tempo_tx() -> eyre::Result<()> {
1010 let key_pair = P256KeyPair::random();
1011 let caller = key_pair.address;
1012
1013 let signed_auth = key_pair.create_signed_authorization(Address::repeat_byte(0x42))?;
1015
1016 let tx = TxBuilder::new()
1018 .call_identity(&[0x01, 0x02, 0x03, 0x04])
1019 .call_identity(&[0xAA, 0xBB, 0xCC, 0xDD])
1020 .authorization(signed_auth.clone())
1021 .build();
1022
1023 let signed_tx = key_pair.sign_tx(tx)?;
1024 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1025
1026 assert!(tx_env.tempo_tx_env.is_some(),);
1028 let tempo_env = tx_env.tempo_tx_env.as_ref().unwrap();
1029 assert_eq!(tempo_env.tempo_authorization_list.len(), 1);
1030 assert_eq!(tempo_env.aa_calls.len(), 2);
1031
1032 let mut evm = create_funded_evm_t1(caller);
1034
1035 let result = evm.transact_commit(tx_env)?;
1037 assert!(result.is_success());
1038
1039 let key_auth = KeyAuthorization::unrestricted(1, SignatureType::WebAuthn, caller);
1041 let key_auth_webauthn_sig = key_pair.sign_webauthn(key_auth.signature_hash().as_slice())?;
1042 let signed_key_auth =
1043 key_auth.into_signed(PrimitiveSignature::WebAuthn(key_auth_webauthn_sig));
1044
1045 let tx2 = TxBuilder::new()
1047 .call_identity(&[0x01, 0x02, 0x03, 0x04])
1048 .call_identity(&[0xAA, 0xBB, 0xCC, 0xDD])
1049 .authorization(signed_auth)
1050 .nonce(1)
1051 .gas_limit(1_000_000)
1052 .key_authorization(signed_key_auth)
1053 .build();
1054
1055 let signed_tx = key_pair.sign_tx_keychain(tx2)?;
1056 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1057
1058 let tempo_env_keychain = tx_env
1060 .tempo_tx_env
1061 .as_ref()
1062 .expect("Transaction should have tempo_tx_env");
1063 let keychain_sig = tempo_env_keychain
1064 .signature
1065 .as_keychain()
1066 .expect("Signature should be a KeychainSignature");
1067
1068 assert_eq!(keychain_sig.user_address, caller,);
1071
1072 assert!(matches!(
1074 keychain_sig.signature,
1075 PrimitiveSignature::WebAuthn(_)
1076 ));
1077
1078 let recovered_key_id = keychain_sig
1080 .key_id(&tempo_env_keychain.signature_hash)
1081 .expect("Key ID recovery should succeed");
1082 assert_eq!(recovered_key_id, caller,);
1083
1084 let result = evm.transact_commit(tx_env)?;
1086 assert!(result.is_success());
1087
1088 let tx_fail = TxBuilder::new()
1090 .call(PATH_USD_ADDRESS, &[0x01, 0x02]) .nonce(2)
1092 .build();
1093
1094 let signed_tx_fail = key_pair.sign_tx_keychain(tx_fail)?;
1095 let tx_env_fail = TempoTxEnv::from_recovered_tx(&signed_tx_fail, caller);
1096
1097 let result_fail = evm.transact(tx_env_fail)?;
1098 assert!(!result_fail.result.is_success());
1099
1100 let nonce_key_2d = U256::from(42);
1102
1103 let tx_2d = TxBuilder::new()
1104 .call_identity(&[0x2D, 0x2D, 0x2D, 0x2D])
1105 .nonce_key(nonce_key_2d)
1106 .build();
1107
1108 let signed_tx_2d = key_pair.sign_tx_keychain(tx_2d)?;
1109 let tx_env_2d = TempoTxEnv::from_recovered_tx(&signed_tx_2d, caller);
1110
1111 assert!(tx_env_2d.tempo_tx_env.is_some());
1112 assert_eq!(
1113 tx_env_2d.tempo_tx_env.as_ref().unwrap().nonce_key,
1114 nonce_key_2d
1115 );
1116
1117 let result_2d = evm.transact_commit(tx_env_2d)?;
1118 assert!(result_2d.is_success());
1119
1120 let nonce_slot = NonceManager::new().nonces[caller][nonce_key_2d].slot();
1122 let stored_nonce = evm
1123 .ctx
1124 .db()
1125 .storage_ref(NONCE_PRECOMPILE_ADDRESS, nonce_slot)
1126 .unwrap_or_default();
1127 assert_eq!(stored_nonce, U256::from(1));
1128
1129 let tx_2d_2 = TxBuilder::new()
1131 .call_identity(&[0x2E, 0x2E, 0x2E, 0x2E])
1132 .nonce_key(nonce_key_2d)
1133 .nonce(1)
1134 .build();
1135
1136 let signed_tx_2d_2 = key_pair.sign_tx_keychain(tx_2d_2)?;
1137 let tx_env_2d_2 = TempoTxEnv::from_recovered_tx(&signed_tx_2d_2, caller);
1138
1139 let result_2d_2 = evm.transact_commit(tx_env_2d_2)?;
1140 assert!(result_2d_2.is_success());
1141
1142 let stored_nonce_2 = evm
1144 .ctx
1145 .db()
1146 .storage_ref(NONCE_PRECOMPILE_ADDRESS, nonce_slot)
1147 .unwrap_or_default();
1148 assert_eq!(stored_nonce_2, U256::from(2));
1149
1150 Ok(())
1151 }
1152
1153 #[test]
1154 fn test_t3_key_authorization_deny_all_scopes_blocks_same_tx_call() -> eyre::Result<()> {
1155 let key_pair = P256KeyPair::random();
1156 let caller = key_pair.address;
1157
1158 let mut evm = create_funded_evm_t3(caller);
1159
1160 let block = TempoBlockEnv::default();
1162 {
1163 let ctx = &mut evm.ctx;
1164 let internals = EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
1165 let mut provider = EvmPrecompileStorageProvider::new_max_gas(internals, &ctx.cfg);
1166
1167 StorageCtx::enter(&mut provider, || {
1168 TIP20Setup::path_usd(caller)
1169 .with_issuer(caller)
1170 .with_mint(caller, U256::from(10_000_000))
1171 .apply()
1172 })?;
1173 }
1174
1175 let key_auth =
1177 KeyAuthorization::unrestricted(1, SignatureType::WebAuthn, caller).with_no_calls();
1178 let key_auth_sig = key_pair.sign_webauthn(key_auth.signature_hash().as_slice())?;
1179 let signed_key_auth = key_auth.into_signed(PrimitiveSignature::WebAuthn(key_auth_sig));
1180
1181 let tx = TxBuilder::new()
1182 .call_identity(&[0x01])
1183 .key_authorization(signed_key_auth)
1184 .gas_limit(5_000_000)
1185 .build();
1186
1187 let signed_tx = key_pair.sign_tx_keychain(tx)?;
1189 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1190
1191 let result = evm.transact_commit(tx_env)?;
1192 assert!(
1193 !result.is_success(),
1194 "deny-all scope should now fail during paid execution"
1195 );
1196 assert!(
1197 result.tx_gas_used() > 0,
1198 "failed execution should still consume gas"
1199 );
1200
1201 Ok(())
1202 }
1203
1204 #[test]
1205 fn test_t3_key_authorization_accepts_empty_recipient_allowlist_as_unconstrained()
1206 -> eyre::Result<()> {
1207 let key_pair = P256KeyPair::random();
1208 let caller = key_pair.address;
1209
1210 let mut evm = create_funded_evm_t3(caller);
1211
1212 let block = TempoBlockEnv::default();
1213 {
1214 let ctx = &mut evm.ctx;
1215 let internals = EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
1216 let mut provider = EvmPrecompileStorageProvider::new_max_gas(internals, &ctx.cfg);
1217
1218 StorageCtx::enter(&mut provider, || {
1219 TIP20Setup::path_usd(caller)
1220 .with_issuer(caller)
1221 .with_mint(caller, U256::from(10_000_000))
1222 .apply()
1223 })?;
1224 }
1225
1226 let transfer_to = Address::repeat_byte(0xaa);
1227 let transfer_input = ITIP20::transferCall {
1228 to: transfer_to,
1229 amount: U256::from(1_u64),
1230 }
1231 .abi_encode();
1232
1233 let key_auth = KeyAuthorization::unrestricted(1, SignatureType::WebAuthn, caller)
1234 .with_allowed_calls(vec![tempo_primitives::transaction::CallScope {
1235 target: PATH_USD_ADDRESS,
1236 selector_rules: vec![tempo_primitives::transaction::SelectorRule {
1237 selector: ITIP20::transferCall::SELECTOR,
1238 recipients: Vec::new(),
1239 }],
1240 }]);
1241 let key_auth_sig = key_pair.sign_webauthn(key_auth.signature_hash().as_slice())?;
1242 let signed_key_auth = key_auth.into_signed(PrimitiveSignature::WebAuthn(key_auth_sig));
1243
1244 let tx = TxBuilder::new()
1245 .call(PATH_USD_ADDRESS, &transfer_input)
1246 .key_authorization(signed_key_auth)
1247 .gas_limit(5_000_000)
1248 .build();
1249
1250 let signed_tx = key_pair.sign_tx_keychain(tx)?;
1251 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1252
1253 evm.transact_commit(tx_env)
1254 .expect("empty recipient allowlist should allow the call");
1255
1256 Ok(())
1257 }
1258
1259 #[test]
1260 fn test_same_tx_key_authorization_rejects_key_type_mismatch() -> eyre::Result<()> {
1261 let key_pair = P256KeyPair::random();
1262 let caller = key_pair.address;
1263
1264 let mut evm = create_funded_evm_t3(caller);
1265
1266 let key_auth = KeyAuthorization::unrestricted(1, SignatureType::Secp256k1, caller);
1267 let key_auth_sig = key_pair.sign_webauthn(key_auth.signature_hash().as_slice())?;
1268 let signed_key_auth = key_auth.into_signed(PrimitiveSignature::WebAuthn(key_auth_sig));
1269
1270 let tx = TxBuilder::new()
1271 .call_identity(&[0x01])
1272 .key_authorization(signed_key_auth)
1273 .gas_limit(5_000_000)
1274 .build();
1275
1276 let signed_tx = key_pair.sign_tx_keychain(tx)?;
1277 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1278
1279 let err = evm
1280 .transact_commit(tx_env)
1281 .expect_err("mismatched key_type should reject same-tx auth+use");
1282
1283 assert!(
1284 matches!(
1285 err,
1286 revm::context::result::EVMError::Transaction(
1287 TempoInvalidTransaction::KeychainValidationFailed { .. }
1288 )
1289 ),
1290 "expected KeychainValidationFailed, got: {err:?}"
1291 );
1292
1293 Ok(())
1294 }
1295
1296 #[test]
1299 fn test_tempo_tx_time_window() -> eyre::Result<()> {
1300 let key_pair = P256KeyPair::random();
1301 let caller = key_pair.address;
1302
1303 let signed_auth = key_pair.create_signed_authorization(Address::repeat_byte(0x42))?;
1305
1306 let create_signed_tx = |valid_after: Option<u64>, valid_before: Option<u64>| {
1308 let tx = TxBuilder::new()
1309 .call_identity(&[0x01, 0x02, 0x03, 0x04])
1310 .authorization(signed_auth.clone())
1311 .valid_after(valid_after)
1312 .valid_before(valid_before)
1313 .build();
1314 key_pair.sign_tx(tx)
1315 };
1316
1317 {
1319 let mut evm = create_funded_evm_with_timestamp(caller, 100);
1320 let signed_tx = create_signed_tx(Some(200), None)?;
1321 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1322
1323 let result = evm.transact(tx_env);
1324 assert!(result.is_err());
1325 let err = result.unwrap_err();
1326 assert!(
1327 matches!(
1328 err,
1329 revm::context::result::EVMError::Transaction(
1330 TempoInvalidTransaction::ValidAfter {
1331 current: 100,
1332 valid_after: 200
1333 }
1334 )
1335 ),
1336 "Expected ValidAfter error, got: {err:?}"
1337 );
1338 }
1339
1340 {
1342 let mut evm = create_funded_evm_with_timestamp(caller, 200);
1343 let signed_tx = create_signed_tx(None, Some(200))?;
1344 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1345
1346 let result = evm.transact(tx_env);
1347 assert!(result.is_err());
1348 let err = result.unwrap_err();
1349 assert!(
1350 matches!(
1351 err,
1352 revm::context::result::EVMError::Transaction(
1353 TempoInvalidTransaction::ValidBefore {
1354 current: 200,
1355 valid_before: 200
1356 }
1357 )
1358 ),
1359 "Expected ValidBefore error, got: {err:?}"
1360 );
1361 }
1362
1363 {
1365 let mut evm = create_funded_evm_with_timestamp(caller, 300);
1366 let signed_tx = create_signed_tx(None, Some(200))?;
1367 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1368
1369 let result = evm.transact(tx_env);
1370 assert!(result.is_err());
1371 let err = result.unwrap_err();
1372 assert!(
1373 matches!(
1374 err,
1375 revm::context::result::EVMError::Transaction(
1376 TempoInvalidTransaction::ValidBefore {
1377 current: 300,
1378 valid_before: 200
1379 }
1380 )
1381 ),
1382 "Expected ValidBefore error, got: {err:?}"
1383 );
1384 }
1385
1386 {
1388 let mut evm = create_funded_evm_with_timestamp(caller, 200);
1389 let signed_tx = create_signed_tx(Some(200), None)?;
1390 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1391
1392 let result = evm.transact(tx_env)?;
1393 assert!(result.result.is_success());
1394 }
1395
1396 {
1398 let mut evm = create_funded_evm_with_timestamp(caller, 150);
1399 let signed_tx = create_signed_tx(Some(100), Some(200))?;
1400 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1401
1402 let result = evm.transact(tx_env)?;
1403 assert!(result.result.is_success());
1404 }
1405
1406 {
1408 let mut evm = create_funded_evm_with_timestamp(caller, 50);
1409 let signed_tx = create_signed_tx(Some(100), Some(200))?;
1410 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1411
1412 let result = evm.transact(tx_env);
1413 assert!(result.is_err());
1414 let err = result.unwrap_err();
1415 assert!(
1416 matches!(
1417 err,
1418 revm::context::result::EVMError::Transaction(
1419 TempoInvalidTransaction::ValidAfter {
1420 current: 50,
1421 valid_after: 100
1422 }
1423 )
1424 ),
1425 "Expected ValidAfter error, got: {err:?}"
1426 );
1427 }
1428
1429 {
1431 let mut evm = create_funded_evm_with_timestamp(caller, 200);
1432 let signed_tx = create_signed_tx(Some(100), Some(200))?;
1433 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1434
1435 let result = evm.transact(tx_env);
1436 assert!(result.is_err());
1437 let err = result.unwrap_err();
1438 assert!(
1439 matches!(
1440 err,
1441 revm::context::result::EVMError::Transaction(
1442 TempoInvalidTransaction::ValidBefore {
1443 current: 200,
1444 valid_before: 200
1445 }
1446 )
1447 ),
1448 "Expected ValidBefore error, got: {err:?}"
1449 );
1450 }
1451
1452 Ok(())
1453 }
1454
1455 #[test]
1458 fn test_tempo_tx_create_first_call() -> eyre::Result<()> {
1459 let key_pair = P256KeyPair::random();
1460 let caller = key_pair.address;
1461
1462 let initcode = vec![0x60, 0x00, 0x60, 0x00, 0xF3];
1464
1465 let tx = TxBuilder::new()
1467 .create(&initcode)
1468 .call_identity(&[0x01, 0x02])
1469 .gas_limit(200_000)
1470 .build();
1471
1472 let signed_tx = key_pair.sign_tx(tx)?;
1473 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1474
1475 let mut evm = create_funded_evm(caller);
1477 let result = evm.transact_commit(tx_env)?;
1478
1479 assert!(result.is_success(), "CREATE as first call should succeed");
1480
1481 Ok(())
1482 }
1483
1484 #[test]
1487 fn test_tempo_tx_create_second_call_fails() -> eyre::Result<()> {
1488 let key_pair = P256KeyPair::random();
1489 let caller = key_pair.address;
1490
1491 let initcode = vec![0x60, 0x00, 0x60, 0x00, 0xF3];
1493
1494 let tx = TxBuilder::new()
1496 .call_identity(&[0x01, 0x02])
1497 .create(&initcode)
1498 .gas_limit(200_000)
1499 .build();
1500
1501 let signed_tx = key_pair.sign_tx(tx)?;
1502 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1503
1504 let mut evm = create_funded_evm(caller);
1506 let result = evm.transact(tx_env);
1507
1508 assert!(result.is_err(), "CREATE as second call should fail");
1509 let err = result.unwrap_err();
1510 assert!(
1511 matches!(
1512 err,
1513 revm::context::result::EVMError::Transaction(
1514 TempoInvalidTransaction::CallsValidation(msg)
1515 ) if msg.contains("first call")
1516 ),
1517 "Expected CallsValidation error about 'first call', got: {err:?}"
1518 );
1519
1520 Ok(())
1521 }
1522
1523 #[test]
1529 fn test_validate_aa_initial_tx_gas_errors() -> eyre::Result<()> {
1530 use revm::{context::result::EVMError, handler::Handler};
1531
1532 use crate::handler::TempoEvmHandler;
1533
1534 let key_pair = P256KeyPair::random();
1535 let caller = key_pair.address;
1536
1537 let create_evm_with_tx =
1539 |tx: TempoTransaction| -> eyre::Result<TempoEvm<CacheDB<EmptyDB>, ()>> {
1540 let signed_tx = key_pair.sign_tx(tx)?;
1541 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1542 let mut evm = create_funded_evm(caller);
1543 evm.ctx.tx = tx_env;
1544 Ok(evm)
1545 };
1546
1547 let handler = TempoEvmHandler::default();
1548
1549 {
1551 let oversized_initcode = vec![0x60; 50_000];
1553
1554 let mut evm = create_evm_with_tx(
1555 TxBuilder::new()
1556 .create(&oversized_initcode)
1557 .gas_limit(10_000_000)
1558 .build(),
1559 )?;
1560
1561 let result = handler.validate_initial_tx_gas(&mut evm);
1562 assert!(
1563 matches!(
1564 result,
1565 Err(EVMError::Transaction(
1566 TempoInvalidTransaction::EthInvalidTransaction(
1567 revm::context::result::InvalidTransaction::CreateInitCodeSizeLimit
1568 )
1569 ))
1570 ),
1571 "Expected CreateInitCodeSizeLimit error, got: {result:?}"
1572 );
1573 }
1574
1575 {
1577 let mut evm = create_evm_with_tx(
1578 TxBuilder::new()
1579 .call_with_value(IDENTITY_PRECOMPILE, &[0x01, 0x02], U256::from(1000))
1580 .build(),
1581 )?;
1582
1583 let result = handler.validate_initial_tx_gas(&mut evm);
1584 assert!(
1585 matches!(
1586 result,
1587 Err(EVMError::Transaction(
1588 TempoInvalidTransaction::ValueTransferNotAllowedInAATx
1589 ))
1590 ),
1591 "Expected ValueTransferNotAllowedInAATx error, got: {result:?}"
1592 );
1593 }
1594
1595 {
1597 let mut evm = create_evm_with_tx(
1598 TxBuilder::new()
1599 .call_identity(&[0x01, 0x02, 0x03, 0x04])
1600 .gas_limit(1000) .build(),
1602 )?;
1603
1604 let result = handler.validate_initial_tx_gas(&mut evm);
1605 assert!(
1606 matches!(
1607 result,
1608 Err(EVMError::Transaction(
1609 TempoInvalidTransaction::EthInvalidTransaction(
1610 InvalidTransaction::CallGasCostMoreThanGasLimit {
1611 gas_limit: 1000,
1612 initial_gas
1613 }
1614 )
1615 )) if initial_gas > 1000
1616 ),
1617 "Expected CallGasCostMoreThanGasLimit error, got: {result:?}"
1618 );
1619 }
1620
1621 {
1628 let large_calldata = vec![0x42; 1000]; let mut evm = create_evm_with_tx(
1631 TxBuilder::new()
1632 .call_identity(&large_calldata)
1633 .gas_limit(31_000)
1634 .build(),
1635 )?;
1636
1637 let result = handler.validate_initial_tx_gas(&mut evm);
1638
1639 assert!(
1640 matches!(
1641 result,
1642 Err(EVMError::Transaction(
1643 TempoInvalidTransaction::EthInvalidTransaction(
1644 InvalidTransaction::CallGasCostMoreThanGasLimit {
1645 gas_limit: 31_000,
1646 initial_gas
1647 }
1648 )
1649 )) if initial_gas > 31_000
1650 ),
1651 "Expected CallGasCostMoreThanGasLimit, got: {result:?}"
1652 );
1653 }
1654
1655 {
1658 let large_calldata = vec![0x42; 1000];
1659
1660 let mut evm = create_evm_with_tx(
1661 TxBuilder::new()
1662 .call_identity(&large_calldata)
1663 .gas_limit(1_000_000) .build(),
1665 )?;
1666
1667 let result = handler.validate_initial_tx_gas(&mut evm);
1668 assert!(
1669 result.is_ok(),
1670 "Expected success with sufficient gas, got: {result:?}"
1671 );
1672
1673 let gas = result.unwrap();
1674 assert!(
1676 gas.floor_gas > gas.initial_total_gas,
1677 "Expected floor_gas ({}) > initial_total_gas ({}) for large calldata",
1678 gas.floor_gas,
1679 gas.initial_total_gas
1680 );
1681 }
1682
1683 {
1685 let mut evm = create_evm_with_tx(
1686 TxBuilder::new()
1687 .call_identity(&[0x01, 0x02, 0x03, 0x04])
1688 .gas_limit(1_000_000)
1689 .build(),
1690 )?;
1691
1692 let result = handler.validate_initial_tx_gas(&mut evm);
1693 assert!(result.is_ok(), "Expected success, got: {result:?}");
1694
1695 let gas = result.unwrap();
1696 assert!(
1697 gas.initial_total_gas >= 21_000,
1698 "Initial gas should be at least 21k base"
1699 );
1700 }
1701
1702 Ok(())
1703 }
1704
1705 #[test]
1711 fn test_aa_tx_gas_baseline_identity_call() -> eyre::Result<()> {
1712 let key_pair = P256KeyPair::random();
1713 let caller = key_pair.address;
1714
1715 let mut evm = create_funded_evm_t1(caller);
1716
1717 let tx = TxBuilder::new()
1720 .call_identity(&[0x01, 0x02, 0x03, 0x04])
1721 .gas_limit(500_000)
1722 .build();
1723
1724 let signed_tx = key_pair.sign_tx(tx)?;
1725 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1726
1727 let result = evm.transact_commit(tx_env)?;
1728 assert!(result.is_success());
1729
1730 let gas_used = result.tx_gas_used();
1732 assert_eq!(
1733 gas_used, 278738,
1734 "T1 baseline identity call gas should be exact"
1735 );
1736
1737 Ok(())
1738 }
1739
1740 #[test]
1744 fn test_aa_tx_gas_sstore_new_slot() -> eyre::Result<()> {
1745 let key_pair = P256KeyPair::random();
1746 let caller = key_pair.address;
1747 let contract = Address::repeat_byte(0x55);
1748
1749 let mut evm = create_funded_evm_t1(caller);
1750
1751 let sstore_bytecode = Bytecode::new_raw(bytes!("60426000555B00"));
1755 evm.ctx.db_mut().insert_account_info(
1756 contract,
1757 AccountInfo {
1758 code: Some(sstore_bytecode),
1759 ..Default::default()
1760 },
1761 );
1762
1763 let tx = TxBuilder::new()
1765 .call(contract, &[])
1766 .gas_limit(600_000)
1767 .build();
1768
1769 let signed_tx = key_pair.sign_tx(tx)?;
1770 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1771
1772 let result = evm.transact_commit(tx_env)?;
1773 assert!(result.is_success(), "SSTORE transaction should succeed");
1774
1775 let gas_used = result.tx_gas_used();
1777 assert_eq!(
1778 gas_used, 530863,
1779 "T1 SSTORE to new slot gas should be exact"
1780 );
1781
1782 Ok(())
1783 }
1784
1785 #[test]
1789 fn test_aa_tx_gas_sstore_warm_slot() -> eyre::Result<()> {
1790 let key_pair = P256KeyPair::random();
1791 let caller = key_pair.address;
1792 let contract = Address::repeat_byte(0x56);
1793
1794 let mut evm = create_funded_evm_t1(caller);
1795
1796 let sstore_bytecode = Bytecode::new_raw(bytes!("60426000555B00"));
1799 evm.ctx.db_mut().insert_account_info(
1800 contract,
1801 AccountInfo {
1802 code: Some(sstore_bytecode),
1803 ..Default::default()
1804 },
1805 );
1806
1807 evm.ctx
1809 .db_mut()
1810 .insert_account_storage(contract, U256::ZERO, U256::from(1))
1811 .unwrap();
1812
1813 let tx = TxBuilder::new()
1815 .call(contract, &[])
1816 .gas_limit(500_000)
1817 .build();
1818
1819 let signed_tx = key_pair.sign_tx(tx)?;
1820 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1821
1822 let result = evm.transact_commit(tx_env)?;
1823 assert!(
1824 result.is_success(),
1825 "SSTORE to existing slot should succeed"
1826 );
1827
1828 let gas_used = result.tx_gas_used();
1831 assert_eq!(
1832 gas_used, 283663,
1833 "T1 SSTORE to existing slot gas should be exact"
1834 );
1835
1836 Ok(())
1837 }
1838
1839 #[test]
1842 fn test_aa_tx_gas_multiple_sstores() -> eyre::Result<()> {
1843 let key_pair = P256KeyPair::random();
1844 let caller = key_pair.address;
1845 let contract = Address::repeat_byte(0x57);
1846
1847 let mut evm = create_funded_evm_t1(caller);
1848
1849 let multi_sstore_bytecode = Bytecode::new_raw(bytes!("601160005560226001555B00"));
1854 evm.ctx.db_mut().insert_account_info(
1855 contract,
1856 AccountInfo {
1857 code: Some(multi_sstore_bytecode),
1858 ..Default::default()
1859 },
1860 );
1861
1862 let tx = TxBuilder::new()
1864 .call(contract, &[])
1865 .gas_limit(1_000_000)
1866 .build();
1867
1868 let signed_tx = key_pair.sign_tx(tx)?;
1869 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1870
1871 let result = evm.transact_commit(tx_env)?;
1872 assert!(
1873 result.is_success(),
1874 "Multiple SSTORE transaction should succeed"
1875 );
1876
1877 let gas_used = result.tx_gas_used();
1879 assert_eq!(gas_used, 783069, "T1 multiple SSTOREs gas should be exact");
1880
1881 Ok(())
1882 }
1883
1884 #[test]
1888 fn test_aa_tx_gas_create_contract() -> eyre::Result<()> {
1889 let key_pair = P256KeyPair::random();
1890 let caller = key_pair.address;
1891
1892 let mut evm = create_funded_evm_t1(caller);
1893
1894 let initcode = vec![0x60, 0x00, 0x60, 0x00, 0xF3];
1896
1897 let tx = TxBuilder::new()
1899 .create(&initcode)
1900 .gas_limit(1_000_000)
1901 .build();
1902
1903 let signed_tx = key_pair.sign_tx(tx)?;
1904 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
1905
1906 let result = evm.transact_commit(tx_env)?;
1907 assert!(result.is_success(), "CREATE transaction should succeed");
1908
1909 let gas_used = result.tx_gas_used();
1911 assert_eq!(gas_used, 778720, "T1 CREATE contract gas should be exact");
1912
1913 Ok(())
1914 }
1915
1916 #[test]
1919 fn test_t4_create_tx_charges_hash_cost() -> eyre::Result<()> {
1920 let key_pair = P256KeyPair::random();
1921 let caller = key_pair.address;
1922
1923 let tx = TxBuilder::new()
1925 .create(&hex!("6001600c60003960016000f300"))
1926 .gas_limit(1_000_000)
1927 .build();
1928 let signed_tx = key_pair.sign_tx(tx)?;
1929
1930 let run_create = |without_word_cost: bool| -> eyre::Result<u64> {
1931 let mut evm = create_funded_evm_t4(caller);
1932 if without_word_cost {
1933 evm.ctx.cfg.gas_params.override_gas(vec![(
1934 revm::context_interface::cfg::GasId::keccak256_per_word(),
1935 0,
1936 )]);
1937 }
1938
1939 let result = evm.transact_commit(TempoTxEnv::from_recovered_tx(&signed_tx, caller))?;
1940 assert!(
1941 result.is_success(),
1942 "T4 CREATE transaction should succeed with keccak256_per_word={without_word_cost:?}"
1943 );
1944 Ok(result.tx_gas_used())
1945 };
1946
1947 assert_eq!(
1948 run_create(false)? - run_create(true)?, tempo_gas_params(TempoHardfork::T4).keccak256_cost(1),
1950 "generic CREATE should add HASH_COST(L) on top of the non-hash baseline"
1951 );
1952 Ok(())
1953 }
1954
1955 #[test]
1959 fn test_aa_tx_gas_create_with_2d_nonce() -> eyre::Result<()> {
1960 let key_pair = P256KeyPair::random();
1961 let caller = key_pair.address;
1962
1963 let mut evm = create_funded_evm_t1(caller);
1964
1965 let initcode = vec![0x60, 0x00, 0x60, 0x00, 0xF3];
1967 let nonce_key_2d = U256::from(42);
1968
1969 let tx1 = TxBuilder::new()
1972 .create(&initcode)
1973 .nonce_key(nonce_key_2d)
1974 .gas_limit(2_000_000)
1975 .build();
1976
1977 assert_eq!(
1979 evm.ctx
1980 .db()
1981 .basic_ref(caller)
1982 .ok()
1983 .flatten()
1984 .map(|a| a.nonce)
1985 .unwrap_or(0),
1986 0,
1987 "Caller account nonce should be 0 before first tx"
1988 );
1989
1990 let signed_tx1 = key_pair.sign_tx(tx1)?;
1991 let tx_env1 = TempoTxEnv::from_recovered_tx(&signed_tx1, caller);
1992
1993 let result1 = evm.transact_commit(tx_env1)?;
1994 assert!(result1.is_success(), "CREATE with 2D nonce should succeed");
1995
1996 assert_eq!(
1998 result1.tx_gas_used(),
1999 1028720,
2000 "T1 CREATE with 2D nonce (caller.nonce=0) gas should be exact"
2001 );
2002
2003 let nonce_key_2d_2 = U256::from(43);
2008 let tx2 = TxBuilder::new()
2009 .create(&initcode)
2010 .nonce_key(nonce_key_2d_2)
2011 .nonce(0) .gas_limit(2_000_000)
2013 .build();
2014
2015 let signed_tx2 = key_pair.sign_tx(tx2)?;
2016 let tx_env2 = TempoTxEnv::from_recovered_tx(&signed_tx2, caller);
2017
2018 let result2 = evm.transact_commit(tx_env2)?;
2019 assert!(
2020 result2.is_success(),
2021 "Second CREATE with 2D nonce should succeed"
2022 );
2023
2024 assert_eq!(
2026 result2.tx_gas_used(),
2027 778720,
2028 "T1 CREATE with 2D nonce (caller.nonce=1) gas should be exact"
2029 );
2030
2031 let gas_difference = result1.tx_gas_used() - result2.tx_gas_used();
2033 assert_eq!(
2034 gas_difference, 250_000,
2035 "Gas difference should be exactly new_account_cost (250,000), got {gas_difference:?}",
2036 );
2037
2038 Ok(())
2039 }
2040
2041 #[test]
2044 fn test_aa_tx_gas_create_with_expiring_nonce() -> eyre::Result<()> {
2045 use tempo_primitives::transaction::TEMPO_EXPIRING_NONCE_KEY;
2046
2047 let key_pair = P256KeyPair::random();
2048 let caller = key_pair.address;
2049 let initcode = vec![0x60, 0x00, 0x60, 0x00, 0xF3]; let timestamp = 1000u64;
2051 let valid_before = timestamp + 30;
2052
2053 let mut evm1 = create_funded_evm_t1_with_timestamp(caller, timestamp);
2055 let tx1 = TxBuilder::new()
2056 .create(&initcode)
2057 .nonce_key(TEMPO_EXPIRING_NONCE_KEY)
2058 .valid_before(Some(valid_before))
2059 .gas_limit(2_000_000)
2060 .build();
2061 let result1 = evm1.transact_commit(TempoTxEnv::from_recovered_tx(
2062 &key_pair.sign_tx(tx1)?,
2063 caller,
2064 ))?;
2065 assert!(result1.is_success());
2066 let gas_nonce_zero = result1.tx_gas_used();
2067
2068 let mut evm2 = create_funded_evm_t1_with_timestamp(caller, timestamp);
2070 evm2.ctx.db_mut().insert_account_info(
2071 caller,
2072 AccountInfo {
2073 balance: U256::from(DEFAULT_BALANCE),
2074 nonce: 1,
2075 ..Default::default()
2076 },
2077 );
2078 let tx2 = TxBuilder::new()
2079 .create(&initcode)
2080 .nonce_key(TEMPO_EXPIRING_NONCE_KEY)
2081 .valid_before(Some(valid_before))
2082 .gas_limit(2_000_000)
2083 .build();
2084 let result2 = evm2.transact_commit(TempoTxEnv::from_recovered_tx(
2085 &key_pair.sign_tx(tx2)?,
2086 caller,
2087 ))?;
2088 assert!(result2.is_success());
2089 let gas_nonce_one = result2.tx_gas_used();
2090
2091 assert_eq!(
2093 gas_nonce_zero - gas_nonce_one,
2094 250_000,
2095 "new_account_cost not charged"
2096 );
2097
2098 Ok(())
2099 }
2100
2101 #[test]
2104 fn test_aa_tx_gas_single_vs_multiple_calls() -> eyre::Result<()> {
2105 let key_pair = P256KeyPair::random();
2106 let caller = key_pair.address;
2107
2108 let mut evm1 = create_funded_evm_t1(caller);
2111 let tx1 = TxBuilder::new()
2112 .call_identity(&[0x01, 0x02, 0x03, 0x04])
2113 .gas_limit(500_000)
2114 .build();
2115
2116 let signed_tx1 = key_pair.sign_tx(tx1)?;
2117 let tx_env1 = TempoTxEnv::from_recovered_tx(&signed_tx1, caller);
2118 let result1 = evm1.transact_commit(tx_env1)?;
2119 assert!(result1.is_success());
2120 let gas_single = result1.tx_gas_used();
2121
2122 let mut evm2 = create_funded_evm_t1(caller);
2125 let tx2 = TxBuilder::new()
2126 .call_identity(&[0x01, 0x02, 0x03, 0x04])
2127 .call_identity(&[0x05, 0x06, 0x07, 0x08])
2128 .call_identity(&[0x09, 0x0A, 0x0B, 0x0C])
2129 .gas_limit(500_000)
2130 .build();
2131
2132 let signed_tx2 = key_pair.sign_tx(tx2)?;
2133 let tx_env2 = TempoTxEnv::from_recovered_tx(&signed_tx2, caller);
2134 let result2 = evm2.transact_commit(tx_env2)?;
2135 assert!(result2.is_success());
2136 let gas_triple = result2.tx_gas_used();
2137
2138 assert_eq!(gas_single, 278738, "T1 single call gas should be exact");
2140 assert_eq!(gas_triple, 284102, "T1 triple call gas should be exact");
2141 assert!(
2142 gas_triple > gas_single,
2143 "3 calls should cost more than 1 call"
2144 );
2145 assert!(
2146 gas_triple < gas_single * 3,
2147 "3 calls should cost less than 3x single call (base costs shared)"
2148 );
2149
2150 Ok(())
2151 }
2152
2153 #[test]
2156 fn test_aa_tx_gas_sload_cold_vs_warm() -> eyre::Result<()> {
2157 let key_pair = P256KeyPair::random();
2158 let caller = key_pair.address;
2159 let contract = Address::repeat_byte(0x58);
2160
2161 let mut evm = create_funded_evm_t1(caller);
2162
2163 let sload_bytecode = Bytecode::new_raw(bytes!("6000545060005450"));
2168 evm.ctx.db_mut().insert_account_info(
2169 contract,
2170 AccountInfo {
2171 code: Some(sload_bytecode),
2172 ..Default::default()
2173 },
2174 );
2175
2176 evm.ctx
2178 .db_mut()
2179 .insert_account_storage(contract, U256::ZERO, U256::from(0x1234))
2180 .unwrap();
2181
2182 let tx = TxBuilder::new()
2184 .call(contract, &[])
2185 .gas_limit(500_000)
2186 .build();
2187
2188 let signed_tx = key_pair.sign_tx(tx)?;
2189 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
2190
2191 let result = evm.transact_commit(tx_env)?;
2192 assert!(result.is_success(), "SLOAD transaction should succeed");
2193
2194 let gas_used = result.tx_gas_used();
2196 assert_eq!(gas_used, 280866, "T1 SLOAD cold/warm gas should be exact");
2197
2198 Ok(())
2199 }
2200
2201 #[test]
2206 fn test_system_call_and_inspector() -> eyre::Result<()> {
2207 let caller = Address::repeat_byte(0x01);
2208 let contract = Address::repeat_byte(0x42);
2209
2210 let bytecode = Bytecode::new_raw(bytes!("444360006000F3"));
2213
2214 {
2216 let mut evm = create_evm();
2217 evm.ctx.db_mut().insert_account_info(
2218 contract,
2219 AccountInfo {
2220 code: Some(bytecode.clone()),
2221 ..Default::default()
2222 },
2223 );
2224
2225 let result = evm.system_call_one_with_caller(caller, contract, Bytes::new())?;
2226 assert!(result.is_success());
2227 }
2228
2229 {
2231 let mut evm = create_evm_with_inspector(CountInspector::new());
2232 evm.ctx.db_mut().insert_account_info(
2233 contract,
2234 AccountInfo {
2235 code: Some(bytecode),
2236 ..Default::default()
2237 },
2238 );
2239
2240 let result = evm.inspect_one_system_call_with_caller(caller, contract, Bytes::new())?;
2242 assert!(result.is_success());
2243
2244 assert!(evm.inspector.call_count() > 0,);
2246
2247 evm.set_inspector(CountInspector::new());
2249
2250 assert_eq!(evm.inspector.call_count(), 0,);
2252
2253 let result = evm.inspect_one_system_call_with_caller(caller, contract, Bytes::new())?;
2255 assert!(result.is_success());
2256 assert!(evm.inspector.call_count() > 0);
2257 }
2258
2259 Ok(())
2260 }
2261
2262 #[test]
2272 fn test_key_authorization_t1() -> eyre::Result<()> {
2273 use tempo_precompiles::account_keychain::AccountKeychain;
2274
2275 let key_pair = P256KeyPair::random();
2276 let caller = key_pair.address;
2277
2278 let mut evm = create_funded_evm_t1(caller);
2280
2281 let block = TempoBlockEnv::default();
2283 {
2284 let ctx = &mut evm.ctx;
2285 let internals = EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
2286 let mut provider = EvmPrecompileStorageProvider::new_max_gas(internals, &ctx.cfg);
2287
2288 StorageCtx::enter(&mut provider, || {
2289 TIP20Setup::path_usd(caller)
2290 .with_issuer(caller)
2291 .with_mint(caller, U256::from(10_000_000))
2292 .apply()
2293 })?;
2294 }
2295
2296 let access_key = P256KeyPair::random();
2300 let key_auth =
2301 KeyAuthorization::unrestricted(1, SignatureType::WebAuthn, access_key.address);
2302 let key_auth_sig = key_pair.sign_webauthn(key_auth.signature_hash().as_slice())?;
2303 let signed_key_auth = key_auth.into_signed(PrimitiveSignature::WebAuthn(key_auth_sig));
2304
2305 {
2307 let ctx = &mut evm.ctx;
2308 let internals = EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
2309 let mut provider = EvmPrecompileStorageProvider::new_max_gas(internals, &ctx.cfg);
2310
2311 let key_exists = StorageCtx::enter(&mut provider, || {
2312 let keychain = AccountKeychain::default();
2313 keychain.keys[caller][access_key.address].read()
2314 })?;
2315 assert_eq!(
2316 key_exists.expiry, 0,
2317 "Key should not exist before transaction"
2318 );
2319 }
2320
2321 let signed_auth = key_pair.create_signed_authorization(Address::repeat_byte(0x42))?;
2322
2323 let tx_low_gas = TxBuilder::new()
2325 .call_identity(&[0x01])
2326 .authorization(signed_auth)
2327 .key_authorization(signed_key_auth)
2328 .gas_limit(589_000)
2329 .build();
2330
2331 let signed_tx_low = key_pair.sign_tx(tx_low_gas)?;
2332 let tx_env_low = TempoTxEnv::from_recovered_tx(&signed_tx_low, caller);
2333
2334 let result_low = evm.transact_commit(tx_env_low);
2336
2337 let nonce_incremented = match &result_low {
2340 Ok(result) => {
2341 assert_eq!(
2342 result.tx_gas_used(),
2343 589_000,
2344 "Gas used should be gas limit"
2345 );
2346 assert!(
2347 !result.is_success(),
2348 "Transaction with insufficient gas should fail"
2349 );
2350 true }
2352 Err(e) => {
2353 assert!(
2355 matches!(
2356 e,
2357 revm::context::result::EVMError::Transaction(
2358 TempoInvalidTransaction::EthInvalidTransaction(
2359 revm::context::result::InvalidTransaction::CallGasCostMoreThanGasLimit { .. }
2360 )
2361 )
2362 ),
2363 "Expected CallGasCostMoreThanGasLimit, got: {e:?}"
2364 );
2365 false }
2367 };
2368
2369 {
2372 let ctx = &mut evm.ctx;
2373 let internals = EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
2374 let mut provider = EvmPrecompileStorageProvider::new_max_gas(internals, &ctx.cfg);
2375
2376 let key_after_fail = StorageCtx::enter(&mut provider, || {
2377 let keychain = AccountKeychain::default();
2378 keychain.keys[caller][access_key.address].read()
2379 })?;
2380
2381 assert_eq!(
2382 key_after_fail,
2383 AuthorizedKey::default(),
2384 "Key should NOT be authorized when transaction fails due to insufficient gas"
2385 );
2386 }
2387
2388 let access_key2 = P256KeyPair::random();
2392 let key_auth2 =
2393 KeyAuthorization::unrestricted(1, SignatureType::WebAuthn, access_key2.address);
2394 let key_auth_sig2 = key_pair.sign_webauthn(key_auth2.signature_hash().as_slice())?;
2395 let signed_key_auth2 = key_auth2.into_signed(PrimitiveSignature::WebAuthn(key_auth_sig2));
2396
2397 let signed_auth2 = key_pair.create_signed_authorization(Address::repeat_byte(0x43))?;
2398
2399 let next_nonce = if nonce_incremented { 1 } else { 0 };
2401 let tx = TxBuilder::new()
2402 .call_identity(&[0x01])
2403 .authorization(signed_auth2)
2404 .key_authorization(signed_key_auth2)
2405 .nonce(next_nonce)
2406 .gas_limit(1_000_000)
2407 .build();
2408
2409 let signed_tx = key_pair.sign_tx(tx)?;
2410 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
2411
2412 let result = evm.transact_commit(tx_env)?;
2413 assert!(result.is_success(), "Transaction should succeed");
2414
2415 {
2417 let ctx = &mut evm.ctx;
2418 let internals = EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
2419 let mut provider = EvmPrecompileStorageProvider::new_max_gas(internals, &ctx.cfg);
2420
2421 let key_after_success = StorageCtx::enter(&mut provider, || {
2422 let keychain = AccountKeychain::default();
2423 keychain.keys[caller][access_key2.address].read()
2424 })?;
2425
2426 assert_eq!(
2427 key_after_success.expiry,
2428 u64::MAX,
2429 "Key should be authorized after successful transaction"
2430 );
2431 }
2432
2433 Ok(())
2434 }
2435
2436 #[test]
2451 fn test_create_nonce_replay_regression() -> eyre::Result<()> {
2452 use tempo_precompiles::account_keychain::AccountKeychain;
2453
2454 fn run_create_with_key_auth(
2457 spec: TempoHardfork,
2458 gas_limit: u64,
2459 ) -> eyre::Result<(u64, u64)> {
2460 let key_pair = P256KeyPair::random();
2461 let caller = key_pair.address;
2462
2463 let db = CacheDB::new(EmptyDB::new());
2464 let mut cfg = CfgEnv::<TempoHardfork>::default();
2465 cfg.spec = spec;
2466 cfg.gas_params = tempo_gas_params(spec);
2467
2468 let ctx = Context::mainnet()
2469 .with_db(db)
2470 .with_block(Default::default())
2471 .with_cfg(cfg)
2472 .with_tx(Default::default());
2473
2474 let mut evm = TempoEvm::new(ctx, ());
2475 fund_account(&mut evm, caller);
2476
2477 let block = TempoBlockEnv::default();
2478 {
2479 let ctx = &mut evm.ctx;
2480 let internals =
2481 EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
2482 let mut provider =
2486 EvmPrecompileStorageProvider::new_max_gas(internals, &Default::default());
2487 StorageCtx::enter(&mut provider, || {
2488 TIP20Setup::path_usd(caller)
2489 .with_issuer(caller)
2490 .with_mint(caller, U256::from(100_000_000))
2491 .apply()
2492 })?;
2493 }
2494
2495 let access_key = P256KeyPair::random();
2496 let key_auth =
2497 KeyAuthorization::unrestricted(1, SignatureType::WebAuthn, access_key.address);
2498 let key_auth_sig = key_pair.sign_webauthn(key_auth.signature_hash().as_slice())?;
2499 let signed_key_auth = key_auth.into_signed(PrimitiveSignature::WebAuthn(key_auth_sig));
2500
2501 let tx = TxBuilder::new()
2502 .create(&[0x60, 0x00, 0x60, 0x00, 0xF3])
2503 .key_authorization(signed_key_auth)
2504 .gas_limit(gas_limit)
2505 .build();
2506
2507 let signed_tx = key_pair.sign_tx(tx)?;
2508 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
2509 let _result = evm.transact_commit(tx_env);
2510
2511 let nonce = evm
2512 .ctx
2513 .db()
2514 .basic_ref(caller)
2515 .ok()
2516 .flatten()
2517 .map(|a| a.nonce)
2518 .unwrap_or(0);
2519
2520 let key_expiry = {
2521 let ctx = &mut evm.ctx;
2522 let internals =
2523 EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
2524 let mut provider =
2525 EvmPrecompileStorageProvider::new_max_gas(internals, &Default::default());
2526 let key = StorageCtx::enter(&mut provider, || {
2527 AccountKeychain::default().keys[caller][access_key.address].read()
2528 })?;
2529 key.expiry
2530 };
2531
2532 Ok((nonce, key_expiry))
2533 }
2534
2535 let (t1_nonce, t1_key_expiry) = run_create_with_key_auth(TempoHardfork::T1, 780_000)?;
2540 assert_eq!(
2541 t1_nonce, 0,
2542 "T1 bug: nonce must NOT be bumped when keychain OOGs"
2543 );
2544 assert_eq!(
2545 t1_key_expiry, 0,
2546 "T1 bug: key must NOT be authorized when keychain OOGs"
2547 );
2548
2549 let (t1b_nonce, t1b_key_expiry) = run_create_with_key_auth(TempoHardfork::T1B, 1_050_000)?;
2555 assert_eq!(
2556 t1b_nonce, 1,
2557 "T1B fix: nonce must be bumped after CREATE+KeyAuth"
2558 );
2559 assert_eq!(t1b_key_expiry, u64::MAX, "T1B fix: key must be authorized");
2560
2561 Ok(())
2562 }
2563
2564 #[test]
2575 fn test_double_charge_key_authorization_regression() -> eyre::Result<()> {
2576 fn run_call_with_key_auth(spec: TempoHardfork) -> eyre::Result<u64> {
2578 let key_pair = P256KeyPair::random();
2579 let caller = key_pair.address;
2580
2581 let db = CacheDB::new(EmptyDB::new());
2582 let mut cfg = CfgEnv::<TempoHardfork>::default();
2583 cfg.spec = spec;
2584 cfg.gas_params = tempo_gas_params(spec);
2585
2586 let ctx = Context::mainnet()
2587 .with_db(db)
2588 .with_block(Default::default())
2589 .with_cfg(cfg)
2590 .with_tx(Default::default());
2591
2592 let mut evm = TempoEvm::new(ctx, ());
2593 fund_account(&mut evm, caller);
2594
2595 let block = TempoBlockEnv::default();
2596 {
2597 let ctx = &mut evm.ctx;
2598 let internals =
2599 EvmInternals::new(&mut ctx.journaled_state, &block, &ctx.cfg, &ctx.tx);
2600 let mut provider =
2601 EvmPrecompileStorageProvider::new_max_gas(internals, &Default::default());
2602 StorageCtx::enter(&mut provider, || {
2603 TIP20Setup::path_usd(caller)
2604 .with_issuer(caller)
2605 .with_mint(caller, U256::from(100_000_000))
2606 .apply()
2607 })?;
2608 }
2609
2610 let access_key = P256KeyPair::random();
2611 let key_auth =
2612 KeyAuthorization::unrestricted(1, SignatureType::Secp256k1, access_key.address);
2613 let key_auth_sig = key_pair.sign_webauthn(key_auth.signature_hash().as_slice())?;
2614 let signed_key_auth = key_auth.into_signed(PrimitiveSignature::WebAuthn(key_auth_sig));
2615
2616 let tx = TxBuilder::new()
2617 .call_identity(&[])
2618 .key_authorization(signed_key_auth)
2619 .gas_limit(2_000_000)
2620 .build();
2621
2622 let signed_tx = key_pair.sign_tx(tx)?;
2623 let tx_env = TempoTxEnv::from_recovered_tx(&signed_tx, caller);
2624 let result = evm.transact_commit(tx_env)?;
2625 assert!(result.is_success());
2626 Ok(result.tx_gas_used())
2627 }
2628
2629 let t1_gas = run_call_with_key_auth(TempoHardfork::T1)?;
2630 let t1b_gas = run_call_with_key_auth(TempoHardfork::T1B)?;
2631
2632 assert!(
2635 t1_gas > 500_000,
2636 "T1 bug: should double-charge (got {t1_gas}, expected >500k)"
2637 );
2638
2639 assert!(
2643 t1b_gas < t1_gas,
2644 "T1B fix: gas ({t1b_gas}) must be less than T1 double-charge ({t1_gas})"
2645 );
2646
2647 Ok(())
2648 }
2649
2650 #[test]
2662 fn test_aa_tx_transfer_calls_format_no_extra_250k() -> eyre::Result<()> {
2663 let key_pair = P256KeyPair::random();
2664 let caller = key_pair.address;
2665 let recipient = Address::with_last_byte(0xff);
2666
2667 let mut evm_baseline = create_funded_evm_t1(caller);
2671 let tx_baseline = TxBuilder::new()
2672 .call(recipient, &[])
2673 .nonce_key(U256::ZERO)
2674 .nonce(0)
2675 .gas_limit(500_000)
2676 .build();
2677 let result_baseline = evm_baseline.transact_commit(TempoTxEnv::from_recovered_tx(
2678 &key_pair.sign_tx(tx_baseline)?,
2679 caller,
2680 ))?;
2681 assert!(
2682 result_baseline.is_success(),
2683 "baseline transfer should succeed"
2684 );
2685 let gas_baseline = result_baseline.tx_gas_used();
2686
2687 let nonce_key = U256::from(42);
2692 let mut evm_2d = create_funded_evm_t1(caller);
2693 let tx_2d = TxBuilder::new()
2694 .call(recipient, &[])
2695 .nonce_key(nonce_key)
2696 .nonce(0)
2697 .gas_limit(500_000)
2698 .build();
2699 let result_2d = evm_2d.transact_commit(TempoTxEnv::from_recovered_tx(
2700 &key_pair.sign_tx(tx_2d)?,
2701 caller,
2702 ))?;
2703 assert!(
2704 result_2d.is_success(),
2705 "calls-format transfer with 2D nonce should succeed"
2706 );
2707 let gas_2d = result_2d.tx_gas_used();
2708
2709 let diff = gas_2d.saturating_sub(gas_baseline);
2714 assert!(
2715 diff < 10_000,
2716 "calls-format transfer with nonceKey={nonce_key} (gas={gas_2d}) must not cost \
2717 ~250k more than baseline (gas={gas_baseline}, diff={diff}). \
2718 A diff near 250_000 means new_account_cost is incorrectly added for \
2719 transfers (issue #3178)."
2720 );
2721
2722 Ok(())
2723 }
2724}