Skip to main content

stellar_registry_cli/commands/
create_alias.rs

1use clap::Parser;
2
3use stellar_cli::commands::contract::invoke;
4use stellar_registry_build::named_registry::PrefixedName;
5use stellar_strkey::Contract;
6
7use crate::commands::global;
8
9#[derive(Parser, Debug, Clone)]
10pub struct Cmd {
11    /// Name of deployed contract. Can use prefix if not using verified registry.
12    /// E.g. `unverified/<name>`
13    pub contract: PrefixedName,
14
15    /// Optional custom local name for the alias. If not provided, uses the name from the registry.
16    pub local_name: Option<String>,
17
18    /// Force overwrite if an alias with the same name already exists.
19    #[arg(short, long)]
20    pub force: bool,
21
22    #[command(flatten)]
23    pub config: global::Args,
24}
25
26#[derive(thiserror::Error, Debug)]
27pub enum Error {
28    #[error(transparent)]
29    Invoke(#[from] invoke::Error),
30    #[error(transparent)]
31    Strkey(#[from] stellar_strkey::DecodeError),
32    #[error(transparent)]
33    LocatorConfig(#[from] stellar_cli::config::locator::Error),
34    #[error(transparent)]
35    Config(#[from] stellar_cli::config::Error),
36    #[error(transparent)]
37    Registry(#[from] stellar_registry_build::Error),
38    #[error(
39        "Existing alias \"{1}\" exists. Overwrite with -f or provide a different local name like: \"create-alias {0} other-{1}\"."
40    )]
41    AliasExists(PrefixedName, String),
42}
43
44impl Cmd {
45    pub async fn run(&self) -> Result<(), Error> {
46        let network_passphrase = self.config.get_network()?.network_passphrase;
47        let alias = self.local_name.as_deref().unwrap_or(&self.contract.name);
48        let contract = self.get_contract_id().await?;
49
50        // Check if alias already exists
51        if !self.force
52            && self
53                .config
54                .locator
55                .get_contract_id(alias, &network_passphrase)?
56                .is_some()
57        {
58            return Err(Error::AliasExists(self.contract.clone(), alias.to_string()));
59        }
60
61        // Only create alias mapping, don't fetch wasm here
62        self.config
63            .locator
64            .save_contract_id(&network_passphrase, &contract, alias)?;
65        eprintln!("✅ Successfully registered contract alias '{alias}' for {contract}");
66        Ok(())
67    }
68
69    pub async fn get_contract_id(&self) -> Result<Contract, Error> {
70        let registry = &self.contract.registry(&self.config).await?;
71        eprintln!("Fetching contract ID via registry...");
72        Ok(registry.fetch_contract_id(&self.contract.name).await?)
73    }
74}
75
76#[cfg(feature = "integration-tests")]
77#[cfg(test)]
78mod tests {
79
80    use stellar_scaffold_test::{AssertExt, RegistryTest};
81
82    fn publish_and_deploy(registry: &RegistryTest, name: &str) {
83        // Path to the hello world contract WASM
84        let wasm_path = registry.hello_wasm_v1();
85
86        // First publish the contract
87        registry
88            .registry_cli("publish")
89            .arg("--wasm")
90            .arg(&wasm_path)
91            .arg("--binver")
92            .arg("0.0.2")
93            .arg("--wasm-name")
94            .arg(name)
95            .assert()
96            .success();
97
98        // Then deploy it
99        registry
100            .registry_cli("deploy")
101            .arg("--contract-name")
102            .arg(name)
103            .arg("--wasm-name")
104            .arg(name)
105            .arg("--version")
106            .arg("0.0.2")
107            .arg("--")
108            .arg("--admin=alice")
109            .assert()
110            .success();
111    }
112
113    #[tokio::test]
114    async fn test_run() {
115        let registry = RegistryTest::new().await;
116        let test_env = registry.clone().env;
117
118        publish_and_deploy(&registry, "hello");
119
120        // Create test command for create-alias
121        let cmd = registry.parse_cmd::<super::Cmd>(&["hello"]).unwrap();
122
123        // Run the create-alias command
124        cmd.run().await.unwrap();
125        assert!(
126            test_env
127                .cwd
128                .join(".config/stellar/contract-ids/hello.json")
129                .exists()
130        );
131    }
132
133    #[tokio::test]
134    async fn name_collision() {
135        let registry = RegistryTest::new().await;
136        let test_env = registry.clone().env;
137
138        publish_and_deploy(&registry, "hello");
139        publish_and_deploy(&registry, "unverified/hello");
140
141        // Run create-alias command
142        registry
143            .parse_cmd::<super::Cmd>(&["hello"])
144            .unwrap()
145            .run()
146            .await
147            .unwrap();
148
149        assert!(
150            test_env
151                .cwd
152                .join(".config/stellar/contract-ids/hello.json")
153                .exists()
154        );
155
156        let contract_id = test_env
157            .stellar("contract")
158            .args(["alias", "show", "hello"])
159            .assert()
160            .stdout_as_str();
161
162        // Run the create-alias command
163        let cmd = registry
164            .parse_cmd::<super::Cmd>(&["unverified/hello"])
165            .unwrap()
166            .run()
167            .await;
168
169        // assert that cmd returns error (panics if result is ok)
170        cmd.unwrap_err();
171
172        // assert the alias still points at the same contract id
173        assert_eq!(
174            contract_id,
175            test_env
176                .stellar("contract")
177                .args(["alias", "show", "hello"])
178                .assert()
179                .success()
180                .stdout_as_str()
181        );
182    }
183
184    #[tokio::test]
185    async fn name_collision_with_overwrite() {
186        let registry = RegistryTest::new().await;
187        let test_env = registry.clone().env;
188
189        publish_and_deploy(&registry, "hello");
190        publish_and_deploy(&registry, "unverified/hello");
191
192        // Run create-alias command
193        registry
194            .parse_cmd::<super::Cmd>(&["hello"])
195            .unwrap()
196            .run()
197            .await
198            .unwrap();
199
200        assert!(
201            test_env
202                .cwd
203                .join(".config/stellar/contract-ids/hello.json")
204                .exists()
205        );
206
207        let contract_id = test_env
208            .stellar("contract")
209            .args(["alias", "show", "hello"])
210            .assert()
211            .stdout_as_str();
212
213        // Run the create-alias command
214        let cmd = registry
215            .parse_cmd::<super::Cmd>(&["unverified/hello", "-f"])
216            .unwrap()
217            .run()
218            .await;
219
220        // assert that cmd succeeded
221        cmd.unwrap();
222
223        // assert the alias changed
224        assert_ne!(
225            contract_id,
226            test_env
227                .stellar("contract")
228                .args(["alias", "show", "hello"])
229                .assert()
230                .success()
231                .stdout_as_str()
232        );
233    }
234
235    #[tokio::test]
236    async fn alternate_local_name() {
237        let registry = RegistryTest::new().await;
238        let test_env = registry.clone().env;
239
240        publish_and_deploy(&registry, "hello");
241        publish_and_deploy(&registry, "unverified/hello");
242
243        // Run create-alias command
244        registry
245            .parse_cmd::<super::Cmd>(&["hello"])
246            .unwrap()
247            .run()
248            .await
249            .unwrap();
250
251        assert!(
252            test_env
253                .cwd
254                .join(".config/stellar/contract-ids/hello.json")
255                .exists()
256        );
257
258        let contract_id = test_env
259            .stellar("contract")
260            .args(["alias", "show", "hello"])
261            .assert()
262            .stdout_as_str();
263
264        // Run the create-alias command
265        let cmd = registry
266            .parse_cmd::<super::Cmd>(&["unverified/hello", "unverified_hello"])
267            .unwrap()
268            .run()
269            .await;
270
271        // assert that cmd succeeded
272        cmd.unwrap();
273
274        // assert the "hello" alias is the same
275        assert_eq!(
276            contract_id,
277            test_env
278                .stellar("contract")
279                .args(["alias", "show", "hello"])
280                .assert()
281                .success()
282                .stdout_as_str()
283        );
284
285        // assert we created a differently-named alias for unverified/hello
286        assert!(
287            test_env
288                .cwd
289                .join(".config/stellar/contract-ids/unverified_hello.json")
290                .exists()
291        );
292    }
293
294    #[tokio::test]
295    async fn unverified() {
296        let registry = RegistryTest::new().await;
297        let test_env = registry.clone().env;
298
299        publish_and_deploy(&registry, "unverified/hello");
300
301        // Create test command for install
302        let cmd = registry
303            .parse_cmd::<super::Cmd>(&["unverified/hello"])
304            .unwrap();
305
306        // Run the install command
307        cmd.run().await.unwrap();
308        assert!(
309            test_env
310                .cwd
311                .join(".config/stellar/contract-ids/hello.json")
312                .exists()
313        );
314    }
315}