1use std::{
2 collections::HashMap,
3 path::{Path, PathBuf}
4};
5
6use anyhow::Result;
7use clap::ArgMatches;
8use odra::{
9 contract_def::HasIdent,
10 entry_point_callback::EntryPointsCaller,
11 host::{EntryPointsCallerProvider, HostEnv},
12 schema::{SchemaCustomTypes, SchemaEntrypoints, SchemaEvents},
13 OdraContract
14};
15
16use crate::{
17 cmd::{
18 ContractsCmd, DeployCmd, DeployScript, MainCmd, MutableCommand, OdraCommand,
19 PrintEventsCmd, Scenario, ScenarioMetadata, ScenariosCmd, WhoamiCmd, CONTRACTS_SUBCOMMAND,
20 DEPLOY_SUBCOMMAND, PRINT_EVENTS_SUBCOMMAND, SCENARIOS_SUBCOMMAND, WHOAMI_SUBCOMMAND
21 },
22 container::FileContractStorage,
23 custom_types::CustomTypes,
24 utils::get_default_contracts_file,
25 ContractProvider, DeployedContractsContainer
26};
27
28pub struct OdraCli {
30 main_cmd: MainCmd,
31 deploy_cmd: Option<DeployCmd>,
32 contracts_cmd: ContractsCmd,
33 print_events_cmd: PrintEventsCmd,
34 scenarios_cmd: ScenariosCmd,
35 whoami_cmd: WhoamiCmd,
36 custom_types: CustomTypes,
37 host_env: HostEnv,
38 callers: HashMap<(String, String), EntryPointsCaller>,
39 default_contract_path: Option<PathBuf>
40}
41
42impl Default for OdraCli {
43 fn default() -> Self {
44 Self::new()
45 }
46}
47
48impl OdraCli {
49 pub fn new() -> Self {
51 Self {
52 main_cmd: MainCmd::default(),
53 deploy_cmd: None,
54 contracts_cmd: ContractsCmd::default(),
55 print_events_cmd: PrintEventsCmd::default(),
56 scenarios_cmd: ScenariosCmd::default(),
57 whoami_cmd: WhoamiCmd::new(),
58 host_env: odra_casper_livenet_env::env(),
59 custom_types: CustomTypes::default(),
60 callers: HashMap::default(),
61 default_contract_path: None
62 }
63 }
64
65 pub fn about(mut self, about: &'static str) -> Self {
67 self.main_cmd = self.main_cmd.about(about);
68 self
69 }
70
71 pub fn contracts_file<P: AsRef<Path>>(mut self, path: P) -> Self {
75 self.default_contract_path = Some(path.as_ref().to_path_buf());
76 self
77 }
78
79 pub fn contract<T: SchemaEntrypoints + SchemaCustomTypes + SchemaEvents + OdraContract>(
84 mut self
85 ) -> Self {
86 self.callers.insert(
87 (T::HostRef::ident(), T::HostRef::ident()),
88 T::HostRef::entry_points_caller(&self.host_env)
89 );
90 self.custom_types.register::<T>();
91 self.contracts_cmd.add_contract::<T>();
92 self.print_events_cmd.add_contract::<T>();
93 self
94 }
95
96 pub fn named_contract<
101 T: SchemaEntrypoints + SchemaCustomTypes + SchemaEvents + OdraContract
102 >(
103 mut self,
104 name: String
105 ) -> Self {
106 self.callers.insert(
107 (T::HostRef::ident(), name.clone()),
108 T::HostRef::entry_points_caller(&self.host_env)
109 );
110 self.custom_types.register::<T>();
111 self.contracts_cmd.add_contract_named::<T>(name.clone());
112 self.print_events_cmd.add_contract_named::<T>(name);
113 self
114 }
115
116 pub fn deploy(mut self, script: impl DeployScript + 'static) -> Self {
120 let cmd = DeployCmd::new(script);
121 self.main_cmd = self.main_cmd.subcommand(&cmd);
122 self.deploy_cmd = Some(cmd);
123 self
124 }
125
126 pub fn scenario<S: ScenarioMetadata + Scenario>(mut self, scenario: S) -> Self {
132 self.scenarios_cmd.add_scenario(scenario);
133 self
134 }
135
136 pub fn build(mut self) -> Self {
138 self.main_cmd = self.main_cmd.subcommand(&self.contracts_cmd);
139 self.main_cmd = self.main_cmd.subcommand(&self.scenarios_cmd);
140 self.main_cmd = self.main_cmd.subcommand(&self.print_events_cmd);
141 self.main_cmd = self.main_cmd.subcommand(&self.whoami_cmd);
142 self
143 }
144
145 pub fn run(self) {
147 let (cmd, args, contracts_path) = self.main_cmd.get_matches();
148 let contracts_path = match contracts_path {
149 Some(path) => Some(path),
150 None => self.default_contract_path.clone()
151 };
152
153 let storage = FileContractStorage::new(contracts_path.clone()).unwrap_or_else(|e| {
154 prettycli::error(&format!("Failed to create contract storage: {e}"));
155 std::process::exit(1);
156 });
157 let mut container = DeployedContractsContainer::instance(storage);
159
160 for deployed_contract in container.all_contracts() {
163 let caller = self.callers.get(&(deployed_contract.name(), deployed_contract.key_name())).unwrap_or_else(|| {
164 let path = match &contracts_path {
165 Some(path) => path.to_str().map(|s| s.to_string()).unwrap_or_default(),
166 None => get_default_contracts_file()
167 };
168 prettycli::error(&format!(
169 "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::<{}>()`.",
170 &deployed_contract.key_name(), path, &deployed_contract.name()
171 ));
172 std::process::exit(1);
173 }).clone();
174 self.host_env.register_contract(
175 deployed_contract.address(),
176 deployed_contract.key_name(),
177 caller
178 );
179 }
180
181 let result = match cmd.as_str() {
182 DEPLOY_SUBCOMMAND => self
183 .deploy_cmd
184 .as_ref()
185 .unwrap_or_else(|| {
186 prettycli::error("Deploy command not found. Did you forget to add it?");
187 std::process::exit(1);
188 })
189 .run(&self.host_env, &args, &self.custom_types, &mut container),
190 CONTRACTS_SUBCOMMAND => self.run_command(&self.contracts_cmd, args, &container),
191 PRINT_EVENTS_SUBCOMMAND => self.run_command(&self.print_events_cmd, args, &container),
192 SCENARIOS_SUBCOMMAND => self.run_command(&self.scenarios_cmd, args, &container),
193 WHOAMI_SUBCOMMAND => self.run_command(&self.whoami_cmd, args, &container),
194 _ => unreachable!()
195 };
196
197 match result {
198 Ok(_) => prettycli::info("Command executed successfully"),
199 Err(err) => prettycli::error(&format!("{:?}", err))
200 }
201 }
202
203 fn run_command<T: OdraCommand>(
204 &self,
205 cmd: &T,
206 args: ArgMatches,
207 container: &DeployedContractsContainer
208 ) -> Result<()> {
209 cmd.run(&self.host_env, &args, &self.custom_types, container)
210 }
211}