1use alloc::{
2 string::{String, ToString},
3 vec::Vec,
4};
5#[cfg(feature = "std")]
6use std::{ffi::OsStr, path::Path};
7
8use miden_processor::{
9 ExecutionOptions, StackInputs,
10 advice::{AdviceInputs, AdviceStack},
11};
12use serde::Deserialize;
13
14use crate::felt::Felt;
15
16#[derive(Debug, Clone, Default, Deserialize)]
17#[serde(try_from = "ExecutionConfigFile")]
18pub struct ExecutionConfig {
19 pub inputs: StackInputs,
20 pub advice_inputs: AdviceInputs,
21 pub options: ExecutionOptions,
22}
23
24impl TryFrom<ExecutionConfigFile> for ExecutionConfig {
25 type Error = String;
26
27 #[inline]
28 fn try_from(file: ExecutionConfigFile) -> Result<Self, Self::Error> {
29 Self::from_inputs_file(file)
30 }
31}
32
33impl ExecutionConfig {
34 #[cfg(feature = "std")]
35 pub fn parse_file<P>(path: P) -> std::io::Result<Self>
36 where
37 P: AsRef<std::path::Path>,
38 {
39 let path = path.as_ref();
40 let content = std::fs::read_to_string(path)?;
41
42 let file =
43 toml::from_str::<ExecutionConfigFile>(&content).map_err(std::io::Error::other)?;
44 Self::from_inputs_file(file).map_err(std::io::Error::other)
45 }
46
47 pub fn parse_str(content: &str) -> Result<Self, String> {
48 let file = toml::from_str::<ExecutionConfigFile>(content).map_err(|err| err.to_string())?;
49
50 Self::from_inputs_file(file)
51 }
52
53 fn from_inputs_file(file: ExecutionConfigFile) -> Result<Self, String> {
54 let felts: Vec<_> = file.inputs.stack.into_iter().map(|felt| felt.0).collect();
55 let inputs =
56 StackInputs::new(&felts).map_err(|err| format!("invalid value for 'stack': {err}"))?;
57 let advice_inputs = AdviceInputs::default()
58 .with_stack(
59 file.inputs.advice.stack.into_iter().map(|felt| felt.0).collect::<AdviceStack>(),
60 )
61 .with_map(file.inputs.advice.map.into_iter().map(|entry| {
62 (entry.digest.0, entry.values.into_iter().map(|felt| felt.0).collect::<Vec<_>>())
63 }));
64
65 Ok(Self {
66 inputs,
67 advice_inputs,
68 options: file.options,
69 })
70 }
71}
72
73#[derive(Debug, Clone, Default, Deserialize)]
74#[serde(default)]
75struct ExecutionConfigFile {
76 inputs: Inputs,
77 #[serde(deserialize_with = "deserialize_execution_options")]
78 options: ExecutionOptions,
79}
80
81#[derive(Debug, Clone, Default, Deserialize)]
82#[serde(default)]
83struct Inputs {
84 stack: Vec<Felt>,
86 advice: Advice,
88}
89
90#[derive(Debug, Clone, Default, Deserialize)]
91#[serde(default)]
92struct Advice {
93 stack: Vec<Felt>,
95 map: Vec<AdviceMapEntry>,
97}
98
99#[derive(Debug, Clone, Deserialize)]
100struct AdviceMapEntry {
101 digest: Word,
102 values: Vec<Felt>,
104}
105
106#[cfg(feature = "std")]
107impl clap::builder::ValueParserFactory for ExecutionConfig {
108 type Parser = ExecutionConfigParser;
109
110 fn value_parser() -> Self::Parser {
111 ExecutionConfigParser
112 }
113}
114
115#[cfg(feature = "std")]
116#[doc(hidden)]
117#[derive(Clone)]
118pub struct ExecutionConfigParser;
119
120#[cfg(feature = "std")]
121impl clap::builder::TypedValueParser for ExecutionConfigParser {
122 type Value = ExecutionConfig;
123
124 fn parse_ref(
125 &self,
126 _cmd: &clap::Command,
127 _arg: Option<&clap::Arg>,
128 value: &OsStr,
129 ) -> Result<Self::Value, clap::error::Error> {
130 use clap::error::{Error, ErrorKind};
131
132 let inputs_path = Path::new(value);
133 if !inputs_path.is_file() {
134 return Err(Error::raw(
135 ErrorKind::InvalidValue,
136 format!("invalid inputs file: '{}' is not a file", inputs_path.display()),
137 ));
138 }
139
140 let content = std::fs::read_to_string(inputs_path).map_err(|err| {
141 Error::raw(ErrorKind::ValueValidation, format!("failed to read inputs file: {err}"))
142 })?;
143 let inputs_file = toml::from_str::<ExecutionConfigFile>(&content).map_err(|err| {
144 Error::raw(ErrorKind::ValueValidation, format!("invalid inputs file: {err}"))
145 })?;
146
147 ExecutionConfig::from_inputs_file(inputs_file).map_err(|err| {
148 Error::raw(ErrorKind::ValueValidation, format!("invalid inputs file: {err}"))
149 })
150 }
151}
152
153#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
154struct Word(miden_core::Word);
155impl<'de> Deserialize<'de> for Word {
156 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
157 where
158 D: serde::Deserializer<'de>,
159 {
160 let digest = String::deserialize(deserializer)?;
161 miden_core::Word::try_from(&digest)
162 .map_err(|err| serde::de::Error::custom(format!("invalid digest: {err}")))
163 .map(Self)
164 }
165}
166
167fn deserialize_execution_options<'de, D>(deserializer: D) -> Result<ExecutionOptions, D::Error>
168where
169 D: serde::Deserializer<'de>,
170{
171 #[derive(Default, Deserialize)]
172 #[serde(default)]
173 struct ExecOptions {
174 max_cycles: Option<u32>,
175 expected_cycles: u32,
176 }
177
178 ExecOptions::deserialize(deserializer).and_then(|opts| {
179 ExecutionOptions::new(
180 opts.max_cycles,
181 opts.expected_cycles,
182 ExecutionOptions::DEFAULT_CORE_TRACE_FRAGMENT_SIZE,
183 )
184 .map_err(|err| serde::de::Error::custom(format!("invalid execution options: {err}")))
185 })
186}
187
188#[cfg(test)]
189mod tests {
190 use miden_processor::Felt as RawFelt;
191 use toml::toml;
192
193 use super::{ExecutionConfig, *};
194
195 #[test]
196 fn execution_config_empty() {
197 let text = toml::to_string_pretty(&toml! {
198 [inputs]
199 [options]
200 })
201 .unwrap();
202
203 let file = toml::from_str::<ExecutionConfig>(&text).unwrap();
204 let expected_inputs = StackInputs::new(&[]).unwrap();
205 assert_eq!(file.inputs.as_ref(), expected_inputs.as_ref());
206 assert!(file.advice_inputs.stack().is_empty());
207 assert_eq!(file.options.max_cycles(), ExecutionOptions::MAX_CYCLES);
208 assert_eq!(file.options.expected_cycles(), ExecutionOptions::default().expected_cycles());
209 }
210
211 #[test]
212 fn execution_config_with_options() {
213 let text = toml::to_string_pretty(&toml! {
214 [inputs]
215 [options]
216 max_cycles = 100000
217 })
218 .unwrap();
219
220 let file = ExecutionConfig::parse_str(&text).unwrap();
221 let expected_inputs = StackInputs::new(&[]).unwrap();
222 assert_eq!(file.inputs.as_ref(), expected_inputs.as_ref());
223 assert!(file.advice_inputs.stack().is_empty());
224 assert_eq!(file.options.max_cycles(), 100000);
225 assert_eq!(file.options.expected_cycles(), ExecutionOptions::default().expected_cycles());
226 }
227
228 #[test]
229 fn execution_config_with_operands() {
230 let text = toml::to_string_pretty(&toml! {
231 [inputs]
232 stack = [1, 2, 3]
233
234 [options]
235 max_cycles = 100000
236 })
237 .unwrap();
238
239 let file = ExecutionConfig::parse_str(&text).unwrap();
240 let expected_inputs = StackInputs::new(&[
241 RawFelt::new(1).expect("value exceeds field modulus"),
242 RawFelt::new(2).expect("value exceeds field modulus"),
243 RawFelt::new(3).expect("value exceeds field modulus"),
244 ])
245 .unwrap();
246 assert_eq!(file.inputs.as_ref(), expected_inputs.as_ref());
247 assert!(file.advice_inputs.stack().is_empty());
248 assert_eq!(file.options.max_cycles(), 100000);
249 assert_eq!(file.options.expected_cycles(), ExecutionOptions::default().expected_cycles());
250 }
251
252 #[test]
253 fn execution_config_with_advice() {
254 let text = toml::to_string_pretty(&toml! {
255 [inputs]
256 stack = [1, 2, 0x3]
257
258 [inputs.advice]
259 stack = [1, 2, 3, 4]
260
261 [[inputs.advice.map]]
262 digest = "0x3cff5b58a573dc9d25fd3c57130cc57e5b1b381dc58b5ae3594b390c59835e63"
263 values = [1, 2, 3, 4]
264
265 [options]
266 max_cycles = 100000
267 })
268 .unwrap();
269 let digest = miden_core::Word::try_from(
270 "0x3cff5b58a573dc9d25fd3c57130cc57e5b1b381dc58b5ae3594b390c59835e63",
271 )
272 .unwrap();
273 let file = ExecutionConfig::parse_str(&text).unwrap_or_else(|err| panic!("{err}"));
274 let expected_inputs = StackInputs::new(&[
275 RawFelt::new(1).expect("value exceeds field modulus"),
276 RawFelt::new(2).expect("value exceeds field modulus"),
277 RawFelt::new(3).expect("value exceeds field modulus"),
278 ])
279 .unwrap();
280 assert_eq!(file.inputs.as_ref(), expected_inputs.as_ref());
281 assert_eq!(
282 file.advice_inputs.stack().into_elements(),
283 &[
284 RawFelt::new(1).expect("value exceeds field modulus"),
285 RawFelt::new(2).expect("value exceeds field modulus"),
286 RawFelt::new(3).expect("value exceeds field modulus"),
287 RawFelt::new(4).expect("value exceeds field modulus")
288 ]
289 );
290 assert_eq!(
291 file.advice_inputs.map().get(&digest).map(|value| value.as_ref()),
292 Some(
293 [
294 RawFelt::new(1).expect("value exceeds field modulus"),
295 RawFelt::new(2).expect("value exceeds field modulus"),
296 RawFelt::new(3).expect("value exceeds field modulus"),
297 RawFelt::new(4).expect("value exceeds field modulus")
298 ]
299 .as_slice()
300 )
301 );
302 assert_eq!(file.options.max_cycles(), 100000);
303 assert_eq!(file.options.expected_cycles(), ExecutionOptions::default().expected_cycles());
304 }
305}