1use crate::abi::{AbiValue, ContractArtifact};
2use crate::deployment::{ContractDeployer, DeployMethod};
3use crate::error::Error;
4use crate::tx::{AuthWitness, Capsule, ExecutionPayload, FunctionCall, HashedValues};
5use crate::types::{AztecAddress, PublicKeys};
6use crate::wallet::{
7 ProfileOptions, SendOptions, SendResult, SimulateOptions, TxProfileResult, TxSimulationResult,
8 Wallet,
9};
10
11pub struct Contract<W> {
20 pub address: AztecAddress,
22 pub artifact: ContractArtifact,
24 wallet: W,
25}
26
27impl<W: Wallet> Contract<W> {
28 pub const fn at(address: AztecAddress, artifact: ContractArtifact, wallet: W) -> Self {
30 Self {
31 address,
32 artifact,
33 wallet,
34 }
35 }
36
37 pub fn deploy<'a>(
42 wallet: &'a W,
43 artifact: ContractArtifact,
44 args: Vec<AbiValue>,
45 constructor_name: Option<&str>,
46 ) -> Result<DeployMethod<'a, W>, Error> {
47 let mut deployer = ContractDeployer::new(artifact, wallet);
48 if let Some(name) = constructor_name {
49 deployer = deployer.with_constructor_name(name);
50 }
51 deployer.deploy(args)
52 }
53
54 pub fn deploy_with_public_keys<'a>(
56 public_keys: PublicKeys,
57 wallet: &'a W,
58 artifact: ContractArtifact,
59 args: Vec<AbiValue>,
60 constructor_name: Option<&str>,
61 ) -> Result<DeployMethod<'a, W>, Error> {
62 let mut deployer = ContractDeployer::new(artifact, wallet).with_public_keys(public_keys);
63 if let Some(name) = constructor_name {
64 deployer = deployer.with_constructor_name(name);
65 }
66 deployer.deploy(args)
67 }
68
69 pub fn with_wallet<W2: Wallet>(self, wallet: W2) -> Contract<W2> {
71 Contract {
72 address: self.address,
73 artifact: self.artifact,
74 wallet,
75 }
76 }
77
78 pub fn method(
84 &self,
85 name: &str,
86 args: Vec<AbiValue>,
87 ) -> Result<ContractFunctionInteraction<'_, W>, Error> {
88 let func = self.artifact.find_function(name)?;
89 let expected = func.parameters.len();
90 let got = args.len();
91 if got != expected {
92 return Err(Error::Abi(format!(
93 "function '{name}' expects {expected} argument(s), got {got}"
94 )));
95 }
96 let selector = func.selector.ok_or_else(|| {
97 Error::Abi(format!(
98 "function '{}' in artifact '{}' has no selector",
99 name, self.artifact.name
100 ))
101 })?;
102 let encoded_args = aztec_core::abi::encode_arguments(func, &args)?;
103 let call = FunctionCall {
104 to: self.address,
105 selector,
106 args: encoded_args.into_iter().map(AbiValue::Field).collect(),
107 function_type: func.function_type.clone(),
108 is_static: func.is_static,
109 hide_msg_sender: false,
110 };
111 Ok(ContractFunctionInteraction {
112 wallet: &self.wallet,
113 call,
114 capsules: vec![],
115 auth_witnesses: vec![],
116 extra_hashed_args: vec![],
117 })
118 }
119}
120
121pub(crate) fn merge_fee_payload(
127 mut payload: ExecutionPayload,
128 fee: &Option<ExecutionPayload>,
129) -> Result<ExecutionPayload, Error> {
130 if let Some(fee_payload) = fee {
131 payload.calls.extend(fee_payload.calls.clone());
132 payload
133 .auth_witnesses
134 .extend(fee_payload.auth_witnesses.clone());
135 payload.capsules.extend(fee_payload.capsules.clone());
136 payload
137 .extra_hashed_args
138 .extend(fee_payload.extra_hashed_args.clone());
139 if let Some(payer) = fee_payload.fee_payer {
140 payload.fee_payer = Some(payer);
141 }
142 }
143 Ok(payload)
144}
145
146pub struct ContractFunctionInteraction<'a, W> {
156 wallet: &'a W,
157 call: FunctionCall,
158 capsules: Vec<Capsule>,
159 auth_witnesses: Vec<AuthWitness>,
160 extra_hashed_args: Vec<HashedValues>,
161}
162
163impl<W> std::fmt::Debug for ContractFunctionInteraction<'_, W> {
164 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165 f.debug_struct("ContractFunctionInteraction")
166 .field("call", &self.call)
167 .finish_non_exhaustive()
168 }
169}
170
171impl<'a, W: Wallet> ContractFunctionInteraction<'a, W> {
172 pub fn new(wallet: &'a W, call: FunctionCall) -> Self {
174 Self {
175 wallet,
176 call,
177 capsules: vec![],
178 auth_witnesses: vec![],
179 extra_hashed_args: vec![],
180 }
181 }
182
183 pub fn new_with_capsules(wallet: &'a W, call: FunctionCall, capsules: Vec<Capsule>) -> Self {
185 Self {
186 wallet,
187 call,
188 capsules,
189 auth_witnesses: vec![],
190 extra_hashed_args: vec![],
191 }
192 }
193
194 pub fn with(mut self, auth_witnesses: Vec<AuthWitness>, capsules: Vec<Capsule>) -> Self {
196 self.auth_witnesses.extend(auth_witnesses);
197 self.capsules.extend(capsules);
198 self
199 }
200
201 pub fn get_function_call(&self) -> &FunctionCall {
203 &self.call
204 }
205
206 pub fn request(&self) -> Result<ExecutionPayload, Error> {
208 Ok(ExecutionPayload {
209 calls: vec![self.call.clone()],
210 capsules: self.capsules.clone(),
211 auth_witnesses: self.auth_witnesses.clone(),
212 extra_hashed_args: self.extra_hashed_args.clone(),
213 ..ExecutionPayload::default()
214 })
215 }
216
217 pub async fn simulate(&self, opts: SimulateOptions) -> Result<TxSimulationResult, Error> {
219 let payload = merge_fee_payload(self.request()?, &opts.fee_execution_payload)?;
220 self.wallet.simulate_tx(payload, opts).await
221 }
222
223 pub async fn profile(&self, opts: ProfileOptions) -> Result<TxProfileResult, Error> {
225 let payload = merge_fee_payload(self.request()?, &opts.fee_execution_payload)?;
226 self.wallet.profile_tx(payload, opts).await
227 }
228
229 pub async fn send(&self, opts: SendOptions) -> Result<SendResult, Error> {
231 let payload = merge_fee_payload(self.request()?, &opts.fee_execution_payload)?;
232 self.wallet.send_tx(payload, opts).await
233 }
234}
235
236pub struct BatchCall<'a, W> {
246 wallet: &'a W,
247 payloads: Vec<ExecutionPayload>,
248}
249
250impl<W> std::fmt::Debug for BatchCall<'_, W> {
251 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252 f.debug_struct("BatchCall")
253 .field("payload_count", &self.payloads.len())
254 .finish_non_exhaustive()
255 }
256}
257
258impl<'a, W: Wallet> BatchCall<'a, W> {
259 pub const fn new(wallet: &'a W, payloads: Vec<ExecutionPayload>) -> Self {
261 Self { wallet, payloads }
262 }
263
264 pub fn request(&self) -> Result<ExecutionPayload, Error> {
266 let mut merged = ExecutionPayload::default();
267
268 for payload in &self.payloads {
269 merged.calls.extend(payload.calls.clone());
270 merged.auth_witnesses.extend(payload.auth_witnesses.clone());
271 merged.capsules.extend(payload.capsules.clone());
272 merged
273 .extra_hashed_args
274 .extend(payload.extra_hashed_args.clone());
275
276 if payload.fee_payer.is_some() {
277 merged.fee_payer = payload.fee_payer;
278 }
279 }
280
281 Ok(merged)
282 }
283
284 pub async fn simulate(&self, opts: SimulateOptions) -> Result<TxSimulationResult, Error> {
286 let payload = merge_fee_payload(self.request()?, &opts.fee_execution_payload)?;
287 self.wallet.simulate_tx(payload, opts).await
288 }
289
290 pub async fn profile(&self, opts: ProfileOptions) -> Result<TxProfileResult, Error> {
292 let payload = merge_fee_payload(self.request()?, &opts.fee_execution_payload)?;
293 self.wallet.profile_tx(payload, opts).await
294 }
295
296 pub async fn send(&self, opts: SendOptions) -> Result<SendResult, Error> {
298 let payload = merge_fee_payload(self.request()?, &opts.fee_execution_payload)?;
299 self.wallet.send_tx(payload, opts).await
300 }
301}
302
303#[cfg(test)]
308#[allow(clippy::unwrap_used, clippy::expect_used)]
309mod tests {
310 use super::*;
311 use crate::abi::{AbiValue, FunctionType};
312 use crate::fee::Gas;
313 use crate::tx::TxHash;
314 use crate::types::Fr;
315 use crate::wallet::{ChainInfo, MockWallet, SendResult, TxSimulationResult};
316
317 const TOKEN_ARTIFACT: &str = r#"
318 {
319 "name": "TokenContract",
320 "functions": [
321 {
322 "name": "constructor",
323 "function_type": "private",
324 "is_initializer": true,
325 "is_static": false,
326 "parameters": [
327 { "name": "admin", "type": { "kind": "field" } }
328 ],
329 "return_types": [],
330 "selector": "0xe5fb6c81"
331 },
332 {
333 "name": "transfer",
334 "function_type": "private",
335 "is_initializer": false,
336 "is_static": false,
337 "parameters": [
338 { "name": "from", "type": { "kind": "field" } },
339 { "name": "to", "type": { "kind": "field" } },
340 { "name": "amount", "type": { "kind": "integer", "sign": "unsigned", "width": 64 } }
341 ],
342 "return_types": [],
343 "selector": "0xd6f42325"
344 },
345 {
346 "name": "balance_of",
347 "function_type": "utility",
348 "is_initializer": false,
349 "is_static": true,
350 "parameters": [
351 { "name": "owner", "type": { "kind": "field" } }
352 ],
353 "return_types": [
354 { "kind": "integer", "sign": "unsigned", "width": 64 }
355 ],
356 "selector": "0x12345678"
357 },
358 {
359 "name": "total_supply",
360 "function_type": "public",
361 "is_initializer": false,
362 "is_static": true,
363 "parameters": [],
364 "return_types": [
365 { "kind": "integer", "sign": "unsigned", "width": 64 }
366 ],
367 "selector": "0xabcdef01"
368 }
369 ]
370 }
371 "#;
372
373 const NO_SELECTOR_ARTIFACT: &str = r#"
374 {
375 "name": "NoSelector",
376 "functions": [
377 {
378 "name": "foo",
379 "function_type": "public",
380 "is_initializer": false,
381 "is_static": false,
382 "parameters": [],
383 "return_types": []
384 }
385 ]
386 }
387 "#;
388
389 fn sample_chain_info() -> ChainInfo {
390 ChainInfo {
391 chain_id: Fr::from(31337u64),
392 version: Fr::from(1u64),
393 }
394 }
395
396 fn sample_address() -> AztecAddress {
397 AztecAddress(Fr::from(42u64))
398 }
399
400 fn load_token_artifact() -> ContractArtifact {
401 ContractArtifact::from_json(TOKEN_ARTIFACT).expect("parse token artifact")
402 }
403
404 #[test]
407 fn contract_at_creates_handle() {
408 let wallet = MockWallet::new(sample_chain_info());
409 let artifact = load_token_artifact();
410 let addr = sample_address();
411
412 let contract = Contract::at(addr, artifact, wallet);
413 assert_eq!(contract.address, addr);
414 assert_eq!(contract.artifact.name, "TokenContract");
415 }
416
417 #[test]
420 fn method_finds_function_and_builds_call() {
421 let wallet = MockWallet::new(sample_chain_info());
422 let contract = Contract::at(sample_address(), load_token_artifact(), wallet);
423
424 let interaction = contract
425 .method(
426 "transfer",
427 vec![
428 AbiValue::Field(Fr::from(1u64)),
429 AbiValue::Field(Fr::from(2u64)),
430 AbiValue::Integer(100),
431 ],
432 )
433 .expect("find transfer");
434
435 assert_eq!(interaction.call.to, sample_address());
436 assert_eq!(interaction.call.function_type, FunctionType::Private);
437 assert!(!interaction.call.is_static);
438 assert_eq!(interaction.call.args.len(), 3);
439 assert_eq!(interaction.call.selector.to_string(), "0xd6f42325");
440 }
441
442 #[test]
443 fn method_preserves_private_type() {
444 let wallet = MockWallet::new(sample_chain_info());
445 let contract = Contract::at(sample_address(), load_token_artifact(), wallet);
446
447 let interaction = contract
448 .method("constructor", vec![AbiValue::Field(Fr::from(1u64))])
449 .expect("find constructor");
450 assert_eq!(interaction.call.function_type, FunctionType::Private);
451 assert!(!interaction.call.is_static);
452 }
453
454 #[test]
455 fn method_preserves_utility_static() {
456 let wallet = MockWallet::new(sample_chain_info());
457 let contract = Contract::at(sample_address(), load_token_artifact(), wallet);
458
459 let interaction = contract
460 .method("balance_of", vec![AbiValue::Field(Fr::from(1u64))])
461 .expect("find balance_of");
462 assert_eq!(interaction.call.function_type, FunctionType::Utility);
463 assert!(interaction.call.is_static);
464 }
465
466 #[test]
467 fn method_preserves_public_static() {
468 let wallet = MockWallet::new(sample_chain_info());
469 let contract = Contract::at(sample_address(), load_token_artifact(), wallet);
470
471 let interaction = contract
472 .method("total_supply", vec![])
473 .expect("find total_supply");
474 assert_eq!(interaction.call.function_type, FunctionType::Public);
475 assert!(interaction.call.is_static);
476 }
477
478 #[test]
479 fn method_not_found_returns_error() {
480 let wallet = MockWallet::new(sample_chain_info());
481 let contract = Contract::at(sample_address(), load_token_artifact(), wallet);
482
483 let result = contract.method("nonexistent", vec![]);
484 assert!(result.is_err());
485 let err = result.unwrap_err();
486 assert!(
487 err.to_string().contains("nonexistent"),
488 "error should mention function name: {err}"
489 );
490 }
491
492 #[test]
493 fn method_without_selector_returns_error() {
494 let wallet = MockWallet::new(sample_chain_info());
495 let artifact =
496 ContractArtifact::from_json(NO_SELECTOR_ARTIFACT).expect("parse no-selector artifact");
497 let contract = Contract::at(sample_address(), artifact, wallet);
498
499 let result = contract.method("foo", vec![]);
500 assert!(result.is_err());
501 let err = result.unwrap_err();
502 assert!(
503 err.to_string().contains("no selector"),
504 "error should mention missing selector: {err}"
505 );
506 }
507
508 #[test]
509 fn method_argument_count_mismatch_returns_error() {
510 let wallet = MockWallet::new(sample_chain_info());
511 let contract = Contract::at(sample_address(), load_token_artifact(), wallet);
512
513 let result = contract.method(
514 "transfer",
515 vec![
516 AbiValue::Field(Fr::from(1u64)),
517 AbiValue::Field(Fr::from(2u64)),
518 ],
519 );
520
521 assert!(result.is_err());
522 let err = result.unwrap_err();
523 assert!(
524 err.to_string().contains("expects 3 argument(s), got 2"),
525 "error should mention argument mismatch: {err}"
526 );
527 }
528
529 #[test]
532 fn request_wraps_single_call() {
533 let wallet = MockWallet::new(sample_chain_info());
534 let contract = Contract::at(sample_address(), load_token_artifact(), wallet);
535
536 let interaction = contract
537 .method(
538 "transfer",
539 vec![
540 AbiValue::Field(Fr::from(1u64)),
541 AbiValue::Field(Fr::from(2u64)),
542 AbiValue::Integer(100),
543 ],
544 )
545 .expect("find transfer");
546 let payload = interaction.request().expect("build payload");
547
548 assert_eq!(payload.calls.len(), 1);
549 assert_eq!(payload.calls[0].to, sample_address());
550 assert_eq!(payload.calls[0].selector.to_string(), "0xd6f42325");
551 assert!(payload.auth_witnesses.is_empty());
552 assert!(payload.capsules.is_empty());
553 assert!(payload.extra_hashed_args.is_empty());
554 assert!(payload.fee_payer.is_none());
555 }
556
557 #[tokio::test]
560 async fn simulate_delegates_to_wallet() {
561 let wallet =
562 MockWallet::new(sample_chain_info()).with_simulate_result(TxSimulationResult {
563 return_values: serde_json::json!({"balance": 1000}),
564 gas_used: Some(Gas {
565 da_gas: 10,
566 l2_gas: 20,
567 }),
568 });
569 let contract = Contract::at(sample_address(), load_token_artifact(), wallet);
570
571 let result = contract
572 .method("balance_of", vec![AbiValue::Field(Fr::from(1u64))])
573 .expect("find balance_of")
574 .simulate(SimulateOptions::default())
575 .await
576 .expect("simulate");
577
578 assert_eq!(result.return_values, serde_json::json!({"balance": 1000}));
579 assert_eq!(result.gas_used.as_ref().map(|g| g.l2_gas), Some(20));
580 }
581
582 #[tokio::test]
585 async fn send_delegates_to_wallet() {
586 let tx_hash =
587 TxHash::from_hex("0x00000000000000000000000000000000000000000000000000000000deadbeef")
588 .expect("valid hex");
589 let wallet = MockWallet::new(sample_chain_info()).with_send_result(SendResult { tx_hash });
590 let contract = Contract::at(sample_address(), load_token_artifact(), wallet);
591
592 let result = contract
593 .method(
594 "transfer",
595 vec![
596 AbiValue::Field(Fr::from(1u64)),
597 AbiValue::Field(Fr::from(2u64)),
598 AbiValue::Integer(100),
599 ],
600 )
601 .expect("find transfer")
602 .send(SendOptions::default())
603 .await
604 .expect("send");
605
606 assert_eq!(result.tx_hash, tx_hash);
607 }
608
609 use crate::abi::FunctionSelector;
614 use crate::tx::{AuthWitness, Capsule, HashedValues};
615
616 fn make_call(addr: u64, selector: &str) -> FunctionCall {
617 FunctionCall {
618 to: AztecAddress(Fr::from(addr)),
619 selector: FunctionSelector::from_hex(selector).expect("valid selector"),
620 args: vec![AbiValue::Field(Fr::from(addr))],
621 function_type: FunctionType::Private,
622 is_static: false,
623 hide_msg_sender: false,
624 }
625 }
626
627 fn make_payload(addr: u64, selector: &str) -> ExecutionPayload {
628 ExecutionPayload {
629 calls: vec![make_call(addr, selector)],
630 ..ExecutionPayload::default()
631 }
632 }
633
634 #[test]
637 fn batch_call_empty() {
638 let wallet = MockWallet::new(sample_chain_info());
639 let batch = BatchCall::new(&wallet, vec![]);
640 let payload = batch.request().expect("empty batch");
641 assert!(payload.calls.is_empty());
642 assert!(payload.auth_witnesses.is_empty());
643 assert!(payload.capsules.is_empty());
644 assert!(payload.extra_hashed_args.is_empty());
645 assert!(payload.fee_payer.is_none());
646 }
647
648 #[test]
649 fn batch_call_single_payload() {
650 let wallet = MockWallet::new(sample_chain_info());
651 let p = make_payload(1, "0xaabbccdd");
652 let batch = BatchCall::new(&wallet, vec![p]);
653 let payload = batch.request().expect("single payload");
654 assert_eq!(payload.calls.len(), 1);
655 assert_eq!(payload.calls[0].to, AztecAddress(Fr::from(1u64)));
656 }
657
658 #[test]
659 fn batch_call_merges_multiple_payloads() {
660 let wallet = MockWallet::new(sample_chain_info());
661 let p1 = ExecutionPayload {
662 calls: vec![make_call(1, "0xaabbccdd")],
663 auth_witnesses: vec![AuthWitness {
664 fields: vec![Fr::from(10u64)],
665 ..Default::default()
666 }],
667 capsules: vec![Capsule {
668 contract_address: AztecAddress(Fr::from(10u64)),
669 storage_slot: Fr::from(1u64),
670 data: vec![Fr::from(1u64)],
671 }],
672 extra_hashed_args: vec![HashedValues::from_args(vec![Fr::from(20u64)])],
673 fee_payer: None,
674 };
675 let p2 = ExecutionPayload {
676 calls: vec![make_call(2, "0x11223344")],
677 auth_witnesses: vec![AuthWitness {
678 fields: vec![Fr::from(30u64)],
679 ..Default::default()
680 }],
681 capsules: vec![],
682 extra_hashed_args: vec![],
683 fee_payer: Some(AztecAddress(Fr::from(99u64))),
684 };
685
686 let batch = BatchCall::new(&wallet, vec![p1, p2]);
687 let payload = batch.request().expect("merge payloads");
688
689 assert_eq!(payload.calls.len(), 2);
690 assert_eq!(payload.calls[0].to, AztecAddress(Fr::from(1u64)));
691 assert_eq!(payload.calls[1].to, AztecAddress(Fr::from(2u64)));
692 assert_eq!(payload.auth_witnesses.len(), 2);
693 assert_eq!(payload.capsules.len(), 1);
694 assert_eq!(payload.extra_hashed_args.len(), 1);
695 assert_eq!(payload.fee_payer, Some(AztecAddress(Fr::from(99u64))));
696 }
697
698 #[test]
699 fn batch_call_fee_payer_uses_last_non_none() {
700 let wallet = MockWallet::new(sample_chain_info());
701 let p1 = ExecutionPayload {
702 fee_payer: Some(AztecAddress(Fr::from(1u64))),
703 ..ExecutionPayload::default()
704 };
705 let p2 = ExecutionPayload {
706 fee_payer: None,
707 ..ExecutionPayload::default()
708 };
709 let p3 = ExecutionPayload {
710 fee_payer: Some(AztecAddress(Fr::from(3u64))),
711 ..ExecutionPayload::default()
712 };
713
714 let batch = BatchCall::new(&wallet, vec![p1, p2, p3]);
715 let payload = batch.request().expect("merge payloads");
716 assert_eq!(payload.fee_payer, Some(AztecAddress(Fr::from(3u64))));
717 }
718
719 #[tokio::test]
722 async fn batch_call_simulate_delegates_to_wallet() {
723 let wallet =
724 MockWallet::new(sample_chain_info()).with_simulate_result(TxSimulationResult {
725 return_values: serde_json::json!({"batch": true}),
726 gas_used: Some(Gas {
727 da_gas: 10,
728 l2_gas: 20,
729 }),
730 });
731
732 let batch = BatchCall::new(
733 &wallet,
734 vec![make_payload(1, "0xaabbccdd"), make_payload(2, "0x11223344")],
735 );
736
737 let result = batch
738 .simulate(SimulateOptions::default())
739 .await
740 .expect("simulate batch");
741
742 assert_eq!(result.return_values, serde_json::json!({"batch": true}));
743 assert_eq!(result.gas_used.as_ref().map(|g| g.l2_gas), Some(20));
744 }
745
746 #[tokio::test]
749 async fn batch_call_send_delegates_to_wallet() {
750 let tx_hash =
751 TxHash::from_hex("0x00000000000000000000000000000000000000000000000000000000deadbeef")
752 .expect("valid hex");
753 let wallet = MockWallet::new(sample_chain_info()).with_send_result(SendResult { tx_hash });
754
755 let batch = BatchCall::new(
756 &wallet,
757 vec![make_payload(1, "0xaabbccdd"), make_payload(2, "0x11223344")],
758 );
759
760 let result = batch
761 .send(SendOptions::default())
762 .await
763 .expect("send batch");
764
765 assert_eq!(result.tx_hash, tx_hash);
766 }
767
768 #[test]
771 fn batch_call_debug() {
772 let wallet = MockWallet::new(sample_chain_info());
773 let batch = BatchCall::new(&wallet, vec![make_payload(1, "0xaabbccdd")]);
774 let dbg = format!("{batch:?}");
775 assert!(dbg.contains("payload_count: 1"));
776 }
777
778 #[test]
781 fn with_adds_capsules_and_auth_witnesses() {
782 let wallet = MockWallet::new(sample_chain_info());
783 let contract = Contract::at(sample_address(), load_token_artifact(), wallet);
784
785 let aw = AuthWitness {
786 fields: vec![Fr::from(99u64)],
787 ..Default::default()
788 };
789 let cap = Capsule {
790 contract_address: AztecAddress(Fr::from(10u64)),
791 storage_slot: Fr::from(1u64),
792 data: vec![Fr::from(42u64)],
793 };
794
795 let interaction = contract
796 .method("total_supply", vec![])
797 .expect("find total_supply")
798 .with(vec![aw.clone()], vec![cap.clone()]);
799
800 let payload = interaction.request().expect("build payload");
801 assert_eq!(payload.auth_witnesses.len(), 1);
802 assert_eq!(payload.auth_witnesses[0].fields, aw.fields);
803 assert_eq!(payload.capsules.len(), 1);
804 assert_eq!(payload.capsules[0].storage_slot, cap.storage_slot);
805 }
806
807 #[test]
808 fn get_function_call_returns_call() {
809 let wallet = MockWallet::new(sample_chain_info());
810 let contract = Contract::at(sample_address(), load_token_artifact(), wallet);
811
812 let interaction = contract
813 .method("total_supply", vec![])
814 .expect("find total_supply");
815
816 let call = interaction.get_function_call();
817 assert_eq!(call.to, sample_address());
818 assert_eq!(call.selector.to_string(), "0xabcdef01");
819 }
820
821 #[test]
822 fn request_includes_auth_witnesses() {
823 let wallet = MockWallet::new(sample_chain_info());
824 let contract = Contract::at(sample_address(), load_token_artifact(), wallet);
825
826 let aw = AuthWitness {
827 fields: vec![Fr::from(1u64), Fr::from(2u64)],
828 ..Default::default()
829 };
830
831 let interaction = contract
832 .method("total_supply", vec![])
833 .expect("find total_supply")
834 .with(vec![aw], vec![]);
835
836 let payload = interaction.request().expect("build payload");
837 assert_eq!(payload.auth_witnesses.len(), 1);
838 assert_eq!(payload.auth_witnesses[0].fields.len(), 2);
839 }
840
841 #[tokio::test]
844 async fn profile_delegates_to_wallet() {
845 let wallet = MockWallet::new(sample_chain_info());
846 let contract = Contract::at(sample_address(), load_token_artifact(), wallet);
847
848 let result = contract
849 .method("total_supply", vec![])
850 .expect("find total_supply")
851 .profile(ProfileOptions::default())
852 .await
853 .expect("profile");
854
855 assert_eq!(result.return_values, serde_json::Value::Null);
856 }
857
858 #[tokio::test]
859 async fn batch_profile_delegates_to_wallet() {
860 let wallet = MockWallet::new(sample_chain_info());
861
862 let batch = BatchCall::new(
863 &wallet,
864 vec![make_payload(1, "0xaabbccdd"), make_payload(2, "0x11223344")],
865 );
866
867 let result = batch
868 .profile(ProfileOptions::default())
869 .await
870 .expect("profile batch");
871
872 assert_eq!(result.return_values, serde_json::Value::Null);
873 }
874
875 #[test]
878 fn send_options_with_fee_payload_merged() {
879 let fee_payload = ExecutionPayload {
880 calls: vec![make_call(99, "0x11111111")],
881 fee_payer: Some(AztecAddress(Fr::from(99u64))),
882 ..ExecutionPayload::default()
883 };
884 let main_payload = ExecutionPayload {
885 calls: vec![make_call(1, "0xaabbccdd")],
886 ..ExecutionPayload::default()
887 };
888 let merged = merge_fee_payload(main_payload, &Some(fee_payload)).expect("merge");
889 assert_eq!(merged.calls.len(), 2);
890 assert_eq!(merged.fee_payer, Some(AztecAddress(Fr::from(99u64))));
891 }
892
893 #[test]
894 fn simulate_options_with_gas_estimation_flags() {
895 let opts = SimulateOptions {
896 estimate_gas: true,
897 estimated_gas_padding: Some(0.1),
898 ..SimulateOptions::default()
899 };
900 assert!(opts.estimate_gas);
901 assert_eq!(opts.estimated_gas_padding, Some(0.1));
902 }
903
904 #[test]
907 fn contract_deploy_creates_deploy_method() {
908 let wallet = MockWallet::new(sample_chain_info());
909 let artifact = load_token_artifact();
910 let result = Contract::deploy(
911 &wallet,
912 artifact,
913 vec![AbiValue::Field(Fr::from(1u64))],
914 None,
915 );
916 assert!(result.is_ok(), "deploy should succeed");
917 }
918
919 #[test]
920 fn contract_deploy_with_public_keys() {
921 use crate::types::PublicKeys;
922
923 let wallet = MockWallet::new(sample_chain_info());
924 let artifact = load_token_artifact();
925 let keys = PublicKeys::default();
926 let result = Contract::deploy_with_public_keys(
927 keys,
928 &wallet,
929 artifact,
930 vec![AbiValue::Field(Fr::from(1u64))],
931 None,
932 );
933 assert!(result.is_ok(), "deploy_with_public_keys should succeed");
934 }
935
936 #[test]
937 fn contract_with_wallet_changes_wallet() {
938 let wallet1 = MockWallet::new(sample_chain_info());
939 let wallet2 = MockWallet::new(ChainInfo {
940 chain_id: Fr::from(999u64),
941 version: Fr::from(2u64),
942 });
943 let addr = sample_address();
944 let contract = Contract::at(addr, load_token_artifact(), wallet1);
945 let contract2 = contract.with_wallet(wallet2);
946 assert_eq!(contract2.address, addr);
947 assert_eq!(contract2.artifact.name, "TokenContract");
948 }
949}