mina_tree/scan_state/
transaction_logic.rs

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