1use leo_ast::{TEST_PRIVATE_KEY, interpreter_value::Value};
18use leo_errors::Result;
19
20use aleo_std_storage::StorageMode;
21use anyhow::anyhow;
22use snarkvm::{
23 prelude::{
24 Address,
25 Execution,
26 Ledger,
27 PrivateKey,
28 ProgramID,
29 TestnetV0,
30 Transaction,
31 VM,
32 Value as SvmValue,
33 store::{ConsensusStore, helpers::memory::ConsensusMemory},
34 },
35 synthesizer::program::ProgramCore,
36};
37
38use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng as _};
39use rayon::prelude::*;
40use serde_json;
41use snarkvm::prelude::{ConsensusVersion, Network};
42use std::{fmt, str::FromStr as _};
43
44type CurrentNetwork = TestnetV0;
45
46#[derive(Debug)]
48pub struct Config {
49 pub seed: u64,
50 pub start_height: Option<u32>,
52 pub programs: Vec<Program>,
53}
54
55#[derive(Clone, Debug, Default)]
57pub struct Program {
58 pub bytecode: String,
59 pub name: String,
60}
61
62#[derive(Clone, Debug, Default)]
64pub struct Case {
65 pub program_name: String,
66 pub function: String,
67 pub private_key: Option<String>,
68 pub input: Vec<String>,
69}
70
71#[derive(Clone, Debug, PartialEq, Eq)]
73pub enum Status {
74 None,
75 Aborted,
76 Accepted,
77 Rejected,
78 Halted(String),
79}
80
81impl fmt::Display for Status {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 match self {
84 Status::Halted(s) => write!(f, "halted ({s})"),
85 Status::None => "none".fmt(f),
86 Status::Aborted => "aborted".fmt(f),
87 Status::Accepted => "accepted".fmt(f),
88 Status::Rejected => "rejected".fmt(f),
89 }
90 }
91}
92
93#[derive(Debug, Clone)]
95pub struct CaseOutcome {
96 pub program_name: String,
97 pub function: String,
98 pub status: Status,
99 pub verified: bool,
100 pub execution: String,
101 pub output: Value,
102}
103
104pub fn run_with_ledger(config: &Config, case_sets: &[Vec<Case>]) -> Result<Vec<Vec<CaseOutcome>>> {
110 if case_sets.is_empty() {
111 return Ok(Vec::new());
112 }
113
114 let mut rng = ChaCha20Rng::seed_from_u64(config.seed);
116
117 let genesis_private_key = PrivateKey::from_str(TEST_PRIVATE_KEY).unwrap();
119
120 let mut blocks = Vec::new();
122
123 let genesis_block = VM::<CurrentNetwork, ConsensusMemory<CurrentNetwork>>::from(ConsensusStore::open(0).unwrap())
125 .unwrap()
126 .genesis_beacon(&genesis_private_key, &mut rng)
127 .unwrap();
128
129 let ledger =
131 Ledger::<CurrentNetwork, ConsensusMemory<CurrentNetwork>>::load(genesis_block.clone(), StorageMode::Production)
132 .unwrap();
133
134 let latest_consensus_version = ConsensusVersion::latest();
136 let start_height =
137 config.start_height.unwrap_or(CurrentNetwork::CONSENSUS_HEIGHT(latest_consensus_version).unwrap());
138 while ledger.latest_height() < start_height {
139 let block = ledger
140 .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], vec![], &mut rng)
141 .map_err(|_| anyhow!("Failed to prepare advance to next beacon block"))?;
142 ledger.advance_to_next_block(&block).map_err(|_| anyhow!("Failed to advance to next block"))?;
143 blocks.push(block);
144 }
145
146 for Program { bytecode, name } in &config.programs {
148 let aleo_program =
151 ProgramCore::from_str(bytecode).map_err(|e| anyhow!("Failed to parse bytecode of program {name}: {e}"))?;
152
153 let mut deploy = || -> Result<()> {
154 let deployment = ledger
157 .vm()
158 .deploy(&genesis_private_key, &aleo_program, None, 0, None, &mut rng)
159 .map_err(|e| anyhow!("Failed to deploy program {name}: {e}"))?;
160 let block = ledger
161 .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], vec![deployment], &mut rng)
162 .map_err(|e| anyhow!("Failed to prepare to advance block for program {name}: {e}"))?;
163 ledger
164 .advance_to_next_block(&block)
165 .map_err(|e| anyhow!("Failed to advance block for program {name}: {e}"))?;
166
167 if block.transactions().num_accepted() != 1 {
169 return Err(anyhow!("Deployment transaction for program {name} not accepted.").into());
170 }
171
172 blocks.push(block);
174
175 Ok(())
176 };
177
178 deploy()?;
180 if !aleo_program.contains_constructor() {
182 deploy()?;
183 }
184 }
185
186 let mut indexed_ledgers = vec![(0, ledger)];
188 indexed_ledgers.extend(
189 (1..case_sets.len())
190 .into_par_iter()
191 .map(|i| {
192 let l = Ledger::<CurrentNetwork, ConsensusMemory<CurrentNetwork>>::load(
194 genesis_block.clone(),
195 StorageMode::Production,
196 )
197 .expect("Failed to load copy of ledger");
198 for block in blocks.iter() {
200 l.advance_to_next_block(block).expect("Failed to add setup block to ledger");
201 }
202
203 (i, l)
204 })
205 .collect::<Vec<_>>(),
206 );
207
208 let results = indexed_ledgers
210 .into_par_iter()
211 .map(|(index, ledger)| {
212 let cases = &case_sets[index];
214 let mut rng = rng.clone();
216
217 let transactions: Vec<Transaction<CurrentNetwork>> = cases
219 .iter()
220 .filter_map(|case| case.private_key.as_ref())
221 .map(|key| {
222 let private_key =
224 PrivateKey::<CurrentNetwork>::from_str(key).expect("Failed to parse private key.");
225 let address = Address::try_from(private_key).expect("Failed to convert private key to address.");
227 ledger
229 .vm()
230 .execute(
231 &genesis_private_key,
232 ("credits.aleo", "transfer_public"),
233 [
234 SvmValue::from_str(&format!("{address}")).expect("Failed to parse recipient address"),
235 SvmValue::from_str("1_000_000_000_000u64").expect("Failed to parse amount"),
236 ]
237 .iter(),
238 None,
239 0u64,
240 None,
241 &mut rng,
242 )
243 .expect("Failed to generate funding transaction")
244 })
245 .collect();
246
247 let block = ledger
249 .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], transactions, &mut rng)
250 .expect("Failed to prepare advance to next beacon block");
251 assert!(block.aborted_transaction_ids().is_empty());
253 assert_eq!(block.transactions().num_rejected(), 0);
254 ledger.advance_to_next_block(&block).expect("Failed to advance to next block");
256
257 let mut case_outcomes = Vec::new();
258
259 for case in cases {
260 assert!(
261 ledger.vm().contains_program(&ProgramID::from_str(&case.program_name).unwrap()),
262 "Program {} should exist.",
263 case.program_name
264 );
265
266 let private_key = case
267 .private_key
268 .as_ref()
269 .map(|key| PrivateKey::from_str(key).expect("Failed to parse private key."))
270 .unwrap_or(genesis_private_key);
271
272 let mut execution = None;
273 let mut verified = false;
274 let mut status = Status::None;
275
276 let execute_output = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
280 ledger.vm().execute_with_response(
281 &private_key,
282 (&case.program_name, &case.function),
283 case.input.iter(),
284 None,
285 0,
286 None,
287 &mut rng,
288 )
289 }));
290
291 if let Err(payload) = execute_output {
292 let s1 = payload.downcast_ref::<&str>().map(|s| s.to_string());
293 let s2 = payload.downcast_ref::<String>().cloned();
294 let s = s1.or(s2).unwrap_or_else(|| "Unknown panic payload".to_string());
295
296 case_outcomes.push(CaseOutcome {
297 program_name: case.program_name.clone(),
298 function: case.function.clone(),
299 status: Status::Halted(s),
300 verified: false,
301 execution: "".to_string(),
302 output: Value::make_unit(),
303 });
304 continue;
305 }
306
307 let result = execute_output.unwrap().and_then(|(transaction, response)| {
308 verified = ledger.vm().check_transaction(&transaction, None, &mut rng).is_ok();
309 execution = Some(transaction.clone());
310 let block = ledger.prepare_advance_to_next_beacon_block(
311 &private_key,
312 vec![],
313 vec![],
314 vec![transaction],
315 &mut rng,
316 )?;
317 status =
318 match (block.aborted_transaction_ids().is_empty(), block.transactions().num_accepted() == 1) {
319 (false, _) => Status::Aborted,
320 (true, true) => Status::Accepted,
321 (true, false) => Status::Rejected,
322 };
323 ledger.advance_to_next_block(&block)?;
324 Ok(response)
325 });
326
327 let output = match result {
328 Ok(response) => {
329 let outputs = response.outputs();
330 match outputs.len() {
331 0 => Value::make_unit(),
332 1 => outputs[0].clone().into(),
333 _ => Value::make_tuple(outputs.iter().map(|x| x.clone().into())),
334 }
335 }
336 Err(e) => Value::make_string(format!("Failed to extract output: {e}")),
337 };
338
339 let execution = if let Some(Transaction::Execute(_, _, execution, _)) = execution {
342 Some(Execution::from(execution.into_transitions(), Default::default(), None).unwrap())
343 } else {
344 None
345 };
346
347 case_outcomes.push(CaseOutcome {
348 program_name: case.program_name.clone(),
349 function: case.function.clone(),
350 status,
351 verified,
352 execution: serde_json::to_string_pretty(&execution).expect("Serialization failure"),
353 output,
354 });
355 }
356
357 Ok((index, case_outcomes))
358 })
359 .collect::<Result<Vec<_>>>()?;
360
361 let mut ordered_results: Vec<Vec<CaseOutcome>> = vec![Default::default(); case_sets.len()];
363 for (index, outcomes) in results.into_iter() {
364 ordered_results[index] = outcomes;
365 }
366
367 Ok(ordered_results)
368}