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