Skip to main content

soroban_cli/config/
address.rs

1use std::{
2    fmt::{self, Display, Formatter},
3    str::FromStr,
4};
5
6use crate::{signer, xdr};
7
8use super::{key, locator, secret, utils};
9
10/// Address can be either a public key or eventually an alias of a address.
11#[derive(Clone, Debug)]
12pub enum UnresolvedMuxedAccount {
13    Resolved(xdr::MuxedAccount),
14    AliasOrSecret(String),
15}
16
17impl Default for UnresolvedMuxedAccount {
18    fn default() -> Self {
19        UnresolvedMuxedAccount::AliasOrSecret(String::default())
20    }
21}
22
23impl Display for UnresolvedMuxedAccount {
24    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
25        match self {
26            UnresolvedMuxedAccount::Resolved(muxed_account) => write!(f, "{muxed_account}"),
27            UnresolvedMuxedAccount::AliasOrSecret(alias_or_secret) => {
28                write!(f, "{alias_or_secret}")
29            }
30        }
31    }
32}
33
34#[derive(thiserror::Error, Debug)]
35pub enum Error {
36    #[error(transparent)]
37    Locator(#[from] locator::Error),
38    #[error(transparent)]
39    Secret(#[from] secret::Error),
40    #[error(transparent)]
41    Signer(#[from] signer::Error),
42    #[error(transparent)]
43    Key(#[from] key::Error),
44    #[error("Address cannot be used to sign {0}")]
45    CannotSign(xdr::MuxedAccount),
46    #[error("Invalid key name: {0}\n only alphanumeric characters, underscores (_), and hyphens (-) are allowed.")]
47    InvalidKeyNameCharacters(String),
48    #[error("Invalid key name: {0}\n keys cannot exceed 250 characters")]
49    InvalidKeyNameLength(String),
50    #[error(transparent)]
51    Name(#[from] utils::Error),
52}
53
54impl FromStr for UnresolvedMuxedAccount {
55    type Err = Error;
56
57    fn from_str(value: &str) -> Result<Self, Self::Err> {
58        Ok(xdr::MuxedAccount::from_str(value).map_or_else(
59            |_| UnresolvedMuxedAccount::AliasOrSecret(value.to_string()),
60            UnresolvedMuxedAccount::Resolved,
61        ))
62    }
63}
64
65impl UnresolvedMuxedAccount {
66    pub fn resolve_muxed_account(
67        &self,
68        locator: &locator::Args,
69        hd_path: Option<u32>,
70    ) -> Result<xdr::MuxedAccount, Error> {
71        match self {
72            UnresolvedMuxedAccount::Resolved(muxed_account) => Ok(muxed_account.clone()),
73            UnresolvedMuxedAccount::AliasOrSecret(alias_or_secret) => Ok(locator
74                .read_key_with_secure_store_cache(alias_or_secret, hd_path)?
75                .muxed_account(hd_path)?),
76        }
77    }
78
79    pub fn resolve_secret(&self, locator: &locator::Args) -> Result<secret::Secret, Error> {
80        match &self {
81            UnresolvedMuxedAccount::Resolved(muxed_account) => {
82                Err(Error::CannotSign(muxed_account.clone()))
83            }
84            UnresolvedMuxedAccount::AliasOrSecret(alias_or_secret) => {
85                Ok(locator.read_key(alias_or_secret)?.try_into()?)
86            }
87        }
88    }
89}
90
91#[derive(Clone, Debug)]
92pub struct KeyName(pub String);
93
94impl std::ops::Deref for KeyName {
95    type Target = str;
96    fn deref(&self) -> &Self::Target {
97        &self.0
98    }
99}
100
101impl std::str::FromStr for KeyName {
102    type Err = Error;
103    fn from_str(s: &str) -> Result<Self, Self::Err> {
104        utils::validate_name(s).map_err(|e| match e {
105            utils::Error::InvalidNameLength(s) => Error::InvalidKeyNameLength(s),
106            utils::Error::InvalidNameCharacters(s) => Error::InvalidKeyNameCharacters(s),
107        })?;
108        Ok(KeyName(s.to_string()))
109    }
110}
111
112impl Display for KeyName {
113    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
114        write!(f, "{}", self.0)
115    }
116}
117
118pub fn validate_name(s: &str) -> Result<(), Error> {
119    Ok(utils::validate_name(s)?)
120}
121
122#[derive(Clone, Debug)]
123pub struct NetworkName(String);
124
125impl std::ops::Deref for NetworkName {
126    type Target = str;
127    fn deref(&self) -> &Self::Target {
128        &self.0
129    }
130}
131
132impl std::str::FromStr for NetworkName {
133    type Err = Error;
134    fn from_str(s: &str) -> Result<Self, Self::Err> {
135        validate_name(s)?;
136        Ok(NetworkName(s.to_string()))
137    }
138}
139
140impl Display for NetworkName {
141    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
142        write!(f, "{}", self.0)
143    }
144}
145
146#[derive(Clone, Debug)]
147pub struct AliasName(String);
148
149impl std::ops::Deref for AliasName {
150    type Target = str;
151    fn deref(&self) -> &Self::Target {
152        &self.0
153    }
154}
155
156impl std::str::FromStr for AliasName {
157    type Err = Error;
158    fn from_str(s: &str) -> Result<Self, Self::Err> {
159        validate_name(s)?;
160        Ok(AliasName(s.to_string()))
161    }
162}
163
164impl Display for AliasName {
165    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
166        write!(f, "{}", self.0)
167    }
168}
169
170#[derive(Clone, Debug)]
171pub struct ContractName(String);
172
173impl std::ops::Deref for ContractName {
174    type Target = str;
175    fn deref(&self) -> &Self::Target {
176        &self.0
177    }
178}
179
180impl std::str::FromStr for ContractName {
181    type Err = Error;
182    fn from_str(s: &str) -> Result<Self, Self::Err> {
183        validate_name(s)?;
184        Ok(ContractName(s.to_string()))
185    }
186}
187
188impl Display for ContractName {
189    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
190        write!(f, "{}", self.0)
191    }
192}
193
194impl AsRef<std::path::Path> for ContractName {
195    fn as_ref(&self) -> &std::path::Path {
196        std::path::Path::new(&self.0)
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203
204    #[test]
205    fn ledger_shorthand_is_not_recognized() {
206        match "ledger".parse::<UnresolvedMuxedAccount>().unwrap() {
207            UnresolvedMuxedAccount::AliasOrSecret(s) => assert_eq!(s, "ledger"),
208            UnresolvedMuxedAccount::Resolved(m) => panic!("unexpected resolved muxed: {m}"),
209        }
210    }
211
212    #[test]
213    fn ledger_indexed_shorthand_is_not_recognized() {
214        match "ledger:5".parse::<UnresolvedMuxedAccount>().unwrap() {
215            UnresolvedMuxedAccount::AliasOrSecret(s) => assert_eq!(s, "ledger:5"),
216            UnresolvedMuxedAccount::Resolved(m) => panic!("unexpected resolved muxed: {m}"),
217        }
218    }
219
220    #[test]
221    fn network_name_valid() {
222        assert!("my-network".parse::<NetworkName>().is_ok());
223        assert!("my_network_123".parse::<NetworkName>().is_ok());
224        assert!("ledger".parse::<NetworkName>().is_ok());
225    }
226
227    #[test]
228    fn network_name_rejects_path_traversal() {
229        assert!("../evil".parse::<NetworkName>().is_err());
230        assert!("../../etc/passwd".parse::<NetworkName>().is_err());
231        assert!("foo/bar".parse::<NetworkName>().is_err());
232        assert!("foo\\bar".parse::<NetworkName>().is_err());
233    }
234
235    #[test]
236    fn network_name_rejects_too_long() {
237        assert!("a".repeat(251).parse::<NetworkName>().is_err());
238        assert!("a".repeat(250).parse::<NetworkName>().is_ok());
239    }
240
241    #[test]
242    fn alias_name_valid() {
243        assert!("my_alias_123".parse::<AliasName>().is_ok());
244        assert!("ledger".parse::<AliasName>().is_ok());
245    }
246
247    #[test]
248    fn alias_name_rejects_path_traversal() {
249        assert!("../evil".parse::<AliasName>().is_err());
250        assert!("../../etc/passwd".parse::<AliasName>().is_err());
251        assert!("foo/bar".parse::<AliasName>().is_err());
252        assert!("foo\\bar".parse::<AliasName>().is_err());
253    }
254
255    #[test]
256    fn alias_name_rejects_too_long() {
257        assert!("a".repeat(251).parse::<AliasName>().is_err());
258        assert!("a".repeat(250).parse::<AliasName>().is_ok());
259    }
260
261    #[test]
262    fn network_name_rejects_empty() {
263        assert!("".parse::<NetworkName>().is_err());
264    }
265
266    #[test]
267    fn alias_name_rejects_empty() {
268        assert!("".parse::<AliasName>().is_err());
269    }
270
271    #[test]
272    fn contract_name_valid() {
273        assert!("hello-world".parse::<ContractName>().is_ok());
274        assert!("my_contract_123".parse::<ContractName>().is_ok());
275    }
276
277    #[test]
278    fn contract_name_rejects_path_traversal() {
279        assert!("../evil".parse::<ContractName>().is_err());
280        assert!("../../etc/passwd".parse::<ContractName>().is_err());
281        assert!("foo/bar".parse::<ContractName>().is_err());
282        assert!("foo\\bar".parse::<ContractName>().is_err());
283    }
284
285    #[test]
286    fn contract_name_rejects_too_long() {
287        assert!("a".repeat(251).parse::<ContractName>().is_err());
288        assert!("a".repeat(250).parse::<ContractName>().is_ok());
289    }
290
291    #[test]
292    fn contract_name_rejects_empty() {
293        assert!("".parse::<ContractName>().is_err());
294    }
295}