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