casper_types/transaction/
runtime_args.rs

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