1use leo_ast::interpreter_value::Value;
18use leo_errors::{BufferEmitter, ErrBuffer, Handler, LeoError, Result, WarningBuffer};
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 serde_json;
40use snarkvm::prelude::{ConsensusVersion, Network};
41use std::{fmt, str::FromStr as _};
42
43type CurrentNetwork = TestnetV0;
44
45#[derive(Debug)]
47pub struct Config {
48 pub seed: u64,
49 pub start_height: Option<u32>,
50 pub programs: Vec<Program>,
51}
52
53#[derive(Clone, Debug, Default)]
55pub struct Program {
56 pub bytecode: String,
57 pub name: String,
58}
59
60#[derive(Clone, Debug, Default)]
62pub struct Case {
63 pub program_name: String,
64 pub function: String,
65 pub private_key: Option<String>,
66 pub input: Vec<String>,
67}
68
69#[derive(Clone, Debug, PartialEq, Eq)]
71pub enum Status {
72 None,
73 Aborted,
74 Accepted,
75 Rejected,
76 Halted(String),
77}
78
79impl fmt::Display for Status {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 match self {
82 Status::Halted(s) => write!(f, "halted ({s})"),
83 Status::None => "none".fmt(f),
84 Status::Aborted => "aborted".fmt(f),
85 Status::Accepted => "accepted".fmt(f),
86 Status::Rejected => "rejected".fmt(f),
87 }
88 }
89}
90
91#[derive(Debug)]
93pub struct CaseOutcome {
94 pub status: Status,
95 pub verified: bool,
96 pub errors: ErrBuffer,
97 pub warnings: WarningBuffer,
98 pub execution: String,
99 pub output: Value,
100}
101
102pub fn run_with_ledger(
108 config: &Config,
109 cases: &[Case],
110 handler: &Handler,
111 buf: &BufferEmitter,
112) -> Result<Vec<CaseOutcome>> {
113 if cases.is_empty() {
114 return Ok(Vec::new());
115 }
116
117 let mut rng = ChaCha20Rng::seed_from_u64(config.seed);
119
120 let genesis_private_key = PrivateKey::new(&mut rng).unwrap();
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, 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 }
144
145 for Program { bytecode, name } in &config.programs {
147 let aleo_program =
150 ProgramCore::from_str(bytecode).map_err(|e| anyhow!("Failed to parse bytecode of program {name}: {e}"))?;
151
152 let mut deploy = || -> Result<()> {
153 let deployment = ledger
156 .vm()
157 .deploy(&genesis_private_key, &aleo_program, None, 0, None, &mut rng)
158 .map_err(|e| anyhow!("Failed to deploy program {name}: {e}"))?;
159 let block = ledger
160 .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], vec![deployment], &mut rng)
161 .map_err(|e| anyhow!("Failed to prepare to advance block for program {name}: {e}"))?;
162 ledger
163 .advance_to_next_block(&block)
164 .map_err(|e| anyhow!("Failed to advance block for program {name}: {e}"))?;
165
166 if block.transactions().num_accepted() != 1 {
168 return Err(anyhow!("Deployment transaction for program {name} not accepted.").into());
169 }
170 Ok(())
171 };
172
173 deploy()?;
175 if !aleo_program.contains_constructor() {
177 deploy()?;
178 }
179 }
180
181 let transactions: Vec<Transaction<CurrentNetwork>> = cases
183 .iter()
184 .filter_map(|case| case.private_key.as_ref())
185 .map(|key| {
186 let private_key = PrivateKey::<CurrentNetwork>::from_str(key).expect("Failed to parse private key.");
188 let address = Address::try_from(private_key).expect("Failed to convert private key to address.");
190 ledger
192 .vm()
193 .execute(
194 &genesis_private_key,
195 ("credits.aleo", "transfer_public"),
196 [
197 SvmValue::from_str(&format!("{address}")).expect("Failed to parse recipient address"),
198 SvmValue::from_str("1_000_000_000_000u64").expect("Failed to parse amount"),
199 ]
200 .iter(),
201 None,
202 0u64,
203 None,
204 &mut rng,
205 )
206 .expect("Failed to generate funding transaction")
207 })
208 .collect();
209
210 let block = ledger
212 .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], transactions, &mut rng)
213 .expect("Failed to prepare advance to next beacon block");
214 assert!(block.aborted_transaction_ids().is_empty());
216 assert_eq!(block.transactions().num_rejected(), 0);
217 ledger.advance_to_next_block(&block).expect("Failed to advance to next block");
219
220 let mut case_outcomes = Vec::new();
221
222 for case in cases {
223 assert!(
224 ledger.vm().contains_program(&ProgramID::from_str(&case.program_name).unwrap()),
225 "Program {} should exist.",
226 case.program_name
227 );
228
229 let private_key = case
230 .private_key
231 .as_ref()
232 .map(|key| PrivateKey::from_str(key).expect("Failed to parse private key."))
233 .unwrap_or(genesis_private_key);
234
235 let mut execution = None;
236 let mut verified = false;
237 let mut status = Status::None;
238
239 let execute_output = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
243 ledger.vm().execute_with_response(
244 &private_key,
245 (&case.program_name, &case.function),
246 case.input.iter(),
247 None,
248 0,
249 None,
250 &mut rng,
251 )
252 }));
253
254 if let Err(payload) = execute_output {
255 let s1 = payload.downcast_ref::<&str>().map(|s| s.to_string());
256 let s2 = payload.downcast_ref::<String>().cloned();
257 let s = s1.or(s2).unwrap_or_else(|| "Unknown panic payload".to_string());
258
259 case_outcomes.push(CaseOutcome {
260 status: Status::Halted(s),
261 verified: false,
262 errors: buf.extract_errs(),
263 warnings: buf.extract_warnings(),
264 execution: "".to_string(),
265 output: Value::make_unit(),
266 });
267 continue;
268 }
269
270 let result = execute_output.unwrap().and_then(|(transaction, response)| {
271 verified = ledger.vm().check_transaction(&transaction, None, &mut rng).is_ok();
272 execution = Some(transaction.clone());
273 let block = ledger.prepare_advance_to_next_beacon_block(
274 &private_key,
275 vec![],
276 vec![],
277 vec![transaction],
278 &mut rng,
279 )?;
280 status = match (block.aborted_transaction_ids().is_empty(), block.transactions().num_accepted() == 1) {
281 (false, _) => Status::Aborted,
282 (true, true) => Status::Accepted,
283 (true, false) => Status::Rejected,
284 };
285 ledger.advance_to_next_block(&block)?;
286 Ok(response)
287 });
288
289 let output = match result {
290 Ok(response) => {
291 let outputs = response.outputs();
292 match outputs.len() {
293 0 => Value::make_unit(),
294 1 => outputs[0].clone().into(),
295 _ => Value::make_tuple(outputs.iter().map(|x| x.clone().into())),
296 }
297 }
298 Err(e) => {
299 handler.emit_err(LeoError::Anyhow(e));
300 Value::make_unit()
301 }
302 };
303
304 let execution = if let Some(Transaction::Execute(_, _, execution, _)) = execution {
307 Some(Execution::from(execution.into_transitions(), Default::default(), None).unwrap())
308 } else {
309 None
310 };
311
312 case_outcomes.push(CaseOutcome {
313 status,
314 verified,
315 errors: buf.extract_errs(),
316 warnings: buf.extract_warnings(),
317 execution: serde_json::to_string_pretty(&execution).expect("Serialization failure"),
318 output,
319 });
320 }
321
322 Ok(case_outcomes)
323}