1use std::collections::HashMap;
2
3use anyhow::Result;
4use clap::ArgMatches;
5use odra::{
6 contract_def::HasIdent,
7 entry_point_callback::EntryPointsCaller,
8 host::{EntryPointsCallerProvider, HostEnv},
9 schema::{SchemaCustomTypes, SchemaEntrypoints, SchemaEvents},
10 OdraContract
11};
12
13use crate::{
14 cmd::{
15 ContractsCmd, DeployCmd, DeployScript, MainCmd, MutableCommand, OdraCommand,
16 PrintEventsCmd, Scenario, ScenarioMetadata, ScenariosCmd, WhoamiCmd, CONTRACTS_SUBCOMMAND,
17 DEPLOY_SUBCOMMAND, PRINT_EVENTS_SUBCOMMAND, SCENARIOS_SUBCOMMAND, WHOAMI_SUBCOMMAND
18 },
19 container::{FileContractStorage, DEPLOYED_CONTRACTS_FILE},
20 custom_types::CustomTypes,
21 ContractProvider, DeployedContractsContainer
22};
23
24pub struct OdraCli {
26 main_cmd: MainCmd,
27 deploy_cmd: Option<DeployCmd>,
28 contracts_cmd: ContractsCmd,
29 print_events_cmd: PrintEventsCmd,
30 scenarios_cmd: ScenariosCmd,
31 whoami_cmd: WhoamiCmd,
32 custom_types: CustomTypes,
33 host_env: HostEnv,
34 callers: HashMap<String, EntryPointsCaller>
35}
36
37impl Default for OdraCli {
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43impl OdraCli {
44 pub fn new() -> Self {
46 Self {
47 main_cmd: MainCmd::default(),
48 deploy_cmd: None,
49 contracts_cmd: ContractsCmd::default(),
50 print_events_cmd: PrintEventsCmd::default(),
51 scenarios_cmd: ScenariosCmd::default(),
52 whoami_cmd: WhoamiCmd::new(),
53 host_env: odra_casper_livenet_env::env(),
54 custom_types: CustomTypes::default(),
55 callers: HashMap::default()
56 }
57 }
58
59 #[cfg(test)]
60 pub fn test(host_env: HostEnv) -> Self {
61 Self {
62 main_cmd: MainCmd::default(),
63 deploy_cmd: None,
64 contracts_cmd: ContractsCmd::default(),
65 print_events_cmd: PrintEventsCmd::default(),
66 scenarios_cmd: ScenariosCmd::default(),
67 whoami_cmd: WhoamiCmd::new(),
68 host_env,
69 custom_types: CustomTypes::default(),
70 callers: HashMap::default()
71 }
72 }
73
74 pub fn about(mut self, about: &'static str) -> Self {
76 self.main_cmd = self.main_cmd.about(about);
77 self
78 }
79
80 pub fn contract<T: SchemaEntrypoints + SchemaCustomTypes + SchemaEvents + OdraContract>(
85 mut self
86 ) -> Self {
87 self.callers.insert(
88 T::HostRef::ident(),
89 T::HostRef::entry_points_caller(&self.host_env)
90 );
91 self.custom_types.register::<T>();
92 self.contracts_cmd.add_contract::<T>();
93 self.print_events_cmd.add_contract::<T>();
94 self
95 }
96
97 pub fn deploy(mut self, script: impl DeployScript + 'static) -> Self {
101 let cmd = DeployCmd::new(script);
102 self.main_cmd = self.main_cmd.subcommand(&cmd);
103 self.deploy_cmd = Some(cmd);
104 self
105 }
106
107 pub fn scenario<S: ScenarioMetadata + Scenario>(mut self, scenario: S) -> Self {
113 self.scenarios_cmd.add_scenario(scenario);
114 self
115 }
116
117 pub fn build(mut self) -> Self {
119 self.main_cmd = self.main_cmd.subcommand(&self.contracts_cmd);
120 self.main_cmd = self.main_cmd.subcommand(&self.scenarios_cmd);
121 self.main_cmd = self.main_cmd.subcommand(&self.print_events_cmd);
122 self.main_cmd = self.main_cmd.subcommand(&self.whoami_cmd);
123 self
124 }
125
126 pub fn run(self) {
128 let (cmd, args, contracts_path) = self.main_cmd.get_matches();
129
130 let storage = FileContractStorage::new(contracts_path.clone()).unwrap_or_else(|e| {
131 prettycli::error(&format!("Failed to create contract storage: {e}"));
132 std::process::exit(1);
133 });
134 let mut container = DeployedContractsContainer::instance(storage);
136
137 for (name, address) in container.all_contracts() {
139 let caller = self.callers.get(&name).unwrap_or_else(|| {
140 let path = match &contracts_path {
141 Some(path) => path.to_str().map(|s| s.to_string()).unwrap_or_default(),
142 None => DEPLOYED_CONTRACTS_FILE.to_string()
143 };
144 prettycli::error(&format!(
145 "Caller for `{}` not found. The contract is registered in {:?} file, but not in the CLI builder. Make sure you have added it to the builder using `.contract::<{}>()`.",
146 &name, path, &name
147 ));
148 std::process::exit(1);
149 }).clone();
150 self.host_env.register_contract(address, name, caller);
151 }
152
153 let result = match cmd.as_str() {
154 DEPLOY_SUBCOMMAND => self
155 .deploy_cmd
156 .as_ref()
157 .unwrap_or_else(|| {
158 prettycli::error("Deploy command not found. Did you forget to add it?");
159 std::process::exit(1);
160 })
161 .run(&self.host_env, &args, &self.custom_types, &mut container),
162 CONTRACTS_SUBCOMMAND => self.run_command(&self.contracts_cmd, args, &container),
163 PRINT_EVENTS_SUBCOMMAND => self.run_command(&self.print_events_cmd, args, &container),
164 SCENARIOS_SUBCOMMAND => self.run_command(&self.scenarios_cmd, args, &container),
165 WHOAMI_SUBCOMMAND => self.run_command(&self.whoami_cmd, args, &container),
166 _ => unreachable!()
167 };
168
169 match result {
170 Ok(_) => prettycli::info("Command executed successfully"),
171 Err(err) => prettycli::error(&format!("{:?}", err))
172 }
173 }
174
175 fn run_command<T: OdraCommand>(
176 &self,
177 cmd: &T,
178 args: ArgMatches,
179 container: &DeployedContractsContainer
180 ) -> Result<()> {
181 cmd.run(&self.host_env, &args, &self.custom_types, container)
182 }
183}