use crate::error::BootError;
use crate::state::StateInterface;
use cosmrs::Denom;
use cosmwasm_std::Addr;
use serde::{Deserialize, Serialize};
use serde_json::{from_reader, json, Value};
use std::{collections::HashMap, env, fs::File, rc::Rc, str::FromStr};
use tonic::transport::Channel;
#[derive(Clone, Debug)]
pub struct DaemonState {
pub json_file_path: String,
pub kind: NetworkKind,
pub id: String,
pub grpc_channel: Channel,
pub chain: ChainInfo<'static>,
pub gas_denom: Denom,
pub gas_price: f64,
pub lcd_url: Option<String>,
pub fcd_url: Option<String>,
}
impl DaemonState {
pub async fn new(network: NetworkInfo<'static>) -> Result<DaemonState, BootError> {
let grpc_channel = Channel::from_static(network.grpc_url).connect().await?;
let mut path = env::var("DAEMON_STATE_PATH").unwrap();
if network.kind == NetworkKind::Local {
let name = path.split('.').next().unwrap();
path = format!("{}_local.json", name);
}
let state = DaemonState {
json_file_path: path,
kind: network.kind,
grpc_channel,
chain: network.chain_info,
id: network.id.to_string(),
gas_denom: Denom::from_str(network.gas_denom).unwrap(),
gas_price: network.gas_price,
lcd_url: network.lcd_url.map(|url| url.to_string()),
fcd_url: network.fcd_url.map(|url| url.to_string()),
};
state.check_file_validity();
Ok(state)
}
pub fn check_file_validity(&self) {
let file = File::open(&self.json_file_path).unwrap_or_else(|_| {
let file = File::create(&self.json_file_path).unwrap();
serde_json::to_writer_pretty(&file, &json!({})).unwrap();
File::open(&self.json_file_path).unwrap()
});
log::info!("Opening daemon state at {}", self.json_file_path);
let mut json: serde_json::Value = from_reader(file).unwrap();
if json.get(self.chain.chain_id).is_none() {
json[self.chain.chain_id] = json!({});
}
if json[self.chain.chain_id].get(&self.id).is_none() {
json[self.chain.chain_id][&self.id] = json!({
"addresses": {},
"code_ids": {}
});
}
serde_json::to_writer_pretty(File::create(&self.json_file_path).unwrap(), &json).unwrap();
}
fn json(&self) -> serde_json::Value {
let file = File::open(&self.json_file_path)
.unwrap_or_else(|_| panic!("file should be present at {}", self.json_file_path));
let json: serde_json::Value = from_reader(file).unwrap();
json
}
fn get(&self, key: &str) -> Value {
let json = self.json();
json[&self.chain.chain_id][&self.id.to_string()][key].clone()
}
fn set<T: Serialize>(&self, key: &str, contract_id: &str, value: T) {
let mut json = self.json();
json[&self.chain.chain_id][&self.id.to_string()][key][contract_id] = json!(value);
serde_json::to_writer_pretty(File::create(&self.json_file_path).unwrap(), &json).unwrap();
}
}
impl StateInterface for Rc<DaemonState> {
fn get_address(&self, contract_id: &str) -> Result<Addr, BootError> {
let value = self
.get("addresses")
.get(contract_id)
.ok_or_else(|| BootError::AddrNotInFile(contract_id.to_owned()))?
.clone();
Ok(Addr::unchecked(value.as_str().unwrap()))
}
fn set_address(&mut self, contract_id: &str, address: &Addr) {
self.set("addresses", contract_id, address.as_str());
}
fn get_code_id(&self, contract_id: &str) -> Result<u64, BootError> {
let value = self
.get("code_ids")
.get(contract_id)
.ok_or_else(|| BootError::CodeIdNotInFile(contract_id.to_owned()))?
.clone();
Ok(value.as_u64().unwrap())
}
fn set_code_id(&mut self, contract_id: &str, code_id: u64) {
self.set("code_ids", contract_id, code_id);
}
fn get_all_addresses(&self) -> Result<HashMap<String, Addr>, BootError> {
let mut store = HashMap::new();
let addresses = self.get("addresses");
let value = addresses.as_object().unwrap();
for (id, addr) in value {
store.insert(id.clone(), Addr::unchecked(addr.as_str().unwrap()));
}
Ok(store)
}
fn get_all_code_ids(&self) -> Result<HashMap<String, u64>, BootError> {
let mut store = HashMap::new();
let code_ids = self.get("code_ids");
let value = code_ids.as_object().unwrap();
for (id, code_id) in value {
store.insert(id.clone(), code_id.as_u64().unwrap());
}
Ok(store)
}
}
#[derive(Clone, Debug)]
pub struct NetworkInfo<'a> {
pub id: &'a str,
pub gas_denom: &'a str,
pub gas_price: f64,
pub grpc_url: &'a str,
pub lcd_url: Option<&'a str>,
pub fcd_url: Option<&'a str>,
pub chain_info: ChainInfo<'a>,
pub kind: NetworkKind,
}
#[derive(Clone, Debug, Serialize, Default)]
pub struct ChainInfo<'a> {
pub chain_id: &'a str,
pub pub_address_prefix: &'a str,
pub coin_type: u32,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum NetworkKind {
Local,
Mainnet,
Testnet,
}
impl NetworkKind {
pub fn new() -> Result<Self, BootError> {
let network_id = env::var("NETWORK")?;
let network = match network_id.as_str() {
"testnet" => NetworkKind::Testnet,
"mainnet" => NetworkKind::Mainnet,
_ => NetworkKind::Local,
};
Ok(network)
}
pub fn mnemonic_name(&self) -> &str {
match *self {
NetworkKind::Local => "LOCAL_MNEMONIC",
NetworkKind::Mainnet => "MAIN_MNEMONIC",
NetworkKind::Testnet => "TEST_MNEMONIC",
}
}
pub fn multisig_name(&self) -> &str {
match *self {
NetworkKind::Local => "LOCAL_MULTISIG",
NetworkKind::Mainnet => "MAIN_MULTISIG",
NetworkKind::Testnet => "TEST_MULTISIG",
}
}
}
impl ToString for NetworkKind {
fn to_string(&self) -> String {
match *self {
NetworkKind::Local => "local",
NetworkKind::Mainnet => "mainnet",
NetworkKind::Testnet => "testnet",
}
.into()
}
}