mina_tree/scan_state/
transaction_logic.rs

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