1use async_trait::async_trait;
2use serde::{Deserialize, Serialize};
3
4use crate::abi::{AbiValue, ContractArtifact};
5use crate::error::Error;
6use crate::fee::GasSettings;
7use crate::tx::{AuthWitness, Capsule, ExecutionPayload, HashedValues, TxContext};
8use crate::types::{AztecAddress, CompleteAddress, ContractInstanceWithAddress, Fr, Salt};
9use crate::wallet::{
10 ChainInfo, MessageHashOrIntent, SendOptions, SendResult, SimulateOptions, TxSimulationResult,
11 Wallet,
12};
13
14use aztec_contract::deployment::{
15 get_contract_instance_from_instantiation_params, ContractInstantiationParams,
16};
17use aztec_fee::FeePaymentMethod;
18
19#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub struct EntrypointOptions {
27 pub fee_payer: Option<AztecAddress>,
29 pub gas_settings: Option<GasSettings>,
31 #[serde(default)]
33 pub fee_payment_method: Option<u8>,
34}
35
36#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub struct TxExecutionRequest {
42 pub origin: AztecAddress,
44 pub function_selector: crate::abi::FunctionSelector,
46 pub first_call_args_hash: Fr,
48 pub tx_context: TxContext,
50 pub args_of_calls: Vec<HashedValues>,
52 pub auth_witnesses: Vec<AuthWitness>,
54 pub capsules: Vec<Capsule>,
56 pub salt: Fr,
58 #[serde(default, skip_serializing_if = "Option::is_none")]
60 pub fee_payer: Option<AztecAddress>,
61}
62
63#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
65#[serde(rename_all = "camelCase")]
66pub struct InitializationSpec {
67 pub constructor_name: String,
69 pub constructor_args: Vec<AbiValue>,
71}
72
73#[async_trait]
83pub trait AuthorizationProvider: Send + Sync {
84 async fn create_auth_wit(
86 &self,
87 intent: MessageHashOrIntent,
88 chain_info: &ChainInfo,
89 ) -> Result<AuthWitness, Error>;
90}
91
92#[async_trait]
99pub trait Account: Send + Sync + AuthorizationProvider {
100 fn complete_address(&self) -> &CompleteAddress;
102
103 fn address(&self) -> AztecAddress;
105
106 async fn create_tx_execution_request(
111 &self,
112 exec: ExecutionPayload,
113 gas_settings: GasSettings,
114 chain_info: &ChainInfo,
115 options: EntrypointOptions,
116 ) -> Result<TxExecutionRequest, Error>;
117
118 async fn wrap_execution_payload(
123 &self,
124 exec: ExecutionPayload,
125 options: EntrypointOptions,
126 ) -> Result<ExecutionPayload, Error>;
127}
128
129#[async_trait]
135pub trait AccountContract: Send + Sync {
136 async fn contract_artifact(&self) -> Result<ContractArtifact, Error>;
138
139 async fn initialization_function_and_args(&self) -> Result<Option<InitializationSpec>, Error>;
141
142 fn account(&self, address: CompleteAddress) -> Box<dyn Account>;
144
145 fn auth_witness_provider(&self, address: CompleteAddress) -> Box<dyn AuthorizationProvider>;
147}
148
149pub async fn get_account_contract_address(
161 account_contract: &dyn AccountContract,
162 secret: Fr,
163 salt: impl Into<Salt>,
164) -> Result<AztecAddress, Error> {
165 let salt: Fr = salt.into();
166 let derived = aztec_crypto::derive_keys(&secret);
167 let public_keys = derived.public_keys;
168
169 let init_spec = account_contract.initialization_function_and_args().await?;
170 let artifact = account_contract.contract_artifact().await?;
171
172 let constructor_name = init_spec
173 .as_ref()
174 .map(|spec| spec.constructor_name.as_str());
175 let constructor_args = init_spec
176 .as_ref()
177 .map(|spec| spec.constructor_args.clone())
178 .unwrap_or_default();
179
180 let instance = get_contract_instance_from_instantiation_params(
181 &artifact,
182 ContractInstantiationParams {
183 constructor_name,
184 constructor_args,
185 salt,
186 public_keys,
187 deployer: AztecAddress::zero(),
188 },
189 )?;
190
191 Ok(instance.address)
192}
193
194pub struct AccountWithSecretKey {
203 pub account: Box<dyn Account>,
205 pub secret_key: Fr,
207 pub salt: Salt,
209}
210
211impl std::fmt::Debug for AccountWithSecretKey {
212 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213 f.debug_struct("AccountWithSecretKey")
214 .field("secret_key", &self.secret_key)
215 .field("salt", &self.salt)
216 .finish_non_exhaustive()
217 }
218}
219
220#[async_trait]
221impl AuthorizationProvider for AccountWithSecretKey {
222 async fn create_auth_wit(
223 &self,
224 intent: MessageHashOrIntent,
225 chain_info: &ChainInfo,
226 ) -> Result<AuthWitness, Error> {
227 self.account.create_auth_wit(intent, chain_info).await
228 }
229}
230
231#[async_trait]
232impl Account for AccountWithSecretKey {
233 fn complete_address(&self) -> &CompleteAddress {
234 self.account.complete_address()
235 }
236
237 fn address(&self) -> AztecAddress {
238 self.account.address()
239 }
240
241 async fn create_tx_execution_request(
242 &self,
243 exec: ExecutionPayload,
244 gas_settings: GasSettings,
245 chain_info: &ChainInfo,
246 options: EntrypointOptions,
247 ) -> Result<TxExecutionRequest, Error> {
248 self.account
249 .create_tx_execution_request(exec, gas_settings, chain_info, options)
250 .await
251 }
252
253 async fn wrap_execution_payload(
254 &self,
255 exec: ExecutionPayload,
256 options: EntrypointOptions,
257 ) -> Result<ExecutionPayload, Error> {
258 self.account.wrap_execution_payload(exec, options).await
259 }
260}
261
262pub struct DeployAccountOptions {
268 pub skip_class_publication: bool,
270 pub skip_instance_publication: bool,
272 pub skip_initialization: bool,
274 pub skip_registration: bool,
276 pub from: Option<AztecAddress>,
278 pub fee: Option<std::sync::Arc<dyn aztec_fee::FeePaymentMethod>>,
280 pub fee_entrypoint_options: Option<crate::entrypoint::DefaultAccountEntrypointOptions>,
282}
283
284impl Default for DeployAccountOptions {
285 fn default() -> Self {
286 Self {
287 skip_class_publication: true,
288 skip_instance_publication: true,
289 skip_initialization: false,
290 skip_registration: false,
291 from: None,
292 fee: None,
293 fee_entrypoint_options: None,
294 }
295 }
296}
297
298impl std::fmt::Debug for DeployAccountOptions {
299 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300 f.debug_struct("DeployAccountOptions")
301 .field("skip_class_publication", &self.skip_class_publication)
302 .field("skip_instance_publication", &self.skip_instance_publication)
303 .field("skip_initialization", &self.skip_initialization)
304 .field("from", &self.from)
305 .field("has_fee", &self.fee.is_some())
306 .finish_non_exhaustive()
307 }
308}
309
310pub struct DeployAccountMethod<'a, W> {
316 wallet: &'a W,
317 account: std::sync::Arc<dyn Account>,
318 inner: aztec_contract::deployment::DeployMethod<'a, W>,
319 inner_instance: ContractInstanceWithAddress,
320}
321
322impl<W> std::fmt::Debug for DeployAccountMethod<'_, W> {
323 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
324 f.debug_struct("DeployAccountMethod")
325 .field("inner", &self.inner)
326 .finish_non_exhaustive()
327 }
328}
329
330impl<W: Wallet> DeployAccountMethod<'_, W> {
331 pub async fn request(&self, opts: &DeployAccountOptions) -> Result<ExecutionPayload, Error> {
337 let deploy_opts = self.to_deploy_options(opts);
338 let deploy_payload = self.inner.request(&deploy_opts).await?;
339 let is_self_deploy = opts.from == Some(AztecAddress::zero());
340
341 let fee_payload = match (&opts.fee, is_self_deploy) {
342 (Some(method), true) => {
343 let wrapped = crate::meta_payment::AccountEntrypointMetaPaymentMethod::new(
344 self.account.clone(),
345 Some(method.clone()),
346 opts.fee_entrypoint_options.clone(),
347 );
348 Some(wrapped.get_fee_execution_payload().await?)
349 }
350 (Some(method), false) => Some(method.get_fee_execution_payload().await?),
351 (None, true) => {
352 let wrapped = crate::meta_payment::AccountEntrypointMetaPaymentMethod::new(
353 self.account.clone(),
354 None,
355 Some(crate::entrypoint::DefaultAccountEntrypointOptions {
356 cancellable: false,
357 tx_nonce: None,
358 fee_payment_method_options:
359 crate::entrypoint::AccountFeePaymentMethodOptions::PreexistingFeeJuice,
360 }),
361 );
362 Some(wrapped.get_fee_execution_payload().await?)
363 }
364 (None, false) => None,
365 };
366
367 match fee_payload {
368 Some(fee) if is_self_deploy => ExecutionPayload::merge(vec![deploy_payload, fee]),
369 Some(fee) => ExecutionPayload::merge(vec![fee, deploy_payload]),
370 None => Ok(deploy_payload),
371 }
372 }
373
374 pub async fn simulate(
376 &self,
377 opts: &DeployAccountOptions,
378 sim_opts: SimulateOptions,
379 ) -> Result<TxSimulationResult, Error> {
380 let payload = self.request(opts).await?;
381 self.wallet.simulate_tx(payload, sim_opts).await
382 }
383
384 pub async fn send(
386 &self,
387 opts: &DeployAccountOptions,
388 send_opts: SendOptions,
389 ) -> Result<DeployResult, Error> {
390 let deploy_opts = self.to_deploy_options(opts);
391 let instance = self.inner.get_instance(&deploy_opts)?;
392 let payload = self.request(opts).await?;
393 let send_result = self.wallet.send_tx(payload, send_opts).await?;
394 Ok(DeployResult {
395 send_result,
396 instance,
397 })
398 }
399
400 pub fn instance(&self) -> &ContractInstanceWithAddress {
402 &self.inner_instance
403 }
404
405 fn to_deploy_options(
406 &self,
407 opts: &DeployAccountOptions,
408 ) -> aztec_contract::deployment::DeployOptions {
409 aztec_contract::deployment::DeployOptions {
410 contract_address_salt: Some(self.inner_instance.inner.salt),
411 skip_class_publication: opts.skip_class_publication,
412 skip_instance_publication: opts.skip_instance_publication,
413 skip_initialization: opts.skip_initialization,
414 skip_registration: opts.skip_registration,
415 universal_deploy: true,
416 from: None,
417 }
418 }
419}
420
421#[derive(Clone, Debug)]
423pub struct DeployResult {
424 pub send_result: SendResult,
426 pub instance: ContractInstanceWithAddress,
428}
429
430pub struct AccountManager<W> {
441 wallet: W,
442 secret_key: Fr,
443 account_contract: Box<dyn AccountContract>,
444 has_initializer: bool,
445 instance: ContractInstanceWithAddress,
446 salt: Salt,
447}
448
449impl<W: Wallet> AccountManager<W> {
450 pub async fn create(
457 wallet: W,
458 secret_key: Fr,
459 account_contract: Box<dyn AccountContract>,
460 salt: Option<impl Into<Salt>>,
461 ) -> Result<Self, Error> {
462 let salt: Salt = salt.map(Into::into).unwrap_or_else(Salt::random);
463
464 let artifact = account_contract.contract_artifact().await?;
465 let init_spec = account_contract.initialization_function_and_args().await?;
466
467 if let Some(spec) = &init_spec {
468 let initializer = artifact.find_function(&spec.constructor_name)?;
469 if !initializer.is_initializer {
470 return Err(Error::Abi(format!(
471 "function '{}' in account artifact '{}' is not marked as an initializer",
472 spec.constructor_name, artifact.name
473 )));
474 }
475
476 let expected = initializer.parameters.len();
477 let actual = spec.constructor_args.len();
478 if actual != expected {
479 return Err(Error::Abi(format!(
480 "initializer '{}' in account artifact '{}' expects {expected} argument(s), got {actual}",
481 spec.constructor_name, artifact.name
482 )));
483 }
484 }
485
486 let derived = aztec_crypto::derive_keys(&secret_key);
488 let public_keys = derived.public_keys;
489
490 let constructor_name = init_spec
491 .as_ref()
492 .map(|spec| spec.constructor_name.as_str());
493 let constructor_args = init_spec
494 .as_ref()
495 .map(|spec| spec.constructor_args.clone())
496 .unwrap_or_default();
497
498 let instance = get_contract_instance_from_instantiation_params(
499 &artifact,
500 ContractInstantiationParams {
501 constructor_name,
502 constructor_args,
503 salt,
504 public_keys,
505 deployer: AztecAddress::zero(),
506 },
507 )?;
508
509 Ok(Self {
510 wallet,
511 secret_key,
512 account_contract,
513 has_initializer: init_spec.is_some(),
514 instance,
515 salt,
516 })
517 }
518
519 #[allow(clippy::unused_async)]
524 pub async fn complete_address(&self) -> Result<CompleteAddress, Error> {
525 use aztec_core::hash::{
526 compute_address, compute_partial_address, compute_salted_initialization_hash,
527 };
528 use aztec_crypto::derive_keys;
529
530 let derived = derive_keys(&self.secret_key);
531
532 let salted_init_hash = compute_salted_initialization_hash(
533 self.instance.inner.salt,
534 self.instance.inner.initialization_hash,
535 self.instance.inner.deployer,
536 );
537 let partial_address = compute_partial_address(
538 self.instance.inner.original_contract_class_id,
539 salted_init_hash,
540 );
541
542 let address = compute_address(&derived.public_keys, &partial_address)?;
543
544 Ok(CompleteAddress {
545 address,
546 public_keys: derived.public_keys,
547 partial_address,
548 })
549 }
550
551 pub const fn address(&self) -> AztecAddress {
553 self.instance.address
554 }
555
556 pub const fn instance(&self) -> &ContractInstanceWithAddress {
558 &self.instance
559 }
560
561 pub const fn salt(&self) -> Salt {
563 self.salt
564 }
565
566 pub const fn secret_key(&self) -> Fr {
568 self.secret_key
569 }
570
571 pub const fn has_initializer(&self) -> bool {
573 self.has_initializer
574 }
575
576 pub async fn account(&self) -> Result<AccountWithSecretKey, Error> {
579 let complete_addr = self.complete_address().await?;
580 Ok(AccountWithSecretKey {
581 account: self.account_contract.account(complete_addr),
582 secret_key: self.secret_key,
583 salt: self.salt,
584 })
585 }
586
587 pub async fn deploy_method(&self) -> Result<DeployAccountMethod<'_, W>, Error> {
593 let artifact = self.account_contract.contract_artifact().await?;
594 let init_spec = self
595 .account_contract
596 .initialization_function_and_args()
597 .await?;
598
599 let complete_addr = self.complete_address().await?;
600 let account: std::sync::Arc<dyn Account> =
601 std::sync::Arc::from(self.account_contract.account(complete_addr));
602
603 let constructor_args = init_spec
604 .as_ref()
605 .map(|s| s.constructor_args.clone())
606 .unwrap_or_default();
607
608 let mut deployer =
609 aztec_contract::deployment::ContractDeployer::new(artifact, &self.wallet)
610 .with_public_keys(self.instance.inner.public_keys.clone());
611 if let Some(spec) = &init_spec {
612 deployer = deployer.with_constructor_name(spec.constructor_name.clone());
613 }
614
615 let inner = deployer.deploy(constructor_args)?;
616
617 let deploy_opts = aztec_contract::deployment::DeployOptions {
619 contract_address_salt: Some(self.salt),
620 ..Default::default()
621 };
622 let inner_instance = inner.get_instance(&deploy_opts)?;
623
624 Ok(DeployAccountMethod {
625 wallet: &self.wallet,
626 account,
627 inner,
628 inner_instance,
629 })
630 }
631}
632
633#[cfg(test)]
638#[allow(clippy::unwrap_used, clippy::expect_used)]
639mod tests {
640 use super::*;
641 use crate::abi::{AbiParameter, AbiType, FunctionArtifact, FunctionSelector, FunctionType};
642 use crate::fee::Gas;
643 use crate::types::PublicKeys;
644 use crate::wallet::MockWallet;
645
646 struct MockAuthProvider {
649 addr: CompleteAddress,
650 }
651
652 #[async_trait]
653 impl AuthorizationProvider for MockAuthProvider {
654 async fn create_auth_wit(
655 &self,
656 _intent: MessageHashOrIntent,
657 _chain_info: &ChainInfo,
658 ) -> Result<AuthWitness, Error> {
659 Ok(AuthWitness {
660 fields: vec![self.addr.address.0],
661 ..Default::default()
662 })
663 }
664 }
665
666 struct MockAccount {
667 addr: CompleteAddress,
668 }
669
670 #[async_trait]
671 impl AuthorizationProvider for MockAccount {
672 async fn create_auth_wit(
673 &self,
674 _intent: MessageHashOrIntent,
675 _chain_info: &ChainInfo,
676 ) -> Result<AuthWitness, Error> {
677 Ok(AuthWitness {
678 fields: vec![self.addr.address.0],
679 ..Default::default()
680 })
681 }
682 }
683
684 #[async_trait]
685 impl Account for MockAccount {
686 fn complete_address(&self) -> &CompleteAddress {
687 &self.addr
688 }
689
690 fn address(&self) -> AztecAddress {
691 self.addr.address
692 }
693
694 async fn create_tx_execution_request(
695 &self,
696 exec: ExecutionPayload,
697 gas_settings: GasSettings,
698 chain_info: &ChainInfo,
699 _options: EntrypointOptions,
700 ) -> Result<TxExecutionRequest, Error> {
701 Ok(TxExecutionRequest {
702 origin: self.addr.address,
703 function_selector: FunctionSelector::from_hex("0x12345678")
704 .expect("valid selector"),
705 first_call_args_hash: Fr::from(11u64),
706 tx_context: TxContext {
707 chain_id: chain_info.chain_id,
708 version: chain_info.version,
709 gas_settings,
710 },
711 args_of_calls: exec.extra_hashed_args,
712 auth_witnesses: exec.auth_witnesses,
713 capsules: exec.capsules,
714 salt: Fr::from(7u64),
715 fee_payer: exec.fee_payer,
716 })
717 }
718
719 async fn wrap_execution_payload(
720 &self,
721 exec: ExecutionPayload,
722 options: EntrypointOptions,
723 ) -> Result<ExecutionPayload, Error> {
724 Ok(ExecutionPayload {
725 fee_payer: options.fee_payer.or(exec.fee_payer),
726 ..exec
727 })
728 }
729 }
730
731 struct MockAccountContract;
732
733 #[async_trait]
734 impl AccountContract for MockAccountContract {
735 async fn contract_artifact(&self) -> Result<ContractArtifact, Error> {
736 Ok(ContractArtifact {
737 name: "MockAccount".to_owned(),
738 functions: vec![FunctionArtifact {
739 name: "constructor".to_owned(),
740 function_type: FunctionType::Private,
741 is_initializer: true,
742 is_static: false,
743 parameters: vec![AbiParameter {
744 name: "owner".to_owned(),
745 typ: AbiType::Field,
746 visibility: None,
747 }],
748 return_types: vec![],
749 selector: Some(
750 FunctionSelector::from_hex("0x12345678").expect("valid selector"),
751 ),
752 bytecode: None,
753 verification_key_hash: None,
754 verification_key: None,
755 custom_attributes: None,
756 is_unconstrained: None,
757 debug_symbols: None,
758 error_types: None,
759 is_only_self: None,
760 }],
761 outputs: None,
762 file_map: None,
763 context_inputs_sizes: None,
764 })
765 }
766
767 async fn initialization_function_and_args(
768 &self,
769 ) -> Result<Option<InitializationSpec>, Error> {
770 Ok(Some(InitializationSpec {
771 constructor_name: "constructor".to_owned(),
772 constructor_args: vec![AbiValue::Field(Fr::from(42u64))],
773 }))
774 }
775
776 fn account(&self, address: CompleteAddress) -> Box<dyn Account> {
777 Box::new(MockAccount { addr: address })
778 }
779
780 fn auth_witness_provider(
781 &self,
782 address: CompleteAddress,
783 ) -> Box<dyn AuthorizationProvider> {
784 Box::new(MockAuthProvider { addr: address })
785 }
786 }
787
788 struct NoInitializerAccountContract;
789
790 #[async_trait]
791 impl AccountContract for NoInitializerAccountContract {
792 async fn contract_artifact(&self) -> Result<ContractArtifact, Error> {
793 Ok(ContractArtifact {
794 name: "NoInitializerAccount".to_owned(),
795 functions: vec![],
796 outputs: None,
797 file_map: None,
798 context_inputs_sizes: None,
799 })
800 }
801
802 async fn initialization_function_and_args(
803 &self,
804 ) -> Result<Option<InitializationSpec>, Error> {
805 Ok(None)
806 }
807
808 fn account(&self, address: CompleteAddress) -> Box<dyn Account> {
809 Box::new(MockAccount { addr: address })
810 }
811
812 fn auth_witness_provider(
813 &self,
814 address: CompleteAddress,
815 ) -> Box<dyn AuthorizationProvider> {
816 Box::new(MockAuthProvider { addr: address })
817 }
818 }
819
820 struct BadInitializerNameAccountContract;
821
822 #[async_trait]
823 impl AccountContract for BadInitializerNameAccountContract {
824 async fn contract_artifact(&self) -> Result<ContractArtifact, Error> {
825 MockAccountContract.contract_artifact().await
826 }
827
828 async fn initialization_function_and_args(
829 &self,
830 ) -> Result<Option<InitializationSpec>, Error> {
831 Ok(Some(InitializationSpec {
832 constructor_name: "missing".to_owned(),
833 constructor_args: vec![AbiValue::Field(Fr::from(42u64))],
834 }))
835 }
836
837 fn account(&self, address: CompleteAddress) -> Box<dyn Account> {
838 Box::new(MockAccount { addr: address })
839 }
840
841 fn auth_witness_provider(
842 &self,
843 address: CompleteAddress,
844 ) -> Box<dyn AuthorizationProvider> {
845 Box::new(MockAuthProvider { addr: address })
846 }
847 }
848
849 struct BadInitializerArgsAccountContract;
850
851 #[async_trait]
852 impl AccountContract for BadInitializerArgsAccountContract {
853 async fn contract_artifact(&self) -> Result<ContractArtifact, Error> {
854 MockAccountContract.contract_artifact().await
855 }
856
857 async fn initialization_function_and_args(
858 &self,
859 ) -> Result<Option<InitializationSpec>, Error> {
860 Ok(Some(InitializationSpec {
861 constructor_name: "constructor".to_owned(),
862 constructor_args: vec![],
863 }))
864 }
865
866 fn account(&self, address: CompleteAddress) -> Box<dyn Account> {
867 Box::new(MockAccount { addr: address })
868 }
869
870 fn auth_witness_provider(
871 &self,
872 address: CompleteAddress,
873 ) -> Box<dyn AuthorizationProvider> {
874 Box::new(MockAuthProvider { addr: address })
875 }
876 }
877
878 fn sample_chain_info() -> ChainInfo {
879 ChainInfo {
880 chain_id: Fr::from(31337u64),
881 version: Fr::from(1u64),
882 }
883 }
884
885 fn sample_complete_address() -> CompleteAddress {
886 CompleteAddress {
887 address: AztecAddress(Fr::from(99u64)),
888 public_keys: PublicKeys::default(),
889 partial_address: Fr::from(1u64),
890 }
891 }
892
893 #[test]
896 fn authorization_provider_is_object_safe() {
897 fn _assert(_: &dyn AuthorizationProvider) {}
898 }
899
900 #[test]
901 fn account_is_object_safe() {
902 fn _assert(_: &dyn Account) {}
903 }
904
905 #[test]
906 fn account_contract_is_object_safe() {
907 fn _assert(_: &dyn AccountContract) {}
908 }
909
910 #[test]
913 fn mock_account_is_send_sync() {
914 fn assert_send_sync<T: Send + Sync>() {}
915 assert_send_sync::<MockAccount>();
916 assert_send_sync::<MockAuthProvider>();
917 assert_send_sync::<MockAccountContract>();
918 }
919
920 #[test]
921 fn account_with_secret_key_is_send() {
922 fn assert_send<T: Send>() {}
923 assert_send::<AccountWithSecretKey>();
924 }
925
926 #[test]
929 fn entrypoint_options_default() {
930 let opts = EntrypointOptions::default();
931 assert!(opts.fee_payer.is_none());
932 assert!(opts.gas_settings.is_none());
933 }
934
935 #[test]
936 fn entrypoint_options_roundtrip() {
937 let opts = EntrypointOptions {
938 fee_payer: Some(AztecAddress(Fr::from(1u64))),
939 gas_settings: Some(GasSettings {
940 gas_limits: Some(Gas {
941 da_gas: 100,
942 l2_gas: 200,
943 }),
944 ..GasSettings::default()
945 }),
946 fee_payment_method: None,
947 };
948 let json = serde_json::to_string(&opts).expect("serialize EntrypointOptions");
949 let decoded: EntrypointOptions =
950 serde_json::from_str(&json).expect("deserialize EntrypointOptions");
951 assert_eq!(decoded, opts);
952 }
953
954 #[test]
955 fn tx_execution_request_roundtrip() {
956 let req = TxExecutionRequest {
957 origin: AztecAddress(Fr::from(1u64)),
958 function_selector: FunctionSelector::from_hex("0xaabbccdd").expect("selector"),
959 first_call_args_hash: Fr::from(3u64),
960 tx_context: TxContext {
961 chain_id: Fr::from(31337u64),
962 version: Fr::from(1u64),
963 gas_settings: GasSettings::default(),
964 },
965 args_of_calls: vec![HashedValues::from_args(vec![Fr::from(7u64)])],
966 auth_witnesses: vec![AuthWitness {
967 fields: vec![Fr::from(42u64)],
968 ..Default::default()
969 }],
970 capsules: vec![],
971 salt: Fr::from(9u64),
972 fee_payer: None,
973 };
974 let json = serde_json::to_string(&req).expect("serialize TxExecutionRequest");
975 let decoded: TxExecutionRequest =
976 serde_json::from_str(&json).expect("deserialize TxExecutionRequest");
977 assert_eq!(decoded, req);
978 }
979
980 #[test]
981 fn initialization_spec_fields() {
982 let spec = InitializationSpec {
983 constructor_name: "constructor".to_owned(),
984 constructor_args: vec![AbiValue::Field(Fr::from(1u64)), AbiValue::Boolean(true)],
985 };
986 assert_eq!(spec.constructor_name, "constructor");
987 assert_eq!(spec.constructor_args.len(), 2);
988 }
989
990 #[tokio::test]
993 async fn mock_auth_provider_creates_witness() {
994 let provider = MockAuthProvider {
995 addr: sample_complete_address(),
996 };
997 let chain_info = sample_chain_info();
998 let wit = provider
999 .create_auth_wit(
1000 MessageHashOrIntent::Hash {
1001 hash: Fr::from(1u64),
1002 },
1003 &chain_info,
1004 )
1005 .await
1006 .expect("create auth wit");
1007 assert_eq!(wit.fields.len(), 1);
1008 assert_eq!(wit.fields[0], Fr::from(99u64));
1009 }
1010
1011 #[tokio::test]
1014 async fn mock_account_address() {
1015 let account = MockAccount {
1016 addr: sample_complete_address(),
1017 };
1018 assert_eq!(account.address(), AztecAddress(Fr::from(99u64)));
1019 assert_eq!(
1020 account.complete_address().address,
1021 AztecAddress(Fr::from(99u64))
1022 );
1023 }
1024
1025 #[tokio::test]
1026 async fn mock_account_creates_execution_request() {
1027 let account = MockAccount {
1028 addr: sample_complete_address(),
1029 };
1030 let chain_info = sample_chain_info();
1031
1032 let payload = ExecutionPayload {
1033 calls: vec![],
1034 auth_witnesses: vec![AuthWitness {
1035 fields: vec![Fr::from(1u64)],
1036 ..Default::default()
1037 }],
1038 capsules: vec![],
1039 extra_hashed_args: vec![],
1040 fee_payer: None,
1041 };
1042
1043 let gas_settings = GasSettings {
1044 gas_limits: Some(Gas {
1045 da_gas: 100,
1046 l2_gas: 200,
1047 }),
1048 ..GasSettings::default()
1049 };
1050
1051 let options = EntrypointOptions {
1052 fee_payer: Some(AztecAddress(Fr::from(5u64))),
1053 gas_settings: None,
1054 fee_payment_method: None,
1055 };
1056
1057 let req = account
1058 .create_tx_execution_request(payload, gas_settings.clone(), &chain_info, options)
1059 .await
1060 .expect("create tx execution request");
1061
1062 assert_eq!(req.origin, AztecAddress(Fr::from(99u64)));
1063 assert_eq!(req.auth_witnesses.len(), 1);
1064 assert_eq!(req.tx_context.gas_settings, gas_settings);
1065 assert_eq!(req.tx_context.chain_id, chain_info.chain_id);
1066 }
1067
1068 #[tokio::test]
1069 async fn mock_account_wraps_payload() {
1070 let account = MockAccount {
1071 addr: sample_complete_address(),
1072 };
1073
1074 let payload = ExecutionPayload {
1075 calls: vec![],
1076 auth_witnesses: vec![],
1077 capsules: vec![],
1078 extra_hashed_args: vec![],
1079 fee_payer: Some(AztecAddress(Fr::from(3u64))),
1080 };
1081
1082 let options = EntrypointOptions::default();
1083
1084 let wrapped = account
1085 .wrap_execution_payload(payload, options)
1086 .await
1087 .expect("wrap execution payload");
1088
1089 assert_eq!(wrapped.fee_payer, Some(AztecAddress(Fr::from(3u64))));
1090 }
1091
1092 #[tokio::test]
1093 async fn mock_account_wrap_overrides_fee_payer() {
1094 let account = MockAccount {
1095 addr: sample_complete_address(),
1096 };
1097
1098 let payload = ExecutionPayload {
1099 calls: vec![],
1100 auth_witnesses: vec![],
1101 capsules: vec![],
1102 extra_hashed_args: vec![],
1103 fee_payer: Some(AztecAddress(Fr::from(3u64))),
1104 };
1105
1106 let options = EntrypointOptions {
1107 fee_payer: Some(AztecAddress(Fr::from(7u64))),
1108 gas_settings: None,
1109 fee_payment_method: None,
1110 };
1111
1112 let wrapped = account
1113 .wrap_execution_payload(payload, options)
1114 .await
1115 .expect("wrap execution payload");
1116
1117 assert_eq!(wrapped.fee_payer, Some(AztecAddress(Fr::from(7u64))));
1118 }
1119
1120 #[tokio::test]
1121 async fn account_with_secret_key_delegates_to_inner_account() {
1122 let account = AccountWithSecretKey {
1123 account: Box::new(MockAccount {
1124 addr: sample_complete_address(),
1125 }),
1126 secret_key: Fr::from(42u64),
1127 salt: Fr::from(7u64),
1128 };
1129
1130 let chain_info = sample_chain_info();
1131 let wit = account
1132 .create_auth_wit(
1133 MessageHashOrIntent::Hash {
1134 hash: Fr::from(1u64),
1135 },
1136 &chain_info,
1137 )
1138 .await
1139 .expect("create auth wit");
1140
1141 assert_eq!(account.address(), AztecAddress(Fr::from(99u64)));
1142 assert_eq!(account.complete_address().partial_address, Fr::from(1u64));
1143 assert_eq!(account.secret_key, Fr::from(42u64));
1144 assert_eq!(account.salt, Fr::from(7u64));
1145 assert_eq!(wit.fields, vec![Fr::from(99u64)]);
1146 }
1147
1148 #[tokio::test]
1151 async fn mock_account_contract_artifact() {
1152 let contract = MockAccountContract;
1153 let artifact = contract
1154 .contract_artifact()
1155 .await
1156 .expect("get contract artifact");
1157 assert_eq!(artifact.name, "MockAccount");
1158 assert_eq!(artifact.functions.len(), 1);
1159 assert!(artifact.functions[0].is_initializer);
1160 }
1161
1162 #[tokio::test]
1163 async fn mock_account_contract_init_spec() {
1164 let contract = MockAccountContract;
1165 let spec = contract
1166 .initialization_function_and_args()
1167 .await
1168 .expect("get init spec")
1169 .expect("should have init spec");
1170 assert_eq!(spec.constructor_name, "constructor");
1171 assert_eq!(spec.constructor_args.len(), 1);
1172 }
1173
1174 #[tokio::test]
1175 async fn mock_account_contract_produces_account() {
1176 let contract = MockAccountContract;
1177 let addr = sample_complete_address();
1178 let account = contract.account(addr);
1179 assert_eq!(account.address(), AztecAddress(Fr::from(99u64)));
1180 }
1181
1182 #[tokio::test]
1183 async fn mock_account_contract_produces_auth_provider() {
1184 let contract = MockAccountContract;
1185 let addr = sample_complete_address();
1186 let provider = contract.auth_witness_provider(addr);
1187 let chain_info = sample_chain_info();
1188
1189 let wit = provider
1190 .create_auth_wit(
1191 MessageHashOrIntent::Hash {
1192 hash: Fr::from(1u64),
1193 },
1194 &chain_info,
1195 )
1196 .await
1197 .expect("create auth wit");
1198 assert_eq!(wit.fields.len(), 1);
1199 }
1200
1201 #[tokio::test]
1204 async fn account_manager_create() {
1205 let wallet = MockWallet::new(sample_chain_info());
1206 let manager = AccountManager::create(
1207 wallet,
1208 Fr::from(42u64),
1209 Box::new(MockAccountContract),
1210 Some(Fr::from(7u64)),
1211 )
1212 .await
1213 .expect("create account manager");
1214
1215 assert_eq!(manager.salt(), Fr::from(7u64));
1216 assert_eq!(manager.secret_key(), Fr::from(42u64));
1217 }
1218
1219 #[tokio::test]
1220 async fn account_manager_default_salt() {
1221 let wallet = MockWallet::new(sample_chain_info());
1222 let manager = AccountManager::create(
1223 wallet,
1224 Fr::from(1u64),
1225 Box::new(MockAccountContract),
1226 None::<Fr>,
1227 )
1228 .await
1229 .expect("create account manager");
1230
1231 assert_ne!(manager.salt(), Fr::zero());
1232 }
1233
1234 #[tokio::test]
1235 async fn account_manager_rejects_missing_initializer_function() {
1236 let wallet = MockWallet::new(sample_chain_info());
1237 let result = AccountManager::create(
1238 wallet,
1239 Fr::from(1u64),
1240 Box::new(BadInitializerNameAccountContract),
1241 None::<Fr>,
1242 )
1243 .await;
1244 assert!(result.is_err());
1245 let err = result.err().expect("initializer lookup should fail");
1246
1247 assert!(err.to_string().contains("not found"));
1248 }
1249
1250 #[tokio::test]
1251 async fn account_manager_rejects_initializer_argument_mismatch() {
1252 let wallet = MockWallet::new(sample_chain_info());
1253 let result = AccountManager::create(
1254 wallet,
1255 Fr::from(1u64),
1256 Box::new(BadInitializerArgsAccountContract),
1257 None::<Fr>,
1258 )
1259 .await;
1260 assert!(result.is_err());
1261 let err = result
1262 .err()
1263 .expect("initializer arg validation should fail");
1264
1265 assert!(err.to_string().contains("expects 1 argument(s), got 0"));
1266 }
1267
1268 #[tokio::test]
1269 async fn account_manager_address_accessors() {
1270 let wallet = MockWallet::new(sample_chain_info());
1271 let manager = AccountManager::create(
1272 wallet,
1273 Fr::from(1u64),
1274 Box::new(MockAccountContract),
1275 None::<Fr>,
1276 )
1277 .await
1278 .expect("create account manager");
1279
1280 assert_ne!(manager.address(), AztecAddress(Fr::zero()));
1282
1283 let complete = manager
1285 .complete_address()
1286 .await
1287 .expect("complete address derivation");
1288 assert!(!complete.public_keys.is_empty());
1289 assert!(!complete.address.0.is_zero());
1290
1291 let instance = manager.instance();
1292 assert_eq!(instance.inner.version, 1);
1293 }
1294
1295 #[tokio::test]
1296 async fn account_manager_account() {
1297 let wallet = MockWallet::new(sample_chain_info());
1298 let manager = AccountManager::create(
1299 wallet,
1300 Fr::from(42u64),
1301 Box::new(MockAccountContract),
1302 None::<Fr>,
1303 )
1304 .await
1305 .expect("create account manager");
1306
1307 let account = manager.account().await.expect("account construction");
1309 assert_eq!(account.secret_key, Fr::from(42u64));
1310 }
1311
1312 #[tokio::test]
1313 async fn account_manager_account_creates_auth_wit() {
1314 let wallet = MockWallet::new(sample_chain_info());
1315 let manager = AccountManager::create(
1316 wallet,
1317 Fr::from(42u64),
1318 Box::new(MockAccountContract),
1319 None::<Fr>,
1320 )
1321 .await
1322 .expect("create account manager");
1323
1324 let account = manager.account().await.expect("account construction");
1325 let chain_info = sample_chain_info();
1327 let wit = account
1328 .create_auth_wit(
1329 MessageHashOrIntent::Hash {
1330 hash: Fr::from(999u64),
1331 },
1332 &chain_info,
1333 )
1334 .await
1335 .expect("create auth wit");
1336 assert!(!wit.fields.is_empty());
1337 }
1338
1339 #[tokio::test]
1342 async fn deploy_method_request_builds_payload() {
1343 let wallet = MockWallet::new(sample_chain_info());
1344 let manager = AccountManager::create(
1345 wallet,
1346 Fr::from(1u64),
1347 Box::new(MockAccountContract),
1348 None::<Fr>,
1349 )
1350 .await
1351 .expect("create account manager");
1352
1353 let deploy = manager.deploy_method().await.expect("build deploy method");
1354 let opts = DeployAccountOptions {
1355 skip_registration: true,
1356 ..Default::default()
1357 };
1358 let payload = deploy.request(&opts).await.expect("request should succeed");
1359 assert!(!payload.calls.is_empty());
1361 }
1362
1363 #[tokio::test]
1364 async fn deploy_method_requires_initializer() {
1365 let wallet = MockWallet::new(sample_chain_info());
1366 let manager = AccountManager::create(
1367 wallet,
1368 Fr::from(1u64),
1369 Box::new(NoInitializerAccountContract),
1370 None::<Fr>,
1371 )
1372 .await
1373 .expect("create account manager");
1374
1375 assert!(!manager.has_initializer());
1376 let deploy = manager
1377 .deploy_method()
1378 .await
1379 .expect("deploy method should still build");
1380 let payload = deploy
1381 .request(&DeployAccountOptions {
1382 skip_registration: true,
1383 ..Default::default()
1384 })
1385 .await
1386 .expect("request should succeed without initializer");
1387 assert!(payload.calls.is_empty());
1388 }
1389
1390 #[tokio::test]
1391 async fn deploy_method_instance() {
1392 let wallet = MockWallet::new(sample_chain_info());
1393 let manager = AccountManager::create(
1394 wallet,
1395 Fr::from(1u64),
1396 Box::new(MockAccountContract),
1397 Some(Fr::from(99u64)),
1398 )
1399 .await
1400 .expect("create account manager");
1401
1402 let deploy = manager.deploy_method().await.expect("build deploy method");
1403 assert_eq!(deploy.instance().inner.salt, Fr::from(99u64));
1404 assert_ne!(deploy.instance().address, AztecAddress(Fr::zero()));
1406 }
1407
1408 #[tokio::test]
1409 async fn deploy_method_instance_matches_manager_address() {
1410 let wallet = MockWallet::new(sample_chain_info());
1411 let manager = AccountManager::create(
1412 wallet,
1413 Fr::from(1u64),
1414 Box::new(MockAccountContract),
1415 Some(Fr::from(99u64)),
1416 )
1417 .await
1418 .expect("create account manager");
1419
1420 let deploy = manager.deploy_method().await.expect("build deploy method");
1421 assert_eq!(deploy.instance().address, manager.instance().address);
1423 }
1424
1425 #[tokio::test]
1426 async fn deploy_method_skip_flags_pass_through() {
1427 let wallet = MockWallet::new(sample_chain_info());
1428 let manager = AccountManager::create(
1429 wallet,
1430 Fr::from(1u64),
1431 Box::new(MockAccountContract),
1432 None::<Fr>,
1433 )
1434 .await
1435 .expect("create account manager");
1436
1437 let deploy = manager.deploy_method().await.expect("build deploy method");
1438 let opts = DeployAccountOptions {
1439 skip_registration: true,
1440 skip_class_publication: true,
1441 skip_instance_publication: true,
1442 skip_initialization: true,
1443 ..Default::default()
1444 };
1445 let payload = deploy.request(&opts).await.expect("request");
1446 let _ = payload;
1450 }
1451}