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