1use crate::models::transaction::stellar::asset::AssetSpec;
4use crate::models::transaction::stellar::host_function::{ContractSource, WasmSource};
5use crate::models::SignerError;
6use serde::{Deserialize, Serialize};
7use soroban_rs::xdr::{
8 HostFunction, InvokeHostFunctionOp, MuxedAccount as XdrMuxedAccount, MuxedAccountMed25519,
9 Operation, OperationBody, PaymentOp, SorobanAuthorizationEntry, SorobanAuthorizedFunction,
10 SorobanAuthorizedInvocation, SorobanCredentials, Uint256, VecM,
11};
12use std::convert::TryFrom;
13use stellar_strkey::{ed25519::MuxedAccount, ed25519::PublicKey};
14use utoipa::ToSchema;
15
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
18#[serde(tag = "type", rename_all = "snake_case")]
19pub enum AuthSpec {
20 None,
22
23 SourceAccount,
25
26 Addresses { signers: Vec<String> },
28
29 Xdr { entries: Vec<String> },
31}
32
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
34#[serde(tag = "type", rename_all = "snake_case")]
35pub enum OperationSpec {
36 Payment {
37 destination: String,
38 amount: i64,
39 asset: AssetSpec,
40 },
41 InvokeContract {
42 contract_address: String,
43 function_name: String,
44 args: Vec<serde_json::Value>,
45 #[serde(skip_serializing_if = "Option::is_none")]
46 auth: Option<AuthSpec>,
47 },
48 CreateContract {
49 source: ContractSource,
50 wasm_hash: String,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 salt: Option<String>,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 constructor_args: Option<Vec<serde_json::Value>>,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 auth: Option<AuthSpec>,
57 },
58 UploadWasm {
59 wasm: WasmSource,
60 #[serde(skip_serializing_if = "Option::is_none")]
61 auth: Option<AuthSpec>,
62 },
63}
64
65fn parse_destination_address(destination: &str) -> Result<XdrMuxedAccount, SignerError> {
69 if let Ok(m) = MuxedAccount::from_string(destination) {
70 Ok(XdrMuxedAccount::MuxedEd25519(MuxedAccountMed25519 {
72 id: m.id,
73 ed25519: Uint256(m.ed25519),
74 }))
75 } else {
76 let pk = PublicKey::from_string(destination)
78 .map_err(|e| SignerError::ConversionError(format!("Invalid destination: {}", e)))?;
79 Ok(XdrMuxedAccount::Ed25519(Uint256(pk.0)))
80 }
81}
82
83fn create_source_account_auth_entry(
85 function: SorobanAuthorizedFunction,
86) -> SorobanAuthorizationEntry {
87 SorobanAuthorizationEntry {
88 credentials: SorobanCredentials::SourceAccount,
89 root_invocation: SorobanAuthorizedInvocation {
90 function,
91 sub_invocations: VecM::default(),
92 },
93 }
94}
95
96fn decode_xdr_auth_entries(
98 xdr_entries: Vec<String>,
99) -> Result<Vec<SorobanAuthorizationEntry>, SignerError> {
100 use soroban_rs::xdr::{Limits, ReadXdr};
101
102 xdr_entries
103 .iter()
104 .map(|xdr_str| {
105 SorobanAuthorizationEntry::from_xdr_base64(xdr_str, Limits::none())
106 .map_err(|e| SignerError::ConversionError(format!("Invalid auth XDR: {}", e)))
107 })
108 .collect()
109}
110
111fn generate_default_auth_entries(
113 host_function: &HostFunction,
114) -> Result<Vec<SorobanAuthorizationEntry>, SignerError> {
115 match host_function {
116 HostFunction::CreateContract(ref create_args) => {
117 let auth_entry = create_source_account_auth_entry(
118 SorobanAuthorizedFunction::CreateContractHostFn(create_args.clone()),
119 );
120 Ok(vec![auth_entry])
121 }
122 HostFunction::CreateContractV2(ref create_args_v2) => {
123 let auth_entry = create_source_account_auth_entry(
124 SorobanAuthorizedFunction::CreateContractV2HostFn(create_args_v2.clone()),
125 );
126 Ok(vec![auth_entry])
127 }
128 HostFunction::InvokeContract(ref invoke_args) => {
129 let auth_entry = create_source_account_auth_entry(
130 SorobanAuthorizedFunction::ContractFn(invoke_args.clone()),
131 );
132 Ok(vec![auth_entry])
133 }
134 _ => Ok(vec![]),
135 }
136}
137
138fn build_auth_vector(
140 auth: Option<AuthSpec>,
141 host_function: &HostFunction,
142) -> Result<VecM<SorobanAuthorizationEntry, { u32::MAX }>, SignerError> {
143 let auth_entries = match auth {
144 Some(AuthSpec::None) => vec![],
145 Some(AuthSpec::SourceAccount) => generate_default_auth_entries(host_function)?,
146 Some(AuthSpec::Addresses { signers: _ }) => {
147 return Err(SignerError::ConversionError(
149 "Address-based auth not yet implemented".into(),
150 ));
151 }
152 Some(AuthSpec::Xdr { entries }) => decode_xdr_auth_entries(entries)?,
153 None => generate_default_auth_entries(host_function)?,
154 };
155
156 auth_entries.try_into().map_err(|e| {
157 SignerError::ConversionError(format!("Failed to convert auth entries: {:?}", e))
158 })
159}
160
161fn convert_payment_operation(
163 destination: String,
164 amount: i64,
165 asset: AssetSpec,
166) -> Result<Operation, SignerError> {
167 let dest = parse_destination_address(&destination)?;
168
169 Ok(Operation {
170 source_account: None,
171 body: OperationBody::Payment(PaymentOp {
172 destination: dest,
173 asset: asset.try_into()?,
174 amount,
175 }),
176 })
177}
178
179fn convert_invoke_contract_operation(
181 contract_address: String,
182 function_name: String,
183 args: Vec<serde_json::Value>,
184 auth: Option<AuthSpec>,
185) -> Result<Operation, SignerError> {
186 use crate::models::transaction::stellar::host_function::HostFunctionSpec;
187
188 let spec = HostFunctionSpec::InvokeContract {
190 contract_address,
191 function_name,
192 args,
193 };
194
195 let host_function = HostFunction::try_from(spec)?;
197
198 let auth_vec = build_auth_vector(auth, &host_function)?;
200
201 Ok(Operation {
202 source_account: None,
203 body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp {
204 auth: auth_vec,
205 host_function,
206 }),
207 })
208}
209
210fn convert_create_contract_operation(
212 source: ContractSource,
213 wasm_hash: String,
214 salt: Option<String>,
215 constructor_args: Option<Vec<serde_json::Value>>,
216 auth: Option<AuthSpec>,
217) -> Result<Operation, SignerError> {
218 use crate::models::transaction::stellar::host_function::HostFunctionSpec;
219
220 let spec = HostFunctionSpec::CreateContract {
222 source,
223 wasm_hash,
224 salt,
225 constructor_args,
226 };
227
228 let host_function = HostFunction::try_from(spec)?;
230
231 let auth_vec = build_auth_vector(auth, &host_function)?;
233
234 Ok(Operation {
235 source_account: None,
236 body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp {
237 auth: auth_vec,
238 host_function,
239 }),
240 })
241}
242
243fn convert_upload_wasm_operation(
245 wasm: WasmSource,
246 auth: Option<AuthSpec>,
247) -> Result<Operation, SignerError> {
248 use crate::models::transaction::stellar::host_function::HostFunctionSpec;
249
250 let spec = HostFunctionSpec::UploadWasm { wasm };
252
253 let host_function = HostFunction::try_from(spec)?;
255
256 let auth_vec = build_auth_vector(auth, &host_function)?;
258
259 Ok(Operation {
260 source_account: None,
261 body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp {
262 auth: auth_vec,
263 host_function,
264 }),
265 })
266}
267
268impl TryFrom<OperationSpec> for Operation {
269 type Error = SignerError;
270
271 fn try_from(op: OperationSpec) -> Result<Self, Self::Error> {
272 match op {
273 OperationSpec::Payment {
274 destination,
275 amount,
276 asset,
277 } => convert_payment_operation(destination, amount, asset),
278
279 OperationSpec::InvokeContract {
280 contract_address,
281 function_name,
282 args,
283 auth,
284 } => convert_invoke_contract_operation(contract_address, function_name, args, auth),
285
286 OperationSpec::CreateContract {
287 source,
288 wasm_hash,
289 salt,
290 constructor_args,
291 auth,
292 } => convert_create_contract_operation(source, wasm_hash, salt, constructor_args, auth),
293
294 OperationSpec::UploadWasm { wasm, auth } => convert_upload_wasm_operation(wasm, auth),
295 }
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302 use crate::models::transaction::stellar::host_function::ContractSource;
303 use soroban_rs::xdr::{
304 AccountId, ContractExecutable, ContractIdPreimage, ContractIdPreimageFromAddress,
305 CreateContractArgs, CreateContractArgsV2, Hash, PublicKey as XdrPublicKey, ScAddress,
306 };
307
308 const TEST_PK: &str = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF";
309 const TEST_CONTRACT: &str = "CA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUWDA";
310 const TEST_MUXED: &str =
311 "MAAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITLVL6";
312
313 mod parse_destination_address_tests {
314 use super::*;
315
316 #[test]
317 fn test_regular_public_key() {
318 let result = parse_destination_address(TEST_PK).unwrap();
319 assert!(matches!(result, XdrMuxedAccount::Ed25519(_)));
320 }
321
322 #[test]
323 fn test_muxed_account() {
324 let result = parse_destination_address(TEST_MUXED).unwrap();
325 assert!(matches!(result, XdrMuxedAccount::MuxedEd25519(_)));
326 }
327
328 #[test]
329 fn test_invalid_address() {
330 let result = parse_destination_address("INVALID");
331 assert!(result.is_err());
332 }
333 }
334
335 mod create_source_account_auth_entry_tests {
336 use super::*;
337 use soroban_rs::xdr::Uint256;
338
339 #[test]
340 fn test_creates_correct_structure() {
341 let function = SorobanAuthorizedFunction::CreateContractHostFn(CreateContractArgs {
342 contract_id_preimage: ContractIdPreimage::Address(ContractIdPreimageFromAddress {
343 address: ScAddress::Account(AccountId(XdrPublicKey::PublicKeyTypeEd25519(
344 Uint256([0u8; 32]),
345 ))),
346 salt: Uint256([0u8; 32]),
347 }),
348 executable: ContractExecutable::Wasm(Hash([0u8; 32])),
349 });
350
351 let entry = create_source_account_auth_entry(function.clone());
352 assert!(matches!(
353 entry.credentials,
354 SorobanCredentials::SourceAccount
355 ));
356 }
358 }
359
360 mod decode_xdr_auth_entries_tests {
361 use super::*;
362
363 #[test]
364 fn test_invalid_base64() {
365 let xdr_entries = vec!["!!!invalid!!!".to_string()];
366 let result = decode_xdr_auth_entries(xdr_entries);
367 assert!(result.is_err());
368 }
369
370 #[test]
371 fn test_malformed_xdr() {
372 let xdr_entries = vec!["dGVzdA==".to_string()]; let result = decode_xdr_auth_entries(xdr_entries);
374 assert!(result.is_err());
375 }
376
377 #[test]
378 fn test_empty_list() {
379 let xdr_entries = vec![];
380 let result = decode_xdr_auth_entries(xdr_entries);
381 assert!(result.is_ok());
382 assert_eq!(result.unwrap().len(), 0);
383 }
384 }
385
386 mod generate_default_auth_entries_tests {
387 use super::*;
388 use soroban_rs::xdr::Uint256;
389
390 #[test]
391 fn test_create_contract() {
392 let host_function = HostFunction::CreateContract(CreateContractArgs {
393 contract_id_preimage: ContractIdPreimage::Address(ContractIdPreimageFromAddress {
394 address: ScAddress::Account(AccountId(XdrPublicKey::PublicKeyTypeEd25519(
395 Uint256([0u8; 32]),
396 ))),
397 salt: Uint256([0u8; 32]),
398 }),
399 executable: ContractExecutable::Wasm(Hash([0u8; 32])),
400 });
401
402 let result = generate_default_auth_entries(&host_function);
403 assert!(result.is_ok());
404 assert_eq!(result.unwrap().len(), 1);
405 }
406
407 #[test]
408 fn test_create_contract_v2() {
409 let host_function = HostFunction::CreateContractV2(CreateContractArgsV2 {
410 contract_id_preimage: ContractIdPreimage::Address(ContractIdPreimageFromAddress {
411 address: ScAddress::Account(AccountId(XdrPublicKey::PublicKeyTypeEd25519(
412 Uint256([0u8; 32]),
413 ))),
414 salt: Uint256([0u8; 32]),
415 }),
416 executable: ContractExecutable::Wasm(Hash([0u8; 32])),
417 constructor_args: VecM::default(),
418 });
419
420 let result = generate_default_auth_entries(&host_function);
421 assert!(result.is_ok());
422 assert_eq!(result.unwrap().len(), 1);
423 }
424
425 #[test]
426 fn test_invoke_contract() {
427 let host_function = HostFunction::InvokeContract(soroban_rs::xdr::InvokeContractArgs {
428 contract_address: ScAddress::Contract(Hash([0u8; 32])),
429 function_name: soroban_rs::xdr::ScSymbol::try_from(b"test".to_vec()).unwrap(),
430 args: VecM::default(),
431 });
432
433 let result = generate_default_auth_entries(&host_function);
434 assert!(result.is_ok());
435 assert_eq!(result.unwrap().len(), 1);
436 }
437
438 #[test]
439 fn test_other_operations() {
440 let host_function = HostFunction::UploadContractWasm(vec![0u8; 10].try_into().unwrap());
441
442 let result = generate_default_auth_entries(&host_function);
443 assert!(result.is_ok());
444 assert_eq!(result.unwrap().len(), 0);
445 }
446 }
447
448 mod build_auth_vector_tests {
449 use super::*;
450 use soroban_rs::xdr::Uint256;
451
452 #[test]
453 fn test_simple_auth() {
454 let host_function = HostFunction::CreateContract(CreateContractArgs {
455 contract_id_preimage: ContractIdPreimage::Address(ContractIdPreimageFromAddress {
456 address: ScAddress::Account(AccountId(XdrPublicKey::PublicKeyTypeEd25519(
457 Uint256([0u8; 32]),
458 ))),
459 salt: Uint256([0u8; 32]),
460 }),
461 executable: ContractExecutable::Wasm(Hash([0u8; 32])),
462 });
463
464 let auth = Some(AuthSpec::SourceAccount);
465 let result = build_auth_vector(auth, &host_function);
466
467 assert!(result.is_ok());
468 assert_eq!(result.unwrap().len(), 1);
469 }
470
471 #[test]
472 fn test_xdr_auth_invalid() {
473 let host_function = HostFunction::CreateContract(CreateContractArgs {
474 contract_id_preimage: ContractIdPreimage::Address(ContractIdPreimageFromAddress {
475 address: ScAddress::Account(AccountId(XdrPublicKey::PublicKeyTypeEd25519(
476 Uint256([0u8; 32]),
477 ))),
478 salt: Uint256([0u8; 32]),
479 }),
480 executable: ContractExecutable::Wasm(Hash([0u8; 32])),
481 });
482
483 let auth = Some(AuthSpec::Xdr {
484 entries: vec!["invalid".to_string()],
485 });
486 let result = build_auth_vector(auth, &host_function);
487
488 assert!(result.is_err());
489 }
490
491 #[test]
492 fn test_none_default_create_contract() {
493 let host_function = HostFunction::CreateContract(CreateContractArgs {
494 contract_id_preimage: ContractIdPreimage::Address(ContractIdPreimageFromAddress {
495 address: ScAddress::Account(AccountId(XdrPublicKey::PublicKeyTypeEd25519(
496 Uint256([0u8; 32]),
497 ))),
498 salt: Uint256([0u8; 32]),
499 }),
500 executable: ContractExecutable::Wasm(Hash([0u8; 32])),
501 });
502
503 let result = build_auth_vector(None, &host_function);
504
505 assert!(result.is_ok());
506 assert_eq!(result.unwrap().len(), 1);
507 }
508
509 #[test]
510 fn test_none_default_invoke_contract() {
511 let host_function = HostFunction::InvokeContract(soroban_rs::xdr::InvokeContractArgs {
512 contract_address: ScAddress::Contract(Hash([0u8; 32])),
513 function_name: soroban_rs::xdr::ScSymbol::try_from(b"test".to_vec()).unwrap(),
514 args: VecM::default(),
515 });
516
517 let result = build_auth_vector(None, &host_function);
518
519 assert!(result.is_ok());
520 assert_eq!(result.unwrap().len(), 1);
521 }
522 }
523
524 mod convert_payment_operation_tests {
525 use super::*;
526
527 #[test]
528 fn test_with_native_asset() {
529 let result = convert_payment_operation(TEST_PK.to_string(), 1000, AssetSpec::Native);
530
531 assert!(result.is_ok());
532 if let Operation {
533 body: OperationBody::Payment(op),
534 ..
535 } = result.unwrap()
536 {
537 assert_eq!(op.amount, 1000);
538 assert!(matches!(op.asset, soroban_rs::xdr::Asset::Native));
539 } else {
540 panic!("Expected Payment operation");
541 }
542 }
543
544 #[test]
545 fn test_with_credit_asset() {
546 let result = convert_payment_operation(
547 TEST_PK.to_string(),
548 500,
549 AssetSpec::Credit4 {
550 code: "USDC".to_string(),
551 issuer: TEST_PK.to_string(),
552 },
553 );
554
555 assert!(result.is_ok());
556 }
557
558 #[test]
559 fn test_invalid_destination() {
560 let result = convert_payment_operation("INVALID".to_string(), 1000, AssetSpec::Native);
561
562 assert!(result.is_err());
563 }
564
565 #[test]
566 fn test_invalid_asset() {
567 let result = convert_payment_operation(
568 TEST_PK.to_string(),
569 1000,
570 AssetSpec::Credit4 {
571 code: "TOOLONG".to_string(),
572 issuer: TEST_PK.to_string(),
573 },
574 );
575
576 assert!(result.is_err());
577 }
578 }
579
580 mod convert_invoke_contract_operation_tests {
581 use super::*;
582
583 #[test]
584 fn test_basic_contract_invocation() {
585 let result = convert_invoke_contract_operation(
586 TEST_CONTRACT.to_string(),
587 "test".to_string(),
588 vec![],
589 None,
590 );
591 assert!(result.is_ok());
592 }
593
594 #[test]
595 fn test_with_auth() {
596 let auth = Some(AuthSpec::SourceAccount);
597 let result = convert_invoke_contract_operation(
598 TEST_CONTRACT.to_string(),
599 "transfer".to_string(),
600 vec![],
601 auth,
602 );
603
604 assert!(result.is_ok());
605 if let Operation {
606 body: OperationBody::InvokeHostFunction(op),
607 ..
608 } = result.unwrap()
609 {
610 assert_eq!(op.auth.len(), 1);
611 } else {
612 panic!("Expected InvokeHostFunction operation");
613 }
614 }
615 }
616
617 mod convert_create_contract_operation_tests {
618 use super::*;
619
620 #[test]
621 fn test_create_contract() {
622 let source = ContractSource::Address {
623 address: TEST_PK.to_string(),
624 };
625 let wasm_hash =
626 "0000000000000000000000000000000000000000000000000000000000000001".to_string();
627
628 let result = convert_create_contract_operation(source, wasm_hash, None, None, None);
629 assert!(result.is_ok());
630 }
631 }
632
633 #[test]
635 fn test_payment_operation() {
636 let spec = OperationSpec::Payment {
637 destination: TEST_PK.to_string(),
638 amount: 1000,
639 asset: AssetSpec::Native,
640 };
641
642 let result = Operation::try_from(spec);
643 assert!(result.is_ok());
644 assert!(matches!(result.unwrap().body, OperationBody::Payment(_)));
645 }
646
647 #[test]
648 fn test_invoke_contract_operation() {
649 let spec = OperationSpec::InvokeContract {
650 contract_address: TEST_CONTRACT.to_string(),
651 function_name: "test".to_string(),
652 args: vec![],
653 auth: None,
654 };
655
656 let result = Operation::try_from(spec);
657 assert!(result.is_ok());
658 assert!(matches!(
659 result.unwrap().body,
660 OperationBody::InvokeHostFunction(_)
661 ));
662 }
663
664 #[test]
665 fn test_operation_spec_serde() {
666 let spec = OperationSpec::Payment {
667 destination: TEST_PK.to_string(),
668 amount: 1000,
669 asset: AssetSpec::Native,
670 };
671 let json = serde_json::to_string(&spec).unwrap();
672 assert!(json.contains("payment"));
673 assert!(json.contains("native"));
674
675 let deserialized: OperationSpec = serde_json::from_str(&json).unwrap();
676 assert_eq!(spec, deserialized);
677 }
678
679 #[test]
680 fn test_auth_spec_serde() {
681 let spec = AuthSpec::SourceAccount;
682 let json = serde_json::to_string(&spec).unwrap();
683 assert!(json.contains("source_account"));
684
685 let deserialized: AuthSpec = serde_json::from_str(&json).unwrap();
686 assert_eq!(spec, deserialized);
687 }
688
689 #[test]
690 fn test_auth_spec_json_format() {
691 let none = AuthSpec::None;
693 let none_json = serde_json::to_value(&none).unwrap();
694 assert_eq!(none_json["type"], "none");
695
696 let source = AuthSpec::SourceAccount;
698 let source_json = serde_json::to_value(&source).unwrap();
699 assert_eq!(source_json["type"], "source_account");
700
701 let addresses = AuthSpec::Addresses {
703 signers: vec![TEST_PK.to_string()],
704 };
705 let addresses_json = serde_json::to_value(&addresses).unwrap();
706 assert_eq!(addresses_json["type"], "addresses");
707 assert!(addresses_json["signers"].is_array());
708
709 let xdr = AuthSpec::Xdr {
711 entries: vec!["base64data".to_string()],
712 };
713 let xdr_json = serde_json::to_value(&xdr).unwrap();
714 assert_eq!(xdr_json["type"], "xdr");
715 assert!(xdr_json["entries"].is_array());
716 }
717
718 #[test]
719 fn test_operation_spec_json_format() {
720 let payment = OperationSpec::Payment {
722 destination: TEST_PK.to_string(),
723 amount: 1000,
724 asset: AssetSpec::Native,
725 };
726 let payment_json = serde_json::to_value(&payment).unwrap();
727 assert_eq!(payment_json["type"], "payment");
728 assert_eq!(payment_json["asset"]["type"], "native");
729
730 let invoke = OperationSpec::InvokeContract {
732 contract_address: TEST_CONTRACT.to_string(),
733 function_name: "test".to_string(),
734 args: vec![],
735 auth: None,
736 };
737 let invoke_json = serde_json::to_value(&invoke).unwrap();
738 assert_eq!(invoke_json["type"], "invoke_contract");
739 assert_eq!(invoke_json["contract_address"], TEST_CONTRACT);
740 assert_eq!(invoke_json["function_name"], "test");
741 assert!(invoke_json["args"].is_array());
742 }
743
744 #[test]
745 fn test_invoke_contract_with_source_account_auth_integration() {
746 let spec = OperationSpec::InvokeContract {
748 contract_address: TEST_CONTRACT.to_string(),
749 function_name: "transfer".to_string(),
750 args: vec![], auth: Some(AuthSpec::SourceAccount),
752 };
753
754 let result = Operation::try_from(spec);
756 assert!(result.is_ok());
757
758 let operation = result.unwrap();
759 match operation.body {
760 OperationBody::InvokeHostFunction(ref invoke_op) => {
761 assert_eq!(invoke_op.auth.len(), 1);
763
764 let auth_entry = &invoke_op.auth[0];
766 assert!(matches!(
767 auth_entry.credentials,
768 SorobanCredentials::SourceAccount
769 ));
770
771 match &auth_entry.root_invocation.function {
773 SorobanAuthorizedFunction::ContractFn(invoke_args) => {
774 assert!(matches!(
776 invoke_args.contract_address,
777 ScAddress::Contract(_)
778 ));
779 assert_eq!(invoke_args.function_name.0.as_slice(), b"transfer");
780 }
781 _ => panic!("Expected ContractFn authorization"),
782 }
783 }
784 _ => panic!("Expected InvokeHostFunction operation"),
785 }
786 }
787
788 #[test]
789 fn test_invoke_contract_with_none_auth_gets_default() {
790 let spec = OperationSpec::InvokeContract {
792 contract_address: TEST_CONTRACT.to_string(),
793 function_name: "mint".to_string(),
794 args: vec![],
795 auth: None, };
797
798 let result = Operation::try_from(spec);
800 assert!(result.is_ok());
801
802 let operation = result.unwrap();
803 match operation.body {
804 OperationBody::InvokeHostFunction(ref invoke_op) => {
805 assert_eq!(invoke_op.auth.len(), 1);
807
808 let auth_entry = &invoke_op.auth[0];
810 assert!(matches!(
811 auth_entry.credentials,
812 SorobanCredentials::SourceAccount
813 ));
814
815 match &auth_entry.root_invocation.function {
817 SorobanAuthorizedFunction::ContractFn(invoke_args) => {
818 assert_eq!(invoke_args.function_name.0.as_slice(), b"mint");
819 }
820 _ => panic!("Expected ContractFn authorization"),
821 }
822 }
823 _ => panic!("Expected InvokeHostFunction operation"),
824 }
825 }
826}