cargo_openvm/commands/
run.rs

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