leo_lang/cli/commands/common/
options.rs

1// Copyright (C) 2019-2025 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17use super::*;
18use leo_package::NetworkName;
19use snarkvm::prelude::ConsensusVersion;
20
21/// Compiler Options wrapper for Build command. Also used by other commands which
22/// require Build command output as their input.
23#[derive(Parser, Clone, Debug)]
24pub struct BuildOptions {
25    #[clap(long, help = "Does not recursively compile dependencies.")]
26    pub non_recursive: bool,
27    #[clap(long, help = "Enables offline mode.")]
28    pub offline: bool,
29    #[clap(long, help = "Enable spans in AST snapshots.")]
30    pub enable_ast_spans: bool,
31    #[clap(long, help = "Enables dead code elimination in the compiler.", default_value = "true")]
32    pub enable_dce: bool,
33    #[clap(long, help = "Max depth to type check nested conditionals.", default_value = "10")]
34    pub conditional_block_max_depth: usize,
35    #[clap(long, help = "Disable type checking of nested conditional branches in finalize scope.")]
36    pub disable_conditional_branch_type_checking: bool,
37    #[clap(long, help = "Write an AST snapshot immediately after parsing.")]
38    pub enable_initial_ast_snapshot: bool,
39    #[clap(long, help = "Writes all AST snapshots for the different compiler phases.")]
40    pub enable_all_ast_snapshots: bool,
41    #[clap(long, help = "Comma separated list of passes whose AST snapshots to capture.", value_delimiter = ',', num_args = 1..)]
42    pub ast_snapshots: Vec<String>,
43    #[clap(long, help = "Build tests along with the main program and dependencies.")]
44    pub build_tests: bool,
45    #[clap(long, help = "Don't use the dependency cache.")]
46    pub no_cache: bool,
47}
48
49impl Default for BuildOptions {
50    fn default() -> Self {
51        Self {
52            non_recursive: false,
53            offline: false,
54            enable_ast_spans: false,
55            enable_dce: true,
56            conditional_block_max_depth: 10,
57            disable_conditional_branch_type_checking: false,
58            enable_initial_ast_snapshot: false,
59            enable_all_ast_snapshots: false,
60            ast_snapshots: Vec::new(),
61            build_tests: false,
62            no_cache: false,
63        }
64    }
65}
66
67/// Overrides for the `.env` file.
68#[derive(Parser, Clone, Debug, Default)]
69pub struct EnvOptions {
70    #[clap(long, help = "The private key to use for the deployment. Overrides the `PRIVATE_KEY` in the `.env` file.")]
71    pub(crate) private_key: Option<String>,
72    #[clap(long, help = "The network to deploy to. Overrides the `NETWORK` in the .env file.")]
73    pub(crate) network: Option<String>,
74    #[clap(long, help = "The endpoint to deploy to. Overrides the `ENDPOINT` in the .env file.")]
75    pub(crate) endpoint: Option<String>,
76}
77
78/// The fee options for the transactions.
79#[derive(Parser, Clone, Debug, Default)]
80pub struct FeeOptions {
81    #[clap(
82        long,
83        help = "[UNUSED] Base fees in microcredits, delimited by `|`, and used in order. The fees must either be valid `u64` or `default`. Defaults to automatic calculation.",
84        value_delimiter = '|',
85        value_parser = parse_amount
86    )]
87    pub(crate) base_fees: Vec<Option<u64>>,
88    #[clap(
89        long,
90        help = "Priority fee in microcredits, delimited by `|`, and used in order. The fees must either be valid `u64` or `default`. Defaults to 0.",
91        value_delimiter = '|',
92        value_parser = parse_amount
93    )]
94    pub(crate) priority_fees: Vec<Option<u64>>,
95    #[clap(
96        short,
97        help = "Records to pay for fees privately, delimited by '|', and used in order. The fees must either be valid plaintext, ciphertext, or `default`. Defaults to public fees.",
98        long,
99        value_delimiter = '|',
100        value_parser = parse_record_string,
101    )]
102    fee_records: Vec<Option<String>>,
103}
104
105// A helper function to parse amounts, which can either be a `u64` or `default`.
106fn parse_amount(s: &str) -> Result<Option<u64>, String> {
107    let trimmed = s.trim();
108    if trimmed == "default" { Ok(None) } else { trimmed.parse::<u64>().map_err(|e| e.to_string()).map(Some) }
109}
110
111// A helper function to parse record strings, which can either be a string or `default`.
112fn parse_record_string(s: &str) -> Result<Option<String>, String> {
113    let trimmed = s.trim();
114    if trimmed == "default" { Ok(None) } else { Ok(Some(trimmed.to_string())) }
115}
116
117/// Parses the record string. If the string is a ciphertext, then attempt to decrypt it. Lifted from snarkOS.
118fn parse_record<N: Network>(private_key: &PrivateKey<N>, record: &str) -> Result<Record<N, Plaintext<N>>> {
119    match record.starts_with("record1") {
120        true => {
121            // Parse the ciphertext.
122            let ciphertext = Record::<N, Ciphertext<N>>::from_str(record)?;
123            // Derive the view key.
124            let view_key = ViewKey::try_from(private_key)?;
125            // Decrypt the ciphertext.
126            Ok(ciphertext.decrypt(&view_key)?)
127        }
128        false => Ok(Record::<N, Plaintext<N>>::from_str(record)?),
129    }
130}
131
132// A helper function to construct fee options for `k` transactions.
133#[allow(clippy::type_complexity)]
134pub fn parse_fee_options<N: Network>(
135    private_key: &PrivateKey<N>,
136    fee_options: &FeeOptions,
137    k: usize,
138) -> Result<Vec<(Option<u64>, Option<u64>, Option<Record<N, Plaintext<N>>>)>> {
139    // Parse the base fees.
140    let base_fees = fee_options.base_fees.clone();
141    // Parse the priority fees.
142    let priority_fees = fee_options.priority_fees.clone();
143    // Parse the fee records.
144    let parse_record = |record: &Option<String>| record.as_ref().map(|r| parse_record::<N>(private_key, r)).transpose();
145    let fee_records = fee_options.fee_records.iter().map(parse_record).collect::<Result<Vec<_>>>()?;
146
147    // Pad the vectors to length `k`.
148    let base_fees = base_fees.into_iter().chain(iter::repeat(None)).take(k);
149    let priority_fees = priority_fees.into_iter().chain(iter::repeat(None)).take(k);
150    let fee_records = fee_records.into_iter().chain(iter::repeat(None)).take(k);
151
152    Ok(base_fees.zip(priority_fees).zip(fee_records).map(|((x, y), z)| (x, y, z)).collect())
153}
154
155/// Additional options that are common across a number of commands.
156#[derive(Parser, Clone, Debug, Default)]
157pub struct ExtraOptions {
158    #[clap(
159        short,
160        long,
161        help = "Don't ask for confirmation. DO NOT SET THIS FLAG UNLESS YOU KNOW WHAT YOU ARE DOING",
162        default_value = "false"
163    )]
164    pub(crate) yes: bool,
165    #[clap(
166        long,
167        help = "Consensus version to use. If one is not provided, the CLI will attempt to determine it from the latest block."
168    )]
169    pub(crate) consensus_version: Option<u8>,
170    #[clap(
171        long,
172        help = "Seconds to wait for a block to appear when searching for a transaction.",
173        default_value = "8"
174    )]
175    pub(crate) max_wait: usize,
176    #[clap(long, help = "Number of blocks to look at when searching for a transaction.", default_value = "12")]
177    pub(crate) blocks_to_check: usize,
178}
179
180// A helper function to get the consensus version from the fee options.
181// If a consensus version is not provided, then attempt to query the current block height and use it to determine the version.
182pub fn get_consensus_version<N: Network>(
183    consensus_version: &Option<u8>,
184    endpoint: &str,
185    network: NetworkName,
186    context: &Context,
187) -> Result<ConsensusVersion> {
188    // Get the consensus version.
189    match consensus_version {
190        Some(1) => Ok(ConsensusVersion::V1),
191        Some(2) => Ok(ConsensusVersion::V2),
192        Some(3) => Ok(ConsensusVersion::V3),
193        Some(4) => Ok(ConsensusVersion::V4),
194        // If none is provided, then attempt to query the current block height and use it to determine the version.
195        None => {
196            println!("Attempting to determine the consensus version from the latest block height at {endpoint}...");
197            get_latest_block_height(endpoint, network, context)
198                .and_then(|current_block_height| Ok(N::CONSENSUS_VERSION(current_block_height)?))
199                .map_err(|_| {
200                    CliError::custom(
201                        "Failed to get consensus version. Ensure that your endpoint is valid or provide an explicit version to use via `--consensus-version`",
202                    )
203                        .into()
204                })
205        }
206        Some(version) => Err(CliError::custom(format!("Invalid consensus version: {version}")).into()),
207    }
208}
209
210/// What to do with a transaction produced by the CLI.
211#[derive(Args, Clone, Debug)]
212pub struct TransactionAction {
213    #[arg(long, help = "Print the transaction to stdout.")]
214    pub print: bool,
215    #[arg(long, help = "Broadcast the transaction to the network.")]
216    pub broadcast: bool,
217    #[arg(long, help = "Save the transaction to the provided directory.")]
218    pub save: Option<String>,
219}