aztec_wallet/
wallet.rs

1use std::collections::HashMap;
2use std::sync::{Arc, Mutex};
3
4use async_trait::async_trait;
5use serde::{Deserialize, Serialize};
6
7use crate::abi::{AbiType, ContractArtifact, EventSelector};
8use crate::error::Error;
9use crate::fee::{Gas, GasSettings};
10use crate::node::LogId;
11use crate::tx::{AuthWitness, Capsule, ExecutionPayload, FunctionCall, TxHash};
12use crate::types::{AztecAddress, ContractInstanceWithAddress, Fr};
13
14// Re-export ChainInfo and MessageHashOrIntent from aztec-core::hash
15// so existing consumers of aztec-wallet continue to find them here.
16pub use aztec_core::hash::{ChainInfo, MessageHashOrIntent};
17
18// ---------------------------------------------------------------------------
19// Supporting types
20// ---------------------------------------------------------------------------
21
22/// A value with an optional human-readable alias.
23#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(rename_all = "camelCase")]
25pub struct Aliased<T> {
26    /// Human-readable alias.
27    pub alias: String,
28    /// The aliased value.
29    pub item: T,
30}
31
32/// Metadata about a registered contract instance.
33#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(rename_all = "camelCase")]
35pub struct ContractMetadata {
36    /// The contract instance, if registered.
37    pub instance: Option<ContractInstanceWithAddress>,
38    /// Whether the contract has been initialized.
39    pub is_contract_initialized: bool,
40    /// Whether the contract class has been published on-chain.
41    pub is_contract_published: bool,
42    /// Whether the contract has been upgraded.
43    pub is_contract_updated: bool,
44    /// Updated contract class ID after an upgrade.
45    pub updated_contract_class_id: Option<Fr>,
46}
47
48/// Metadata about a registered contract class.
49#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct ContractClassMetadata {
52    /// Whether the artifact has been registered locally.
53    pub is_artifact_registered: bool,
54    /// Whether the class has been published on-chain.
55    pub is_contract_class_publicly_registered: bool,
56}
57
58/// Options for transaction simulation.
59#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
60#[serde(rename_all = "camelCase")]
61pub struct SimulateOptions {
62    /// Address of the simulating account.
63    pub from: AztecAddress,
64    /// Skip validation checks during simulation.
65    #[serde(default)]
66    pub skip_validation: bool,
67    /// Whether to skip fee enforcement during simulation.
68    #[serde(default = "default_skip_fee_enforcement")]
69    pub skip_fee_enforcement: bool,
70    /// Additional authorization witnesses.
71    #[serde(default)]
72    pub auth_witnesses: Vec<AuthWitness>,
73    /// Private data capsules for the simulation.
74    #[serde(default)]
75    pub capsules: Vec<Capsule>,
76    /// Additional note-discovery scopes.
77    #[serde(default)]
78    pub additional_scopes: Vec<AztecAddress>,
79    /// Gas settings for the simulation.
80    pub gas_settings: Option<GasSettings>,
81    /// Pre-resolved fee execution payload to merge into the transaction.
82    #[serde(default, skip_serializing_if = "Option::is_none")]
83    pub fee_execution_payload: Option<ExecutionPayload>,
84    /// If true, estimate gas and include suggested gas settings in the result.
85    #[serde(default)]
86    pub estimate_gas: bool,
87    /// Padding factor for gas estimation (default: 0.1 = 10%).
88    #[serde(default, skip_serializing_if = "Option::is_none")]
89    pub estimated_gas_padding: Option<f64>,
90}
91
92/// Options for transaction sending.
93#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
94#[serde(rename_all = "camelCase")]
95pub struct SendOptions {
96    /// Address of the sending account.
97    pub from: AztecAddress,
98    /// Additional authorization witnesses.
99    #[serde(default)]
100    pub auth_witnesses: Vec<AuthWitness>,
101    /// Private data capsules.
102    #[serde(default)]
103    pub capsules: Vec<Capsule>,
104    /// Additional note-discovery scopes.
105    #[serde(default)]
106    pub additional_scopes: Vec<AztecAddress>,
107    /// Gas settings for the transaction.
108    pub gas_settings: Option<GasSettings>,
109    /// Pre-resolved fee execution payload to merge into the transaction.
110    #[serde(default, skip_serializing_if = "Option::is_none")]
111    pub fee_execution_payload: Option<ExecutionPayload>,
112}
113
114/// Profiling mode for transaction analysis.
115#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
116#[serde(rename_all = "kebab-case")]
117pub enum ProfileMode {
118    /// Count constraint gates.
119    Gates,
120    /// Count execution steps.
121    ExecutionSteps,
122    /// Full profiling (gates + execution steps).
123    Full,
124}
125
126/// Options for transaction profiling.
127#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
128#[serde(rename_all = "camelCase")]
129pub struct ProfileOptions {
130    /// Address of the profiling account.
131    pub from: AztecAddress,
132    /// Additional authorization witnesses.
133    #[serde(default)]
134    pub auth_witnesses: Vec<AuthWitness>,
135    /// Private data capsules.
136    #[serde(default)]
137    pub capsules: Vec<Capsule>,
138    /// Additional note-discovery scopes.
139    #[serde(default)]
140    pub additional_scopes: Vec<AztecAddress>,
141    /// Profiling mode.
142    pub profile_mode: Option<ProfileMode>,
143    /// Whether proof generation should be skipped while profiling.
144    #[serde(default = "default_skip_proof_generation")]
145    pub skip_proof_generation: bool,
146    /// Gas settings for profiling.
147    pub gas_settings: Option<GasSettings>,
148    /// Pre-resolved fee execution payload to merge into the transaction.
149    #[serde(default, skip_serializing_if = "Option::is_none")]
150    pub fee_execution_payload: Option<ExecutionPayload>,
151}
152
153/// Options for utility function execution.
154#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
155#[serde(rename_all = "camelCase")]
156pub struct ExecuteUtilityOptions {
157    /// Address scope for note discovery.
158    pub scope: AztecAddress,
159    /// Additional authorization witnesses.
160    #[serde(default)]
161    pub auth_witnesses: Vec<AuthWitness>,
162}
163
164/// Result of a transaction simulation.
165#[derive(Clone, Debug, Serialize, Deserialize)]
166pub struct TxSimulationResult {
167    /// Return values from the simulated execution.
168    pub return_values: serde_json::Value,
169    /// Gas consumed during simulation.
170    pub gas_used: Option<Gas>,
171}
172
173/// Result of a transaction profiling.
174#[derive(Clone, Debug, Serialize, Deserialize)]
175pub struct TxProfileResult {
176    /// Return values from the profiled execution.
177    pub return_values: serde_json::Value,
178    /// Gas consumed during profiling.
179    pub gas_used: Option<Gas>,
180    /// Detailed profiling data.
181    pub profile_data: serde_json::Value,
182}
183
184/// Result of a utility function execution.
185#[derive(Clone, Debug, Serialize, Deserialize)]
186pub struct UtilityExecutionResult {
187    /// Return values from the utility function call.
188    pub result: serde_json::Value,
189    /// Optional simulation stats payload.
190    #[serde(default, skip_serializing_if = "Option::is_none")]
191    pub stats: Option<serde_json::Value>,
192}
193
194/// Result of sending a transaction.
195#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
196pub struct SendResult {
197    /// Hash of the submitted transaction.
198    pub tx_hash: TxHash,
199}
200
201/// Metadata definition for event decoding.
202#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
203#[serde(rename_all = "camelCase")]
204pub struct EventMetadataDefinition {
205    /// Selector identifying the event type.
206    pub event_selector: EventSelector,
207    /// ABI type describing the event's fields.
208    pub abi_type: AbiType,
209    /// Ordered field names for decoding.
210    pub field_names: Vec<String>,
211}
212
213/// Filter for querying private events from a wallet.
214#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
215#[serde(rename_all = "camelCase")]
216pub struct PrivateEventFilter {
217    /// Contract to filter events from.
218    pub contract_address: AztecAddress,
219    /// Note-discovery scopes.
220    #[serde(default)]
221    pub scopes: Vec<AztecAddress>,
222    /// Filter by transaction hash.
223    pub tx_hash: Option<TxHash>,
224    /// Start block (inclusive).
225    pub from_block: Option<u64>,
226    /// End block (inclusive).
227    pub to_block: Option<u64>,
228    /// Cursor for pagination.
229    pub after_log: Option<LogId>,
230}
231
232/// Metadata attached to a decoded private event.
233#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
234#[serde(rename_all = "camelCase")]
235pub struct PrivateEventMetadata {
236    /// Hash of the transaction that emitted the event.
237    pub tx_hash: TxHash,
238    /// Block number, if available.
239    pub block_number: Option<u64>,
240    /// Log index within the block.
241    pub log_index: Option<u64>,
242}
243
244/// A private event retrieved from the wallet.
245///
246/// Event data is kept as opaque JSON; callers can deserialize into a
247/// concrete type as needed.
248#[derive(Clone, Debug, Serialize, Deserialize)]
249#[serde(rename_all = "camelCase")]
250pub struct PrivateEvent {
251    /// Decoded event data (opaque JSON).
252    pub event: serde_json::Value,
253    /// Event metadata (tx hash, block, index).
254    pub metadata: PrivateEventMetadata,
255}
256
257impl Default for SimulateOptions {
258    fn default() -> Self {
259        Self {
260            from: AztecAddress(Fr::zero()),
261            skip_validation: false,
262            skip_fee_enforcement: default_skip_fee_enforcement(),
263            auth_witnesses: vec![],
264            capsules: vec![],
265            additional_scopes: vec![],
266            gas_settings: None,
267            fee_execution_payload: None,
268            estimate_gas: false,
269            estimated_gas_padding: None,
270        }
271    }
272}
273
274impl Default for SendOptions {
275    fn default() -> Self {
276        Self {
277            from: AztecAddress(Fr::zero()),
278            auth_witnesses: vec![],
279            capsules: vec![],
280            additional_scopes: vec![],
281            gas_settings: None,
282            fee_execution_payload: None,
283        }
284    }
285}
286
287impl Default for ProfileOptions {
288    fn default() -> Self {
289        Self {
290            from: AztecAddress(Fr::zero()),
291            auth_witnesses: vec![],
292            capsules: vec![],
293            additional_scopes: vec![],
294            profile_mode: None,
295            skip_proof_generation: default_skip_proof_generation(),
296            gas_settings: None,
297            fee_execution_payload: None,
298        }
299    }
300}
301
302impl Default for ExecuteUtilityOptions {
303    fn default() -> Self {
304        Self {
305            scope: AztecAddress(Fr::zero()),
306            auth_witnesses: vec![],
307        }
308    }
309}
310
311const fn default_skip_fee_enforcement() -> bool {
312    true
313}
314
315const fn default_skip_proof_generation() -> bool {
316    true
317}
318
319// ---------------------------------------------------------------------------
320// Wallet trait
321// ---------------------------------------------------------------------------
322
323/// Main private execution interface for end users.
324///
325/// This trait is the primary abstraction for interacting with the Aztec network
326/// through a wallet implementation. It provides methods for account management,
327/// contract registration, transaction simulation, sending, and event retrieval.
328#[async_trait]
329pub trait Wallet: Send + Sync {
330    /// Get chain identification information.
331    async fn get_chain_info(&self) -> Result<ChainInfo, Error>;
332
333    /// Get the list of accounts managed by this wallet.
334    async fn get_accounts(&self) -> Result<Vec<Aliased<AztecAddress>>, Error>;
335
336    /// Get the address book entries.
337    async fn get_address_book(&self) -> Result<Vec<Aliased<AztecAddress>>, Error>;
338
339    /// Register a sender address with an optional alias.
340    async fn register_sender(
341        &self,
342        address: AztecAddress,
343        alias: Option<String>,
344    ) -> Result<AztecAddress, Error>;
345
346    /// Get metadata about a registered contract.
347    async fn get_contract_metadata(&self, address: AztecAddress)
348        -> Result<ContractMetadata, Error>;
349
350    /// Get metadata about a registered contract class.
351    async fn get_contract_class_metadata(
352        &self,
353        class_id: Fr,
354    ) -> Result<ContractClassMetadata, Error>;
355
356    /// Register a contract instance (and optionally its artifact) with the wallet.
357    async fn register_contract(
358        &self,
359        instance: ContractInstanceWithAddress,
360        artifact: Option<ContractArtifact>,
361        secret_key: Option<Fr>,
362    ) -> Result<ContractInstanceWithAddress, Error>;
363
364    /// Get private events matching the given filter.
365    async fn get_private_events(
366        &self,
367        event_metadata: &EventMetadataDefinition,
368        filter: PrivateEventFilter,
369    ) -> Result<Vec<PrivateEvent>, Error>;
370
371    /// Simulate a transaction without sending it.
372    async fn simulate_tx(
373        &self,
374        exec: ExecutionPayload,
375        opts: SimulateOptions,
376    ) -> Result<TxSimulationResult, Error>;
377
378    /// Execute a utility (view) function.
379    async fn execute_utility(
380        &self,
381        call: FunctionCall,
382        opts: ExecuteUtilityOptions,
383    ) -> Result<UtilityExecutionResult, Error>;
384
385    /// Profile a transaction for gas estimation and performance data.
386    async fn profile_tx(
387        &self,
388        exec: ExecutionPayload,
389        opts: ProfileOptions,
390    ) -> Result<TxProfileResult, Error>;
391
392    /// Send a transaction to the network.
393    async fn send_tx(&self, exec: ExecutionPayload, opts: SendOptions)
394        -> Result<SendResult, Error>;
395
396    /// Wait until a deployed contract instance is queryable from the node.
397    async fn wait_for_contract(&self, address: AztecAddress) -> Result<(), Error>;
398
399    /// Wait until the block containing a transaction is proven on L1.
400    async fn wait_for_tx_proven(&self, tx_hash: TxHash) -> Result<(), Error>;
401
402    /// Create an authorization witness.
403    async fn create_auth_wit(
404        &self,
405        from: AztecAddress,
406        message_hash_or_intent: MessageHashOrIntent,
407    ) -> Result<AuthWitness, Error>;
408
409    /// Read a public storage value at the given contract address and slot.
410    async fn get_public_storage_at(&self, contract: &AztecAddress, slot: &Fr) -> Result<Fr, Error>;
411}
412
413// ---------------------------------------------------------------------------
414// Arc blanket impl — lets `Arc<W>` be used as a cloneable `Wallet`
415// ---------------------------------------------------------------------------
416
417#[async_trait]
418impl<W: Wallet> Wallet for Arc<W> {
419    async fn get_chain_info(&self) -> Result<ChainInfo, Error> {
420        (**self).get_chain_info().await
421    }
422    async fn get_accounts(&self) -> Result<Vec<Aliased<AztecAddress>>, Error> {
423        (**self).get_accounts().await
424    }
425    async fn get_address_book(&self) -> Result<Vec<Aliased<AztecAddress>>, Error> {
426        (**self).get_address_book().await
427    }
428    async fn register_sender(
429        &self,
430        address: AztecAddress,
431        alias: Option<String>,
432    ) -> Result<AztecAddress, Error> {
433        (**self).register_sender(address, alias).await
434    }
435    async fn get_contract_metadata(
436        &self,
437        address: AztecAddress,
438    ) -> Result<ContractMetadata, Error> {
439        (**self).get_contract_metadata(address).await
440    }
441    async fn get_contract_class_metadata(
442        &self,
443        class_id: Fr,
444    ) -> Result<ContractClassMetadata, Error> {
445        (**self).get_contract_class_metadata(class_id).await
446    }
447    async fn register_contract(
448        &self,
449        instance: ContractInstanceWithAddress,
450        artifact: Option<ContractArtifact>,
451        secret_key: Option<Fr>,
452    ) -> Result<ContractInstanceWithAddress, Error> {
453        (**self)
454            .register_contract(instance, artifact, secret_key)
455            .await
456    }
457    async fn get_private_events(
458        &self,
459        event_metadata: &EventMetadataDefinition,
460        filter: PrivateEventFilter,
461    ) -> Result<Vec<PrivateEvent>, Error> {
462        (**self).get_private_events(event_metadata, filter).await
463    }
464    async fn simulate_tx(
465        &self,
466        exec: ExecutionPayload,
467        opts: SimulateOptions,
468    ) -> Result<TxSimulationResult, Error> {
469        (**self).simulate_tx(exec, opts).await
470    }
471    async fn execute_utility(
472        &self,
473        call: FunctionCall,
474        opts: ExecuteUtilityOptions,
475    ) -> Result<UtilityExecutionResult, Error> {
476        (**self).execute_utility(call, opts).await
477    }
478    async fn profile_tx(
479        &self,
480        exec: ExecutionPayload,
481        opts: ProfileOptions,
482    ) -> Result<TxProfileResult, Error> {
483        (**self).profile_tx(exec, opts).await
484    }
485    async fn send_tx(
486        &self,
487        exec: ExecutionPayload,
488        opts: SendOptions,
489    ) -> Result<SendResult, Error> {
490        (**self).send_tx(exec, opts).await
491    }
492    async fn wait_for_contract(&self, address: AztecAddress) -> Result<(), Error> {
493        (**self).wait_for_contract(address).await
494    }
495    async fn wait_for_tx_proven(&self, tx_hash: TxHash) -> Result<(), Error> {
496        (**self).wait_for_tx_proven(tx_hash).await
497    }
498    async fn create_auth_wit(
499        &self,
500        from: AztecAddress,
501        message_hash_or_intent: MessageHashOrIntent,
502    ) -> Result<AuthWitness, Error> {
503        (**self).create_auth_wit(from, message_hash_or_intent).await
504    }
505    async fn get_public_storage_at(&self, contract: &AztecAddress, slot: &Fr) -> Result<Fr, Error> {
506        (**self).get_public_storage_at(contract, slot).await
507    }
508}
509
510// ---------------------------------------------------------------------------
511// MockWallet
512// ---------------------------------------------------------------------------
513
514fn lock_mutex<T>(mutex: &Mutex<T>) -> Result<std::sync::MutexGuard<'_, T>, Error> {
515    mutex
516        .lock()
517        .map_err(|e| Error::InvalidData(format!("mutex poisoned: {e}")))
518}
519
520/// In-memory test wallet implementation.
521///
522/// Provides configurable behavior for testing contract interactions
523/// without a real wallet backend. Simulation and send calls return
524/// configurable default results.
525pub struct MockWallet {
526    chain_info: ChainInfo,
527    accounts: Mutex<Vec<Aliased<AztecAddress>>>,
528    address_book: Mutex<Vec<Aliased<AztecAddress>>>,
529    contracts: Mutex<HashMap<AztecAddress, ContractMetadata>>,
530    contract_classes: Mutex<HashMap<Fr, ContractClassMetadata>>,
531    simulate_result: TxSimulationResult,
532    send_result: SendResult,
533}
534
535impl MockWallet {
536    /// Create a new mock wallet with the given chain info and sensible defaults.
537    pub fn new(chain_info: ChainInfo) -> Self {
538        Self {
539            chain_info,
540            accounts: Mutex::new(vec![]),
541            address_book: Mutex::new(vec![]),
542            contracts: Mutex::new(HashMap::new()),
543            contract_classes: Mutex::new(HashMap::new()),
544            simulate_result: TxSimulationResult {
545                return_values: serde_json::Value::Null,
546                gas_used: None,
547            },
548            send_result: SendResult {
549                tx_hash: TxHash::zero(),
550            },
551        }
552    }
553
554    /// Set the default result returned by `simulate_tx`.
555    #[must_use]
556    pub fn with_simulate_result(mut self, result: TxSimulationResult) -> Self {
557        self.simulate_result = result;
558        self
559    }
560
561    /// Set the default result returned by `send_tx`.
562    #[must_use]
563    #[allow(clippy::missing_const_for_fn)]
564    pub fn with_send_result(mut self, result: SendResult) -> Self {
565        self.send_result = result;
566        self
567    }
568
569    /// Add an account to the mock wallet.
570    pub fn add_account(&self, address: AztecAddress, alias: Option<String>) -> Result<(), Error> {
571        lock_mutex(&self.accounts)?.push(Aliased {
572            alias: alias.unwrap_or_default(),
573            item: address,
574        });
575        Ok(())
576    }
577
578    /// Register a contract class in the mock wallet.
579    pub fn register_contract_class(
580        &self,
581        class_id: Fr,
582        metadata: ContractClassMetadata,
583    ) -> Result<(), Error> {
584        lock_mutex(&self.contract_classes)?.insert(class_id, metadata);
585        Ok(())
586    }
587}
588
589#[async_trait]
590impl Wallet for MockWallet {
591    async fn get_chain_info(&self) -> Result<ChainInfo, Error> {
592        Ok(self.chain_info.clone())
593    }
594
595    async fn get_accounts(&self) -> Result<Vec<Aliased<AztecAddress>>, Error> {
596        Ok(lock_mutex(&self.accounts)?.clone())
597    }
598
599    async fn get_address_book(&self) -> Result<Vec<Aliased<AztecAddress>>, Error> {
600        Ok(lock_mutex(&self.address_book)?.clone())
601    }
602
603    async fn register_sender(
604        &self,
605        address: AztecAddress,
606        alias: Option<String>,
607    ) -> Result<AztecAddress, Error> {
608        lock_mutex(&self.address_book)?.push(Aliased {
609            alias: alias.unwrap_or_default(),
610            item: address,
611        });
612        Ok(address)
613    }
614
615    async fn get_contract_metadata(
616        &self,
617        address: AztecAddress,
618    ) -> Result<ContractMetadata, Error> {
619        lock_mutex(&self.contracts)?
620            .get(&address)
621            .cloned()
622            .ok_or_else(|| Error::InvalidData(format!("contract not registered: {address}")))
623    }
624
625    async fn get_contract_class_metadata(
626        &self,
627        class_id: Fr,
628    ) -> Result<ContractClassMetadata, Error> {
629        lock_mutex(&self.contract_classes)?
630            .get(&class_id)
631            .cloned()
632            .ok_or_else(|| Error::InvalidData(format!("contract class not registered: {class_id}")))
633    }
634
635    async fn register_contract(
636        &self,
637        instance: ContractInstanceWithAddress,
638        artifact: Option<ContractArtifact>,
639        _secret_key: Option<Fr>,
640    ) -> Result<ContractInstanceWithAddress, Error> {
641        let metadata = ContractMetadata {
642            instance: Some(instance.clone()),
643            is_contract_initialized: false,
644            is_contract_published: false,
645            is_contract_updated: false,
646            updated_contract_class_id: None,
647        };
648        lock_mutex(&self.contracts)?.insert(instance.address, metadata);
649
650        if artifact.is_some() {
651            lock_mutex(&self.contract_classes)?
652                .entry(instance.inner.current_contract_class_id)
653                .or_insert(ContractClassMetadata {
654                    is_artifact_registered: true,
655                    is_contract_class_publicly_registered: false,
656                });
657        }
658
659        Ok(instance)
660    }
661
662    async fn get_private_events(
663        &self,
664        _event_metadata: &EventMetadataDefinition,
665        _filter: PrivateEventFilter,
666    ) -> Result<Vec<PrivateEvent>, Error> {
667        Ok(vec![])
668    }
669
670    async fn simulate_tx(
671        &self,
672        _exec: ExecutionPayload,
673        _opts: SimulateOptions,
674    ) -> Result<TxSimulationResult, Error> {
675        Ok(self.simulate_result.clone())
676    }
677
678    async fn execute_utility(
679        &self,
680        _call: FunctionCall,
681        _opts: ExecuteUtilityOptions,
682    ) -> Result<UtilityExecutionResult, Error> {
683        Ok(UtilityExecutionResult {
684            result: serde_json::Value::Null,
685            stats: None,
686        })
687    }
688
689    async fn profile_tx(
690        &self,
691        _exec: ExecutionPayload,
692        _opts: ProfileOptions,
693    ) -> Result<TxProfileResult, Error> {
694        Ok(TxProfileResult {
695            return_values: serde_json::Value::Null,
696            gas_used: None,
697            profile_data: serde_json::Value::Null,
698        })
699    }
700
701    async fn send_tx(
702        &self,
703        _exec: ExecutionPayload,
704        _opts: SendOptions,
705    ) -> Result<SendResult, Error> {
706        Ok(self.send_result.clone())
707    }
708
709    async fn wait_for_contract(&self, _address: AztecAddress) -> Result<(), Error> {
710        Ok(())
711    }
712
713    async fn wait_for_tx_proven(&self, _tx_hash: TxHash) -> Result<(), Error> {
714        Ok(())
715    }
716
717    async fn create_auth_wit(
718        &self,
719        _from: AztecAddress,
720        _message_hash_or_intent: MessageHashOrIntent,
721    ) -> Result<AuthWitness, Error> {
722        Ok(AuthWitness::default())
723    }
724
725    async fn get_public_storage_at(
726        &self,
727        _contract: &AztecAddress,
728        _slot: &Fr,
729    ) -> Result<Fr, Error> {
730        Ok(Fr::zero())
731    }
732}
733
734// ---------------------------------------------------------------------------
735// Tests
736// ---------------------------------------------------------------------------
737
738#[cfg(test)]
739#[allow(clippy::unwrap_used, clippy::expect_used)]
740mod tests {
741    use super::*;
742    use crate::abi::{AbiParameter, FunctionSelector, FunctionType};
743    use crate::types::{ContractInstance, PublicKeys};
744
745    fn sample_chain_info() -> ChainInfo {
746        ChainInfo {
747            chain_id: Fr::from(31337u64),
748            version: Fr::from(1u64),
749        }
750    }
751
752    fn sample_instance() -> ContractInstanceWithAddress {
753        ContractInstanceWithAddress {
754            address: AztecAddress(Fr::from(1u64)),
755            inner: ContractInstance {
756                version: 1,
757                salt: Fr::from(42u64),
758                deployer: AztecAddress(Fr::from(2u64)),
759                current_contract_class_id: Fr::from(100u64),
760                original_contract_class_id: Fr::from(100u64),
761                initialization_hash: Fr::from(0u64),
762                public_keys: PublicKeys::default(),
763            },
764        }
765    }
766
767    // -- Trait object safety --
768
769    #[test]
770    fn wallet_is_object_safe() {
771        fn _assert_object_safe(_: &dyn Wallet) {}
772    }
773
774    // -- Send + Sync --
775
776    #[test]
777    fn mock_wallet_is_send_sync() {
778        fn assert_send_sync<T: Send + Sync>() {}
779        assert_send_sync::<MockWallet>();
780    }
781
782    // -- Supporting type serde --
783
784    #[test]
785    fn chain_info_roundtrip() {
786        let info = sample_chain_info();
787        let json = serde_json::to_string(&info).expect("serialize ChainInfo");
788        let decoded: ChainInfo = serde_json::from_str(&json).expect("deserialize ChainInfo");
789        assert_eq!(decoded, info);
790    }
791
792    #[test]
793    fn aliased_with_alias_roundtrip() {
794        let aliased = Aliased {
795            alias: "alice".to_owned(),
796            item: AztecAddress(Fr::from(1u64)),
797        };
798        let json = serde_json::to_string(&aliased).expect("serialize");
799        let decoded: Aliased<AztecAddress> = serde_json::from_str(&json).expect("deserialize");
800        assert_eq!(decoded, aliased);
801    }
802
803    #[test]
804    fn aliased_without_alias_roundtrip() {
805        let aliased: Aliased<AztecAddress> = Aliased {
806            alias: String::new(),
807            item: AztecAddress(Fr::from(1u64)),
808        };
809        let json = serde_json::to_string(&aliased).expect("serialize");
810        let decoded: Aliased<AztecAddress> = serde_json::from_str(&json).expect("deserialize");
811        assert_eq!(decoded, aliased);
812    }
813
814    #[test]
815    fn simulate_options_default() {
816        let opts = SimulateOptions::default();
817        assert_eq!(opts.from, AztecAddress(Fr::zero()));
818        assert!(!opts.skip_validation);
819        assert!(opts.skip_fee_enforcement);
820        assert!(opts.gas_settings.is_none());
821        assert!(opts.auth_witnesses.is_empty());
822        assert!(opts.capsules.is_empty());
823        assert!(opts.additional_scopes.is_empty());
824    }
825
826    #[test]
827    fn send_options_default() {
828        let opts = SendOptions::default();
829        assert_eq!(opts.from, AztecAddress(Fr::zero()));
830        assert!(opts.gas_settings.is_none());
831        assert!(opts.auth_witnesses.is_empty());
832        assert!(opts.capsules.is_empty());
833        assert!(opts.additional_scopes.is_empty());
834    }
835
836    #[test]
837    fn profile_options_default() {
838        let opts = ProfileOptions::default();
839        assert_eq!(opts.from, AztecAddress(Fr::zero()));
840        assert!(opts.profile_mode.is_none());
841        assert!(opts.skip_proof_generation);
842        assert!(opts.gas_settings.is_none());
843    }
844
845    #[test]
846    fn execute_utility_options_default() {
847        let opts = ExecuteUtilityOptions::default();
848        assert_eq!(opts.scope, AztecAddress(Fr::zero()));
849        assert!(opts.auth_witnesses.is_empty());
850    }
851
852    #[test]
853    fn send_result_roundtrip() {
854        let result = SendResult {
855            tx_hash: TxHash::zero(),
856        };
857        let json = serde_json::to_string(&result).expect("serialize");
858        let decoded: SendResult = serde_json::from_str(&json).expect("deserialize");
859        assert_eq!(decoded, result);
860    }
861
862    #[test]
863    fn private_event_filter_default() {
864        let filter = PrivateEventFilter::default();
865        assert_eq!(filter.contract_address, AztecAddress(Fr::zero()));
866        assert!(filter.scopes.is_empty());
867        assert!(filter.tx_hash.is_none());
868        assert!(filter.from_block.is_none());
869        assert!(filter.to_block.is_none());
870        assert!(filter.after_log.is_none());
871    }
872
873    #[test]
874    fn message_hash_roundtrip() {
875        let msg = MessageHashOrIntent::Hash {
876            hash: Fr::from(42u64),
877        };
878        let json = serde_json::to_string(&msg).expect("serialize");
879        let decoded: MessageHashOrIntent = serde_json::from_str(&json).expect("deserialize");
880        assert_eq!(decoded, msg);
881    }
882
883    #[test]
884    fn message_intent_roundtrip() {
885        let msg = MessageHashOrIntent::Intent {
886            caller: AztecAddress(Fr::from(1u64)),
887            call: FunctionCall {
888                to: AztecAddress(Fr::from(2u64)),
889                selector: FunctionSelector::from_hex("0xaabbccdd").expect("valid selector"),
890                args: vec![],
891                function_type: FunctionType::Private,
892                is_static: false,
893                hide_msg_sender: false,
894            },
895        };
896        let json = serde_json::to_string(&msg).expect("serialize");
897        let decoded: MessageHashOrIntent = serde_json::from_str(&json).expect("deserialize");
898        assert_eq!(decoded, msg);
899    }
900
901    #[test]
902    fn contract_metadata_roundtrip() {
903        let meta = ContractMetadata {
904            instance: Some(sample_instance()),
905            is_contract_initialized: false,
906            is_contract_published: false,
907            is_contract_updated: false,
908            updated_contract_class_id: None,
909        };
910        let json = serde_json::to_string(&meta).expect("serialize");
911        let decoded: ContractMetadata = serde_json::from_str(&json).expect("deserialize");
912        assert_eq!(decoded, meta);
913    }
914
915    #[test]
916    fn contract_class_metadata_roundtrip() {
917        let meta = ContractClassMetadata {
918            is_artifact_registered: true,
919            is_contract_class_publicly_registered: true,
920        };
921        let json = serde_json::to_string(&meta).expect("serialize");
922        let decoded: ContractClassMetadata = serde_json::from_str(&json).expect("deserialize");
923        assert_eq!(decoded, meta);
924    }
925
926    #[test]
927    fn event_metadata_definition_roundtrip() {
928        let def = EventMetadataDefinition {
929            event_selector: EventSelector(Fr::from(1u64)),
930            abi_type: AbiType::Struct {
931                name: "Transfer".to_owned(),
932                fields: vec![
933                    AbiParameter {
934                        name: "amount".to_owned(),
935                        typ: AbiType::Field,
936                        visibility: None,
937                    },
938                    AbiParameter {
939                        name: "sender".to_owned(),
940                        typ: AbiType::Field,
941                        visibility: None,
942                    },
943                ],
944            },
945            field_names: vec!["amount".to_owned(), "sender".to_owned()],
946        };
947        let json = serde_json::to_string(&def).expect("serialize");
948        let decoded: EventMetadataDefinition = serde_json::from_str(&json).expect("deserialize");
949        assert_eq!(decoded, def);
950    }
951
952    // -- MockWallet: chain info --
953
954    #[tokio::test]
955    async fn mock_wallet_get_chain_info() {
956        let wallet = MockWallet::new(sample_chain_info());
957        let info = wallet.get_chain_info().await.expect("get chain info");
958        assert_eq!(info.chain_id, Fr::from(31337u64));
959        assert_eq!(info.version, Fr::from(1u64));
960    }
961
962    // -- MockWallet: accounts --
963
964    #[tokio::test]
965    async fn mock_wallet_accounts_initially_empty() {
966        let wallet = MockWallet::new(sample_chain_info());
967        let accounts = wallet.get_accounts().await.expect("get accounts");
968        assert!(accounts.is_empty());
969    }
970
971    #[tokio::test]
972    async fn mock_wallet_add_and_get_accounts() {
973        let wallet = MockWallet::new(sample_chain_info());
974        wallet
975            .add_account(AztecAddress(Fr::from(1u64)), Some("alice".into()))
976            .expect("add account");
977        wallet
978            .add_account(AztecAddress(Fr::from(2u64)), None)
979            .expect("add account");
980
981        let accounts = wallet.get_accounts().await.expect("get accounts");
982        assert_eq!(accounts.len(), 2);
983        assert_eq!(accounts[0].alias, "alice");
984        assert!(accounts[1].alias.is_empty());
985    }
986
987    // -- MockWallet: address book --
988
989    #[tokio::test]
990    async fn mock_wallet_register_sender() {
991        let wallet = MockWallet::new(sample_chain_info());
992        let addr = AztecAddress(Fr::from(99u64));
993        let result = wallet
994            .register_sender(addr, Some("bob".into()))
995            .await
996            .expect("register sender");
997        assert_eq!(result, addr);
998
999        let book = wallet.get_address_book().await.expect("get address book");
1000        assert_eq!(book.len(), 1);
1001        assert_eq!(book[0].item, addr);
1002        assert_eq!(book[0].alias, "bob");
1003    }
1004
1005    // -- MockWallet: contract registration --
1006
1007    #[tokio::test]
1008    async fn mock_wallet_register_and_get_contract() {
1009        let wallet = MockWallet::new(sample_chain_info());
1010        let instance = sample_instance();
1011
1012        let registered = wallet
1013            .register_contract(instance.clone(), None, None)
1014            .await
1015            .expect("register contract");
1016        assert_eq!(registered.address, instance.address);
1017
1018        let metadata = wallet
1019            .get_contract_metadata(instance.address)
1020            .await
1021            .expect("get contract metadata");
1022        assert_eq!(
1023            metadata.instance.expect("registered instance").address,
1024            instance.address
1025        );
1026        assert!(!metadata.is_contract_initialized);
1027        assert!(!metadata.is_contract_published);
1028        assert!(!metadata.is_contract_updated);
1029        assert!(metadata.updated_contract_class_id.is_none());
1030    }
1031
1032    #[tokio::test]
1033    async fn mock_wallet_unregistered_contract_fails() {
1034        let wallet = MockWallet::new(sample_chain_info());
1035        let result = wallet
1036            .get_contract_metadata(AztecAddress(Fr::from(999u64)))
1037            .await;
1038        assert!(result.is_err());
1039    }
1040
1041    // -- MockWallet: contract class --
1042
1043    #[tokio::test]
1044    async fn mock_wallet_contract_class_metadata() {
1045        let wallet = MockWallet::new(sample_chain_info());
1046        let class_id = Fr::from(100u64);
1047
1048        wallet
1049            .register_contract_class(
1050                class_id,
1051                ContractClassMetadata {
1052                    is_artifact_registered: true,
1053                    is_contract_class_publicly_registered: true,
1054                },
1055            )
1056            .expect("register class");
1057
1058        let meta = wallet
1059            .get_contract_class_metadata(class_id)
1060            .await
1061            .expect("get class metadata");
1062        assert!(meta.is_artifact_registered);
1063        assert!(meta.is_contract_class_publicly_registered);
1064    }
1065
1066    #[tokio::test]
1067    async fn mock_wallet_unregistered_class_fails() {
1068        let wallet = MockWallet::new(sample_chain_info());
1069        let result = wallet.get_contract_class_metadata(Fr::from(999u64)).await;
1070        assert!(result.is_err());
1071    }
1072
1073    // -- MockWallet: simulate --
1074
1075    #[tokio::test]
1076    async fn mock_wallet_simulate_default() {
1077        let wallet = MockWallet::new(sample_chain_info());
1078        let result = wallet
1079            .simulate_tx(ExecutionPayload::default(), SimulateOptions::default())
1080            .await
1081            .expect("simulate tx");
1082        assert_eq!(result.return_values, serde_json::Value::Null);
1083        assert!(result.gas_used.is_none());
1084    }
1085
1086    #[tokio::test]
1087    async fn mock_wallet_simulate_custom_result() {
1088        let wallet =
1089            MockWallet::new(sample_chain_info()).with_simulate_result(TxSimulationResult {
1090                return_values: serde_json::json!([42]),
1091                gas_used: Some(Gas {
1092                    da_gas: 100,
1093                    l2_gas: 200,
1094                }),
1095            });
1096
1097        let result = wallet
1098            .simulate_tx(ExecutionPayload::default(), SimulateOptions::default())
1099            .await
1100            .expect("simulate tx");
1101        assert_eq!(result.return_values, serde_json::json!([42]));
1102        assert_eq!(result.gas_used.as_ref().map(|g| g.l2_gas), Some(200));
1103    }
1104
1105    // -- MockWallet: send --
1106
1107    #[tokio::test]
1108    async fn mock_wallet_send_default() {
1109        let wallet = MockWallet::new(sample_chain_info());
1110        let result = wallet
1111            .send_tx(ExecutionPayload::default(), SendOptions::default())
1112            .await
1113            .expect("send tx");
1114        assert_eq!(result.tx_hash, TxHash::zero());
1115    }
1116
1117    #[tokio::test]
1118    async fn mock_wallet_send_custom_result() {
1119        let tx_hash =
1120            TxHash::from_hex("0x00000000000000000000000000000000000000000000000000000000deadbeef")
1121                .expect("valid hex");
1122        let wallet = MockWallet::new(sample_chain_info()).with_send_result(SendResult { tx_hash });
1123
1124        let result = wallet
1125            .send_tx(ExecutionPayload::default(), SendOptions::default())
1126            .await
1127            .expect("send tx");
1128        assert_eq!(result.tx_hash, tx_hash);
1129    }
1130
1131    // -- MockWallet: utility execution --
1132
1133    #[tokio::test]
1134    async fn mock_wallet_execute_utility() {
1135        let wallet = MockWallet::new(sample_chain_info());
1136        let call = FunctionCall {
1137            to: AztecAddress(Fr::from(1u64)),
1138            selector: FunctionSelector::from_hex("0xaabbccdd").expect("valid selector"),
1139            args: vec![],
1140            function_type: FunctionType::Utility,
1141            is_static: true,
1142            hide_msg_sender: false,
1143        };
1144        let result = wallet
1145            .execute_utility(call, ExecuteUtilityOptions::default())
1146            .await
1147            .expect("execute utility");
1148        assert_eq!(result.result, serde_json::Value::Null);
1149        assert!(result.stats.is_none());
1150    }
1151
1152    // -- MockWallet: profile --
1153
1154    #[tokio::test]
1155    async fn mock_wallet_profile_tx() {
1156        let wallet = MockWallet::new(sample_chain_info());
1157        let result = wallet
1158            .profile_tx(ExecutionPayload::default(), ProfileOptions::default())
1159            .await
1160            .expect("profile tx");
1161        assert_eq!(result.return_values, serde_json::Value::Null);
1162        assert!(result.gas_used.is_none());
1163    }
1164
1165    // -- MockWallet: private events --
1166
1167    #[tokio::test]
1168    async fn mock_wallet_private_events_empty() {
1169        let wallet = MockWallet::new(sample_chain_info());
1170        let events = wallet
1171            .get_private_events(
1172                &EventMetadataDefinition {
1173                    event_selector: EventSelector(Fr::from(1u64)),
1174                    abi_type: AbiType::Struct {
1175                        name: "AmountOnly".to_owned(),
1176                        fields: vec![AbiParameter {
1177                            name: "amount".to_owned(),
1178                            typ: AbiType::Field,
1179                            visibility: None,
1180                        }],
1181                    },
1182                    field_names: vec!["amount".to_owned()],
1183                },
1184                PrivateEventFilter {
1185                    contract_address: AztecAddress(Fr::from(1u64)),
1186                    scopes: vec![AztecAddress(Fr::from(2u64))],
1187                    ..PrivateEventFilter::default()
1188                },
1189            )
1190            .await
1191            .expect("get private events");
1192        assert!(events.is_empty());
1193    }
1194
1195    // -- MockWallet: auth witness --
1196
1197    #[tokio::test]
1198    async fn mock_wallet_create_auth_wit() {
1199        let wallet = MockWallet::new(sample_chain_info());
1200        let wit = wallet
1201            .create_auth_wit(
1202                AztecAddress(Fr::from(1u64)),
1203                MessageHashOrIntent::Hash {
1204                    hash: Fr::from(42u64),
1205                },
1206            )
1207            .await
1208            .expect("create auth wit");
1209        assert!(wit.fields.is_empty());
1210    }
1211
1212    // -- ProfileMode serialization --
1213
1214    #[test]
1215    fn profile_mode_serialization() {
1216        for mode in [
1217            ProfileMode::Gates,
1218            ProfileMode::ExecutionSteps,
1219            ProfileMode::Full,
1220        ] {
1221            let json = serde_json::to_string(&mode).expect("serialize");
1222            let decoded: ProfileMode = serde_json::from_str(&json).expect("deserialize");
1223            assert_eq!(decoded, mode);
1224        }
1225        assert_eq!(
1226            serde_json::to_string(&ProfileMode::Gates).unwrap(),
1227            "\"gates\""
1228        );
1229        assert_eq!(
1230            serde_json::to_string(&ProfileMode::ExecutionSteps).unwrap(),
1231            "\"execution-steps\""
1232        );
1233        assert_eq!(
1234            serde_json::to_string(&ProfileMode::Full).unwrap(),
1235            "\"full\""
1236        );
1237    }
1238}