|
| 1 | +use super::{ |
| 2 | + zkapp_command, Account, AccountId, Amount, Balance, Fee, Memo, Nonce, TokenId, |
| 3 | + VerificationKeyWire, |
| 4 | +}; |
| 5 | +use crate::{ |
| 6 | + gen_keypair, |
| 7 | + scan_state::{currency::Magnitude, parallel_scan::ceil_log2}, |
| 8 | + sparse_ledger::LedgerIntf, |
| 9 | + AuthRequired, BaseLedger, Mask, Permissions, VerificationKey, ZkAppAccount, |
| 10 | + TXN_VERSION_CURRENT, |
| 11 | +}; |
| 12 | +use mina_curves::pasta::Fp; |
| 13 | +use mina_signer::{CompressedPubKey, Keypair}; |
| 14 | +use rand::Rng; |
| 15 | +use std::collections::{HashMap, HashSet}; |
| 16 | + |
| 17 | +const MIN_INIT_BALANCE: u64 = 8000000000; |
| 18 | +const MAX_INIT_BALANCE: u64 = 8000000000000; |
| 19 | +const NUM_ACCOUNTS: u64 = 10; |
| 20 | +const NUM_TRANSACTIONS: u64 = 10; |
| 21 | +const DEPTH: u64 = ceil_log2(NUM_ACCOUNTS + NUM_TRANSACTIONS); |
| 22 | + |
| 23 | +/// Use this for tests only |
| 24 | +/// Hashmaps are not deterministic |
| 25 | +#[derive(Debug, PartialEq, Eq)] |
| 26 | +pub struct HashableKeypair(pub Keypair); |
| 27 | + |
| 28 | +impl std::hash::Hash for HashableKeypair { |
| 29 | + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { |
| 30 | + let compressed = self.0.public.into_compressed(); |
| 31 | + HashableCompressedPubKey(compressed).hash(state); |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +/// Use this for tests only |
| 36 | +/// Hashmaps are not deterministic |
| 37 | +#[derive(Clone, Debug, Eq, derive_more::From)] |
| 38 | +pub struct HashableCompressedPubKey(pub CompressedPubKey); |
| 39 | + |
| 40 | +impl PartialEq for HashableCompressedPubKey { |
| 41 | + fn eq(&self, other: &Self) -> bool { |
| 42 | + self.0 == other.0 |
| 43 | + } |
| 44 | +} |
| 45 | + |
| 46 | +impl std::hash::Hash for HashableCompressedPubKey { |
| 47 | + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { |
| 48 | + self.0.x.hash(state); |
| 49 | + self.0.is_odd.hash(state); |
| 50 | + } |
| 51 | +} |
| 52 | + |
| 53 | +impl PartialOrd for HashableCompressedPubKey { |
| 54 | + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { |
| 55 | + match self.0.x.partial_cmp(&other.0.x) { |
| 56 | + Some(core::cmp::Ordering::Equal) => {} |
| 57 | + ord => return ord, |
| 58 | + }; |
| 59 | + self.0.is_odd.partial_cmp(&other.0.is_odd) |
| 60 | + } |
| 61 | +} |
| 62 | + |
| 63 | +/// OCaml reference: src/lib/transaction_logic/mina_transaction_logic.ml L:2285-2285 |
| 64 | +/// Commit: 5da42ccd72e791f164d4d200cf1ce300262873b3 |
| 65 | +/// Last verified: 2025-10-10 |
| 66 | +#[derive(Debug)] |
| 67 | +pub struct InitLedger(pub Vec<(Keypair, u64)>); |
| 68 | + |
| 69 | +/// OCaml reference: src/lib/transaction_logic/mina_transaction_logic.ml L:2351-2356 |
| 70 | +/// Commit: 5da42ccd72e791f164d4d200cf1ce300262873b3 |
| 71 | +/// Last verified: 2025-10-10 |
| 72 | +#[derive(Debug)] |
| 73 | +pub struct TransactionSpec { |
| 74 | + pub fee: Fee, |
| 75 | + pub sender: (Keypair, Nonce), |
| 76 | + pub receiver: CompressedPubKey, |
| 77 | + pub amount: Amount, |
| 78 | +} |
| 79 | + |
| 80 | +/// OCaml reference: src/lib/transaction_logic/mina_transaction_logic.ml L:2407 |
| 81 | +/// Commit: 5da42ccd72e791f164d4d200cf1ce300262873b3 |
| 82 | +/// Last verified: 2025-10-10 |
| 83 | +#[derive(Debug)] |
| 84 | +pub struct TestSpec { |
| 85 | + pub init_ledger: InitLedger, |
| 86 | + pub specs: Vec<TransactionSpec>, |
| 87 | +} |
| 88 | + |
| 89 | +impl InitLedger { |
| 90 | + pub fn init(&self, zkapp: Option<bool>, ledger: &mut impl LedgerIntf) { |
| 91 | + let zkapp = zkapp.unwrap_or(true); |
| 92 | + |
| 93 | + self.0.iter().for_each(|(kp, amount)| { |
| 94 | + let (_tag, mut account, loc) = ledger |
| 95 | + .get_or_create(&AccountId::new( |
| 96 | + kp.public.into_compressed(), |
| 97 | + TokenId::default(), |
| 98 | + )) |
| 99 | + .unwrap(); |
| 100 | + |
| 101 | + use AuthRequired::Either; |
| 102 | + let permissions = Permissions { |
| 103 | + edit_state: Either, |
| 104 | + access: AuthRequired::None, |
| 105 | + send: Either, |
| 106 | + receive: AuthRequired::None, |
| 107 | + set_delegate: Either, |
| 108 | + set_permissions: Either, |
| 109 | + set_verification_key: crate::SetVerificationKey { |
| 110 | + auth: Either, |
| 111 | + txn_version: TXN_VERSION_CURRENT, |
| 112 | + }, |
| 113 | + set_zkapp_uri: Either, |
| 114 | + edit_action_state: Either, |
| 115 | + set_token_symbol: Either, |
| 116 | + increment_nonce: Either, |
| 117 | + set_voting_for: Either, |
| 118 | + set_timing: Either, |
| 119 | + }; |
| 120 | + |
| 121 | + let zkapp = if zkapp { |
| 122 | + let zkapp = ZkAppAccount { |
| 123 | + verification_key: Some(VerificationKeyWire::new( |
| 124 | + crate::dummy::trivial_verification_key(), |
| 125 | + )), |
| 126 | + ..Default::default() |
| 127 | + }; |
| 128 | + |
| 129 | + Some(zkapp.into()) |
| 130 | + } else { |
| 131 | + None |
| 132 | + }; |
| 133 | + |
| 134 | + account.balance = Balance::from_u64(*amount); |
| 135 | + account.permissions = permissions; |
| 136 | + account.zkapp = zkapp; |
| 137 | + |
| 138 | + ledger.set(&loc, account); |
| 139 | + }); |
| 140 | + } |
| 141 | + |
| 142 | + pub fn gen() -> Self { |
| 143 | + let mut rng = rand::thread_rng(); |
| 144 | + |
| 145 | + let mut tbl = HashSet::with_capacity(256); |
| 146 | + |
| 147 | + let init = (0..NUM_ACCOUNTS) |
| 148 | + .map(|_| { |
| 149 | + let kp = loop { |
| 150 | + let keypair = gen_keypair(); |
| 151 | + let compressed = keypair.public.into_compressed(); |
| 152 | + if !tbl.contains(&HashableCompressedPubKey(compressed)) { |
| 153 | + break keypair; |
| 154 | + } |
| 155 | + }; |
| 156 | + |
| 157 | + let amount = rng.gen_range(MIN_INIT_BALANCE..MAX_INIT_BALANCE); |
| 158 | + tbl.insert(HashableCompressedPubKey(kp.public.into_compressed())); |
| 159 | + (kp, amount) |
| 160 | + }) |
| 161 | + .collect(); |
| 162 | + |
| 163 | + Self(init) |
| 164 | + } |
| 165 | +} |
| 166 | + |
| 167 | +impl TransactionSpec { |
| 168 | + pub fn gen(init_ledger: &InitLedger, nonces: &mut HashMap<HashableKeypair, Nonce>) -> Self { |
| 169 | + let mut rng = rand::thread_rng(); |
| 170 | + |
| 171 | + let pk = |(kp, _): (Keypair, u64)| kp.public.into_compressed(); |
| 172 | + |
| 173 | + let receiver_is_new: bool = rng.gen(); |
| 174 | + |
| 175 | + let mut gen_index = || rng.gen_range(0..init_ledger.0.len().checked_sub(1).unwrap()); |
| 176 | + |
| 177 | + let receiver_index = if receiver_is_new { |
| 178 | + None |
| 179 | + } else { |
| 180 | + Some(gen_index()) |
| 181 | + }; |
| 182 | + |
| 183 | + let receiver = match receiver_index { |
| 184 | + None => gen_keypair().public.into_compressed(), |
| 185 | + Some(i) => pk(init_ledger.0[i].clone()), |
| 186 | + }; |
| 187 | + |
| 188 | + let sender = { |
| 189 | + let i = match receiver_index { |
| 190 | + None => gen_index(), |
| 191 | + Some(j) => loop { |
| 192 | + let i = gen_index(); |
| 193 | + if i != j { |
| 194 | + break i; |
| 195 | + } |
| 196 | + }, |
| 197 | + }; |
| 198 | + init_ledger.0[i].0.clone() |
| 199 | + }; |
| 200 | + |
| 201 | + let nonce = nonces |
| 202 | + .get(&HashableKeypair(sender.clone())) |
| 203 | + .cloned() |
| 204 | + .unwrap(); |
| 205 | + |
| 206 | + let amount = Amount::from_u64(rng.gen_range(1_000_000..100_000_000)); |
| 207 | + let fee = Fee::from_u64(rng.gen_range(1_000_000..100_000_000)); |
| 208 | + |
| 209 | + let old = nonces.get_mut(&HashableKeypair(sender.clone())).unwrap(); |
| 210 | + *old = old.incr(); |
| 211 | + |
| 212 | + Self { |
| 213 | + fee, |
| 214 | + sender: (sender, nonce), |
| 215 | + receiver, |
| 216 | + amount, |
| 217 | + } |
| 218 | + } |
| 219 | +} |
| 220 | + |
| 221 | +impl TestSpec { |
| 222 | + fn mk_gen(num_transactions: Option<u64>) -> TestSpec { |
| 223 | + let num_transactions = num_transactions.unwrap_or(NUM_TRANSACTIONS); |
| 224 | + |
| 225 | + let init_ledger = InitLedger::gen(); |
| 226 | + |
| 227 | + let mut map = init_ledger |
| 228 | + .0 |
| 229 | + .iter() |
| 230 | + .map(|(kp, _)| (HashableKeypair(kp.clone()), Nonce::zero())) |
| 231 | + .collect(); |
| 232 | + |
| 233 | + let specs = (0..num_transactions) |
| 234 | + .map(|_| TransactionSpec::gen(&init_ledger, &mut map)) |
| 235 | + .collect(); |
| 236 | + |
| 237 | + Self { init_ledger, specs } |
| 238 | + } |
| 239 | + |
| 240 | + pub fn gen() -> Self { |
| 241 | + Self::mk_gen(Some(NUM_TRANSACTIONS)) |
| 242 | + } |
| 243 | +} |
| 244 | + |
| 245 | +#[derive(Debug)] |
| 246 | +pub struct UpdateStatesSpec { |
| 247 | + pub fee: Fee, |
| 248 | + pub sender: (Keypair, Nonce), |
| 249 | + pub fee_payer: Option<(Keypair, Nonce)>, |
| 250 | + pub receivers: Vec<(CompressedPubKey, Amount)>, |
| 251 | + pub amount: Amount, |
| 252 | + pub zkapp_account_keypairs: Vec<Keypair>, |
| 253 | + pub memo: Memo, |
| 254 | + pub new_zkapp_account: bool, |
| 255 | + pub snapp_update: zkapp_command::Update, |
| 256 | + // Authorization for the update being performed |
| 257 | + pub current_auth: AuthRequired, |
| 258 | + pub actions: Vec<Vec<Fp>>, |
| 259 | + pub events: Vec<Vec<Fp>>, |
| 260 | + pub call_data: Fp, |
| 261 | + pub preconditions: Option<zkapp_command::Preconditions>, |
| 262 | +} |
| 263 | + |
| 264 | +pub fn trivial_zkapp_account( |
| 265 | + permissions: Option<Permissions<AuthRequired>>, |
| 266 | + vk: VerificationKey, |
| 267 | + pk: CompressedPubKey, |
| 268 | +) -> Account { |
| 269 | + let id = AccountId::new(pk, TokenId::default()); |
| 270 | + let mut account = Account::create_with(id, Balance::from_u64(1_000_000_000_000_000)); |
| 271 | + account.permissions = permissions.unwrap_or_else(Permissions::user_default); |
| 272 | + account.zkapp = Some( |
| 273 | + ZkAppAccount { |
| 274 | + verification_key: Some(VerificationKeyWire::new(vk)), |
| 275 | + ..Default::default() |
| 276 | + } |
| 277 | + .into(), |
| 278 | + ); |
| 279 | + account |
| 280 | +} |
| 281 | + |
| 282 | +pub fn create_trivial_zkapp_account( |
| 283 | + permissions: Option<Permissions<AuthRequired>>, |
| 284 | + vk: VerificationKey, |
| 285 | + ledger: &mut Mask, |
| 286 | + pk: CompressedPubKey, |
| 287 | +) { |
| 288 | + let id = AccountId::new(pk.clone(), TokenId::default()); |
| 289 | + let account = trivial_zkapp_account(permissions, vk, pk); |
| 290 | + assert!(BaseLedger::location_of_account(ledger, &id).is_none()); |
| 291 | + ledger.get_or_create_account(id, account).unwrap(); |
| 292 | +} |
0 commit comments