1use crate::{Env, Signer, TransactionBuilder, error::SorobanHelperError};
33use stellar_strkey::ed25519::PublicKey;
34use stellar_xdr::curr::{
35 AccountEntry, AccountId, DecoratedSignature, Hash, Operation, OperationBody, SetOptionsOp,
36 Signer as XdrSigner, SignerKey, Transaction, TransactionEnvelope, TransactionV1Envelope, VecM,
37};
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub struct AccountSequence(i64);
42
43impl AccountSequence {
44 pub fn new(val: i64) -> Self {
50 AccountSequence(val)
51 }
52
53 pub fn next(&self) -> Self {
55 AccountSequence(self.0 + 1)
56 }
57
58 pub fn increment(self) -> Self {
63 self.next()
64 }
65
66 pub fn value(self) -> i64 {
68 self.0
69 }
70}
71
72#[derive(Debug, Clone, Copy)]
78pub struct AuthorizedCalls(u16);
79
80impl AuthorizedCalls {
81 pub fn new(calls: u16) -> Self {
87 AuthorizedCalls(calls)
88 }
89
90 pub fn can_call(&self) -> bool {
94 self.0 > 0
95 }
96
97 pub fn try_decrement(&mut self) -> Result<(), SorobanHelperError> {
104 if self.can_call() {
105 self.0 -= 1;
106 Ok(())
107 } else {
108 Err(SorobanHelperError::Unauthorized(
109 "Account has reached the max number of authorized calls".to_string(),
110 ))
111 }
112 }
113
114 pub fn value(&self) -> u16 {
116 self.0
117 }
118}
119
120pub struct AccountConfig {
138 pub master_weight: Option<u32>,
140 pub low_threshold: Option<u32>,
142 pub med_threshold: Option<u32>,
144 pub high_threshold: Option<u32>,
146 pub signers: Vec<(PublicKey, u32)>,
148}
149
150impl Default for AccountConfig {
151 fn default() -> Self {
152 Self::new()
153 }
154}
155
156impl AccountConfig {
157 pub fn new() -> Self {
159 Self {
160 master_weight: None,
161 low_threshold: None,
162 med_threshold: None,
163 high_threshold: None,
164 signers: Vec::new(),
165 }
166 }
167
168 pub fn with_master_weight(mut self, weight: u32) -> Self {
175 self.master_weight = Some(weight);
176 self
177 }
178
179 pub fn with_thresholds(mut self, low: u32, med: u32, high: u32) -> Self {
187 self.low_threshold = Some(low);
188 self.med_threshold = Some(med);
189 self.high_threshold = Some(high);
190 self
191 }
192
193 pub fn add_signer(mut self, key: PublicKey, weight: u32) -> Self {
200 self.signers.push((key, weight));
201 self
202 }
203}
204
205#[derive(Clone)]
207pub struct SingleAccount {
208 pub account_id: AccountId,
210 pub signers: Vec<Signer>,
212 pub authorized_calls: AuthorizedCalls,
214}
215
216#[derive(Clone)]
218pub struct MultisigAccount {
219 pub account_id: AccountId,
221 pub signers: Vec<Signer>,
223 pub authorized_calls: AuthorizedCalls,
225}
226
227#[derive(Clone)]
233pub enum Account {
234 KeyPair(SingleAccount),
236 Multisig(MultisigAccount),
238}
239
240impl Account {
241 pub fn single(signer: Signer) -> Self {
251 Self::KeyPair(SingleAccount {
252 account_id: signer.account_id(),
253 signers: vec![signer],
254 authorized_calls: AuthorizedCalls::new(i16::MAX as u16),
255 })
256 }
257
258 pub fn multisig(account_id: AccountId, signers: Vec<Signer>) -> Self {
269 Self::Multisig(MultisigAccount {
270 account_id,
271 signers,
272 authorized_calls: AuthorizedCalls::new(i16::MAX as u16),
273 })
274 }
275
276 pub async fn load(&self, env: &Env) -> Result<AccountEntry, SorobanHelperError> {
286 env.get_account(&self.account_id().to_string()).await
287 }
288
289 pub fn account_id(&self) -> AccountId {
291 match self {
292 Self::KeyPair(account) => account.account_id.clone(),
293 Self::Multisig(account) => account.account_id.clone(),
294 }
295 }
296
297 pub async fn get_sequence(&self, env: &Env) -> Result<AccountSequence, SorobanHelperError> {
307 let entry = self.load(env).await?;
308 Ok(AccountSequence::new(entry.seq_num.0))
309 }
310
311 pub async fn next_sequence(&self, env: &Env) -> Result<AccountSequence, SorobanHelperError> {
323 let current = self.get_sequence(env).await?;
324 Ok(current.next())
325 }
326
327 fn signers(&self) -> &[Signer] {
329 match self {
330 Self::KeyPair(account) => &account.signers,
331 Self::Multisig(account) => &account.signers,
332 }
333 }
334
335 fn authorized_calls(&self) -> AuthorizedCalls {
337 match self {
338 Self::KeyPair(account) => account.authorized_calls,
339 Self::Multisig(account) => account.authorized_calls,
340 }
341 }
342
343 fn authorized_calls_mut(&mut self) -> &mut AuthorizedCalls {
345 match self {
346 Self::KeyPair(account) => &mut account.authorized_calls,
347 Self::Multisig(account) => &mut account.authorized_calls,
348 }
349 }
350
351 pub fn set_authorized_calls(&mut self, authorized_calls: i16) {
357 *self.authorized_calls_mut() = AuthorizedCalls::new(authorized_calls as u16);
358 }
359
360 fn sign_with_tx(
372 tx: &Transaction,
373 network_id: &Hash,
374 signers: &[Signer],
375 ) -> Result<VecM<DecoratedSignature, 20>, SorobanHelperError> {
376 let signatures: Vec<DecoratedSignature> = signers
377 .iter()
378 .map(|signer| signer.sign_transaction(tx, network_id))
379 .collect::<Result<_, _>>()
380 .map_err(|e| SorobanHelperError::XdrEncodingFailed(e.to_string()))?;
381 signatures.try_into().map_err(|_| {
382 SorobanHelperError::XdrEncodingFailed("Failed to convert signatures to XDR".to_string())
383 })
384 }
385
386 pub fn sign_transaction_unsafe(
399 &self,
400 tx: &Transaction,
401 network_id: &Hash,
402 ) -> Result<TransactionEnvelope, SorobanHelperError> {
403 let signatures = Self::sign_with_tx(tx, network_id, self.signers())?;
404 Ok(TransactionEnvelope::Tx(TransactionV1Envelope {
405 tx: tx.clone(),
406 signatures,
407 }))
408 }
409
410 pub fn sign_transaction(
423 &mut self,
424 tx: &Transaction,
425 network_id: &Hash,
426 ) -> Result<TransactionEnvelope, SorobanHelperError> {
427 if !self.authorized_calls().can_call() {
428 return Err(SorobanHelperError::Unauthorized(
429 "Account has reached the max number of authorized calls".to_string(),
430 ));
431 }
432
433 let signatures = Self::sign_with_tx(tx, network_id, self.signers())?;
434 self.authorized_calls_mut().try_decrement()?;
435
436 Ok(TransactionEnvelope::Tx(TransactionV1Envelope {
437 tx: tx.clone(),
438 signatures,
439 }))
440 }
441
442 pub fn sign_transaction_envelope(
453 &mut self,
454 tx_envelope: &TransactionEnvelope,
455 network_id: &Hash,
456 ) -> Result<TransactionEnvelope, SorobanHelperError> {
457 if !self.authorized_calls().can_call() {
458 return Err(SorobanHelperError::Unauthorized(
459 "Account has reached the max number of authorized calls".to_string(),
460 ));
461 }
462
463 let tx_v1 = match tx_envelope {
464 TransactionEnvelope::Tx(tx_v1) => tx_v1,
465 _ => {
466 return Err(SorobanHelperError::XdrEncodingFailed(
467 "Invalid transaction envelope".to_string(),
468 ));
469 }
470 };
471
472 let prev_signatures = tx_v1.signatures.clone();
473 let new_signatures = Self::sign_with_tx(&tx_v1.tx, network_id, self.signers())?;
474
475 let mut all_signatures: Vec<DecoratedSignature> = prev_signatures.to_vec();
476 all_signatures.extend(new_signatures.to_vec());
477 let signatures: VecM<DecoratedSignature, 20> = all_signatures.try_into().map_err(|_| {
478 SorobanHelperError::XdrEncodingFailed("Too many signatures for XDR vector".to_string())
479 })?;
480
481 self.authorized_calls_mut().try_decrement()?;
482
483 Ok(TransactionEnvelope::Tx(TransactionV1Envelope {
484 tx: tx_v1.tx.clone(),
485 signatures,
486 }))
487 }
488
489 pub async fn configure(
502 mut self,
503 env: &Env,
504 config: AccountConfig,
505 ) -> Result<TransactionEnvelope, SorobanHelperError> {
506 let mut tx = TransactionBuilder::new(&self, env);
507
508 for (public_key, weight) in config.signers {
510 let signer_key = SignerKey::Ed25519(public_key.0.into());
511 tx = tx.add_operation(Operation {
512 source_account: None,
513 body: OperationBody::SetOptions(SetOptionsOp {
514 inflation_dest: None,
515 clear_flags: None,
516 set_flags: None,
517 master_weight: None,
518 low_threshold: None,
519 med_threshold: None,
520 high_threshold: None,
521 home_domain: None,
522 signer: Some(XdrSigner {
523 key: signer_key,
524 weight,
525 }),
526 }),
527 });
528 }
529
530 if config.master_weight.is_some()
532 || config.low_threshold.is_some()
533 || config.med_threshold.is_some()
534 || config.high_threshold.is_some()
535 {
536 tx = tx.add_operation(Operation {
537 source_account: None,
538 body: OperationBody::SetOptions(SetOptionsOp {
539 inflation_dest: None,
540 clear_flags: None,
541 set_flags: None,
542 master_weight: config.master_weight,
543 low_threshold: config.low_threshold,
544 med_threshold: config.med_threshold,
545 high_threshold: config.high_threshold,
546 home_domain: None,
547 signer: None,
548 }),
549 });
550 }
551
552 let tx = tx
553 .simulate_and_build(env, &self)
554 .await
555 .map_err(|e| SorobanHelperError::TransactionBuildFailed(e.to_string()))?;
556
557 self.sign_transaction(&tx, &env.network_id())
558 }
559}
560
561#[cfg(test)]
562mod test {
563 use stellar_xdr::curr::{OperationBody, Signer as XdrSigner, SignerKey, TransactionEnvelope};
564
565 use crate::account::AuthorizedCalls;
566 use crate::mock::{all_signers, mock_env, mock_signer1, mock_signer3};
567 use crate::{Account, AccountConfig, TransactionBuilder};
568
569 #[tokio::test]
570 async fn load_account() {
571 let env = mock_env(None, None, None);
572
573 let account = Account::single(mock_signer1());
575
576 let entry = account.load(&env).await;
578
579 let expected_account_id = mock_signer1().account_id().0.to_string();
580 let res_account_id = entry.unwrap().account_id.0.to_string();
581
582 assert_eq!(expected_account_id, res_account_id);
583 }
584
585 #[tokio::test]
586 async fn multisig() {
587 let env = mock_env(None, None, None);
588
589 let account = Account::multisig(mock_signer3().account_id(), all_signers());
591
592 let entry = account.load(&env).await;
594
595 let expected_account_id = mock_signer3().account_id().0.to_string();
596 let res_account_id = entry.unwrap().account_id.0.to_string();
597
598 let signers = account.signers();
599
600 for (i, sig) in signers.iter().enumerate() {
601 assert_eq!(sig.account_id(), all_signers()[i].account_id())
602 }
603
604 assert_eq!(expected_account_id, res_account_id);
605 }
606
607 #[tokio::test]
608 async fn sign_transaction() {
609 let env = mock_env(None, None, None);
610
611 let mut account = Account::single(mock_signer1());
613
614 let tx = TransactionBuilder::new(&account, &env)
616 .build()
617 .await
618 .unwrap();
619
620 account.set_authorized_calls(1);
621
622 let signed_tx = account.sign_transaction(&tx, &env.network_id());
623
624 assert!(signed_tx.is_ok());
625 }
626
627 #[tokio::test]
628 async fn sign_transaction_unsafe() {
629 let env = mock_env(None, None, None);
630
631 let mut account = Account::single(mock_signer1());
633
634 let tx = TransactionBuilder::new(&account, &env)
636 .build()
637 .await
638 .unwrap();
639
640 account.set_authorized_calls(0);
642
643 let signed_tx = account.sign_transaction_unsafe(&tx, &env.network_id());
645
646 assert!(signed_tx.is_ok());
647 }
648
649 #[test]
650 fn test_authorized_calls_decrement() {
651 let mut auth = AuthorizedCalls::new(2);
652 assert!(auth.can_call());
653 assert!(auth.try_decrement().is_ok());
654 assert!(auth.try_decrement().is_ok());
655 assert!(auth.try_decrement().is_err());
656 }
657
658 #[test]
659 fn test_authorized_calls_value() {
660 let auth_zero = AuthorizedCalls::new(0);
661 assert_eq!(auth_zero.value(), 0);
662 assert!(!auth_zero.can_call());
663
664 let auth_with_calls = AuthorizedCalls::new(42);
665 assert_eq!(auth_with_calls.value(), 42);
666 assert!(auth_with_calls.can_call());
667
668 let mut auth_decrement = AuthorizedCalls::new(5);
669 assert_eq!(auth_decrement.value(), 5);
670
671 assert!(auth_decrement.try_decrement().is_ok());
672 assert_eq!(auth_decrement.value(), 4);
673
674 assert!(auth_decrement.try_decrement().is_ok());
675 assert_eq!(auth_decrement.value(), 3);
676 }
677
678 #[tokio::test]
679 async fn test_next_sequence() {
680 let env = mock_env(None, None, None);
681 let account = Account::single(mock_signer1());
682
683 let current_seq = account.get_sequence(&env).await.unwrap();
684
685 let next_seq = account.next_sequence(&env).await.unwrap();
686 assert_eq!(next_seq.value(), current_seq.value() + 1);
687
688 let second_current = account.get_sequence(&env).await.unwrap();
689 let second_next = account.next_sequence(&env).await.unwrap();
690 assert_eq!(second_current.value(), current_seq.value());
691
692 assert_eq!(second_next.value(), second_current.value() + 1);
693 }
694
695 #[tokio::test]
696 async fn test_configure() {
697 let env = mock_env(None, None, None);
698
699 let account = Account::single(mock_signer1());
700 let config = AccountConfig::new()
701 .with_master_weight(10)
702 .with_thresholds(1, 2, 3)
703 .add_signer(mock_signer3().public_key(), 5);
704
705 let tx = account.configure(&env, config).await;
706
707 if let TransactionEnvelope::Tx(tx_env) = tx.unwrap() {
708 if let OperationBody::SetOptions(op) = &tx_env.tx.operations[0].body {
709 assert_eq!(
710 op.signer,
711 Some(XdrSigner {
712 key: SignerKey::Ed25519(mock_signer3().public_key().0.into()),
713 weight: 5
714 })
715 );
716 }
717
718 if let OperationBody::SetOptions(op) = &tx_env.tx.operations[1].body {
719 assert_eq!(op.master_weight, Some(10));
720 assert_eq!(op.low_threshold, Some(1));
721 assert_eq!(op.med_threshold, Some(2));
722 assert_eq!(op.high_threshold, Some(3));
723 }
724 }
725 }
726
727 #[tokio::test]
728 async fn test_sign_transaction_envelope() {
729 let env = mock_env(None, None, None);
730
731 let mut first_account = Account::single(mock_signer1());
732 let mut second_account = Account::single(mock_signer3());
733
734 first_account.set_authorized_calls(1);
735 second_account.set_authorized_calls(1);
736
737 let tx = TransactionBuilder::new(&first_account, &env)
738 .build()
739 .await
740 .unwrap();
741
742 let first_signed_envelope = first_account
743 .sign_transaction(&tx, &env.network_id())
744 .unwrap();
745
746 let first_envelope_signatures = match &first_signed_envelope {
747 TransactionEnvelope::Tx(tx_v1) => &tx_v1.signatures,
748 _ => &[][..], };
750 assert_eq!(first_envelope_signatures.len(), 1); let final_envelope = second_account
753 .sign_transaction_envelope(&first_signed_envelope, &env.network_id())
754 .unwrap();
755
756 let final_signatures = match &final_envelope {
757 TransactionEnvelope::Tx(tx_v1) => &tx_v1.signatures,
758 _ => &[][..], };
760 assert_eq!(final_signatures.len(), 2); let first_public_key = mock_signer1().public_key();
763 let second_public_key = mock_signer3().public_key();
764 assert_eq!(final_signatures[0].hint.0, &first_public_key.0[28..32]); assert_eq!(final_signatures[1].hint.0, &second_public_key.0[28..32]); assert_eq!(second_account.authorized_calls().value(), 0); }
769}