cosm_script/data_structures/
network.rs

1use crate::cosm_denom_format;
2use crate::error::CosmScriptError;
3use cosmrs::Denom;
4use serde::{Deserialize, Serialize};
5use serde_json::{from_reader, from_value, json, to_value, Value};
6use std::{env, fs::File, str::FromStr};
7use tonic::transport::Channel;
8
9#[derive(Clone, Debug)]
10pub struct Network {
11    /// What kind of network
12    pub kind: NetworkKind,
13    /// Identifier for the network ex. columbus-2
14    pub id: String,
15    /// gRPC channel
16    pub grpc_channel: Channel,
17    /// Underlying chain details
18    pub chain: Chain,
19    /// Max gas and denom info
20    pub gas_denom: Denom,
21    /// gas price
22    pub gas_price: f64,
23    /// Optional urls for custom functionality
24    pub lcd_url: Option<String>,
25    pub fcd_url: Option<String>,
26}
27
28impl Network {
29    pub fn get(&self) -> Result<Value, CosmScriptError> {
30        let file = File::open(&self.chain.file_path)
31            .unwrap_or_else(|_| panic!("file should be present at {}", self.chain.file_path));
32        let json: serde_json::Value = from_reader(file)?;
33        Ok(json[&self.chain.chain_id]["networks"][&self.kind.to_string()].clone())
34    }
35
36    pub fn set(&self, value: Value) -> Result<(), CosmScriptError> {
37        let file = File::open(&self.chain.file_path)
38            .unwrap_or_else(|_| panic!("file should be present at {}", self.chain.file_path));
39        let mut json: serde_json::Value = from_reader(file).unwrap();
40        json[&self.chain.chain_id]["networks"][&self.kind.to_string()] = json!(value);
41        serde_json::to_writer_pretty(File::create(&self.chain.file_path)?, &json)?;
42        Ok(())
43    }
44
45    /// Get the locally-saved version version of the contract's latest version on this network
46    pub fn get_latest_version(&self, contract_name: &str) -> Result<u64, CosmScriptError> {
47        let network = self.get()?;
48        let maybe_code_id = network["code_ids"].get(contract_name);
49        match maybe_code_id {
50            Some(code_id) => Ok(code_id.as_u64().unwrap()),
51            None => Err(CosmScriptError::CodeIdNotInFile(contract_name.to_owned())),
52        }
53    }
54
55    /// Set the locally-saved version version of the contract's latest version on this network
56    pub fn set_contract_version(
57        &self,
58        contract_name: &str,
59        code_id: u64,
60    ) -> Result<(), CosmScriptError> {
61        let mut network = self.get()?;
62        network["code_ids"][contract_name] = to_value(code_id)?;
63        self.set(network)
64    }
65}
66
67#[derive(Clone, Debug, Serialize, Deserialize)]
68pub struct NetworkInfo {
69    /// Identifier for the network ex. columbus-2
70    pub id: String,
71    /// Max gas and denom info
72    #[serde(with = "cosm_denom_format")]
73    pub gas_denom: Denom,
74    /// gas price
75    pub gas_price: f64,
76    pub grpc_url: String,
77    /// Optional urls for custom functionality
78    pub lcd_url: Option<String>,
79    pub fcd_url: Option<String>,
80}
81
82impl Default for NetworkInfo {
83    fn default() -> Self {
84        Self {
85            gas_denom: Denom::from_str("").unwrap(),
86            id: String::default(),
87            gas_price: 0f64,
88            grpc_url: String::default(),
89            lcd_url: None,
90            fcd_url: None,
91        }
92    }
93}
94
95impl NetworkInfo {
96    pub async fn into_network(
97        self,
98        kind: NetworkKind,
99        chain: &Chain,
100    ) -> Result<Network, CosmScriptError> {
101        let grpc_channel = Channel::from_shared(self.grpc_url)
102            .unwrap()
103            .connect()
104            .await?;
105
106        Ok(Network {
107            kind,
108            grpc_channel,
109            chain: chain.clone(),
110            id: self.id,
111            gas_denom: self.gas_denom,
112            gas_price: self.gas_price,
113            lcd_url: self.lcd_url,
114            fcd_url: self.fcd_url,
115        })
116    }
117}
118
119#[derive(Clone, Debug)]
120pub struct Chain {
121    /// Name of the chain, ex Juno, Terra, ...
122    pub chain_id: String,
123    /// address prefix
124    pub pub_addr_prefix: String,
125    /// coin type for key derivation
126    pub coin_type: u32,
127
128    pub file_path: String,
129}
130
131#[derive(Clone, Debug, Serialize, Deserialize, Default)]
132pub struct ChainInfo {
133    /// address prefix
134    pub pub_addr_prefix: String,
135    /// coin type for key derivation
136    pub coin_type: u32,
137}
138
139impl Chain {
140    pub async fn new(chain_id: &str, store_path: &str) -> Result<Self, CosmScriptError> {
141        let file =
142            File::open(store_path).unwrap_or_else(|_| panic!("file not present at {}", store_path));
143        let mut config: serde_json::Value = from_reader(file)?;
144
145        match config.get(chain_id) {
146            Some(chain) => {
147                let info: ChainInfo = from_value(chain["info"].clone())?;
148                if info.pub_addr_prefix == "FILL" {
149                    return Err(CosmScriptError::NewChain(store_path.into()));
150                };
151                Ok(Self {
152                    chain_id: chain_id.into(),
153                    pub_addr_prefix: info.pub_addr_prefix,
154                    coin_type: info.coin_type,
155                    file_path: store_path.into(),
156                })
157            }
158            None => {
159                let info = ChainInfo {
160                    coin_type: 118u32,
161                    pub_addr_prefix: "FILL".into(),
162                };
163                config[chain_id] = json!(
164                    {
165                        "info": info,
166                        "networks": {}
167                    }
168                );
169                serde_json::to_writer_pretty(File::create(&store_path)?, &config)?;
170                Err(CosmScriptError::NewChain(store_path.into()))
171            }
172        }
173    }
174
175    pub async fn network(&self) -> Result<Network, CosmScriptError> {
176        let file = File::open(&self.file_path)
177            .unwrap_or_else(|_| panic!("file present at {}", self.file_path));
178        let mut config: serde_json::Value = from_reader(file)?;
179
180        let network_kind = NetworkKind::new()?;
181
182        let network = config[&self.chain_id]["networks"].get(network_kind.to_string());
183
184        match network {
185            Some(network) => {
186                let info: NetworkInfo = from_value(network["info"].clone())?;
187                if info.grpc_url == String::default() {
188                    return Err(CosmScriptError::NewNetwork(self.file_path.clone()));
189                }
190                info.into_network(network_kind, self).await
191            }
192            // Fill scaffold for user
193            None => {
194                let info = NetworkInfo::default();
195                config[&self.chain_id]["networks"][network_kind.to_string()] = json!(
196                    {
197                        "info": info,
198                        "code_ids": {},
199                        "deployments": {}
200                    }
201                );
202                serde_json::to_writer_pretty(File::create(&self.file_path)?, &config)?;
203                Err(CosmScriptError::NewNetwork(self.file_path.clone()))
204            }
205        }
206    }
207}
208
209#[derive(Clone, Debug, Serialize, Deserialize)]
210pub enum NetworkKind {
211    Local,
212    Mainnet,
213    Testnet,
214}
215
216impl NetworkKind {
217    pub fn new() -> Result<Self, CosmScriptError> {
218        let network_id = env::var("NETWORK")?;
219        let network = match network_id.as_str() {
220            "testnet" => NetworkKind::Testnet,
221            "mainnet" => NetworkKind::Mainnet,
222            _ => NetworkKind::Local,
223        };
224        Ok(network)
225    }
226
227    pub fn mnemonic_name(&self) -> &str {
228        match *self {
229            NetworkKind::Local => "LOCAL_MNEMONIC",
230            NetworkKind::Mainnet => "MAIN_MNEMONIC",
231            NetworkKind::Testnet => "TEST_MNEMONIC",
232        }
233    }
234
235    pub fn multisig_name(&self) -> &str {
236        match *self {
237            NetworkKind::Local => "LOCAL_MULTISIG",
238            NetworkKind::Mainnet => "MAIN_MULTISIG",
239            NetworkKind::Testnet => "TEST_MULTISIG",
240        }
241    }
242}
243
244impl ToString for NetworkKind {
245    fn to_string(&self) -> String {
246        match *self {
247            NetworkKind::Local => "local",
248            NetworkKind::Mainnet => "mainnet",
249            NetworkKind::Testnet => "testnet",
250        }
251        .into()
252    }
253}