1use leo_errors::{BufferEmitter, ErrBuffer, Handler, LeoError, Result, WarningBuffer};
18
19use aleo_std::StorageMode;
20use snarkvm::{
21 prelude::{
22 Address,
23 Execution,
24 Ledger,
25 PrivateKey,
26 ProgramID,
27 TestnetV0,
28 Transaction,
29 VM,
30 Value,
31 anyhow,
32 store::{ConsensusStore, helpers::memory::ConsensusMemory},
33 },
34 synthesizer::program::ProgramCore,
35};
36
37use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng as _};
38use serde_json;
39use snarkvm::prelude::{ConsensusVersion, Network};
40use std::{fmt, str::FromStr as _};
41
42type CurrentNetwork = TestnetV0;
43
44pub struct Config {
46 pub seed: u64,
47 pub start_height: Option<u32>,
48 pub programs: Vec<Program>,
49}
50
51#[derive(Clone, Debug, Default)]
53pub struct Program {
54 pub bytecode: String,
55 pub name: String,
56}
57
58#[derive(Clone, Debug, Default)]
60pub struct Case {
61 pub program_name: String,
62 pub function: String,
63 pub private_key: Option<String>,
64 pub input: Vec<String>,
65}
66
67#[derive(Clone, PartialEq, Eq)]
69pub enum Status {
70 None,
71 Aborted,
72 Accepted,
73 Rejected,
74 Halted(String),
75}
76
77impl fmt::Display for Status {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 match self {
80 Status::Halted(s) => write!(f, "halted ({s})"),
81 Status::None => "none".fmt(f),
82 Status::Aborted => "aborted".fmt(f),
83 Status::Accepted => "accepted".fmt(f),
84 Status::Rejected => "rejected".fmt(f),
85 }
86 }
87}
88
89pub struct CaseOutcome {
91 pub status: Status,
92 pub verified: bool,
93 pub errors: ErrBuffer,
94 pub warnings: WarningBuffer,
95 pub execution: String,
96}
97
98pub fn run_with_ledger(
104 config: &Config,
105 cases: &[Case],
106 handler: &Handler,
107 buf: &BufferEmitter,
108) -> Result<Vec<CaseOutcome>> {
109 if cases.is_empty() {
110 return Ok(Vec::new());
111 }
112
113 let mut rng = ChaCha20Rng::seed_from_u64(config.seed);
115
116 let genesis_private_key = PrivateKey::new(&mut rng).unwrap();
118
119 let genesis_block = VM::<CurrentNetwork, ConsensusMemory<CurrentNetwork>>::from(ConsensusStore::open(0).unwrap())
121 .unwrap()
122 .genesis_beacon(&genesis_private_key, &mut rng)
123 .unwrap();
124
125 let ledger =
127 Ledger::<CurrentNetwork, ConsensusMemory<CurrentNetwork>>::load(genesis_block, StorageMode::Production)
128 .unwrap();
129
130 let latest_consensus_version = ConsensusVersion::latest();
132 let start_height =
133 config.start_height.unwrap_or(CurrentNetwork::CONSENSUS_HEIGHT(latest_consensus_version).unwrap());
134 while ledger.latest_height() < start_height {
135 let block = ledger
136 .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], vec![], &mut rng)
137 .map_err(|_| anyhow!("Failed to prepare advance to next beacon block"))?;
138 ledger.advance_to_next_block(&block).map_err(|_| anyhow!("Failed to advance to next block"))?;
139 }
140
141 for Program { bytecode, name } in &config.programs {
143 let aleo_program =
146 ProgramCore::from_str(bytecode).map_err(|e| anyhow!("Failed to parse bytecode of program {name}: {e}"))?;
147
148 let mut deploy = || -> Result<()> {
149 let deployment = ledger
152 .vm()
153 .deploy(&genesis_private_key, &aleo_program, None, 0, None, &mut rng)
154 .map_err(|e| anyhow!("Failed to deploy program {name}: {e}"))?;
155 let block = ledger
156 .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], vec![deployment], &mut rng)
157 .map_err(|e| anyhow!("Failed to prepare to advance block for program {name}: {e}"))?;
158 ledger
159 .advance_to_next_block(&block)
160 .map_err(|e| anyhow!("Failed to advance block for program {name}: {e}"))?;
161
162 if block.transactions().num_accepted() != 1 {
164 return Err(anyhow!("Deployment transaction for program {name} not accepted.").into());
165 }
166 Ok(())
167 };
168
169 deploy()?;
171 if !aleo_program.contains_constructor() {
173 deploy()?;
174 }
175 }
176
177 let transactions: Vec<Transaction<CurrentNetwork>> = cases
179 .iter()
180 .filter_map(|case| case.private_key.as_ref())
181 .map(|key| {
182 let private_key = PrivateKey::<CurrentNetwork>::from_str(key).expect("Failed to parse private key.");
184 let address = Address::try_from(private_key).expect("Failed to convert private key to address.");
186 ledger
188 .vm()
189 .execute(
190 &genesis_private_key,
191 ("credits.aleo", "transfer_public"),
192 [
193 Value::from_str(&format!("{address}")).expect("Failed to parse recipient address"),
194 Value::from_str("1_000_000_000_000u64").expect("Failed to parse amount"),
195 ]
196 .iter(),
197 None,
198 0u64,
199 None,
200 &mut rng,
201 )
202 .expect("Failed to generate funding transaction")
203 })
204 .collect();
205
206 let block = ledger
208 .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], transactions, &mut rng)
209 .expect("Failed to prepare advance to next beacon block");
210 assert!(block.aborted_transaction_ids().is_empty());
212 assert_eq!(block.transactions().num_rejected(), 0);
213 ledger.advance_to_next_block(&block).expect("Failed to advance to next block");
215
216 let mut case_outcomes = Vec::new();
217
218 for case in cases {
219 assert!(
220 ledger.vm().contains_program(&ProgramID::from_str(&case.program_name).unwrap()),
221 "Program {} should exist.",
222 case.program_name
223 );
224
225 let private_key = case
226 .private_key
227 .as_ref()
228 .map(|key| PrivateKey::from_str(key).expect("Failed to parse private key."))
229 .unwrap_or(genesis_private_key);
230
231 let mut execution = None;
232 let mut verified = false;
233 let mut status = Status::None;
234
235 let execute_output = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
239 ledger.vm().execute(
240 &private_key,
241 (&case.program_name, &case.function),
242 case.input.iter(),
243 None,
244 0,
245 None,
246 &mut rng,
247 )
248 }));
249
250 if let Err(payload) = execute_output {
251 let s1 = payload.downcast_ref::<&str>().map(|s| s.to_string());
252 let s2 = payload.downcast_ref::<String>().cloned();
253 let s = s1.or(s2).unwrap_or_else(|| "Unknown panic payload".to_string());
254
255 case_outcomes.push(CaseOutcome {
256 status: Status::Halted(s),
257 verified: false,
258 errors: buf.extract_errs(),
259 warnings: buf.extract_warnings(),
260 execution: "".to_string(),
261 });
262 continue;
263 }
264
265 let result = execute_output
266 .unwrap()
267 .and_then(|transaction| {
268 verified = ledger.vm().check_transaction(&transaction, None, &mut rng).is_ok();
269 execution = Some(transaction.clone());
270 ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], &mut rng)
271 })
272 .and_then(|block| {
273 status = match (block.aborted_transaction_ids().is_empty(), block.transactions().num_accepted() == 1) {
274 (false, _) => Status::Aborted,
275 (true, true) => Status::Accepted,
276 (true, false) => Status::Rejected,
277 };
278 ledger.advance_to_next_block(&block)
279 });
280
281 if let Err(e) = result {
282 handler.emit_err(LeoError::Anyhow(e));
283 }
284
285 let execution = if let Some(Transaction::Execute(_, _, execution, _)) = execution {
288 let transitions = execution.into_transitions();
289 Some(Execution::from(transitions, Default::default(), None).unwrap())
290 } else {
291 None
292 };
293
294 case_outcomes.push(CaseOutcome {
295 status,
296 verified,
297 errors: buf.extract_errs(),
298 warnings: buf.extract_warnings(),
299 execution: serde_json::to_string_pretty(&execution).expect("Serialization failure"),
300 });
301 }
302
303 Ok(case_outcomes)
304}