1use super::{
43 transaction_applied::{CoinbaseApplied, FeeTransferApplied},
44 *,
45};
46
47#[derive(Clone, Debug)]
48pub struct ZkappCommandPartiallyApplied<L: LedgerNonSnark> {
49 pub command: ZkAppCommand,
50 pub previous_hash: Fp,
51 pub original_first_pass_account_states: Vec<(AccountId, Option<(L::Location, Box<Account>)>)>,
52 pub constraint_constants: ConstraintConstants,
53 pub state_view: ProtocolStateView,
54 pub global_state: GlobalState<L>,
55 pub local_state: LocalStateEnv<L>,
56}
57
58#[derive(Clone, Debug)]
59pub struct FullyApplied<T> {
60 pub previous_hash: Fp,
61 pub applied: T,
62}
63
64#[derive(Clone, Debug)]
65pub enum TransactionPartiallyApplied<L: LedgerNonSnark> {
66 SignedCommand(FullyApplied<SignedCommandApplied>),
67 ZkappCommand(Box<ZkappCommandPartiallyApplied<L>>),
68 FeeTransfer(FullyApplied<FeeTransferApplied>),
69 Coinbase(FullyApplied<CoinbaseApplied>),
70}
71
72impl<L> TransactionPartiallyApplied<L>
73where
74 L: LedgerNonSnark,
75{
76 pub fn command(self) -> Transaction {
77 use Transaction as T;
78
79 match self {
80 Self::SignedCommand(s) => T::Command(UserCommand::SignedCommand(Box::new(
81 s.applied.common.user_command.data,
82 ))),
83 Self::ZkappCommand(z) => T::Command(UserCommand::ZkAppCommand(Box::new(z.command))),
84 Self::FeeTransfer(ft) => T::FeeTransfer(ft.applied.fee_transfer.data),
85 Self::Coinbase(cb) => T::Coinbase(cb.applied.coinbase.data),
86 }
87 }
88}
89
90pub fn apply_transaction_first_pass<L>(
191 constraint_constants: &ConstraintConstants,
192 global_slot: Slot,
193 txn_state_view: &ProtocolStateView,
194 ledger: &mut L,
195 transaction: &Transaction,
196) -> Result<TransactionPartiallyApplied<L>, String>
197where
198 L: LedgerNonSnark,
199{
200 use Transaction::*;
201 use UserCommand::*;
202
203 let previous_hash = ledger.merkle_root();
204 let txn_global_slot = &global_slot;
205
206 match transaction {
207 Command(SignedCommand(cmd)) => apply_user_command(
208 constraint_constants,
209 txn_state_view,
210 txn_global_slot,
211 ledger,
212 cmd,
213 )
214 .map(|applied| {
215 TransactionPartiallyApplied::SignedCommand(FullyApplied {
216 previous_hash,
217 applied,
218 })
219 }),
220 Command(ZkAppCommand(txn)) => apply_zkapp_command_first_pass(
221 constraint_constants,
222 global_slot,
223 txn_state_view,
224 None,
225 None,
226 ledger,
227 txn,
228 )
229 .map(Box::new)
230 .map(TransactionPartiallyApplied::ZkappCommand),
231 FeeTransfer(fee_transfer) => {
232 apply_fee_transfer(constraint_constants, txn_global_slot, ledger, fee_transfer).map(
233 |applied| {
234 TransactionPartiallyApplied::FeeTransfer(FullyApplied {
235 previous_hash,
236 applied,
237 })
238 },
239 )
240 }
241 Coinbase(coinbase) => {
242 apply_coinbase(constraint_constants, txn_global_slot, ledger, coinbase).map(|applied| {
243 TransactionPartiallyApplied::Coinbase(FullyApplied {
244 previous_hash,
245 applied,
246 })
247 })
248 }
249 }
250}
251
252pub fn apply_transaction_second_pass<L>(
318 constraint_constants: &ConstraintConstants,
319 ledger: &mut L,
320 partial_transaction: TransactionPartiallyApplied<L>,
321) -> Result<TransactionApplied, String>
322where
323 L: LedgerNonSnark,
324{
325 use TransactionPartiallyApplied as P;
326
327 match partial_transaction {
328 P::SignedCommand(FullyApplied {
329 previous_hash,
330 applied,
331 }) => Ok(TransactionApplied {
332 previous_hash,
333 varying: Varying::Command(CommandApplied::SignedCommand(Box::new(applied))),
334 }),
335 P::ZkappCommand(partially_applied) => {
336 let previous_hash = partially_applied.previous_hash;
341 let applied =
342 apply_zkapp_command_second_pass(constraint_constants, ledger, *partially_applied)?;
343
344 Ok(TransactionApplied {
345 previous_hash,
346 varying: Varying::Command(CommandApplied::ZkappCommand(Box::new(applied))),
347 })
348 }
349 P::FeeTransfer(FullyApplied {
350 previous_hash,
351 applied,
352 }) => Ok(TransactionApplied {
353 previous_hash,
354 varying: Varying::FeeTransfer(applied),
355 }),
356 P::Coinbase(FullyApplied {
357 previous_hash,
358 applied,
359 }) => Ok(TransactionApplied {
360 previous_hash,
361 varying: Varying::Coinbase(applied),
362 }),
363 }
364}
365
366pub fn apply_transactions<L>(
367 constraint_constants: &ConstraintConstants,
368 global_slot: Slot,
369 txn_state_view: &ProtocolStateView,
370 ledger: &mut L,
371 txns: &[Transaction],
372) -> Result<Vec<TransactionApplied>, String>
373where
374 L: LedgerNonSnark,
375{
376 let first_pass: Vec<_> = txns
377 .iter()
378 .map(|txn| {
379 apply_transaction_first_pass(
380 constraint_constants,
381 global_slot,
382 txn_state_view,
383 ledger,
384 txn,
385 )
386 })
387 .collect::<Result<Vec<TransactionPartiallyApplied<_>>, _>>()?;
388
389 first_pass
390 .into_iter()
391 .map(|partial_transaction| {
392 apply_transaction_second_pass(constraint_constants, ledger, partial_transaction)
393 })
394 .collect()
395}
396
397pub struct FailureCollection {
398 inner: Vec<Vec<TransactionFailure>>,
399}
400
401impl FailureCollection {
403 fn empty() -> Self {
404 Self {
405 inner: Vec::default(),
406 }
407 }
408
409 fn no_failure() -> Vec<TransactionFailure> {
410 vec![]
411 }
412
413 fn single_failure() -> Self {
415 Self {
416 inner: vec![vec![TransactionFailure::UpdateNotPermittedBalance]],
417 }
418 }
419
420 fn update_failed() -> Vec<TransactionFailure> {
421 vec![TransactionFailure::UpdateNotPermittedBalance]
422 }
423
424 fn append_entry(list: Vec<TransactionFailure>, mut s: Self) -> Self {
426 if s.inner.is_empty() {
427 Self { inner: vec![list] }
428 } else {
429 s.inner.insert(1, list);
430 s
431 }
432 }
433
434 fn is_empty(&self) -> bool {
435 self.inner.iter().all(Vec::is_empty)
436 }
437
438 fn take(self) -> Vec<Vec<TransactionFailure>> {
439 self.inner
440 }
441}
442
443pub fn apply_coinbase<L>(
455 constraint_constants: &ConstraintConstants,
456 txn_global_slot: &Slot,
457 ledger: &mut L,
458 coinbase: &Coinbase,
459) -> Result<transaction_applied::CoinbaseApplied, String>
460where
461 L: LedgerIntf,
462{
463 let Coinbase {
464 receiver,
465 amount: coinbase_amount,
466 fee_transfer,
467 } = &coinbase;
468
469 let (
470 receiver_reward,
471 new_accounts1,
472 transferee_update,
473 transferee_timing_prev,
474 failures1,
475 burned_tokens1,
476 ) = match fee_transfer {
477 None => (
478 *coinbase_amount,
479 None,
480 None,
481 None,
482 FailureCollection::empty(),
483 Amount::zero(),
484 ),
485 Some(
486 ft @ CoinbaseFeeTransfer {
487 receiver_pk: transferee,
488 fee,
489 },
490 ) => {
491 assert_ne!(transferee, receiver);
492
493 let transferee_id = ft.receiver();
494 let fee = Amount::of_fee(fee);
495
496 let receiver_reward = coinbase_amount
497 .checked_sub(&fee)
498 .ok_or_else(|| "Coinbase fee transfer too large".to_string())?;
499
500 let (transferee_account, action, can_receive) =
501 has_permission_to_receive(ledger, &transferee_id);
502 let new_accounts = get_new_accounts(action, transferee_id.clone());
503
504 let timing = update_timing_when_no_deduction(txn_global_slot, &transferee_account)?;
505
506 let balance = {
507 let amount = sub_account_creation_fee(constraint_constants, action, fee)?;
508 add_amount(transferee_account.balance, amount)?
509 };
510
511 if can_receive.0 {
512 let (_, mut transferee_account, transferee_location) =
513 ledger.get_or_create(&transferee_id)?;
514
515 transferee_account.balance = balance;
516 transferee_account.timing = timing;
517
518 let timing = transferee_account.timing.clone();
519
520 (
521 receiver_reward,
522 new_accounts,
523 Some((transferee_location, transferee_account)),
524 Some(timing),
525 FailureCollection::append_entry(
526 FailureCollection::no_failure(),
527 FailureCollection::empty(),
528 ),
529 Amount::zero(),
530 )
531 } else {
532 (
533 receiver_reward,
534 None,
535 None,
536 None,
537 FailureCollection::single_failure(),
538 fee,
539 )
540 }
541 }
542 };
543
544 let receiver_id = AccountId::new(receiver.clone(), TokenId::default());
545 let (receiver_account, action2, can_receive) = has_permission_to_receive(ledger, &receiver_id);
546 let new_accounts2 = get_new_accounts(action2, receiver_id.clone());
547
548 let coinbase_receiver_timing = match transferee_timing_prev {
555 None => update_timing_when_no_deduction(txn_global_slot, &receiver_account)?,
556 Some(_) => receiver_account.timing.clone(),
557 };
558
559 let receiver_balance = {
560 let amount = sub_account_creation_fee(constraint_constants, action2, receiver_reward)?;
561 add_amount(receiver_account.balance, amount)?
562 };
563
564 let (failures, burned_tokens2) = if can_receive.0 {
565 let (_action2, mut receiver_account, receiver_location) =
566 ledger.get_or_create(&receiver_id)?;
567
568 receiver_account.balance = receiver_balance;
569 receiver_account.timing = coinbase_receiver_timing;
570
571 ledger.set(&receiver_location, receiver_account);
572
573 (
574 FailureCollection::append_entry(FailureCollection::no_failure(), failures1),
575 Amount::zero(),
576 )
577 } else {
578 (
579 FailureCollection::append_entry(FailureCollection::update_failed(), failures1),
580 receiver_reward,
581 )
582 };
583
584 if let Some((addr, account)) = transferee_update {
585 ledger.set(&addr, account);
586 };
587
588 let burned_tokens = burned_tokens1
589 .checked_add(&burned_tokens2)
590 .ok_or_else(|| "burned tokens overflow".to_string())?;
591
592 let status = if failures.is_empty() {
593 TransactionStatus::Applied
594 } else {
595 TransactionStatus::Failed(failures.take())
596 };
597
598 let new_accounts: Vec<_> = [new_accounts1, new_accounts2]
599 .into_iter()
600 .flatten()
601 .collect();
602
603 Ok(transaction_applied::CoinbaseApplied {
604 coinbase: WithStatus {
605 data: coinbase.clone(),
606 status,
607 },
608 new_accounts,
609 burned_tokens,
610 })
611}
612
613pub fn apply_fee_transfer<L>(
615 constraint_constants: &ConstraintConstants,
616 txn_global_slot: &Slot,
617 ledger: &mut L,
618 fee_transfer: &FeeTransfer,
619) -> Result<transaction_applied::FeeTransferApplied, String>
620where
621 L: LedgerIntf,
622{
623 let (new_accounts, failures, burned_tokens) = process_fee_transfer(
624 ledger,
625 fee_transfer,
626 |action, _, balance, fee| {
627 let amount = {
628 let amount = Amount::of_fee(fee);
629 sub_account_creation_fee(constraint_constants, action, amount)?
630 };
631 add_amount(balance, amount)
632 },
633 |account| update_timing_when_no_deduction(txn_global_slot, account),
634 )?;
635
636 let status = if failures.is_empty() {
637 TransactionStatus::Applied
638 } else {
639 TransactionStatus::Failed(failures.take())
640 };
641
642 Ok(transaction_applied::FeeTransferApplied {
643 fee_transfer: WithStatus {
644 data: fee_transfer.clone(),
645 status,
646 },
647 new_accounts,
648 burned_tokens,
649 })
650}
651
652fn sub_account_creation_fee(
654 constraint_constants: &ConstraintConstants,
655 action: AccountState,
656 amount: Amount,
657) -> Result<Amount, String> {
658 let account_creation_fee = Amount::from_u64(constraint_constants.account_creation_fee);
659
660 match action {
661 AccountState::Added => {
662 if let Some(amount) = amount.checked_sub(&account_creation_fee) {
663 return Ok(amount);
664 }
665 Err(format!(
666 "Error subtracting account creation fee {:?}; transaction amount {:?} insufficient",
667 account_creation_fee, amount
668 ))
669 }
670 AccountState::Existed => Ok(amount),
671 }
672}
673
674fn update_timing_when_no_deduction(
675 txn_global_slot: &Slot,
676 account: &Account,
677) -> Result<Timing, String> {
678 validate_timing(account, Amount::zero(), txn_global_slot)
679}
680
681fn get_new_accounts<T>(action: AccountState, data: T) -> Option<T> {
682 match action {
683 AccountState::Added => Some(data),
684 AccountState::Existed => None,
685 }
686}
687
688pub fn process_fee_transfer<L, FunBalance, FunTiming>(
699 ledger: &mut L,
700 fee_transfer: &FeeTransfer,
701 modify_balance: FunBalance,
702 modify_timing: FunTiming,
703) -> Result<(Vec<AccountId>, FailureCollection, Amount), String>
704where
705 L: LedgerIntf,
706 FunTiming: Fn(&Account) -> Result<Timing, String>,
707 FunBalance: Fn(AccountState, &AccountId, Balance, &Fee) -> Result<Balance, String>,
708{
709 if !fee_transfer.fee_tokens().all(TokenId::is_default) {
710 return Err("Cannot pay fees in non-default tokens.".to_string());
711 }
712
713 match &**fee_transfer {
714 OneOrTwo::One(fee_transfer) => {
715 let account_id = fee_transfer.receiver();
716 let (a, action, can_receive) = has_permission_to_receive(ledger, &account_id);
717
718 let timing = modify_timing(&a)?;
719 let balance = modify_balance(action, &account_id, a.balance, &fee_transfer.fee)?;
720
721 if can_receive.0 {
722 let (_, mut account, loc) = ledger.get_or_create(&account_id)?;
723 let new_accounts = get_new_accounts(action, account_id.clone());
724
725 account.balance = balance;
726 account.timing = timing;
727
728 ledger.set(&loc, account);
729
730 let new_accounts: Vec<_> = new_accounts.into_iter().collect();
731 Ok((new_accounts, FailureCollection::empty(), Amount::zero()))
732 } else {
733 Ok((
734 vec![],
735 FailureCollection::single_failure(),
736 Amount::of_fee(&fee_transfer.fee),
737 ))
738 }
739 }
740 OneOrTwo::Two((fee_transfer1, fee_transfer2)) => {
741 let account_id1 = fee_transfer1.receiver();
742 let (a1, action1, can_receive1) = has_permission_to_receive(ledger, &account_id1);
743
744 let account_id2 = fee_transfer2.receiver();
745
746 if account_id1 == account_id2 {
747 let fee = fee_transfer1
748 .fee
749 .checked_add(&fee_transfer2.fee)
750 .ok_or_else(|| "Overflow".to_string())?;
751
752 let timing = modify_timing(&a1)?;
753 let balance = modify_balance(action1, &account_id1, a1.balance, &fee)?;
754
755 if can_receive1.0 {
756 let (_, mut a1, l1) = ledger.get_or_create(&account_id1)?;
757 let new_accounts1 = get_new_accounts(action1, account_id1);
758
759 a1.balance = balance;
760 a1.timing = timing;
761
762 ledger.set(&l1, a1);
763
764 let new_accounts: Vec<_> = new_accounts1.into_iter().collect();
765 Ok((new_accounts, FailureCollection::empty(), Amount::zero()))
766 } else {
767 Ok((
770 vec![],
771 FailureCollection::append_entry(
772 FailureCollection::update_failed(),
773 FailureCollection::single_failure(),
774 ),
775 Amount::of_fee(&fee),
776 ))
777 }
778 } else {
779 let (a2, action2, can_receive2) = has_permission_to_receive(ledger, &account_id2);
780
781 let balance1 =
782 modify_balance(action1, &account_id1, a1.balance, &fee_transfer1.fee)?;
783
784 let timing2 = modify_timing(&a2)?;
790 let balance2 =
791 modify_balance(action2, &account_id2, a2.balance, &fee_transfer2.fee)?;
792
793 let (new_accounts1, failures, burned_tokens1) = if can_receive1.0 {
794 let (_, mut a1, l1) = ledger.get_or_create(&account_id1)?;
795 let new_accounts1 = get_new_accounts(action1, account_id1);
796
797 a1.balance = balance1;
798 ledger.set(&l1, a1);
799
800 (
801 new_accounts1,
802 FailureCollection::append_entry(
803 FailureCollection::no_failure(),
804 FailureCollection::empty(),
805 ),
806 Amount::zero(),
807 )
808 } else {
809 (
810 None,
811 FailureCollection::single_failure(),
812 Amount::of_fee(&fee_transfer1.fee),
813 )
814 };
815
816 let (new_accounts2, failures, burned_tokens2) = if can_receive2.0 {
817 let (_, mut a2, l2) = ledger.get_or_create(&account_id2)?;
818 let new_accounts2 = get_new_accounts(action2, account_id2);
819
820 a2.balance = balance2;
821 a2.timing = timing2;
822
823 ledger.set(&l2, a2);
824
825 (
826 new_accounts2,
827 FailureCollection::append_entry(FailureCollection::no_failure(), failures),
828 Amount::zero(),
829 )
830 } else {
831 (
832 None,
833 FailureCollection::append_entry(
834 FailureCollection::update_failed(),
835 failures,
836 ),
837 Amount::of_fee(&fee_transfer2.fee),
838 )
839 };
840
841 let burned_tokens = burned_tokens1
842 .checked_add(&burned_tokens2)
843 .ok_or_else(|| "burned tokens overflow".to_string())?;
844
845 let new_accounts: Vec<_> = [new_accounts1, new_accounts2]
846 .into_iter()
847 .flatten()
848 .collect();
849
850 Ok((new_accounts, failures, burned_tokens))
851 }
852 }
853 }
854}
855
856#[derive(Copy, Clone, Debug)]
857pub enum AccountState {
858 Added,
859 Existed,
860}
861
862#[derive(Debug)]
863struct HasPermissionToReceive(bool);
864
865fn has_permission_to_receive<L>(
867 ledger: &mut L,
868 receiver_account_id: &AccountId,
869) -> (Box<Account>, AccountState, HasPermissionToReceive)
870where
871 L: LedgerIntf,
872{
873 use crate::PermissionTo::*;
874 use AccountState::*;
875
876 let init_account = Account::initialize(receiver_account_id);
877
878 match ledger.location_of_account(receiver_account_id) {
879 None => {
880 let perm = init_account.has_permission_to(ControlTag::NoneGiven, Receive);
882 (Box::new(init_account), Added, HasPermissionToReceive(perm))
883 }
884 Some(location) => match ledger.get(&location) {
885 None => panic!("Ledger location with no account"),
886 Some(receiver_account) => {
887 let perm = receiver_account.has_permission_to(ControlTag::NoneGiven, Receive);
888 (receiver_account, Existed, HasPermissionToReceive(perm))
889 }
890 },
891 }
892}
893
894pub fn validate_time(valid_until: &Slot, current_global_slot: &Slot) -> Result<(), String> {
895 if current_global_slot <= valid_until {
896 return Ok(());
897 }
898
899 Err(format!(
900 "Current global slot {:?} greater than transaction expiry slot {:?}",
901 current_global_slot, valid_until
902 ))
903}
904
905pub fn is_timed(a: &Account) -> bool {
906 matches!(&a.timing, Timing::Timed { .. })
907}
908
909pub fn set_with_location<L>(
910 ledger: &mut L,
911 location: &ExistingOrNew<L::Location>,
912 account: Box<Account>,
913) -> Result<(), String>
914where
915 L: LedgerIntf,
916{
917 match location {
918 ExistingOrNew::Existing(location) => {
919 ledger.set(location, account);
920 Ok(())
921 }
922 ExistingOrNew::New => ledger
923 .create_new_account(account.id(), *account)
924 .map_err(|_| "set_with_location".to_string()),
925 }
926}
927
928pub struct Updates<Location> {
929 pub located_accounts: Vec<(ExistingOrNew<Location>, Box<Account>)>,
930 pub applied_body: signed_command_applied::Body,
931}
932
933pub fn compute_updates<L>(
934 constraint_constants: &ConstraintConstants,
935 receiver: AccountId,
936 ledger: &mut L,
937 current_global_slot: &Slot,
938 user_command: &SignedCommand,
939 fee_payer: &AccountId,
940 fee_payer_account: &Account,
941 fee_payer_location: &ExistingOrNew<L::Location>,
942 reject_command: &mut bool,
943) -> Result<Updates<L::Location>, TransactionFailure>
944where
945 L: LedgerIntf,
946{
947 match &user_command.payload.body {
948 signed_command::Body::StakeDelegation(_) => {
949 let (receiver_location, _) = get_with_location(ledger, &receiver).unwrap();
950
951 if let ExistingOrNew::New = receiver_location {
952 return Err(TransactionFailure::ReceiverNotPresent);
953 }
954 if !fee_payer_account.has_permission_to_set_delegate() {
955 return Err(TransactionFailure::UpdateNotPermittedDelegate);
956 }
957
958 let previous_delegate = fee_payer_account.delegate.clone();
959
960 let fee_payer_account = {
963 let timing = timing_error_to_user_command_status(validate_timing(
964 fee_payer_account,
965 Amount::zero(),
966 current_global_slot,
967 ))?;
968
969 Box::new(Account {
970 delegate: Some(receiver.public_key.clone()),
971 timing,
972 ..fee_payer_account.clone()
973 })
974 };
975
976 Ok(Updates {
977 located_accounts: vec![(fee_payer_location.clone(), fee_payer_account)],
978 applied_body: signed_command_applied::Body::StakeDelegation { previous_delegate },
979 })
980 }
981 signed_command::Body::Payment(payment) => {
982 let get_fee_payer_account = || {
983 let balance = fee_payer_account
984 .balance
985 .sub_amount(payment.amount)
986 .ok_or(TransactionFailure::SourceInsufficientBalance)?;
987
988 let timing = timing_error_to_user_command_status(validate_timing(
989 fee_payer_account,
990 payment.amount,
991 current_global_slot,
992 ))?;
993
994 Ok(Box::new(Account {
995 balance,
996 timing,
997 ..fee_payer_account.clone()
998 }))
999 };
1000
1001 let fee_payer_account = match get_fee_payer_account() {
1002 Ok(fee_payer_account) => fee_payer_account,
1003 Err(e) => {
1004 *reject_command = true;
1012 return Err(e);
1013 }
1014 };
1015
1016 let (receiver_location, mut receiver_account) = if fee_payer == &receiver {
1017 (fee_payer_location.clone(), fee_payer_account.clone())
1018 } else {
1019 get_with_location(ledger, &receiver).unwrap()
1020 };
1021
1022 if !fee_payer_account.has_permission_to_send() {
1023 return Err(TransactionFailure::UpdateNotPermittedBalance);
1024 }
1025
1026 if !receiver_account.has_permission_to_receive() {
1027 return Err(TransactionFailure::UpdateNotPermittedBalance);
1028 }
1029
1030 let receiver_amount = match &receiver_location {
1031 ExistingOrNew::Existing(_) => payment.amount,
1032 ExistingOrNew::New => {
1033 match payment
1034 .amount
1035 .checked_sub(&Amount::from_u64(constraint_constants.account_creation_fee))
1036 {
1037 Some(amount) => amount,
1038 None => return Err(TransactionFailure::AmountInsufficientToCreateAccount),
1039 }
1040 }
1041 };
1042
1043 let balance = match receiver_account.balance.add_amount(receiver_amount) {
1044 Some(balance) => balance,
1045 None => return Err(TransactionFailure::Overflow),
1046 };
1047
1048 let new_accounts = match receiver_location {
1049 ExistingOrNew::New => vec![receiver.clone()],
1050 ExistingOrNew::Existing(_) => vec![],
1051 };
1052
1053 receiver_account.balance = balance;
1054
1055 let updated_accounts = if fee_payer == &receiver {
1056 vec![(receiver_location, receiver_account)]
1058 } else {
1059 vec![
1060 (receiver_location, receiver_account),
1061 (fee_payer_location.clone(), fee_payer_account),
1062 ]
1063 };
1064
1065 Ok(Updates {
1066 located_accounts: updated_accounts,
1067 applied_body: signed_command_applied::Body::Payments { new_accounts },
1068 })
1069 }
1070 }
1071}
1072
1073pub fn apply_user_command_unchecked<L>(
1074 constraint_constants: &ConstraintConstants,
1075 _txn_state_view: &ProtocolStateView,
1076 txn_global_slot: &Slot,
1077 ledger: &mut L,
1078 user_command: &SignedCommand,
1079) -> Result<SignedCommandApplied, String>
1080where
1081 L: LedgerIntf,
1082{
1083 let SignedCommand {
1084 payload: _,
1085 signer: signer_pk,
1086 signature: _,
1087 } = &user_command;
1088 let current_global_slot = txn_global_slot;
1089
1090 let valid_until = user_command.valid_until();
1091 validate_time(&valid_until, current_global_slot)?;
1092
1093 let fee_payer = user_command.fee_payer();
1095 let (fee_payer_location, fee_payer_account) =
1096 pay_fee(user_command, signer_pk, ledger, current_global_slot)?;
1097
1098 if !fee_payer_account.has_permission_to_send() {
1099 return Err(TransactionFailure::UpdateNotPermittedBalance.to_string());
1100 }
1101 if !fee_payer_account.has_permission_to_increment_nonce() {
1102 return Err(TransactionFailure::UpdateNotPermittedNonce.to_string());
1103 }
1104
1105 set_with_location(ledger, &fee_payer_location, fee_payer_account.clone())?;
1109
1110 let receiver = user_command.receiver();
1111
1112 let mut reject_command = false;
1113
1114 match compute_updates(
1115 constraint_constants,
1116 receiver,
1117 ledger,
1118 current_global_slot,
1119 user_command,
1120 &fee_payer,
1121 &fee_payer_account,
1122 &fee_payer_location,
1123 &mut reject_command,
1124 ) {
1125 Ok(Updates {
1126 located_accounts,
1127 applied_body,
1128 }) => {
1129 for (location, account) in located_accounts {
1130 set_with_location(ledger, &location, account)?;
1131 }
1132
1133 Ok(SignedCommandApplied {
1134 common: signed_command_applied::Common {
1135 user_command: WithStatus::<SignedCommand> {
1136 data: user_command.clone(),
1137 status: TransactionStatus::Applied,
1138 },
1139 },
1140 body: applied_body,
1141 })
1142 }
1143 Err(failure) if !reject_command => Ok(SignedCommandApplied {
1144 common: signed_command_applied::Common {
1145 user_command: WithStatus::<SignedCommand> {
1146 data: user_command.clone(),
1147 status: TransactionStatus::Failed(vec![vec![failure]]),
1148 },
1149 },
1150 body: signed_command_applied::Body::Failed,
1151 }),
1152 Err(failure) => {
1153 assert!(reject_command);
1156 Err(failure.to_string())
1157 }
1158 }
1159}
1160
1161pub fn apply_user_command<L>(
1162 constraint_constants: &ConstraintConstants,
1163 txn_state_view: &ProtocolStateView,
1164 txn_global_slot: &Slot,
1165 ledger: &mut L,
1166 user_command: &SignedCommand,
1167) -> Result<SignedCommandApplied, String>
1168where
1169 L: LedgerIntf,
1170{
1171 apply_user_command_unchecked(
1172 constraint_constants,
1173 txn_state_view,
1174 txn_global_slot,
1175 ledger,
1176 user_command,
1177 )
1178}
1179
1180pub fn pay_fee<L, Loc>(
1181 user_command: &SignedCommand,
1182 signer_pk: &CompressedPubKey,
1183 ledger: &mut L,
1184 current_global_slot: &Slot,
1185) -> Result<(ExistingOrNew<Loc>, Box<Account>), String>
1186where
1187 L: LedgerIntf<Location = Loc>,
1188{
1189 let nonce = user_command.nonce();
1190 let fee_payer = user_command.fee_payer();
1191 let fee_token = user_command.fee_token();
1192
1193 if &fee_payer.public_key != signer_pk {
1194 return Err("Cannot pay fees from a public key that did not sign the transaction".into());
1195 }
1196
1197 if fee_token != TokenId::default() {
1198 return Err("Cannot create transactions with fee_token different from the default".into());
1199 }
1200
1201 pay_fee_impl(
1202 &user_command.payload,
1203 nonce,
1204 fee_payer,
1205 user_command.fee(),
1206 ledger,
1207 current_global_slot,
1208 )
1209}
1210
1211pub fn pay_fee_impl<L>(
1212 command: &SignedCommandPayload,
1213 nonce: Nonce,
1214 fee_payer: AccountId,
1215 fee: Fee,
1216 ledger: &mut L,
1217 current_global_slot: &Slot,
1218) -> Result<(ExistingOrNew<L::Location>, Box<Account>), String>
1219where
1220 L: LedgerIntf,
1221{
1222 let (location, mut account) = get_with_location(ledger, &fee_payer)?;
1224
1225 if let ExistingOrNew::New = location {
1226 return Err("The fee-payer account does not exist".to_string());
1227 };
1228
1229 let fee = Amount::of_fee(&fee);
1230 let balance = sub_amount(account.balance, fee)?;
1231
1232 validate_nonces(nonce, account.nonce)?;
1233 let timing = validate_timing(&account, fee, current_global_slot)?;
1234
1235 account.balance = balance;
1236 account.nonce = account.nonce.incr(); account.receipt_chain_hash = cons_signed_command_payload(command, account.receipt_chain_hash);
1238 account.timing = timing;
1239
1240 Ok((location, account))
1241}