stellar_registry_cli/
contract.rs

1use sha2::{Digest, Sha256};
2
3use soroban_rpc as rpc;
4use stellar_cli::{
5    commands::{contract::invoke, NetworkRunnable},
6    config::{self, network::Network, UnresolvedContract},
7    xdr::{self, WriteXdr},
8};
9
10pub trait NetworkContract {
11    fn contract_id(&self) -> Result<stellar_strkey::Contract, config::Error>;
12
13    fn contract_sc_address(&self) -> Result<xdr::ScAddress, config::Error> {
14        Ok(xdr::ScAddress::Contract(xdr::Hash(self.contract_id()?.0)))
15    }
16
17    fn invoke_registry(
18        &self,
19        slop: &[&str],
20        fee: Option<&stellar_cli::fee::Args>,
21        view_only: bool,
22    ) -> impl std::future::Future<Output = Result<String, invoke::Error>> + Send;
23
24    fn rpc_client(&self) -> Result<rpc::Client, config::Error>;
25}
26
27impl NetworkContract for config::Args {
28    fn contract_id(&self) -> Result<stellar_strkey::Contract, config::Error> {
29        let Network {
30            network_passphrase, ..
31        } = &self.get_network()?;
32        let contract: UnresolvedContract = unsafe {
33            if let Ok(contract_id) = std::env::var("STELLAR_REGISTRY_CONTRACT_ID") {
34                contract_id.parse()
35            } else {
36                contract_id(network_passphrase).to_string().parse()
37            }
38            .unwrap_unchecked()
39        };
40        Ok(contract.resolve_contract_id(&self.locator, network_passphrase)?)
41    }
42
43    async fn invoke_registry(
44        &self,
45        slop: &[&str],
46        fee: Option<&stellar_cli::fee::Args>,
47        view_only: bool,
48    ) -> Result<String, invoke::Error> {
49        invoke_registry(slop, self, fee, view_only).await
50    }
51
52    fn rpc_client(&self) -> Result<rpc::Client, config::Error> {
53        Ok(rpc::Client::new(&self.get_network()?.rpc_url)?)
54    }
55}
56
57pub fn build_invoke_cmd(
58    slop: &[&str],
59    config: &stellar_cli::config::Args,
60    fee: Option<&stellar_cli::fee::Args>,
61    view_only: bool,
62) -> Result<invoke::Cmd, config::Error> {
63    Ok(invoke::Cmd {
64        contract_id: UnresolvedContract::Resolved(config.contract_id()?),
65        slop: slop.iter().map(Into::into).collect(),
66        config: config.clone(),
67        fee: fee.cloned().unwrap_or_default(),
68        send: view_only.then_some(invoke::Send::No).unwrap_or_default(),
69        ..Default::default()
70    })
71}
72
73pub async fn invoke_registry(
74    slop: &[&str],
75    config: &stellar_cli::config::Args,
76    fee: Option<&stellar_cli::fee::Args>,
77    view_only: bool,
78) -> Result<String, invoke::Error> {
79    Ok(build_invoke_cmd(slop, config, fee, view_only)?
80        .run_against_rpc_server(Some(&stellar_cli::commands::global::Args::default()), None)
81        .await?
82        .into_result()
83        .expect("Failed to parse JSON"))
84}
85
86pub fn stellar_address() -> stellar_strkey::ed25519::PublicKey {
87    "GAMPJROHOAW662FINQ4XQOY2ULX5IEGYXCI4SMZYE75EHQBR6PSTJG3M"
88        .parse()
89        .unwrap()
90}
91
92pub fn contract_id(network_passphrase: &str) -> stellar_strkey::Contract {
93    let network_id = xdr::Hash(Sha256::digest(network_passphrase.as_bytes()).into());
94    let preimage = xdr::HashIdPreimage::ContractId(xdr::HashIdPreimageContractId {
95        network_id,
96        contract_id_preimage: xdr::ContractIdPreimage::Address(
97            xdr::ContractIdPreimageFromAddress {
98                address: xdr::ScAddress::Account(xdr::AccountId(
99                    xdr::PublicKey::PublicKeyTypeEd25519(stellar_address().0.into()),
100                )),
101                salt: xdr::Uint256([0; 32]),
102            },
103        ),
104    });
105    let preimage_xdr = preimage
106        .to_xdr(xdr::Limits::none())
107        .expect("HashIdPreimage should not fail encoding to xdr");
108    stellar_strkey::Contract(Sha256::digest(preimage_xdr).into())
109}
110
111#[cfg(test)]
112mod generate_id {
113    use stellar_cli::config::network::passphrase::*;
114
115    fn test_contract_id((passphrase, contract_id): (&str, &str)) {
116        assert_eq!(
117            &super::contract_id(passphrase).to_string(),
118            contract_id,
119            "{passphrase}"
120        );
121    }
122    #[test]
123    fn futurenet() {
124        test_contract_id((
125            FUTURENET,
126            "CACPZCQSLEGF6QOSBF42X6LOUQXQB2EJRDKNKQO6US6ZZH5FD6EB325M",
127        ));
128    }
129
130    #[test]
131    fn testnet() {
132        test_contract_id((
133            TESTNET,
134            "CBCOGWBDGBFWR5LQFKRQUPFIG6OLOON35PBKUPB6C542DFZI3OMBOGHX",
135        ));
136    }
137
138    #[test]
139    fn mainnet() {
140        test_contract_id((
141            MAINNET,
142            "CC3SILHAJ5O75KMSJ5J6I5HV753OTPWEVMZUYHS4QEM2ZTISQRAOMMF4",
143        ));
144    }
145
146    #[test]
147    fn local() {
148        test_contract_id((
149            LOCAL,
150            "CCMHAZ6QTUUF2W4PUBW5BAI6R75BVKIUVHJU6IBQTWCS5RBASDOKHF7T",
151        ));
152    }
153}