1use std::{fs::File, io::Write, path::PathBuf, str::FromStr};
2
3use chrono::{DateTime, SecondsFormat, Utc};
4use odra::{
5 contract_def::HasIdent,
6 host::{HostEnv, HostRef, HostRefLoader},
7 prelude::Address,
8 OdraContract
9};
10use serde_derive::{Deserialize, Serialize};
11use thiserror::Error;
12
13const DEPLOYED_CONTRACTS_FILE: &str = "resources/deployed_contracts.toml";
14
15#[derive(Error, Debug)]
16pub enum ContractError {
17 #[error("TOML serialization error")]
18 TomlSerialize(#[from] toml::ser::Error),
19 #[error("TOML deserialization error")]
20 TomlDeserialize(#[from] toml::de::Error),
21 #[error("Couldn't read file")]
22 Io(#[from] std::io::Error),
23 #[error("Couldn't find contract `{0}`")]
24 NotFound(String)
25}
26
27#[derive(Deserialize, Serialize, Debug, Clone)]
35pub struct DeployedContractsContainer {
36 time: String,
37 pub contracts: Vec<DeployedContract>
38}
39
40impl DeployedContractsContainer {
41 pub(crate) fn new() -> Result<Self, ContractError> {
43 Self::handle_previous_version()?;
44 let now: DateTime<Utc> = Utc::now();
45 Ok(Self {
46 time: now.to_rfc3339_opts(SecondsFormat::Secs, true),
47 contracts: Vec::new()
48 })
49 }
50
51 pub fn add_contract<T: HostRef + HasIdent>(
53 &mut self,
54 contract: &T
55 ) -> Result<(), ContractError> {
56 self.contracts
57 .push(DeployedContract::new::<T>(contract.address()));
58 self.update()
59 }
60
61 pub fn get_ref<T: OdraContract + 'static>(
65 &self,
66 env: &HostEnv
67 ) -> Result<T::HostRef, ContractError> {
68 self.contracts
69 .iter()
70 .find(|c| c.name == T::HostRef::ident())
71 .map(|c| Address::from_str(&c.package_hash).ok())
72 .and_then(|opt| opt.map(|addr| <T as HostRefLoader<T::HostRef>>::load(env, addr)))
73 .ok_or(ContractError::NotFound(T::HostRef::ident()))
74 }
75
76 pub fn address(&self, name: &str) -> Option<Address> {
78 self.contracts
79 .iter()
80 .find(|c| c.name == name)
81 .and_then(|c| Address::from_str(&c.package_hash).ok())
82 }
83
84 pub fn load() -> Result<Self, ContractError> {
86 let path = Self::file_path()?;
87 let file = std::fs::read_to_string(path).map_err(ContractError::Io)?;
88
89 let result = toml::from_str(&file).map_err(ContractError::TomlDeserialize)?;
90 Ok(result)
91 }
92
93 pub(crate) fn handle_previous_version() -> Result<(), ContractError> {
95 if let Ok(deployed_contracts) = Self::load() {
96 let date = deployed_contracts.time();
98 let mut path = project_root::get_project_root().map_err(ContractError::Io)?;
99 path.push(format!("{}.{}", DEPLOYED_CONTRACTS_FILE, date));
100
101 deployed_contracts.save_at(&path)?;
103
104 std::fs::remove_file(path).map_err(ContractError::Io)?;
106 }
107 Ok(())
108 }
109
110 fn save_at(&self, file_path: &PathBuf) -> Result<(), ContractError> {
112 let content = toml::to_string_pretty(&self).map_err(ContractError::TomlSerialize)?;
113 let mut file = File::create(file_path).map_err(ContractError::Io)?;
114
115 file.write_all(content.as_bytes())
116 .map_err(ContractError::Io)?;
117 Ok(())
118 }
119
120 fn time(&self) -> &str {
122 &self.time
123 }
124
125 fn update(&self) -> Result<(), ContractError> {
127 let path = Self::file_path()?;
128 self.save_at(&path)
129 }
130
131 fn file_path() -> Result<PathBuf, ContractError> {
132 let mut path = project_root::get_project_root().map_err(ContractError::Io)?;
133 path.push(DEPLOYED_CONTRACTS_FILE);
134
135 Ok(path)
136 }
137}
138
139#[derive(Deserialize, Serialize, Debug, Clone)]
141pub struct DeployedContract {
142 pub name: String,
143 pub package_hash: String
144}
145
146impl DeployedContract {
147 fn new<T: HasIdent>(address: &Address) -> Self {
148 Self {
149 name: T::ident(),
150 package_hash: address.to_string()
151 }
152 }
153}