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