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