1use super::*;
20
21pub mod deploy;
22pub use deploy::*;
23
24pub mod execute;
25pub use execute::*;
26
27pub mod helpers;
28pub use helpers::*;
29
30pub mod network;
31pub use network::*;
32
33pub mod resolver;
34pub use resolver::*;
35
36pub mod transfer;
37pub use transfer::*;
38
39#[derive(Clone)]
45pub struct ProgramManager<N: Network> {
46 pub(crate) programs: IndexMap<ProgramID<N>, Program<N>>,
47 pub(crate) private_key: Option<PrivateKey<N>>,
48 pub(crate) private_key_ciphertext: Option<Ciphertext<N>>,
49 pub(crate) local_program_directory: Option<PathBuf>,
50 pub(crate) api_client: Option<AleoAPIClient<N>>,
51 pub(crate) vm: Option<VM<N, ConsensusMemory<N>>>,
52}
53
54impl<N: Network> ProgramManager<N> {
55 pub fn new(
59 private_key: Option<PrivateKey<N>>,
60 private_key_ciphertext: Option<Ciphertext<N>>,
61 api_client: Option<AleoAPIClient<N>>,
62 local_program_directory: Option<PathBuf>,
63 use_cache: bool,
64 ) -> Result<Self> {
65 if private_key.is_some() && private_key_ciphertext.is_some() {
66 bail!("Cannot have both private key and private key ciphertext");
67 } else if private_key.is_none() && private_key_ciphertext.is_none() {
68 bail!("Must have either private key or private key ciphertext");
69 }
70 let programs = IndexMap::new();
71 let vm = if use_cache {
72 let store = ConsensusStore::<N, ConsensusMemory<N>>::open(None)?;
73 Some(VM::<N, ConsensusMemory<N>>::from(store)?)
74 } else {
75 None
76 };
77 Ok(Self { programs, private_key, private_key_ciphertext, local_program_directory, api_client, vm })
78 }
79
80 pub fn add_program(&mut self, program: &Program<N>) -> Result<()> {
82 if self.contains_program(program.id())? {
83 bail!("program already exists")
84 };
85 self.programs.entry(*program.id()).or_insert(program.clone());
86 Ok(())
87 }
88
89 pub fn initialize_vm(
91 api_client: &AleoAPIClient<N>,
92 program: &Program<N>,
93 initialize_execution: bool,
94 ) -> Result<VM<N, ConsensusMemory<N>>> {
95 let store = ConsensusStore::<N, ConsensusMemory<N>>::open(None)?;
98 let vm = VM::<N, ConsensusMemory<N>>::from(store)?;
99
100 let credits_id = ProgramID::<N>::from_str("credits.aleo")?;
102 api_client.get_program_imports_from_source(program)?.iter().try_for_each(|(_, import)| {
103 if import.id() != &credits_id {
104 vm.process().write().add_program(import)?
105 }
106 Ok::<_, Error>(())
107 })?;
108
109 if initialize_execution {
112 vm.process().write().add_program(program)?;
113 }
114 Ok(vm)
115 }
116
117 pub fn update_program(&mut self, program: &Program<N>) -> Option<Program<N>> {
120 self.programs.insert(*program.id(), program.clone())
121 }
122
123 pub fn get_program(&self, program_id: impl TryInto<ProgramID<N>>) -> Result<Program<N>> {
125 let program_id = program_id.try_into().map_err(|_| anyhow!("invalid program id"))?;
126 self.programs.get(&program_id).map_or(Err(anyhow!("program not found")), |program| Ok(program.clone()))
127 }
128
129 pub fn contains_program(&self, program_id: impl TryInto<ProgramID<N>>) -> Result<bool> {
131 let program_id = program_id.try_into().map_err(|_| anyhow!("invalid program id"))?;
132 Ok(self.programs.contains_key(&program_id))
133 }
134
135 pub(super) fn get_private_key(&self, password: Option<&str>) -> Result<PrivateKey<N>> {
138 if self.private_key.is_none() && self.private_key_ciphertext.is_none() {
139 bail!("Private key is not configured");
140 };
141 if let Some(private_key) = &self.private_key {
142 if self.private_key_ciphertext.is_some() {
143 bail!(
144 "Private key ciphertext is also configured, cannot have both private key and private key ciphertext"
145 );
146 }
147 return Ok(*private_key);
148 };
149 if let Some(ciphertext) = &self.private_key_ciphertext {
150 if self.private_key.is_some() {
151 bail!("Private key is already configured, cannot have both private key and private key ciphertext");
152 }
153
154 let password = password.ok_or_else(|| anyhow!("Private key is encrypted, password is required"))?;
155 return Encryptor::<N>::decrypt_private_key_with_secret(ciphertext, password);
156 };
157 bail!("Private key configuration error")
158 }
159
160 pub fn vm(&self) -> &Option<VM<N, ConsensusMemory<N>>> {
161 &self.vm
162 }
163}
164
165#[cfg(test)]
166#[cfg(not(feature = "wasm"))]
167mod tests {
168
169 use super::*;
170 use crate::{
171 test_utils::{HELLO_PROGRAM, HELLO_PROGRAM_2},
172 RECIPIENT_PRIVATE_KEY,
173 };
174
175 #[test]
176 fn test_constructors_fail_with_multiple_keys_or_no_keys() {
177 let api_client = AleoAPIClient::<Testnet3>::testnet3();
178 let private_key = PrivateKey::<Testnet3>::from_str(RECIPIENT_PRIVATE_KEY).unwrap();
179 let private_key_ciphertext =
180 Encryptor::<Testnet3>::encrypt_private_key_with_secret(&private_key, "password").unwrap();
181 let temp_dir = std::env::temp_dir();
183
184 let program_manager =
186 ProgramManager::<Testnet3>::new(None, None, Some(api_client.clone()), Some(temp_dir.clone()), false);
187
188 assert!(program_manager.is_err());
189
190 let program_manager = ProgramManager::<Testnet3>::new(
192 Some(private_key),
193 Some(private_key_ciphertext.clone()),
194 Some(api_client.clone()),
195 Some(temp_dir.clone()),
196 false,
197 );
198
199 assert!(program_manager.is_err());
200
201 let program_manager = ProgramManager::<Testnet3>::new(
203 Some(private_key),
204 None,
205 Some(api_client.clone()),
206 Some(temp_dir.clone()),
207 false,
208 );
209
210 assert!(program_manager.is_ok());
211
212 let program_manager = ProgramManager::<Testnet3>::new(
214 None,
215 Some(private_key_ciphertext),
216 Some(api_client),
217 Some(temp_dir),
218 false,
219 );
220
221 assert!(program_manager.is_ok());
222 }
223
224 #[test]
225 fn test_program_management_methods() {
226 let private_key = PrivateKey::<Testnet3>::from_str(RECIPIENT_PRIVATE_KEY).unwrap();
227 let mut program_manager = ProgramManager::<Testnet3>::new(Some(private_key), None, None, None, false).unwrap();
228
229 let program = Program::<Testnet3>::from_str(HELLO_PROGRAM).unwrap();
231 assert!(!program_manager.contains_program(program.id()).unwrap());
232 program_manager.add_program(&program).unwrap();
233 assert!(program_manager.contains_program(program.id()).unwrap());
234 assert_eq!(program_manager.get_program(program.id()).unwrap(), program);
235 assert_eq!(program_manager.get_program("hello.aleo").unwrap(), program);
236 assert!(program_manager.contains_program("hello.aleo").unwrap());
237 assert!(program_manager.add_program(&program).is_err());
238
239 let program_2 = Program::<Testnet3>::from_str(HELLO_PROGRAM_2).unwrap();
241 let replaced_program = program_manager.update_program(&program_2).unwrap();
242 let retrieved_program = program_manager.get_program(program.id()).unwrap();
243 assert_eq!(replaced_program, program);
244 assert_eq!(retrieved_program, program_2);
245 }
246
247 #[test]
248 fn test_private_key_retrieval_from_ciphertext() {
249 let private_key = PrivateKey::from_str(RECIPIENT_PRIVATE_KEY).unwrap();
250 let private_key_ciphertext =
251 Encryptor::<Testnet3>::encrypt_private_key_with_secret(&private_key, "password").unwrap();
252 let temp_dir = std::env::temp_dir();
253 let api_client = AleoAPIClient::<Testnet3>::testnet3();
254
255 let program_manager = ProgramManager::<Testnet3>::new(
256 None,
257 Some(private_key_ciphertext),
258 Some(api_client),
259 Some(temp_dir),
260 false,
261 )
262 .unwrap();
263
264 let recovered_private_key = program_manager.get_private_key(Some("password")).unwrap();
266 assert_eq!(recovered_private_key, private_key);
267
268 let recovered_private_key = program_manager.get_private_key(Some("wrong_password"));
270 assert!(recovered_private_key.is_err());
271
272 let recoverd_private_key = program_manager.get_private_key(None);
274 assert!(recoverd_private_key.is_err());
275 }
276
277 #[test]
278 fn test_private_key_retrieval_from_plaintext() {
279 let private_key = PrivateKey::<Testnet3>::from_str(RECIPIENT_PRIVATE_KEY).unwrap();
280 let temp_dir = std::env::temp_dir();
281 let api_client = AleoAPIClient::<Testnet3>::testnet3();
282
283 let program_manager =
284 ProgramManager::<Testnet3>::new(Some(private_key), None, Some(api_client), Some(temp_dir), false).unwrap();
285
286 let recovered_private_key = program_manager.get_private_key(None).unwrap();
288 assert_eq!(recovered_private_key, private_key);
289
290 let recovered_private_key = program_manager.get_private_key(Some("password")).unwrap();
291 assert_eq!(recovered_private_key, private_key);
292 }
293
294 #[test]
295 fn test_import_resolution() {
296 let api_client = AleoAPIClient::<Testnet3>::testnet3();
297 let top_level_program = api_client.get_program("imported_add_mul.aleo").unwrap();
298 let add_program = api_client.get_program("addition_test.aleo").unwrap();
299 let multiply_program = api_client.get_program("multiply_test.aleo").unwrap();
300 let double_program = api_client.get_program("double_test.aleo").unwrap();
301 let vm_execute = ProgramManager::<Testnet3>::initialize_vm(&api_client, &top_level_program, true).unwrap();
302 let vm_deploy = ProgramManager::<Testnet3>::initialize_vm(&api_client, &top_level_program, false).unwrap();
303
304 let top_program = vm_execute.process().read().get_program("imported_add_mul.aleo").unwrap().clone();
306 assert_eq!(top_level_program, top_program);
307 let add_import = vm_execute.process().read().get_program("addition_test.aleo").unwrap().clone();
308 assert_eq!(add_program, add_import);
309 let multiply_import = vm_execute.process().read().get_program("multiply_test.aleo").unwrap().clone();
310 assert_eq!(multiply_program, multiply_import);
311 let double_import = vm_execute.process().read().get_program("double_test.aleo").unwrap().clone();
312 assert_eq!(double_program, double_import);
313
314 let top_program = vm_deploy.process().read().get_program("imported_add_mul.aleo").cloned();
316 assert!(top_program.is_err());
317 let add_import = vm_deploy.process().read().get_program("addition_test.aleo").unwrap().clone();
318 assert_eq!(add_program, add_import);
319 let multiply_import = vm_deploy.process().read().get_program("multiply_test.aleo").unwrap().clone();
320 assert_eq!(multiply_program, multiply_import);
321 let double_import = vm_deploy.process().read().get_program("double_test.aleo").unwrap().clone();
322 assert_eq!(double_program, double_import);
323 }
324}