soroban_cli/config/
sc_address.rs1use std::str::FromStr;
2
3use crate::xdr;
4
5use super::{alias, key, locator, UnresolvedContract};
6
7#[allow(clippy::module_name_repetitions)]
9#[derive(Clone, Debug)]
10pub enum UnresolvedScAddress {
11 Resolved(xdr::ScAddress),
12 Alias(String),
13}
14
15impl Default for UnresolvedScAddress {
16 fn default() -> Self {
17 UnresolvedScAddress::Alias(String::default())
18 }
19}
20
21#[derive(thiserror::Error, Debug)]
22pub enum Error {
23 #[error(transparent)]
24 Locator(#[from] locator::Error),
25 #[error(transparent)]
26 Key(#[from] key::Error),
27 #[error("Account alias \"{0}\" not Found")]
28 AccountAliasNotFound(String),
29 #[error("alias '{0}' is reserved for the native asset contract but also matches a stored key; pass an explicit contract (C...) or account (G...) address instead")]
30 ReservedAliasShadowsKey(String),
31}
32
33impl FromStr for UnresolvedScAddress {
34 type Err = Error;
35
36 fn from_str(value: &str) -> Result<Self, Self::Err> {
37 Ok(xdr::ScAddress::from_str(value).map_or_else(
38 |_| UnresolvedScAddress::Alias(value.to_string()),
39 UnresolvedScAddress::Resolved,
40 ))
41 }
42}
43
44impl UnresolvedScAddress {
45 pub fn resolve(
46 self,
47 locator: &locator::Args,
48 network_passphrase: &str,
49 hd_path: Option<u32>,
50 ) -> Result<xdr::ScAddress, Error> {
51 let alias = match self {
52 UnresolvedScAddress::Resolved(addr) => return Ok(addr),
53 UnresolvedScAddress::Alias(alias) => alias,
54 };
55 let contract = UnresolvedContract::resolve_alias(&alias, locator, network_passphrase);
56 let key = locator.read_key(&alias);
57 match (contract, key) {
58 (Ok(contract), Ok(_)) => {
59 if alias::is_reserved(&alias) {
63 return Err(Error::ReservedAliasShadowsKey(alias));
64 }
65 eprintln!(
66 "Warning: ScAddress alias {alias} is ambiguous, assuming it is a contract"
67 );
68 Ok(xdr::ScAddress::Contract(stellar_xdr::ContractId(
69 xdr::Hash(contract.0),
70 )))
71 }
72 (Ok(contract), _) => Ok(xdr::ScAddress::Contract(stellar_xdr::ContractId(
73 xdr::Hash(contract.0),
74 ))),
75 (Err(err @ locator::Error::ShadowedReservedAlias { .. }), _) => Err(err.into()),
80 (_, Ok(key)) => Ok(xdr::ScAddress::Account(
81 key.muxed_account(hd_path)?.account_id(),
82 )),
83 _ => Err(Error::AccountAliasNotFound(alias)),
84 }
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use crate::config::key::Key;
92 use crate::config::locator::KeyType;
93 use std::str::FromStr;
94
95 #[test]
96 fn resolve_errors_when_reserved_alias_shadows_key() {
97 let dir = tempfile::tempdir().unwrap();
98 let locator = locator::Args {
99 config_dir: Some(dir.path().to_path_buf()),
100 };
101 let network_passphrase = "Test Network";
102 let native = alias::NATIVE;
103
104 let key =
107 Key::from_str("SBEQMTXGCLDFQG3OXMRSMGLKJCPROAHB5GZCCGVZERDI645LCCCRLFGY").unwrap();
108 KeyType::Identity.write(native, &key, dir.path()).unwrap();
109
110 let err = UnresolvedScAddress::Alias(native.to_string())
111 .resolve(&locator, network_passphrase, None)
112 .unwrap_err();
113
114 assert!(matches!(err, Error::ReservedAliasShadowsKey(alias) if alias == native));
115 }
116
117 #[test]
118 fn resolve_errors_when_reserved_alias_shadowed_by_stored_alias_and_key() {
119 let dir = tempfile::tempdir().unwrap();
120 let locator = locator::Args {
121 config_dir: Some(dir.path().to_path_buf()),
122 };
123 let network_passphrase = "Test Network";
124 let native = alias::NATIVE;
125
126 let key =
131 Key::from_str("SBEQMTXGCLDFQG3OXMRSMGLKJCPROAHB5GZCCGVZERDI645LCCCRLFGY").unwrap();
132 KeyType::Identity.write(native, &key, dir.path()).unwrap();
133
134 let contract_ids = dir.path().join("contract-ids");
135 std::fs::create_dir_all(&contract_ids).unwrap();
136 std::fs::write(
137 contract_ids.join(format!("{native}.json")),
138 format!(
139 r#"{{"ids":{{"{network_passphrase}":"CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE"}}}}"#
140 ),
141 )
142 .unwrap();
143
144 let err = UnresolvedScAddress::Alias(native.to_string())
145 .resolve(&locator, network_passphrase, None)
146 .unwrap_err();
147
148 assert!(matches!(
149 err,
150 Error::Locator(locator::Error::ShadowedReservedAlias { alias, .. }) if alias == native
151 ));
152 }
153}