odra_mock_vm/
contract_container.rs

1use std::collections::BTreeMap;
2
3use odra_types::{
4    casper_types::{NamedArg, RuntimeArgs},
5    OdraError, VmError
6};
7
8#[doc(hidden)]
9pub type EntrypointCall = fn(String, &RuntimeArgs) -> Vec<u8>;
10#[doc(hidden)]
11pub type EntrypointArgs = Vec<String>;
12
13#[derive(Default, Clone)]
14pub struct ContractContainer {
15    name: String,
16    entrypoints: BTreeMap<String, (EntrypointArgs, EntrypointCall)>,
17    constructors: BTreeMap<String, (EntrypointArgs, EntrypointCall)>
18}
19
20impl ContractContainer {
21    pub fn new(
22        name: &str,
23        entrypoints: BTreeMap<String, (EntrypointArgs, EntrypointCall)>,
24        constructors: BTreeMap<String, (EntrypointArgs, EntrypointCall)>
25    ) -> Self {
26        Self {
27            name: String::from(name),
28            entrypoints,
29            constructors
30        }
31    }
32
33    pub fn call(&self, entrypoint: String, args: &RuntimeArgs) -> Result<Vec<u8>, OdraError> {
34        if self.constructors.get(&entrypoint).is_some() {
35            return Err(OdraError::VmError(VmError::InvalidContext));
36        }
37
38        match self.entrypoints.get(&entrypoint) {
39            Some((ep_args, call)) => {
40                self.validate_args(ep_args, args)?;
41                Ok(call(self.name.clone(), args))
42            }
43            None => Err(OdraError::VmError(VmError::NoSuchMethod(entrypoint)))
44        }
45    }
46
47    pub fn call_constructor(
48        &self,
49        entrypoint: String,
50        args: &RuntimeArgs
51    ) -> Result<Vec<u8>, OdraError> {
52        match self.constructors.get(&entrypoint) {
53            Some((ep_args, call)) => {
54                self.validate_args(ep_args, args)?;
55                Ok(call(self.name.clone(), args))
56            }
57            None => Err(OdraError::VmError(VmError::NoSuchMethod(entrypoint)))
58        }
59    }
60
61    fn validate_args(&self, args: &[String], input_args: &RuntimeArgs) -> Result<(), OdraError> {
62        let named_args = input_args
63            .named_args()
64            .map(NamedArg::name)
65            .collect::<Vec<_>>();
66
67        if args
68            .iter()
69            .filter(|arg| !named_args.contains(&arg.as_str()))
70            .map(|arg| arg.to_owned())
71            .next()
72            .is_none()
73        {
74            Ok(())
75        } else {
76            Err(OdraError::VmError(VmError::MissingArg))
77        }
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use std::collections::BTreeMap;
84
85    use odra_types::{
86        casper_types::{runtime_args, RuntimeArgs},
87        OdraError, VmError
88    };
89
90    use crate::{EntrypointArgs, EntrypointCall};
91
92    use super::ContractContainer;
93
94    #[test]
95    fn test_call_wrong_entrypoint() {
96        // Given an instance with no entrypoints.
97        let instance = ContractContainer::empty();
98
99        // When call some entrypoint.
100        let result = instance.call(String::from("ep"), &RuntimeArgs::new());
101
102        // Then an error occurs.
103        assert!(result.is_err());
104    }
105
106    #[test]
107    fn test_call_valid_entrypoint() {
108        // Given an instance with a single no-args entrypoint.
109        let ep_name = String::from("ep");
110        let instance = ContractContainer::setup_entrypoint(ep_name.clone(), vec![]);
111
112        // When call the registered entrypoint.
113        let result = instance.call(ep_name, &RuntimeArgs::new());
114
115        // Then teh call succeeds.
116        assert!(result.is_ok());
117    }
118
119    #[test]
120    fn test_call_valid_entrypoint_with_wrong_arg_name() {
121        // Given an instance with a single entrypoint with one arg named "first".
122        let ep_name = String::from("ep");
123        let instance = ContractContainer::setup_entrypoint(ep_name.clone(), vec!["first"]);
124
125        // When call the registered entrypoint with an arg named "second".
126        let args = runtime_args! { "second" => 0 };
127        let result = instance.call(ep_name, &args);
128
129        // Then MissingArg error is returned.
130        assert_eq!(result.unwrap_err(), OdraError::VmError(VmError::MissingArg));
131    }
132
133    #[test]
134    fn test_call_valid_entrypoint_with_missing_arg() {
135        // Given an instance with a single entrypoint with one arg named "first".
136        let ep_name = String::from("ep");
137        let instance = ContractContainer::setup_entrypoint(ep_name.clone(), vec!["first"]);
138
139        // When call a valid entrypoint without args.
140        let result = instance.call(ep_name, &RuntimeArgs::new());
141
142        // Then MissingArg error is returned.
143        assert_eq!(result.unwrap_err(), OdraError::VmError(VmError::MissingArg));
144    }
145
146    #[test]
147    #[ignore = "At the moment is impossible to find the name of all missing args."]
148    fn test_all_missing_args_are_caught() {
149        // Given an instance with a single entrypoint with "first", "second" and "third" args.
150        let ep_name = String::from("ep");
151        let instance =
152            ContractContainer::setup_entrypoint(ep_name.clone(), vec!["first", "second", "third"]);
153
154        // When call a valid entrypoint with a single valid args,
155        let args = runtime_args! { "third" => 0 };
156        let result = instance.call(ep_name, &args);
157
158        // Then MissingArg error is returned with the two remaining args.
159        assert_eq!(result.unwrap_err(), OdraError::VmError(VmError::MissingArg));
160    }
161
162    #[test]
163    fn test_call_valid_constructor() {
164        // Given an instance with a single no-arg constructor.
165        let name = String::from("init");
166        let instance = ContractContainer::setup_constructor(name.clone(), vec![]);
167
168        // When call a valid constructor with a single valid args,
169        let result = instance.call_constructor(name, &RuntimeArgs::new());
170
171        // Then the call succeeds.
172        assert!(result.is_ok());
173    }
174
175    #[test]
176    fn test_call_invalid_constructor() {
177        // Given an instance with no constructors.
178        let instance = ContractContainer::empty();
179
180        // When try to call some constructor.
181        let result = instance.call_constructor(String::from("c"), &RuntimeArgs::new());
182
183        // Then the call fails.
184        assert!(result.is_err());
185    }
186
187    #[test]
188    fn test_call_valid_constructor_with_missing_arg() {
189        // Given an instance with a single constructor with one arg named "first".
190        let name = String::from("init");
191        let instance = ContractContainer::setup_constructor(name.clone(), vec!["first"]);
192
193        // When call a valid constructor, but with no args.
194        let result = instance.call_constructor(name, &RuntimeArgs::new());
195
196        // Then MissingArgs error is returned.
197        assert_eq!(result.unwrap_err(), OdraError::VmError(VmError::MissingArg));
198    }
199
200    #[test]
201    fn test_call_constructor_in_invalid_context() {
202        // Given an instance with a single constructor.
203        let name = String::from("init");
204        let instance = ContractContainer::setup_constructor(name.clone(), vec![]);
205
206        // When call the constructor in the entrypoint context.
207        let result = instance.call(name, &RuntimeArgs::new());
208
209        // Then the call fails.
210        assert!(result.is_err());
211    }
212
213    impl ContractContainer {
214        fn empty() -> Self {
215            Self {
216                name: String::from("contract"),
217                entrypoints: BTreeMap::new(),
218                constructors: BTreeMap::new()
219            }
220        }
221
222        fn setup_entrypoint(ep_name: String, args: Vec<&str>) -> Self {
223            let call: EntrypointCall = |_, _| vec![1, 2, 3];
224            let args: EntrypointArgs = args.iter().map(|arg| arg.to_string()).collect();
225
226            let mut entrypoints = BTreeMap::new();
227            entrypoints.insert(ep_name, (args, call));
228
229            Self {
230                name: String::from("contract"),
231                entrypoints,
232                constructors: BTreeMap::new()
233            }
234        }
235
236        fn setup_constructor(ep_name: String, args: Vec<&str>) -> Self {
237            let call: EntrypointCall = |_, _| vec![1, 2, 3];
238            let args: EntrypointArgs = args.iter().map(|arg| arg.to_string()).collect();
239
240            let mut constructors = BTreeMap::new();
241            constructors.insert(ep_name, (args, call));
242
243            Self {
244                name: String::from("contract"),
245                entrypoints: BTreeMap::new(),
246                constructors
247            }
248        }
249    }
250}