odra_core/
args.rs

1//! This module provides types and traits for working with entrypoint arguments.
2
3use crate::{contract_def::Argument, prelude::*, ContractEnv};
4use casper_types::{
5    bytesrepr::{FromBytes, ToBytes},
6    CLType, CLTyped, Parameter, RuntimeArgs
7};
8
9/// A type that represents an entrypoint arg that may or may not be present.
10#[derive(Default, Debug, Clone)]
11pub enum Maybe<T> {
12    /// A value is present.
13    Some(T),
14    /// No value is present.
15    #[default]
16    None
17}
18
19impl<T> Maybe<T> {
20    /// Returns `true` if the value is present.
21    pub fn is_some(&self) -> bool {
22        matches!(self, Maybe::Some(_))
23    }
24
25    /// Returns `true` if the value is not present.
26    pub fn is_none(&self) -> bool {
27        matches!(self, Maybe::None)
28    }
29
30    /// Unwraps the value.
31    /// If the value is not present, the contract reverts with an `ExecutionError::UnwrapError`.
32    pub fn unwrap(self, env: &ContractEnv) -> T {
33        match self {
34            Maybe::Some(value) => value,
35            Maybe::None => env.revert(ExecutionError::UnwrapError)
36        }
37    }
38
39    /// Unwraps the value or returns the default value.
40    pub fn unwrap_or(self, default: T) -> T {
41        match self {
42            Maybe::Some(value) => value,
43            Maybe::None => default
44        }
45    }
46}
47
48impl<T: Default> Maybe<T> {
49    /// Unwraps the value or returns the default value.
50    pub fn unwrap_or_default(self) -> T {
51        match self {
52            Maybe::Some(value) => value,
53            Maybe::None => T::default()
54        }
55    }
56}
57
58impl<T: ToBytes> ToBytes for Maybe<T> {
59    fn to_bytes(&self) -> Result<Vec<u8>, casper_types::bytesrepr::Error> {
60        match self {
61            Maybe::Some(value) => value.to_bytes(),
62            Maybe::None => Ok(Vec::new())
63        }
64    }
65
66    fn serialized_length(&self) -> usize {
67        match self {
68            Maybe::Some(value) => value.serialized_length(),
69            Maybe::None => 0
70        }
71    }
72}
73
74impl<T: FromBytes> FromBytes for Maybe<T> {
75    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), casper_types::bytesrepr::Error> {
76        let res = T::from_bytes(bytes);
77        if let Ok((value, rem)) = res {
78            Ok((Maybe::Some(value), rem))
79        } else {
80            Ok((Maybe::None, bytes))
81        }
82    }
83
84    fn from_vec(bytes: Vec<u8>) -> Result<(Self, Vec<u8>), casper_types::bytesrepr::Error> {
85        Self::from_bytes(bytes.as_slice()).map(|(x, remainder)| (x, Vec::from(remainder)))
86    }
87}
88
89/// A trait for types that can be used as entrypoint arguments.
90pub trait EntrypointArgument: Sized {
91    /// Returns `true` if the argument is required.
92    fn is_required() -> bool;
93    /// Returns the CLType of the argument.
94    fn cl_type() -> CLType;
95    /// Inserts the argument into the runtime args.
96    fn insert_runtime_arg(self, name: &str, args: &mut RuntimeArgs);
97    /// Unwraps the argument from an Option.
98    fn unwrap(value: Option<Self>, env: &ContractEnv) -> Self;
99}
100
101impl<T: CLTyped + ToBytes> EntrypointArgument for Maybe<T> {
102    fn is_required() -> bool {
103        false
104    }
105
106    fn cl_type() -> CLType {
107        T::cl_type()
108    }
109
110    fn insert_runtime_arg(self, name: &str, args: &mut RuntimeArgs) {
111        if let Maybe::Some(v) = self {
112            let _ = args.insert(name, v);
113        }
114    }
115
116    fn unwrap(value: Option<Self>, _env: &ContractEnv) -> Self {
117        match value {
118            Some(v) => v,
119            None => Maybe::None
120        }
121    }
122}
123
124impl<T: CLTyped + ToBytes> EntrypointArgument for T {
125    fn is_required() -> bool {
126        true
127    }
128
129    fn cl_type() -> CLType {
130        T::cl_type()
131    }
132
133    fn insert_runtime_arg(self, name: &str, args: &mut RuntimeArgs) {
134        let _ = args.insert(name, self);
135    }
136
137    fn unwrap(value: Option<Self>, env: &ContractEnv) -> Self {
138        match value {
139            Some(v) => v,
140            None => env.revert(ExecutionError::UnwrapError)
141        }
142    }
143}
144
145/// A type representing arguments for batch upgrading child contracts.
146pub struct BatchUpgradeArgs<T: Into<casper_types::RuntimeArgs>>(BTreeMap<String, T>);
147
148impl<T: Into<casper_types::RuntimeArgs>> From<BTreeMap<String, T>> for BatchUpgradeArgs<T> {
149    fn from(val: BTreeMap<String, T>) -> Self {
150        BatchUpgradeArgs(val)
151    }
152}
153
154impl<T: Into<casper_types::RuntimeArgs>> EntrypointArgument for BatchUpgradeArgs<T> {
155    fn is_required() -> bool {
156        true
157    }
158
159    fn cl_type() -> CLType {
160        CLType::Map {
161            key: Box::new(CLType::String),
162            value: Box::new(CLType::List(Box::new(CLType::U8)))
163        }
164    }
165
166    fn insert_runtime_arg(self, name: &str, args: &mut RuntimeArgs) {
167        let mut args_map: BTreeMap<String, casper_types::bytesrepr::Bytes> = Default::default();
168        for (contract, v) in self.0 {
169            let rt: RuntimeArgs = v.into();
170            let bytes = ToBytes::to_bytes(&rt).unwrap();
171            args_map.insert(
172                contract.to_string(),
173                casper_types::bytesrepr::Bytes::from(bytes)
174            );
175        }
176        let _ = args.insert(name, args_map);
177    }
178
179    fn unwrap(value: Option<Self>, env: &ContractEnv) -> Self {
180        match value {
181            Some(v) => v,
182            None => env.revert(ExecutionError::UnwrapError)
183        }
184    }
185}
186
187/// Returns a Casper entrypoint argument representation.
188/// If the parameter is not required, it returns `None`.
189pub fn parameter<T: EntrypointArgument>(name: &str) -> Option<Parameter> {
190    match T::is_required() {
191        true => Some(Parameter::new(name, T::cl_type())),
192        false => None
193    }
194}
195
196/// Returns an Odra's entrypoint argument representation.
197pub fn odra_argument<T: EntrypointArgument>(name: &str) -> Argument {
198    Argument {
199        name: name.to_string(),
200        ty: T::cl_type(),
201        is_ref: false,
202        is_slice: false,
203        is_required: T::is_required()
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210    use crate::contract_context::MockContractContext;
211    use casper_types::U256;
212
213    #[test]
214    fn test_maybe() {
215        let some = Maybe::Some(1);
216        let none: Maybe<u32> = Maybe::None;
217
218        let ctx = MockContractContext::new();
219        let env = ContractEnv::new(0, Rc::new(RefCell::new(ctx)));
220
221        assert!(some.is_some());
222        assert!(!some.is_none());
223        assert_eq!(some.clone().unwrap(&env), 1);
224        assert_eq!(some.unwrap_or_default(), 1);
225
226        assert!(!none.is_some());
227        assert!(none.is_none());
228        assert_eq!(none.unwrap_or_default(), 0);
229    }
230
231    #[test]
232    #[should_panic(expected = "revert")]
233    fn unwrap_on_none() {
234        let none: Maybe<u32> = Maybe::None;
235        let mut ctx = MockContractContext::new();
236        ctx.expect_revert().returning(|_| panic!("revert"));
237        let env = ContractEnv::new(0, Rc::new(RefCell::new(ctx)));
238
239        none.unwrap(&env);
240    }
241
242    #[test]
243    fn test_into_args() {
244        let args = [
245            odra_argument::<Maybe<u32>>("arg1"),
246            odra_argument::<U256>("arg2"),
247            odra_argument::<Option<String>>("arg3")
248        ];
249
250        assert_eq!(args.len(), 3);
251    }
252
253    #[test]
254    fn test_into_casper_parameters() {
255        let params = [
256            parameter::<Maybe<u32>>("arg1"),
257            parameter::<Option<u32>>("arg2"),
258            parameter::<Maybe<Option<u32>>>("arg3"),
259            parameter::<Address>("arg4")
260        ]
261        .into_iter()
262        .flatten()
263        .collect::<Vec<_>>();
264
265        assert_eq!(params.len(), 2);
266    }
267}