defuse_near_utils/
promise.rs

1use near_sdk::{Promise, env, json_types::U128, serde::de::DeserializeOwned, serde_json};
2
3pub trait PromiseExt: Sized {
4    fn and_maybe(self, p: Option<Promise>) -> Promise;
5}
6
7impl PromiseExt for Promise {
8    #[inline]
9    fn and_maybe(self, p: Option<Promise>) -> Promise {
10        if let Some(p) = p { self.and(p) } else { self }
11    }
12}
13
14pub type PromiseResult<T> = Result<T, near_sdk::PromiseError>;
15pub type PromiseJsonResult<T> = PromiseResult<Result<T, serde_json::Error>>;
16
17/// NOTE: The NEAR runtime limits the Wasm operand stack height to 16,384 entries
18/// (`max_stack_height` genesis parameter). Each recursive call to
19/// [`MaxJsonLength::max_json_length_at`] consumes stack entries for params,
20/// locals, operand depth, and gas metering overhead — the exact amount
21/// depends on the monomorphized `Args` type. Since different trait
22/// implementations produce different frame sizes, we use a conservative
23/// limit that stays well within budget across all reasonable type nesting.
24/// See the NEAR specification on contract preparation for details:
25/// <https://nomicon.io/RuntimeSpec/Preparation>
26pub const MAX_JSON_LENGTH_RECURSION_LIMIT: usize = 32;
27
28pub trait MaxJsonLength: DeserializeOwned {
29    type Args;
30
31    fn max_json_length_root(args: Self::Args) -> usize {
32        Self::max_json_length_at(0, args)
33    }
34
35    fn max_json_length_at(depth: usize, args: Self::Args) -> usize;
36}
37
38#[inline]
39pub fn promise_result_checked_json_with_args<T: MaxJsonLength>(
40    result_idx: u64,
41    args: T::Args,
42) -> PromiseJsonResult<T> {
43    let value = env::promise_result_checked(result_idx, T::max_json_length_root(args))?;
44    Ok(serde_json::from_slice::<T>(&value))
45}
46
47#[inline]
48pub fn promise_result_checked_json<T: MaxJsonLength<Args = ()>>(
49    result_idx: u64,
50) -> PromiseJsonResult<T> {
51    promise_result_checked_json_with_args::<T>(result_idx, ())
52}
53
54#[inline]
55pub fn promise_result_checked_json_with_len<T: MaxJsonLength<Args = (usize, ())>>(
56    result_idx: u64,
57    length: usize,
58) -> PromiseJsonResult<T> {
59    let max_len = T::max_json_length_root((length, ()));
60    let value = env::promise_result_checked(result_idx, max_len)?;
61    Ok(serde_json::from_slice::<T>(&value))
62}
63
64/// Returns `Ok(())` if the promise at `result_idx` succeeded with an empty result.
65/// This is the expected outcome for void-returning cross-contract calls
66/// (e.g. `ft_transfer`, `nft_transfer`, `mt_batch_transfer`).
67#[inline]
68pub fn promise_result_checked_void(result_idx: u64) -> PromiseResult<()> {
69    let data = env::promise_result_checked(result_idx, 0)?;
70    debug_assert!(data.is_empty());
71    Ok(())
72}
73
74impl MaxJsonLength for bool {
75    type Args = ();
76
77    fn max_json_length_at(_depth: usize, _args: ()) -> usize {
78        " false ".len()
79    }
80}
81
82impl MaxJsonLength for U128 {
83    type Args = ();
84
85    fn max_json_length_at(_depth: usize, _args: ()) -> usize {
86        " \"\" ".len() + "+340282366920938463463374607431768211455".len()
87    }
88}
89
90impl<T> MaxJsonLength for Vec<T>
91where
92    T: MaxJsonLength,
93{
94    type Args = (usize, T::Args);
95
96    fn max_json_length_at(depth: usize, (length, item_args): (usize, T::Args)) -> usize {
97        const INDENT_STEP: usize = "        ".len();
98
99        if depth >= MAX_JSON_LENGTH_RECURSION_LIMIT {
100            return usize::MAX;
101        }
102
103        let ident = INDENT_STEP.saturating_mul(depth);
104        let item_indent = ident.saturating_add(INDENT_STEP);
105
106        item_indent
107            .saturating_add(T::max_json_length_at(depth + 1, item_args))
108            .saturating_add(",\n".len())
109            .saturating_mul(length)
110            .saturating_add(" [\n] ".len() + ident)
111    }
112}
113
114impl<T, const N: usize> MaxJsonLength for [T; N]
115where
116    T: MaxJsonLength,
117    Self: DeserializeOwned,
118{
119    type Args = T::Args;
120
121    fn max_json_length_at(depth: usize, args: Self::Args) -> usize {
122        <Vec<T>>::max_json_length_at(depth, (N, args))
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use near_sdk::json_types::U128;
129    use rstest::rstest;
130
131    use super::*;
132
133    #[test]
134    fn test_max_bool_json_len() {
135        let max_len = bool::max_json_length_root(());
136
137        let prettified_false = serde_json::to_string_pretty(&false).unwrap();
138        let prettified_true = serde_json::to_string_pretty(&true).unwrap();
139        assert!(prettified_false.len() <= max_len);
140        assert!(prettified_true.len() <= max_len);
141
142        let compact_false = serde_json::to_string(&false).unwrap();
143        let compact_true = serde_json::to_string(&true).unwrap();
144        assert!(compact_false.len() <= max_len);
145        assert!(compact_true.len() <= max_len);
146    }
147
148    #[test]
149    fn test_max_u128_json_len() {
150        let max_len = U128::max_json_length_root(());
151
152        let max_val = U128(u128::MAX);
153        let prettified = serde_json::to_string_pretty(&max_val).unwrap();
154        assert!(prettified.len() <= max_len);
155
156        let compact = serde_json::to_string(&max_val).unwrap();
157        assert!(compact.len() <= max_len);
158    }
159
160    #[rstest]
161    #[case::len_0(0)]
162    #[case::len_1(1)]
163    #[case::len_2(2)]
164    #[case::len_5(5)]
165    #[case::len_10(10)]
166    #[case::len_100(100)]
167    fn test_max_vec_u128_json_len(#[case] count: usize) {
168        let max_len = Vec::<U128>::max_json_length_root((count, ()));
169
170        let vec: Vec<U128> = vec![U128(u128::MAX); count];
171        let prettified = serde_json::to_string_pretty(&vec).unwrap();
172        assert!(prettified.len() <= max_len);
173
174        let compact = serde_json::to_string(&vec).unwrap();
175        assert!(compact.len() <= max_len);
176    }
177
178    #[rstest]
179    #[case::outer_1_inner_3(1, 3)]
180    #[case::outer_3_inner_5(3, 5)]
181    #[case::outer_5_inner_10(5, 10)]
182    fn test_max_nested_vec_u128_json_len(#[case] outer: usize, #[case] inner: usize) {
183        let max_len = Vec::<Vec<U128>>::max_json_length_at(0, (outer, (inner, ())));
184
185        let vec: Vec<Vec<U128>> = vec![vec![U128(u128::MAX); inner]; outer];
186        let prettified = serde_json::to_string_pretty(&vec).unwrap();
187        assert!(prettified.len() <= max_len);
188
189        let compact = serde_json::to_string(&vec).unwrap();
190        assert!(compact.len() <= max_len);
191    }
192
193    #[test]
194    fn test_max_array_u128_json_len() {
195        let max_len = <[U128; 5]>::max_json_length_root(());
196
197        let arr = [U128(u128::MAX); 5];
198        let prettified = serde_json::to_string_pretty(&arr).unwrap();
199        assert!(prettified.len() <= max_len);
200
201        let compact = serde_json::to_string(&arr).unwrap();
202        assert!(compact.len() <= max_len);
203    }
204
205    #[test]
206    fn test_depth_exceeds_limit_returns_max() {
207        assert_eq!(
208            Vec::<U128>::max_json_length_at(MAX_JSON_LENGTH_RECURSION_LIMIT + 1, (10, ())),
209            usize::MAX,
210        );
211    }
212}