Skip to main content

odra_cli/cmd/
deploy.rs

1use crate::{
2    cmd::{
3        args::{read_arg, Arg},
4        DEPLOY_SUBCOMMAND
5    },
6    container::ContractError,
7    custom_types::CustomTypeSet,
8    DeployedContractsContainer
9};
10use anyhow::Result;
11use clap::{ArgMatches, Command};
12use odra::{host::HostEnv, prelude::OdraError};
13use thiserror::Error;
14
15use super::MutableCommand;
16
17/// DeployCmd is a struct that represents the deploy command in the Odra CLI.
18///
19/// The deploy command runs the [DeployScript].
20pub(crate) struct DeployCmd {
21    pub script: Box<dyn DeployScript>
22}
23
24impl DeployCmd {
25    /// Creates a new instance of `DeployCmd` with the provided script.
26    pub fn new(script: impl DeployScript + 'static) -> Self {
27        DeployCmd {
28            script: Box::new(script)
29        }
30    }
31}
32
33impl MutableCommand for DeployCmd {
34    fn run(
35        &self,
36        env: &HostEnv,
37        args: &ArgMatches,
38        _types: &CustomTypeSet,
39        container: &mut DeployedContractsContainer
40    ) -> Result<()> {
41        let deploy_mode =
42            read_arg::<String>(args, Arg::DeployMode).ok_or(DeployError::MissingDeployMode)?;
43        crate::log(format!("Deploy mode: {}", deploy_mode));
44        container.apply_deploy_mode(deploy_mode)?;
45
46        self.script.deploy(env, container)?;
47        crate::log("Deployment completed successfully.");
48        Ok(())
49    }
50}
51
52impl From<&DeployCmd> for Command {
53    fn from(_value: &DeployCmd) -> Self {
54        Command::new(DEPLOY_SUBCOMMAND)
55            .arg(Arg::DeployMode)
56            .about("Runs the deploy script")
57    }
58}
59
60/// Script that deploys contracts to the blockchain and stores contract data for further use.
61///
62/// In a deploy script, you can define the contracts that you want to deploy to the blockchain
63/// and write metadata to the container.
64pub trait DeployScript {
65    fn deploy(
66        &self,
67        env: &HostEnv,
68        container: &mut DeployedContractsContainer
69    ) -> core::result::Result<(), DeployError>;
70}
71
72/// Error that occurs during contract deployment.
73#[derive(Debug, Error)]
74pub enum DeployError {
75    #[error("Deploy error: {message}")]
76    OdraError { message: String },
77    #[error("Contract read error: {0}")]
78    ContractReadError(#[from] ContractError),
79    #[error("Missing deploy mode argument")]
80    MissingDeployMode
81}
82
83impl From<OdraError> for DeployError {
84    fn from(err: OdraError) -> Self {
85        DeployError::OdraError {
86            message: format!("{err:?}")
87        }
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use crate::test_utils;
94
95    use super::*;
96    use odra::host::HostEnv;
97
98    struct MockDeployScript;
99
100    impl DeployScript for MockDeployScript {
101        fn deploy(
102            &self,
103            _env: &HostEnv,
104            _container: &mut DeployedContractsContainer
105        ) -> core::result::Result<(), DeployError> {
106            Ok(())
107        }
108    }
109
110    #[test]
111    fn deploy_cmd_run() {
112        // This is a placeholder test to ensure the DeployCmd can be instantiated and run.
113        let env = test_utils::mock_host_env();
114        let cmd = DeployCmd::new(MockDeployScript);
115        let mut container = test_utils::mock_contracts_container();
116        let command: Command = (&cmd).into();
117        let arg_matches = command.try_get_matches_from(vec!["test"]).unwrap();
118        let result = cmd.run(
119            &env,
120            &arg_matches,
121            &CustomTypeSet::default(),
122            &mut container
123        );
124        assert!(result.is_ok());
125    }
126
127    #[test]
128    fn parsing_deploy_cmd() {
129        // This is a placeholder test to ensure the DeployCmd can be converted to a Command.
130        let cmd = DeployCmd::new(MockDeployScript);
131        let command: Command = (&cmd).into();
132        assert_eq!(command.get_name(), DEPLOY_SUBCOMMAND);
133        assert!(command.get_about().is_some());
134    }
135
136    #[test]
137    fn deploy_accepts_mode_arg() {
138        let cmd = DeployCmd::new(MockDeployScript);
139        let command: Command = (&cmd).into();
140
141        let result =
142            command
143                .clone()
144                .try_get_matches_from(vec!["test", "--deploy-mode", "override"]);
145        assert!(result.is_ok());
146
147        let result = command
148            .clone()
149            .try_get_matches_from(vec!["test", "--deploy-mode", "default"]);
150        assert!(result.is_ok());
151
152        let result = command
153            .clone()
154            .try_get_matches_from(vec!["test", "--deploy-mode", "archive"]);
155        assert!(result.is_ok());
156
157        let result = command
158            .clone()
159            .try_get_matches_from(vec!["test", "--deploy-mode", "abc"]);
160        assert!(result.is_err());
161
162        let result = command.try_get_matches_from(vec!["test"]);
163        assert!(result.is_ok());
164
165        let command: Command = (&cmd).into();
166        assert_eq!(command.get_arguments().count(), 1);
167    }
168}