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