odra_core/
contract_container.rs

1use crate::entry_point_callback::EntryPointsCaller;
2use crate::prelude::*;
3use crate::{CallDef, VmError};
4use casper_types::bytesrepr::Bytes;
5use casper_types::U512;
6
7/// A wrapper struct for a EntryPointsCaller that is a layer of abstraction between the host and the entry points caller.
8///
9/// The container validates a contract call definition before calling the entry point.
10#[derive(Clone)]
11pub struct ContractContainer {
12    entry_points_caller: EntryPointsCaller,
13    ctx: ExecutionContext
14}
15
16impl ContractContainer {
17    /// Creates a new instance of `ContractContainer`.
18    pub fn new(entry_points_caller: EntryPointsCaller) -> Self {
19        Self {
20            entry_points_caller,
21            ctx: ExecutionContext::Installation
22        }
23    }
24
25    pub(crate) fn post_install(&mut self) {
26        self.ctx = ExecutionContext::Runtime;
27    }
28
29    /// Calls the entry point with the given call definition.
30    pub fn call(&self, call_def: CallDef) -> OdraResult<Bytes> {
31        // find the entry point
32        let ep = self
33            .entry_points_caller
34            .entry_points()
35            .iter()
36            .find(|ep| ep.name == call_def.entry_point())
37            .ok_or_else(|| {
38                OdraError::VmError(VmError::NoSuchMethod(call_def.entry_point().to_owned()))
39            })?;
40        if !ep.is_payable && call_def.amount() > U512::zero() {
41            return Err(OdraError::ExecutionError(ExecutionError::NonPayable));
42        }
43        if ep.name == "init" && self.ctx == ExecutionContext::Runtime {
44            return Err(OdraError::VmError(VmError::InvalidContext));
45        }
46        self.entry_points_caller.call(call_def)
47    }
48}
49
50#[derive(PartialEq, Eq, Clone, Copy)]
51enum ExecutionContext {
52    Installation,
53    Runtime
54}
55
56#[cfg(test)]
57mod tests {
58    use super::{ContractContainer, ExecutionContext};
59    use crate::contract_context::MockContractContext;
60    use crate::entry_point_callback::{Argument, EntryPoint, EntryPointsCaller};
61    use crate::host::{HostEnv, MockHostContext};
62    use crate::{casper_types::RuntimeArgs, VmError};
63    use crate::{prelude::*, CallDef, ContractEnv};
64
65    const TEST_ENTRYPOINT: &str = "ep";
66
67    #[test]
68    fn test_call_wrong_entrypoint() {
69        // Given an instance with no entrypoints.
70        let instance = ContractContainer::empty();
71
72        // When call some entrypoint.
73        let call_def = CallDef::new(TEST_ENTRYPOINT, false, RuntimeArgs::new());
74        let result = instance.call(call_def);
75
76        // Then an error occurs.
77        assert!(result.is_err());
78    }
79
80    #[test]
81    fn test_call_valid_entrypoint() {
82        // Given an instance with a single no-args entrypoint.
83        let instance = ContractContainer::with_entrypoint(vec![]);
84
85        // When call the registered entrypoint.
86        let call_def = CallDef::new(TEST_ENTRYPOINT, false, RuntimeArgs::new());
87        let result = instance.call(call_def);
88
89        // Then teh call succeeds.
90        assert!(result.is_ok());
91    }
92
93    impl ContractContainer {
94        fn empty() -> Self {
95            let ctx = Rc::new(RefCell::new(MockHostContext::new()));
96            let env = HostEnv::new(ctx);
97            let entry_points_caller = EntryPointsCaller::new(env, vec![], |_, call_def| {
98                Err(OdraError::VmError(VmError::NoSuchMethod(
99                    call_def.entry_point().to_string()
100                )))
101            });
102            Self {
103                entry_points_caller,
104                ctx: ExecutionContext::Installation
105            }
106        }
107
108        fn with_entrypoint(args: Vec<&str>) -> Self {
109            let entry_points = vec![EntryPoint::new(
110                String::from(TEST_ENTRYPOINT),
111                args.iter()
112                    .map(|name| Argument::new::<u32>(String::from(*name)))
113                    .collect()
114            )];
115            let mut ctx = MockHostContext::new();
116            ctx.expect_contract_env().returning(|| {
117                ContractEnv::new(0, Rc::new(RefCell::new(MockContractContext::new())))
118            });
119            let env = HostEnv::new(Rc::new(RefCell::new(ctx)));
120
121            let entry_points_caller = EntryPointsCaller::new(env, entry_points, |_, call_def| {
122                if call_def.entry_point() == TEST_ENTRYPOINT {
123                    Ok(vec![1, 2, 3].into())
124                } else {
125                    Err(OdraError::VmError(VmError::NoSuchMethod(
126                        call_def.entry_point().to_string()
127                    )))
128                }
129            });
130
131            Self {
132                entry_points_caller,
133                ctx: ExecutionContext::Installation
134            }
135        }
136    }
137}