1use std::path::PathBuf;
2
3use clap::{Parser, ValueEnum};
4use eyre::{eyre, Result};
5use openvm_circuit::arch::instructions::exe::VmExe;
6use openvm_sdk::{
7 config::AggregationSystemParams, fs::read_object_from_file, keygen::AppProvingKey, Sdk, F,
8};
9use openvm_sdk_config::SdkVmConfig;
10
11use super::{build, BuildArgs, BuildCargoArgs};
12use crate::{
13 args::{ManifestArgs, OpenVmConfigArgs},
14 default::{OPENVM_CONFIG_FILENAME, VMEXE_EXT},
15 input::{read_to_stdin, Input},
16 util::{
17 get_app_pk_path, get_manifest_path_and_dir, get_single_target_name, get_target_dir,
18 read_config_toml_or_default,
19 },
20};
21
22#[derive(Clone, Debug, ValueEnum)]
23pub enum ExecutionMode {
24 Pure,
26 Meter,
28 Segment,
31}
32
33#[derive(Parser)]
34#[command(name = "run", about = "Run an OpenVM program")]
35pub struct RunCmd {
36 #[clap(flatten)]
37 run_args: RunArgs,
38
39 #[clap(flatten)]
40 cargo_args: RunCargoArgs,
41}
42
43#[derive(Clone, Parser)]
44pub struct RunArgs {
45 #[arg(
46 long,
47 action,
48 help = "Path to OpenVM executable, if specified build will be skipped",
49 help_heading = "OpenVM Options"
50 )]
51 pub exe: Option<PathBuf>,
52
53 #[clap(flatten)]
54 pub openvm_config: OpenVmConfigArgs,
55
56 #[arg(
57 long,
58 value_parser,
59 help = "Input to OpenVM program",
60 help_heading = "OpenVM Options"
61 )]
62 pub input: Option<Input>,
63
64 #[arg(
65 long,
66 value_enum,
67 default_value = "pure",
68 help = "Execution mode",
69 help_heading = "OpenVM Options"
70 )]
71 pub mode: ExecutionMode,
72}
73
74impl From<RunArgs> for BuildArgs {
75 fn from(args: RunArgs) -> Self {
76 BuildArgs {
77 openvm_config: args.openvm_config,
78 ..Default::default()
79 }
80 }
81}
82
83#[derive(Clone, Parser)]
84pub struct RunCargoArgs {
85 #[arg(
86 long,
87 short = 'p',
88 value_name = "PACKAGES",
89 help = "The package to run; by default is the package in the current workspace",
90 help_heading = "Package Selection"
91 )]
92 pub package: Option<String>,
93
94 #[arg(
95 long,
96 value_name = "BIN",
97 help = "Run the specified binary",
98 help_heading = "Target Selection"
99 )]
100 pub bin: Vec<String>,
101
102 #[arg(
103 long,
104 value_name = "EXAMPLE",
105 help = "Run the specified example",
106 help_heading = "Target Selection"
107 )]
108 pub example: Vec<String>,
109
110 #[arg(
111 long,
112 short = 'F',
113 value_name = "FEATURES",
114 value_delimiter = ',',
115 help = "Space/comma separated list of features to activate",
116 help_heading = "Feature Selection"
117 )]
118 pub features: Vec<String>,
119
120 #[arg(
121 long,
122 help = "Activate all available features of all selected packages",
123 help_heading = "Feature Selection"
124 )]
125 pub all_features: bool,
126
127 #[arg(
128 long,
129 help = "Do not activate the `default` feature of the selected packages",
130 help_heading = "Feature Selection"
131 )]
132 pub no_default_features: bool,
133
134 #[arg(
135 long,
136 value_name = "NAME",
137 default_value = "release",
138 help = "Run with the given profile",
139 help_heading = "Compilation Options"
140 )]
141 pub profile: String,
142
143 #[clap(flatten)]
144 pub manifest: ManifestArgs,
145
146 #[arg(
147 long,
148 short = 'v',
149 help = "Use verbose output",
150 help_heading = "Display Options"
151 )]
152 pub verbose: bool,
153
154 #[arg(
155 long,
156 short = 'q',
157 help = "Do not print cargo log messages",
158 help_heading = "Display Options"
159 )]
160 pub quiet: bool,
161
162 #[arg(
163 long,
164 value_name = "WHEN",
165 default_value = "always",
166 help = "Control when colored output is used",
167 help_heading = "Display Options"
168 )]
169 pub color: String,
170
171 #[arg(
172 long,
173 help = "Ignore rust-version specification in packages",
174 help_heading = "Manifest Options"
175 )]
176 pub ignore_rust_version: bool,
177
178 #[arg(
179 long,
180 help = "Asserts same dependencies and versions are used as when the existing Cargo.lock file was originally generated",
181 help_heading = "Manifest Options"
182 )]
183 pub locked: bool,
184
185 #[arg(
186 long,
187 help = "Prevents Cargo from accessing the network for any reason",
188 help_heading = "Manifest Options"
189 )]
190 pub offline: bool,
191
192 #[arg(
193 long,
194 help = "Equivalent to specifying both --locked and --offline",
195 help_heading = "Manifest Options"
196 )]
197 pub frozen: bool,
198}
199
200impl From<RunCargoArgs> for BuildCargoArgs {
201 fn from(args: RunCargoArgs) -> Self {
202 BuildCargoArgs {
203 package: args.package.into_iter().collect(),
204 bin: args.bin,
205 example: args.example,
206 features: args.features,
207 all_features: args.all_features,
208 no_default_features: args.no_default_features,
209 profile: args.profile,
210 manifest: args.manifest,
211 verbose: args.verbose,
212 quiet: args.quiet,
213 color: args.color,
214 ignore_rust_version: args.ignore_rust_version,
215 locked: args.locked,
216 offline: args.offline,
217 frozen: args.frozen,
218 ..Default::default()
219 }
220 }
221}
222
223impl RunCmd {
224 pub fn run(&self) -> Result<()> {
225 let exe_path = if let Some(exe) = &self.run_args.exe {
226 exe
227 } else {
228 let target_name = get_single_target_name(&self.cargo_args)?;
230 let build_args = self.run_args.clone().into();
231 let cargo_args = self.cargo_args.clone().into();
232 let output_dir = build(&build_args, &cargo_args)?;
233 &output_dir.join(target_name.with_extension(VMEXE_EXT))
234 };
235
236 let (manifest_path, manifest_dir) =
237 get_manifest_path_and_dir(&self.cargo_args.manifest.manifest_path)?;
238 let exe: VmExe<F> = read_object_from_file(exe_path)?;
239 let inputs = read_to_stdin(&self.run_args.input)?;
240
241 let sdk = if matches!(
242 self.run_args.mode,
243 ExecutionMode::Segment | ExecutionMode::Meter
244 ) {
245 let target_dir = get_target_dir(&self.cargo_args.manifest.target_dir, &manifest_path);
246 let app_pk_path = get_app_pk_path(&target_dir);
247
248 let app_pk: AppProvingKey<SdkVmConfig> =
249 read_object_from_file(&app_pk_path).map_err(|e| {
250 eyre!(
251 "Failed to read app proving key from {}: {e}\nRun 'cargo openvm keygen --app-only' first to generate it",
252 app_pk_path.display()
253 )
254 })?;
255 Sdk::builder()
256 .app_pk(app_pk)
257 .agg_params(AggregationSystemParams::default())
258 .build()?
259 } else {
260 let config_path = self
261 .run_args
262 .openvm_config
263 .config
264 .to_owned()
265 .unwrap_or_else(|| manifest_dir.join(OPENVM_CONFIG_FILENAME));
266 let app_config = read_config_toml_or_default(&config_path)?;
267 Sdk::new(app_config, AggregationSystemParams::default())?
268 };
269
270 match self.run_args.mode {
271 ExecutionMode::Pure => {
272 let output = sdk.execute(exe, inputs)?;
273 println!("Execution output: {output:?}");
274 }
275 ExecutionMode::Meter => {
276 let (output, (cost, instret)) = sdk.execute_metered_cost(exe, inputs)?;
277 println!("Execution output: {output:?}");
278
279 println!("Number of instructions executed: {instret}");
280 println!("Total cost: {cost}");
281 }
282 ExecutionMode::Segment => {
283 let (output, segments) = sdk.execute_metered(exe, inputs)?;
284 println!("Execution output: {output:?}");
285
286 let total_instructions: u64 = segments.iter().map(|s| s.num_insns).sum();
287 println!("Number of instructions executed: {total_instructions}");
288 println!("Total segments: {}", segments.len());
289 }
290 }
291
292 Ok(())
293 }
294}