soroban_cli/config/
address.rs1use std::{
2 fmt::{self, Display, Formatter},
3 str::FromStr,
4};
5
6use crate::{signer, xdr};
7
8use super::{key, locator, secret, utils};
9
10#[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}