casper_types/
runtime_args.rs

1//! Home of RuntimeArgs for calling contracts
2
3// TODO - remove once schemars stops causing warning.
4#![allow(clippy::field_reassign_with_default)]
5
6use alloc::{collections::BTreeMap, string::String, vec::Vec};
7
8#[cfg(feature = "datasize")]
9use datasize::DataSize;
10#[cfg(feature = "json-schema")]
11use schemars::JsonSchema;
12use serde::{Deserialize, Serialize};
13
14use crate::{
15    bytesrepr::{self, Error, FromBytes, ToBytes},
16    CLType, CLTyped, CLValue, CLValueError, U512,
17};
18/// Named arguments to a contract.
19#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize, Debug)]
20#[cfg_attr(feature = "datasize", derive(DataSize))]
21#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
22pub struct NamedArg(String, CLValue);
23
24impl NamedArg {
25    /// Returns a new `NamedArg`.
26    pub fn new(name: String, value: CLValue) -> Self {
27        NamedArg(name, value)
28    }
29
30    /// Returns the name of the named arg.
31    pub fn name(&self) -> &str {
32        &self.0
33    }
34
35    /// Returns the value of the named arg.
36    pub fn cl_value(&self) -> &CLValue {
37        &self.1
38    }
39
40    /// Returns a mutable reference to the value of the named arg.
41    pub fn cl_value_mut(&mut self) -> &mut CLValue {
42        &mut self.1
43    }
44}
45
46impl From<(String, CLValue)> for NamedArg {
47    fn from((name, value): (String, CLValue)) -> NamedArg {
48        NamedArg(name, value)
49    }
50}
51
52impl ToBytes for NamedArg {
53    fn to_bytes(&self) -> Result<Vec<u8>, Error> {
54        let mut result = bytesrepr::allocate_buffer(self)?;
55        result.append(&mut self.0.to_bytes()?);
56        result.append(&mut self.1.to_bytes()?);
57        Ok(result)
58    }
59
60    fn serialized_length(&self) -> usize {
61        self.0.serialized_length() + self.1.serialized_length()
62    }
63}
64
65impl FromBytes for NamedArg {
66    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> {
67        let (name, remainder) = String::from_bytes(bytes)?;
68        let (cl_value, remainder) = CLValue::from_bytes(remainder)?;
69        Ok((NamedArg(name, cl_value), remainder))
70    }
71}
72
73/// Represents a collection of arguments passed to a smart contract.
74#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize, Debug, Default)]
75#[cfg_attr(feature = "datasize", derive(DataSize))]
76#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
77pub struct RuntimeArgs(Vec<NamedArg>);
78
79impl RuntimeArgs {
80    /// Create an empty [`RuntimeArgs`] instance.
81    pub fn new() -> RuntimeArgs {
82        RuntimeArgs::default()
83    }
84
85    /// A wrapper that lets you easily and safely create runtime arguments.
86    ///
87    /// This method is useful when you have to construct a [`RuntimeArgs`] with multiple entries,
88    /// but error handling at given call site would require to have a match statement for each
89    /// [`RuntimeArgs::insert`] call. With this method you can use ? operator inside the closure and
90    /// then handle single result. When `try_block` will be stabilized this method could be
91    /// deprecated in favor of using those blocks.
92    pub fn try_new<F>(func: F) -> Result<RuntimeArgs, CLValueError>
93    where
94        F: FnOnce(&mut RuntimeArgs) -> Result<(), CLValueError>,
95    {
96        let mut runtime_args = RuntimeArgs::new();
97        func(&mut runtime_args)?;
98        Ok(runtime_args)
99    }
100
101    /// Gets an argument by its name.
102    pub fn get(&self, name: &str) -> Option<&CLValue> {
103        self.0.iter().find_map(|NamedArg(named_name, named_value)| {
104            if named_name == name {
105                Some(named_value)
106            } else {
107                None
108            }
109        })
110    }
111
112    /// Gets the length of the collection.
113    pub fn len(&self) -> usize {
114        self.0.len()
115    }
116
117    /// Returns `true` if the collection of arguments is empty.
118    pub fn is_empty(&self) -> bool {
119        self.0.is_empty()
120    }
121
122    /// Inserts a new named argument into the collection.
123    pub fn insert<K, V>(&mut self, key: K, value: V) -> Result<(), CLValueError>
124    where
125        K: Into<String>,
126        V: CLTyped + ToBytes,
127    {
128        let cl_value = CLValue::from_t(value)?;
129        self.0.push(NamedArg(key.into(), cl_value));
130        Ok(())
131    }
132
133    /// Inserts a new named argument into the collection.
134    pub fn insert_cl_value<K>(&mut self, key: K, cl_value: CLValue)
135    where
136        K: Into<String>,
137    {
138        self.0.push(NamedArg(key.into(), cl_value));
139    }
140
141    /// Returns all the values of the named args.
142    pub fn to_values(&self) -> Vec<&CLValue> {
143        self.0.iter().map(|NamedArg(_name, value)| value).collect()
144    }
145
146    /// Returns an iterator of references over all arguments in insertion order.
147    pub fn named_args(&self) -> impl Iterator<Item = &NamedArg> {
148        self.0.iter()
149    }
150
151    /// Returns an iterator of mutable references over all arguments in insertion order.
152    pub fn named_args_mut(&mut self) -> impl Iterator<Item = &mut NamedArg> {
153        self.0.iter_mut()
154    }
155
156    /// Returns the numeric value of `name` arg from the runtime arguments or defaults to
157    /// 0 if that arg doesn't exist or is not an integer type.
158    ///
159    /// Supported [`CLType`]s for numeric conversions are U64, and U512.
160    ///
161    /// Returns an error if parsing the arg fails.
162    pub fn try_get_number(&self, name: &str) -> Result<U512, CLValueError> {
163        let amount_arg = match self.get(name) {
164            None => return Ok(U512::zero()),
165            Some(arg) => arg,
166        };
167        match amount_arg.cl_type() {
168            CLType::U512 => amount_arg.clone().into_t::<U512>(),
169            CLType::U64 => amount_arg.clone().into_t::<u64>().map(U512::from),
170            _ => Ok(U512::zero()),
171        }
172    }
173}
174
175impl From<Vec<NamedArg>> for RuntimeArgs {
176    fn from(values: Vec<NamedArg>) -> Self {
177        RuntimeArgs(values)
178    }
179}
180
181impl From<BTreeMap<String, CLValue>> for RuntimeArgs {
182    fn from(cl_values: BTreeMap<String, CLValue>) -> RuntimeArgs {
183        RuntimeArgs(cl_values.into_iter().map(NamedArg::from).collect())
184    }
185}
186
187impl From<RuntimeArgs> for BTreeMap<String, CLValue> {
188    fn from(args: RuntimeArgs) -> BTreeMap<String, CLValue> {
189        let mut map = BTreeMap::new();
190        for named in args.0 {
191            map.insert(named.0, named.1);
192        }
193        map
194    }
195}
196
197impl ToBytes for RuntimeArgs {
198    fn to_bytes(&self) -> Result<Vec<u8>, Error> {
199        self.0.to_bytes()
200    }
201
202    fn serialized_length(&self) -> usize {
203        self.0.serialized_length()
204    }
205}
206
207impl FromBytes for RuntimeArgs {
208    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> {
209        let (args, remainder) = Vec::<NamedArg>::from_bytes(bytes)?;
210        Ok((RuntimeArgs(args), remainder))
211    }
212}
213
214/// Macro that makes it easier to construct named arguments.
215///
216/// NOTE: This macro does not propagate possible errors that could occur while creating a
217/// [`crate::CLValue`]. For such cases creating [`RuntimeArgs`] manually is recommended.
218///
219/// # Example usage
220/// ```
221/// use casper_types::{RuntimeArgs, runtime_args};
222/// let _named_args = runtime_args! {
223///   "foo" => 42,
224///   "bar" => "Hello, world!"
225/// };
226/// ```
227#[macro_export]
228macro_rules! runtime_args {
229    () => (RuntimeArgs::new());
230    ( $($key:expr => $value:expr,)+ ) => (runtime_args!($($key => $value),+));
231    ( $($key:expr => $value:expr),* ) => {
232        {
233            let mut named_args = RuntimeArgs::new();
234            $(
235                named_args.insert($key, $value).unwrap();
236            )*
237            named_args
238        }
239    };
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245
246    const ARG_AMOUNT: &str = "amount";
247
248    #[test]
249    fn test_runtime_args() {
250        let arg1 = CLValue::from_t(1).unwrap();
251        let arg2 = CLValue::from_t("Foo").unwrap();
252        let arg3 = CLValue::from_t(Some(1)).unwrap();
253        let args = {
254            let mut map = BTreeMap::new();
255            map.insert("bar".into(), arg2.clone());
256            map.insert("foo".into(), arg1.clone());
257            map.insert("qwer".into(), arg3.clone());
258            map
259        };
260        let runtime_args = RuntimeArgs::from(args);
261        assert_eq!(runtime_args.get("qwer"), Some(&arg3));
262        assert_eq!(runtime_args.get("foo"), Some(&arg1));
263        assert_eq!(runtime_args.get("bar"), Some(&arg2));
264        assert_eq!(runtime_args.get("aaa"), None);
265
266        // Ensure macro works
267
268        let runtime_args_2 = runtime_args! {
269            "bar" => "Foo",
270            "foo" => 1i32,
271            "qwer" => Some(1i32),
272        };
273        assert_eq!(runtime_args, runtime_args_2);
274    }
275
276    #[test]
277    fn empty_macro() {
278        assert_eq!(runtime_args! {}, RuntimeArgs::new());
279    }
280
281    #[test]
282    fn btreemap_compat() {
283        // This test assumes same serialization format as BTreeMap
284        let runtime_args_1 = runtime_args! {
285            "bar" => "Foo",
286            "foo" => 1i32,
287            "qwer" => Some(1i32),
288        };
289        let tagless = runtime_args_1.to_bytes().unwrap().to_vec();
290
291        let mut runtime_args_2 = BTreeMap::new();
292        runtime_args_2.insert(String::from("bar"), CLValue::from_t("Foo").unwrap());
293        runtime_args_2.insert(String::from("foo"), CLValue::from_t(1i32).unwrap());
294        runtime_args_2.insert(String::from("qwer"), CLValue::from_t(Some(1i32)).unwrap());
295
296        assert_eq!(tagless, runtime_args_2.to_bytes().unwrap());
297    }
298
299    #[test]
300    fn named_serialization_roundtrip() {
301        let args = runtime_args! {
302            "foo" => 1i32,
303        };
304        bytesrepr::test_serialization_roundtrip(&args);
305    }
306
307    #[test]
308    fn should_create_args_with() {
309        let res = RuntimeArgs::try_new(|runtime_args| {
310            runtime_args.insert(String::from("foo"), 123)?;
311            runtime_args.insert(String::from("bar"), 456)?;
312            Ok(())
313        });
314
315        let expected = runtime_args! {
316            "foo" => 123,
317            "bar" => 456,
318        };
319        assert!(matches!(res, Ok(args) if expected == args));
320    }
321
322    #[test]
323    fn try_get_number_should_work() {
324        let mut args = RuntimeArgs::new();
325        args.insert(ARG_AMOUNT, 0u64).expect("is ok");
326        assert_eq!(args.try_get_number(ARG_AMOUNT).unwrap(), U512::zero());
327
328        let mut args = RuntimeArgs::new();
329        args.insert(ARG_AMOUNT, U512::zero()).expect("is ok");
330        assert_eq!(args.try_get_number(ARG_AMOUNT).unwrap(), U512::zero());
331
332        let args = RuntimeArgs::new();
333        assert_eq!(args.try_get_number(ARG_AMOUNT).unwrap(), U512::zero());
334
335        let hundred = 100u64;
336
337        let mut args = RuntimeArgs::new();
338        let input = U512::from(hundred);
339        args.insert(ARG_AMOUNT, input).expect("is ok");
340        assert_eq!(args.try_get_number(ARG_AMOUNT).unwrap(), input);
341
342        let mut args = RuntimeArgs::new();
343        args.insert(ARG_AMOUNT, hundred).expect("is ok");
344        assert_eq!(
345            args.try_get_number(ARG_AMOUNT).unwrap(),
346            U512::from(hundred)
347        );
348    }
349
350    #[test]
351    fn try_get_number_should_return_zero_for_non_numeric_type() {
352        let mut args = RuntimeArgs::new();
353        args.insert(ARG_AMOUNT, "Non-numeric-string").unwrap();
354        assert_eq!(
355            args.try_get_number(ARG_AMOUNT).expect("should get amount"),
356            U512::zero()
357        );
358    }
359
360    #[test]
361    fn try_get_number_should_return_zero_if_amount_is_missing() {
362        let args = RuntimeArgs::new();
363        assert_eq!(
364            args.try_get_number(ARG_AMOUNT).expect("should get amount"),
365            U512::zero()
366        );
367    }
368}