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        Ok(())
48    }
49}
50
51impl From<&DeployCmd> for Command {
52    fn from(_value: &DeployCmd) -> Self {
53        Command::new(DEPLOY_SUBCOMMAND)
54            .arg(Arg::DeployMode)
55            .about("Runs the deploy script")
56    }
57}
58
59/// Script that deploys contracts to the blockchain and stores contract data for further use.
60///
61/// In a deploy script, you can define the contracts that you want to deploy to the blockchain
62/// and write metadata to the container.
63pub trait DeployScript {
64    fn deploy(
65        &self,
66        env: &HostEnv,
67        container: &mut DeployedContractsContainer
68    ) -> core::result::Result<(), DeployError>;
69}
70
71/// Error that occurs during contract deployment.
72#[derive(Debug, Error)]
73pub enum DeployError {
74    #[error("Deploy error: {message}")]
75    OdraError { message: String },
76    #[error("Contract read error: {0}")]
77    ContractReadError(#[from] ContractError),
78    #[error("Missing deploy mode argument")]
79    MissingDeployMode
80}
81
82impl From<OdraError> for DeployError {
83    fn from(err: OdraError) -> Self {
84        DeployError::OdraError {
85            message: format!("{:?}", err)
86        }
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use crate::test_utils;
93
94    use super::*;
95    use odra::host::HostEnv;
96
97    struct MockDeployScript;
98
99    impl DeployScript for MockDeployScript {
100        fn deploy(
101            &self,
102            _env: &HostEnv,
103            _container: &mut DeployedContractsContainer
104        ) -> core::result::Result<(), DeployError> {
105            Ok(())
106        }
107    }
108
109    #[test]
110    fn deploy_cmd_run() {
111        // This is a placeholder test to ensure the DeployCmd can be instantiated and run.
112        let env = test_utils::mock_host_env();
113        let cmd = DeployCmd::new(MockDeployScript);
114        let mut container = test_utils::mock_contracts_container();
115        let command: Command = (&cmd).into();
116        let arg_matches = command.try_get_matches_from(vec!["test"]).unwrap();
117        let result = cmd.run(
118            &env,
119            &arg_matches,
120            &CustomTypeSet::default(),
121            &mut container
122        );
123        assert!(result.is_ok());
124    }
125
126    #[test]
127    fn parsing_deploy_cmd() {
128        // This is a placeholder test to ensure the DeployCmd can be converted to a Command.
129        let cmd = DeployCmd::new(MockDeployScript);
130        let command: Command = (&cmd).into();
131        assert_eq!(command.get_name(), DEPLOY_SUBCOMMAND);
132        assert!(command.get_about().is_some());
133    }
134
135    #[test]
136    fn deploy_accepts_mode_arg() {
137        let cmd = DeployCmd::new(MockDeployScript);
138        let command: Command = (&cmd).into();
139
140        let result =
141            command
142                .clone()
143                .try_get_matches_from(vec!["test", "--deploy-mode", "override"]);
144        assert!(result.is_ok());
145
146        let result = command
147            .clone()
148            .try_get_matches_from(vec!["test", "--deploy-mode", "default"]);
149        assert!(result.is_ok());
150
151        let result = command
152            .clone()
153            .try_get_matches_from(vec!["test", "--deploy-mode", "archive"]);
154        assert!(result.is_ok());
155
156        let result = command
157            .clone()
158            .try_get_matches_from(vec!["test", "--deploy-mode", "abc"]);
159        assert!(result.is_err());
160
161        let result = command.try_get_matches_from(vec!["test"]);
162        assert!(result.is_ok());
163
164        let command: Command = (&cmd).into();
165        assert_eq!(command.get_arguments().count(), 1);
166    }
167}