Skip to main content

soroban_cli/
auth_mode.rs

1//! Auth mode for Soroban transaction simulation.
2//!
3//! Selects how the RPC handles authorization entries while simulating a
4//! transaction. The variants map onto the RPC `simulateTransaction` `authMode`
5//! parameter; leaving the argument unset omits the parameter and uses the RPC
6//! default.
7
8use clap::ValueEnum;
9
10#[derive(thiserror::Error, Debug)]
11pub enum Error {
12    #[error(
13        "--auth-mode=enforce requires a transaction with existing authorization entries; use a different auth mode when building new transactions"
14    )]
15    EnforceRequiresExistingAuth,
16}
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
19pub enum AuthMode {
20    /// Validate the authorization entries already on the transaction.
21    Enforce,
22    /// Record authorization entries, requiring each to be rooted at the
23    /// transaction's top-level operation.
24    Root,
25    /// Record all authorization entries, including non-root entries.
26    #[value(name = "non-root")]
27    NonRoot,
28}
29
30impl AuthMode {
31    /// Map to the RPC `simulateTransaction` `authMode` parameter.
32    pub fn to_rpc(self) -> soroban_rpc::AuthMode {
33        match self {
34            AuthMode::Enforce => soroban_rpc::AuthMode::Enforce,
35            AuthMode::Root => soroban_rpc::AuthMode::Record,
36            AuthMode::NonRoot => soroban_rpc::AuthMode::RecordAllowNonRoot,
37        }
38    }
39}
40
41/// Shared `--auth-mode` argument for commands that simulate Soroban
42/// transactions.
43///
44/// The argument is optional: when unset, no `authMode` is sent and the RPC uses
45/// its default (record with root mode if no authorization entries exist,
46/// otherwise enforce the provided entries). This is also the only safe behavior
47/// for envelopes whose operation is not `InvokeHostFunction`, since the RPC
48/// rejects `authMode` for those.
49#[derive(Debug, clap::Args, Clone, Default)]
50#[group(skip)]
51pub struct Args {
52    /// Set the authorization mode for transaction simulation. When unset, the RPC
53    /// default is used: record with the root mode if no authorization entries
54    /// exist, otherwise enforce the provided entries. Should only be set for
55    /// `InvokeHostFunction` transactions. The `enforce` mode is for simulating
56    /// transactions that already contain authorization entries.
57    #[arg(
58        long,
59        env = "STELLAR_AUTH_MODE",
60        help_heading = crate::commands::HEADING_RPC,
61    )]
62    pub auth_mode: Option<AuthMode>,
63}
64
65impl Args {
66    pub fn to_rpc(&self) -> Option<soroban_rpc::AuthMode> {
67        self.auth_mode.map(AuthMode::to_rpc)
68    }
69
70    pub fn validate_not_enforce(&self) -> Result<(), Error> {
71        if matches!(self.auth_mode, Some(AuthMode::Enforce)) {
72            return Err(Error::EnforceRequiresExistingAuth);
73        }
74        Ok(())
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn unset_omits_rpc_auth_mode() {
84        assert!(Args::default().to_rpc().is_none());
85    }
86
87    #[test]
88    fn enforce_maps_to_enforce() {
89        assert!(matches!(
90            AuthMode::Enforce.to_rpc(),
91            soroban_rpc::AuthMode::Enforce
92        ));
93    }
94
95    #[test]
96    fn root_maps_to_record() {
97        assert!(matches!(
98            AuthMode::Root.to_rpc(),
99            soroban_rpc::AuthMode::Record
100        ));
101    }
102
103    #[test]
104    fn non_root_maps_to_record_allow_non_root() {
105        assert!(matches!(
106            AuthMode::NonRoot.to_rpc(),
107            soroban_rpc::AuthMode::RecordAllowNonRoot
108        ));
109    }
110
111    #[test]
112    fn validate_not_enforce_requires_recording_mode() {
113        assert!(matches!(
114            Args {
115                auth_mode: Some(AuthMode::Enforce)
116            }
117            .validate_not_enforce(),
118            Err(Error::EnforceRequiresExistingAuth)
119        ));
120    }
121
122    #[test]
123    fn validate_not_enforce_allows_recording_modes() {
124        assert!(Args {
125            auth_mode: Some(AuthMode::Root)
126        }
127        .validate_not_enforce()
128        .is_ok());
129        assert!(Args {
130            auth_mode: Some(AuthMode::NonRoot)
131        }
132        .validate_not_enforce()
133        .is_ok());
134        assert!(Args::default().validate_not_enforce().is_ok());
135    }
136}