mhome_script_api/
value.rs1use crate::ContractError;
2use serde::{Deserialize, Serialize};
3use std::collections::BTreeMap;
4
5#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
9#[serde(
10 tag = "type",
11 content = "value",
12 rename_all = "camelCase",
13 deny_unknown_fields
14)]
15pub enum ScriptValue {
16 Null,
17 Bool(bool),
18 Number(f64),
19 I64(#[serde(with = "signed")] i64),
20 U64(#[serde(with = "unsigned")] u64),
21 String(String),
22 Bytes(#[serde(with = "binary")] Vec<u8>),
23 List(Vec<ScriptValue>),
24 Object(BTreeMap<String, ScriptValue>),
25}
26
27macro_rules! decimal {
28 ($module:ident, $ty:ty) => {
29 mod $module {
30 use serde::{Deserialize, Deserializer, Serializer};
31 pub fn serialize<S: Serializer>(value: &$ty, serializer: S) -> Result<S::Ok, S::Error> {
32 serializer.serialize_str(&value.to_string())
33 }
34 pub fn deserialize<'de, D: Deserializer<'de>>(
35 deserializer: D,
36 ) -> Result<$ty, D::Error> {
37 let text = String::deserialize(deserializer)?;
38 let value: $ty = text.parse().map_err(serde::de::Error::custom)?;
39 if value.to_string() != text {
40 return Err(serde::de::Error::custom(
41 "integer must use canonical decimal text",
42 ));
43 }
44 Ok(value)
45 }
46 }
47 };
48}
49decimal!(signed, i64);
50decimal!(unsigned, u64);
51
52mod binary {
53 use base64::{engine::general_purpose::STANDARD, Engine};
54 use serde::{Deserialize, Deserializer, Serializer};
55 pub fn serialize<S: Serializer>(value: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
56 serializer.serialize_str(&STANDARD.encode(value))
57 }
58 pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
59 STANDARD
60 .decode(String::deserialize(deserializer)?)
61 .map_err(serde::de::Error::custom)
62 }
63}
64
65#[derive(Clone, Copy, Debug)]
66pub struct ValueLimits {
67 pub bytes: usize,
68 pub nodes: usize,
69 pub depth: usize,
70}
71
72impl Default for ValueLimits {
73 fn default() -> Self {
74 Self {
75 bytes: 4 * 1024 * 1024,
76 nodes: 65_536,
77 depth: 64,
78 }
79 }
80}
81
82impl ScriptValue {
83 pub fn validate(&self, mut limits: ValueLimits) -> Result<(), ContractError> {
86 self.check(&mut limits, 0)
87 }
88
89 fn check(&self, limits: &mut ValueLimits, depth: usize) -> Result<(), ContractError> {
90 if depth > limits.depth || limits.nodes == 0 {
91 return Err(ContractError(
92 "script value exceeds node/depth limit".into(),
93 ));
94 }
95 limits.nodes -= 1;
96 let size = match self {
97 Self::String(value) => value.len().saturating_add(8),
98 Self::Bytes(value) => value.len().saturating_add(8),
99 Self::Number(value) if !value.is_finite() => {
100 return Err(ContractError("script number must be finite".into()));
101 }
102 _ => 8,
103 };
104 limits.bytes = limits
105 .bytes
106 .checked_sub(size)
107 .ok_or_else(|| ContractError("script value exceeds byte limit".into()))?;
108 match self {
109 Self::List(values) => {
110 for value in values {
111 value.check(limits, depth + 1)?;
112 }
113 }
114 Self::Object(values) => {
115 for (name, value) in values {
116 limits.bytes = limits
117 .bytes
118 .checked_sub(name.len())
119 .ok_or_else(|| ContractError("script value exceeds byte limit".into()))?;
120 value.check(limits, depth + 1)?;
121 }
122 }
123 _ => {}
124 }
125 Ok(())
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn integer_wire_values_never_use_json_numbers_or_accept_rounding() {
135 for value in [
136 ScriptValue::I64(i64::MIN),
137 ScriptValue::I64(i64::MAX),
138 ScriptValue::U64(u64::MAX),
139 ] {
140 let json = serde_json::to_value(&value).unwrap();
141 assert!(json["value"].is_string());
142 assert_eq!(serde_json::from_value::<ScriptValue>(json).unwrap(), value);
143 }
144 for invalid in [
145 r#"{"type":"u64","value":18446744073709551615}"#,
146 r#"{"type":"i64","value":"01"}"#,
147 r#"{"type":"i64","value":"+1"}"#,
148 r#"{"type":"u64","value":"18446744073709551616"}"#,
149 ] {
150 assert!(serde_json::from_str::<ScriptValue>(invalid).is_err());
151 }
152 }
153
154 #[test]
155 fn values_have_aggregate_bounds_and_reject_nonfinite_numbers() {
156 let value = ScriptValue::List(vec![ScriptValue::String("abcdef".into()); 3]);
157 assert!(value
158 .validate(ValueLimits {
159 bytes: 25,
160 ..ValueLimits::default()
161 })
162 .is_err());
163 assert!(value
164 .validate(ValueLimits {
165 nodes: 3,
166 ..ValueLimits::default()
167 })
168 .is_err());
169 assert!(value
170 .validate(ValueLimits {
171 depth: 0,
172 ..ValueLimits::default()
173 })
174 .is_err());
175 assert!(value.validate(ValueLimits::default()).is_ok());
176 assert!(ScriptValue::Number(f64::NAN)
177 .validate(ValueLimits::default())
178 .is_err());
179 }
180}