1use std::{
2 collections::{BTreeMap, HashMap, HashSet},
3 fmt::Display,
4};
5
6use ark_ff::Zero;
7use itertools::{FoldWhile, Itertools};
8use mina_core::constants::ConstraintConstants;
9use mina_hasher::{Fp, Hashable, ROInput};
10use mina_macros::SerdeYojsonEnum;
11use mina_p2p_messages::{
12 bigint::InvalidBigInt,
13 binprot,
14 v2::{MinaBaseUserCommandStableV2, MinaTransactionTransactionStableV2},
15};
16use mina_signer::{CompressedPubKey, NetworkId};
17use poseidon::hash::{
18 hash_noinputs, hash_with_kimchi,
19 params::{CODA_RECEIPT_UC, MINA_ZKAPP_MEMO},
20 Inputs,
21};
22
23use crate::{
24 proofs::witness::Witness,
25 scan_state::transaction_logic::{
26 transaction_applied::{CommandApplied, Varying},
27 transaction_partially_applied::FullyApplied,
28 zkapp_command::MaybeWithStatus,
29 },
30 sparse_ledger::{LedgerIntf, SparseLedger},
31 zkapps,
32 zkapps::non_snark::{LedgerNonSnark, ZkappNonSnark},
33 Account, AccountId, AccountIdOrderable, AppendToInputs, BaseLedger, ControlTag,
34 ReceiptChainHash, Timing, TokenId, VerificationKeyWire,
35};
36
37use self::{
38 local_state::{CallStack, LocalStateEnv, StackFrame},
39 protocol_state::{GlobalState, ProtocolStateView},
40 signed_command::{SignedCommand, SignedCommandPayload},
41 transaction_applied::{
42 signed_command_applied::{self, SignedCommandApplied},
43 TransactionApplied, ZkappCommandApplied,
44 },
45 transaction_union_payload::TransactionUnionPayload,
46 zkapp_command::{AccessedOrNot, AccountUpdate, WithHash, ZkAppCommand},
47};
48
49use super::{
50 currency::{Amount, Balance, Fee, Index, Length, Magnitude, Nonce, Signed, Slot, SlotSpan},
51 fee_excess::FeeExcess,
52 fee_rate::FeeRate,
53 scan_state::transaction_snark::OneOrTwo,
54};
55use crate::zkapps::zkapp_logic::ZkAppCommandElt;
56
57#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
59pub enum TransactionFailure {
60 Predicate,
61 SourceNotPresent,
62 ReceiverNotPresent,
63 AmountInsufficientToCreateAccount,
64 CannotPayCreationFeeInToken,
65 SourceInsufficientBalance,
66 SourceMinimumBalanceViolation,
67 ReceiverAlreadyExists,
68 TokenOwnerNotCaller,
69 Overflow,
70 GlobalExcessOverflow,
71 LocalExcessOverflow,
72 LocalSupplyIncreaseOverflow,
73 GlobalSupplyIncreaseOverflow,
74 SignedCommandOnZkappAccount,
75 ZkappAccountNotPresent,
76 UpdateNotPermittedBalance,
77 UpdateNotPermittedAccess,
78 UpdateNotPermittedTiming,
79 UpdateNotPermittedDelegate,
80 UpdateNotPermittedAppState,
81 UpdateNotPermittedVerificationKey,
82 UpdateNotPermittedActionState,
83 UpdateNotPermittedZkappUri,
84 UpdateNotPermittedTokenSymbol,
85 UpdateNotPermittedPermissions,
86 UpdateNotPermittedNonce,
87 UpdateNotPermittedVotingFor,
88 ZkappCommandReplayCheckFailed,
89 FeePayerNonceMustIncrease,
90 FeePayerMustBeSigned,
91 AccountBalancePreconditionUnsatisfied,
92 AccountNoncePreconditionUnsatisfied,
93 AccountReceiptChainHashPreconditionUnsatisfied,
94 AccountDelegatePreconditionUnsatisfied,
95 AccountActionStatePreconditionUnsatisfied,
96 AccountAppStatePreconditionUnsatisfied(u64),
97 AccountProvedStatePreconditionUnsatisfied,
98 AccountIsNewPreconditionUnsatisfied,
99 ProtocolStatePreconditionUnsatisfied,
100 UnexpectedVerificationKeyHash,
101 ValidWhilePreconditionUnsatisfied,
102 IncorrectNonce,
103 InvalidFeeExcess,
104 Cancelled,
105}
106
107impl Display for TransactionFailure {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 let message = match self {
110 Self::Predicate => "Predicate",
111 Self::SourceNotPresent => "Source_not_present",
112 Self::ReceiverNotPresent => "Receiver_not_present",
113 Self::AmountInsufficientToCreateAccount => "Amount_insufficient_to_create_account",
114 Self::CannotPayCreationFeeInToken => "Cannot_pay_creation_fee_in_token",
115 Self::SourceInsufficientBalance => "Source_insufficient_balance",
116 Self::SourceMinimumBalanceViolation => "Source_minimum_balance_violation",
117 Self::ReceiverAlreadyExists => "Receiver_already_exists",
118 Self::TokenOwnerNotCaller => "Token_owner_not_caller",
119 Self::Overflow => "Overflow",
120 Self::GlobalExcessOverflow => "Global_excess_overflow",
121 Self::LocalExcessOverflow => "Local_excess_overflow",
122 Self::LocalSupplyIncreaseOverflow => "Local_supply_increase_overflow",
123 Self::GlobalSupplyIncreaseOverflow => "Global_supply_increase_overflow",
124 Self::SignedCommandOnZkappAccount => "Signed_command_on_zkapp_account",
125 Self::ZkappAccountNotPresent => "Zkapp_account_not_present",
126 Self::UpdateNotPermittedBalance => "Update_not_permitted_balance",
127 Self::UpdateNotPermittedAccess => "Update_not_permitted_access",
128 Self::UpdateNotPermittedTiming => "Update_not_permitted_timing",
129 Self::UpdateNotPermittedDelegate => "update_not_permitted_delegate",
130 Self::UpdateNotPermittedAppState => "Update_not_permitted_app_state",
131 Self::UpdateNotPermittedVerificationKey => "Update_not_permitted_verification_key",
132 Self::UpdateNotPermittedActionState => "Update_not_permitted_action_state",
133 Self::UpdateNotPermittedZkappUri => "Update_not_permitted_zkapp_uri",
134 Self::UpdateNotPermittedTokenSymbol => "Update_not_permitted_token_symbol",
135 Self::UpdateNotPermittedPermissions => "Update_not_permitted_permissions",
136 Self::UpdateNotPermittedNonce => "Update_not_permitted_nonce",
137 Self::UpdateNotPermittedVotingFor => "Update_not_permitted_voting_for",
138 Self::ZkappCommandReplayCheckFailed => "Zkapp_command_replay_check_failed",
139 Self::FeePayerNonceMustIncrease => "Fee_payer_nonce_must_increase",
140 Self::FeePayerMustBeSigned => "Fee_payer_must_be_signed",
141 Self::AccountBalancePreconditionUnsatisfied => {
142 "Account_balance_precondition_unsatisfied"
143 }
144 Self::AccountNoncePreconditionUnsatisfied => "Account_nonce_precondition_unsatisfied",
145 Self::AccountReceiptChainHashPreconditionUnsatisfied => {
146 "Account_receipt_chain_hash_precondition_unsatisfied"
147 }
148 Self::AccountDelegatePreconditionUnsatisfied => {
149 "Account_delegate_precondition_unsatisfied"
150 }
151 Self::AccountActionStatePreconditionUnsatisfied => {
152 "Account_action_state_precondition_unsatisfied"
153 }
154 Self::AccountAppStatePreconditionUnsatisfied(i) => {
155 return write!(f, "Account_app_state_{}_precondition_unsatisfied", i);
156 }
157 Self::AccountProvedStatePreconditionUnsatisfied => {
158 "Account_proved_state_precondition_unsatisfied"
159 }
160 Self::AccountIsNewPreconditionUnsatisfied => "Account_is_new_precondition_unsatisfied",
161 Self::ProtocolStatePreconditionUnsatisfied => "Protocol_state_precondition_unsatisfied",
162 Self::IncorrectNonce => "Incorrect_nonce",
163 Self::InvalidFeeExcess => "Invalid_fee_excess",
164 Self::Cancelled => "Cancelled",
165 Self::UnexpectedVerificationKeyHash => "Unexpected_verification_key_hash",
166 Self::ValidWhilePreconditionUnsatisfied => "Valid_while_precondition_unsatisfied",
167 };
168
169 write!(f, "{}", message)
170 }
171}
172
173#[derive(SerdeYojsonEnum, Debug, Clone, PartialEq, Eq)]
175pub enum TransactionStatus {
176 Applied,
177 Failed(Vec<Vec<TransactionFailure>>),
178}
179
180impl TransactionStatus {
181 pub fn is_applied(&self) -> bool {
182 matches!(self, Self::Applied)
183 }
184 pub fn is_failed(&self) -> bool {
185 matches!(self, Self::Failed(_))
186 }
187}
188
189#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
191pub struct WithStatus<T> {
192 pub data: T,
193 pub status: TransactionStatus,
194}
195
196impl<T> WithStatus<T> {
197 pub fn applied(data: T) -> Self {
198 Self {
199 data,
200 status: TransactionStatus::Applied,
201 }
202 }
203
204 pub fn failed(data: T, failures: Vec<Vec<TransactionFailure>>) -> Self {
205 Self {
206 data,
207 status: TransactionStatus::Failed(failures),
208 }
209 }
210
211 pub fn map<F, R>(&self, fun: F) -> WithStatus<R>
212 where
213 F: Fn(&T) -> R,
214 {
215 WithStatus {
216 data: fun(&self.data),
217 status: self.status.clone(),
218 }
219 }
220
221 pub fn into_map<F, R>(self, fun: F) -> WithStatus<R>
222 where
223 F: Fn(T) -> R,
224 {
225 WithStatus {
226 data: fun(self.data),
227 status: self.status,
228 }
229 }
230}
231
232pub trait GenericCommand {
233 fn fee(&self) -> Fee;
234 fn forget(&self) -> UserCommand;
235}
236
237pub trait GenericTransaction: Sized {
238 fn is_fee_transfer(&self) -> bool;
239 fn is_coinbase(&self) -> bool;
240 fn is_command(&self) -> bool;
241}
242
243impl<T> GenericCommand for WithStatus<T>
244where
245 T: GenericCommand,
246{
247 fn fee(&self) -> Fee {
248 self.data.fee()
249 }
250
251 fn forget(&self) -> UserCommand {
252 self.data.forget()
253 }
254}
255
256pub mod valid {
257 use super::*;
258
259 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
260 pub struct VerificationKeyHash(pub Fp);
261
262 pub type SignedCommand = super::signed_command::SignedCommand;
263
264 use serde::{Deserialize, Serialize};
265
266 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
267 #[serde(into = "MinaBaseUserCommandStableV2")]
268 #[serde(try_from = "MinaBaseUserCommandStableV2")]
269 pub enum UserCommand {
270 SignedCommand(Box<SignedCommand>),
271 ZkAppCommand(Box<super::zkapp_command::valid::ZkAppCommand>),
272 }
273
274 impl UserCommand {
275 pub fn forget_check(&self) -> super::UserCommand {
277 match self {
278 UserCommand::SignedCommand(cmd) => super::UserCommand::SignedCommand(cmd.clone()),
279 UserCommand::ZkAppCommand(cmd) => {
280 super::UserCommand::ZkAppCommand(Box::new(cmd.zkapp_command.clone()))
281 }
282 }
283 }
284
285 pub fn fee_payer(&self) -> AccountId {
286 match self {
287 UserCommand::SignedCommand(cmd) => cmd.fee_payer(),
288 UserCommand::ZkAppCommand(cmd) => cmd.zkapp_command.fee_payer(),
289 }
290 }
291
292 pub fn nonce(&self) -> Option<Nonce> {
293 match self {
294 UserCommand::SignedCommand(cmd) => Some(cmd.nonce()),
295 UserCommand::ZkAppCommand(_) => None,
296 }
297 }
298 }
299
300 impl GenericCommand for UserCommand {
301 fn fee(&self) -> Fee {
302 match self {
303 UserCommand::SignedCommand(cmd) => cmd.fee(),
304 UserCommand::ZkAppCommand(cmd) => cmd.zkapp_command.fee(),
305 }
306 }
307
308 fn forget(&self) -> super::UserCommand {
309 match self {
310 UserCommand::SignedCommand(cmd) => super::UserCommand::SignedCommand(cmd.clone()),
311 UserCommand::ZkAppCommand(cmd) => {
312 super::UserCommand::ZkAppCommand(Box::new(cmd.zkapp_command.clone()))
313 }
314 }
315 }
316 }
317
318 impl GenericTransaction for Transaction {
319 fn is_fee_transfer(&self) -> bool {
320 matches!(self, Transaction::FeeTransfer(_))
321 }
322 fn is_coinbase(&self) -> bool {
323 matches!(self, Transaction::Coinbase(_))
324 }
325 fn is_command(&self) -> bool {
326 matches!(self, Transaction::Command(_))
327 }
328 }
329
330 #[derive(Debug, derive_more::From)]
331 pub enum Transaction {
332 Command(UserCommand),
333 FeeTransfer(super::FeeTransfer),
334 Coinbase(super::Coinbase),
335 }
336
337 impl Transaction {
338 pub fn forget(&self) -> super::Transaction {
340 match self {
341 Transaction::Command(cmd) => super::Transaction::Command(cmd.forget_check()),
342 Transaction::FeeTransfer(ft) => super::Transaction::FeeTransfer(ft.clone()),
343 Transaction::Coinbase(cb) => super::Transaction::Coinbase(cb.clone()),
344 }
345 }
346 }
347}
348
349#[derive(Debug, Clone, PartialEq)]
351pub struct SingleFeeTransfer {
352 pub receiver_pk: CompressedPubKey,
353 pub fee: Fee,
354 pub fee_token: TokenId,
355}
356
357impl SingleFeeTransfer {
358 pub fn receiver(&self) -> AccountId {
359 AccountId {
360 public_key: self.receiver_pk.clone(),
361 token_id: self.fee_token.clone(),
362 }
363 }
364
365 pub fn create(receiver_pk: CompressedPubKey, fee: Fee, fee_token: TokenId) -> Self {
366 Self {
367 receiver_pk,
368 fee,
369 fee_token,
370 }
371 }
372}
373
374#[derive(Debug, Clone, PartialEq)]
376pub struct FeeTransfer(pub(super) OneOrTwo<SingleFeeTransfer>);
377
378impl std::ops::Deref for FeeTransfer {
379 type Target = OneOrTwo<SingleFeeTransfer>;
380
381 fn deref(&self) -> &Self::Target {
382 &self.0
383 }
384}
385
386impl FeeTransfer {
387 pub fn fee_tokens(&self) -> impl Iterator<Item = &TokenId> {
388 self.0.iter().map(|fee_transfer| &fee_transfer.fee_token)
389 }
390
391 pub fn receiver_pks(&self) -> impl Iterator<Item = &CompressedPubKey> {
392 self.0.iter().map(|fee_transfer| &fee_transfer.receiver_pk)
393 }
394
395 pub fn receivers(&self) -> impl Iterator<Item = AccountId> + '_ {
396 self.0.iter().map(|fee_transfer| AccountId {
397 public_key: fee_transfer.receiver_pk.clone(),
398 token_id: fee_transfer.fee_token.clone(),
399 })
400 }
401
402 pub fn fee_excess(&self) -> Result<FeeExcess, String> {
404 let one_or_two = self.0.map(|SingleFeeTransfer { fee, fee_token, .. }| {
405 (fee_token.clone(), Signed::<Fee>::of_unsigned(*fee).negate())
406 });
407 FeeExcess::of_one_or_two(one_or_two)
408 }
409
410 pub fn of_singles(singles: OneOrTwo<SingleFeeTransfer>) -> Result<Self, String> {
412 match singles {
413 OneOrTwo::One(a) => Ok(Self(OneOrTwo::One(a))),
414 OneOrTwo::Two((one, two)) => {
415 if one.fee_token == two.fee_token {
416 Ok(Self(OneOrTwo::Two((one, two))))
417 } else {
418 Err(format!(
421 "Cannot combine single fee transfers with incompatible tokens: {:?} <> {:?}",
422 one, two
423 ))
424 }
425 }
426 }
427 }
428}
429
430#[derive(Debug, Clone, PartialEq)]
431pub struct CoinbaseFeeTransfer {
432 pub receiver_pk: CompressedPubKey,
433 pub fee: Fee,
434}
435
436impl CoinbaseFeeTransfer {
437 pub fn create(receiver_pk: CompressedPubKey, fee: Fee) -> Self {
438 Self { receiver_pk, fee }
439 }
440
441 pub fn receiver(&self) -> AccountId {
442 AccountId {
443 public_key: self.receiver_pk.clone(),
444 token_id: TokenId::default(),
445 }
446 }
447}
448
449#[derive(Debug, Clone, PartialEq)]
451pub struct Coinbase {
452 pub receiver: CompressedPubKey,
453 pub amount: Amount,
454 pub fee_transfer: Option<CoinbaseFeeTransfer>,
455}
456
457impl Coinbase {
458 fn is_valid(&self) -> bool {
459 match &self.fee_transfer {
460 None => true,
461 Some(CoinbaseFeeTransfer { fee, .. }) => Amount::of_fee(fee) <= self.amount,
462 }
463 }
464
465 pub fn create(
466 amount: Amount,
467 receiver: CompressedPubKey,
468 fee_transfer: Option<CoinbaseFeeTransfer>,
469 ) -> Result<Coinbase, String> {
470 let mut this = Self {
471 receiver: receiver.clone(),
472 amount,
473 fee_transfer,
474 };
475
476 if this.is_valid() {
477 let adjusted_fee_transfer = this.fee_transfer.as_ref().and_then(|ft| {
478 if receiver != ft.receiver_pk {
479 Some(ft.clone())
480 } else {
481 None
482 }
483 });
484 this.fee_transfer = adjusted_fee_transfer;
485 Ok(this)
486 } else {
487 Err("Coinbase.create: invalid coinbase".to_string())
488 }
489 }
490
491 fn expected_supply_increase(&self) -> Result<Amount, String> {
493 let Self {
494 amount,
495 fee_transfer,
496 ..
497 } = self;
498
499 match fee_transfer {
500 None => Ok(*amount),
501 Some(CoinbaseFeeTransfer { fee, .. }) => amount
502 .checked_sub(&Amount::of_fee(fee))
503 .map(|_| *amount)
505 .ok_or_else(|| "Coinbase underflow".to_string()),
506 }
507 }
508
509 pub fn fee_excess(&self) -> Result<FeeExcess, String> {
510 self.expected_supply_increase().map(|_| FeeExcess::empty())
511 }
512
513 pub fn receiver(&self) -> AccountId {
515 AccountId::new(self.receiver.clone(), TokenId::default())
516 }
517
518 pub fn account_access_statuses(
520 &self,
521 status: &TransactionStatus,
522 ) -> Vec<(AccountId, zkapp_command::AccessedOrNot)> {
523 let access_status = match status {
524 TransactionStatus::Applied => zkapp_command::AccessedOrNot::Accessed,
525 TransactionStatus::Failed(_) => zkapp_command::AccessedOrNot::NotAccessed,
526 };
527
528 let mut ids = Vec::with_capacity(2);
529
530 if let Some(fee_transfer) = self.fee_transfer.as_ref() {
531 ids.push((fee_transfer.receiver(), access_status.clone()));
532 };
533
534 ids.push((self.receiver(), access_status));
535
536 ids
537 }
538
539 pub fn accounts_referenced(&self) -> Vec<AccountId> {
541 self.account_access_statuses(&TransactionStatus::Applied)
542 .into_iter()
543 .map(|(id, _status)| id)
544 .collect()
545 }
546}
547
548#[derive(Clone, PartialEq)]
553pub struct Memo(pub [u8; 34]);
554
555impl std::fmt::Debug for Memo {
556 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
557 use crate::staged_ledger::hash::OCamlString;
558
559 f.write_fmt(format_args!("\"{}\"", self.0.to_ocaml_str()))
563 }
564}
565
566impl std::str::FromStr for Memo {
567 type Err = ();
568
569 fn from_str(s: &str) -> Result<Self, Self::Err> {
570 let length = std::cmp::min(s.len(), Self::DIGEST_LENGTH) as u8;
571 let mut memo: [u8; Self::MEMO_LENGTH] = std::array::from_fn(|i| (i == 0) as u8);
572 memo[Self::TAG_INDEX] = Self::BYTES_TAG;
573 memo[Self::LENGTH_INDEX] = length;
574 let padded = format!("{s:\0<32}");
575 memo[2..].copy_from_slice(
576 &padded.as_bytes()[..std::cmp::min(padded.len(), Self::DIGEST_LENGTH)],
577 );
578 Ok(Memo(memo))
579 }
580}
581
582impl std::fmt::Display for Memo {
583 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
584 if self.0[0] != Self::BYTES_TAG {
585 return Err(std::fmt::Error);
586 }
587
588 let length = self.0[1] as usize;
589 let memo_slice = &self.0[2..2 + length];
590 let memo_str = String::from_utf8_lossy(memo_slice).to_string();
591 let trimmed = memo_str.trim_end_matches('\0').to_string();
592
593 write!(f, "{trimmed}")
594 }
595}
596
597impl Memo {
598 const TAG_INDEX: usize = 0;
599 const LENGTH_INDEX: usize = 1;
600
601 const DIGEST_TAG: u8 = 0x00;
602 const BYTES_TAG: u8 = 0x01;
603
604 const DIGEST_LENGTH: usize = 32; const DIGEST_LENGTH_BYTE: u8 = Self::DIGEST_LENGTH as u8;
606
607 const MEMO_LENGTH: usize = Self::DIGEST_LENGTH + 2;
609
610 const MAX_INPUT_LENGTH: usize = Self::DIGEST_LENGTH;
611
612 const MAX_DIGESTIBLE_STRING_LENGTH: usize = 1000;
613
614 pub fn to_bits(&self) -> [bool; std::mem::size_of::<Self>() * 8] {
615 use crate::proofs::transaction::legacy_input::BitsIterator;
616
617 const NBYTES: usize = 34;
618 const NBITS: usize = NBYTES * 8;
619 assert_eq!(std::mem::size_of::<Self>(), NBYTES);
620
621 let mut iter = BitsIterator {
622 index: 0,
623 number: self.0,
624 }
625 .take(NBITS);
626 std::array::from_fn(|_| iter.next().unwrap())
627 }
628
629 pub fn hash(&self) -> Fp {
630 use poseidon::hash::{hash_with_kimchi, legacy};
631
632 let mut inputs = legacy::Inputs::new();
634 inputs.append_bytes(&self.0);
635 hash_with_kimchi(&MINA_ZKAPP_MEMO, &inputs.to_fields())
636 }
637
638 pub fn as_slice(&self) -> &[u8] {
639 self.0.as_slice()
640 }
641
642 pub fn dummy() -> Self {
644 Self([0; 34])
646 }
647
648 pub fn empty() -> Self {
649 let mut array = [0; 34];
650 array[0] = 1;
651 Self(array)
652 }
653
654 #[cfg(test)]
657 pub fn from_ocaml_str(s: &str) -> Self {
658 use crate::staged_ledger::hash::OCamlString;
659
660 Self(<[u8; 34]>::from_ocaml_str(s))
661 }
662
663 pub fn with_number(number: usize) -> Self {
664 let s = format!("{:034}", number);
665 assert_eq!(s.len(), 34);
666 Self(s.into_bytes().try_into().unwrap())
667 }
668
669 fn create_by_digesting_string_exn(s: &str) -> Self {
671 if s.len() > Self::MAX_DIGESTIBLE_STRING_LENGTH {
672 panic!("Too_long_digestible_string");
673 }
674
675 let mut memo = [0; 34];
676 memo[Self::TAG_INDEX] = Self::DIGEST_TAG;
677 memo[Self::LENGTH_INDEX] = Self::DIGEST_LENGTH_BYTE;
678
679 use blake2::{
680 digest::{Update, VariableOutput},
681 Blake2bVar,
682 };
683 let mut hasher = Blake2bVar::new(32).expect("Invalid Blake2bVar output size");
684 hasher.update(s.as_bytes());
685 hasher.finalize_variable(&mut memo[2..]).unwrap();
686
687 Self(memo)
688 }
689
690 pub fn gen() -> Self {
692 use rand::distributions::{Alphanumeric, DistString};
693 let random_string = Alphanumeric.sample_string(&mut rand::thread_rng(), 50);
694
695 Self::create_by_digesting_string_exn(&random_string)
696 }
697}
698
699pub mod signed_command {
700 use mina_p2p_messages::v2::MinaBaseSignedCommandStableV2;
701 use mina_signer::Signature;
702
703 use crate::decompress_pk;
704
705 use super::*;
706
707 #[derive(Debug, Clone, PartialEq)]
709 pub struct Common {
710 pub fee: Fee,
711 pub fee_payer_pk: CompressedPubKey,
712 pub nonce: Nonce,
713 pub valid_until: Slot,
714 pub memo: Memo,
715 }
716
717 #[derive(Debug, Clone, PartialEq, Eq)]
718 pub struct PaymentPayload {
719 pub receiver_pk: CompressedPubKey,
720 pub amount: Amount,
721 }
722
723 #[derive(Debug, Clone, PartialEq, Eq)]
725 pub enum StakeDelegationPayload {
726 SetDelegate { new_delegate: CompressedPubKey },
727 }
728
729 impl StakeDelegationPayload {
730 pub fn receiver(&self) -> AccountId {
732 let Self::SetDelegate { new_delegate } = self;
733 AccountId::new(new_delegate.clone(), TokenId::default())
734 }
735
736 pub fn receiver_pk(&self) -> &CompressedPubKey {
738 let Self::SetDelegate { new_delegate } = self;
739 new_delegate
740 }
741 }
742
743 #[derive(Debug, Clone, PartialEq, Eq)]
745 pub enum Body {
746 Payment(PaymentPayload),
747 StakeDelegation(StakeDelegationPayload),
748 }
749
750 #[derive(Debug, Clone, PartialEq)]
752 pub struct SignedCommandPayload {
753 pub common: Common,
754 pub body: Body,
755 }
756
757 impl SignedCommandPayload {
758 pub fn create(
759 fee: Fee,
760 fee_payer_pk: CompressedPubKey,
761 nonce: Nonce,
762 valid_until: Option<Slot>,
763 memo: Memo,
764 body: Body,
765 ) -> Self {
766 Self {
767 common: Common {
768 fee,
769 fee_payer_pk,
770 nonce,
771 valid_until: valid_until.unwrap_or_else(Slot::max),
772 memo,
773 },
774 body,
775 }
776 }
777 }
778
779 mod weight {
781 use super::*;
782
783 fn payment(_: &PaymentPayload) -> u64 {
784 1
785 }
786 fn stake_delegation(_: &StakeDelegationPayload) -> u64 {
787 1
788 }
789 pub fn of_body(body: &Body) -> u64 {
790 match body {
791 Body::Payment(p) => payment(p),
792 Body::StakeDelegation(s) => stake_delegation(s),
793 }
794 }
795 }
796
797 #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
798 #[serde(into = "MinaBaseSignedCommandStableV2")]
799 #[serde(try_from = "MinaBaseSignedCommandStableV2")]
800 pub struct SignedCommand {
801 pub payload: SignedCommandPayload,
802 pub signer: CompressedPubKey, pub signature: Signature,
804 }
805
806 impl SignedCommand {
807 pub fn valid_until(&self) -> Slot {
808 self.payload.common.valid_until
809 }
810
811 pub fn fee_payer(&self) -> AccountId {
813 let public_key = self.payload.common.fee_payer_pk.clone();
814 AccountId::new(public_key, TokenId::default())
815 }
816
817 pub fn fee_payer_pk(&self) -> &CompressedPubKey {
819 &self.payload.common.fee_payer_pk
820 }
821
822 pub fn weight(&self) -> u64 {
823 let Self {
824 payload: SignedCommandPayload { common: _, body },
825 signer: _,
826 signature: _,
827 } = self;
828 weight::of_body(body)
829 }
830
831 pub fn fee_token(&self) -> TokenId {
833 TokenId::default()
834 }
835
836 pub fn fee(&self) -> Fee {
837 self.payload.common.fee
838 }
839
840 pub fn receiver(&self) -> AccountId {
842 match &self.payload.body {
843 Body::Payment(payload) => {
844 AccountId::new(payload.receiver_pk.clone(), TokenId::default())
845 }
846 Body::StakeDelegation(payload) => payload.receiver(),
847 }
848 }
849
850 pub fn receiver_pk(&self) -> &CompressedPubKey {
852 match &self.payload.body {
853 Body::Payment(payload) => &payload.receiver_pk,
854 Body::StakeDelegation(payload) => payload.receiver_pk(),
855 }
856 }
857
858 pub fn amount(&self) -> Option<Amount> {
859 match &self.payload.body {
860 Body::Payment(payload) => Some(payload.amount),
861 Body::StakeDelegation(_) => None,
862 }
863 }
864
865 pub fn nonce(&self) -> Nonce {
866 self.payload.common.nonce
867 }
868
869 pub fn fee_excess(&self) -> FeeExcess {
870 FeeExcess::of_single((self.fee_token(), Signed::<Fee>::of_unsigned(self.fee())))
871 }
872
873 pub fn account_access_statuses(
875 &self,
876 status: &TransactionStatus,
877 ) -> Vec<(AccountId, AccessedOrNot)> {
878 use AccessedOrNot::*;
879 use TransactionStatus::*;
880
881 match status {
882 Applied => vec![(self.fee_payer(), Accessed), (self.receiver(), Accessed)],
883 Failed(_) => vec![(self.fee_payer(), Accessed), (self.receiver(), NotAccessed)],
886 }
887 }
888
889 pub fn accounts_referenced(&self) -> Vec<AccountId> {
890 self.account_access_statuses(&TransactionStatus::Applied)
891 .into_iter()
892 .map(|(id, _status)| id)
893 .collect()
894 }
895
896 pub fn public_keys(&self) -> [&CompressedPubKey; 2] {
898 [self.fee_payer_pk(), self.receiver_pk()]
899 }
900
901 pub fn check_valid_keys(&self) -> bool {
903 self.public_keys()
904 .into_iter()
905 .all(|pk| decompress_pk(pk).is_some())
906 }
907 }
908}
909
910pub mod zkapp_command {
911 use std::sync::Arc;
912
913 use ark_ff::UniformRand;
914 use mina_p2p_messages::v2::MinaBaseZkappCommandTStableV1WireStableV1AccountUpdatesA;
915 use mina_signer::Signature;
916 use poseidon::hash::params::{
917 MINA_ACCOUNT_UPDATE_CONS, MINA_ACCOUNT_UPDATE_NODE, MINA_ZKAPP_EVENT, MINA_ZKAPP_EVENTS,
918 MINA_ZKAPP_SEQ_EVENTS, NO_INPUT_MINA_ZKAPP_ACTIONS_EMPTY, NO_INPUT_MINA_ZKAPP_EVENTS_EMPTY,
919 };
920 use rand::{seq::SliceRandom, Rng};
921
922 use crate::{
923 dummy, gen_compressed, gen_keypair,
924 proofs::{
925 field::{Boolean, ToBoolean},
926 to_field_elements::ToFieldElements,
927 transaction::Check,
928 },
929 scan_state::{
930 currency::{MinMax, Sgn},
931 GenesisConstant, GENESIS_CONSTANT,
932 },
933 zkapps::checks::{ZkappCheck, ZkappCheckOps},
934 AuthRequired, MutableFp, MyCow, Permissions, SetVerificationKey, ToInputs, TokenSymbol,
935 VerificationKey, VerificationKeyWire, VotingFor, ZkAppAccount, ZkAppUri,
936 };
937
938 use super::{zkapp_statement::TransactionCommitment, *};
939
940 #[derive(Debug, Clone, PartialEq)]
941 pub struct Event(pub Vec<Fp>);
942
943 impl Event {
944 pub fn empty() -> Self {
945 Self(Vec::new())
946 }
947 pub fn hash(&self) -> Fp {
948 hash_with_kimchi(&MINA_ZKAPP_EVENT, &self.0[..])
949 }
950 pub fn len(&self) -> usize {
951 let Self(list) = self;
952 list.len()
953 }
954 }
955
956 #[derive(Debug, Clone, PartialEq)]
958 pub struct Events(pub Vec<Event>);
959
960 #[derive(Debug, Clone, PartialEq)]
962 pub struct Actions(pub Vec<Event>);
963
964 pub fn gen_events() -> Vec<Event> {
965 let mut rng = rand::thread_rng();
966
967 let n = rng.gen_range(0..=5);
968
969 (0..=n)
970 .map(|_| {
971 let n = rng.gen_range(0..=3);
972 let event = (0..=n).map(|_| Fp::rand(&mut rng)).collect();
973 Event(event)
974 })
975 .collect()
976 }
977
978 use poseidon::hash::LazyParam;
979
980 pub trait MakeEvents {
982 const DERIVER_NAME: (); fn get_salt_phrase() -> &'static LazyParam;
985 fn get_hash_prefix() -> &'static LazyParam;
986 fn events(&self) -> &[Event];
987 fn empty_hash() -> Fp;
988 }
989
990 impl MakeEvents for Events {
992 const DERIVER_NAME: () = ();
993 fn get_salt_phrase() -> &'static LazyParam {
994 &NO_INPUT_MINA_ZKAPP_EVENTS_EMPTY
995 }
996 fn get_hash_prefix() -> &'static poseidon::hash::LazyParam {
997 &MINA_ZKAPP_EVENTS
998 }
999 fn events(&self) -> &[Event] {
1000 self.0.as_slice()
1001 }
1002 fn empty_hash() -> Fp {
1003 cache_one!(Fp, events_to_field(&Events::empty()))
1004 }
1005 }
1006
1007 impl MakeEvents for Actions {
1009 const DERIVER_NAME: () = ();
1010 fn get_salt_phrase() -> &'static LazyParam {
1011 &NO_INPUT_MINA_ZKAPP_ACTIONS_EMPTY
1012 }
1013 fn get_hash_prefix() -> &'static poseidon::hash::LazyParam {
1014 &MINA_ZKAPP_SEQ_EVENTS
1015 }
1016 fn events(&self) -> &[Event] {
1017 self.0.as_slice()
1018 }
1019 fn empty_hash() -> Fp {
1020 cache_one!(Fp, events_to_field(&Actions::empty()))
1021 }
1022 }
1023
1024 pub fn events_to_field<E>(e: &E) -> Fp
1026 where
1027 E: MakeEvents,
1028 {
1029 let init = hash_noinputs(E::get_salt_phrase());
1030
1031 e.events().iter().rfold(init, |accum, elem| {
1032 hash_with_kimchi(E::get_hash_prefix(), &[accum, elem.hash()])
1033 })
1034 }
1035
1036 impl ToInputs for Events {
1037 fn to_inputs(&self, inputs: &mut Inputs) {
1038 inputs.append(&events_to_field(self));
1039 }
1040 }
1041
1042 impl ToInputs for Actions {
1043 fn to_inputs(&self, inputs: &mut Inputs) {
1044 inputs.append(&events_to_field(self));
1045 }
1046 }
1047
1048 impl ToFieldElements<Fp> for Events {
1049 fn to_field_elements(&self, fields: &mut Vec<Fp>) {
1050 events_to_field(self).to_field_elements(fields);
1051 }
1052 }
1053
1054 impl ToFieldElements<Fp> for Actions {
1055 fn to_field_elements(&self, fields: &mut Vec<Fp>) {
1056 events_to_field(self).to_field_elements(fields);
1057 }
1058 }
1059
1060 #[derive(Clone, Debug, PartialEq, Eq)]
1064 pub struct Timing {
1065 pub initial_minimum_balance: Balance,
1066 pub cliff_time: Slot,
1067 pub cliff_amount: Amount,
1068 pub vesting_period: SlotSpan,
1069 pub vesting_increment: Amount,
1070 }
1071
1072 impl Timing {
1073 fn dummy() -> Self {
1075 Self {
1076 initial_minimum_balance: Balance::zero(),
1077 cliff_time: Slot::zero(),
1078 cliff_amount: Amount::zero(),
1079 vesting_period: SlotSpan::zero(),
1080 vesting_increment: Amount::zero(),
1081 }
1082 }
1083
1084 pub fn of_account_timing(timing: crate::account::Timing) -> Option<Self> {
1087 match timing {
1088 crate::Timing::Untimed => None,
1089 crate::Timing::Timed {
1090 initial_minimum_balance,
1091 cliff_time,
1092 cliff_amount,
1093 vesting_period,
1094 vesting_increment,
1095 } => Some(Self {
1096 initial_minimum_balance,
1097 cliff_time,
1098 cliff_amount,
1099 vesting_period,
1100 vesting_increment,
1101 }),
1102 }
1103 }
1104
1105 pub fn to_account_timing(self) -> crate::account::Timing {
1107 let Self {
1108 initial_minimum_balance,
1109 cliff_time,
1110 cliff_amount,
1111 vesting_period,
1112 vesting_increment,
1113 } = self;
1114
1115 crate::account::Timing::Timed {
1116 initial_minimum_balance,
1117 cliff_time,
1118 cliff_amount,
1119 vesting_period,
1120 vesting_increment,
1121 }
1122 }
1123 }
1124
1125 impl ToFieldElements<Fp> for Timing {
1126 fn to_field_elements(&self, fields: &mut Vec<Fp>) {
1127 let Self {
1128 initial_minimum_balance,
1129 cliff_time,
1130 cliff_amount,
1131 vesting_period,
1132 vesting_increment,
1133 } = self;
1134
1135 initial_minimum_balance.to_field_elements(fields);
1136 cliff_time.to_field_elements(fields);
1137 cliff_amount.to_field_elements(fields);
1138 vesting_period.to_field_elements(fields);
1139 vesting_increment.to_field_elements(fields);
1140 }
1141 }
1142
1143 impl Check<Fp> for Timing {
1144 fn check(&self, w: &mut Witness<Fp>) {
1145 let Self {
1146 initial_minimum_balance,
1147 cliff_time,
1148 cliff_amount,
1149 vesting_period,
1150 vesting_increment,
1151 } = self;
1152
1153 initial_minimum_balance.check(w);
1154 cliff_time.check(w);
1155 cliff_amount.check(w);
1156 vesting_period.check(w);
1157 vesting_increment.check(w);
1158 }
1159 }
1160
1161 impl ToInputs for Timing {
1162 fn to_inputs(&self, inputs: &mut Inputs) {
1164 let Timing {
1165 initial_minimum_balance,
1166 cliff_time,
1167 cliff_amount,
1168 vesting_period,
1169 vesting_increment,
1170 } = self;
1171
1172 inputs.append_u64(initial_minimum_balance.as_u64());
1173 inputs.append_u32(cliff_time.as_u32());
1174 inputs.append_u64(cliff_amount.as_u64());
1175 inputs.append_u32(vesting_period.as_u32());
1176 inputs.append_u64(vesting_increment.as_u64());
1177 }
1178 }
1179
1180 impl Events {
1181 pub fn empty() -> Self {
1182 Self(Vec::new())
1183 }
1184
1185 pub fn is_empty(&self) -> bool {
1186 self.0.is_empty()
1187 }
1188
1189 pub fn push_event(acc: Fp, event: Event) -> Fp {
1190 hash_with_kimchi(Self::get_hash_prefix(), &[acc, event.hash()])
1191 }
1192
1193 pub fn push_events(&self, acc: Fp) -> Fp {
1194 let hash = self
1195 .0
1196 .iter()
1197 .rfold(hash_noinputs(Self::get_salt_phrase()), |acc, e| {
1198 Self::push_event(acc, e.clone())
1199 });
1200 hash_with_kimchi(Self::get_hash_prefix(), &[acc, hash])
1201 }
1202 }
1203
1204 impl Actions {
1205 pub fn empty() -> Self {
1206 Self(Vec::new())
1207 }
1208
1209 pub fn is_empty(&self) -> bool {
1210 self.0.is_empty()
1211 }
1212
1213 pub fn push_event(acc: Fp, event: Event) -> Fp {
1214 hash_with_kimchi(Self::get_hash_prefix(), &[acc, event.hash()])
1215 }
1216
1217 pub fn push_events(&self, acc: Fp) -> Fp {
1218 let hash = self
1219 .0
1220 .iter()
1221 .rfold(hash_noinputs(Self::get_salt_phrase()), |acc, e| {
1222 Self::push_event(acc, e.clone())
1223 });
1224 hash_with_kimchi(Self::get_hash_prefix(), &[acc, hash])
1225 }
1226 }
1227
1228 #[derive(Debug, Clone, PartialEq, Eq)]
1230 pub enum SetOrKeep<T: Clone> {
1231 Set(T),
1232 Keep,
1233 }
1234
1235 impl<T: Clone> SetOrKeep<T> {
1236 fn map<'a, F, U>(&'a self, fun: F) -> SetOrKeep<U>
1237 where
1238 F: FnOnce(&'a T) -> U,
1239 U: Clone,
1240 {
1241 match self {
1242 SetOrKeep::Set(v) => SetOrKeep::Set(fun(v)),
1243 SetOrKeep::Keep => SetOrKeep::Keep,
1244 }
1245 }
1246
1247 pub fn into_map<F, U>(self, fun: F) -> SetOrKeep<U>
1248 where
1249 F: FnOnce(T) -> U,
1250 U: Clone,
1251 {
1252 match self {
1253 SetOrKeep::Set(v) => SetOrKeep::Set(fun(v)),
1254 SetOrKeep::Keep => SetOrKeep::Keep,
1255 }
1256 }
1257
1258 pub fn set_or_keep(&self, x: T) -> T {
1259 match self {
1260 Self::Set(data) => data.clone(),
1261 Self::Keep => x,
1262 }
1263 }
1264
1265 pub fn is_keep(&self) -> bool {
1266 match self {
1267 Self::Keep => true,
1268 Self::Set(_) => false,
1269 }
1270 }
1271
1272 pub fn is_set(&self) -> bool {
1273 !self.is_keep()
1274 }
1275
1276 pub fn gen<F>(mut fun: F) -> Self
1277 where
1278 F: FnMut() -> T,
1279 {
1280 let mut rng = rand::thread_rng();
1281
1282 if rng.gen() {
1283 Self::Set(fun())
1284 } else {
1285 Self::Keep
1286 }
1287 }
1288 }
1289
1290 impl<T, F> ToInputs for (&SetOrKeep<T>, F)
1291 where
1292 T: ToInputs,
1293 T: Clone,
1294 F: Fn() -> T,
1295 {
1296 fn to_inputs(&self, inputs: &mut Inputs) {
1298 let (set_or_keep, default_fn) = self;
1299
1300 match set_or_keep {
1301 SetOrKeep::Set(this) => {
1302 inputs.append_bool(true);
1303 this.to_inputs(inputs);
1304 }
1305 SetOrKeep::Keep => {
1306 inputs.append_bool(false);
1307 let default = default_fn();
1308 default.to_inputs(inputs);
1309 }
1310 }
1311 }
1312 }
1313
1314 impl<T, F> ToFieldElements<Fp> for (&SetOrKeep<T>, F)
1315 where
1316 T: ToFieldElements<Fp>,
1317 T: Clone,
1318 F: Fn() -> T,
1319 {
1320 fn to_field_elements(&self, fields: &mut Vec<Fp>) {
1321 let (set_or_keep, default_fn) = self;
1322
1323 match set_or_keep {
1324 SetOrKeep::Set(this) => {
1325 Boolean::True.to_field_elements(fields);
1326 this.to_field_elements(fields);
1327 }
1328 SetOrKeep::Keep => {
1329 Boolean::False.to_field_elements(fields);
1330 let default = default_fn();
1331 default.to_field_elements(fields);
1332 }
1333 }
1334 }
1335 }
1336
1337 impl<T, F> Check<Fp> for (&SetOrKeep<T>, F)
1338 where
1339 T: Check<Fp>,
1340 T: Clone,
1341 F: Fn() -> T,
1342 {
1343 fn check(&self, w: &mut Witness<Fp>) {
1344 let (set_or_keep, default_fn) = self;
1345 let value = match set_or_keep {
1346 SetOrKeep::Set(this) => MyCow::Borrow(this),
1347 SetOrKeep::Keep => MyCow::Own(default_fn()),
1348 };
1349 value.check(w);
1350 }
1351 }
1352
1353 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
1354 pub struct WithHash<T, H = Fp> {
1355 pub data: T,
1356 pub hash: H,
1357 }
1358
1359 impl<T, H: Ord> Ord for WithHash<T, H> {
1360 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1361 self.hash.cmp(&other.hash)
1362 }
1363 }
1364
1365 impl<T, H: PartialOrd> PartialOrd for WithHash<T, H> {
1366 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1367 self.hash.partial_cmp(&other.hash)
1368 }
1369 }
1370
1371 impl<T, H: Eq> Eq for WithHash<T, H> {}
1372
1373 impl<T, H: PartialEq> PartialEq for WithHash<T, H> {
1374 fn eq(&self, other: &Self) -> bool {
1375 self.hash == other.hash
1376 }
1377 }
1378
1379 impl<T, Hash: std::hash::Hash> std::hash::Hash for WithHash<T, Hash> {
1380 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1381 let Self { data: _, hash } = self;
1382 hash.hash(state);
1383 }
1384 }
1385
1386 impl<T> ToFieldElements<Fp> for WithHash<T> {
1387 fn to_field_elements(&self, fields: &mut Vec<Fp>) {
1388 let Self { data: _, hash } = self;
1389 hash.to_field_elements(fields);
1390 }
1391 }
1392
1393 impl<T> std::ops::Deref for WithHash<T> {
1394 type Target = T;
1395
1396 fn deref(&self) -> &Self::Target {
1397 &self.data
1398 }
1399 }
1400
1401 impl<T> WithHash<T> {
1402 pub fn of_data(data: T, hash_data: impl Fn(&T) -> Fp) -> Self {
1403 let hash = hash_data(&data);
1404 Self { data, hash }
1405 }
1406 }
1407
1408 #[derive(Debug, Clone, PartialEq, Eq)]
1410 pub struct Update {
1411 pub app_state: [SetOrKeep<Fp>; 8],
1412 pub delegate: SetOrKeep<CompressedPubKey>,
1413 pub verification_key: SetOrKeep<VerificationKeyWire>,
1414 pub permissions: SetOrKeep<Permissions<AuthRequired>>,
1415 pub zkapp_uri: SetOrKeep<ZkAppUri>,
1416 pub token_symbol: SetOrKeep<TokenSymbol>,
1417 pub timing: SetOrKeep<Timing>,
1418 pub voting_for: SetOrKeep<VotingFor>,
1419 }
1420
1421 impl ToFieldElements<Fp> for Update {
1422 fn to_field_elements(&self, fields: &mut Vec<Fp>) {
1423 let Self {
1424 app_state,
1425 delegate,
1426 verification_key,
1427 permissions,
1428 zkapp_uri,
1429 token_symbol,
1430 timing,
1431 voting_for,
1432 } = self;
1433
1434 for s in app_state {
1435 (s, Fp::zero).to_field_elements(fields);
1436 }
1437 (delegate, CompressedPubKey::empty).to_field_elements(fields);
1438 (&verification_key.map(|w| w.hash()), Fp::zero).to_field_elements(fields);
1439 (permissions, Permissions::empty).to_field_elements(fields);
1440 (&zkapp_uri.map(Some), || Option::<&ZkAppUri>::None).to_field_elements(fields);
1441 (token_symbol, TokenSymbol::default).to_field_elements(fields);
1442 (timing, Timing::dummy).to_field_elements(fields);
1443 (voting_for, VotingFor::dummy).to_field_elements(fields);
1444 }
1445 }
1446
1447 impl Update {
1448 pub fn noop() -> Self {
1450 Self {
1451 app_state: std::array::from_fn(|_| SetOrKeep::Keep),
1452 delegate: SetOrKeep::Keep,
1453 verification_key: SetOrKeep::Keep,
1454 permissions: SetOrKeep::Keep,
1455 zkapp_uri: SetOrKeep::Keep,
1456 token_symbol: SetOrKeep::Keep,
1457 timing: SetOrKeep::Keep,
1458 voting_for: SetOrKeep::Keep,
1459 }
1460 }
1461
1462 pub fn dummy() -> Self {
1464 Self::noop()
1465 }
1466
1467 pub fn gen(
1469 token_account: Option<bool>,
1470 zkapp_account: Option<bool>,
1471 vk: Option<&VerificationKeyWire>,
1472 permissions_auth: Option<crate::ControlTag>,
1473 ) -> Self {
1474 let mut rng = rand::thread_rng();
1475
1476 let token_account = token_account.unwrap_or(false);
1477 let zkapp_account = zkapp_account.unwrap_or(false);
1478
1479 let app_state: [_; 8] = std::array::from_fn(|_| SetOrKeep::gen(|| Fp::rand(&mut rng)));
1480
1481 let delegate = if !token_account {
1482 SetOrKeep::gen(|| gen_keypair().public.into_compressed())
1483 } else {
1484 SetOrKeep::Keep
1485 };
1486
1487 let verification_key = if zkapp_account {
1488 SetOrKeep::gen(|| match vk {
1489 None => VerificationKeyWire::dummy(),
1490 Some(vk) => vk.clone(),
1491 })
1492 } else {
1493 SetOrKeep::Keep
1494 };
1495
1496 let permissions = match permissions_auth {
1497 None => SetOrKeep::Keep,
1498 Some(auth_tag) => SetOrKeep::Set(Permissions::gen(auth_tag)),
1499 };
1500
1501 let zkapp_uri = SetOrKeep::gen(|| {
1502 ZkAppUri::from(
1503 [
1504 "https://www.example.com",
1505 "https://www.minaprotocol.com",
1506 "https://www.gurgle.com",
1507 "https://faceplant.com",
1508 ]
1509 .choose(&mut rng)
1510 .unwrap()
1511 .to_string()
1512 .into_bytes(),
1513 )
1514 });
1515
1516 let token_symbol = SetOrKeep::gen(|| {
1517 TokenSymbol::from(
1518 ["MINA", "TOKEN1", "TOKEN2", "TOKEN3", "TOKEN4", "TOKEN5"]
1519 .choose(&mut rng)
1520 .unwrap()
1521 .to_string()
1522 .into_bytes(),
1523 )
1524 });
1525
1526 let voting_for = SetOrKeep::gen(|| VotingFor(Fp::rand(&mut rng)));
1527
1528 let timing = SetOrKeep::Keep;
1529
1530 Self {
1531 app_state,
1532 delegate,
1533 verification_key,
1534 permissions,
1535 zkapp_uri,
1536 token_symbol,
1537 timing,
1538 voting_for,
1539 }
1540 }
1541 }
1542
1543 #[derive(Debug, Clone, PartialEq)]
1546 pub struct ClosedInterval<T> {
1547 pub lower: T,
1548 pub upper: T,
1549 }
1550
1551 impl<T> ClosedInterval<T>
1552 where
1553 T: MinMax,
1554 {
1555 pub fn min_max() -> Self {
1556 Self {
1557 lower: T::min(),
1558 upper: T::max(),
1559 }
1560 }
1561 }
1562
1563 impl<T> ToInputs for ClosedInterval<T>
1564 where
1565 T: ToInputs,
1566 {
1567 fn to_inputs(&self, inputs: &mut Inputs) {
1569 let ClosedInterval { lower, upper } = self;
1570
1571 lower.to_inputs(inputs);
1572 upper.to_inputs(inputs);
1573 }
1574 }
1575
1576 impl<T> ToFieldElements<Fp> for ClosedInterval<T>
1577 where
1578 T: ToFieldElements<Fp>,
1579 {
1580 fn to_field_elements(&self, fields: &mut Vec<Fp>) {
1581 let ClosedInterval { lower, upper } = self;
1582
1583 lower.to_field_elements(fields);
1584 upper.to_field_elements(fields);
1585 }
1586 }
1587
1588 impl<T> Check<Fp> for ClosedInterval<T>
1589 where
1590 T: Check<Fp>,
1591 {
1592 fn check(&self, w: &mut Witness<Fp>) {
1593 let ClosedInterval { lower, upper } = self;
1594 lower.check(w);
1595 upper.check(w);
1596 }
1597 }
1598
1599 impl<T> ClosedInterval<T>
1600 where
1601 T: PartialOrd,
1602 {
1603 pub fn is_constant(&self) -> bool {
1604 self.lower == self.upper
1605 }
1606
1607 pub fn gen<F>(mut fun: F) -> Self
1609 where
1610 F: FnMut() -> T,
1611 {
1612 let a1 = fun();
1613 let a2 = fun();
1614
1615 if a1 <= a2 {
1616 Self {
1617 lower: a1,
1618 upper: a2,
1619 }
1620 } else {
1621 Self {
1622 lower: a2,
1623 upper: a1,
1624 }
1625 }
1626 }
1627 }
1628
1629 #[derive(Debug, Clone, PartialEq)]
1631 pub enum OrIgnore<T> {
1632 Check(T),
1633 Ignore,
1634 }
1635
1636 impl<T, F> ToInputs for (&OrIgnore<T>, F)
1637 where
1638 T: ToInputs,
1639 F: Fn() -> T,
1640 {
1641 fn to_inputs(&self, inputs: &mut Inputs) {
1643 let (or_ignore, default_fn) = self;
1644
1645 match or_ignore {
1646 OrIgnore::Check(this) => {
1647 inputs.append_bool(true);
1648 this.to_inputs(inputs);
1649 }
1650 OrIgnore::Ignore => {
1651 inputs.append_bool(false);
1652 let default = default_fn();
1653 default.to_inputs(inputs);
1654 }
1655 }
1656 }
1657 }
1658
1659 impl<T, F> ToFieldElements<Fp> for (&OrIgnore<T>, F)
1660 where
1661 T: ToFieldElements<Fp>,
1662 F: Fn() -> T,
1663 {
1664 fn to_field_elements(&self, fields: &mut Vec<Fp>) {
1665 let (or_ignore, default_fn) = self;
1666
1667 match or_ignore {
1668 OrIgnore::Check(this) => {
1669 Boolean::True.to_field_elements(fields);
1670 this.to_field_elements(fields);
1671 }
1672 OrIgnore::Ignore => {
1673 Boolean::False.to_field_elements(fields);
1674 let default = default_fn();
1675 default.to_field_elements(fields);
1676 }
1677 };
1678 }
1679 }
1680
1681 impl<T, F> Check<Fp> for (&OrIgnore<T>, F)
1682 where
1683 T: Check<Fp>,
1684 F: Fn() -> T,
1685 {
1686 fn check(&self, w: &mut Witness<Fp>) {
1687 let (or_ignore, default_fn) = self;
1688 let value = match or_ignore {
1689 OrIgnore::Check(this) => MyCow::Borrow(this),
1690 OrIgnore::Ignore => MyCow::Own(default_fn()),
1691 };
1692 value.check(w);
1693 }
1694 }
1695
1696 impl<T> OrIgnore<T> {
1697 pub fn gen<F>(mut fun: F) -> Self
1699 where
1700 F: FnMut() -> T,
1701 {
1702 let mut rng = rand::thread_rng();
1703
1704 if rng.gen() {
1705 Self::Check(fun())
1706 } else {
1707 Self::Ignore
1708 }
1709 }
1710
1711 pub fn map<F, V>(&self, fun: F) -> OrIgnore<V>
1712 where
1713 F: Fn(&T) -> V,
1714 {
1715 match self {
1716 OrIgnore::Check(v) => OrIgnore::Check(fun(v)),
1717 OrIgnore::Ignore => OrIgnore::Ignore,
1718 }
1719 }
1720 }
1721
1722 impl<T> OrIgnore<ClosedInterval<T>>
1723 where
1724 T: PartialOrd,
1725 {
1726 pub fn is_constant(&self) -> bool {
1728 match self {
1729 OrIgnore::Check(interval) => interval.lower == interval.upper,
1730 OrIgnore::Ignore => false,
1731 }
1732 }
1733 }
1734
1735 pub type Hash<T> = OrIgnore<T>;
1737
1738 pub type EqData<T> = OrIgnore<T>;
1740
1741 pub type Numeric<T> = OrIgnore<ClosedInterval<T>>;
1743
1744 #[derive(Debug, Clone, PartialEq)]
1746 pub struct EpochLedger {
1747 pub hash: Hash<Fp>,
1748 pub total_currency: Numeric<Amount>,
1749 }
1750
1751 #[derive(Debug, Clone, PartialEq)]
1753 pub struct EpochData {
1754 pub(crate) ledger: EpochLedger,
1755 pub seed: Hash<Fp>,
1756 pub start_checkpoint: Hash<Fp>,
1757 pub lock_checkpoint: Hash<Fp>,
1758 pub epoch_length: Numeric<Length>,
1759 }
1760
1761 #[cfg(feature = "fuzzing")]
1762 impl EpochData {
1763 pub fn new(
1764 ledger: EpochLedger,
1765 seed: Hash<Fp>,
1766 start_checkpoint: Hash<Fp>,
1767 lock_checkpoint: Hash<Fp>,
1768 epoch_length: Numeric<Length>,
1769 ) -> Self {
1770 EpochData {
1771 ledger,
1772 seed,
1773 start_checkpoint,
1774 lock_checkpoint,
1775 epoch_length,
1776 }
1777 }
1778
1779 pub fn ledger_mut(&mut self) -> &mut EpochLedger {
1780 &mut self.ledger
1781 }
1782 }
1783
1784 impl ToInputs for EpochData {
1785 fn to_inputs(&self, inputs: &mut Inputs) {
1787 let EpochData {
1788 ledger,
1789 seed,
1790 start_checkpoint,
1791 lock_checkpoint,
1792 epoch_length,
1793 } = self;
1794
1795 {
1796 let EpochLedger {
1797 hash,
1798 total_currency,
1799 } = ledger;
1800
1801 inputs.append(&(hash, Fp::zero));
1802 inputs.append(&(total_currency, ClosedInterval::min_max));
1803 }
1804
1805 inputs.append(&(seed, Fp::zero));
1806 inputs.append(&(start_checkpoint, Fp::zero));
1807 inputs.append(&(lock_checkpoint, Fp::zero));
1808 inputs.append(&(epoch_length, ClosedInterval::min_max));
1809 }
1810 }
1811
1812 impl ToFieldElements<Fp> for EpochData {
1813 fn to_field_elements(&self, fields: &mut Vec<Fp>) {
1814 let EpochData {
1815 ledger,
1816 seed,
1817 start_checkpoint,
1818 lock_checkpoint,
1819 epoch_length,
1820 } = self;
1821
1822 {
1823 let EpochLedger {
1824 hash,
1825 total_currency,
1826 } = ledger;
1827
1828 (hash, Fp::zero).to_field_elements(fields);
1829 (total_currency, ClosedInterval::min_max).to_field_elements(fields);
1830 }
1831
1832 (seed, Fp::zero).to_field_elements(fields);
1833 (start_checkpoint, Fp::zero).to_field_elements(fields);
1834 (lock_checkpoint, Fp::zero).to_field_elements(fields);
1835 (epoch_length, ClosedInterval::min_max).to_field_elements(fields);
1836 }
1837 }
1838
1839 impl Check<Fp> for EpochData {
1840 fn check(&self, w: &mut Witness<Fp>) {
1841 let EpochData {
1842 ledger,
1843 seed,
1844 start_checkpoint,
1845 lock_checkpoint,
1846 epoch_length,
1847 } = self;
1848
1849 {
1850 let EpochLedger {
1851 hash,
1852 total_currency,
1853 } = ledger;
1854
1855 (hash, Fp::zero).check(w);
1856 (total_currency, ClosedInterval::min_max).check(w);
1857 }
1858
1859 (seed, Fp::zero).check(w);
1860 (start_checkpoint, Fp::zero).check(w);
1861 (lock_checkpoint, Fp::zero).check(w);
1862 (epoch_length, ClosedInterval::min_max).check(w);
1863 }
1864 }
1865
1866 impl EpochData {
1867 pub fn gen() -> Self {
1868 let mut rng = rand::thread_rng();
1869
1870 EpochData {
1871 ledger: EpochLedger {
1872 hash: OrIgnore::gen(|| Fp::rand(&mut rng)),
1873 total_currency: OrIgnore::gen(|| ClosedInterval::gen(|| rng.gen())),
1874 },
1875 seed: OrIgnore::gen(|| Fp::rand(&mut rng)),
1876 start_checkpoint: OrIgnore::gen(|| Fp::rand(&mut rng)),
1877 lock_checkpoint: OrIgnore::gen(|| Fp::rand(&mut rng)),
1878 epoch_length: OrIgnore::gen(|| ClosedInterval::gen(|| rng.gen())),
1879 }
1880 }
1881 }
1882
1883 #[derive(Debug, Clone, PartialEq)]
1885 pub struct ZkAppPreconditions {
1886 pub snarked_ledger_hash: Hash<Fp>,
1887 pub blockchain_length: Numeric<Length>,
1888 pub min_window_density: Numeric<Length>,
1889 pub total_currency: Numeric<Amount>,
1890 pub global_slot_since_genesis: Numeric<Slot>,
1891 pub staking_epoch_data: EpochData,
1892 pub next_epoch_data: EpochData,
1893 }
1894
1895 impl ZkAppPreconditions {
1896 pub fn zcheck<Ops: ZkappCheckOps>(
1897 &self,
1898 s: &ProtocolStateView,
1899 w: &mut Witness<Fp>,
1900 ) -> Boolean {
1901 let Self {
1902 snarked_ledger_hash,
1903 blockchain_length,
1904 min_window_density,
1905 total_currency,
1906 global_slot_since_genesis,
1907 staking_epoch_data,
1908 next_epoch_data,
1909 } = self;
1910
1911 let epoch_data = |epoch_data: &EpochData,
1914 view: &protocol_state::EpochData<Fp>,
1915 w: &mut Witness<Fp>| {
1916 let EpochData {
1917 ledger:
1918 EpochLedger {
1919 hash,
1920 total_currency,
1921 },
1922 seed: _,
1923 start_checkpoint,
1924 lock_checkpoint,
1925 epoch_length,
1926 } = epoch_data;
1927 [
1930 (epoch_length, ClosedInterval::min_max).zcheck::<Ops>(&view.epoch_length, w),
1931 (lock_checkpoint, Fp::zero).zcheck::<Ops>(&view.lock_checkpoint, w),
1932 (start_checkpoint, Fp::zero).zcheck::<Ops>(&view.start_checkpoint, w),
1933 (total_currency, ClosedInterval::min_max)
1934 .zcheck::<Ops>(&view.ledger.total_currency, w),
1935 (hash, Fp::zero).zcheck::<Ops>(&view.ledger.hash, w),
1936 ]
1937 };
1938
1939 let next_epoch_data = epoch_data(next_epoch_data, &s.next_epoch_data, w);
1940 let staking_epoch_data = epoch_data(staking_epoch_data, &s.staking_epoch_data, w);
1941
1942 let bools = [
1945 (global_slot_since_genesis, ClosedInterval::min_max)
1946 .zcheck::<Ops>(&s.global_slot_since_genesis, w),
1947 (total_currency, ClosedInterval::min_max).zcheck::<Ops>(&s.total_currency, w),
1948 (min_window_density, ClosedInterval::min_max)
1949 .zcheck::<Ops>(&s.min_window_density, w),
1950 (blockchain_length, ClosedInterval::min_max).zcheck::<Ops>(&s.blockchain_length, w),
1951 (snarked_ledger_hash, Fp::zero).zcheck::<Ops>(&s.snarked_ledger_hash, w),
1952 ]
1953 .into_iter()
1954 .rev()
1955 .chain(staking_epoch_data.into_iter().rev())
1956 .chain(next_epoch_data.into_iter().rev());
1957
1958 Ops::boolean_all(bools, w)
1959 }
1960
1961 pub fn accept() -> Self {
1963 let epoch_data = || EpochData {
1964 ledger: EpochLedger {
1965 hash: OrIgnore::Ignore,
1966 total_currency: OrIgnore::Ignore,
1967 },
1968 seed: OrIgnore::Ignore,
1969 start_checkpoint: OrIgnore::Ignore,
1970 lock_checkpoint: OrIgnore::Ignore,
1971 epoch_length: OrIgnore::Ignore,
1972 };
1973
1974 Self {
1975 snarked_ledger_hash: OrIgnore::Ignore,
1976 blockchain_length: OrIgnore::Ignore,
1977 min_window_density: OrIgnore::Ignore,
1978 total_currency: OrIgnore::Ignore,
1979 global_slot_since_genesis: OrIgnore::Ignore,
1980 staking_epoch_data: epoch_data(),
1981 next_epoch_data: epoch_data(),
1982 }
1983 }
1984 }
1985
1986 impl ToInputs for ZkAppPreconditions {
1987 fn to_inputs(&self, inputs: &mut Inputs) {
1989 let ZkAppPreconditions {
1990 snarked_ledger_hash,
1991 blockchain_length,
1992 min_window_density,
1993 total_currency,
1994 global_slot_since_genesis,
1995 staking_epoch_data,
1996 next_epoch_data,
1997 } = &self;
1998
1999 inputs.append(&(snarked_ledger_hash, Fp::zero));
2000 inputs.append(&(blockchain_length, ClosedInterval::min_max));
2001 inputs.append(&(min_window_density, ClosedInterval::min_max));
2002 inputs.append(&(total_currency, ClosedInterval::min_max));
2003 inputs.append(&(global_slot_since_genesis, ClosedInterval::min_max));
2004 inputs.append(staking_epoch_data);
2005 inputs.append(next_epoch_data);
2006 }
2007 }
2008
2009 impl ToFieldElements<Fp> for ZkAppPreconditions {
2010 fn to_field_elements(&self, fields: &mut Vec<Fp>) {
2011 let Self {
2012 snarked_ledger_hash,
2013 blockchain_length,
2014 min_window_density,
2015 total_currency,
2016 global_slot_since_genesis,
2017 staking_epoch_data,
2018 next_epoch_data,
2019 } = self;
2020
2021 (snarked_ledger_hash, Fp::zero).to_field_elements(fields);
2022 (blockchain_length, ClosedInterval::min_max).to_field_elements(fields);
2023 (min_window_density, ClosedInterval::min_max).to_field_elements(fields);
2024 (total_currency, ClosedInterval::min_max).to_field_elements(fields);
2025 (global_slot_since_genesis, ClosedInterval::min_max).to_field_elements(fields);
2026 staking_epoch_data.to_field_elements(fields);
2027 next_epoch_data.to_field_elements(fields);
2028 }
2029 }
2030
2031 impl Check<Fp> for ZkAppPreconditions {
2032 fn check(&self, w: &mut Witness<Fp>) {
2033 let Self {
2034 snarked_ledger_hash,
2035 blockchain_length,
2036 min_window_density,
2037 total_currency,
2038 global_slot_since_genesis,
2039 staking_epoch_data,
2040 next_epoch_data,
2041 } = self;
2042
2043 (snarked_ledger_hash, Fp::zero).check(w);
2044 (blockchain_length, ClosedInterval::min_max).check(w);
2045 (min_window_density, ClosedInterval::min_max).check(w);
2046 (total_currency, ClosedInterval::min_max).check(w);
2047 (global_slot_since_genesis, ClosedInterval::min_max).check(w);
2048 staking_epoch_data.check(w);
2049 next_epoch_data.check(w);
2050 }
2051 }
2052
2053 fn invalid_public_key() -> CompressedPubKey {
2055 CompressedPubKey {
2056 x: Fp::zero(),
2057 is_odd: false,
2058 }
2059 }
2060
2061 #[derive(Debug, Clone, PartialEq)]
2063 pub struct Account {
2064 pub balance: Numeric<Balance>,
2065 pub nonce: Numeric<Nonce>,
2066 pub receipt_chain_hash: Hash<Fp>, pub delegate: EqData<CompressedPubKey>,
2068 pub state: [EqData<Fp>; 8],
2069 pub action_state: EqData<Fp>,
2070 pub proved_state: EqData<bool>,
2071 pub is_new: EqData<bool>,
2072 }
2073
2074 impl Account {
2075 pub fn accept() -> Self {
2077 Self {
2078 balance: Numeric::Ignore,
2079 nonce: Numeric::Ignore,
2080 receipt_chain_hash: Hash::Ignore,
2081 delegate: EqData::Ignore,
2082 state: std::array::from_fn(|_| EqData::Ignore),
2083 action_state: EqData::Ignore,
2084 proved_state: EqData::Ignore,
2085 is_new: EqData::Ignore,
2086 }
2087 }
2088 }
2089
2090 impl Account {
2091 fn zchecks<Ops: ZkappCheckOps>(
2092 &self,
2093 account: &crate::Account,
2094 new_account: Boolean,
2095 w: &mut Witness<Fp>,
2096 ) -> Vec<(TransactionFailure, Boolean)> {
2097 use TransactionFailure::*;
2098
2099 let Self {
2100 balance,
2101 nonce,
2102 receipt_chain_hash,
2103 delegate,
2104 state,
2105 action_state,
2106 proved_state,
2107 is_new,
2108 } = self;
2109
2110 let zkapp_account = account.zkapp_or_empty();
2111 let is_new = is_new.map(ToBoolean::to_boolean);
2112 let proved_state = proved_state.map(ToBoolean::to_boolean);
2113
2114 let mut checks: Vec<(TransactionFailure, _)> = [
2121 (
2122 AccountIsNewPreconditionUnsatisfied,
2123 (&is_new, || Boolean::False).zcheck::<Ops>(&new_account, w),
2124 ),
2125 (
2126 AccountProvedStatePreconditionUnsatisfied,
2127 (&proved_state, || Boolean::False)
2128 .zcheck::<Ops>(&zkapp_account.proved_state.to_boolean(), w),
2129 ),
2130 ]
2131 .into_iter()
2132 .chain({
2133 let bools = state
2134 .iter()
2135 .zip(&zkapp_account.app_state)
2136 .enumerate()
2137 .rev()
2139 .map(|(i, (s, account_s))| {
2140 let b = (s, Fp::zero).zcheck::<Ops>(account_s, w);
2141 (AccountAppStatePreconditionUnsatisfied(i as u64), b)
2142 })
2143 .collect::<Vec<_>>();
2144 bools.into_iter()
2147 })
2148 .chain([
2149 {
2150 let bools: Vec<_> = zkapp_account
2151 .action_state
2152 .iter()
2153 .rev()
2155 .map(|account_s| {
2156 (action_state, ZkAppAccount::empty_action_state)
2157 .zcheck::<Ops>(account_s, w)
2158 })
2159 .collect();
2160 (
2161 AccountActionStatePreconditionUnsatisfied,
2162 Ops::boolean_any(bools, w),
2163 )
2164 },
2165 (
2166 AccountDelegatePreconditionUnsatisfied,
2167 (delegate, CompressedPubKey::empty)
2168 .zcheck::<Ops>(&*account.delegate_or_empty(), w),
2169 ),
2170 (
2171 AccountReceiptChainHashPreconditionUnsatisfied,
2172 (receipt_chain_hash, Fp::zero).zcheck::<Ops>(&account.receipt_chain_hash.0, w),
2173 ),
2174 (
2175 AccountNoncePreconditionUnsatisfied,
2176 (nonce, ClosedInterval::min_max).zcheck::<Ops>(&account.nonce, w),
2177 ),
2178 (
2179 AccountBalancePreconditionUnsatisfied,
2180 (balance, ClosedInterval::min_max).zcheck::<Ops>(&account.balance, w),
2181 ),
2182 ])
2183 .collect::<Vec<_>>();
2184
2185 checks.reverse();
2186 checks
2187 }
2188 }
2189
2190 #[derive(Debug, Clone, PartialEq)]
2192 pub struct AccountPreconditions(pub Account);
2193
2194 impl ToInputs for AccountPreconditions {
2195 fn to_inputs(&self, inputs: &mut Inputs) {
2198 let Account {
2199 balance,
2200 nonce,
2201 receipt_chain_hash,
2202 delegate,
2203 state,
2204 action_state,
2205 proved_state,
2206 is_new,
2207 } = &self.0;
2208
2209 inputs.append(&(balance, ClosedInterval::min_max));
2210 inputs.append(&(nonce, ClosedInterval::min_max));
2211 inputs.append(&(receipt_chain_hash, Fp::zero));
2212 inputs.append(&(delegate, CompressedPubKey::empty));
2213 for s in state.iter() {
2214 inputs.append(&(s, Fp::zero));
2215 }
2216 inputs.append(&(action_state, ZkAppAccount::empty_action_state));
2218 inputs.append(&(proved_state, || false));
2219 inputs.append(&(is_new, || false));
2220 }
2221 }
2222
2223 impl ToFieldElements<Fp> for AccountPreconditions {
2224 fn to_field_elements(&self, fields: &mut Vec<Fp>) {
2225 let Account {
2226 balance,
2227 nonce,
2228 receipt_chain_hash,
2229 delegate,
2230 state,
2231 action_state,
2232 proved_state,
2233 is_new,
2234 } = &self.0;
2235
2236 (balance, ClosedInterval::min_max).to_field_elements(fields);
2237 (nonce, ClosedInterval::min_max).to_field_elements(fields);
2238 (receipt_chain_hash, Fp::zero).to_field_elements(fields);
2239 (delegate, CompressedPubKey::empty).to_field_elements(fields);
2240 state.iter().for_each(|s| {
2241 (s, Fp::zero).to_field_elements(fields);
2242 });
2243 (action_state, ZkAppAccount::empty_action_state).to_field_elements(fields);
2244 (proved_state, || false).to_field_elements(fields);
2245 (is_new, || false).to_field_elements(fields);
2246 }
2247 }
2248
2249 impl Check<Fp> for AccountPreconditions {
2250 fn check(&self, w: &mut Witness<Fp>) {
2251 let Account {
2252 balance,
2253 nonce,
2254 receipt_chain_hash,
2255 delegate,
2256 state,
2257 action_state,
2258 proved_state,
2259 is_new,
2260 } = &self.0;
2261
2262 (balance, ClosedInterval::min_max).check(w);
2263 (nonce, ClosedInterval::min_max).check(w);
2264 (receipt_chain_hash, Fp::zero).check(w);
2265 (delegate, CompressedPubKey::empty).check(w);
2266 state.iter().for_each(|s| {
2267 (s, Fp::zero).check(w);
2268 });
2269 (action_state, ZkAppAccount::empty_action_state).check(w);
2270 (proved_state, || false).check(w);
2271 (is_new, || false).check(w);
2272 }
2273 }
2274
2275 impl AccountPreconditions {
2276 pub fn with_nonce(nonce: Nonce) -> Self {
2277 use OrIgnore::{Check, Ignore};
2278 AccountPreconditions(Account {
2279 balance: Ignore,
2280 nonce: Check(ClosedInterval {
2281 lower: nonce,
2282 upper: nonce,
2283 }),
2284 receipt_chain_hash: Ignore,
2285 delegate: Ignore,
2286 state: std::array::from_fn(|_| EqData::Ignore),
2287 action_state: Ignore,
2288 proved_state: Ignore,
2289 is_new: Ignore,
2290 })
2291 }
2292
2293 pub fn nonce(&self) -> Numeric<Nonce> {
2294 self.0.nonce.clone()
2295 }
2296
2297 pub fn to_full(&self) -> MyCow<'_, Account> {
2299 MyCow::Borrow(&self.0)
2300 }
2301
2302 pub fn zcheck<Ops, Fun>(
2303 &self,
2304 new_account: Boolean,
2305 account: &crate::Account,
2306 mut check: Fun,
2307 w: &mut Witness<Fp>,
2308 ) where
2309 Ops: ZkappCheckOps,
2310 Fun: FnMut(TransactionFailure, Boolean, &mut Witness<Fp>),
2311 {
2312 let this = self.to_full();
2313 for (failure, passed) in this.zchecks::<Ops>(account, new_account, w) {
2314 check(failure, passed, w);
2315 }
2316 }
2317 }
2318
2319 #[derive(Debug, Clone, PartialEq)]
2321 pub struct Preconditions {
2322 pub(crate) network: ZkAppPreconditions,
2323 pub account: AccountPreconditions,
2324 pub valid_while: Numeric<Slot>,
2325 }
2326
2327 #[cfg(feature = "fuzzing")]
2328 impl Preconditions {
2329 pub fn new(
2330 network: ZkAppPreconditions,
2331 account: AccountPreconditions,
2332 valid_while: Numeric<Slot>,
2333 ) -> Self {
2334 Self {
2335 network,
2336 account,
2337 valid_while,
2338 }
2339 }
2340
2341 pub fn network_mut(&mut self) -> &mut ZkAppPreconditions {
2342 &mut self.network
2343 }
2344 }
2345
2346 impl ToFieldElements<Fp> for Preconditions {
2347 fn to_field_elements(&self, fields: &mut Vec<Fp>) {
2348 let Self {
2349 network,
2350 account,
2351 valid_while,
2352 } = self;
2353
2354 network.to_field_elements(fields);
2355 account.to_field_elements(fields);
2356 (valid_while, ClosedInterval::min_max).to_field_elements(fields);
2357 }
2358 }
2359
2360 impl Check<Fp> for Preconditions {
2361 fn check(&self, w: &mut Witness<Fp>) {
2362 let Self {
2363 network,
2364 account,
2365 valid_while,
2366 } = self;
2367
2368 network.check(w);
2369 account.check(w);
2370 (valid_while, ClosedInterval::min_max).check(w);
2371 }
2372 }
2373
2374 impl ToInputs for Preconditions {
2375 fn to_inputs(&self, inputs: &mut Inputs) {
2377 let Self {
2378 network,
2379 account,
2380 valid_while,
2381 } = self;
2382
2383 inputs.append(network);
2384 inputs.append(account);
2385 inputs.append(&(valid_while, ClosedInterval::min_max));
2386 }
2387 }
2388
2389 #[derive(Debug, Clone, PartialEq, Eq)]
2391 pub enum AuthorizationKind {
2392 NoneGiven,
2393 Signature,
2394 Proof(Fp), }
2396
2397 impl AuthorizationKind {
2398 pub fn vk_hash(&self) -> Fp {
2399 match self {
2400 AuthorizationKind::NoneGiven | AuthorizationKind::Signature => {
2401 VerificationKey::dummy().hash()
2402 }
2403 AuthorizationKind::Proof(hash) => *hash,
2404 }
2405 }
2406
2407 pub fn is_proved(&self) -> bool {
2408 match self {
2409 AuthorizationKind::Proof(_) => true,
2410 AuthorizationKind::NoneGiven => false,
2411 AuthorizationKind::Signature => false,
2412 }
2413 }
2414
2415 pub fn is_signed(&self) -> bool {
2416 match self {
2417 AuthorizationKind::Proof(_) => false,
2418 AuthorizationKind::NoneGiven => false,
2419 AuthorizationKind::Signature => true,
2420 }
2421 }
2422
2423 fn to_structured(&self) -> ([bool; 2], Fp) {
2424 let bits = match self {
2426 AuthorizationKind::NoneGiven => [false, false],
2427 AuthorizationKind::Signature => [true, false],
2428 AuthorizationKind::Proof(_) => [false, true],
2429 };
2430 let field = self.vk_hash();
2431 (bits, field)
2432 }
2433 }
2434
2435 impl ToInputs for AuthorizationKind {
2436 fn to_inputs(&self, inputs: &mut Inputs) {
2438 let (bits, field) = self.to_structured();
2439
2440 for bit in bits {
2441 inputs.append_bool(bit);
2442 }
2443 inputs.append_field(field);
2444 }
2445 }
2446
2447 impl ToFieldElements<Fp> for AuthorizationKind {
2448 fn to_field_elements(&self, fields: &mut Vec<Fp>) {
2449 self.to_structured().to_field_elements(fields);
2450 }
2451 }
2452
2453 #[derive(Debug, Clone, PartialEq)]
2455 pub struct Body {
2456 pub public_key: CompressedPubKey,
2457 pub token_id: TokenId,
2458 pub update: Update,
2459 pub balance_change: Signed<Amount>,
2460 pub increment_nonce: bool,
2461 pub events: Events,
2462 pub actions: Actions,
2463 pub call_data: Fp,
2464 pub preconditions: Preconditions,
2465 pub use_full_commitment: bool,
2466 pub implicit_account_creation_fee: bool,
2467 pub may_use_token: MayUseToken,
2468 pub authorization_kind: AuthorizationKind,
2469 }
2470
2471 impl ToInputs for Body {
2472 fn to_inputs(&self, inputs: &mut Inputs) {
2474 let Self {
2475 public_key,
2476 token_id,
2477 update,
2478 balance_change,
2479 increment_nonce,
2480 events,
2481 actions,
2482 call_data,
2483 preconditions,
2484 use_full_commitment,
2485 implicit_account_creation_fee,
2486 may_use_token,
2487 authorization_kind,
2488 } = self;
2489
2490 inputs.append(public_key);
2491 inputs.append(token_id);
2492
2493 {
2495 let Update {
2496 app_state,
2497 delegate,
2498 verification_key,
2499 permissions,
2500 zkapp_uri,
2501 token_symbol,
2502 timing,
2503 voting_for,
2504 } = update;
2505
2506 for state in app_state {
2507 inputs.append(&(state, Fp::zero));
2508 }
2509
2510 inputs.append(&(delegate, CompressedPubKey::empty));
2511 inputs.append(&(&verification_key.map(|w| w.hash()), Fp::zero));
2512 inputs.append(&(permissions, Permissions::empty));
2513 inputs.append(&(&zkapp_uri.map(Some), || Option::<&ZkAppUri>::None));
2514 inputs.append(&(token_symbol, TokenSymbol::default));
2515 inputs.append(&(timing, Timing::dummy));
2516 inputs.append(&(voting_for, VotingFor::dummy));
2517 }
2518
2519 inputs.append(balance_change);
2520 inputs.append(increment_nonce);
2521 inputs.append(events);
2522 inputs.append(actions);
2523 inputs.append(call_data);
2524 inputs.append(preconditions);
2525 inputs.append(use_full_commitment);
2526 inputs.append(implicit_account_creation_fee);
2527 inputs.append(may_use_token);
2528 inputs.append(authorization_kind);
2529 }
2530 }
2531
2532 impl ToFieldElements<Fp> for Body {
2533 fn to_field_elements(&self, fields: &mut Vec<Fp>) {
2534 let Self {
2535 public_key,
2536 token_id,
2537 update,
2538 balance_change,
2539 increment_nonce,
2540 events,
2541 actions,
2542 call_data,
2543 preconditions,
2544 use_full_commitment,
2545 implicit_account_creation_fee,
2546 may_use_token,
2547 authorization_kind,
2548 } = self;
2549
2550 public_key.to_field_elements(fields);
2551 token_id.to_field_elements(fields);
2552 update.to_field_elements(fields);
2553 balance_change.to_field_elements(fields);
2554 increment_nonce.to_field_elements(fields);
2555 events.to_field_elements(fields);
2556 actions.to_field_elements(fields);
2557 call_data.to_field_elements(fields);
2558 preconditions.to_field_elements(fields);
2559 use_full_commitment.to_field_elements(fields);
2560 implicit_account_creation_fee.to_field_elements(fields);
2561 may_use_token.to_field_elements(fields);
2562 authorization_kind.to_field_elements(fields);
2563 }
2564 }
2565
2566 impl Check<Fp> for Body {
2567 fn check(&self, w: &mut Witness<Fp>) {
2568 let Self {
2569 public_key: _,
2570 token_id: _,
2571 update:
2572 Update {
2573 app_state: _,
2574 delegate: _,
2575 verification_key: _,
2576 permissions,
2577 zkapp_uri: _,
2578 token_symbol,
2579 timing,
2580 voting_for: _,
2581 },
2582 balance_change,
2583 increment_nonce: _,
2584 events: _,
2585 actions: _,
2586 call_data: _,
2587 preconditions,
2588 use_full_commitment: _,
2589 implicit_account_creation_fee: _,
2590 may_use_token,
2591 authorization_kind: _,
2592 } = self;
2593
2594 (permissions, Permissions::empty).check(w);
2595 (token_symbol, TokenSymbol::default).check(w);
2596 (timing, Timing::dummy).check(w);
2597 balance_change.check(w);
2598
2599 preconditions.check(w);
2600 may_use_token.check(w);
2601 }
2602 }
2603
2604 impl Body {
2605 pub fn account_id(&self) -> AccountId {
2606 let Self {
2607 public_key,
2608 token_id,
2609 ..
2610 } = self;
2611 AccountId::create(public_key.clone(), token_id.clone())
2612 }
2613 }
2614
2615 #[derive(Debug, Clone, PartialEq)]
2617 pub struct BodySimple {
2618 pub public_key: CompressedPubKey,
2619 pub token_id: TokenId,
2620 pub update: Update,
2621 pub balance_change: Signed<Amount>,
2622 pub increment_nonce: bool,
2623 pub events: Events,
2624 pub actions: Actions,
2625 pub call_data: Fp,
2626 pub call_depth: usize,
2627 pub preconditions: Preconditions,
2628 pub use_full_commitment: bool,
2629 pub implicit_account_creation_fee: bool,
2630 pub may_use_token: MayUseToken,
2631 pub authorization_kind: AuthorizationKind,
2632 }
2633
2634 pub type SideLoadedProof = Arc<mina_p2p_messages::v2::PicklesProofProofsVerifiedMaxStableV2>;
2644
2645 #[derive(Clone, PartialEq)]
2647 pub enum Control {
2648 Proof(SideLoadedProof),
2649 Signature(Signature),
2650 NoneGiven,
2651 }
2652
2653 impl std::fmt::Debug for Control {
2654 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2655 match self {
2656 Self::Proof(_) => f.debug_tuple("Proof").field(&"_").finish(),
2657 Self::Signature(arg0) => f.debug_tuple("Signature").field(arg0).finish(),
2658 Self::NoneGiven => write!(f, "NoneGiven"),
2659 }
2660 }
2661 }
2662
2663 impl Control {
2664 pub fn tag(&self) -> crate::ControlTag {
2666 match self {
2667 Control::Proof(_) => crate::ControlTag::Proof,
2668 Control::Signature(_) => crate::ControlTag::Signature,
2669 Control::NoneGiven => crate::ControlTag::NoneGiven,
2670 }
2671 }
2672
2673 pub fn dummy_of_tag(tag: ControlTag) -> Self {
2674 match tag {
2675 ControlTag::Proof => Self::Proof(dummy::sideloaded_proof()),
2676 ControlTag::Signature => Self::Signature(Signature::dummy()),
2677 ControlTag::NoneGiven => Self::NoneGiven,
2678 }
2679 }
2680
2681 pub fn dummy(&self) -> Self {
2682 Self::dummy_of_tag(self.tag())
2683 }
2684 }
2685
2686 #[derive(Clone, Debug, PartialEq)]
2687 pub enum MayUseToken {
2688 No,
2691 ParentsOwnToken,
2695 InheritFromParent,
2697 }
2698
2699 impl MayUseToken {
2700 pub fn parents_own_token(&self) -> bool {
2701 matches!(self, Self::ParentsOwnToken)
2702 }
2703
2704 pub fn inherit_from_parent(&self) -> bool {
2705 matches!(self, Self::InheritFromParent)
2706 }
2707
2708 fn to_bits(&self) -> [bool; 2] {
2709 match self {
2711 MayUseToken::No => [false, false],
2712 MayUseToken::ParentsOwnToken => [true, false],
2713 MayUseToken::InheritFromParent => [false, true],
2714 }
2715 }
2716 }
2717
2718 impl ToInputs for MayUseToken {
2719 fn to_inputs(&self, inputs: &mut Inputs) {
2720 for bit in self.to_bits() {
2721 inputs.append_bool(bit);
2722 }
2723 }
2724 }
2725
2726 impl ToFieldElements<Fp> for MayUseToken {
2727 fn to_field_elements(&self, fields: &mut Vec<Fp>) {
2728 for bit in self.to_bits() {
2729 bit.to_field_elements(fields);
2730 }
2731 }
2732 }
2733
2734 impl Check<Fp> for MayUseToken {
2735 fn check(&self, w: &mut Witness<Fp>) {
2736 use crate::proofs::field::field;
2737
2738 let [parents_own_token, inherit_from_parent] = self.to_bits();
2739 let [parents_own_token, inherit_from_parent] = [
2740 parents_own_token.to_boolean(),
2741 inherit_from_parent.to_boolean(),
2742 ];
2743
2744 let sum = parents_own_token.to_field::<Fp>() + inherit_from_parent.to_field::<Fp>();
2745 let _sum_squared = field::mul(sum, sum, w);
2746 }
2747 }
2748
2749 pub struct CheckAuthorizationResult<Bool> {
2750 pub proof_verifies: Bool,
2751 pub signature_verifies: Bool,
2752 }
2753
2754 pub type AccountUpdate = AccountUpdateSkeleton<Body>;
2756
2757 #[derive(Debug, Clone, PartialEq)]
2758 pub struct AccountUpdateSkeleton<Body> {
2759 pub body: Body,
2760 pub authorization: Control,
2761 }
2762
2763 #[derive(Debug, Clone, PartialEq)]
2765 pub struct AccountUpdateSimple {
2766 pub body: BodySimple,
2767 pub authorization: Control,
2768 }
2769
2770 impl ToInputs for AccountUpdate {
2771 fn to_inputs(&self, inputs: &mut Inputs) {
2773 let Self {
2775 body,
2776 authorization: _,
2777 } = self;
2778
2779 inputs.append(body);
2780 }
2781 }
2782
2783 impl AccountUpdate {
2784 pub fn of_fee_payer(fee_payer: FeePayer) -> Self {
2787 let FeePayer {
2788 body:
2789 FeePayerBody {
2790 public_key,
2791 fee,
2792 valid_until,
2793 nonce,
2794 },
2795 authorization,
2796 } = fee_payer;
2797
2798 Self {
2799 body: Body {
2800 public_key,
2801 token_id: TokenId::default(),
2802 update: Update::noop(),
2803 balance_change: Signed {
2804 magnitude: Amount::of_fee(&fee),
2805 sgn: Sgn::Neg,
2806 },
2807 increment_nonce: true,
2808 events: Events::empty(),
2809 actions: Actions::empty(),
2810 call_data: Fp::zero(),
2811 preconditions: Preconditions {
2812 network: {
2813 let mut network = ZkAppPreconditions::accept();
2814
2815 let valid_util = valid_until.unwrap_or_else(Slot::max);
2816 network.global_slot_since_genesis = OrIgnore::Check(ClosedInterval {
2817 lower: Slot::zero(),
2818 upper: valid_util,
2819 });
2820
2821 network
2822 },
2823 account: AccountPreconditions::with_nonce(nonce),
2824 valid_while: Numeric::Ignore,
2825 },
2826 use_full_commitment: true,
2827 authorization_kind: AuthorizationKind::Signature,
2828 implicit_account_creation_fee: true,
2829 may_use_token: MayUseToken::No,
2830 },
2831 authorization: Control::Signature(authorization),
2832 }
2833 }
2834
2835 pub fn account_id(&self) -> AccountId {
2837 AccountId::new(self.body.public_key.clone(), self.body.token_id.clone())
2838 }
2839
2840 pub fn digest(&self) -> Fp {
2842 self.hash_with_param(mina_core::NetworkConfig::global().account_update_hash_param)
2843 }
2844
2845 pub fn timing(&self) -> SetOrKeep<Timing> {
2846 self.body.update.timing.clone()
2847 }
2848
2849 pub fn may_use_parents_own_token(&self) -> bool {
2850 self.body.may_use_token.parents_own_token()
2851 }
2852
2853 pub fn may_use_token_inherited_from_parent(&self) -> bool {
2854 self.body.may_use_token.inherit_from_parent()
2855 }
2856
2857 pub fn public_key(&self) -> CompressedPubKey {
2858 self.body.public_key.clone()
2859 }
2860
2861 pub fn token_id(&self) -> TokenId {
2862 self.body.token_id.clone()
2863 }
2864
2865 pub fn increment_nonce(&self) -> bool {
2866 self.body.increment_nonce
2867 }
2868
2869 pub fn implicit_account_creation_fee(&self) -> bool {
2870 self.body.implicit_account_creation_fee
2871 }
2872
2873 pub fn check_authorization(
2875 &self,
2876 _will_succeed: bool,
2877 _commitment: Fp,
2878 _calls: CallForest<AccountUpdate>,
2879 ) -> CheckAuthorizationResult<bool> {
2880 match self.authorization {
2881 Control::Signature(_) => CheckAuthorizationResult {
2882 proof_verifies: false,
2883 signature_verifies: true,
2884 },
2885 Control::Proof(_) => CheckAuthorizationResult {
2886 proof_verifies: true,
2887 signature_verifies: false,
2888 },
2889 Control::NoneGiven => CheckAuthorizationResult {
2890 proof_verifies: false,
2891 signature_verifies: false,
2892 },
2893 }
2894 }
2895
2896 pub fn permissions(&self) -> SetOrKeep<Permissions<AuthRequired>> {
2897 self.body.update.permissions.clone()
2898 }
2899
2900 pub fn app_state(&self) -> [SetOrKeep<Fp>; 8] {
2901 self.body.update.app_state.clone()
2902 }
2903
2904 pub fn zkapp_uri(&self) -> SetOrKeep<ZkAppUri> {
2905 self.body.update.zkapp_uri.clone()
2906 }
2907
2908 pub fn token_symbol(&self) -> SetOrKeep<TokenSymbol> {
2915 self.body.update.token_symbol.clone()
2916 }
2917
2918 pub fn delegate(&self) -> SetOrKeep<CompressedPubKey> {
2919 self.body.update.delegate.clone()
2920 }
2921
2922 pub fn voting_for(&self) -> SetOrKeep<VotingFor> {
2923 self.body.update.voting_for.clone()
2924 }
2925
2926 pub fn verification_key(&self) -> SetOrKeep<VerificationKeyWire> {
2927 self.body.update.verification_key.clone()
2928 }
2929
2930 pub fn valid_while_precondition(&self) -> OrIgnore<ClosedInterval<Slot>> {
2931 self.body.preconditions.valid_while.clone()
2932 }
2933
2934 pub fn actions(&self) -> Actions {
2935 self.body.actions.clone()
2936 }
2937
2938 pub fn balance_change(&self) -> Signed<Amount> {
2939 self.body.balance_change
2940 }
2941 pub fn use_full_commitment(&self) -> bool {
2942 self.body.use_full_commitment
2943 }
2944
2945 pub fn protocol_state_precondition(&self) -> ZkAppPreconditions {
2946 self.body.preconditions.network.clone()
2947 }
2948
2949 pub fn account_precondition(&self) -> AccountPreconditions {
2950 self.body.preconditions.account.clone()
2951 }
2952
2953 pub fn is_proved(&self) -> bool {
2954 match &self.body.authorization_kind {
2955 AuthorizationKind::Proof(_) => true,
2956 AuthorizationKind::Signature | AuthorizationKind::NoneGiven => false,
2957 }
2958 }
2959
2960 pub fn is_signed(&self) -> bool {
2961 match &self.body.authorization_kind {
2962 AuthorizationKind::Signature => true,
2963 AuthorizationKind::Proof(_) | AuthorizationKind::NoneGiven => false,
2964 }
2965 }
2966
2967 pub fn verification_key_hash(&self) -> Option<Fp> {
2969 match &self.body.authorization_kind {
2970 AuthorizationKind::Proof(vk_hash) => Some(*vk_hash),
2971 _ => None,
2972 }
2973 }
2974
2975 pub fn of_simple(simple: &AccountUpdateSimple) -> Self {
2977 let AccountUpdateSimple {
2978 body:
2979 BodySimple {
2980 public_key,
2981 token_id,
2982 update,
2983 balance_change,
2984 increment_nonce,
2985 events,
2986 actions,
2987 call_data,
2988 call_depth: _,
2989 preconditions,
2990 use_full_commitment,
2991 implicit_account_creation_fee,
2992 may_use_token,
2993 authorization_kind,
2994 },
2995 authorization,
2996 } = simple.clone();
2997
2998 Self {
2999 body: Body {
3000 public_key,
3001 token_id,
3002 update,
3003 balance_change,
3004 increment_nonce,
3005 events,
3006 actions,
3007 call_data,
3008 preconditions,
3009 use_full_commitment,
3010 implicit_account_creation_fee,
3011 may_use_token,
3012 authorization_kind,
3013 },
3014 authorization,
3015 }
3016 }
3017
3018 pub fn rand() -> Self {
3020 let mut rng = rand::thread_rng();
3021 let rng = &mut rng;
3022
3023 Self {
3024 body: Body {
3025 public_key: gen_compressed(),
3026 token_id: TokenId(Fp::rand(rng)),
3027 update: Update {
3028 app_state: std::array::from_fn(|_| SetOrKeep::gen(|| Fp::rand(rng))),
3029 delegate: SetOrKeep::gen(gen_compressed),
3030 verification_key: SetOrKeep::gen(VerificationKeyWire::gen),
3031 permissions: SetOrKeep::gen(|| {
3032 let auth_tag = [
3033 ControlTag::NoneGiven,
3034 ControlTag::Proof,
3035 ControlTag::Signature,
3036 ]
3037 .choose(rng)
3038 .unwrap();
3039
3040 Permissions::gen(*auth_tag)
3041 }),
3042 zkapp_uri: SetOrKeep::gen(ZkAppUri::gen),
3043 token_symbol: SetOrKeep::gen(TokenSymbol::gen),
3044 timing: SetOrKeep::gen(|| Timing {
3045 initial_minimum_balance: rng.gen(),
3046 cliff_time: rng.gen(),
3047 cliff_amount: rng.gen(),
3048 vesting_period: rng.gen(),
3049 vesting_increment: rng.gen(),
3050 }),
3051 voting_for: SetOrKeep::gen(|| VotingFor(Fp::rand(rng))),
3052 },
3053 balance_change: Signed::gen(),
3054 increment_nonce: rng.gen(),
3055 events: Events(gen_events()),
3056 actions: Actions(gen_events()),
3057 call_data: Fp::rand(rng),
3058 preconditions: Preconditions {
3059 network: ZkAppPreconditions {
3060 snarked_ledger_hash: OrIgnore::gen(|| Fp::rand(rng)),
3061 blockchain_length: OrIgnore::gen(|| ClosedInterval::gen(|| rng.gen())),
3062 min_window_density: OrIgnore::gen(|| ClosedInterval::gen(|| rng.gen())),
3063 total_currency: OrIgnore::gen(|| ClosedInterval::gen(|| rng.gen())),
3064 global_slot_since_genesis: OrIgnore::gen(|| {
3065 ClosedInterval::gen(|| rng.gen())
3066 }),
3067 staking_epoch_data: EpochData::gen(),
3068 next_epoch_data: EpochData::gen(),
3069 },
3070 account: AccountPreconditions(Account {
3071 balance: OrIgnore::gen(|| ClosedInterval::gen(|| rng.gen())),
3072 nonce: OrIgnore::gen(|| ClosedInterval::gen(|| rng.gen())),
3073 receipt_chain_hash: OrIgnore::gen(|| Fp::rand(rng)),
3074 delegate: OrIgnore::gen(gen_compressed),
3075 state: std::array::from_fn(|_| OrIgnore::gen(|| Fp::rand(rng))),
3076 action_state: OrIgnore::gen(|| Fp::rand(rng)),
3077 proved_state: OrIgnore::gen(|| rng.gen()),
3078 is_new: OrIgnore::gen(|| rng.gen()),
3079 }),
3080 valid_while: OrIgnore::gen(|| ClosedInterval::gen(|| rng.gen())),
3081 },
3082 use_full_commitment: rng.gen(),
3083 implicit_account_creation_fee: rng.gen(),
3084 may_use_token: {
3085 match MayUseToken::No {
3086 MayUseToken::No => (),
3087 MayUseToken::ParentsOwnToken => (),
3088 MayUseToken::InheritFromParent => (),
3089 };
3090
3091 [
3092 MayUseToken::No,
3093 MayUseToken::InheritFromParent,
3094 MayUseToken::ParentsOwnToken,
3095 ]
3096 .choose(rng)
3097 .cloned()
3098 .unwrap()
3099 },
3100 authorization_kind: {
3101 match AuthorizationKind::NoneGiven {
3102 AuthorizationKind::NoneGiven => (),
3103 AuthorizationKind::Signature => (),
3104 AuthorizationKind::Proof(_) => (),
3105 };
3106
3107 [
3108 AuthorizationKind::NoneGiven,
3109 AuthorizationKind::Signature,
3110 AuthorizationKind::Proof(Fp::rand(rng)),
3111 ]
3112 .choose(rng)
3113 .cloned()
3114 .unwrap()
3115 },
3116 },
3117 authorization: {
3118 match Control::NoneGiven {
3119 Control::Proof(_) => (),
3120 Control::Signature(_) => (),
3121 Control::NoneGiven => (),
3122 };
3123
3124 match rng.gen_range(0..3) {
3125 0 => Control::NoneGiven,
3126 1 => Control::Signature(Signature::dummy()),
3127 _ => Control::Proof(dummy::sideloaded_proof()),
3128 }
3129 },
3130 }
3131 }
3132 }
3133
3134 #[derive(Debug, Clone, PartialEq)]
3136 pub struct Tree<AccUpdate: Clone + AccountUpdateRef> {
3137 pub account_update: AccUpdate,
3138 pub account_update_digest: MutableFp,
3139 pub calls: CallForest<AccUpdate>,
3140 }
3141
3142 impl<AccUpdate: Clone + AccountUpdateRef> Tree<AccUpdate> {
3143 pub fn digest(&self) -> Fp {
3145 let stack_hash = match self.calls.0.first() {
3146 Some(e) => e.stack_hash.get().expect("Must call `ensure_hashed`"),
3147 None => Fp::zero(),
3148 };
3149 let account_update_digest = self.account_update_digest.get().unwrap();
3150 hash_with_kimchi(
3151 &MINA_ACCOUNT_UPDATE_NODE,
3152 &[account_update_digest, stack_hash],
3153 )
3154 }
3155
3156 fn fold<F>(&self, init: Vec<AccountId>, f: &mut F) -> Vec<AccountId>
3157 where
3158 F: FnMut(Vec<AccountId>, &AccUpdate) -> Vec<AccountId>,
3159 {
3160 self.calls.fold(f(init, &self.account_update), f)
3161 }
3162 }
3163
3164 #[derive(Debug, Clone)]
3166 pub struct WithStackHash<AccUpdate: Clone + AccountUpdateRef> {
3167 pub elt: Tree<AccUpdate>,
3168 pub stack_hash: MutableFp,
3169 }
3170
3171 impl<AccUpdate: Clone + AccountUpdateRef + PartialEq> PartialEq for WithStackHash<AccUpdate> {
3172 fn eq(&self, other: &Self) -> bool {
3173 self.elt == other.elt && self.stack_hash == other.stack_hash
3174 }
3175 }
3176
3177 #[derive(Debug, Clone, PartialEq)]
3179 pub struct CallForest<AccUpdate: Clone + AccountUpdateRef>(pub Vec<WithStackHash<AccUpdate>>);
3180
3181 impl<Data: Clone + AccountUpdateRef> Default for CallForest<Data> {
3182 fn default() -> Self {
3183 Self::new()
3184 }
3185 }
3186
3187 #[derive(Clone)]
3188 struct CallForestContext {
3189 caller: TokenId,
3190 this: TokenId,
3191 }
3192
3193 pub trait AccountUpdateRef {
3194 fn account_update_ref(&self) -> &AccountUpdate;
3195 }
3196 impl AccountUpdateRef for AccountUpdate {
3197 fn account_update_ref(&self) -> &AccountUpdate {
3198 self
3199 }
3200 }
3201 impl<T> AccountUpdateRef for (AccountUpdate, T) {
3202 fn account_update_ref(&self) -> &AccountUpdate {
3203 let (this, _) = self;
3204 this
3205 }
3206 }
3207 impl AccountUpdateRef for AccountUpdateSimple {
3208 fn account_update_ref(&self) -> &AccountUpdate {
3209 unreachable!()
3211 }
3212 }
3213
3214 impl<AccUpdate: Clone + AccountUpdateRef> CallForest<AccUpdate> {
3215 pub fn new() -> Self {
3216 Self(Vec::new())
3217 }
3218
3219 pub fn empty() -> Self {
3220 Self::new()
3221 }
3222
3223 pub fn is_empty(&self) -> bool {
3224 self.0.is_empty()
3225 }
3226
3227 pub fn iter(&self) -> impl Iterator<Item = &WithStackHash<AccUpdate>> {
3231 self.0.iter() }
3233 pub fn first(&self) -> Option<&WithStackHash<AccUpdate>> {
3235 self.0.first()
3236 }
3237 pub fn tail(&self) -> Option<&[WithStackHash<AccUpdate>]> {
3239 self.0.get(1..)
3240 }
3241
3242 pub fn hash(&self) -> Fp {
3243 self.ensure_hashed();
3244 if let Some(x) = self.first() {
3251 x.stack_hash.get().unwrap() } else {
3253 Fp::zero()
3254 }
3255 }
3256
3257 fn cons_tree(&self, tree: Tree<AccUpdate>) -> Self {
3258 self.ensure_hashed();
3259
3260 let hash = tree.digest();
3261 let h_tl = self.hash();
3262
3263 let stack_hash = hash_with_kimchi(&MINA_ACCOUNT_UPDATE_CONS, &[hash, h_tl]);
3264 let node = WithStackHash::<AccUpdate> {
3265 elt: tree,
3266 stack_hash: MutableFp::new(stack_hash),
3267 };
3268 let mut forest = Vec::with_capacity(self.0.len() + 1);
3269 forest.push(node);
3270 forest.extend(self.0.iter().cloned());
3271
3272 Self(forest)
3273 }
3274
3275 pub fn pop_exn(&self) -> ((AccUpdate, CallForest<AccUpdate>), CallForest<AccUpdate>) {
3276 if self.0.is_empty() {
3277 panic!()
3278 }
3279
3280 let Tree::<AccUpdate> {
3281 account_update,
3282 calls,
3283 ..
3284 } = self.0[0].elt.clone();
3285 (
3286 (account_update, calls),
3287 CallForest(Vec::from_iter(self.0[1..].iter().cloned())),
3288 )
3289 }
3290
3291 fn fold_impl<'a, A, F>(&'a self, init: A, fun: &mut F) -> A
3293 where
3294 F: FnMut(A, &'a AccUpdate) -> A,
3295 {
3296 let mut accum = init;
3297 for elem in self.iter() {
3298 accum = fun(accum, &elem.elt.account_update);
3299 accum = elem.elt.calls.fold_impl(accum, fun);
3300 }
3301 accum
3302 }
3303
3304 pub fn fold<'a, A, F>(&'a self, init: A, mut fun: F) -> A
3305 where
3306 F: FnMut(A, &'a AccUpdate) -> A,
3307 {
3308 self.fold_impl(init, &mut fun)
3309 }
3310
3311 pub fn exists<'a, F>(&'a self, mut fun: F) -> bool
3312 where
3313 F: FnMut(&'a AccUpdate) -> bool,
3314 {
3315 self.fold(false, |acc, x| acc || fun(x))
3316 }
3317
3318 fn map_to_impl<F, AnotherAccUpdate: Clone + AccountUpdateRef>(
3319 &self,
3320 fun: &F,
3321 ) -> CallForest<AnotherAccUpdate>
3322 where
3323 F: Fn(&AccUpdate) -> AnotherAccUpdate,
3324 {
3325 CallForest::<AnotherAccUpdate>(
3326 self.iter()
3327 .map(|item| WithStackHash::<AnotherAccUpdate> {
3328 elt: Tree::<AnotherAccUpdate> {
3329 account_update: fun(&item.elt.account_update),
3330 account_update_digest: item.elt.account_update_digest.clone(),
3331 calls: item.elt.calls.map_to_impl(fun),
3332 },
3333 stack_hash: item.stack_hash.clone(),
3334 })
3335 .collect(),
3336 )
3337 }
3338
3339 #[must_use]
3340 pub fn map_to<F, AnotherAccUpdate: Clone + AccountUpdateRef>(
3341 &self,
3342 fun: F,
3343 ) -> CallForest<AnotherAccUpdate>
3344 where
3345 F: Fn(&AccUpdate) -> AnotherAccUpdate,
3346 {
3347 self.map_to_impl(&fun)
3348 }
3349
3350 fn map_with_trees_to_impl<F, AnotherAccUpdate: Clone + AccountUpdateRef>(
3351 &self,
3352 fun: &F,
3353 ) -> CallForest<AnotherAccUpdate>
3354 where
3355 F: Fn(&AccUpdate, &Tree<AccUpdate>) -> AnotherAccUpdate,
3356 {
3357 CallForest::<AnotherAccUpdate>(
3358 self.iter()
3359 .map(|item| {
3360 let account_update = fun(&item.elt.account_update, &item.elt);
3361
3362 WithStackHash::<AnotherAccUpdate> {
3363 elt: Tree::<AnotherAccUpdate> {
3364 account_update,
3365 account_update_digest: item.elt.account_update_digest.clone(),
3366 calls: item.elt.calls.map_with_trees_to_impl(fun),
3367 },
3368 stack_hash: item.stack_hash.clone(),
3369 }
3370 })
3371 .collect(),
3372 )
3373 }
3374
3375 #[must_use]
3376 pub fn map_with_trees_to<F, AnotherAccUpdate: Clone + AccountUpdateRef>(
3377 &self,
3378 fun: F,
3379 ) -> CallForest<AnotherAccUpdate>
3380 where
3381 F: Fn(&AccUpdate, &Tree<AccUpdate>) -> AnotherAccUpdate,
3382 {
3383 self.map_with_trees_to_impl(&fun)
3384 }
3385
3386 fn try_map_to_impl<F, E, AnotherAccUpdate: Clone + AccountUpdateRef>(
3387 &self,
3388 fun: &mut F,
3389 ) -> Result<CallForest<AnotherAccUpdate>, E>
3390 where
3391 F: FnMut(&AccUpdate) -> Result<AnotherAccUpdate, E>,
3392 {
3393 Ok(CallForest::<AnotherAccUpdate>(
3394 self.iter()
3395 .map(|item| {
3396 Ok(WithStackHash::<AnotherAccUpdate> {
3397 elt: Tree::<AnotherAccUpdate> {
3398 account_update: fun(&item.elt.account_update)?,
3399 account_update_digest: item.elt.account_update_digest.clone(),
3400 calls: item.elt.calls.try_map_to_impl(fun)?,
3401 },
3402 stack_hash: item.stack_hash.clone(),
3403 })
3404 })
3405 .collect::<Result<_, E>>()?,
3406 ))
3407 }
3408
3409 pub fn try_map_to<F, E, AnotherAccUpdate: Clone + AccountUpdateRef>(
3410 &self,
3411 mut fun: F,
3412 ) -> Result<CallForest<AnotherAccUpdate>, E>
3413 where
3414 F: FnMut(&AccUpdate) -> Result<AnotherAccUpdate, E>,
3415 {
3416 self.try_map_to_impl(&mut fun)
3417 }
3418
3419 fn to_account_updates_impl(&self, accounts: &mut Vec<AccUpdate>) {
3420 for elem in self.iter() {
3422 accounts.push(elem.elt.account_update.clone());
3423 elem.elt.calls.to_account_updates_impl(accounts);
3424 }
3425 }
3426
3427 pub fn to_account_updates(&self) -> Vec<AccUpdate> {
3429 let mut accounts = Vec::with_capacity(128);
3430 self.to_account_updates_impl(&mut accounts);
3431 accounts
3432 }
3433
3434 fn to_zkapp_command_with_hashes_list_impl(&self, output: &mut Vec<(AccUpdate, Fp)>) {
3435 self.iter().for_each(|item| {
3436 let WithStackHash { elt, stack_hash } = item;
3437 let Tree {
3438 account_update,
3439 account_update_digest: _,
3440 calls,
3441 } = elt;
3442 output.push((account_update.clone(), stack_hash.get().unwrap())); calls.to_zkapp_command_with_hashes_list_impl(output);
3444 });
3445 }
3446
3447 pub fn to_zkapp_command_with_hashes_list(&self) -> Vec<(AccUpdate, Fp)> {
3448 self.ensure_hashed();
3449
3450 let mut output = Vec::with_capacity(128);
3451 self.to_zkapp_command_with_hashes_list_impl(&mut output);
3452 output
3453 }
3454
3455 pub fn ensure_hashed(&self) {
3456 let Some(first) = self.first() else {
3457 return;
3458 };
3459 if first.stack_hash.get().is_none() {
3460 self.accumulate_hashes();
3461 }
3462 }
3463 }
3464
3465 impl<AccUpdate: Clone + AccountUpdateRef> CallForest<AccUpdate> {
3466 pub fn accumulate_hashes(&self) {
3468 fn cons(hash: Fp, h_tl: Fp) -> Fp {
3470 hash_with_kimchi(&MINA_ACCOUNT_UPDATE_CONS, &[hash, h_tl])
3471 }
3472
3473 fn hash<AccUpdate: Clone + AccountUpdateRef>(
3475 elem: Option<&WithStackHash<AccUpdate>>,
3476 ) -> Fp {
3477 match elem {
3478 Some(next) => next.stack_hash.get().unwrap(), None => Fp::zero(),
3480 }
3481 }
3482
3483 for index in (0..self.0.len()).rev() {
3489 let elem = &self.0[index];
3490 let WithStackHash {
3491 elt:
3492 Tree::<AccUpdate> {
3493 account_update,
3494 account_update_digest,
3495 calls,
3496 ..
3497 },
3498 ..
3499 } = elem;
3500
3501 calls.accumulate_hashes();
3502 account_update_digest.set(account_update.account_update_ref().digest());
3503
3504 let node_hash = elem.elt.digest();
3505 let hash = hash(self.0.get(index + 1));
3506
3507 self.0[index].stack_hash.set(cons(node_hash, hash));
3508 }
3509 }
3510 }
3511
3512 impl CallForest<AccountUpdate> {
3513 pub fn cons(
3514 &self,
3515 calls: Option<CallForest<AccountUpdate>>,
3516 account_update: AccountUpdate,
3517 ) -> Self {
3518 let account_update_digest = account_update.digest();
3519
3520 let tree = Tree::<AccountUpdate> {
3521 account_update,
3522 account_update_digest: MutableFp::new(account_update_digest),
3523 calls: calls.unwrap_or_else(|| CallForest(Vec::new())),
3524 };
3525 self.cons_tree(tree)
3526 }
3527
3528 pub fn accumulate_hashes_predicated(&mut self) {
3529 self.accumulate_hashes();
3531 }
3532
3533 pub fn of_wire(
3535 &mut self,
3536 _wired: &[MinaBaseZkappCommandTStableV1WireStableV1AccountUpdatesA],
3537 ) {
3538 self.accumulate_hashes();
3539 }
3540
3541 pub fn to_wire(
3543 &self,
3544 _wired: &mut [MinaBaseZkappCommandTStableV1WireStableV1AccountUpdatesA],
3545 ) {
3546 }
3548 }
3549
3550 impl CallForest<(AccountUpdate, Option<WithHash<VerificationKey>>)> {
3551 }
3569
3570 #[derive(Debug, Clone, PartialEq, Eq)]
3572 pub struct FeePayerBody {
3573 pub public_key: CompressedPubKey,
3574 pub fee: Fee,
3575 pub valid_until: Option<Slot>,
3576 pub nonce: Nonce,
3577 }
3578
3579 #[derive(Debug, Clone, PartialEq, Eq)]
3581 pub struct FeePayer {
3582 pub body: FeePayerBody,
3583 pub authorization: Signature,
3584 }
3585
3586 #[derive(Debug, Clone, PartialEq)]
3588 pub struct ZkAppCommand {
3589 pub fee_payer: FeePayer,
3590 pub account_updates: CallForest<AccountUpdate>,
3591 pub memo: Memo,
3592 }
3593
3594 #[derive(Debug, Clone, PartialEq, Hash, Eq, Ord, PartialOrd)]
3595 pub enum AccessedOrNot {
3596 Accessed,
3597 NotAccessed,
3598 }
3599
3600 impl ZkAppCommand {
3601 pub fn fee_payer(&self) -> AccountId {
3602 let public_key = self.fee_payer.body.public_key.clone();
3603 AccountId::new(public_key, self.fee_token())
3604 }
3605
3606 pub fn fee_token(&self) -> TokenId {
3607 TokenId::default()
3608 }
3609
3610 pub fn fee(&self) -> Fee {
3611 self.fee_payer.body.fee
3612 }
3613
3614 pub fn fee_excess(&self) -> FeeExcess {
3615 FeeExcess::of_single((self.fee_token(), Signed::<Fee>::of_unsigned(self.fee())))
3616 }
3617
3618 fn fee_payer_account_update(&self) -> &FeePayer {
3619 let Self { fee_payer, .. } = self;
3620 fee_payer
3621 }
3622
3623 pub fn applicable_at_nonce(&self) -> Nonce {
3624 self.fee_payer_account_update().body.nonce
3625 }
3626
3627 pub fn weight(&self) -> u64 {
3628 let Self {
3629 fee_payer,
3630 account_updates,
3631 memo,
3632 } = self;
3633 [
3634 zkapp_weight::fee_payer(fee_payer),
3635 zkapp_weight::account_updates(account_updates),
3636 zkapp_weight::memo(memo),
3637 ]
3638 .iter()
3639 .sum()
3640 }
3641
3642 pub fn has_zero_vesting_period(&self) -> bool {
3643 self.account_updates
3644 .exists(|account_update| match &account_update.body.update.timing {
3645 SetOrKeep::Keep => false,
3646 SetOrKeep::Set(Timing { vesting_period, .. }) => vesting_period.is_zero(),
3647 })
3648 }
3649
3650 pub fn is_incompatible_version(&self) -> bool {
3651 self.account_updates.exists(|account_update| {
3652 match &account_update.body.update.permissions {
3653 SetOrKeep::Keep => false,
3654 SetOrKeep::Set(Permissions {
3655 set_verification_key,
3656 ..
3657 }) => {
3658 let SetVerificationKey {
3659 auth: _,
3660 txn_version,
3661 } = set_verification_key;
3662 *txn_version != crate::TXN_VERSION_CURRENT
3663 }
3664 }
3665 })
3666 }
3667
3668 fn zkapp_cost(
3669 proof_segments: usize,
3670 signed_single_segments: usize,
3671 signed_pair_segments: usize,
3672 ) -> f64 {
3673 let GenesisConstant {
3675 zkapp_proof_update_cost: proof_cost,
3676 zkapp_signed_pair_update_cost: signed_pair_cost,
3677 zkapp_signed_single_update_cost: signed_single_cost,
3678 ..
3679 } = GENESIS_CONSTANT;
3680
3681 (proof_cost * (proof_segments as f64))
3682 + (signed_pair_cost * (signed_pair_segments as f64))
3683 + (signed_single_cost * (signed_single_segments as f64))
3684 }
3685
3686 pub fn valid_size(&self) -> Result<(), String> {
3690 use crate::proofs::zkapp::group::{SegmentBasic, ZkappCommandIntermediateState};
3691
3692 let Self {
3693 account_updates,
3694 fee_payer: _,
3695 memo: _,
3696 } = self;
3697
3698 let events_elements =
3699 |events: &[Event]| -> usize { events.iter().map(Event::len).sum() };
3700
3701 let mut n_account_updates = 0;
3702 let (mut num_event_elements, mut num_action_elements) = (0, 0);
3703
3704 account_updates.fold((), |_, account_update| {
3705 num_event_elements += events_elements(account_update.body.events.events());
3706 num_action_elements += events_elements(account_update.body.actions.events());
3707 n_account_updates += 1;
3708 });
3709
3710 let group = std::iter::repeat(((), (), ()))
3711 .take(n_account_updates + 2) .collect::<Vec<_>>();
3713
3714 let groups = crate::proofs::zkapp::group::group_by_zkapp_command_rev::<_, (), (), ()>(
3715 [self],
3716 vec![vec![((), (), ())], group],
3717 );
3718
3719 let (mut proof_segments, mut signed_single_segments, mut signed_pair_segments) =
3720 (0, 0, 0);
3721
3722 for ZkappCommandIntermediateState { spec, .. } in &groups {
3723 match spec {
3724 SegmentBasic::Proved => proof_segments += 1,
3725 SegmentBasic::OptSigned => signed_single_segments += 1,
3726 SegmentBasic::OptSignedOptSigned => signed_pair_segments += 1,
3727 }
3728 }
3729
3730 let GenesisConstant {
3731 zkapp_transaction_cost_limit: cost_limit,
3732 max_event_elements,
3733 max_action_elements,
3734 ..
3735 } = GENESIS_CONSTANT;
3736
3737 let zkapp_cost_within_limit =
3738 Self::zkapp_cost(proof_segments, signed_single_segments, signed_pair_segments)
3739 < cost_limit;
3740 let valid_event_elements = num_event_elements <= max_event_elements;
3741 let valid_action_elements = num_action_elements <= max_action_elements;
3742
3743 if zkapp_cost_within_limit && valid_event_elements && valid_action_elements {
3744 return Ok(());
3745 }
3746
3747 let err = [
3748 (zkapp_cost_within_limit, "zkapp transaction too expensive"),
3749 (valid_event_elements, "too many event elements"),
3750 (valid_action_elements, "too many action elements"),
3751 ]
3752 .iter()
3753 .filter(|(b, _s)| !b)
3754 .map(|(_b, s)| s)
3755 .join(";");
3756
3757 Err(err)
3758 }
3759
3760 pub fn account_access_statuses(
3762 &self,
3763 status: &TransactionStatus,
3764 ) -> Vec<(AccountId, AccessedOrNot)> {
3765 use AccessedOrNot::*;
3766 use TransactionStatus::*;
3767
3768 let init = vec![(self.fee_payer(), Accessed)];
3770
3771 let status_sym = match status {
3772 Applied => Accessed,
3773 Failed(_) => NotAccessed,
3774 };
3775
3776 let ids = self
3777 .account_updates
3778 .fold(init, |mut accum, account_update| {
3779 accum.push((account_update.account_id(), status_sym.clone()));
3780 accum
3781 });
3782 ids.iter()
3784 .unique() .cloned()
3786 .collect()
3787 }
3788
3789 pub fn accounts_referenced(&self) -> Vec<AccountId> {
3791 self.account_access_statuses(&TransactionStatus::Applied)
3792 .into_iter()
3793 .map(|(id, _status)| id)
3794 .collect()
3795 }
3796
3797 pub fn of_verifiable(verifiable: verifiable::ZkAppCommand) -> Self {
3799 Self {
3800 fee_payer: verifiable.fee_payer,
3801 account_updates: verifiable.account_updates.map_to(|(acc, _)| acc.clone()),
3802 memo: verifiable.memo,
3803 }
3804 }
3805
3806 pub fn account_updates_hash(&self) -> Fp {
3808 self.account_updates.hash()
3809 }
3810
3811 pub fn extract_vks(&self) -> Vec<(AccountId, VerificationKeyWire)> {
3813 self.account_updates
3814 .fold(Vec::with_capacity(256), |mut acc, p| {
3815 if let SetOrKeep::Set(vk) = &p.body.update.verification_key {
3816 acc.push((p.account_id(), vk.clone()));
3817 };
3818 acc
3819 })
3820 }
3821
3822 pub fn all_account_updates(&self) -> CallForest<AccountUpdate> {
3823 let p = &self.fee_payer;
3824
3825 let mut fee_payer = AccountUpdate::of_fee_payer(p.clone());
3826 fee_payer.authorization = Control::Signature(p.authorization.clone());
3827
3828 self.account_updates.cons(None, fee_payer)
3829 }
3830
3831 pub fn all_account_updates_list(&self) -> Vec<AccountUpdate> {
3832 let mut account_updates = Vec::with_capacity(16);
3833 account_updates.push(AccountUpdate::of_fee_payer(self.fee_payer.clone()));
3834
3835 self.account_updates.fold(account_updates, |mut acc, u| {
3836 acc.push(u.clone());
3837 acc
3838 })
3839 }
3840
3841 pub fn commitment(&self) -> TransactionCommitment {
3842 let account_updates_hash = self.account_updates_hash();
3843 TransactionCommitment::create(account_updates_hash)
3844 }
3845 }
3846
3847 pub mod verifiable {
3848 use mina_p2p_messages::v2::MinaBaseZkappCommandVerifiableStableV1;
3849
3850 use super::*;
3851
3852 #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
3853 #[serde(try_from = "MinaBaseZkappCommandVerifiableStableV1")]
3854 #[serde(into = "MinaBaseZkappCommandVerifiableStableV1")]
3855 pub struct ZkAppCommand {
3856 pub fee_payer: FeePayer,
3857 pub account_updates: CallForest<(AccountUpdate, Option<VerificationKeyWire>)>,
3858 pub memo: Memo,
3859 }
3860
3861 fn ok_if_vk_hash_expected(
3862 got: VerificationKeyWire,
3863 expected: Fp,
3864 ) -> Result<VerificationKeyWire, String> {
3865 if got.hash() == expected {
3866 return Ok(got.clone());
3867 }
3868 Err(format!(
3869 "Expected vk hash doesn't match hash in vk we received\
3870 expected: {:?}\
3871 got: {:?}",
3872 expected, got
3873 ))
3874 }
3875
3876 pub fn find_vk_via_ledger<L>(
3877 ledger: L,
3878 expected_vk_hash: Fp,
3879 account_id: &AccountId,
3880 ) -> Result<VerificationKeyWire, String>
3881 where
3882 L: LedgerIntf + Clone,
3883 {
3884 let vk = ledger
3885 .location_of_account(account_id)
3886 .and_then(|location| ledger.get(&location))
3887 .and_then(|account| {
3888 account
3889 .zkapp
3890 .as_ref()
3891 .and_then(|zkapp| zkapp.verification_key.clone())
3892 });
3893
3894 match vk {
3895 Some(vk) => ok_if_vk_hash_expected(vk, expected_vk_hash),
3896 None => Err(format!(
3897 "No verification key found for proved account update\
3898 account_id: {:?}",
3899 account_id
3900 )),
3901 }
3902 }
3903
3904 fn check_authorization(p: &AccountUpdate) -> Result<(), String> {
3905 use AuthorizationKind as AK;
3906 use Control as C;
3907
3908 match (&p.authorization, &p.body.authorization_kind) {
3909 (C::NoneGiven, AK::NoneGiven)
3910 | (C::Proof(_), AK::Proof(_))
3911 | (C::Signature(_), AK::Signature) => Ok(()),
3912 _ => Err(format!(
3913 "Authorization kind does not match the authorization\
3914 expected={:#?}\
3915 got={:#?}",
3916 p.body.authorization_kind, p.authorization
3917 )),
3918 }
3919 }
3920
3921 pub fn create(
3929 zkapp: &super::ZkAppCommand,
3930 is_failed: bool,
3931 find_vk: impl Fn(Fp, &AccountId) -> Result<VerificationKeyWire, String>,
3932 ) -> Result<ZkAppCommand, String> {
3933 let super::ZkAppCommand {
3934 fee_payer,
3935 account_updates,
3936 memo,
3937 } = zkapp;
3938
3939 let mut tbl = HashMap::with_capacity(128);
3940 let mut vks_overridden: HashMap<AccountId, Option<VerificationKeyWire>> =
3943 HashMap::with_capacity(128);
3944
3945 let account_updates = account_updates.try_map_to(|p| {
3946 let account_id = p.account_id();
3947
3948 check_authorization(p)?;
3949
3950 let result = match (&p.body.authorization_kind, is_failed) {
3951 (AuthorizationKind::Proof(vk_hash), false) => {
3952 let prioritized_vk = {
3953 match vks_overridden.get(&account_id) {
3958 Some(Some(vk)) => {
3959 ok_if_vk_hash_expected(vk.clone(), *vk_hash)?
3960 },
3961 Some(None) => {
3962 return Err(format!("No verification key found for proved account \
3964 update: the verification key was removed by a \
3965 previous account update\
3966 account_id={:?}", account_id));
3967 }
3968 None => {
3969 find_vk(*vk_hash, &account_id)?
3971 },
3972 }
3973 };
3974
3975 tbl.insert(account_id, prioritized_vk.hash());
3976
3977 Ok((p.clone(), Some(prioritized_vk)))
3978 },
3979
3980 _ => {
3981 Ok((p.clone(), None))
3982 }
3983 };
3984
3985 if let SetOrKeep::Set(vk_next) = &p.body.update.verification_key {
3988 vks_overridden.insert(p.account_id().clone(), Some(vk_next.clone()));
3989 }
3990
3991 result
3992 })?;
3993
3994 Ok(ZkAppCommand {
3995 fee_payer: fee_payer.clone(),
3996 account_updates,
3997 memo: memo.clone(),
3998 })
3999 }
4000 }
4001
4002 pub mod valid {
4003 use crate::scan_state::transaction_logic::zkapp_command::verifiable::create;
4004
4005 use super::*;
4006
4007 #[derive(Clone, Debug, PartialEq)]
4008 pub struct ZkAppCommand {
4009 pub zkapp_command: super::ZkAppCommand,
4010 }
4011
4012 impl ZkAppCommand {
4013 pub fn forget(self) -> super::ZkAppCommand {
4014 self.zkapp_command
4015 }
4016 pub fn forget_ref(&self) -> &super::ZkAppCommand {
4017 &self.zkapp_command
4018 }
4019 }
4020
4021 pub fn of_verifiable(cmd: verifiable::ZkAppCommand) -> ZkAppCommand {
4023 ZkAppCommand {
4024 zkapp_command: super::ZkAppCommand::of_verifiable(cmd),
4025 }
4026 }
4027
4028 pub fn to_valid(
4030 zkapp_command: super::ZkAppCommand,
4031 status: &TransactionStatus,
4032 find_vk: impl Fn(Fp, &AccountId) -> Result<VerificationKeyWire, String>,
4033 ) -> Result<ZkAppCommand, String> {
4034 create(&zkapp_command, status.is_failed(), find_vk).map(of_verifiable)
4035 }
4036 }
4037
4038 pub struct MaybeWithStatus<T> {
4039 pub cmd: T,
4040 pub status: Option<TransactionStatus>,
4041 }
4042
4043 impl<T> From<WithStatus<T>> for MaybeWithStatus<T> {
4044 fn from(value: WithStatus<T>) -> Self {
4045 let WithStatus { data, status } = value;
4046 Self {
4047 cmd: data,
4048 status: Some(status),
4049 }
4050 }
4051 }
4052
4053 impl<T> From<MaybeWithStatus<T>> for WithStatus<T> {
4054 fn from(value: MaybeWithStatus<T>) -> Self {
4055 let MaybeWithStatus { cmd, status } = value;
4056 Self {
4057 data: cmd,
4058 status: status.unwrap(),
4059 }
4060 }
4061 }
4062
4063 impl<T> MaybeWithStatus<T> {
4064 pub fn cmd(&self) -> &T {
4065 &self.cmd
4066 }
4067 pub fn is_failed(&self) -> bool {
4068 self.status
4069 .as_ref()
4070 .map(TransactionStatus::is_failed)
4071 .unwrap_or(false)
4072 }
4073 pub fn map<V, F>(self, fun: F) -> MaybeWithStatus<V>
4074 where
4075 F: FnOnce(T) -> V,
4076 {
4077 MaybeWithStatus {
4078 cmd: fun(self.cmd),
4079 status: self.status,
4080 }
4081 }
4082 }
4083
4084 pub trait ToVerifiableCache {
4085 fn find(&self, account_id: &AccountId, vk_hash: &Fp) -> Option<&VerificationKeyWire>;
4086 fn add(&mut self, account_id: AccountId, vk: VerificationKeyWire);
4087 }
4088
4089 pub trait ToVerifiableStrategy {
4090 type Cache: ToVerifiableCache;
4091
4092 fn create_all(
4093 cmd: &ZkAppCommand,
4094 is_failed: bool,
4095 cache: &mut Self::Cache,
4096 ) -> Result<verifiable::ZkAppCommand, String> {
4097 let verified_cmd = verifiable::create(cmd, is_failed, |vk_hash, account_id| {
4098 cache
4099 .find(account_id, &vk_hash)
4100 .cloned()
4101 .or_else(|| {
4102 cmd.extract_vks()
4103 .iter()
4104 .find(|(id, _)| account_id == id)
4105 .map(|(_, key)| key.clone())
4106 })
4107 .ok_or_else(|| format!("verification key not found in cache: {:?}", vk_hash))
4108 })?;
4109 if !is_failed {
4110 for (account_id, vk) in cmd.extract_vks() {
4111 cache.add(account_id, vk);
4112 }
4113 }
4114 Ok(verified_cmd)
4115 }
4116 }
4117
4118 pub mod from_unapplied_sequence {
4119 use super::*;
4120
4121 pub struct Cache {
4122 cache: HashMap<AccountId, HashMap<Fp, VerificationKeyWire>>,
4123 }
4124
4125 impl Cache {
4126 pub fn new(cache: HashMap<AccountId, HashMap<Fp, VerificationKeyWire>>) -> Self {
4127 Self { cache }
4128 }
4129 }
4130
4131 impl ToVerifiableCache for Cache {
4132 fn find(&self, account_id: &AccountId, vk_hash: &Fp) -> Option<&VerificationKeyWire> {
4133 let vks = self.cache.get(account_id)?;
4134 vks.get(vk_hash)
4135 }
4136 fn add(&mut self, account_id: AccountId, vk: VerificationKeyWire) {
4137 let vks = self.cache.entry(account_id).or_default();
4138 vks.insert(vk.hash(), vk);
4139 }
4140 }
4141
4142 pub struct FromUnappliedSequence;
4143
4144 impl ToVerifiableStrategy for FromUnappliedSequence {
4145 type Cache = Cache;
4146 }
4147 }
4148
4149 pub mod from_applied_sequence {
4150 use super::*;
4151
4152 pub struct Cache {
4153 cache: HashMap<AccountId, VerificationKeyWire>,
4154 }
4155
4156 impl Cache {
4157 pub fn new(cache: HashMap<AccountId, VerificationKeyWire>) -> Self {
4158 Self { cache }
4159 }
4160 }
4161
4162 impl ToVerifiableCache for Cache {
4163 fn find(&self, account_id: &AccountId, vk_hash: &Fp) -> Option<&VerificationKeyWire> {
4164 self.cache
4165 .get(account_id)
4166 .filter(|vk| &vk.hash() == vk_hash)
4167 }
4168 fn add(&mut self, account_id: AccountId, vk: VerificationKeyWire) {
4169 self.cache.insert(account_id, vk);
4170 }
4171 }
4172
4173 pub struct FromAppliedSequence;
4174
4175 impl ToVerifiableStrategy for FromAppliedSequence {
4176 type Cache = Cache;
4177 }
4178 }
4179
4180 pub mod zkapp_weight {
4182 use crate::scan_state::transaction_logic::zkapp_command::{
4183 AccountUpdate, CallForest, FeePayer,
4184 };
4185
4186 pub fn account_update(_: &AccountUpdate) -> u64 {
4187 1
4188 }
4189 pub fn fee_payer(_: &FeePayer) -> u64 {
4190 1
4191 }
4192 pub fn account_updates(list: &CallForest<AccountUpdate>) -> u64 {
4193 list.fold(0, |acc, p| acc + account_update(p))
4194 }
4195 pub fn memo(_: &super::Memo) -> u64 {
4196 0
4197 }
4198 }
4199}
4200
4201pub mod zkapp_statement {
4202 use poseidon::hash::params::MINA_ACCOUNT_UPDATE_CONS;
4203
4204 use super::{
4205 zkapp_command::{CallForest, Tree},
4206 *,
4207 };
4208
4209 #[derive(Copy, Clone, Debug, derive_more::Deref, derive_more::From)]
4210 pub struct TransactionCommitment(pub Fp);
4211
4212 impl TransactionCommitment {
4213 pub fn create(account_updates_hash: Fp) -> Self {
4215 Self(account_updates_hash)
4216 }
4217
4218 pub fn create_complete(&self, memo_hash: Fp, fee_payer_hash: Fp) -> Self {
4220 Self(hash_with_kimchi(
4221 &MINA_ACCOUNT_UPDATE_CONS,
4222 &[memo_hash, fee_payer_hash, self.0],
4223 ))
4224 }
4225
4226 pub fn empty() -> Self {
4227 Self(Fp::zero())
4228 }
4229 }
4230
4231 impl Hashable for TransactionCommitment {
4232 type D = NetworkId;
4233
4234 fn to_roinput(&self) -> ROInput {
4235 let mut roi = ROInput::new();
4236 roi = roi.append_field(self.0);
4237 roi
4238 }
4239
4240 fn domain_string(network_id: NetworkId) -> Option<String> {
4241 match network_id {
4242 NetworkId::MAINNET => mina_core::network::mainnet::SIGNATURE_PREFIX,
4243 NetworkId::TESTNET => mina_core::network::devnet::SIGNATURE_PREFIX,
4244 }
4245 .to_string()
4246 .into()
4247 }
4248 }
4249
4250 #[derive(Clone, Debug)]
4251 pub struct ZkappStatement {
4252 pub account_update: TransactionCommitment,
4253 pub calls: TransactionCommitment,
4254 }
4255
4256 impl ZkappStatement {
4257 pub fn to_field_elements(&self) -> Vec<Fp> {
4258 let Self {
4259 account_update,
4260 calls,
4261 } = self;
4262
4263 vec![**account_update, **calls]
4264 }
4265
4266 pub fn of_tree<AccUpdate: Clone + zkapp_command::AccountUpdateRef>(
4267 tree: &Tree<AccUpdate>,
4268 ) -> Self {
4269 let Tree {
4270 account_update: _,
4271 account_update_digest,
4272 calls,
4273 } = tree;
4274
4275 Self {
4276 account_update: TransactionCommitment(account_update_digest.get().unwrap()),
4277 calls: TransactionCommitment(calls.hash()),
4278 }
4279 }
4280
4281 pub fn zkapp_statements_of_forest_prime<Data: Clone>(
4282 forest: CallForest<(AccountUpdate, Data)>,
4283 ) -> CallForest<(AccountUpdate, (Data, Self))> {
4284 forest.map_with_trees_to(|(account_update, data), tree| {
4285 (account_update.clone(), (data.clone(), Self::of_tree(tree)))
4286 })
4287 }
4288
4289 fn zkapp_statements_of_forest(
4290 forest: CallForest<AccountUpdate>,
4291 ) -> CallForest<(AccountUpdate, Self)> {
4292 forest.map_with_trees_to(|account_update, tree| {
4293 (account_update.clone(), Self::of_tree(tree))
4294 })
4295 }
4296 }
4297}
4298
4299pub mod verifiable {
4300 use std::ops::Neg;
4301
4302 use ark_ff::{BigInteger, PrimeField};
4303
4304 use super::*;
4305
4306 #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
4307 pub enum UserCommand {
4308 SignedCommand(Box<signed_command::SignedCommand>),
4309 ZkAppCommand(Box<zkapp_command::verifiable::ZkAppCommand>),
4310 }
4311
4312 pub fn compressed_to_pubkey(pubkey: &CompressedPubKey) -> mina_signer::PubKey {
4313 let mut pt =
4315 mina_signer::CurvePoint::get_point_from_x_unchecked(pubkey.x, pubkey.is_odd).unwrap();
4316
4317 if pt.y.into_bigint().is_even() == pubkey.is_odd {
4318 pt.y = pt.y.neg();
4319 }
4320
4321 assert!(pt.is_on_curve());
4322
4323 mina_signer::PubKey::from_point_unsafe(pt)
4325 }
4326
4327 pub fn check_only_for_signature(
4329 cmd: Box<signed_command::SignedCommand>,
4330 ) -> Result<valid::UserCommand, Box<signed_command::SignedCommand>> {
4331 let signed_command::SignedCommand {
4334 payload,
4335 signer: pubkey,
4336 signature,
4337 } = &*cmd;
4338
4339 let payload = TransactionUnionPayload::of_user_command_payload(payload);
4340 let pubkey = compressed_to_pubkey(pubkey);
4341
4342 if crate::verifier::common::legacy_verify_signature(signature, &pubkey, &payload) {
4343 Ok(valid::UserCommand::SignedCommand(cmd))
4344 } else {
4345 Err(cmd)
4346 }
4347 }
4348}
4349
4350#[derive(Clone, Debug, PartialEq)]
4351pub enum UserCommand {
4352 SignedCommand(Box<signed_command::SignedCommand>),
4353 ZkAppCommand(Box<zkapp_command::ZkAppCommand>),
4354}
4355
4356impl From<&UserCommand> for MinaBaseUserCommandStableV2 {
4357 fn from(user_command: &UserCommand) -> Self {
4358 match user_command {
4359 UserCommand::SignedCommand(signed_command) => {
4360 MinaBaseUserCommandStableV2::SignedCommand((&(*(signed_command.clone()))).into())
4361 }
4362 UserCommand::ZkAppCommand(zkapp_command) => {
4363 MinaBaseUserCommandStableV2::ZkappCommand((&(*(zkapp_command.clone()))).into())
4364 }
4365 }
4366 }
4367}
4368
4369impl TryFrom<&MinaBaseUserCommandStableV2> for UserCommand {
4370 type Error = InvalidBigInt;
4371
4372 fn try_from(user_command: &MinaBaseUserCommandStableV2) -> Result<Self, Self::Error> {
4373 match user_command {
4374 MinaBaseUserCommandStableV2::SignedCommand(signed_command) => Ok(
4375 UserCommand::SignedCommand(Box::new(signed_command.try_into()?)),
4376 ),
4377 MinaBaseUserCommandStableV2::ZkappCommand(zkapp_command) => Ok(
4378 UserCommand::ZkAppCommand(Box::new(zkapp_command.try_into()?)),
4379 ),
4380 }
4381 }
4382}
4383
4384impl binprot::BinProtWrite for UserCommand {
4385 fn binprot_write<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
4386 let p2p: MinaBaseUserCommandStableV2 = self.into();
4387 p2p.binprot_write(w)
4388 }
4389}
4390
4391impl binprot::BinProtRead for UserCommand {
4392 fn binprot_read<R: std::io::Read + ?Sized>(r: &mut R) -> Result<Self, binprot::Error> {
4393 let p2p = MinaBaseUserCommandStableV2::binprot_read(r)?;
4394 match UserCommand::try_from(&p2p) {
4395 Ok(cmd) => Ok(cmd),
4396 Err(e) => Err(binprot::Error::CustomError(Box::new(e))),
4397 }
4398 }
4399}
4400
4401impl UserCommand {
4402 pub fn account_access_statuses(
4404 &self,
4405 status: &TransactionStatus,
4406 ) -> Vec<(AccountId, AccessedOrNot)> {
4407 match self {
4408 UserCommand::SignedCommand(cmd) => cmd.account_access_statuses(status).to_vec(),
4409 UserCommand::ZkAppCommand(cmd) => cmd.account_access_statuses(status),
4410 }
4411 }
4412
4413 pub fn accounts_referenced(&self) -> Vec<AccountId> {
4415 self.account_access_statuses(&TransactionStatus::Applied)
4416 .into_iter()
4417 .map(|(id, _status)| id)
4418 .collect()
4419 }
4420
4421 pub fn fee_payer(&self) -> AccountId {
4422 match self {
4423 UserCommand::SignedCommand(cmd) => cmd.fee_payer(),
4424 UserCommand::ZkAppCommand(cmd) => cmd.fee_payer(),
4425 }
4426 }
4427
4428 pub fn valid_until(&self) -> Slot {
4429 match self {
4430 UserCommand::SignedCommand(cmd) => cmd.valid_until(),
4431 UserCommand::ZkAppCommand(cmd) => {
4432 let ZkAppCommand { fee_payer, .. } = &**cmd;
4433 fee_payer.body.valid_until.unwrap_or_else(Slot::max)
4434 }
4435 }
4436 }
4437
4438 pub fn applicable_at_nonce(&self) -> Nonce {
4439 match self {
4440 UserCommand::SignedCommand(cmd) => cmd.nonce(),
4441 UserCommand::ZkAppCommand(cmd) => cmd.applicable_at_nonce(),
4442 }
4443 }
4444
4445 pub fn expected_target_nonce(&self) -> Nonce {
4446 self.applicable_at_nonce().succ()
4447 }
4448
4449 pub fn fee(&self) -> Fee {
4451 match self {
4452 UserCommand::SignedCommand(cmd) => cmd.fee(),
4453 UserCommand::ZkAppCommand(cmd) => cmd.fee(),
4454 }
4455 }
4456
4457 pub fn weight(&self) -> u64 {
4458 match self {
4459 UserCommand::SignedCommand(cmd) => cmd.weight(),
4460 UserCommand::ZkAppCommand(cmd) => cmd.weight(),
4461 }
4462 }
4463
4464 pub fn fee_per_wu(&self) -> FeeRate {
4466 FeeRate::make_exn(self.fee(), self.weight())
4467 }
4468
4469 pub fn fee_token(&self) -> TokenId {
4470 match self {
4471 UserCommand::SignedCommand(cmd) => cmd.fee_token(),
4472 UserCommand::ZkAppCommand(cmd) => cmd.fee_token(),
4473 }
4474 }
4475
4476 pub fn extract_vks(&self) -> Vec<(AccountId, VerificationKeyWire)> {
4477 match self {
4478 UserCommand::SignedCommand(_) => vec![],
4479 UserCommand::ZkAppCommand(zkapp) => zkapp.extract_vks(),
4480 }
4481 }
4482
4483 pub fn to_valid_unsafe(self) -> valid::UserCommand {
4485 match self {
4486 UserCommand::SignedCommand(cmd) => valid::UserCommand::SignedCommand(cmd),
4487 UserCommand::ZkAppCommand(cmd) => {
4488 valid::UserCommand::ZkAppCommand(Box::new(zkapp_command::valid::ZkAppCommand {
4489 zkapp_command: *cmd,
4490 }))
4491 }
4492 }
4493 }
4494
4495 pub fn to_verifiable<F>(
4497 &self,
4498 status: &TransactionStatus,
4499 find_vk: F,
4500 ) -> Result<verifiable::UserCommand, String>
4501 where
4502 F: Fn(Fp, &AccountId) -> Result<VerificationKeyWire, String>,
4503 {
4504 use verifiable::UserCommand::{SignedCommand, ZkAppCommand};
4505 match self {
4506 UserCommand::SignedCommand(cmd) => Ok(SignedCommand(cmd.clone())),
4507 UserCommand::ZkAppCommand(zkapp) => Ok(ZkAppCommand(Box::new(
4508 zkapp_command::verifiable::create(zkapp, status.is_failed(), find_vk)?,
4509 ))),
4510 }
4511 }
4512
4513 pub fn load_vks_from_ledger(
4514 account_ids: HashSet<AccountId>,
4515 ledger: &crate::Mask,
4516 ) -> HashMap<AccountId, VerificationKeyWire> {
4517 let ids: Vec<_> = account_ids.iter().cloned().collect();
4518 let locations: Vec<_> = ledger
4519 .location_of_account_batch(&ids)
4520 .into_iter()
4521 .filter_map(|(_, addr)| addr)
4522 .collect();
4523 ledger
4524 .get_batch(&locations)
4525 .into_iter()
4526 .filter_map(|(_, account)| {
4527 let account = account.unwrap();
4528 let zkapp = account.zkapp.as_ref()?;
4529 let vk = zkapp.verification_key.clone()?;
4530 Some((account.id(), vk))
4531 })
4532 .collect()
4533 }
4534
4535 pub fn load_vks_from_ledger_accounts(
4536 accounts: &BTreeMap<AccountId, Account>,
4537 ) -> HashMap<AccountId, VerificationKeyWire> {
4538 accounts
4539 .iter()
4540 .filter_map(|(_, account)| {
4541 let zkapp = account.zkapp.as_ref()?;
4542 let vk = zkapp.verification_key.clone()?;
4543 Some((account.id(), vk))
4544 })
4545 .collect()
4546 }
4547
4548 pub fn to_all_verifiable<S, F>(
4549 ts: Vec<MaybeWithStatus<UserCommand>>,
4550 load_vk_cache: F,
4551 ) -> Result<Vec<MaybeWithStatus<verifiable::UserCommand>>, String>
4552 where
4553 S: zkapp_command::ToVerifiableStrategy,
4554 F: Fn(HashSet<AccountId>) -> S::Cache,
4555 {
4556 let accounts_referenced: HashSet<AccountId> = ts
4557 .iter()
4558 .flat_map(|cmd| match cmd.cmd() {
4559 UserCommand::SignedCommand(_) => Vec::new(),
4560 UserCommand::ZkAppCommand(cmd) => cmd.accounts_referenced(),
4561 })
4562 .collect();
4563 let mut vk_cache = load_vk_cache(accounts_referenced);
4564
4565 ts.into_iter()
4566 .map(|cmd| {
4567 let is_failed = cmd.is_failed();
4568 let MaybeWithStatus { cmd, status } = cmd;
4569 match cmd {
4570 UserCommand::SignedCommand(c) => Ok(MaybeWithStatus {
4571 cmd: verifiable::UserCommand::SignedCommand(c),
4572 status,
4573 }),
4574 UserCommand::ZkAppCommand(c) => {
4575 let zkapp_verifiable = S::create_all(&c, is_failed, &mut vk_cache)?;
4576 Ok(MaybeWithStatus {
4577 cmd: verifiable::UserCommand::ZkAppCommand(Box::new(zkapp_verifiable)),
4578 status,
4579 })
4580 }
4581 }
4582 })
4583 .collect()
4584 }
4585
4586 fn has_insufficient_fee(&self) -> bool {
4587 const MINIMUM_USER_COMMAND_FEE: Fee = Fee::from_u64(1000000);
4589 self.fee() < MINIMUM_USER_COMMAND_FEE
4590 }
4591
4592 fn has_zero_vesting_period(&self) -> bool {
4593 match self {
4594 UserCommand::SignedCommand(_cmd) => false,
4595 UserCommand::ZkAppCommand(cmd) => cmd.has_zero_vesting_period(),
4596 }
4597 }
4598
4599 fn is_incompatible_version(&self) -> bool {
4600 match self {
4601 UserCommand::SignedCommand(_cmd) => false,
4602 UserCommand::ZkAppCommand(cmd) => cmd.is_incompatible_version(),
4603 }
4604 }
4605
4606 fn is_disabled(&self) -> bool {
4607 match self {
4608 UserCommand::SignedCommand(_cmd) => false,
4609 UserCommand::ZkAppCommand(_cmd) => false, }
4611 }
4612
4613 fn valid_size(&self) -> Result<(), String> {
4614 match self {
4615 UserCommand::SignedCommand(_cmd) => Ok(()),
4616 UserCommand::ZkAppCommand(cmd) => cmd.valid_size(),
4617 }
4618 }
4619
4620 pub fn check_well_formedness(&self) -> Result<(), Vec<WellFormednessError>> {
4621 let mut errors: Vec<_> = [
4622 (
4623 Self::has_insufficient_fee as fn(_) -> _,
4624 WellFormednessError::InsufficientFee,
4625 ),
4626 (
4627 Self::has_zero_vesting_period,
4628 WellFormednessError::ZeroVestingPeriod,
4629 ),
4630 (
4631 Self::is_incompatible_version,
4632 WellFormednessError::IncompatibleVersion,
4633 ),
4634 (
4635 Self::is_disabled,
4636 WellFormednessError::TransactionTypeDisabled,
4637 ),
4638 ]
4639 .iter()
4640 .filter_map(|(fun, e)| if fun(self) { Some(e.clone()) } else { None })
4641 .collect();
4642
4643 if let Err(e) = self.valid_size() {
4644 errors.push(WellFormednessError::ZkappTooBig(e));
4645 }
4646
4647 if errors.is_empty() {
4648 Ok(())
4649 } else {
4650 Err(errors)
4651 }
4652 }
4653}
4654
4655#[derive(Debug, Clone, Hash, PartialEq, Eq, thiserror::Error)]
4656pub enum WellFormednessError {
4657 #[error("Insufficient Fee")]
4658 InsufficientFee,
4659 #[error("Zero vesting period")]
4660 ZeroVestingPeriod,
4661 #[error("Zkapp too big: {0}")]
4662 ZkappTooBig(String),
4663 #[error("Transaction type disabled")]
4664 TransactionTypeDisabled,
4665 #[error("Incompatible version")]
4666 IncompatibleVersion,
4667}
4668
4669impl GenericCommand for UserCommand {
4670 fn fee(&self) -> Fee {
4671 match self {
4672 UserCommand::SignedCommand(cmd) => cmd.fee(),
4673 UserCommand::ZkAppCommand(cmd) => cmd.fee(),
4674 }
4675 }
4676
4677 fn forget(&self) -> UserCommand {
4678 self.clone()
4679 }
4680}
4681
4682impl GenericTransaction for Transaction {
4683 fn is_fee_transfer(&self) -> bool {
4684 matches!(self, Transaction::FeeTransfer(_))
4685 }
4686 fn is_coinbase(&self) -> bool {
4687 matches!(self, Transaction::Coinbase(_))
4688 }
4689 fn is_command(&self) -> bool {
4690 matches!(self, Transaction::Command(_))
4691 }
4692}
4693
4694#[derive(Clone, Debug, derive_more::From)]
4695pub enum Transaction {
4696 Command(UserCommand),
4697 FeeTransfer(FeeTransfer),
4698 Coinbase(Coinbase),
4699}
4700
4701impl Transaction {
4702 pub fn is_zkapp(&self) -> bool {
4703 matches!(self, Self::Command(UserCommand::ZkAppCommand(_)))
4704 }
4705
4706 pub fn fee_excess(&self) -> Result<FeeExcess, String> {
4707 use Transaction::*;
4708 use UserCommand::*;
4709
4710 match self {
4711 Command(SignedCommand(cmd)) => Ok(cmd.fee_excess()),
4712 Command(ZkAppCommand(cmd)) => Ok(cmd.fee_excess()),
4713 FeeTransfer(ft) => ft.fee_excess(),
4714 Coinbase(cb) => cb.fee_excess(),
4715 }
4716 }
4717
4718 pub fn public_keys(&self) -> Vec<CompressedPubKey> {
4720 use Transaction::*;
4721 use UserCommand::*;
4722
4723 let to_pks = |ids: Vec<AccountId>| ids.into_iter().map(|id| id.public_key).collect();
4724
4725 match self {
4726 Command(SignedCommand(cmd)) => to_pks(cmd.accounts_referenced()),
4727 Command(ZkAppCommand(cmd)) => to_pks(cmd.accounts_referenced()),
4728 FeeTransfer(ft) => ft.receiver_pks().cloned().collect(),
4729 Coinbase(cb) => to_pks(cb.accounts_referenced()),
4730 }
4731 }
4732
4733 pub fn account_access_statuses(
4735 &self,
4736 status: &TransactionStatus,
4737 ) -> Vec<(AccountId, zkapp_command::AccessedOrNot)> {
4738 use Transaction::*;
4739 use UserCommand::*;
4740
4741 match self {
4742 Command(SignedCommand(cmd)) => cmd.account_access_statuses(status).to_vec(),
4743 Command(ZkAppCommand(cmd)) => cmd.account_access_statuses(status),
4744 FeeTransfer(ft) => ft
4745 .receivers()
4746 .map(|account_id| (account_id, AccessedOrNot::Accessed))
4747 .collect(),
4748 Coinbase(cb) => cb.account_access_statuses(status),
4749 }
4750 }
4751
4752 pub fn accounts_referenced(&self) -> Vec<AccountId> {
4754 self.account_access_statuses(&TransactionStatus::Applied)
4755 .into_iter()
4756 .map(|(id, _status)| id)
4757 .collect()
4758 }
4759}
4760
4761impl From<&Transaction> for MinaTransactionTransactionStableV2 {
4762 fn from(value: &Transaction) -> Self {
4763 match value {
4764 Transaction::Command(v) => Self::Command(Box::new(v.into())),
4765 Transaction::FeeTransfer(v) => Self::FeeTransfer(v.into()),
4766 Transaction::Coinbase(v) => Self::Coinbase(v.into()),
4767 }
4768 }
4769}
4770
4771pub mod transaction_applied {
4772 use crate::AccountId;
4773
4774 use super::*;
4775
4776 pub mod signed_command_applied {
4777 use super::*;
4778
4779 #[derive(Debug, Clone, PartialEq)]
4780 pub struct Common {
4781 pub user_command: WithStatus<signed_command::SignedCommand>,
4782 }
4783
4784 #[derive(Debug, Clone, PartialEq)]
4785 pub enum Body {
4786 Payments {
4787 new_accounts: Vec<AccountId>,
4788 },
4789 StakeDelegation {
4790 previous_delegate: Option<CompressedPubKey>,
4791 },
4792 Failed,
4793 }
4794
4795 #[derive(Debug, Clone, PartialEq)]
4796 pub struct SignedCommandApplied {
4797 pub common: Common,
4798 pub body: Body,
4799 }
4800 }
4801
4802 pub use signed_command_applied::SignedCommandApplied;
4803
4804 impl SignedCommandApplied {
4805 pub fn new_accounts(&self) -> &[AccountId] {
4806 use signed_command_applied::Body::*;
4807
4808 match &self.body {
4809 Payments { new_accounts } => new_accounts.as_slice(),
4810 StakeDelegation { .. } | Failed => &[],
4811 }
4812 }
4813 }
4814
4815 #[derive(Debug, Clone, PartialEq)]
4817 pub struct ZkappCommandApplied {
4818 pub accounts: Vec<(AccountId, Option<Box<Account>>)>,
4819 pub command: WithStatus<zkapp_command::ZkAppCommand>,
4820 pub new_accounts: Vec<AccountId>,
4821 }
4822
4823 #[derive(Debug, Clone, PartialEq)]
4825 pub enum CommandApplied {
4826 SignedCommand(Box<SignedCommandApplied>),
4827 ZkappCommand(Box<ZkappCommandApplied>),
4828 }
4829
4830 #[derive(Debug, Clone, PartialEq)]
4832 pub struct FeeTransferApplied {
4833 pub fee_transfer: WithStatus<FeeTransfer>,
4834 pub new_accounts: Vec<AccountId>,
4835 pub burned_tokens: Amount,
4836 }
4837
4838 #[derive(Debug, Clone, PartialEq)]
4840 pub struct CoinbaseApplied {
4841 pub coinbase: WithStatus<Coinbase>,
4842 pub new_accounts: Vec<AccountId>,
4843 pub burned_tokens: Amount,
4844 }
4845
4846 #[derive(Debug, Clone, PartialEq)]
4848 pub enum Varying {
4849 Command(CommandApplied),
4850 FeeTransfer(FeeTransferApplied),
4851 Coinbase(CoinbaseApplied),
4852 }
4853
4854 #[derive(Debug, Clone, PartialEq)]
4856 pub struct TransactionApplied {
4857 pub previous_hash: Fp,
4858 pub varying: Varying,
4859 }
4860
4861 impl TransactionApplied {
4862 pub fn transaction(&self) -> WithStatus<Transaction> {
4864 use CommandApplied::*;
4865 use Varying::*;
4866
4867 match &self.varying {
4868 Command(SignedCommand(cmd)) => cmd
4869 .common
4870 .user_command
4871 .map(|c| Transaction::Command(UserCommand::SignedCommand(Box::new(c.clone())))),
4872 Command(ZkappCommand(cmd)) => cmd
4873 .command
4874 .map(|c| Transaction::Command(UserCommand::ZkAppCommand(Box::new(c.clone())))),
4875 FeeTransfer(f) => f.fee_transfer.map(|f| Transaction::FeeTransfer(f.clone())),
4876 Coinbase(c) => c.coinbase.map(|c| Transaction::Coinbase(c.clone())),
4877 }
4878 }
4879
4880 pub fn transaction_status(&self) -> &TransactionStatus {
4882 use CommandApplied::*;
4883 use Varying::*;
4884
4885 match &self.varying {
4886 Command(SignedCommand(cmd)) => &cmd.common.user_command.status,
4887 Command(ZkappCommand(cmd)) => &cmd.command.status,
4888 FeeTransfer(f) => &f.fee_transfer.status,
4889 Coinbase(c) => &c.coinbase.status,
4890 }
4891 }
4892
4893 pub fn burned_tokens(&self) -> Amount {
4894 match &self.varying {
4895 Varying::Command(_) => Amount::zero(),
4896 Varying::FeeTransfer(f) => f.burned_tokens,
4897 Varying::Coinbase(c) => c.burned_tokens,
4898 }
4899 }
4900
4901 pub fn new_accounts(&self) -> &[AccountId] {
4902 use CommandApplied::*;
4903 use Varying::*;
4904
4905 match &self.varying {
4906 Command(SignedCommand(cmd)) => cmd.new_accounts(),
4907 Command(ZkappCommand(cmd)) => cmd.new_accounts.as_slice(),
4908 FeeTransfer(f) => f.new_accounts.as_slice(),
4909 Coinbase(cb) => cb.new_accounts.as_slice(),
4910 }
4911 }
4912
4913 pub fn supply_increase(
4915 &self,
4916 constraint_constants: &ConstraintConstants,
4917 ) -> Result<Signed<Amount>, String> {
4918 let burned_tokens = Signed::<Amount>::of_unsigned(self.burned_tokens());
4919
4920 let account_creation_fees = {
4921 let account_creation_fee_int = constraint_constants.account_creation_fee;
4922 let num_accounts_created = self.new_accounts().len() as u64;
4923
4924 let amount = account_creation_fee_int
4926 .checked_mul(num_accounts_created)
4927 .unwrap();
4928 Signed::<Amount>::of_unsigned(Amount::from_u64(amount))
4929 };
4930
4931 let expected_supply_increase = match &self.varying {
4932 Varying::Coinbase(cb) => cb.coinbase.data.expected_supply_increase()?,
4933 _ => Amount::zero(),
4934 };
4935 let expected_supply_increase = Signed::<Amount>::of_unsigned(expected_supply_increase);
4936
4937 let total = [burned_tokens, account_creation_fees]
4939 .into_iter()
4940 .try_fold(expected_supply_increase, |total, amt| {
4941 total.add(&amt.negate())
4942 });
4943
4944 total.ok_or_else(|| "overflow".to_string())
4945 }
4946 }
4947}
4948
4949pub mod transaction_witness {
4950 use mina_p2p_messages::v2::MinaStateProtocolStateBodyValueStableV2;
4951
4952 use crate::scan_state::pending_coinbase::Stack;
4953
4954 use super::*;
4955
4956 #[derive(Debug)]
4958 pub struct TransactionWitness {
4959 pub transaction: Transaction,
4960 pub first_pass_ledger: SparseLedger,
4961 pub second_pass_ledger: SparseLedger,
4962 pub protocol_state_body: MinaStateProtocolStateBodyValueStableV2,
4963 pub init_stack: Stack,
4964 pub status: TransactionStatus,
4965 pub block_global_slot: Slot,
4966 }
4967}
4968
4969pub mod protocol_state {
4970 use mina_p2p_messages::v2::{self, MinaStateProtocolStateValueStableV2};
4971
4972 use crate::proofs::field::FieldWitness;
4973
4974 use super::*;
4975
4976 #[derive(Debug, Clone)]
4977 pub struct EpochLedger<F: FieldWitness> {
4978 pub hash: F,
4979 pub total_currency: Amount,
4980 }
4981
4982 #[derive(Debug, Clone)]
4983 pub struct EpochData<F: FieldWitness> {
4984 pub ledger: EpochLedger<F>,
4985 pub seed: F,
4986 pub start_checkpoint: F,
4987 pub lock_checkpoint: F,
4988 pub epoch_length: Length,
4989 }
4990
4991 #[derive(Debug, Clone)]
4992 pub struct ProtocolStateView {
4993 pub snarked_ledger_hash: Fp,
4994 pub blockchain_length: Length,
4995 pub min_window_density: Length,
4996 pub total_currency: Amount,
4997 pub global_slot_since_genesis: Slot,
4998 pub staking_epoch_data: EpochData<Fp>,
4999 pub next_epoch_data: EpochData<Fp>,
5000 }
5001
5002 pub fn protocol_state_view(
5004 state: &MinaStateProtocolStateValueStableV2,
5005 ) -> Result<ProtocolStateView, InvalidBigInt> {
5006 let MinaStateProtocolStateValueStableV2 {
5007 previous_state_hash: _,
5008 body,
5009 } = state;
5010
5011 protocol_state_body_view(body)
5012 }
5013
5014 pub fn protocol_state_body_view(
5015 body: &v2::MinaStateProtocolStateBodyValueStableV2,
5016 ) -> Result<ProtocolStateView, InvalidBigInt> {
5017 let cs = &body.consensus_state;
5018 let sed = &cs.staking_epoch_data;
5019 let ned = &cs.next_epoch_data;
5020
5021 Ok(ProtocolStateView {
5022 snarked_ledger_hash: body
5025 .blockchain_state
5026 .ledger_proof_statement
5027 .target
5028 .first_pass_ledger
5029 .to_field()?,
5030 blockchain_length: Length(cs.blockchain_length.as_u32()),
5031 min_window_density: Length(cs.min_window_density.as_u32()),
5032 total_currency: Amount(cs.total_currency.as_u64()),
5033 global_slot_since_genesis: (&cs.global_slot_since_genesis).into(),
5034 staking_epoch_data: EpochData {
5035 ledger: EpochLedger {
5036 hash: sed.ledger.hash.to_field()?,
5037 total_currency: Amount(sed.ledger.total_currency.as_u64()),
5038 },
5039 seed: sed.seed.to_field()?,
5040 start_checkpoint: sed.start_checkpoint.to_field()?,
5041 lock_checkpoint: sed.lock_checkpoint.to_field()?,
5042 epoch_length: Length(sed.epoch_length.as_u32()),
5043 },
5044 next_epoch_data: EpochData {
5045 ledger: EpochLedger {
5046 hash: ned.ledger.hash.to_field()?,
5047 total_currency: Amount(ned.ledger.total_currency.as_u64()),
5048 },
5049 seed: ned.seed.to_field()?,
5050 start_checkpoint: ned.start_checkpoint.to_field()?,
5051 lock_checkpoint: ned.lock_checkpoint.to_field()?,
5052 epoch_length: Length(ned.epoch_length.as_u32()),
5053 },
5054 })
5055 }
5056
5057 pub type GlobalState<L> = GlobalStateSkeleton<L, Signed<Amount>, Slot>;
5058
5059 #[derive(Debug, Clone)]
5060 pub struct GlobalStateSkeleton<L, SignedAmount, Slot> {
5061 pub first_pass_ledger: L,
5062 pub second_pass_ledger: L,
5063 pub fee_excess: SignedAmount,
5064 pub supply_increase: SignedAmount,
5065 pub protocol_state: ProtocolStateView,
5066 pub block_global_slot: Slot,
5070 }
5071
5072 impl<L: LedgerIntf + Clone> GlobalState<L> {
5073 pub fn first_pass_ledger(&self) -> L {
5074 self.first_pass_ledger.create_masked()
5075 }
5076
5077 #[must_use]
5078 pub fn set_first_pass_ledger(&self, should_update: bool, ledger: L) -> Self {
5079 let mut this = self.clone();
5080 if should_update {
5081 this.first_pass_ledger.apply_mask(ledger);
5082 }
5083 this
5084 }
5085
5086 pub fn second_pass_ledger(&self) -> L {
5087 self.second_pass_ledger.create_masked()
5088 }
5089
5090 #[must_use]
5091 pub fn set_second_pass_ledger(&self, should_update: bool, ledger: L) -> Self {
5092 let mut this = self.clone();
5093 if should_update {
5094 this.second_pass_ledger.apply_mask(ledger);
5095 }
5096 this
5097 }
5098
5099 pub fn fee_excess(&self) -> Signed<Amount> {
5100 self.fee_excess
5101 }
5102
5103 #[must_use]
5104 pub fn set_fee_excess(&self, fee_excess: Signed<Amount>) -> Self {
5105 let mut this = self.clone();
5106 this.fee_excess = fee_excess;
5107 this
5108 }
5109
5110 pub fn supply_increase(&self) -> Signed<Amount> {
5111 self.supply_increase
5112 }
5113
5114 #[must_use]
5115 pub fn set_supply_increase(&self, supply_increase: Signed<Amount>) -> Self {
5116 let mut this = self.clone();
5117 this.supply_increase = supply_increase;
5118 this
5119 }
5120
5121 pub fn block_global_slot(&self) -> Slot {
5122 self.block_global_slot
5123 }
5124 }
5125}
5126
5127pub mod local_state {
5128 use std::{cell::RefCell, rc::Rc};
5129
5130 use poseidon::hash::params::MINA_ACCOUNT_UPDATE_STACK_FRAME;
5131
5132 use crate::{
5133 proofs::{
5134 field::{field, Boolean, ToBoolean},
5135 numbers::nat::CheckedNat,
5136 to_field_elements::ToFieldElements,
5137 },
5138 zkapps::intefaces::{
5139 CallStackInterface, IndexInterface, SignedAmountInterface, StackFrameInterface,
5140 },
5141 ToInputs,
5142 };
5143
5144 use super::{zkapp_command::CallForest, *};
5145
5146 #[derive(Debug, Clone)]
5147 pub struct StackFrame {
5148 pub caller: TokenId,
5149 pub caller_caller: TokenId,
5150 pub calls: CallForest<AccountUpdate>, }
5152
5153 #[derive(Debug, Clone)]
5155 pub struct StackFrameCheckedFrame {
5156 pub caller: TokenId,
5157 pub caller_caller: TokenId,
5158 pub calls: WithHash<CallForest<AccountUpdate>>,
5159 pub is_default: bool,
5161 }
5162
5163 impl ToFieldElements<Fp> for StackFrameCheckedFrame {
5164 fn to_field_elements(&self, fields: &mut Vec<Fp>) {
5165 let Self {
5166 caller,
5167 caller_caller,
5168 calls,
5169 is_default: _,
5170 } = self;
5171
5172 calls.hash.to_field_elements(fields);
5174 caller_caller.to_field_elements(fields);
5175 caller.to_field_elements(fields);
5176 }
5177 }
5178
5179 enum LazyValueInner<T, D> {
5180 Value(T),
5181 Fun(Box<dyn FnOnce(&mut D) -> T>),
5182 None,
5183 }
5184
5185 impl<T, D> Default for LazyValueInner<T, D> {
5186 fn default() -> Self {
5187 Self::None
5188 }
5189 }
5190
5191 pub struct LazyValue<T, D> {
5192 value: Rc<RefCell<LazyValueInner<T, D>>>,
5193 }
5194
5195 impl<T, D> Clone for LazyValue<T, D> {
5196 fn clone(&self) -> Self {
5197 Self {
5198 value: Rc::clone(&self.value),
5199 }
5200 }
5201 }
5202
5203 impl<T: std::fmt::Debug, D> std::fmt::Debug for LazyValue<T, D> {
5204 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5205 let v = self.try_get();
5206 f.debug_struct("LazyValue").field("value", &v).finish()
5207 }
5208 }
5209
5210 impl<T, D> LazyValue<T, D> {
5211 pub fn make<F>(fun: F) -> Self
5212 where
5213 F: FnOnce(&mut D) -> T + 'static,
5214 {
5215 Self {
5216 value: Rc::new(RefCell::new(LazyValueInner::Fun(Box::new(fun)))),
5217 }
5218 }
5219
5220 fn get_impl(&self) -> std::cell::Ref<'_, T> {
5221 use std::cell::Ref;
5222
5223 let inner = self.value.borrow();
5224 Ref::map(inner, |inner| {
5225 let LazyValueInner::Value(value) = inner else {
5226 panic!("invalid state");
5227 };
5228 value
5229 })
5230 }
5231
5232 pub fn try_get(&self) -> Option<std::cell::Ref<'_, T>> {
5234 let inner = self.value.borrow();
5235
5236 match &*inner {
5237 LazyValueInner::Value(_) => {}
5238 LazyValueInner::Fun(_) => return None,
5239 LazyValueInner::None => panic!("invalid state"),
5240 }
5241
5242 Some(self.get_impl())
5243 }
5244
5245 pub fn get(&self, data: &mut D) -> std::cell::Ref<'_, T> {
5246 let v = self.value.borrow();
5247
5248 if let LazyValueInner::Fun(_) = &*v {
5249 std::mem::drop(v);
5250
5251 let LazyValueInner::Fun(fun) = self.value.take() else {
5252 panic!("invalid state");
5253 };
5254
5255 let data = fun(data);
5256 self.value.replace(LazyValueInner::Value(data));
5257 };
5258
5259 self.get_impl()
5260 }
5261 }
5262
5263 #[derive(Clone, Debug)]
5264 pub struct WithLazyHash<T> {
5265 pub data: T,
5266 hash: LazyValue<Fp, Witness<Fp>>,
5267 }
5268
5269 impl<T> WithLazyHash<T> {
5270 pub fn new<F>(data: T, fun: F) -> Self
5271 where
5272 F: FnOnce(&mut Witness<Fp>) -> Fp + 'static,
5273 {
5274 Self {
5275 data,
5276 hash: LazyValue::make(fun),
5277 }
5278 }
5279
5280 pub fn hash(&self, w: &mut Witness<Fp>) -> Fp {
5281 *self.hash.get(w)
5282 }
5283 }
5284
5285 impl<T> std::ops::Deref for WithLazyHash<T> {
5286 type Target = T;
5287
5288 fn deref(&self) -> &Self::Target {
5289 &self.data
5290 }
5291 }
5292
5293 impl<T> ToFieldElements<Fp> for WithLazyHash<T> {
5294 fn to_field_elements(&self, fields: &mut Vec<Fp>) {
5295 let hash = self.hash.try_get().expect("hash hasn't been computed yet");
5296 hash.to_field_elements(fields)
5297 }
5298 }
5299
5300 pub type StackFrameChecked = WithLazyHash<StackFrameCheckedFrame>;
5302
5303 impl Default for StackFrame {
5304 fn default() -> Self {
5305 StackFrame {
5306 caller: TokenId::default(),
5307 caller_caller: TokenId::default(),
5308 calls: CallForest::new(),
5309 }
5310 }
5311 }
5312
5313 impl StackFrame {
5314 pub fn empty() -> Self {
5315 Self {
5316 caller: TokenId::default(),
5317 caller_caller: TokenId::default(),
5318 calls: CallForest(Vec::new()),
5319 }
5320 }
5321
5322 pub fn hash(&self) -> Fp {
5326 let mut inputs = Inputs::new();
5327
5328 inputs.append_field(self.caller.0);
5329 inputs.append_field(self.caller_caller.0);
5330
5331 self.calls.ensure_hashed();
5332 let field = match self.calls.0.first() {
5333 None => Fp::zero(),
5334 Some(calls) => calls.stack_hash.get().unwrap(), };
5336 inputs.append_field(field);
5337
5338 hash_with_kimchi(&MINA_ACCOUNT_UPDATE_STACK_FRAME, &inputs.to_fields())
5339 }
5340
5341 pub fn digest(&self) -> Fp {
5342 self.hash()
5343 }
5344
5345 pub fn unhash(&self, _h: Fp, w: &mut Witness<Fp>) -> StackFrameChecked {
5346 let v = self.exists_elt(w);
5347 v.hash(w);
5348 v
5349 }
5350
5351 pub fn exists_elt(&self, w: &mut Witness<Fp>) -> StackFrameChecked {
5352 let calls = WithHash {
5354 data: self.calls.clone(),
5355 hash: w.exists(self.calls.hash()),
5356 };
5357 let caller_caller = w.exists(self.caller_caller.clone());
5358 let caller = w.exists(self.caller.clone());
5359
5360 let frame = StackFrameCheckedFrame {
5361 caller,
5362 caller_caller,
5363 calls,
5364 is_default: false,
5365 };
5366
5367 StackFrameChecked::of_frame(frame)
5368 }
5369 }
5370
5371 impl StackFrameCheckedFrame {
5372 pub fn hash(&self, w: &mut Witness<Fp>) -> Fp {
5373 let mut inputs = Inputs::new();
5374
5375 inputs.append(&self.caller);
5376 inputs.append(&self.caller_caller.0);
5377 inputs.append(&self.calls.hash);
5378
5379 let fields = inputs.to_fields();
5380
5381 if self.is_default {
5382 use crate::proofs::transaction::transaction_snark::checked_hash3;
5383 checked_hash3(&MINA_ACCOUNT_UPDATE_STACK_FRAME, &fields, w)
5384 } else {
5385 use crate::proofs::transaction::transaction_snark::checked_hash;
5386 checked_hash(&MINA_ACCOUNT_UPDATE_STACK_FRAME, &fields, w)
5387 }
5388 }
5389 }
5390
5391 impl StackFrameChecked {
5392 pub fn of_frame(frame: StackFrameCheckedFrame) -> Self {
5393 let frame2 = frame.clone();
5395 let hash = LazyValue::make(move |w: &mut Witness<Fp>| frame2.hash(w));
5396
5397 Self { data: frame, hash }
5398 }
5399 }
5400
5401 #[derive(Debug, Clone)]
5402 pub struct CallStack(pub Vec<StackFrame>);
5403
5404 impl Default for CallStack {
5405 fn default() -> Self {
5406 Self::new()
5407 }
5408 }
5409
5410 impl CallStack {
5411 pub fn new() -> Self {
5412 CallStack(Vec::new())
5413 }
5414
5415 pub fn is_empty(&self) -> bool {
5416 self.0.is_empty()
5417 }
5418
5419 pub fn iter(&self) -> impl Iterator<Item = &StackFrame> {
5420 self.0.iter().rev()
5421 }
5422
5423 pub fn push(&self, stack_frame: &StackFrame) -> Self {
5424 let mut ret = self.0.clone();
5425 ret.push(stack_frame.clone());
5426 Self(ret)
5427 }
5428
5429 pub fn pop(&self) -> Option<(StackFrame, CallStack)> {
5430 let mut ret = self.0.clone();
5431 ret.pop().map(|frame| (frame, Self(ret)))
5432 }
5433
5434 pub fn pop_exn(&self) -> (StackFrame, CallStack) {
5435 let mut ret = self.0.clone();
5436 if let Some(frame) = ret.pop() {
5437 (frame, Self(ret))
5438 } else {
5439 panic!()
5440 }
5441 }
5442 }
5443
5444 pub type LocalStateEnv<L> = crate::zkapps::zkapp_logic::LocalState<ZkappNonSnark<L>>;
5460
5461 #[derive(Debug, Clone)]
5463 pub struct LocalStateSkeleton<
5464 L: LedgerIntf + Clone,
5465 StackFrame: StackFrameInterface,
5466 CallStack: CallStackInterface,
5467 TC,
5468 SignedAmount: SignedAmountInterface,
5469 FailuresTable,
5470 Bool,
5471 Index: IndexInterface,
5472 > {
5473 pub stack_frame: StackFrame,
5474 pub call_stack: CallStack,
5475 pub transaction_commitment: TC,
5476 pub full_transaction_commitment: TC,
5477 pub excess: SignedAmount,
5478 pub supply_increase: SignedAmount,
5479 pub ledger: L,
5480 pub success: Bool,
5481 pub account_update_index: Index,
5482 pub failure_status_tbl: FailuresTable,
5484 pub will_succeed: Bool,
5485 }
5486
5487 #[derive(Debug, Clone, PartialEq, Eq)]
5518 pub struct LocalState {
5519 pub stack_frame: Fp,
5520 pub call_stack: Fp,
5521 pub transaction_commitment: Fp,
5522 pub full_transaction_commitment: Fp,
5523 pub excess: Signed<Amount>,
5524 pub supply_increase: Signed<Amount>,
5525 pub ledger: Fp,
5526 pub success: bool,
5527 pub account_update_index: Index,
5528 pub failure_status_tbl: Vec<Vec<TransactionFailure>>,
5529 pub will_succeed: bool,
5530 }
5531
5532 impl ToInputs for LocalState {
5533 fn to_inputs(&self, inputs: &mut Inputs) {
5535 let Self {
5536 stack_frame,
5537 call_stack,
5538 transaction_commitment,
5539 full_transaction_commitment,
5540 excess,
5541 supply_increase,
5542 ledger,
5543 success,
5544 account_update_index,
5545 failure_status_tbl: _,
5546 will_succeed,
5547 } = self;
5548
5549 inputs.append(stack_frame);
5550 inputs.append(call_stack);
5551 inputs.append(transaction_commitment);
5552 inputs.append(full_transaction_commitment);
5553 inputs.append(excess);
5554 inputs.append(supply_increase);
5555 inputs.append(ledger);
5556 inputs.append(account_update_index);
5557 inputs.append(success);
5558 inputs.append(will_succeed);
5559 }
5560 }
5561
5562 impl LocalState {
5563 pub fn dummy() -> Self {
5565 Self {
5566 stack_frame: StackFrame::empty().hash(),
5567 call_stack: Fp::zero(),
5568 transaction_commitment: Fp::zero(),
5569 full_transaction_commitment: Fp::zero(),
5570 excess: Signed::<Amount>::zero(),
5571 supply_increase: Signed::<Amount>::zero(),
5572 ledger: Fp::zero(),
5573 success: true,
5574 account_update_index: <Index as Magnitude>::zero(),
5575 failure_status_tbl: Vec::new(),
5576 will_succeed: true,
5577 }
5578 }
5579
5580 pub fn empty() -> Self {
5581 Self::dummy()
5582 }
5583
5584 pub fn equal_without_ledger(&self, other: &Self) -> bool {
5585 let Self {
5586 stack_frame,
5587 call_stack,
5588 transaction_commitment,
5589 full_transaction_commitment,
5590 excess,
5591 supply_increase,
5592 ledger: _,
5593 success,
5594 account_update_index,
5595 failure_status_tbl,
5596 will_succeed,
5597 } = self;
5598
5599 stack_frame == &other.stack_frame
5600 && call_stack == &other.call_stack
5601 && transaction_commitment == &other.transaction_commitment
5602 && full_transaction_commitment == &other.full_transaction_commitment
5603 && excess == &other.excess
5604 && supply_increase == &other.supply_increase
5605 && success == &other.success
5606 && account_update_index == &other.account_update_index
5607 && failure_status_tbl == &other.failure_status_tbl
5608 && will_succeed == &other.will_succeed
5609 }
5610
5611 pub fn checked_equal_prime(&self, other: &Self, w: &mut Witness<Fp>) -> [Boolean; 11] {
5612 let Self {
5613 stack_frame,
5614 call_stack,
5615 transaction_commitment,
5616 full_transaction_commitment,
5617 excess,
5618 supply_increase,
5619 ledger,
5620 success,
5621 account_update_index,
5622 failure_status_tbl: _,
5623 will_succeed,
5624 } = self;
5625
5626 let mut alls = [
5640 field::equal(*stack_frame, other.stack_frame, w),
5641 field::equal(*call_stack, other.call_stack, w),
5642 field::equal(*transaction_commitment, other.transaction_commitment, w),
5643 field::equal(
5644 *full_transaction_commitment,
5645 other.full_transaction_commitment,
5646 w,
5647 ),
5648 excess
5649 .to_checked::<Fp>()
5650 .equal(&other.excess.to_checked(), w),
5651 supply_increase
5652 .to_checked::<Fp>()
5653 .equal(&other.supply_increase.to_checked(), w),
5654 field::equal(*ledger, other.ledger, w),
5655 success.to_boolean().equal(&other.success.to_boolean(), w),
5656 account_update_index
5657 .to_checked::<Fp>()
5658 .equal(&other.account_update_index.to_checked(), w),
5659 Boolean::True,
5660 will_succeed
5661 .to_boolean()
5662 .equal(&other.will_succeed.to_boolean(), w),
5663 ];
5664 alls.reverse();
5665 alls
5666 }
5667 }
5668}
5669
5670fn step_all<A, L>(
5671 _constraint_constants: &ConstraintConstants,
5672 f: &impl Fn(&mut A, &GlobalState<L>, &LocalStateEnv<L>),
5673 user_acc: &mut A,
5674 (g_state, l_state): (&mut GlobalState<L>, &mut LocalStateEnv<L>),
5675) -> Result<Vec<Vec<TransactionFailure>>, String>
5676where
5677 L: LedgerNonSnark,
5678{
5679 while !l_state.stack_frame.calls.is_empty() {
5680 zkapps::non_snark::step(g_state, l_state)?;
5681 f(user_acc, g_state, l_state);
5682 }
5683 Ok(l_state.failure_status_tbl.clone())
5684}
5685
5686pub fn apply_zkapp_command_first_pass_aux<A, F, L>(
5690 constraint_constants: &ConstraintConstants,
5691 global_slot: Slot,
5692 state_view: &ProtocolStateView,
5693 init: &mut A,
5694 f: F,
5695 fee_excess: Option<Signed<Amount>>,
5696 supply_increase: Option<Signed<Amount>>,
5697 ledger: &mut L,
5698 command: &ZkAppCommand,
5699) -> Result<ZkappCommandPartiallyApplied<L>, String>
5700where
5701 L: LedgerNonSnark,
5702 F: Fn(&mut A, &GlobalState<L>, &LocalStateEnv<L>),
5703{
5704 let fee_excess = fee_excess.unwrap_or_else(Signed::zero);
5705 let supply_increase = supply_increase.unwrap_or_else(Signed::zero);
5706
5707 let previous_hash = ledger.merkle_root();
5708 let original_first_pass_account_states = {
5709 let id = command.fee_payer();
5710 let location = {
5711 let loc = ledger.location_of_account(&id);
5712 let account = loc.as_ref().and_then(|loc| ledger.get(loc));
5713 loc.zip(account)
5714 };
5715
5716 vec![(id, location)]
5717 };
5718 let (mut global_state, mut local_state) = (
5721 GlobalState {
5722 protocol_state: state_view.clone(),
5723 first_pass_ledger: ledger.clone(),
5724 second_pass_ledger: {
5725 <L as LedgerIntf>::empty(0)
5728 },
5729 fee_excess,
5730 supply_increase,
5731 block_global_slot: global_slot,
5732 },
5733 LocalStateEnv {
5734 stack_frame: StackFrame::default(),
5735 call_stack: CallStack::new(),
5736 transaction_commitment: Fp::zero(),
5737 full_transaction_commitment: Fp::zero(),
5738 excess: Signed::<Amount>::zero(),
5739 supply_increase,
5740 ledger: <L as LedgerIntf>::empty(0),
5741 success: true,
5742 account_update_index: Index::zero(),
5743 failure_status_tbl: Vec::new(),
5744 will_succeed: true,
5745 },
5746 );
5747
5748 f(init, &global_state, &local_state);
5749 let account_updates = command.all_account_updates();
5750
5751 zkapps::non_snark::start(
5752 &mut global_state,
5753 &mut local_state,
5754 zkapps::non_snark::StartData {
5755 account_updates,
5756 memo_hash: command.memo.hash(),
5757 will_succeed: true,
5760 },
5761 )?;
5762
5763 let command = command.clone();
5764 let constraint_constants = constraint_constants.clone();
5765 let state_view = state_view.clone();
5766
5767 let res = ZkappCommandPartiallyApplied {
5768 command,
5769 previous_hash,
5770 original_first_pass_account_states,
5771 constraint_constants,
5772 state_view,
5773 global_state,
5774 local_state,
5775 };
5776
5777 Ok(res)
5778}
5779
5780fn apply_zkapp_command_first_pass<L>(
5781 constraint_constants: &ConstraintConstants,
5782 global_slot: Slot,
5783 state_view: &ProtocolStateView,
5784 fee_excess: Option<Signed<Amount>>,
5785 supply_increase: Option<Signed<Amount>>,
5786 ledger: &mut L,
5787 command: &ZkAppCommand,
5788) -> Result<ZkappCommandPartiallyApplied<L>, String>
5789where
5790 L: LedgerNonSnark,
5791{
5792 let mut acc = ();
5793 let partial_stmt = apply_zkapp_command_first_pass_aux(
5794 constraint_constants,
5795 global_slot,
5796 state_view,
5797 &mut acc,
5798 |_acc, _g, _l| {},
5799 fee_excess,
5800 supply_increase,
5801 ledger,
5802 command,
5803 )?;
5804
5805 Ok(partial_stmt)
5806}
5807
5808pub fn apply_zkapp_command_second_pass_aux<A, F, L>(
5809 constraint_constants: &ConstraintConstants,
5810 init: &mut A,
5811 f: F,
5812 ledger: &mut L,
5813 c: ZkappCommandPartiallyApplied<L>,
5814) -> Result<ZkappCommandApplied, String>
5815where
5816 L: LedgerNonSnark,
5817 F: Fn(&mut A, &GlobalState<L>, &LocalStateEnv<L>),
5818{
5819 let original_account_states: Vec<(AccountId, Option<_>)> = {
5822 let accounts_referenced = c.command.accounts_referenced();
5827
5828 let mut account_states = BTreeMap::<AccountIdOrderable, Option<_>>::new();
5829
5830 let referenced = accounts_referenced.into_iter().map(|id| {
5831 let location = {
5832 let loc = ledger.location_of_account(&id);
5833 let account = loc.as_ref().and_then(|loc| ledger.get(loc));
5834 loc.zip(account)
5835 };
5836 (id, location)
5837 });
5838
5839 c.original_first_pass_account_states
5840 .into_iter()
5841 .chain(referenced)
5842 .for_each(|(id, acc_opt)| {
5843 use std::collections::btree_map::Entry::Vacant;
5844
5845 let id_with_order: AccountIdOrderable = id.into();
5846 if let Vacant(entry) = account_states.entry(id_with_order) {
5847 entry.insert(acc_opt);
5848 };
5849 });
5850
5851 account_states
5852 .into_iter()
5853 .map(|(id, account): (AccountIdOrderable, Option<_>)| (id.into(), account))
5855 .collect()
5856 };
5857
5858 let mut account_states_after_fee_payer = {
5859 c.command.accounts_referenced().into_iter().map(|id| {
5864 let loc = ledger.location_of_account(&id);
5865 let a = loc.as_ref().and_then(|loc| ledger.get(loc));
5866
5867 match a {
5868 Some(a) => (id, Some((loc.unwrap(), a))),
5869 None => (id, None),
5870 }
5871 })
5872 };
5873
5874 let accounts = || {
5875 original_account_states
5876 .iter()
5877 .map(|(id, account)| (id.clone(), account.as_ref().map(|(_loc, acc)| acc.clone())))
5878 .collect::<Vec<_>>()
5879 };
5880
5881 let mut global_state = GlobalState {
5889 second_pass_ledger: ledger.clone(),
5890 ..c.global_state
5891 };
5892
5893 let mut local_state = {
5894 if c.local_state.stack_frame.calls.is_empty() {
5895 c.local_state
5898 } else {
5899 LocalStateEnv {
5902 ledger: global_state.second_pass_ledger(),
5903 ..c.local_state
5904 }
5905 }
5906 };
5907
5908 f(init, &global_state, &local_state);
5909 let start = (&mut global_state, &mut local_state);
5910
5911 let reversed_failure_status_tbl = step_all(constraint_constants, &f, init, start)?;
5912
5913 let failure_status_tbl = reversed_failure_status_tbl
5914 .into_iter()
5915 .rev()
5916 .collect::<Vec<_>>();
5917
5918 let account_ids_originally_not_in_ledger =
5919 original_account_states
5920 .iter()
5921 .filter_map(|(acct_id, loc_and_acct)| {
5922 if loc_and_acct.is_none() {
5923 Some(acct_id)
5924 } else {
5925 None
5926 }
5927 });
5928
5929 let successfully_applied = failure_status_tbl.concat().is_empty();
5930
5931 let failure_status_tbl = if successfully_applied {
5935 failure_status_tbl
5936 } else {
5937 failure_status_tbl
5938 .into_iter()
5939 .enumerate()
5940 .map(|(idx, fs)| {
5941 if idx > 0 && fs.is_empty() {
5942 vec![TransactionFailure::Cancelled]
5943 } else {
5944 fs
5945 }
5946 })
5947 .collect()
5948 };
5949
5950 let new_accounts = account_ids_originally_not_in_ledger
5952 .filter(|acct_id| ledger.location_of_account(acct_id).is_some())
5953 .cloned()
5954 .collect::<Vec<_>>();
5955
5956 let new_accounts_is_empty = new_accounts.is_empty();
5957
5958 let valid_result = Ok(ZkappCommandApplied {
5959 accounts: accounts(),
5960 command: WithStatus {
5961 data: c.command,
5962 status: if successfully_applied {
5963 TransactionStatus::Applied
5964 } else {
5965 TransactionStatus::Failed(failure_status_tbl)
5966 },
5967 },
5968 new_accounts,
5969 });
5970
5971 if successfully_applied {
5972 valid_result
5973 } else {
5974 let other_account_update_accounts_unchanged = account_states_after_fee_payer
5975 .fold_while(true, |acc, (_, loc_opt)| match loc_opt {
5976 Some((loc, a)) => match ledger.get(&loc) {
5977 Some(a_) if !(a == a_) => FoldWhile::Done(false),
5978 _ => FoldWhile::Continue(acc),
5979 },
5980 _ => FoldWhile::Continue(acc),
5981 })
5982 .into_inner();
5983
5984 if new_accounts_is_empty && other_account_update_accounts_unchanged {
5986 valid_result
5987 } else {
5988 Err("Zkapp_command application failed but new accounts created or some of the other account_update updates applied".to_string())
5989 }
5990 }
5991}
5992
5993fn apply_zkapp_command_second_pass<L>(
5994 constraint_constants: &ConstraintConstants,
5995 ledger: &mut L,
5996 c: ZkappCommandPartiallyApplied<L>,
5997) -> Result<ZkappCommandApplied, String>
5998where
5999 L: LedgerNonSnark,
6000{
6001 let x = apply_zkapp_command_second_pass_aux(
6002 constraint_constants,
6003 &mut (),
6004 |_, _, _| {},
6005 ledger,
6006 c,
6007 )?;
6008 Ok(x)
6009}
6010
6011fn apply_zkapp_command_unchecked_aux<A, F, L>(
6012 constraint_constants: &ConstraintConstants,
6013 global_slot: Slot,
6014 state_view: &ProtocolStateView,
6015 init: &mut A,
6016 f: F,
6017 fee_excess: Option<Signed<Amount>>,
6018 supply_increase: Option<Signed<Amount>>,
6019 ledger: &mut L,
6020 command: &ZkAppCommand,
6021) -> Result<ZkappCommandApplied, String>
6022where
6023 L: LedgerNonSnark,
6024 F: Fn(&mut A, &GlobalState<L>, &LocalStateEnv<L>),
6025{
6026 let partial_stmt = apply_zkapp_command_first_pass_aux(
6027 constraint_constants,
6028 global_slot,
6029 state_view,
6030 init,
6031 &f,
6032 fee_excess,
6033 supply_increase,
6034 ledger,
6035 command,
6036 )?;
6037
6038 apply_zkapp_command_second_pass_aux(constraint_constants, init, &f, ledger, partial_stmt)
6039}
6040
6041fn apply_zkapp_command_unchecked<L>(
6042 constraint_constants: &ConstraintConstants,
6043 global_slot: Slot,
6044 state_view: &ProtocolStateView,
6045 ledger: &mut L,
6046 command: &ZkAppCommand,
6047) -> Result<(ZkappCommandApplied, (LocalStateEnv<L>, Signed<Amount>)), String>
6048where
6049 L: LedgerNonSnark,
6050{
6051 let zkapp_partially_applied: ZkappCommandPartiallyApplied<L> = apply_zkapp_command_first_pass(
6052 constraint_constants,
6053 global_slot,
6054 state_view,
6055 None,
6056 None,
6057 ledger,
6058 command,
6059 )?;
6060
6061 let mut state_res = None;
6062 let account_update_applied = apply_zkapp_command_second_pass_aux(
6063 constraint_constants,
6064 &mut state_res,
6065 |acc, global_state, local_state| {
6066 *acc = Some((local_state.clone(), global_state.fee_excess))
6067 },
6068 ledger,
6069 zkapp_partially_applied,
6070 )?;
6071 let (state, amount) = state_res.unwrap();
6072
6073 Ok((account_update_applied, (state.clone(), amount)))
6074}
6075
6076pub mod transaction_partially_applied {
6077 use super::{
6078 transaction_applied::{CoinbaseApplied, FeeTransferApplied},
6079 *,
6080 };
6081
6082 #[derive(Clone, Debug)]
6083 pub struct ZkappCommandPartiallyApplied<L: LedgerNonSnark> {
6084 pub command: ZkAppCommand,
6085 pub previous_hash: Fp,
6086 pub original_first_pass_account_states:
6087 Vec<(AccountId, Option<(L::Location, Box<Account>)>)>,
6088 pub constraint_constants: ConstraintConstants,
6089 pub state_view: ProtocolStateView,
6090 pub global_state: GlobalState<L>,
6091 pub local_state: LocalStateEnv<L>,
6092 }
6093
6094 #[derive(Clone, Debug)]
6095 pub struct FullyApplied<T> {
6096 pub previous_hash: Fp,
6097 pub applied: T,
6098 }
6099
6100 #[derive(Clone, Debug)]
6101 pub enum TransactionPartiallyApplied<L: LedgerNonSnark> {
6102 SignedCommand(FullyApplied<SignedCommandApplied>),
6103 ZkappCommand(Box<ZkappCommandPartiallyApplied<L>>),
6104 FeeTransfer(FullyApplied<FeeTransferApplied>),
6105 Coinbase(FullyApplied<CoinbaseApplied>),
6106 }
6107
6108 impl<L> TransactionPartiallyApplied<L>
6109 where
6110 L: LedgerNonSnark,
6111 {
6112 pub fn command(self) -> Transaction {
6113 use Transaction as T;
6114
6115 match self {
6116 Self::SignedCommand(s) => T::Command(UserCommand::SignedCommand(Box::new(
6117 s.applied.common.user_command.data,
6118 ))),
6119 Self::ZkappCommand(z) => T::Command(UserCommand::ZkAppCommand(Box::new(z.command))),
6120 Self::FeeTransfer(ft) => T::FeeTransfer(ft.applied.fee_transfer.data),
6121 Self::Coinbase(cb) => T::Coinbase(cb.applied.coinbase.data),
6122 }
6123 }
6124 }
6125}
6126
6127use transaction_partially_applied::{TransactionPartiallyApplied, ZkappCommandPartiallyApplied};
6128
6129pub fn apply_transaction_first_pass<L>(
6130 constraint_constants: &ConstraintConstants,
6131 global_slot: Slot,
6132 txn_state_view: &ProtocolStateView,
6133 ledger: &mut L,
6134 transaction: &Transaction,
6135) -> Result<TransactionPartiallyApplied<L>, String>
6136where
6137 L: LedgerNonSnark,
6138{
6139 use Transaction::*;
6140 use UserCommand::*;
6141
6142 let previous_hash = ledger.merkle_root();
6143 let txn_global_slot = &global_slot;
6144
6145 match transaction {
6146 Command(SignedCommand(cmd)) => apply_user_command(
6147 constraint_constants,
6148 txn_state_view,
6149 txn_global_slot,
6150 ledger,
6151 cmd,
6152 )
6153 .map(|applied| {
6154 TransactionPartiallyApplied::SignedCommand(FullyApplied {
6155 previous_hash,
6156 applied,
6157 })
6158 }),
6159 Command(ZkAppCommand(txn)) => apply_zkapp_command_first_pass(
6160 constraint_constants,
6161 global_slot,
6162 txn_state_view,
6163 None,
6164 None,
6165 ledger,
6166 txn,
6167 )
6168 .map(Box::new)
6169 .map(TransactionPartiallyApplied::ZkappCommand),
6170 FeeTransfer(fee_transfer) => {
6171 apply_fee_transfer(constraint_constants, txn_global_slot, ledger, fee_transfer).map(
6172 |applied| {
6173 TransactionPartiallyApplied::FeeTransfer(FullyApplied {
6174 previous_hash,
6175 applied,
6176 })
6177 },
6178 )
6179 }
6180 Coinbase(coinbase) => {
6181 apply_coinbase(constraint_constants, txn_global_slot, ledger, coinbase).map(|applied| {
6182 TransactionPartiallyApplied::Coinbase(FullyApplied {
6183 previous_hash,
6184 applied,
6185 })
6186 })
6187 }
6188 }
6189}
6190
6191pub fn apply_transaction_second_pass<L>(
6192 constraint_constants: &ConstraintConstants,
6193 ledger: &mut L,
6194 partial_transaction: TransactionPartiallyApplied<L>,
6195) -> Result<TransactionApplied, String>
6196where
6197 L: LedgerNonSnark,
6198{
6199 use TransactionPartiallyApplied as P;
6200
6201 match partial_transaction {
6202 P::SignedCommand(FullyApplied {
6203 previous_hash,
6204 applied,
6205 }) => Ok(TransactionApplied {
6206 previous_hash,
6207 varying: Varying::Command(CommandApplied::SignedCommand(Box::new(applied))),
6208 }),
6209 P::ZkappCommand(partially_applied) => {
6210 let previous_hash = partially_applied.previous_hash;
6215 let applied =
6216 apply_zkapp_command_second_pass(constraint_constants, ledger, *partially_applied)?;
6217
6218 Ok(TransactionApplied {
6219 previous_hash,
6220 varying: Varying::Command(CommandApplied::ZkappCommand(Box::new(applied))),
6221 })
6222 }
6223 P::FeeTransfer(FullyApplied {
6224 previous_hash,
6225 applied,
6226 }) => Ok(TransactionApplied {
6227 previous_hash,
6228 varying: Varying::FeeTransfer(applied),
6229 }),
6230 P::Coinbase(FullyApplied {
6231 previous_hash,
6232 applied,
6233 }) => Ok(TransactionApplied {
6234 previous_hash,
6235 varying: Varying::Coinbase(applied),
6236 }),
6237 }
6238}
6239
6240pub fn apply_transactions<L>(
6241 constraint_constants: &ConstraintConstants,
6242 global_slot: Slot,
6243 txn_state_view: &ProtocolStateView,
6244 ledger: &mut L,
6245 txns: &[Transaction],
6246) -> Result<Vec<TransactionApplied>, String>
6247where
6248 L: LedgerNonSnark,
6249{
6250 let first_pass: Vec<_> = txns
6251 .iter()
6252 .map(|txn| {
6253 apply_transaction_first_pass(
6254 constraint_constants,
6255 global_slot,
6256 txn_state_view,
6257 ledger,
6258 txn,
6259 )
6260 })
6261 .collect::<Result<Vec<TransactionPartiallyApplied<_>>, _>>()?;
6262
6263 first_pass
6264 .into_iter()
6265 .map(|partial_transaction| {
6266 apply_transaction_second_pass(constraint_constants, ledger, partial_transaction)
6267 })
6268 .collect()
6269}
6270
6271struct FailureCollection {
6272 inner: Vec<Vec<TransactionFailure>>,
6273}
6274
6275impl FailureCollection {
6277 fn empty() -> Self {
6278 Self {
6279 inner: Vec::default(),
6280 }
6281 }
6282
6283 fn no_failure() -> Vec<TransactionFailure> {
6284 vec![]
6285 }
6286
6287 fn single_failure() -> Self {
6289 Self {
6290 inner: vec![vec![TransactionFailure::UpdateNotPermittedBalance]],
6291 }
6292 }
6293
6294 fn update_failed() -> Vec<TransactionFailure> {
6295 vec![TransactionFailure::UpdateNotPermittedBalance]
6296 }
6297
6298 fn append_entry(list: Vec<TransactionFailure>, mut s: Self) -> Self {
6300 if s.inner.is_empty() {
6301 Self { inner: vec![list] }
6302 } else {
6303 s.inner.insert(1, list);
6304 s
6305 }
6306 }
6307
6308 fn is_empty(&self) -> bool {
6309 self.inner.iter().all(Vec::is_empty)
6310 }
6311
6312 fn take(self) -> Vec<Vec<TransactionFailure>> {
6313 self.inner
6314 }
6315}
6316
6317fn apply_coinbase<L>(
6329 constraint_constants: &ConstraintConstants,
6330 txn_global_slot: &Slot,
6331 ledger: &mut L,
6332 coinbase: &Coinbase,
6333) -> Result<transaction_applied::CoinbaseApplied, String>
6334where
6335 L: LedgerIntf,
6336{
6337 let Coinbase {
6338 receiver,
6339 amount: coinbase_amount,
6340 fee_transfer,
6341 } = &coinbase;
6342
6343 let (
6344 receiver_reward,
6345 new_accounts1,
6346 transferee_update,
6347 transferee_timing_prev,
6348 failures1,
6349 burned_tokens1,
6350 ) = match fee_transfer {
6351 None => (
6352 *coinbase_amount,
6353 None,
6354 None,
6355 None,
6356 FailureCollection::empty(),
6357 Amount::zero(),
6358 ),
6359 Some(
6360 ft @ CoinbaseFeeTransfer {
6361 receiver_pk: transferee,
6362 fee,
6363 },
6364 ) => {
6365 assert_ne!(transferee, receiver);
6366
6367 let transferee_id = ft.receiver();
6368 let fee = Amount::of_fee(fee);
6369
6370 let receiver_reward = coinbase_amount
6371 .checked_sub(&fee)
6372 .ok_or_else(|| "Coinbase fee transfer too large".to_string())?;
6373
6374 let (transferee_account, action, can_receive) =
6375 has_permission_to_receive(ledger, &transferee_id);
6376 let new_accounts = get_new_accounts(action, transferee_id.clone());
6377
6378 let timing = update_timing_when_no_deduction(txn_global_slot, &transferee_account)?;
6379
6380 let balance = {
6381 let amount = sub_account_creation_fee(constraint_constants, action, fee)?;
6382 add_amount(transferee_account.balance, amount)?
6383 };
6384
6385 if can_receive.0 {
6386 let (_, mut transferee_account, transferee_location) =
6387 ledger.get_or_create(&transferee_id)?;
6388
6389 transferee_account.balance = balance;
6390 transferee_account.timing = timing;
6391
6392 let timing = transferee_account.timing.clone();
6393
6394 (
6395 receiver_reward,
6396 new_accounts,
6397 Some((transferee_location, transferee_account)),
6398 Some(timing),
6399 FailureCollection::append_entry(
6400 FailureCollection::no_failure(),
6401 FailureCollection::empty(),
6402 ),
6403 Amount::zero(),
6404 )
6405 } else {
6406 (
6407 receiver_reward,
6408 None,
6409 None,
6410 None,
6411 FailureCollection::single_failure(),
6412 fee,
6413 )
6414 }
6415 }
6416 };
6417
6418 let receiver_id = AccountId::new(receiver.clone(), TokenId::default());
6419 let (receiver_account, action2, can_receive) = has_permission_to_receive(ledger, &receiver_id);
6420 let new_accounts2 = get_new_accounts(action2, receiver_id.clone());
6421
6422 let coinbase_receiver_timing = match transferee_timing_prev {
6429 None => update_timing_when_no_deduction(txn_global_slot, &receiver_account)?,
6430 Some(_) => receiver_account.timing.clone(),
6431 };
6432
6433 let receiver_balance = {
6434 let amount = sub_account_creation_fee(constraint_constants, action2, receiver_reward)?;
6435 add_amount(receiver_account.balance, amount)?
6436 };
6437
6438 let (failures, burned_tokens2) = if can_receive.0 {
6439 let (_action2, mut receiver_account, receiver_location) =
6440 ledger.get_or_create(&receiver_id)?;
6441
6442 receiver_account.balance = receiver_balance;
6443 receiver_account.timing = coinbase_receiver_timing;
6444
6445 ledger.set(&receiver_location, receiver_account);
6446
6447 (
6448 FailureCollection::append_entry(FailureCollection::no_failure(), failures1),
6449 Amount::zero(),
6450 )
6451 } else {
6452 (
6453 FailureCollection::append_entry(FailureCollection::update_failed(), failures1),
6454 receiver_reward,
6455 )
6456 };
6457
6458 if let Some((addr, account)) = transferee_update {
6459 ledger.set(&addr, account);
6460 };
6461
6462 let burned_tokens = burned_tokens1
6463 .checked_add(&burned_tokens2)
6464 .ok_or_else(|| "burned tokens overflow".to_string())?;
6465
6466 let status = if failures.is_empty() {
6467 TransactionStatus::Applied
6468 } else {
6469 TransactionStatus::Failed(failures.take())
6470 };
6471
6472 let new_accounts: Vec<_> = [new_accounts1, new_accounts2]
6473 .into_iter()
6474 .flatten()
6475 .collect();
6476
6477 Ok(transaction_applied::CoinbaseApplied {
6478 coinbase: WithStatus {
6479 data: coinbase.clone(),
6480 status,
6481 },
6482 new_accounts,
6483 burned_tokens,
6484 })
6485}
6486
6487fn apply_fee_transfer<L>(
6489 constraint_constants: &ConstraintConstants,
6490 txn_global_slot: &Slot,
6491 ledger: &mut L,
6492 fee_transfer: &FeeTransfer,
6493) -> Result<transaction_applied::FeeTransferApplied, String>
6494where
6495 L: LedgerIntf,
6496{
6497 let (new_accounts, failures, burned_tokens) = process_fee_transfer(
6498 ledger,
6499 fee_transfer,
6500 |action, _, balance, fee| {
6501 let amount = {
6502 let amount = Amount::of_fee(fee);
6503 sub_account_creation_fee(constraint_constants, action, amount)?
6504 };
6505 add_amount(balance, amount)
6506 },
6507 |account| update_timing_when_no_deduction(txn_global_slot, account),
6508 )?;
6509
6510 let status = if failures.is_empty() {
6511 TransactionStatus::Applied
6512 } else {
6513 TransactionStatus::Failed(failures.take())
6514 };
6515
6516 Ok(transaction_applied::FeeTransferApplied {
6517 fee_transfer: WithStatus {
6518 data: fee_transfer.clone(),
6519 status,
6520 },
6521 new_accounts,
6522 burned_tokens,
6523 })
6524}
6525
6526fn sub_account_creation_fee(
6528 constraint_constants: &ConstraintConstants,
6529 action: AccountState,
6530 amount: Amount,
6531) -> Result<Amount, String> {
6532 let account_creation_fee = Amount::from_u64(constraint_constants.account_creation_fee);
6533
6534 match action {
6535 AccountState::Added => {
6536 if let Some(amount) = amount.checked_sub(&account_creation_fee) {
6537 return Ok(amount);
6538 }
6539 Err(format!(
6540 "Error subtracting account creation fee {:?}; transaction amount {:?} insufficient",
6541 account_creation_fee, amount
6542 ))
6543 }
6544 AccountState::Existed => Ok(amount),
6545 }
6546}
6547
6548fn update_timing_when_no_deduction(
6549 txn_global_slot: &Slot,
6550 account: &Account,
6551) -> Result<Timing, String> {
6552 validate_timing(account, Amount::zero(), txn_global_slot)
6553}
6554
6555fn get_new_accounts<T>(action: AccountState, data: T) -> Option<T> {
6583 match action {
6584 AccountState::Added => Some(data),
6585 AccountState::Existed => None,
6586 }
6587}
6588
6589fn process_fee_transfer<L, FunBalance, FunTiming>(
6600 ledger: &mut L,
6601 fee_transfer: &FeeTransfer,
6602 modify_balance: FunBalance,
6603 modify_timing: FunTiming,
6604) -> Result<(Vec<AccountId>, FailureCollection, Amount), String>
6605where
6606 L: LedgerIntf,
6607 FunTiming: Fn(&Account) -> Result<Timing, String>,
6608 FunBalance: Fn(AccountState, &AccountId, Balance, &Fee) -> Result<Balance, String>,
6609{
6610 if !fee_transfer.fee_tokens().all(TokenId::is_default) {
6611 return Err("Cannot pay fees in non-default tokens.".to_string());
6612 }
6613
6614 match &**fee_transfer {
6615 OneOrTwo::One(fee_transfer) => {
6616 let account_id = fee_transfer.receiver();
6617 let (a, action, can_receive) = has_permission_to_receive(ledger, &account_id);
6618
6619 let timing = modify_timing(&a)?;
6620 let balance = modify_balance(action, &account_id, a.balance, &fee_transfer.fee)?;
6621
6622 if can_receive.0 {
6623 let (_, mut account, loc) = ledger.get_or_create(&account_id)?;
6624 let new_accounts = get_new_accounts(action, account_id.clone());
6625
6626 account.balance = balance;
6627 account.timing = timing;
6628
6629 ledger.set(&loc, account);
6630
6631 let new_accounts: Vec<_> = new_accounts.into_iter().collect();
6632 Ok((new_accounts, FailureCollection::empty(), Amount::zero()))
6633 } else {
6634 Ok((
6635 vec![],
6636 FailureCollection::single_failure(),
6637 Amount::of_fee(&fee_transfer.fee),
6638 ))
6639 }
6640 }
6641 OneOrTwo::Two((fee_transfer1, fee_transfer2)) => {
6642 let account_id1 = fee_transfer1.receiver();
6643 let (a1, action1, can_receive1) = has_permission_to_receive(ledger, &account_id1);
6644
6645 let account_id2 = fee_transfer2.receiver();
6646
6647 if account_id1 == account_id2 {
6648 let fee = fee_transfer1
6649 .fee
6650 .checked_add(&fee_transfer2.fee)
6651 .ok_or_else(|| "Overflow".to_string())?;
6652
6653 let timing = modify_timing(&a1)?;
6654 let balance = modify_balance(action1, &account_id1, a1.balance, &fee)?;
6655
6656 if can_receive1.0 {
6657 let (_, mut a1, l1) = ledger.get_or_create(&account_id1)?;
6658 let new_accounts1 = get_new_accounts(action1, account_id1);
6659
6660 a1.balance = balance;
6661 a1.timing = timing;
6662
6663 ledger.set(&l1, a1);
6664
6665 let new_accounts: Vec<_> = new_accounts1.into_iter().collect();
6666 Ok((new_accounts, FailureCollection::empty(), Amount::zero()))
6667 } else {
6668 Ok((
6671 vec![],
6672 FailureCollection::append_entry(
6673 FailureCollection::update_failed(),
6674 FailureCollection::single_failure(),
6675 ),
6676 Amount::of_fee(&fee),
6677 ))
6678 }
6679 } else {
6680 let (a2, action2, can_receive2) = has_permission_to_receive(ledger, &account_id2);
6681
6682 let balance1 =
6683 modify_balance(action1, &account_id1, a1.balance, &fee_transfer1.fee)?;
6684
6685 let timing2 = modify_timing(&a2)?;
6691 let balance2 =
6692 modify_balance(action2, &account_id2, a2.balance, &fee_transfer2.fee)?;
6693
6694 let (new_accounts1, failures, burned_tokens1) = if can_receive1.0 {
6695 let (_, mut a1, l1) = ledger.get_or_create(&account_id1)?;
6696 let new_accounts1 = get_new_accounts(action1, account_id1);
6697
6698 a1.balance = balance1;
6699 ledger.set(&l1, a1);
6700
6701 (
6702 new_accounts1,
6703 FailureCollection::append_entry(
6704 FailureCollection::no_failure(),
6705 FailureCollection::empty(),
6706 ),
6707 Amount::zero(),
6708 )
6709 } else {
6710 (
6711 None,
6712 FailureCollection::single_failure(),
6713 Amount::of_fee(&fee_transfer1.fee),
6714 )
6715 };
6716
6717 let (new_accounts2, failures, burned_tokens2) = if can_receive2.0 {
6718 let (_, mut a2, l2) = ledger.get_or_create(&account_id2)?;
6719 let new_accounts2 = get_new_accounts(action2, account_id2);
6720
6721 a2.balance = balance2;
6722 a2.timing = timing2;
6723
6724 ledger.set(&l2, a2);
6725
6726 (
6727 new_accounts2,
6728 FailureCollection::append_entry(FailureCollection::no_failure(), failures),
6729 Amount::zero(),
6730 )
6731 } else {
6732 (
6733 None,
6734 FailureCollection::append_entry(
6735 FailureCollection::update_failed(),
6736 failures,
6737 ),
6738 Amount::of_fee(&fee_transfer2.fee),
6739 )
6740 };
6741
6742 let burned_tokens = burned_tokens1
6743 .checked_add(&burned_tokens2)
6744 .ok_or_else(|| "burned tokens overflow".to_string())?;
6745
6746 let new_accounts: Vec<_> = [new_accounts1, new_accounts2]
6747 .into_iter()
6748 .flatten()
6749 .collect();
6750
6751 Ok((new_accounts, failures, burned_tokens))
6752 }
6753 }
6754 }
6755}
6756
6757#[derive(Copy, Clone, Debug)]
6758pub enum AccountState {
6759 Added,
6760 Existed,
6761}
6762
6763#[derive(Debug)]
6764struct HasPermissionToReceive(bool);
6765
6766fn has_permission_to_receive<L>(
6768 ledger: &mut L,
6769 receiver_account_id: &AccountId,
6770) -> (Box<Account>, AccountState, HasPermissionToReceive)
6771where
6772 L: LedgerIntf,
6773{
6774 use crate::PermissionTo::*;
6775 use AccountState::*;
6776
6777 let init_account = Account::initialize(receiver_account_id);
6778
6779 match ledger.location_of_account(receiver_account_id) {
6780 None => {
6781 let perm = init_account.has_permission_to(ControlTag::NoneGiven, Receive);
6783 (Box::new(init_account), Added, HasPermissionToReceive(perm))
6784 }
6785 Some(location) => match ledger.get(&location) {
6786 None => panic!("Ledger location with no account"),
6787 Some(receiver_account) => {
6788 let perm = receiver_account.has_permission_to(ControlTag::NoneGiven, Receive);
6789 (receiver_account, Existed, HasPermissionToReceive(perm))
6790 }
6791 },
6792 }
6793}
6794
6795pub fn validate_time(valid_until: &Slot, current_global_slot: &Slot) -> Result<(), String> {
6796 if current_global_slot <= valid_until {
6797 return Ok(());
6798 }
6799
6800 Err(format!(
6801 "Current global slot {:?} greater than transaction expiry slot {:?}",
6802 current_global_slot, valid_until
6803 ))
6804}
6805
6806pub fn is_timed(a: &Account) -> bool {
6807 matches!(&a.timing, Timing::Timed { .. })
6808}
6809
6810pub fn set_with_location<L>(
6811 ledger: &mut L,
6812 location: &ExistingOrNew<L::Location>,
6813 account: Box<Account>,
6814) -> Result<(), String>
6815where
6816 L: LedgerIntf,
6817{
6818 match location {
6819 ExistingOrNew::Existing(location) => {
6820 ledger.set(location, account);
6821 Ok(())
6822 }
6823 ExistingOrNew::New => ledger
6824 .create_new_account(account.id(), *account)
6825 .map_err(|_| "set_with_location".to_string()),
6826 }
6827}
6828
6829pub struct Updates<Location> {
6830 pub located_accounts: Vec<(ExistingOrNew<Location>, Box<Account>)>,
6831 pub applied_body: signed_command_applied::Body,
6832}
6833
6834pub fn compute_updates<L>(
6835 constraint_constants: &ConstraintConstants,
6836 receiver: AccountId,
6837 ledger: &mut L,
6838 current_global_slot: &Slot,
6839 user_command: &SignedCommand,
6840 fee_payer: &AccountId,
6841 fee_payer_account: &Account,
6842 fee_payer_location: &ExistingOrNew<L::Location>,
6843 reject_command: &mut bool,
6844) -> Result<Updates<L::Location>, TransactionFailure>
6845where
6846 L: LedgerIntf,
6847{
6848 match &user_command.payload.body {
6849 signed_command::Body::StakeDelegation(_) => {
6850 let (receiver_location, _) = get_with_location(ledger, &receiver).unwrap();
6851
6852 if let ExistingOrNew::New = receiver_location {
6853 return Err(TransactionFailure::ReceiverNotPresent);
6854 }
6855 if !fee_payer_account.has_permission_to_set_delegate() {
6856 return Err(TransactionFailure::UpdateNotPermittedDelegate);
6857 }
6858
6859 let previous_delegate = fee_payer_account.delegate.clone();
6860
6861 let fee_payer_account = {
6864 let timing = timing_error_to_user_command_status(validate_timing(
6865 fee_payer_account,
6866 Amount::zero(),
6867 current_global_slot,
6868 ))?;
6869
6870 Box::new(Account {
6871 delegate: Some(receiver.public_key.clone()),
6872 timing,
6873 ..fee_payer_account.clone()
6874 })
6875 };
6876
6877 Ok(Updates {
6878 located_accounts: vec![(fee_payer_location.clone(), fee_payer_account)],
6879 applied_body: signed_command_applied::Body::StakeDelegation { previous_delegate },
6880 })
6881 }
6882 signed_command::Body::Payment(payment) => {
6883 let get_fee_payer_account = || {
6884 let balance = fee_payer_account
6885 .balance
6886 .sub_amount(payment.amount)
6887 .ok_or(TransactionFailure::SourceInsufficientBalance)?;
6888
6889 let timing = timing_error_to_user_command_status(validate_timing(
6890 fee_payer_account,
6891 payment.amount,
6892 current_global_slot,
6893 ))?;
6894
6895 Ok(Box::new(Account {
6896 balance,
6897 timing,
6898 ..fee_payer_account.clone()
6899 }))
6900 };
6901
6902 let fee_payer_account = match get_fee_payer_account() {
6903 Ok(fee_payer_account) => fee_payer_account,
6904 Err(e) => {
6905 *reject_command = true;
6913 return Err(e);
6914 }
6915 };
6916
6917 let (receiver_location, mut receiver_account) = if fee_payer == &receiver {
6918 (fee_payer_location.clone(), fee_payer_account.clone())
6919 } else {
6920 get_with_location(ledger, &receiver).unwrap()
6921 };
6922
6923 if !fee_payer_account.has_permission_to_send() {
6924 return Err(TransactionFailure::UpdateNotPermittedBalance);
6925 }
6926
6927 if !receiver_account.has_permission_to_receive() {
6928 return Err(TransactionFailure::UpdateNotPermittedBalance);
6929 }
6930
6931 let receiver_amount = match &receiver_location {
6932 ExistingOrNew::Existing(_) => payment.amount,
6933 ExistingOrNew::New => {
6934 match payment
6935 .amount
6936 .checked_sub(&Amount::from_u64(constraint_constants.account_creation_fee))
6937 {
6938 Some(amount) => amount,
6939 None => return Err(TransactionFailure::AmountInsufficientToCreateAccount),
6940 }
6941 }
6942 };
6943
6944 let balance = match receiver_account.balance.add_amount(receiver_amount) {
6945 Some(balance) => balance,
6946 None => return Err(TransactionFailure::Overflow),
6947 };
6948
6949 let new_accounts = match receiver_location {
6950 ExistingOrNew::New => vec![receiver.clone()],
6951 ExistingOrNew::Existing(_) => vec![],
6952 };
6953
6954 receiver_account.balance = balance;
6955
6956 let updated_accounts = if fee_payer == &receiver {
6957 vec![(receiver_location, receiver_account)]
6959 } else {
6960 vec![
6961 (receiver_location, receiver_account),
6962 (fee_payer_location.clone(), fee_payer_account),
6963 ]
6964 };
6965
6966 Ok(Updates {
6967 located_accounts: updated_accounts,
6968 applied_body: signed_command_applied::Body::Payments { new_accounts },
6969 })
6970 }
6971 }
6972}
6973
6974pub fn apply_user_command_unchecked<L>(
6975 constraint_constants: &ConstraintConstants,
6976 _txn_state_view: &ProtocolStateView,
6977 txn_global_slot: &Slot,
6978 ledger: &mut L,
6979 user_command: &SignedCommand,
6980) -> Result<SignedCommandApplied, String>
6981where
6982 L: LedgerIntf,
6983{
6984 let SignedCommand {
6985 payload: _,
6986 signer: signer_pk,
6987 signature: _,
6988 } = &user_command;
6989 let current_global_slot = txn_global_slot;
6990
6991 let valid_until = user_command.valid_until();
6992 validate_time(&valid_until, current_global_slot)?;
6993
6994 let fee_payer = user_command.fee_payer();
6996 let (fee_payer_location, fee_payer_account) =
6997 pay_fee(user_command, signer_pk, ledger, current_global_slot)?;
6998
6999 if !fee_payer_account.has_permission_to_send() {
7000 return Err(TransactionFailure::UpdateNotPermittedBalance.to_string());
7001 }
7002 if !fee_payer_account.has_permission_to_increment_nonce() {
7003 return Err(TransactionFailure::UpdateNotPermittedNonce.to_string());
7004 }
7005
7006 set_with_location(ledger, &fee_payer_location, fee_payer_account.clone())?;
7010
7011 let receiver = user_command.receiver();
7012
7013 let mut reject_command = false;
7014
7015 match compute_updates(
7016 constraint_constants,
7017 receiver,
7018 ledger,
7019 current_global_slot,
7020 user_command,
7021 &fee_payer,
7022 &fee_payer_account,
7023 &fee_payer_location,
7024 &mut reject_command,
7025 ) {
7026 Ok(Updates {
7027 located_accounts,
7028 applied_body,
7029 }) => {
7030 for (location, account) in located_accounts {
7031 set_with_location(ledger, &location, account)?;
7032 }
7033
7034 Ok(SignedCommandApplied {
7035 common: signed_command_applied::Common {
7036 user_command: WithStatus::<SignedCommand> {
7037 data: user_command.clone(),
7038 status: TransactionStatus::Applied,
7039 },
7040 },
7041 body: applied_body,
7042 })
7043 }
7044 Err(failure) if !reject_command => Ok(SignedCommandApplied {
7045 common: signed_command_applied::Common {
7046 user_command: WithStatus::<SignedCommand> {
7047 data: user_command.clone(),
7048 status: TransactionStatus::Failed(vec![vec![failure]]),
7049 },
7050 },
7051 body: signed_command_applied::Body::Failed,
7052 }),
7053 Err(failure) => {
7054 assert!(reject_command);
7057 Err(failure.to_string())
7058 }
7059 }
7060}
7061
7062pub fn apply_user_command<L>(
7063 constraint_constants: &ConstraintConstants,
7064 txn_state_view: &ProtocolStateView,
7065 txn_global_slot: &Slot,
7066 ledger: &mut L,
7067 user_command: &SignedCommand,
7068) -> Result<SignedCommandApplied, String>
7069where
7070 L: LedgerIntf,
7071{
7072 apply_user_command_unchecked(
7073 constraint_constants,
7074 txn_state_view,
7075 txn_global_slot,
7076 ledger,
7077 user_command,
7078 )
7079}
7080
7081pub fn pay_fee<L, Loc>(
7082 user_command: &SignedCommand,
7083 signer_pk: &CompressedPubKey,
7084 ledger: &mut L,
7085 current_global_slot: &Slot,
7086) -> Result<(ExistingOrNew<Loc>, Box<Account>), String>
7087where
7088 L: LedgerIntf<Location = Loc>,
7089{
7090 let nonce = user_command.nonce();
7091 let fee_payer = user_command.fee_payer();
7092 let fee_token = user_command.fee_token();
7093
7094 if &fee_payer.public_key != signer_pk {
7095 return Err("Cannot pay fees from a public key that did not sign the transaction".into());
7096 }
7097
7098 if fee_token != TokenId::default() {
7099 return Err("Cannot create transactions with fee_token different from the default".into());
7100 }
7101
7102 pay_fee_impl(
7103 &user_command.payload,
7104 nonce,
7105 fee_payer,
7106 user_command.fee(),
7107 ledger,
7108 current_global_slot,
7109 )
7110}
7111
7112fn pay_fee_impl<L>(
7113 command: &SignedCommandPayload,
7114 nonce: Nonce,
7115 fee_payer: AccountId,
7116 fee: Fee,
7117 ledger: &mut L,
7118 current_global_slot: &Slot,
7119) -> Result<(ExistingOrNew<L::Location>, Box<Account>), String>
7120where
7121 L: LedgerIntf,
7122{
7123 let (location, mut account) = get_with_location(ledger, &fee_payer)?;
7125
7126 if let ExistingOrNew::New = location {
7127 return Err("The fee-payer account does not exist".to_string());
7128 };
7129
7130 let fee = Amount::of_fee(&fee);
7131 let balance = sub_amount(account.balance, fee)?;
7132
7133 validate_nonces(nonce, account.nonce)?;
7134 let timing = validate_timing(&account, fee, current_global_slot)?;
7135
7136 account.balance = balance;
7137 account.nonce = account.nonce.incr(); account.receipt_chain_hash = cons_signed_command_payload(command, account.receipt_chain_hash);
7139 account.timing = timing;
7140
7141 Ok((location, account))
7142
7143 }
7154
7155pub mod transaction_union_payload {
7156 use ark_ff::PrimeField;
7157 use mina_hasher::{Hashable, ROInput as LegacyInput};
7158 use mina_signer::{NetworkId, PubKey, Signature};
7159
7160 use crate::{
7161 decompress_pk,
7162 proofs::field::Boolean,
7163 scan_state::transaction_logic::signed_command::{PaymentPayload, StakeDelegationPayload},
7164 };
7165
7166 use super::*;
7167
7168 #[derive(Clone)]
7169 pub struct Common {
7170 pub fee: Fee,
7171 pub fee_token: TokenId,
7172 pub fee_payer_pk: CompressedPubKey,
7173 pub nonce: Nonce,
7174 pub valid_until: Slot,
7175 pub memo: Memo,
7176 }
7177
7178 #[derive(Clone, Debug)]
7179 pub enum Tag {
7180 Payment = 0,
7181 StakeDelegation = 1,
7182 FeeTransfer = 2,
7183 Coinbase = 3,
7184 }
7185
7186 impl Tag {
7187 pub fn is_user_command(&self) -> Boolean {
7188 match self {
7189 Tag::Payment | Tag::StakeDelegation => Boolean::True,
7190 Tag::FeeTransfer | Tag::Coinbase => Boolean::False,
7191 }
7192 }
7193
7194 pub fn is_payment(&self) -> Boolean {
7195 match self {
7196 Tag::Payment => Boolean::True,
7197 Tag::FeeTransfer | Tag::Coinbase | Tag::StakeDelegation => Boolean::False,
7198 }
7199 }
7200
7201 pub fn is_stake_delegation(&self) -> Boolean {
7202 match self {
7203 Tag::StakeDelegation => Boolean::True,
7204 Tag::FeeTransfer | Tag::Coinbase | Tag::Payment => Boolean::False,
7205 }
7206 }
7207
7208 pub fn is_fee_transfer(&self) -> Boolean {
7209 match self {
7210 Tag::FeeTransfer => Boolean::True,
7211 Tag::StakeDelegation | Tag::Coinbase | Tag::Payment => Boolean::False,
7212 }
7213 }
7214
7215 pub fn is_coinbase(&self) -> Boolean {
7216 match self {
7217 Tag::Coinbase => Boolean::True,
7218 Tag::StakeDelegation | Tag::FeeTransfer | Tag::Payment => Boolean::False,
7219 }
7220 }
7221
7222 pub fn to_bits(&self) -> [bool; 3] {
7223 let tag = self.clone() as u8;
7224 let mut bits = [false; 3];
7225 for (index, bit) in [4, 2, 1].iter().enumerate() {
7226 bits[index] = tag & bit != 0;
7227 }
7228 bits
7229 }
7230
7231 pub fn to_untagged_bits(&self) -> [bool; 5] {
7232 let mut is_payment = false;
7233 let mut is_stake_delegation = false;
7234 let mut is_fee_transfer = false;
7235 let mut is_coinbase = false;
7236 let mut is_user_command = false;
7237
7238 match self {
7239 Tag::Payment => {
7240 is_payment = true;
7241 is_user_command = true;
7242 }
7243 Tag::StakeDelegation => {
7244 is_stake_delegation = true;
7245 is_user_command = true;
7246 }
7247 Tag::FeeTransfer => is_fee_transfer = true,
7248 Tag::Coinbase => is_coinbase = true,
7249 }
7250
7251 [
7252 is_payment,
7253 is_stake_delegation,
7254 is_fee_transfer,
7255 is_coinbase,
7256 is_user_command,
7257 ]
7258 }
7259 }
7260
7261 #[derive(Clone)]
7262 pub struct Body {
7263 pub tag: Tag,
7264 pub source_pk: CompressedPubKey,
7265 pub receiver_pk: CompressedPubKey,
7266 pub token_id: TokenId,
7267 pub amount: Amount,
7268 }
7269
7270 #[derive(Clone)]
7271 pub struct TransactionUnionPayload {
7272 pub common: Common,
7273 pub body: Body,
7274 }
7275
7276 impl Hashable for TransactionUnionPayload {
7277 type D = NetworkId;
7278
7279 fn to_roinput(&self) -> LegacyInput {
7280 let fee_token_id = self.common.fee_token.0.into_bigint().0[0];
7290 let token_id = self.body.token_id.0.into_bigint().0[0];
7291
7292 let mut roi = LegacyInput::new()
7293 .append_field(self.common.fee_payer_pk.x)
7294 .append_field(self.body.source_pk.x)
7295 .append_field(self.body.receiver_pk.x)
7296 .append_u64(self.common.fee.as_u64())
7297 .append_u64(fee_token_id)
7298 .append_bool(self.common.fee_payer_pk.is_odd)
7299 .append_u32(self.common.nonce.as_u32())
7300 .append_u32(self.common.valid_until.as_u32())
7301 .append_bytes(&self.common.memo.0);
7302
7303 let tag = self.body.tag.clone() as u8;
7304 for bit in [4, 2, 1] {
7305 roi = roi.append_bool(tag & bit != 0);
7306 }
7307
7308 roi.append_bool(self.body.source_pk.is_odd)
7309 .append_bool(self.body.receiver_pk.is_odd)
7310 .append_u64(token_id)
7311 .append_u64(self.body.amount.as_u64())
7312 .append_bool(false) }
7314
7315 fn domain_string(network_id: NetworkId) -> Option<String> {
7317 match network_id {
7319 NetworkId::MAINNET => mina_core::network::mainnet::SIGNATURE_PREFIX,
7320 NetworkId::TESTNET => mina_core::network::devnet::SIGNATURE_PREFIX,
7321 }
7322 .to_string()
7323 .into()
7324 }
7325 }
7326
7327 impl TransactionUnionPayload {
7328 pub fn of_user_command_payload(payload: &SignedCommandPayload) -> Self {
7329 use signed_command::Body::{Payment, StakeDelegation};
7330
7331 Self {
7332 common: Common {
7333 fee: payload.common.fee,
7334 fee_token: TokenId::default(),
7335 fee_payer_pk: payload.common.fee_payer_pk.clone(),
7336 nonce: payload.common.nonce,
7337 valid_until: payload.common.valid_until,
7338 memo: payload.common.memo.clone(),
7339 },
7340 body: match &payload.body {
7341 Payment(PaymentPayload {
7342 receiver_pk,
7343 amount,
7344 }) => Body {
7345 tag: Tag::Payment,
7346 source_pk: payload.common.fee_payer_pk.clone(),
7347 receiver_pk: receiver_pk.clone(),
7348 token_id: TokenId::default(),
7349 amount: *amount,
7350 },
7351 StakeDelegation(StakeDelegationPayload::SetDelegate { new_delegate }) => Body {
7352 tag: Tag::StakeDelegation,
7353 source_pk: payload.common.fee_payer_pk.clone(),
7354 receiver_pk: new_delegate.clone(),
7355 token_id: TokenId::default(),
7356 amount: Amount::zero(),
7357 },
7358 },
7359 }
7360 }
7361
7362 pub fn to_input_legacy(&self) -> ::poseidon::hash::legacy::Inputs<Fp> {
7364 let mut roi = ::poseidon::hash::legacy::Inputs::new();
7365
7366 {
7368 roi.append_u64(self.common.fee.0);
7369
7370 roi.append_bool(true);
7373 for _ in 0..63 {
7374 roi.append_bool(false);
7375 }
7376
7377 roi.append_field(self.common.fee_payer_pk.x);
7379 roi.append_bool(self.common.fee_payer_pk.is_odd);
7380
7381 roi.append_u32(self.common.nonce.0);
7383
7384 roi.append_u32(self.common.valid_until.0);
7386
7387 roi.append_bytes(&self.common.memo.0);
7389 }
7390
7391 {
7393 let tag = self.body.tag.clone() as u8;
7395 for bit in [4, 2, 1] {
7396 roi.append_bool(tag & bit != 0);
7397 }
7398
7399 roi.append_field(self.body.source_pk.x);
7401 roi.append_bool(self.body.source_pk.is_odd);
7402
7403 roi.append_field(self.body.receiver_pk.x);
7405 roi.append_bool(self.body.receiver_pk.is_odd);
7406
7407 roi.append_u64(1);
7409
7410 roi.append_u64(self.body.amount.0);
7412
7413 roi.append_bool(false);
7415 }
7416
7417 roi
7418 }
7419 }
7420
7421 pub struct TransactionUnion {
7422 pub payload: TransactionUnionPayload,
7423 pub signer: PubKey,
7424 pub signature: Signature,
7425 }
7426
7427 impl TransactionUnion {
7428 pub fn of_transaction(tx: &Transaction) -> Self {
7437 match tx {
7438 Transaction::Command(cmd) => {
7439 let UserCommand::SignedCommand(cmd) = cmd else {
7440 unreachable!();
7441 };
7442
7443 let SignedCommand {
7444 payload,
7445 signer,
7446 signature,
7447 } = cmd.as_ref();
7448
7449 TransactionUnion {
7450 payload: TransactionUnionPayload::of_user_command_payload(payload),
7451 signer: decompress_pk(signer).unwrap(),
7452 signature: signature.clone(),
7453 }
7454 }
7455 Transaction::Coinbase(Coinbase {
7456 receiver,
7457 amount,
7458 fee_transfer,
7459 }) => {
7460 let CoinbaseFeeTransfer {
7461 receiver_pk: other_pk,
7462 fee: other_amount,
7463 } = fee_transfer.clone().unwrap_or_else(|| {
7464 CoinbaseFeeTransfer::create(receiver.clone(), Fee::zero())
7465 });
7466
7467 let signer = decompress_pk(&other_pk).unwrap();
7468 let payload = TransactionUnionPayload {
7469 common: Common {
7470 fee: other_amount,
7471 fee_token: TokenId::default(),
7472 fee_payer_pk: other_pk.clone(),
7473 nonce: Nonce::zero(),
7474 valid_until: Slot::max(),
7475 memo: Memo::empty(),
7476 },
7477 body: Body {
7478 source_pk: other_pk,
7479 receiver_pk: receiver.clone(),
7480 token_id: TokenId::default(),
7481 amount: *amount,
7482 tag: Tag::Coinbase,
7483 },
7484 };
7485
7486 TransactionUnion {
7487 payload,
7488 signer,
7489 signature: Signature::dummy(),
7490 }
7491 }
7492 Transaction::FeeTransfer(tr) => {
7493 let two = |SingleFeeTransfer {
7494 receiver_pk: pk1,
7495 fee: fee1,
7496 fee_token,
7497 },
7498 SingleFeeTransfer {
7499 receiver_pk: pk2,
7500 fee: fee2,
7501 fee_token: token_id,
7502 }| {
7503 let signer = decompress_pk(&pk2).unwrap();
7504 let payload = TransactionUnionPayload {
7505 common: Common {
7506 fee: fee2,
7507 fee_token,
7508 fee_payer_pk: pk2.clone(),
7509 nonce: Nonce::zero(),
7510 valid_until: Slot::max(),
7511 memo: Memo::empty(),
7512 },
7513 body: Body {
7514 source_pk: pk2,
7515 receiver_pk: pk1,
7516 token_id,
7517 amount: Amount::of_fee(&fee1),
7518 tag: Tag::FeeTransfer,
7519 },
7520 };
7521
7522 TransactionUnion {
7523 payload,
7524 signer,
7525 signature: Signature::dummy(),
7526 }
7527 };
7528
7529 match tr.0.clone() {
7530 OneOrTwo::One(t) => {
7531 let other = SingleFeeTransfer::create(
7532 t.receiver_pk.clone(),
7533 Fee::zero(),
7534 t.fee_token.clone(),
7535 );
7536 two(t, other)
7537 }
7538 OneOrTwo::Two((t1, t2)) => two(t1, t2),
7539 }
7540 }
7541 }
7542 }
7543 }
7544}
7545
7546pub fn cons_signed_command_payload(
7548 command_payload: &SignedCommandPayload,
7549 last_receipt_chain_hash: ReceiptChainHash,
7550) -> ReceiptChainHash {
7551 use poseidon::hash::legacy;
7554
7555 let ReceiptChainHash(last_receipt_chain_hash) = last_receipt_chain_hash;
7556 let union = TransactionUnionPayload::of_user_command_payload(command_payload);
7557
7558 let mut inputs = union.to_input_legacy();
7559 inputs.append_field(last_receipt_chain_hash);
7560 let hash = legacy::hash_with_kimchi(&legacy::params::CODA_RECEIPT_UC, &inputs.to_fields());
7561
7562 ReceiptChainHash(hash)
7563}
7564
7565pub fn checked_cons_signed_command_payload(
7567 payload: &TransactionUnionPayload,
7568 last_receipt_chain_hash: ReceiptChainHash,
7569 w: &mut Witness<Fp>,
7570) -> ReceiptChainHash {
7571 use crate::proofs::transaction::{
7572 legacy_input::CheckedLegacyInput, transaction_snark::checked_legacy_hash,
7573 };
7574 use poseidon::hash::legacy;
7575
7576 let mut inputs = payload.to_checked_legacy_input_owned(w);
7577 inputs.append_field(last_receipt_chain_hash.0);
7578
7579 let receipt_chain_hash = checked_legacy_hash(&legacy::params::CODA_RECEIPT_UC, inputs, w);
7580
7581 ReceiptChainHash(receipt_chain_hash)
7582}
7583
7584pub fn cons_zkapp_command_commitment(
7588 index: Index,
7589 e: ZkAppCommandElt,
7590 receipt_hash: &ReceiptChainHash,
7591) -> ReceiptChainHash {
7592 let ZkAppCommandElt::ZkAppCommandCommitment(x) = e;
7593
7594 let mut inputs = Inputs::new();
7595
7596 inputs.append(&index);
7597 inputs.append_field(x.0);
7598 inputs.append(receipt_hash);
7599
7600 ReceiptChainHash(hash_with_kimchi(&CODA_RECEIPT_UC, &inputs.to_fields()))
7601}
7602
7603fn validate_nonces(txn_nonce: Nonce, account_nonce: Nonce) -> Result<(), String> {
7604 if account_nonce == txn_nonce {
7605 return Ok(());
7606 }
7607
7608 Err(format!(
7609 "Nonce in account {:?} different from nonce in transaction {:?}",
7610 account_nonce, txn_nonce,
7611 ))
7612}
7613
7614pub fn validate_timing(
7615 account: &Account,
7616 txn_amount: Amount,
7617 txn_global_slot: &Slot,
7618) -> Result<Timing, String> {
7619 let (timing, _) = validate_timing_with_min_balance(account, txn_amount, txn_global_slot)?;
7620
7621 Ok(timing)
7622}
7623
7624pub fn account_check_timing(
7625 txn_global_slot: &Slot,
7626 account: &Account,
7627) -> (TimingValidation<bool>, Timing) {
7628 let (invalid_timing, timing, _) =
7629 validate_timing_with_min_balance_impl(account, Amount::from_u64(0), txn_global_slot);
7630 (invalid_timing, timing)
7632}
7633
7634fn validate_timing_with_min_balance(
7635 account: &Account,
7636 txn_amount: Amount,
7637 txn_global_slot: &Slot,
7638) -> Result<(Timing, MinBalance), String> {
7639 use TimingValidation::*;
7640
7641 let (possibly_error, timing, min_balance) =
7642 validate_timing_with_min_balance_impl(account, txn_amount, txn_global_slot);
7643
7644 match possibly_error {
7645 InsufficientBalance(true) => Err(format!(
7646 "For timed account, the requested transaction for amount {:?} \
7647 at global slot {:?}, the balance {:?} \
7648 is insufficient",
7649 txn_amount, txn_global_slot, account.balance
7650 )),
7651 InvalidTiming(true) => Err(format!(
7652 "For timed account {}, the requested transaction for amount {:?} \
7653 at global slot {:?}, applying the transaction would put the \
7654 balance below the calculated minimum balance of {:?}",
7655 account.public_key.into_address(),
7656 txn_amount,
7657 txn_global_slot,
7658 min_balance.0
7659 )),
7660 InsufficientBalance(false) => {
7661 panic!("Broken invariant in validate_timing_with_min_balance'")
7662 }
7663 InvalidTiming(false) => Ok((timing, min_balance)),
7664 }
7665}
7666
7667pub fn timing_error_to_user_command_status(
7668 timing_result: Result<Timing, String>,
7669) -> Result<Timing, TransactionFailure> {
7670 match timing_result {
7671 Ok(timing) => Ok(timing),
7672 Err(err_str) => {
7673 if err_str.contains("minimum balance") {
7678 return Err(TransactionFailure::SourceMinimumBalanceViolation);
7679 }
7680
7681 if err_str.contains("is insufficient") {
7682 return Err(TransactionFailure::SourceInsufficientBalance);
7683 }
7684
7685 panic!("Unexpected timed account validation error")
7686 }
7687 }
7688}
7689
7690pub enum TimingValidation<B> {
7691 InsufficientBalance(B),
7692 InvalidTiming(B),
7693}
7694
7695#[derive(Debug)]
7696struct MinBalance(Balance);
7697
7698fn validate_timing_with_min_balance_impl(
7699 account: &Account,
7700 txn_amount: Amount,
7701 txn_global_slot: &Slot,
7702) -> (TimingValidation<bool>, Timing, MinBalance) {
7703 use crate::Timing::*;
7704 use TimingValidation::*;
7705
7706 match &account.timing {
7707 Untimed => {
7708 match account.balance.sub_amount(txn_amount) {
7710 None => (
7711 InsufficientBalance(true),
7712 Untimed,
7713 MinBalance(Balance::zero()),
7714 ),
7715 Some(_) => (InvalidTiming(false), Untimed, MinBalance(Balance::zero())),
7716 }
7717 }
7718 Timed {
7719 initial_minimum_balance,
7720 ..
7721 } => {
7722 let account_balance = account.balance;
7723
7724 let (invalid_balance, invalid_timing, curr_min_balance) =
7725 match account_balance.sub_amount(txn_amount) {
7726 None => {
7727 (true, false, *initial_minimum_balance)
7732 }
7733 Some(proposed_new_balance) => {
7734 let curr_min_balance = account.min_balance_at_slot(*txn_global_slot);
7735
7736 if proposed_new_balance < curr_min_balance {
7737 (false, true, curr_min_balance)
7738 } else {
7739 (false, false, curr_min_balance)
7740 }
7741 }
7742 };
7743
7744 let possibly_error = if invalid_balance {
7746 InsufficientBalance(invalid_balance)
7747 } else {
7748 InvalidTiming(invalid_timing)
7749 };
7750
7751 if curr_min_balance > Balance::zero() {
7752 (
7753 possibly_error,
7754 account.timing.clone(),
7755 MinBalance(curr_min_balance),
7756 )
7757 } else {
7758 (possibly_error, Untimed, MinBalance(Balance::zero()))
7759 }
7760 }
7761 }
7762}
7763
7764fn sub_amount(balance: Balance, amount: Amount) -> Result<Balance, String> {
7765 balance
7766 .sub_amount(amount)
7767 .ok_or_else(|| "insufficient funds".to_string())
7768}
7769
7770fn add_amount(balance: Balance, amount: Amount) -> Result<Balance, String> {
7771 balance
7772 .add_amount(amount)
7773 .ok_or_else(|| "overflow".to_string())
7774}
7775
7776#[derive(Clone, Debug)]
7777pub enum ExistingOrNew<Loc> {
7778 Existing(Loc),
7779 New,
7780}
7781
7782fn get_with_location<L>(
7783 ledger: &mut L,
7784 account_id: &AccountId,
7785) -> Result<(ExistingOrNew<L::Location>, Box<Account>), String>
7786where
7787 L: LedgerIntf,
7788{
7789 match ledger.location_of_account(account_id) {
7790 Some(location) => match ledger.get(&location) {
7791 Some(account) => Ok((ExistingOrNew::Existing(location), account)),
7792 None => panic!("Ledger location with no account"),
7793 },
7794 None => Ok((
7795 ExistingOrNew::New,
7796 Box::new(Account::create_with(account_id.clone(), Balance::zero())),
7797 )),
7798 }
7799}
7800
7801pub fn get_account<L>(
7802 ledger: &mut L,
7803 account_id: AccountId,
7804) -> (Box<Account>, ExistingOrNew<L::Location>)
7805where
7806 L: LedgerIntf,
7807{
7808 let (loc, account) = get_with_location(ledger, &account_id).unwrap();
7809 (account, loc)
7810}
7811
7812pub fn set_account<'a, L>(
7813 l: &'a mut L,
7814 (a, loc): (Box<Account>, &ExistingOrNew<L::Location>),
7815) -> &'a mut L
7816where
7817 L: LedgerIntf,
7818{
7819 set_with_location(l, loc, a).unwrap();
7820 l
7821}
7822
7823#[cfg(any(test, feature = "fuzzing"))]
7824pub mod for_tests {
7825 use mina_signer::Keypair;
7826 use rand::Rng;
7827
7828 use crate::{
7829 gen_keypair, scan_state::parallel_scan::ceil_log2, AuthRequired, Mask, Permissions,
7830 VerificationKey, ZkAppAccount, TXN_VERSION_CURRENT,
7831 };
7832
7833 use super::*;
7834
7835 const MIN_INIT_BALANCE: u64 = 8000000000;
7836 const MAX_INIT_BALANCE: u64 = 8000000000000;
7837 const NUM_ACCOUNTS: u64 = 10;
7838 const NUM_TRANSACTIONS: u64 = 10;
7839 const DEPTH: u64 = ceil_log2(NUM_ACCOUNTS + NUM_TRANSACTIONS);
7840
7841 #[derive(Debug, PartialEq, Eq)]
7844 pub struct HashableKeypair(pub Keypair);
7845
7846 impl std::hash::Hash for HashableKeypair {
7847 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
7848 let compressed = self.0.public.into_compressed();
7849 HashableCompressedPubKey(compressed).hash(state);
7850 }
7851 }
7852
7853 #[derive(Clone, Debug, Eq, derive_more::From)]
7856 pub struct HashableCompressedPubKey(pub CompressedPubKey);
7857
7858 impl PartialEq for HashableCompressedPubKey {
7859 fn eq(&self, other: &Self) -> bool {
7860 self.0 == other.0
7861 }
7862 }
7863
7864 impl std::hash::Hash for HashableCompressedPubKey {
7865 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
7866 self.0.x.hash(state);
7867 self.0.is_odd.hash(state);
7868 }
7869 }
7870
7871 impl PartialOrd for HashableCompressedPubKey {
7872 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
7873 match self.0.x.partial_cmp(&other.0.x) {
7874 Some(core::cmp::Ordering::Equal) => {}
7875 ord => return ord,
7876 };
7877 self.0.is_odd.partial_cmp(&other.0.is_odd)
7878 }
7879 }
7880
7881 #[derive(Debug)]
7883 pub struct InitLedger(pub Vec<(Keypair, u64)>);
7884
7885 #[derive(Debug)]
7887 pub struct TransactionSpec {
7888 pub fee: Fee,
7889 pub sender: (Keypair, Nonce),
7890 pub receiver: CompressedPubKey,
7891 pub amount: Amount,
7892 }
7893
7894 #[derive(Debug)]
7896 pub struct TestSpec {
7897 pub init_ledger: InitLedger,
7898 pub specs: Vec<TransactionSpec>,
7899 }
7900
7901 impl InitLedger {
7902 pub fn init(&self, zkapp: Option<bool>, ledger: &mut impl LedgerIntf) {
7903 let zkapp = zkapp.unwrap_or(true);
7904
7905 self.0.iter().for_each(|(kp, amount)| {
7906 let (_tag, mut account, loc) = ledger
7907 .get_or_create(&AccountId::new(
7908 kp.public.into_compressed(),
7909 TokenId::default(),
7910 ))
7911 .unwrap();
7912
7913 use AuthRequired::Either;
7914 let permissions = Permissions {
7915 edit_state: Either,
7916 access: AuthRequired::None,
7917 send: Either,
7918 receive: AuthRequired::None,
7919 set_delegate: Either,
7920 set_permissions: Either,
7921 set_verification_key: crate::SetVerificationKey {
7922 auth: Either,
7923 txn_version: TXN_VERSION_CURRENT,
7924 },
7925 set_zkapp_uri: Either,
7926 edit_action_state: Either,
7927 set_token_symbol: Either,
7928 increment_nonce: Either,
7929 set_voting_for: Either,
7930 set_timing: Either,
7931 };
7932
7933 let zkapp = if zkapp {
7934 let zkapp = ZkAppAccount {
7935 verification_key: Some(VerificationKeyWire::new(
7936 crate::dummy::trivial_verification_key(),
7937 )),
7938 ..Default::default()
7939 };
7940
7941 Some(zkapp.into())
7942 } else {
7943 None
7944 };
7945
7946 account.balance = Balance::from_u64(*amount);
7947 account.permissions = permissions;
7948 account.zkapp = zkapp;
7949
7950 ledger.set(&loc, account);
7951 });
7952 }
7953
7954 pub fn gen() -> Self {
7955 let mut rng = rand::thread_rng();
7956
7957 let mut tbl = HashSet::with_capacity(256);
7958
7959 let init = (0..NUM_ACCOUNTS)
7960 .map(|_| {
7961 let kp = loop {
7962 let keypair = gen_keypair();
7963 let compressed = keypair.public.into_compressed();
7964 if !tbl.contains(&HashableCompressedPubKey(compressed)) {
7965 break keypair;
7966 }
7967 };
7968
7969 let amount = rng.gen_range(MIN_INIT_BALANCE..MAX_INIT_BALANCE);
7970 tbl.insert(HashableCompressedPubKey(kp.public.into_compressed()));
7971 (kp, amount)
7972 })
7973 .collect();
7974
7975 Self(init)
7976 }
7977 }
7978
7979 impl TransactionSpec {
7980 pub fn gen(init_ledger: &InitLedger, nonces: &mut HashMap<HashableKeypair, Nonce>) -> Self {
7981 let mut rng = rand::thread_rng();
7982
7983 let pk = |(kp, _): (Keypair, u64)| kp.public.into_compressed();
7984
7985 let receiver_is_new: bool = rng.gen();
7986
7987 let mut gen_index = || rng.gen_range(0..init_ledger.0.len().checked_sub(1).unwrap());
7988
7989 let receiver_index = if receiver_is_new {
7990 None
7991 } else {
7992 Some(gen_index())
7993 };
7994
7995 let receiver = match receiver_index {
7996 None => gen_keypair().public.into_compressed(),
7997 Some(i) => pk(init_ledger.0[i].clone()),
7998 };
7999
8000 let sender = {
8001 let i = match receiver_index {
8002 None => gen_index(),
8003 Some(j) => loop {
8004 let i = gen_index();
8005 if i != j {
8006 break i;
8007 }
8008 },
8009 };
8010 init_ledger.0[i].0.clone()
8011 };
8012
8013 let nonce = nonces
8014 .get(&HashableKeypair(sender.clone()))
8015 .cloned()
8016 .unwrap();
8017
8018 let amount = Amount::from_u64(rng.gen_range(1_000_000..100_000_000));
8019 let fee = Fee::from_u64(rng.gen_range(1_000_000..100_000_000));
8020
8021 let old = nonces.get_mut(&HashableKeypair(sender.clone())).unwrap();
8022 *old = old.incr();
8023
8024 Self {
8025 fee,
8026 sender: (sender, nonce),
8027 receiver,
8028 amount,
8029 }
8030 }
8031 }
8032
8033 impl TestSpec {
8034 fn mk_gen(num_transactions: Option<u64>) -> TestSpec {
8035 let num_transactions = num_transactions.unwrap_or(NUM_TRANSACTIONS);
8036
8037 let init_ledger = InitLedger::gen();
8038
8039 let mut map = init_ledger
8040 .0
8041 .iter()
8042 .map(|(kp, _)| (HashableKeypair(kp.clone()), Nonce::zero()))
8043 .collect();
8044
8045 let specs = (0..num_transactions)
8046 .map(|_| TransactionSpec::gen(&init_ledger, &mut map))
8047 .collect();
8048
8049 Self { init_ledger, specs }
8050 }
8051
8052 pub fn gen() -> Self {
8053 Self::mk_gen(Some(NUM_TRANSACTIONS))
8054 }
8055 }
8056
8057 #[derive(Debug)]
8058 pub struct UpdateStatesSpec {
8059 pub fee: Fee,
8060 pub sender: (Keypair, Nonce),
8061 pub fee_payer: Option<(Keypair, Nonce)>,
8062 pub receivers: Vec<(CompressedPubKey, Amount)>,
8063 pub amount: Amount,
8064 pub zkapp_account_keypairs: Vec<Keypair>,
8065 pub memo: Memo,
8066 pub new_zkapp_account: bool,
8067 pub snapp_update: zkapp_command::Update,
8068 pub current_auth: AuthRequired,
8070 pub actions: Vec<Vec<Fp>>,
8071 pub events: Vec<Vec<Fp>>,
8072 pub call_data: Fp,
8073 pub preconditions: Option<zkapp_command::Preconditions>,
8074 }
8075
8076 pub fn trivial_zkapp_account(
8077 permissions: Option<Permissions<AuthRequired>>,
8078 vk: VerificationKey,
8079 pk: CompressedPubKey,
8080 ) -> Account {
8081 let id = AccountId::new(pk, TokenId::default());
8082 let mut account = Account::create_with(id, Balance::from_u64(1_000_000_000_000_000));
8083 account.permissions = permissions.unwrap_or_else(Permissions::user_default);
8084 account.zkapp = Some(
8085 ZkAppAccount {
8086 verification_key: Some(VerificationKeyWire::new(vk)),
8087 ..Default::default()
8088 }
8089 .into(),
8090 );
8091 account
8092 }
8093
8094 pub fn create_trivial_zkapp_account(
8095 permissions: Option<Permissions<AuthRequired>>,
8096 vk: VerificationKey,
8097 ledger: &mut Mask,
8098 pk: CompressedPubKey,
8099 ) {
8100 let id = AccountId::new(pk.clone(), TokenId::default());
8101 let account = trivial_zkapp_account(permissions, vk, pk);
8102 assert!(BaseLedger::location_of_account(ledger, &id).is_none());
8103 ledger.get_or_create_account(id, account).unwrap();
8104 }
8105}
8106
8107#[cfg(test)]
8108mod tests {
8109 use std::str::FromStr;
8110
8111 use o1_utils::FieldHelpers;
8112
8113 #[cfg(target_family = "wasm")]
8114 use wasm_bindgen_test::wasm_bindgen_test as test;
8115
8116 use super::{
8117 signed_command::{Body, Common, PaymentPayload},
8118 *,
8119 };
8120
8121 fn pub_key(address: &str) -> CompressedPubKey {
8122 mina_signer::PubKey::from_address(address)
8123 .unwrap()
8124 .into_compressed()
8125 }
8126
8127 #[test]
8128 fn test_hash_empty_event() {
8129 const EXPECTED: &str =
8131 "6963060754718463299978089777716994949151371320681588566338620419071140958308";
8132
8133 let event = zkapp_command::Event::empty();
8134 assert_eq!(event.hash(), Fp::from_str(EXPECTED).unwrap());
8135 }
8136
8137 #[test]
8140 fn test_cons_receipt_hash_ocaml() {
8141 let from = pub_key("B62qr71UxuyKpkSKYceCPsjw14nuaeLwWKZdMqaBMPber5AAF6nkowS");
8142 let to = pub_key("B62qnvGVnU7FXdy8GdkxL7yciZ8KattyCdq5J6mzo5NCxjgQPjL7BTH");
8143
8144 let common = Common {
8145 fee: Fee::from_u64(9758327274353182341),
8146 fee_payer_pk: from,
8147 nonce: Nonce::from_u32(1609569868),
8148 valid_until: Slot::from_u32(2127252111),
8149 memo: Memo([
8150 1, 32, 101, 26, 225, 104, 115, 118, 55, 102, 76, 118, 108, 78, 114, 50, 0, 115,
8151 110, 108, 53, 75, 109, 112, 50, 110, 88, 97, 76, 66, 76, 81, 235, 79,
8152 ]),
8153 };
8154
8155 let body = Body::Payment(PaymentPayload {
8156 receiver_pk: to,
8157 amount: Amount::from_u64(1155659205107036493),
8158 });
8159
8160 let tx = SignedCommandPayload { common, body };
8161
8162 let prev = "4918218371695029984164006552208340844155171097348169027410983585063546229555";
8163 let prev_receipt_chain_hash = ReceiptChainHash(Fp::from_str(prev).unwrap());
8164
8165 let next = "19078048535981853335308913493724081578728104896524544653528728307378106007337";
8166 let next_receipt_chain_hash = ReceiptChainHash(Fp::from_str(next).unwrap());
8167
8168 let result = cons_signed_command_payload(&tx, prev_receipt_chain_hash);
8169 assert_eq!(result, next_receipt_chain_hash);
8170 }
8171
8172 #[test]
8173 fn test_receipt_hash_update() {
8174 let from = pub_key("B62qmnY6m4c6bdgSPnQGZriSaj9vuSjsfh6qkveGTsFX3yGA5ywRaja");
8175 let to = pub_key("B62qjVQLxt9nYMWGn45mkgwYfcz8e8jvjNCBo11VKJb7vxDNwv5QLPS");
8176
8177 let common = Common {
8178 fee: Fee::from_u64(14500000),
8179 fee_payer_pk: from,
8180 nonce: Nonce::from_u32(15),
8181 valid_until: Slot::from_u32(-1i32 as u32),
8182 memo: Memo([
8183 1, 7, 84, 104, 101, 32, 49, 48, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
8184 0, 0, 0, 0, 0, 0, 0, 0, 0,
8185 ]),
8186 };
8187
8188 let body = Body::Payment(PaymentPayload {
8189 receiver_pk: to,
8190 amount: Amount::from_u64(2354000000),
8191 });
8192
8193 let tx = SignedCommandPayload { common, body };
8194
8195 let mut prev =
8196 hex::decode("09ac04c9965b885acfc9c54141dbecfc63b2394a4532ea2c598d086b894bfb14")
8197 .unwrap();
8198 prev.reverse();
8199 let prev_receipt_chain_hash = ReceiptChainHash(Fp::from_bytes(&prev).unwrap());
8200
8201 let mut next =
8202 hex::decode("3ecaa73739df77549a2f92f7decf822562d0593373cff1e480bb24b4c87dc8f0")
8203 .unwrap();
8204 next.reverse();
8205 let next_receipt_chain_hash = ReceiptChainHash(Fp::from_bytes(&next).unwrap());
8206
8207 let result = cons_signed_command_payload(&tx, prev_receipt_chain_hash);
8208 assert_eq!(result, next_receipt_chain_hash);
8209 }
8210}