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
157 .try_into()
158 .map_err(|e| SignerError::ConversionError(format!("Failed to convert auth entries: {e:?}")))
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, ContractId, ContractIdPreimage,
305 ContractIdPreimageFromAddress, CreateContractArgs, CreateContractArgsV2, Hash,
306 PublicKey as XdrPublicKey, ScAddress,
307 };
308
309 const TEST_PK: &str = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF";
310 const TEST_CONTRACT: &str = "CA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUWDA";
311 const TEST_MUXED: &str =
312 "MAAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITLVL6";
313
314 mod parse_destination_address_tests {
315 use super::*;
316
317 #[test]
318 fn test_regular_public_key() {
319 let result = parse_destination_address(TEST_PK).unwrap();
320 assert!(matches!(result, XdrMuxedAccount::Ed25519(_)));
321 }
322
323 #[test]
324 fn test_muxed_account() {
325 let result = parse_destination_address(TEST_MUXED).unwrap();
326 assert!(matches!(result, XdrMuxedAccount::MuxedEd25519(_)));
327 }
328
329 #[test]
330 fn test_invalid_address() {
331 let result = parse_destination_address("INVALID");
332 assert!(result.is_err());
333 }
334 }
335
336 mod create_source_account_auth_entry_tests {
337 use super::*;
338 use soroban_rs::xdr::Uint256;
339
340 #[test]
341 fn test_creates_correct_structure() {
342 let function = SorobanAuthorizedFunction::CreateContractHostFn(CreateContractArgs {
343 contract_id_preimage: ContractIdPreimage::Address(ContractIdPreimageFromAddress {
344 address: ScAddress::Account(AccountId(XdrPublicKey::PublicKeyTypeEd25519(
345 Uint256([0u8; 32]),
346 ))),
347 salt: Uint256([0u8; 32]),
348 }),
349 executable: ContractExecutable::Wasm(Hash([0u8; 32])),
350 });
351
352 let entry = create_source_account_auth_entry(function.clone());
353 assert!(matches!(
354 entry.credentials,
355 SorobanCredentials::SourceAccount
356 ));
357 }
359 }
360
361 mod decode_xdr_auth_entries_tests {
362 use super::*;
363
364 #[test]
365 fn test_invalid_base64() {
366 let xdr_entries = vec!["!!!invalid!!!".to_string()];
367 let result = decode_xdr_auth_entries(xdr_entries);
368 assert!(result.is_err());
369 }
370
371 #[test]
372 fn test_malformed_xdr() {
373 let xdr_entries = vec!["dGVzdA==".to_string()]; let result = decode_xdr_auth_entries(xdr_entries);
375 assert!(result.is_err());
376 }
377
378 #[test]
379 fn test_empty_list() {
380 let xdr_entries = vec![];
381 let result = decode_xdr_auth_entries(xdr_entries);
382 assert!(result.is_ok());
383 assert_eq!(result.unwrap().len(), 0);
384 }
385 }
386
387 mod generate_default_auth_entries_tests {
388 use super::*;
389 use soroban_rs::xdr::Uint256;
390
391 #[test]
392 fn test_create_contract() {
393 let host_function = HostFunction::CreateContract(CreateContractArgs {
394 contract_id_preimage: ContractIdPreimage::Address(ContractIdPreimageFromAddress {
395 address: ScAddress::Account(AccountId(XdrPublicKey::PublicKeyTypeEd25519(
396 Uint256([0u8; 32]),
397 ))),
398 salt: Uint256([0u8; 32]),
399 }),
400 executable: ContractExecutable::Wasm(Hash([0u8; 32])),
401 });
402
403 let result = generate_default_auth_entries(&host_function);
404 assert!(result.is_ok());
405 assert_eq!(result.unwrap().len(), 1);
406 }
407
408 #[test]
409 fn test_create_contract_v2() {
410 let host_function = HostFunction::CreateContractV2(CreateContractArgsV2 {
411 contract_id_preimage: ContractIdPreimage::Address(ContractIdPreimageFromAddress {
412 address: ScAddress::Account(AccountId(XdrPublicKey::PublicKeyTypeEd25519(
413 Uint256([0u8; 32]),
414 ))),
415 salt: Uint256([0u8; 32]),
416 }),
417 executable: ContractExecutable::Wasm(Hash([0u8; 32])),
418 constructor_args: VecM::default(),
419 });
420
421 let result = generate_default_auth_entries(&host_function);
422 assert!(result.is_ok());
423 assert_eq!(result.unwrap().len(), 1);
424 }
425
426 #[test]
427 fn test_invoke_contract() {
428 let host_function = HostFunction::InvokeContract(soroban_rs::xdr::InvokeContractArgs {
429 contract_address: ScAddress::Contract(ContractId(Hash([0u8; 32]))),
430 function_name: soroban_rs::xdr::ScSymbol::try_from(b"test".to_vec()).unwrap(),
431 args: VecM::default(),
432 });
433
434 let result = generate_default_auth_entries(&host_function);
435 assert!(result.is_ok());
436 assert_eq!(result.unwrap().len(), 1);
437 }
438
439 #[test]
440 fn test_other_operations() {
441 let host_function = HostFunction::UploadContractWasm(vec![0u8; 10].try_into().unwrap());
442
443 let result = generate_default_auth_entries(&host_function);
444 assert!(result.is_ok());
445 assert_eq!(result.unwrap().len(), 0);
446 }
447 }
448
449 mod build_auth_vector_tests {
450 use super::*;
451 use soroban_rs::xdr::Uint256;
452
453 #[test]
454 fn test_simple_auth() {
455 let host_function = HostFunction::CreateContract(CreateContractArgs {
456 contract_id_preimage: ContractIdPreimage::Address(ContractIdPreimageFromAddress {
457 address: ScAddress::Account(AccountId(XdrPublicKey::PublicKeyTypeEd25519(
458 Uint256([0u8; 32]),
459 ))),
460 salt: Uint256([0u8; 32]),
461 }),
462 executable: ContractExecutable::Wasm(Hash([0u8; 32])),
463 });
464
465 let auth = Some(AuthSpec::SourceAccount);
466 let result = build_auth_vector(auth, &host_function);
467
468 assert!(result.is_ok());
469 assert_eq!(result.unwrap().len(), 1);
470 }
471
472 #[test]
473 fn test_xdr_auth_invalid() {
474 let host_function = HostFunction::CreateContract(CreateContractArgs {
475 contract_id_preimage: ContractIdPreimage::Address(ContractIdPreimageFromAddress {
476 address: ScAddress::Account(AccountId(XdrPublicKey::PublicKeyTypeEd25519(
477 Uint256([0u8; 32]),
478 ))),
479 salt: Uint256([0u8; 32]),
480 }),
481 executable: ContractExecutable::Wasm(Hash([0u8; 32])),
482 });
483
484 let auth = Some(AuthSpec::Xdr {
485 entries: vec!["invalid".to_string()],
486 });
487 let result = build_auth_vector(auth, &host_function);
488
489 assert!(result.is_err());
490 }
491
492 #[test]
493 fn test_none_default_create_contract() {
494 let host_function = HostFunction::CreateContract(CreateContractArgs {
495 contract_id_preimage: ContractIdPreimage::Address(ContractIdPreimageFromAddress {
496 address: ScAddress::Account(AccountId(XdrPublicKey::PublicKeyTypeEd25519(
497 Uint256([0u8; 32]),
498 ))),
499 salt: Uint256([0u8; 32]),
500 }),
501 executable: ContractExecutable::Wasm(Hash([0u8; 32])),
502 });
503
504 let result = build_auth_vector(None, &host_function);
505
506 assert!(result.is_ok());
507 assert_eq!(result.unwrap().len(), 1);
508 }
509
510 #[test]
511 fn test_none_default_invoke_contract() {
512 let host_function = HostFunction::InvokeContract(soroban_rs::xdr::InvokeContractArgs {
513 contract_address: ScAddress::Contract(ContractId(Hash([0u8; 32]))),
514 function_name: soroban_rs::xdr::ScSymbol::try_from(b"test".to_vec()).unwrap(),
515 args: VecM::default(),
516 });
517
518 let result = build_auth_vector(None, &host_function);
519
520 assert!(result.is_ok());
521 assert_eq!(result.unwrap().len(), 1);
522 }
523 }
524
525 mod convert_payment_operation_tests {
526 use super::*;
527
528 #[test]
529 fn test_with_native_asset() {
530 let result = convert_payment_operation(TEST_PK.to_string(), 1000, AssetSpec::Native);
531
532 assert!(result.is_ok());
533 if let Operation {
534 body: OperationBody::Payment(op),
535 ..
536 } = result.unwrap()
537 {
538 assert_eq!(op.amount, 1000);
539 assert!(matches!(op.asset, soroban_rs::xdr::Asset::Native));
540 } else {
541 panic!("Expected Payment operation");
542 }
543 }
544
545 #[test]
546 fn test_with_credit_asset() {
547 let result = convert_payment_operation(
548 TEST_PK.to_string(),
549 500,
550 AssetSpec::Credit4 {
551 code: "USDC".to_string(),
552 issuer: TEST_PK.to_string(),
553 },
554 );
555
556 assert!(result.is_ok());
557 }
558
559 #[test]
560 fn test_invalid_destination() {
561 let result = convert_payment_operation("INVALID".to_string(), 1000, AssetSpec::Native);
562
563 assert!(result.is_err());
564 }
565
566 #[test]
567 fn test_invalid_asset() {
568 let result = convert_payment_operation(
569 TEST_PK.to_string(),
570 1000,
571 AssetSpec::Credit4 {
572 code: "TOOLONG".to_string(),
573 issuer: TEST_PK.to_string(),
574 },
575 );
576
577 assert!(result.is_err());
578 }
579 }
580
581 mod convert_invoke_contract_operation_tests {
582 use super::*;
583
584 #[test]
585 fn test_basic_contract_invocation() {
586 let result = convert_invoke_contract_operation(
587 TEST_CONTRACT.to_string(),
588 "test".to_string(),
589 vec![],
590 None,
591 );
592 assert!(result.is_ok());
593 }
594
595 #[test]
596 fn test_with_auth() {
597 let auth = Some(AuthSpec::SourceAccount);
598 let result = convert_invoke_contract_operation(
599 TEST_CONTRACT.to_string(),
600 "transfer".to_string(),
601 vec![],
602 auth,
603 );
604
605 assert!(result.is_ok());
606 if let Operation {
607 body: OperationBody::InvokeHostFunction(op),
608 ..
609 } = result.unwrap()
610 {
611 assert_eq!(op.auth.len(), 1);
612 } else {
613 panic!("Expected InvokeHostFunction operation");
614 }
615 }
616 }
617
618 mod convert_create_contract_operation_tests {
619 use super::*;
620
621 #[test]
622 fn test_create_contract() {
623 let source = ContractSource::Address {
624 address: TEST_PK.to_string(),
625 };
626 let wasm_hash =
627 "0000000000000000000000000000000000000000000000000000000000000001".to_string();
628
629 let result = convert_create_contract_operation(source, wasm_hash, None, None, None);
630 assert!(result.is_ok());
631 }
632 }
633
634 #[test]
636 fn test_payment_operation() {
637 let spec = OperationSpec::Payment {
638 destination: TEST_PK.to_string(),
639 amount: 1000,
640 asset: AssetSpec::Native,
641 };
642
643 let result = Operation::try_from(spec);
644 assert!(result.is_ok());
645 assert!(matches!(result.unwrap().body, OperationBody::Payment(_)));
646 }
647
648 #[test]
649 fn test_invoke_contract_operation() {
650 let spec = OperationSpec::InvokeContract {
651 contract_address: TEST_CONTRACT.to_string(),
652 function_name: "test".to_string(),
653 args: vec![],
654 auth: None,
655 };
656
657 let result = Operation::try_from(spec);
658 assert!(result.is_ok());
659 assert!(matches!(
660 result.unwrap().body,
661 OperationBody::InvokeHostFunction(_)
662 ));
663 }
664
665 #[test]
666 fn test_operation_spec_serde() {
667 let spec = OperationSpec::Payment {
668 destination: TEST_PK.to_string(),
669 amount: 1000,
670 asset: AssetSpec::Native,
671 };
672 let json = serde_json::to_string(&spec).unwrap();
673 assert!(json.contains("payment"));
674 assert!(json.contains("native"));
675
676 let deserialized: OperationSpec = serde_json::from_str(&json).unwrap();
677 assert_eq!(spec, deserialized);
678 }
679
680 #[test]
681 fn test_auth_spec_serde() {
682 let spec = AuthSpec::SourceAccount;
683 let json = serde_json::to_string(&spec).unwrap();
684 assert!(json.contains("source_account"));
685
686 let deserialized: AuthSpec = serde_json::from_str(&json).unwrap();
687 assert_eq!(spec, deserialized);
688 }
689
690 #[test]
691 fn test_auth_spec_json_format() {
692 let none = AuthSpec::None;
694 let none_json = serde_json::to_value(&none).unwrap();
695 assert_eq!(none_json["type"], "none");
696
697 let source = AuthSpec::SourceAccount;
699 let source_json = serde_json::to_value(&source).unwrap();
700 assert_eq!(source_json["type"], "source_account");
701
702 let addresses = AuthSpec::Addresses {
704 signers: vec![TEST_PK.to_string()],
705 };
706 let addresses_json = serde_json::to_value(&addresses).unwrap();
707 assert_eq!(addresses_json["type"], "addresses");
708 assert!(addresses_json["signers"].is_array());
709
710 let xdr = AuthSpec::Xdr {
712 entries: vec!["base64data".to_string()],
713 };
714 let xdr_json = serde_json::to_value(&xdr).unwrap();
715 assert_eq!(xdr_json["type"], "xdr");
716 assert!(xdr_json["entries"].is_array());
717 }
718
719 #[test]
720 fn test_operation_spec_json_format() {
721 let payment = OperationSpec::Payment {
723 destination: TEST_PK.to_string(),
724 amount: 1000,
725 asset: AssetSpec::Native,
726 };
727 let payment_json = serde_json::to_value(&payment).unwrap();
728 assert_eq!(payment_json["type"], "payment");
729 assert_eq!(payment_json["asset"]["type"], "native");
730
731 let invoke = OperationSpec::InvokeContract {
733 contract_address: TEST_CONTRACT.to_string(),
734 function_name: "test".to_string(),
735 args: vec![],
736 auth: None,
737 };
738 let invoke_json = serde_json::to_value(&invoke).unwrap();
739 assert_eq!(invoke_json["type"], "invoke_contract");
740 assert_eq!(invoke_json["contract_address"], TEST_CONTRACT);
741 assert_eq!(invoke_json["function_name"], "test");
742 assert!(invoke_json["args"].is_array());
743 }
744
745 #[test]
746 fn test_invoke_contract_with_source_account_auth_integration() {
747 let spec = OperationSpec::InvokeContract {
749 contract_address: TEST_CONTRACT.to_string(),
750 function_name: "transfer".to_string(),
751 args: vec![], auth: Some(AuthSpec::SourceAccount),
753 };
754
755 let result = Operation::try_from(spec);
757 assert!(result.is_ok());
758
759 let operation = result.unwrap();
760 match operation.body {
761 OperationBody::InvokeHostFunction(ref invoke_op) => {
762 assert_eq!(invoke_op.auth.len(), 1);
764
765 let auth_entry = &invoke_op.auth[0];
767 assert!(matches!(
768 auth_entry.credentials,
769 SorobanCredentials::SourceAccount
770 ));
771
772 match &auth_entry.root_invocation.function {
774 SorobanAuthorizedFunction::ContractFn(invoke_args) => {
775 assert!(matches!(
777 invoke_args.contract_address,
778 ScAddress::Contract(_)
779 ));
780 assert_eq!(invoke_args.function_name.0.as_slice(), b"transfer");
781 }
782 _ => panic!("Expected ContractFn authorization"),
783 }
784 }
785 _ => panic!("Expected InvokeHostFunction operation"),
786 }
787 }
788
789 #[test]
790 fn test_invoke_contract_with_none_auth_gets_default() {
791 let spec = OperationSpec::InvokeContract {
793 contract_address: TEST_CONTRACT.to_string(),
794 function_name: "mint".to_string(),
795 args: vec![],
796 auth: None, };
798
799 let result = Operation::try_from(spec);
801 assert!(result.is_ok());
802
803 let operation = result.unwrap();
804 match operation.body {
805 OperationBody::InvokeHostFunction(ref invoke_op) => {
806 assert_eq!(invoke_op.auth.len(), 1);
808
809 let auth_entry = &invoke_op.auth[0];
811 assert!(matches!(
812 auth_entry.credentials,
813 SorobanCredentials::SourceAccount
814 ));
815
816 match &auth_entry.root_invocation.function {
818 SorobanAuthorizedFunction::ContractFn(invoke_args) => {
819 assert_eq!(invoke_args.function_name.0.as_slice(), b"mint");
820 }
821 _ => panic!("Expected ContractFn authorization"),
822 }
823 }
824 _ => panic!("Expected InvokeHostFunction operation"),
825 }
826 }
827}