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