1use serde::{Deserialize, Serialize};
2
3use crate::abi::{AbiValue, ContractArtifact, FunctionType};
4use crate::contract::{merge_fee_payload, ContractFunctionInteraction};
5use crate::error::Error;
6use crate::tx::{Capsule, ExecutionPayload, FunctionCall};
7use crate::types::{AztecAddress, ContractInstance, ContractInstanceWithAddress, Fr, PublicKeys};
8use crate::wallet::{
9 ProfileOptions, SendOptions, SendResult, SimulateOptions, TxProfileResult, TxSimulationResult,
10 Wallet,
11};
12
13use aztec_core::abi::{buffer_as_fields, FunctionSelector};
14use aztec_core::constants::{
15 contract_class_registry_bytecode_capsule_slot, protocol_contract_address,
16 MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS, MAX_PROCESSABLE_L2_GAS,
17};
18use aztec_core::fee::Gas;
19use aztec_core::hash::{
20 compute_artifact_hash, compute_contract_address_from_instance, compute_contract_class_id,
21 compute_initialization_hash, compute_private_functions_root_from_artifact,
22 compute_public_bytecode_commitment,
23};
24
25#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
31#[serde(rename_all = "camelCase")]
32#[allow(clippy::struct_excessive_bools)]
33pub struct DeployOptions {
34 pub contract_address_salt: Option<Fr>,
36 #[serde(default)]
38 pub skip_class_publication: bool,
39 #[serde(default)]
41 pub skip_instance_publication: bool,
42 #[serde(default)]
44 pub skip_initialization: bool,
45 #[serde(default)]
47 pub skip_registration: bool,
48 #[serde(default)]
50 pub universal_deploy: bool,
51 #[serde(default)]
53 pub from: Option<AztecAddress>,
54}
55
56#[derive(Clone, Debug, PartialEq, Eq)]
62pub struct SuggestedGasLimits {
63 pub gas_limits: Gas,
65 pub teardown_gas_limits: Gas,
67}
68
69pub fn get_gas_limits(
74 simulation_result: &TxSimulationResult,
75 pad: Option<f64>,
76) -> SuggestedGasLimits {
77 let pad_factor = 1.0 + pad.unwrap_or(0.1);
78
79 let gas_used = simulation_result
80 .gas_used
81 .as_ref()
82 .cloned()
83 .unwrap_or_default();
84
85 let padded_da = (gas_used.da_gas as f64 * pad_factor).ceil() as u64;
86 let padded_l2 = (gas_used.l2_gas as f64 * pad_factor).ceil() as u64;
87
88 SuggestedGasLimits {
89 gas_limits: Gas {
90 da_gas: padded_da,
91 l2_gas: padded_l2.min(MAX_PROCESSABLE_L2_GAS),
92 },
93 teardown_gas_limits: Gas::default(),
94 }
95}
96
97#[allow(clippy::unused_async)]
103pub async fn publish_contract_class<'a, W: Wallet>(
104 wallet: &'a W,
105 artifact: &ContractArtifact,
106) -> Result<ContractFunctionInteraction<'a, W>, Error> {
107 let artifact_hash = compute_artifact_hash(artifact);
109 let private_functions_root = compute_private_functions_root_from_artifact(artifact)?;
110
111 let packed_bytecode = extract_packed_bytecode(artifact);
113 let public_bytecode_commitment = compute_public_bytecode_commitment(&packed_bytecode);
114
115 let bytecode_fields = if packed_bytecode.is_empty() {
117 vec![]
118 } else {
119 buffer_as_fields(&packed_bytecode, MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS)?
120 };
121
122 let registerer_address = protocol_contract_address::contract_class_registry();
124
125 let call = FunctionCall {
126 to: registerer_address,
127 selector: FunctionSelector::from_signature("publish(Field,Field,Field)"),
128 args: vec![
129 AbiValue::Field(artifact_hash),
130 AbiValue::Field(private_functions_root),
131 AbiValue::Field(public_bytecode_commitment),
132 ],
133 function_type: FunctionType::Private,
134 is_static: false,
135 hide_msg_sender: false,
136 };
137
138 let capsules = if bytecode_fields.is_empty() {
140 vec![]
141 } else {
142 vec![Capsule {
143 contract_address: registerer_address,
144 storage_slot: contract_class_registry_bytecode_capsule_slot(),
145 data: bytecode_fields,
146 }]
147 };
148
149 Ok(ContractFunctionInteraction::new_with_capsules(
151 wallet, call, capsules,
152 ))
153}
154
155fn extract_packed_bytecode(artifact: &ContractArtifact) -> Vec<u8> {
162 artifact
165 .functions
166 .iter()
167 .find(|f| f.function_type == FunctionType::Public && f.name == "public_dispatch")
168 .and_then(|f| f.bytecode.as_ref())
169 .map(|bc| decode_artifact_bytes(bc))
170 .unwrap_or_default()
171}
172
173pub fn publish_instance<'a, W: Wallet>(
179 wallet: &'a W,
180 instance: &ContractInstanceWithAddress,
181) -> Result<ContractFunctionInteraction<'a, W>, Error> {
182 let is_universal_deploy = instance.inner.deployer == AztecAddress(Fr::zero());
183
184 let deployer_address = protocol_contract_address::contract_instance_registry();
185
186 let call = FunctionCall {
187 to: deployer_address,
188 selector: FunctionSelector::from_signature(
189 "publish_for_public_execution(Field,(Field),Field,(((Field,Field,bool)),((Field,Field,bool)),((Field,Field,bool)),((Field,Field,bool))),bool)"
190 ),
191 args: vec![
192 AbiValue::Field(instance.inner.salt),
193 AbiValue::Tuple(vec![AbiValue::Field(
194 instance.inner.current_contract_class_id,
195 )]),
196 AbiValue::Field(instance.inner.initialization_hash),
197 public_keys_to_abi_value(&instance.inner.public_keys),
198 AbiValue::Boolean(is_universal_deploy),
199 ],
200 function_type: FunctionType::Private,
201 is_static: false,
202 hide_msg_sender: false,
203 };
204
205 Ok(ContractFunctionInteraction::new(wallet, call))
206}
207
208fn point_to_abi_value(point: &aztec_core::types::Point) -> AbiValue {
209 AbiValue::Tuple(vec![
210 AbiValue::Field(point.x),
211 AbiValue::Field(point.y),
212 AbiValue::Boolean(point.is_infinite),
213 ])
214}
215
216fn public_keys_to_abi_value(public_keys: &PublicKeys) -> AbiValue {
217 AbiValue::Tuple(vec![
218 point_to_abi_value(&public_keys.master_nullifier_public_key),
219 point_to_abi_value(&public_keys.master_incoming_viewing_public_key),
220 point_to_abi_value(&public_keys.master_outgoing_viewing_public_key),
221 point_to_abi_value(&public_keys.master_tagging_public_key),
222 ])
223}
224
225fn decode_artifact_bytes(encoded: &str) -> Vec<u8> {
226 if let Some(hex) = encoded.strip_prefix("0x") {
227 return hex::decode(hex).unwrap_or_else(|_| encoded.as_bytes().to_vec());
228 }
229
230 use base64::Engine;
231 base64::engine::general_purpose::STANDARD
232 .decode(encoded)
233 .unwrap_or_else(|_| encoded.as_bytes().to_vec())
234}
235
236pub struct ContractInstantiationParams<'a> {
242 pub constructor_name: Option<&'a str>,
244 pub constructor_args: Vec<AbiValue>,
246 pub salt: Fr,
248 pub public_keys: PublicKeys,
250 pub deployer: AztecAddress,
252}
253
254pub fn get_contract_instance_from_instantiation_params(
260 artifact: &ContractArtifact,
261 params: ContractInstantiationParams<'_>,
262) -> Result<ContractInstanceWithAddress, Error> {
263 let artifact_hash = compute_artifact_hash(artifact);
265 let private_functions_root = compute_private_functions_root_from_artifact(artifact)?;
266 let packed_bytecode = extract_packed_bytecode(artifact);
267 let public_bytecode_commitment = compute_public_bytecode_commitment(&packed_bytecode);
268 let class_id = compute_contract_class_id(
269 artifact_hash,
270 private_functions_root,
271 public_bytecode_commitment,
272 );
273
274 let init_fn = params
276 .constructor_name
277 .map(|name| artifact.find_function(name))
278 .transpose()?;
279
280 let init_hash = compute_initialization_hash(init_fn, ¶ms.constructor_args)?;
281
282 let instance = ContractInstance {
283 version: 1,
284 salt: params.salt,
285 deployer: params.deployer,
286 current_contract_class_id: class_id,
287 original_contract_class_id: class_id,
288 initialization_hash: init_hash,
289 public_keys: params.public_keys,
290 };
291
292 let address = compute_contract_address_from_instance(&instance)?;
293
294 Ok(ContractInstanceWithAddress {
295 address,
296 inner: instance,
297 })
298}
299
300#[derive(Clone, Debug)]
306pub struct DeployResult {
307 pub send_result: SendResult,
309 pub instance: ContractInstanceWithAddress,
311}
312
313pub struct ContractDeployer<'a, W> {
323 artifact: ContractArtifact,
324 wallet: &'a W,
325 public_keys: PublicKeys,
326 constructor_name: Option<String>,
327}
328
329impl<W> std::fmt::Debug for ContractDeployer<'_, W> {
330 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
331 f.debug_struct("ContractDeployer")
332 .field("artifact", &self.artifact.name)
333 .field("constructor_name", &self.constructor_name)
334 .finish_non_exhaustive()
335 }
336}
337
338impl<'a, W: Wallet> ContractDeployer<'a, W> {
339 pub fn new(artifact: ContractArtifact, wallet: &'a W) -> Self {
341 Self {
342 artifact,
343 wallet,
344 public_keys: PublicKeys::default(),
345 constructor_name: None,
346 }
347 }
348
349 #[must_use]
351 pub const fn with_public_keys(mut self, keys: PublicKeys) -> Self {
352 self.public_keys = keys;
353 self
354 }
355
356 #[must_use]
358 pub fn with_constructor_name(mut self, name: impl Into<String>) -> Self {
359 self.constructor_name = Some(name.into());
360 self
361 }
362
363 pub fn deploy(self, args: Vec<AbiValue>) -> Result<DeployMethod<'a, W>, Error> {
368 let constructor_name = if let Some(name) = self.constructor_name {
369 let func = self.artifact.find_function(&name)?;
370 if !func.is_initializer {
371 return Err(Error::Abi(format!(
372 "function '{name}' in artifact '{}' is not an initializer",
373 self.artifact.name,
374 )));
375 }
376
377 let expected = func.parameters.len();
378 let got = args.len();
379 if got != expected {
380 return Err(Error::Abi(format!(
381 "constructor '{name}' expects {expected} argument(s), got {got}",
382 )));
383 }
384
385 Some(name)
386 } else if let Some(func) = self
387 .artifact
388 .functions
389 .iter()
390 .find(|func| func.is_initializer)
391 {
392 let expected = func.parameters.len();
393 let got = args.len();
394 if got != expected {
395 return Err(Error::Abi(format!(
396 "constructor '{}' expects {expected} argument(s), got {got}",
397 func.name
398 )));
399 }
400
401 Some(func.name.clone())
402 } else if args.is_empty() {
403 None
404 } else {
405 return Err(Error::Abi(format!(
406 "artifact '{}' has no initializer but got {} constructor argument(s)",
407 self.artifact.name,
408 args.len()
409 )));
410 };
411
412 Ok(DeployMethod {
413 wallet: self.wallet,
414 artifact: self.artifact,
415 args,
416 public_keys: self.public_keys,
417 constructor_name,
418 default_salt: Fr::random(),
419 })
420 }
421}
422
423pub struct DeployMethod<'a, W> {
432 wallet: &'a W,
433 artifact: ContractArtifact,
434 args: Vec<AbiValue>,
435 public_keys: PublicKeys,
436 constructor_name: Option<String>,
437 default_salt: Fr,
438}
439
440impl<W> std::fmt::Debug for DeployMethod<'_, W> {
441 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
442 f.debug_struct("DeployMethod")
443 .field("artifact", &self.artifact.name)
444 .field("constructor_name", &self.constructor_name)
445 .field("args_count", &self.args.len())
446 .finish_non_exhaustive()
447 }
448}
449
450impl<W: Wallet> DeployMethod<'_, W> {
451 fn with_effective_from(opts: &DeployOptions, from: Option<AztecAddress>) -> DeployOptions {
452 if opts.universal_deploy || opts.from.is_some() {
453 opts.clone()
454 } else {
455 let mut effective = opts.clone();
456 effective.from = from;
457 effective
458 }
459 }
460
461 pub async fn request(&self, opts: &DeployOptions) -> Result<ExecutionPayload, Error> {
463 let instance = self.get_instance(opts)?;
464 let mut payloads: Vec<ExecutionPayload> = Vec::new();
465
466 if !opts.skip_registration {
468 self.wallet
469 .register_contract(instance.clone(), Some(self.artifact.clone()), None)
470 .await?;
471 }
472
473 if !opts.skip_class_publication {
475 let class_id = instance.inner.current_contract_class_id;
476 let already_registered = self
477 .wallet
478 .get_contract_class_metadata(class_id)
479 .await
480 .map(|m| m.is_contract_class_publicly_registered)
481 .unwrap_or(false);
482
483 if !already_registered {
484 let class_interaction = publish_contract_class(self.wallet, &self.artifact).await?;
485 payloads.push(class_interaction.request()?);
486 }
487 }
488
489 if !opts.skip_instance_publication {
491 let instance_interaction = publish_instance(self.wallet, &instance)?;
492 payloads.push(instance_interaction.request()?);
493 }
494
495 if !opts.skip_initialization {
497 if let Some(ref constructor_name) = self.constructor_name {
498 let func = self.artifact.find_function(constructor_name)?;
499 let selector = func.selector.unwrap_or_else(|| {
500 FunctionSelector::from_name_and_parameters(&func.name, &func.parameters)
501 });
502
503 let encoded_fields = aztec_core::abi::encode_arguments(func, &self.args)?;
504
505 let call = FunctionCall {
506 to: instance.address,
507 selector,
508 args: encoded_fields.into_iter().map(AbiValue::Field).collect(),
509 function_type: func.function_type.clone(),
510 is_static: false,
511 hide_msg_sender: false,
512 };
513
514 payloads.push(ExecutionPayload {
515 calls: vec![call],
516 auth_witnesses: vec![],
517 capsules: vec![],
518 extra_hashed_args: vec![],
519 fee_payer: None,
520 });
521 }
522 }
523
524 ExecutionPayload::merge(payloads)
526 }
527
528 pub fn get_instance(&self, opts: &DeployOptions) -> Result<ContractInstanceWithAddress, Error> {
530 let salt = opts.contract_address_salt.unwrap_or(self.default_salt);
531 let deployer = if opts.universal_deploy {
532 AztecAddress::zero()
533 } else {
534 opts.from.unwrap_or(AztecAddress::zero())
535 };
536
537 get_contract_instance_from_instantiation_params(
538 &self.artifact,
539 ContractInstantiationParams {
540 constructor_name: self.constructor_name.as_deref(),
541 constructor_args: self.args.clone(),
542 salt,
543 public_keys: self.public_keys.clone(),
544 deployer,
545 },
546 )
547 }
548
549 pub async fn simulate(
551 &self,
552 deploy_opts: &DeployOptions,
553 sim_opts: SimulateOptions,
554 ) -> Result<TxSimulationResult, Error> {
555 let effective_opts = Self::with_effective_from(deploy_opts, Some(sim_opts.from));
556 let payload = merge_fee_payload(
557 self.request(&effective_opts).await?,
558 &sim_opts.fee_execution_payload,
559 )?;
560 self.wallet.simulate_tx(payload, sim_opts).await
561 }
562
563 pub async fn profile(
565 &self,
566 deploy_opts: &DeployOptions,
567 profile_opts: ProfileOptions,
568 ) -> Result<TxProfileResult, Error> {
569 let effective_opts = Self::with_effective_from(deploy_opts, Some(profile_opts.from));
570 let payload = merge_fee_payload(
571 self.request(&effective_opts).await?,
572 &profile_opts.fee_execution_payload,
573 )?;
574 self.wallet.profile_tx(payload, profile_opts).await
575 }
576
577 pub async fn send(
579 &self,
580 deploy_opts: &DeployOptions,
581 send_opts: SendOptions,
582 ) -> Result<DeployResult, Error> {
583 let effective_opts = Self::with_effective_from(deploy_opts, Some(send_opts.from));
584 let instance = self.get_instance(&effective_opts)?;
585 let payload = merge_fee_payload(
586 self.request(&effective_opts).await?,
587 &send_opts.fee_execution_payload,
588 )?;
589 let send_result = self.wallet.send_tx(payload, send_opts).await?;
590 if !effective_opts.skip_instance_publication {
596 self.wallet.wait_for_contract(instance.address).await?;
597 }
598 Ok(DeployResult {
599 send_result,
600 instance,
601 })
602 }
603}
604
605#[cfg(test)]
610#[allow(clippy::unwrap_used, clippy::expect_used)]
611mod tests {
612 use super::*;
613 use crate::abi::AbiValue;
614 use crate::fee::Gas;
615 use crate::types::Fr;
616 use crate::wallet::{ChainInfo, MockWallet, TxSimulationResult};
617
618 const DEPLOY_ARTIFACT: &str = r#"
619 {
620 "name": "TokenContract",
621 "functions": [
622 {
623 "name": "constructor",
624 "function_type": "private",
625 "is_initializer": true,
626 "is_static": false,
627 "parameters": [
628 { "name": "admin", "type": { "kind": "field" } }
629 ],
630 "return_types": [],
631 "selector": "0xe5fb6c81"
632 },
633 {
634 "name": "transfer",
635 "function_type": "private",
636 "is_initializer": false,
637 "is_static": false,
638 "parameters": [
639 { "name": "from", "type": { "kind": "field" } },
640 { "name": "to", "type": { "kind": "field" } }
641 ],
642 "return_types": [],
643 "selector": "0xd6f42325"
644 }
645 ]
646 }
647 "#;
648
649 const NO_INITIALIZER_ARTIFACT: &str = r#"
650 {
651 "name": "NoInitContract",
652 "functions": [
653 {
654 "name": "do_stuff",
655 "function_type": "public",
656 "is_initializer": false,
657 "is_static": false,
658 "parameters": [],
659 "return_types": [],
660 "selector": "0xaabbccdd"
661 }
662 ]
663 }
664 "#;
665
666 fn sample_chain_info() -> ChainInfo {
667 ChainInfo {
668 chain_id: Fr::from(31337u64),
669 version: Fr::from(1u64),
670 }
671 }
672
673 fn load_artifact(json: &str) -> ContractArtifact {
674 ContractArtifact::from_json(json).expect("parse artifact")
675 }
676
677 #[test]
680 fn deploy_options_default() {
681 let opts = DeployOptions::default();
682 assert!(opts.contract_address_salt.is_none());
683 assert!(!opts.skip_class_publication);
684 assert!(!opts.skip_instance_publication);
685 assert!(!opts.skip_initialization);
686 assert!(!opts.skip_registration);
687 assert!(!opts.universal_deploy);
688 assert!(opts.from.is_none());
689 }
690
691 #[test]
692 fn deploy_options_roundtrip() {
693 let opts = DeployOptions {
694 contract_address_salt: Some(Fr::from(42u64)),
695 skip_class_publication: true,
696 skip_instance_publication: false,
697 skip_initialization: false,
698 skip_registration: true,
699 universal_deploy: false,
700 from: None,
701 };
702 let json = serde_json::to_string(&opts).expect("serialize");
703 let decoded: DeployOptions = serde_json::from_str(&json).expect("deserialize");
704 assert_eq!(decoded, opts);
705 }
706
707 #[test]
710 fn get_gas_limits_default_pad() {
711 let result = TxSimulationResult {
712 return_values: serde_json::Value::Null,
713 gas_used: Some(Gas {
714 da_gas: 1000,
715 l2_gas: 2000,
716 }),
717 };
718 let limits = get_gas_limits(&result, None);
719 assert_eq!(limits.gas_limits.da_gas, 1100);
720 assert_eq!(limits.gas_limits.l2_gas, 2200);
721 }
722
723 #[test]
724 fn get_gas_limits_custom_pad() {
725 let result = TxSimulationResult {
726 return_values: serde_json::Value::Null,
727 gas_used: Some(Gas {
728 da_gas: 1000,
729 l2_gas: 2000,
730 }),
731 };
732 let limits = get_gas_limits(&result, Some(0.5));
733 assert_eq!(limits.gas_limits.da_gas, 1500);
734 assert_eq!(limits.gas_limits.l2_gas, 3000);
735 }
736
737 #[test]
738 fn get_gas_limits_zero_gas() {
739 let result = TxSimulationResult {
740 return_values: serde_json::Value::Null,
741 gas_used: Some(Gas {
742 da_gas: 0,
743 l2_gas: 0,
744 }),
745 };
746 let limits = get_gas_limits(&result, None);
747 assert_eq!(limits.gas_limits.da_gas, 0);
748 assert_eq!(limits.gas_limits.l2_gas, 0);
749 }
750
751 #[tokio::test]
754 async fn publish_contract_class_targets_registerer() {
755 let wallet = MockWallet::new(sample_chain_info());
756 let artifact = load_artifact(DEPLOY_ARTIFACT);
757 let interaction = publish_contract_class(&wallet, &artifact)
758 .await
759 .expect("publish class");
760 let payload = interaction.request().expect("build payload");
761 assert_eq!(payload.calls.len(), 1);
762 assert_eq!(
763 payload.calls[0].to,
764 protocol_contract_address::contract_class_registerer()
765 );
766 }
767
768 #[test]
771 fn publish_instance_targets_deployer() {
772 let wallet = MockWallet::new(sample_chain_info());
773 let instance = ContractInstanceWithAddress {
774 address: AztecAddress(Fr::from(1u64)),
775 inner: ContractInstance {
776 version: 1,
777 salt: Fr::from(42u64),
778 deployer: AztecAddress(Fr::from(2u64)),
779 current_contract_class_id: Fr::from(100u64),
780 original_contract_class_id: Fr::from(100u64),
781 initialization_hash: Fr::from(0u64),
782 public_keys: PublicKeys::default(),
783 },
784 };
785 let interaction = publish_instance(&wallet, &instance).expect("publish instance");
786 let payload = interaction.request().expect("build payload");
787 assert_eq!(payload.calls.len(), 1);
788 assert_eq!(
789 payload.calls[0].to,
790 protocol_contract_address::contract_instance_deployer()
791 );
792 }
793
794 #[test]
795 fn publish_instance_universal_deploy_flag() {
796 let wallet = MockWallet::new(sample_chain_info());
797
798 let instance_non_universal = ContractInstanceWithAddress {
800 address: AztecAddress(Fr::from(1u64)),
801 inner: ContractInstance {
802 version: 1,
803 salt: Fr::from(42u64),
804 deployer: AztecAddress(Fr::from(2u64)),
805 current_contract_class_id: Fr::from(100u64),
806 original_contract_class_id: Fr::from(100u64),
807 initialization_hash: Fr::zero(),
808 public_keys: PublicKeys::default(),
809 },
810 };
811 let interaction = publish_instance(&wallet, &instance_non_universal).expect("non-uni");
812 let payload = interaction.request().expect("payload");
813 assert_eq!(payload.calls[0].args[4], AbiValue::Boolean(false));
815
816 let instance_universal = ContractInstanceWithAddress {
818 address: AztecAddress(Fr::from(1u64)),
819 inner: ContractInstance {
820 version: 1,
821 salt: Fr::from(42u64),
822 deployer: AztecAddress(Fr::zero()),
823 current_contract_class_id: Fr::from(100u64),
824 original_contract_class_id: Fr::from(100u64),
825 initialization_hash: Fr::zero(),
826 public_keys: PublicKeys::default(),
827 },
828 };
829 let interaction2 = publish_instance(&wallet, &instance_universal).expect("uni");
830 let payload2 = interaction2.request().expect("payload2");
831 assert_eq!(payload2.calls[0].args[4], AbiValue::Boolean(true));
832 }
833
834 #[test]
837 fn contract_deployer_creates_deploy_method() {
838 let wallet = MockWallet::new(sample_chain_info());
839 let artifact = load_artifact(DEPLOY_ARTIFACT);
840
841 let deployer = ContractDeployer::new(artifact, &wallet);
842 let deploy = deployer
843 .deploy(vec![AbiValue::Field(Fr::from(1u64))])
844 .expect("create deploy method");
845
846 let dbg = format!("{deploy:?}");
847 assert!(dbg.contains("TokenContract"));
848 assert!(dbg.contains("constructor"));
849 }
850
851 #[test]
852 fn contract_deployer_with_builder_methods() {
853 let wallet = MockWallet::new(sample_chain_info());
854 let artifact = load_artifact(DEPLOY_ARTIFACT);
855
856 let deployer = ContractDeployer::new(artifact, &wallet)
857 .with_public_keys(PublicKeys::default())
858 .with_constructor_name("constructor");
859
860 let deploy = deployer
861 .deploy(vec![AbiValue::Field(Fr::from(1u64))])
862 .expect("create deploy method");
863
864 let dbg = format!("{deploy:?}");
865 assert!(dbg.contains("constructor"));
866 }
867
868 #[test]
869 fn contract_deployer_rejects_missing_function() {
870 let wallet = MockWallet::new(sample_chain_info());
871 let artifact = load_artifact(DEPLOY_ARTIFACT);
872
873 let deployer =
874 ContractDeployer::new(artifact, &wallet).with_constructor_name("nonexistent");
875
876 let result = deployer.deploy(vec![]);
877 assert!(result.is_err());
878 assert!(result.unwrap_err().to_string().contains("not found"));
879 }
880
881 #[test]
882 fn contract_deployer_rejects_non_initializer() {
883 let wallet = MockWallet::new(sample_chain_info());
884 let artifact = load_artifact(DEPLOY_ARTIFACT);
885
886 let deployer = ContractDeployer::new(artifact, &wallet).with_constructor_name("transfer");
887
888 let result = deployer.deploy(vec![
889 AbiValue::Field(Fr::from(1u64)),
890 AbiValue::Field(Fr::from(2u64)),
891 ]);
892 assert!(result.is_err());
893 assert!(result
894 .unwrap_err()
895 .to_string()
896 .contains("not an initializer"));
897 }
898
899 #[test]
900 fn contract_deployer_rejects_arg_count_mismatch() {
901 let wallet = MockWallet::new(sample_chain_info());
902 let artifact = load_artifact(DEPLOY_ARTIFACT);
903
904 let deployer = ContractDeployer::new(artifact, &wallet);
905 let result = deployer.deploy(vec![]);
906 assert!(result.is_err());
907 assert!(result
908 .unwrap_err()
909 .to_string()
910 .contains("expects 1 argument(s), got 0"));
911 }
912
913 #[test]
914 fn contract_deployer_no_initializer_artifact() {
915 let wallet = MockWallet::new(sample_chain_info());
916 let artifact = load_artifact(NO_INITIALIZER_ARTIFACT);
917
918 let deploy = ContractDeployer::new(artifact, &wallet)
919 .deploy(vec![])
920 .expect("create deploy method without initializer");
921 let dbg = format!("{deploy:?}");
922 assert!(dbg.contains("NoInitContract"));
923 assert!(dbg.contains("None"));
924 }
925
926 #[test]
929 fn deploy_method_get_instance_computes_real_address() {
930 let wallet = MockWallet::new(sample_chain_info());
931 let artifact = load_artifact(DEPLOY_ARTIFACT);
932 let deployer = ContractDeployer::new(artifact, &wallet);
933
934 let deploy = deployer
935 .deploy(vec![AbiValue::Field(Fr::from(1u64))])
936 .expect("create deploy method");
937
938 let opts = DeployOptions {
939 contract_address_salt: Some(Fr::from(99u64)),
940 universal_deploy: true,
941 ..DeployOptions::default()
942 };
943 let instance = deploy.get_instance(&opts).expect("get instance");
944
945 assert_ne!(instance.address, AztecAddress(Fr::zero()));
947 assert_eq!(instance.inner.salt, Fr::from(99u64));
948 assert_eq!(instance.inner.version, 1);
949 assert_eq!(instance.inner.deployer, AztecAddress(Fr::zero()));
950 assert_ne!(instance.inner.current_contract_class_id, Fr::zero());
952 assert_eq!(
953 instance.inner.current_contract_class_id,
954 instance.inner.original_contract_class_id
955 );
956 }
957
958 #[test]
959 fn deploy_method_get_instance_uses_provided_salt() {
960 let wallet = MockWallet::new(sample_chain_info());
961 let artifact = load_artifact(DEPLOY_ARTIFACT);
962 let deployer = ContractDeployer::new(artifact, &wallet);
963
964 let deploy = deployer
965 .deploy(vec![AbiValue::Field(Fr::from(1u64))])
966 .expect("create deploy method");
967
968 let opts = DeployOptions {
969 contract_address_salt: Some(Fr::from(99u64)),
970 universal_deploy: true,
971 ..DeployOptions::default()
972 };
973 let instance = deploy.get_instance(&opts).expect("get instance");
974 assert_eq!(instance.inner.salt, Fr::from(99u64));
975 }
976
977 #[test]
978 fn deploy_method_get_instance_generates_random_salt() {
979 let wallet = MockWallet::new(sample_chain_info());
980 let artifact = load_artifact(DEPLOY_ARTIFACT);
981 let deployer = ContractDeployer::new(artifact, &wallet);
982
983 let deploy = deployer
984 .deploy(vec![AbiValue::Field(Fr::from(1u64))])
985 .expect("create deploy method");
986
987 let opts = DeployOptions {
988 universal_deploy: true,
989 ..DeployOptions::default()
990 };
991 let instance = deploy.get_instance(&opts).expect("get instance");
992 assert_ne!(instance.inner.salt, Fr::zero());
993 }
994
995 #[test]
996 fn deploy_method_get_instance_is_stable_for_same_options() {
997 let wallet = MockWallet::new(sample_chain_info());
998 let artifact = load_artifact(DEPLOY_ARTIFACT);
999 let deploy = ContractDeployer::new(artifact, &wallet)
1000 .deploy(vec![AbiValue::Field(Fr::from(1u64))])
1001 .expect("create deploy method");
1002
1003 let opts = DeployOptions {
1004 universal_deploy: true,
1005 ..DeployOptions::default()
1006 };
1007 let first = deploy.get_instance(&opts).expect("first");
1008 let second = deploy.get_instance(&opts).expect("second");
1009 assert_eq!(first.inner.salt, second.inner.salt);
1010 assert_eq!(first.address, second.address);
1011 }
1012
1013 #[test]
1014 fn deploy_method_get_instance_preserves_public_keys() {
1015 let wallet = MockWallet::new(sample_chain_info());
1016 let artifact = load_artifact(DEPLOY_ARTIFACT);
1017
1018 let keys = PublicKeys {
1019 master_nullifier_public_key: crate::types::Point {
1020 x: Fr::from(1u64),
1021 y: Fr::from(2u64),
1022 is_infinite: false,
1023 },
1024 ..PublicKeys::default()
1025 };
1026
1027 let deployer = ContractDeployer::new(artifact, &wallet).with_public_keys(keys.clone());
1028 let deploy = deployer
1029 .deploy(vec![AbiValue::Field(Fr::from(1u64))])
1030 .expect("create deploy method");
1031
1032 let opts = DeployOptions {
1033 universal_deploy: true,
1034 ..DeployOptions::default()
1035 };
1036 let instance = deploy.get_instance(&opts).expect("get instance");
1037 assert_eq!(instance.inner.public_keys, keys);
1038 }
1039
1040 #[test]
1041 fn deploy_method_get_instance_universal_deploy() {
1042 let wallet = MockWallet::new(sample_chain_info());
1043 let artifact = load_artifact(DEPLOY_ARTIFACT);
1044 let deploy = ContractDeployer::new(artifact, &wallet)
1045 .deploy(vec![AbiValue::Field(Fr::from(1u64))])
1046 .expect("create deploy method");
1047
1048 let opts = DeployOptions {
1049 contract_address_salt: Some(Fr::from(1u64)),
1050 universal_deploy: true,
1051 ..DeployOptions::default()
1052 };
1053 let instance = deploy.get_instance(&opts).expect("get instance");
1054 assert_eq!(instance.inner.deployer, AztecAddress(Fr::zero()));
1055 }
1056
1057 #[test]
1058 fn deploy_method_get_instance_with_explicit_from() {
1059 let wallet = MockWallet::new(sample_chain_info());
1060 let artifact = load_artifact(DEPLOY_ARTIFACT);
1061 let deploy = ContractDeployer::new(artifact, &wallet)
1062 .deploy(vec![AbiValue::Field(Fr::from(1u64))])
1063 .expect("create deploy method");
1064
1065 let deployer_addr = AztecAddress(Fr::from(42u64));
1066 let opts = DeployOptions {
1067 contract_address_salt: Some(Fr::from(1u64)),
1068 universal_deploy: false,
1069 from: Some(deployer_addr),
1070 ..DeployOptions::default()
1071 };
1072 let instance = deploy.get_instance(&opts).expect("get instance");
1073 assert_eq!(instance.inner.deployer, deployer_addr);
1074 }
1075
1076 #[tokio::test]
1079 async fn deploy_method_request_full_flow() {
1080 let wallet = MockWallet::new(sample_chain_info());
1081 let artifact = load_artifact(DEPLOY_ARTIFACT);
1082 let deployer = ContractDeployer::new(artifact, &wallet);
1083
1084 let deploy = deployer
1085 .deploy(vec![AbiValue::Field(Fr::from(42u64))])
1086 .expect("create deploy method");
1087
1088 let opts = DeployOptions {
1089 contract_address_salt: Some(Fr::from(1u64)),
1090 universal_deploy: true,
1091 skip_registration: true,
1092 ..DeployOptions::default()
1093 };
1094
1095 let payload = deploy.request(&opts).await.expect("request");
1096
1097 assert!(
1099 payload.calls.len() >= 2,
1100 "expected at least 2 calls, got {}",
1101 payload.calls.len()
1102 );
1103 }
1104
1105 #[tokio::test]
1106 async fn deploy_method_request_skips_class_publication() {
1107 let wallet = MockWallet::new(sample_chain_info());
1108 let artifact = load_artifact(DEPLOY_ARTIFACT);
1109 let deployer = ContractDeployer::new(artifact, &wallet);
1110
1111 let deploy = deployer
1112 .deploy(vec![AbiValue::Field(Fr::from(42u64))])
1113 .expect("create deploy method");
1114
1115 let opts = DeployOptions {
1116 contract_address_salt: Some(Fr::from(1u64)),
1117 universal_deploy: true,
1118 skip_registration: true,
1119 skip_class_publication: true,
1120 ..DeployOptions::default()
1121 };
1122
1123 let payload = deploy.request(&opts).await.expect("request");
1124
1125 let has_registerer_call = payload
1127 .calls
1128 .iter()
1129 .any(|c| c.to == protocol_contract_address::contract_class_registerer());
1130 assert!(
1131 !has_registerer_call,
1132 "should not contain class publication call"
1133 );
1134 }
1135
1136 #[tokio::test]
1137 async fn deploy_method_request_skips_initialization() {
1138 let wallet = MockWallet::new(sample_chain_info());
1139 let artifact = load_artifact(DEPLOY_ARTIFACT);
1140 let deployer = ContractDeployer::new(artifact, &wallet);
1141
1142 let deploy = deployer
1143 .deploy(vec![AbiValue::Field(Fr::from(42u64))])
1144 .expect("create deploy method");
1145
1146 let opts = DeployOptions {
1147 contract_address_salt: Some(Fr::from(1u64)),
1148 universal_deploy: true,
1149 skip_registration: true,
1150 skip_class_publication: true,
1151 skip_instance_publication: true,
1152 skip_initialization: true,
1153 ..DeployOptions::default()
1154 };
1155
1156 let payload = deploy.request(&opts).await.expect("request");
1157 assert!(payload.calls.is_empty());
1158 }
1159
1160 #[test]
1163 fn contract_deployer_debug() {
1164 let wallet = MockWallet::new(sample_chain_info());
1165 let artifact = load_artifact(DEPLOY_ARTIFACT);
1166 let deployer = ContractDeployer::new(artifact, &wallet);
1167 let dbg = format!("{deployer:?}");
1168 assert!(dbg.contains("TokenContract"));
1169 }
1170}