soroban_env_host/host/
comparison.rs

1use core::cmp::{min, Ordering};
2
3use crate::{
4    budget::{AsBudget, Budget, DepthLimiter},
5    host_object::HostObject,
6    storage::Storage,
7    xdr::{
8        AccountId, ContractCostType, ContractDataDurability, ContractExecutable,
9        CreateContractArgs, CreateContractArgsV2, Duration, Hash, Int128Parts, Int256Parts,
10        LedgerKey, LedgerKeyAccount, LedgerKeyContractCode, LedgerKeyContractData,
11        LedgerKeyTrustLine, PublicKey, ScAddress, ScContractInstance, ScError, ScErrorCode,
12        ScErrorType, ScMap, ScMapEntry, ScNonceKey, ScVal, ScVec, TimePoint, TrustLineAsset,
13        UInt128Parts, UInt256Parts, Uint256,
14    },
15    Compare, Host, HostError, SymbolStr, I256, U256,
16};
17
18use super::declared_size::DeclaredSizeForMetering;
19
20// We can't use core::mem::discriminant here because it returns an opaque type
21// that only supports Eq, not Ord, to reduce the possibility of an API breakage
22// based on reordering enums: https://github.com/rust-lang/rust/issues/51561
23//
24// Note that these must have the same order as the impl
25// of Ord for ScVal, re https://github.com/stellar/rs-soroban-env/issues/743
26fn host_obj_discriminant(ho: &HostObject) -> usize {
27    match ho {
28        HostObject::U64(_) => 0,
29        HostObject::I64(_) => 1,
30        HostObject::TimePoint(_) => 2,
31        HostObject::Duration(_) => 3,
32        HostObject::U128(_) => 4,
33        HostObject::I128(_) => 5,
34        HostObject::U256(_) => 6,
35        HostObject::I256(_) => 7,
36        HostObject::Bytes(_) => 8,
37        HostObject::String(_) => 9,
38        HostObject::Symbol(_) => 10,
39        HostObject::Vec(_) => 11,
40        HostObject::Map(_) => 12,
41        HostObject::Address(_) => 13,
42    }
43}
44
45impl Compare<HostObject> for Host {
46    type Error = HostError;
47
48    fn compare(&self, a: &HostObject, b: &HostObject) -> Result<Ordering, Self::Error> {
49        use HostObject::*;
50        let _span = tracy_span!("Compare<HostObject>");
51        // This is the depth limit checkpoint for `Val` comparison.
52        self.budget_cloned().with_limited_depth(|_| {
53            match (a, b) {
54                (U64(a), U64(b)) => self.as_budget().compare(a, b),
55                (I64(a), I64(b)) => self.as_budget().compare(a, b),
56                (TimePoint(a), TimePoint(b)) => self.as_budget().compare(a, b),
57                (Duration(a), Duration(b)) => self.as_budget().compare(a, b),
58                (U128(a), U128(b)) => self.as_budget().compare(a, b),
59                (I128(a), I128(b)) => self.as_budget().compare(a, b),
60                (U256(a), U256(b)) => self.as_budget().compare(a, b),
61                (I256(a), I256(b)) => self.as_budget().compare(a, b),
62                (Vec(a), Vec(b)) => self.compare(a, b),
63                (Map(a), Map(b)) => self.compare(a, b),
64                (Bytes(a), Bytes(b)) => self.as_budget().compare(&a.as_slice(), &b.as_slice()),
65                (String(a), String(b)) => self.as_budget().compare(&a.as_slice(), &b.as_slice()),
66                (Symbol(a), Symbol(b)) => self.as_budget().compare(&a.as_slice(), &b.as_slice()),
67                (Address(a), Address(b)) => self.as_budget().compare(a, b),
68
69                // List out at least one side of all the remaining cases here so
70                // we don't accidentally forget to update this when/if a new
71                // HostObject type is added.
72                (U64(_), _)
73                | (TimePoint(_), _)
74                | (Duration(_), _)
75                | (I64(_), _)
76                | (U128(_), _)
77                | (I128(_), _)
78                | (U256(_), _)
79                | (I256(_), _)
80                | (Vec(_), _)
81                | (Map(_), _)
82                | (Bytes(_), _)
83                | (String(_), _)
84                | (Symbol(_), _)
85                | (Address(_), _) => {
86                    let a = host_obj_discriminant(a);
87                    let b = host_obj_discriminant(b);
88                    Ok(a.cmp(&b))
89                }
90            }
91        })
92    }
93}
94
95impl Compare<&[u8]> for Budget {
96    type Error = HostError;
97
98    fn compare(&self, a: &&[u8], b: &&[u8]) -> Result<Ordering, Self::Error> {
99        self.charge(ContractCostType::MemCmp, Some(min(a.len(), b.len()) as u64))?;
100        Ok(a.cmp(b))
101    }
102}
103
104impl<const N: usize> Compare<[u8; N]> for Budget {
105    type Error = HostError;
106
107    fn compare(&self, a: &[u8; N], b: &[u8; N]) -> Result<Ordering, Self::Error> {
108        self.charge(ContractCostType::MemCmp, Some(min(a.len(), b.len()) as u64))?;
109        Ok(a.cmp(b))
110    }
111}
112
113// Apparently we can't do a blanket T:Ord impl because there are Ord derivations
114// that also go through &T and Option<T> that conflict with our impls above
115// (patches welcome from someone who understands trait-system workarounds
116// better). But we can list out any concrete Ord instances we want to support
117// here.
118//
119// We only do this for declared-size types, because we want to charge them a constant
120// based on their size declared in accordance with their type layout.
121
122struct FixedSizeOrdType<'a, T: Ord + DeclaredSizeForMetering>(&'a T);
123impl<T: Ord + DeclaredSizeForMetering> Compare<FixedSizeOrdType<'_, T>> for Budget {
124    type Error = HostError;
125    fn compare(
126        &self,
127        a: &FixedSizeOrdType<'_, T>,
128        b: &FixedSizeOrdType<'_, T>,
129    ) -> Result<Ordering, Self::Error> {
130        // Here we make a runtime assertion that the type's size is below its promised element
131        // size for budget charging.
132        debug_assert!(
133            std::mem::size_of::<T>() as u64 <= <T as DeclaredSizeForMetering>::DECLARED_SIZE,
134            "mem size: {}, declared: {}",
135            std::mem::size_of::<T>(),
136            <T as DeclaredSizeForMetering>::DECLARED_SIZE
137        );
138        self.charge(
139            ContractCostType::MemCmp,
140            Some(<T as DeclaredSizeForMetering>::DECLARED_SIZE),
141        )?;
142        Ok(a.0.cmp(b.0))
143    }
144}
145
146macro_rules! impl_compare_fixed_size_ord_type {
147    ($t:ty) => {
148        impl Compare<$t> for Budget {
149            type Error = HostError;
150            fn compare(&self, a: &$t, b: &$t) -> Result<Ordering, Self::Error> {
151                self.compare(&FixedSizeOrdType(a), &FixedSizeOrdType(b))
152            }
153        }
154        impl Compare<$t> for Host {
155            type Error = HostError;
156            fn compare(&self, a: &$t, b: &$t) -> Result<Ordering, Self::Error> {
157                self.as_budget().compare(a, b)
158            }
159        }
160    };
161}
162
163impl_compare_fixed_size_ord_type!(bool);
164impl_compare_fixed_size_ord_type!(u32);
165impl_compare_fixed_size_ord_type!(i32);
166impl_compare_fixed_size_ord_type!(u64);
167impl_compare_fixed_size_ord_type!(i64);
168impl_compare_fixed_size_ord_type!(u128);
169impl_compare_fixed_size_ord_type!(i128);
170
171impl_compare_fixed_size_ord_type!(U256);
172impl_compare_fixed_size_ord_type!(I256);
173impl_compare_fixed_size_ord_type!(Int128Parts);
174impl_compare_fixed_size_ord_type!(UInt128Parts);
175impl_compare_fixed_size_ord_type!(Int256Parts);
176impl_compare_fixed_size_ord_type!(UInt256Parts);
177impl_compare_fixed_size_ord_type!(TimePoint);
178impl_compare_fixed_size_ord_type!(Duration);
179impl_compare_fixed_size_ord_type!(Hash);
180impl_compare_fixed_size_ord_type!(Uint256);
181impl_compare_fixed_size_ord_type!(ContractExecutable);
182impl_compare_fixed_size_ord_type!(AccountId);
183impl_compare_fixed_size_ord_type!(ScError);
184impl_compare_fixed_size_ord_type!(ScAddress);
185impl_compare_fixed_size_ord_type!(ScNonceKey);
186impl_compare_fixed_size_ord_type!(PublicKey);
187impl_compare_fixed_size_ord_type!(TrustLineAsset);
188impl_compare_fixed_size_ord_type!(ContractDataDurability);
189impl_compare_fixed_size_ord_type!(CreateContractArgs);
190impl_compare_fixed_size_ord_type!(CreateContractArgsV2);
191
192impl_compare_fixed_size_ord_type!(LedgerKeyAccount);
193impl_compare_fixed_size_ord_type!(LedgerKeyTrustLine);
194// NB: LedgerKeyContractData is not here: it has a variable-size ScVal.
195impl_compare_fixed_size_ord_type!(LedgerKeyContractCode);
196
197impl Compare<SymbolStr> for Budget {
198    type Error = HostError;
199
200    fn compare(&self, a: &SymbolStr, b: &SymbolStr) -> Result<Ordering, Self::Error> {
201        self.compare(
202            &<SymbolStr as AsRef<[u8]>>::as_ref(a),
203            &<SymbolStr as AsRef<[u8]>>::as_ref(b),
204        )
205    }
206}
207
208impl Compare<ScVec> for Budget {
209    type Error = HostError;
210
211    fn compare(&self, a: &ScVec, b: &ScVec) -> Result<Ordering, Self::Error> {
212        let a: &Vec<ScVal> = a;
213        let b: &Vec<ScVal> = b;
214        self.compare(a, b)
215    }
216}
217
218impl Compare<ScMap> for Budget {
219    type Error = HostError;
220
221    fn compare(&self, a: &ScMap, b: &ScMap) -> Result<Ordering, Self::Error> {
222        let a: &Vec<ScMapEntry> = a;
223        let b: &Vec<ScMapEntry> = b;
224        self.compare(a, b)
225    }
226}
227
228impl Compare<ScMapEntry> for Budget {
229    type Error = HostError;
230
231    fn compare(&self, a: &ScMapEntry, b: &ScMapEntry) -> Result<Ordering, Self::Error> {
232        match self.compare(&a.key, &b.key)? {
233            Ordering::Equal => self.compare(&a.val, &b.val),
234            cmp => Ok(cmp),
235        }
236    }
237}
238
239impl Compare<ScVal> for Budget {
240    type Error = HostError;
241
242    fn compare(&self, a: &ScVal, b: &ScVal) -> Result<Ordering, Self::Error> {
243        use ScVal::*;
244        // This is the depth limit checkpoint for `ScVal` comparison.
245        self.clone().with_limited_depth(|_| match (a, b) {
246            (Vec(Some(a)), Vec(Some(b))) => self.compare(a, b),
247            (Map(Some(a)), Map(Some(b))) => self.compare(a, b),
248
249            (Vec(None), _) | (_, Vec(None)) | (Map(None), _) | (_, Map(None)) => {
250                Err((ScErrorType::Value, ScErrorCode::InvalidInput).into())
251            }
252
253            (Bytes(a), Bytes(b)) => {
254                <Self as Compare<&[u8]>>::compare(self, &a.as_slice(), &b.as_slice())
255            }
256
257            (String(a), String(b)) => {
258                <Self as Compare<&[u8]>>::compare(self, &a.as_slice(), &b.as_slice())
259            }
260
261            (Symbol(a), Symbol(b)) => {
262                <Self as Compare<&[u8]>>::compare(self, &a.as_slice(), &b.as_slice())
263            }
264
265            (ContractInstance(a), ContractInstance(b)) => self.compare(&a, &b),
266
267            // These two cases are content-free, besides their discriminant.
268            (Void, Void) => Ok(Ordering::Equal),
269            (LedgerKeyContractInstance, LedgerKeyContractInstance) => Ok(Ordering::Equal),
270
271            // Handle types with impl_compare_fixed_size_ord_type:
272            (Bool(a), Bool(b)) => self.compare(&a, &b),
273            (Error(a), Error(b)) => self.compare(&a, &b),
274            (U32(a), U32(b)) => self.compare(&a, &b),
275            (I32(a), I32(b)) => self.compare(&a, &b),
276            (U64(a), U64(b)) => self.compare(&a, &b),
277            (I64(a), I64(b)) => self.compare(&a, &b),
278            (Timepoint(a), Timepoint(b)) => self.compare(&a, &b),
279            (Duration(a), Duration(b)) => self.compare(&a, &b),
280            (U128(a), U128(b)) => self.compare(&a, &b),
281            (I128(a), I128(b)) => self.compare(&a, &b),
282            (U256(a), U256(b)) => self.compare(&a, &b),
283            (I256(a), I256(b)) => self.compare(&a, &b),
284            (Address(a), Address(b)) => self.compare(&a, &b),
285            (LedgerKeyNonce(a), LedgerKeyNonce(b)) => self.compare(&a, &b),
286
287            // List out at least one side of all the remaining cases here so
288            // we don't accidentally forget to update this when/if a new
289            // ScVal type is added.
290            (Vec(_), _)
291            | (Map(_), _)
292            | (Bytes(_), _)
293            | (String(_), _)
294            | (Symbol(_), _)
295            | (ContractInstance(_), _)
296            | (Bool(_), _)
297            | (Void, _)
298            | (Error(_), _)
299            | (U32(_), _)
300            | (I32(_), _)
301            | (U64(_), _)
302            | (I64(_), _)
303            | (Timepoint(_), _)
304            | (Duration(_), _)
305            | (U128(_), _)
306            | (I128(_), _)
307            | (U256(_), _)
308            | (I256(_), _)
309            | (Address(_), _)
310            | (LedgerKeyContractInstance, _)
311            | (LedgerKeyNonce(_), _) => Ok(a.discriminant().cmp(&b.discriminant())),
312        })
313    }
314}
315
316impl Compare<ScContractInstance> for Budget {
317    type Error = HostError;
318
319    fn compare(
320        &self,
321        a: &ScContractInstance,
322        b: &ScContractInstance,
323    ) -> Result<Ordering, Self::Error> {
324        self.compare(&(&a.executable, &a.storage), &(&b.executable, &b.storage))
325    }
326}
327
328impl Compare<LedgerKeyContractData> for Budget {
329    type Error = HostError;
330
331    fn compare(
332        &self,
333        a: &LedgerKeyContractData,
334        b: &LedgerKeyContractData,
335    ) -> Result<Ordering, Self::Error> {
336        self.compare(
337            &(&a.contract, &a.key, &a.durability),
338            &(&b.contract, &b.key, &b.durability),
339        )
340    }
341}
342
343impl Compare<LedgerKey> for Budget {
344    type Error = HostError;
345
346    fn compare(&self, a: &LedgerKey, b: &LedgerKey) -> Result<Ordering, Self::Error> {
347        Storage::check_supported_ledger_key_type(a)?;
348        Storage::check_supported_ledger_key_type(b)?;
349        use LedgerKey::*;
350        match (a, b) {
351            (Account(a), Account(b)) => self.compare(&a, &b),
352            (Trustline(a), Trustline(b)) => self.compare(&a, &b),
353            (ContractData(a), ContractData(b)) => self.compare(&a, &b),
354            (ContractCode(a), ContractCode(b)) => self.compare(&a, &b),
355
356            // All these cases should have been rejected above by check_supported_ledger_key_type.
357            (Offer(_), _)
358            | (Data(_), _)
359            | (ClaimableBalance(_), _)
360            | (LiquidityPool(_), _)
361            | (ConfigSetting(_), _)
362            | (Ttl(_), _)
363            | (_, Offer(_))
364            | (_, Data(_))
365            | (_, ClaimableBalance(_))
366            | (_, LiquidityPool(_))
367            | (_, ConfigSetting(_))
368            | (_, Ttl(_)) => Err((ScErrorType::Value, ScErrorCode::InternalError).into()),
369
370            // List out one side of each remaining unequal-discriminant case so
371            // we remember to update this code if LedgerKey changes. We don't
372            // charge for these since they're just 1-integer compares.
373            (Account(_), _) | (Trustline(_), _) | (ContractData(_), _) | (ContractCode(_), _) => {
374                Ok(a.discriminant().cmp(&b.discriminant()))
375            }
376        }
377    }
378}
379
380#[cfg(test)]
381mod tests {
382    use super::*;
383    use crate::xdr::ScVal;
384    use crate::{Compare, Host, Tag, TryFromVal, Val};
385    use itertools::Itertools;
386
387    #[test]
388    fn test_scvec_unequal_lengths() {
389        {
390            let v1 = ScVec::try_from((0, 1)).unwrap();
391            let v2 = ScVec::try_from((0, 1, 2)).unwrap();
392            let expected_cmp = Ordering::Less;
393            let budget = Budget::default();
394            let actual_cmp = budget.compare(&v1, &v2).unwrap();
395            assert_eq!(expected_cmp, actual_cmp);
396        }
397        {
398            let v1 = ScVec::try_from((0, 1, 2)).unwrap();
399            let v2 = ScVec::try_from((0, 1)).unwrap();
400            let expected_cmp = Ordering::Greater;
401            let budget = Budget::default();
402            let actual_cmp = budget.compare(&v1, &v2).unwrap();
403            assert_eq!(expected_cmp, actual_cmp);
404        }
405        {
406            let v1 = ScVec::try_from((0, 1)).unwrap();
407            let v2 = ScVec::try_from((0, 0, 2)).unwrap();
408            let expected_cmp = Ordering::Greater;
409            let budget = Budget::default();
410            let actual_cmp = budget.compare(&v1, &v2).unwrap();
411            assert_eq!(expected_cmp, actual_cmp);
412        }
413        {
414            let v1 = ScVec::try_from((0, 0, 2)).unwrap();
415            let v2 = ScVec::try_from((0, 1)).unwrap();
416            let expected_cmp = Ordering::Less;
417            let budget = Budget::default();
418            let actual_cmp = budget.compare(&v1, &v2).unwrap();
419            assert_eq!(expected_cmp, actual_cmp);
420        }
421    }
422
423    #[test]
424    fn test_scmap_unequal_lengths() {
425        {
426            let v1 = ScMap::sorted_from([
427                (ScVal::U32(0), ScVal::U32(0)),
428                (ScVal::U32(1), ScVal::U32(1)),
429            ])
430            .unwrap();
431            let v2 = ScMap::sorted_from([
432                (ScVal::U32(0), ScVal::U32(0)),
433                (ScVal::U32(1), ScVal::U32(1)),
434                (ScVal::U32(2), ScVal::U32(2)),
435            ])
436            .unwrap();
437            let expected_cmp = Ordering::Less;
438            let budget = Budget::default();
439            let actual_cmp = budget.compare(&v1, &v2).unwrap();
440            assert_eq!(expected_cmp, actual_cmp);
441        }
442        {
443            let v1 = ScMap::sorted_from([
444                (ScVal::U32(0), ScVal::U32(0)),
445                (ScVal::U32(1), ScVal::U32(1)),
446                (ScVal::U32(2), ScVal::U32(2)),
447            ])
448            .unwrap();
449            let v2 = ScMap::sorted_from([
450                (ScVal::U32(0), ScVal::U32(0)),
451                (ScVal::U32(1), ScVal::U32(1)),
452            ])
453            .unwrap();
454            let expected_cmp = Ordering::Greater;
455            let budget = Budget::default();
456            let actual_cmp = budget.compare(&v1, &v2).unwrap();
457            assert_eq!(expected_cmp, actual_cmp);
458        }
459        {
460            let v1 = ScMap::sorted_from([
461                (ScVal::U32(0), ScVal::U32(0)),
462                (ScVal::U32(1), ScVal::U32(1)),
463            ])
464            .unwrap();
465            let v2 = ScMap::sorted_from([
466                (ScVal::U32(0), ScVal::U32(0)),
467                (ScVal::U32(1), ScVal::U32(0)),
468                (ScVal::U32(2), ScVal::U32(2)),
469            ])
470            .unwrap();
471            let expected_cmp = Ordering::Greater;
472            let budget = Budget::default();
473            let actual_cmp = budget.compare(&v1, &v2).unwrap();
474            assert_eq!(expected_cmp, actual_cmp);
475        }
476        {
477            let v1 = ScMap::sorted_from([
478                (ScVal::U32(0), ScVal::U32(0)),
479                (ScVal::U32(1), ScVal::U32(0)),
480                (ScVal::U32(2), ScVal::U32(2)),
481            ])
482            .unwrap();
483            let v2 = ScMap::sorted_from([
484                (ScVal::U32(0), ScVal::U32(0)),
485                (ScVal::U32(1), ScVal::U32(1)),
486            ])
487            .unwrap();
488            let expected_cmp = Ordering::Less;
489            let budget = Budget::default();
490            let actual_cmp = budget.compare(&v1, &v2).unwrap();
491            assert_eq!(expected_cmp, actual_cmp);
492        }
493    }
494
495    #[test]
496    fn host_obj_discriminant_order() {
497        // The HostObject discriminants need to be ordered the same
498        // as the ScVal discriminants so that Compare<HostObject>
499        // produces the same results as `Ord for ScVal`,
500        // re https://github.com/stellar/rs-soroban-env/issues/743.
501        //
502        // This test creates pairs of corresponding ScVal/HostObjects,
503        // puts them all into a list, and sorts them 2 ways:
504        // comparing ScVals, and comparing the HostObject discriminants;
505        // then tests that the two lists are the same.
506
507        use crate::ScValObjRef;
508        use soroban_env_common::xdr;
509
510        let host = Host::default();
511
512        let xdr_vals = &[
513            ScVal::U64(u64::MAX),
514            ScVal::I64(i64::MAX),
515            ScVal::Timepoint(xdr::TimePoint(u64::MAX)),
516            ScVal::Duration(xdr::Duration(u64::MAX)),
517            ScVal::U128(xdr::UInt128Parts {
518                hi: u64::MAX,
519                lo: u64::MAX,
520            }),
521            ScVal::I128(xdr::Int128Parts {
522                hi: i64::MIN,
523                lo: u64::MAX,
524            }),
525            ScVal::U256(xdr::UInt256Parts {
526                hi_hi: u64::MAX,
527                hi_lo: u64::MAX,
528                lo_hi: u64::MAX,
529                lo_lo: u64::MAX,
530            }),
531            ScVal::I256(xdr::Int256Parts {
532                hi_hi: i64::MIN,
533                hi_lo: u64::MAX,
534                lo_hi: u64::MAX,
535                lo_lo: u64::MAX,
536            }),
537            ScVal::Bytes(xdr::ScBytes::try_from(vec![]).unwrap()),
538            ScVal::String(xdr::ScString::try_from(vec![]).unwrap()),
539            ScVal::Symbol(xdr::ScSymbol::try_from("very_big_symbol").unwrap()),
540            ScVal::Vec(Some(xdr::ScVec::try_from((0,)).unwrap())),
541            ScVal::Map(Some(xdr::ScMap::try_from(vec![]).unwrap())),
542            ScVal::Address(xdr::ScAddress::Contract(xdr::Hash([0; 32]))),
543        ];
544
545        let pairs: Vec<_> = xdr_vals
546            .into_iter()
547            .map(|xdr_val| {
548                let xdr_obj = ScValObjRef::classify(&xdr_val).unwrap();
549                let host_obj = host.to_host_obj(&xdr_obj).unwrap();
550                (xdr_obj, host_obj)
551            })
552            .collect();
553
554        let mut pairs_xdr_sorted = pairs.clone();
555        let mut pairs_host_sorted = pairs_xdr_sorted.clone();
556
557        pairs_xdr_sorted.sort_by(|&(v1, _), &(v2, _)| v1.cmp(&v2));
558
559        pairs_host_sorted.sort_by(|&(_, v1), &(_, v2)| {
560            host.visit_obj_untyped(v1, |v1| {
561                host.visit_obj_untyped(v2, |v2| {
562                    let v1d = host_obj_discriminant(v1);
563                    let v2d = host_obj_discriminant(v2);
564                    Ok(v1d.cmp(&v2d))
565                })
566            })
567            .unwrap()
568        });
569
570        let iter = pairs_xdr_sorted.into_iter().zip(pairs_host_sorted);
571
572        for ((xdr1, _), (xdr2, _)) in iter {
573            assert_eq!(xdr1, xdr2);
574        }
575    }
576
577    /// Test that comparison of an object of one type to a small value of another
578    /// type produces the same results as the equivalent ScVal comparison.
579    ///
580    /// This is a test of the Host::obj_cmp and Tag::get_scval_type methods.
581    ///
582    /// It works by generating an "example" Val for every possible tag,
583    /// with a match on Tag that ensures it will be updated as Tag changes.
584    ///
585    /// Those examples are then converted to an array of ScVal.
586    ///
587    /// For both arrays, every pairwise comparison is performed, and must be equal.
588    #[test]
589    fn compare_obj_to_small() {
590        let host = Host::default();
591        let vals: Vec<Val> = all_tags()
592            .into_iter()
593            .map(|t| example_for_tag(&host, t))
594            .collect();
595        let scvals: Vec<ScVal> = vals
596            .iter()
597            .map(|r| ScVal::try_from_val(&host, r).expect("scval"))
598            .collect();
599
600        let val_pairs = vals.iter().cartesian_product(&vals);
601        let scval_pairs = scvals.iter().cartesian_product(&scvals);
602
603        let pair_pairs = val_pairs.zip(scval_pairs);
604
605        for ((val1, val2), (scval1, scval2)) in pair_pairs {
606            let val_cmp = host.compare(val1, val2).expect("compare");
607            let scval_cmp = scval1.cmp(scval2);
608            assert_eq!(val_cmp, scval_cmp);
609        }
610    }
611
612    fn all_tags() -> Vec<Tag> {
613        (0_u8..=255)
614            .map(Tag::from_u8)
615            .filter(|t| {
616                // bad tags can't be converted to ScVal
617                !matches!(t, Tag::Bad)
618            })
619            .collect()
620    }
621
622    fn example_for_tag(host: &Host, tag: Tag) -> Val {
623        use crate::{xdr, Error};
624
625        let ex = match tag {
626            Tag::False => Val::from(false),
627            Tag::True => Val::from(true),
628            Tag::Void => Val::from(()),
629            Tag::Error => Val::from(Error::from_type_and_code(
630                ScErrorType::Context,
631                ScErrorCode::InternalError,
632            )),
633            Tag::U32Val => Val::from(u32::MAX),
634            Tag::I32Val => Val::from(i32::MAX),
635            Tag::U64Small => Val::try_from_val(host, &0_u64).unwrap(),
636            Tag::I64Small => Val::try_from_val(host, &0_i64).unwrap(),
637            Tag::TimepointSmall => {
638                Val::try_from_val(host, &ScVal::Timepoint(xdr::TimePoint(0))).unwrap()
639            }
640            Tag::DurationSmall => {
641                Val::try_from_val(host, &ScVal::Duration(xdr::Duration(0))).unwrap()
642            }
643            Tag::U128Small => Val::try_from_val(host, &0_u128).unwrap(),
644            Tag::I128Small => Val::try_from_val(host, &0_i128).unwrap(),
645            Tag::U256Small => Val::try_from_val(
646                host,
647                &ScVal::U256(xdr::UInt256Parts {
648                    hi_hi: 0,
649                    hi_lo: 0,
650                    lo_hi: 0,
651                    lo_lo: 0,
652                }),
653            )
654            .unwrap(),
655            Tag::I256Small => Val::try_from_val(
656                host,
657                &ScVal::I256(xdr::Int256Parts {
658                    hi_hi: 0,
659                    hi_lo: 0,
660                    lo_hi: 0,
661                    lo_lo: 0,
662                }),
663            )
664            .unwrap(),
665            Tag::SymbolSmall => {
666                Val::try_from_val(host, &ScVal::Symbol(xdr::ScSymbol::try_from("").unwrap()))
667                    .unwrap()
668            }
669            Tag::SmallCodeUpperBound => panic!(),
670            Tag::ObjectCodeLowerBound => panic!(),
671            Tag::U64Object => Val::try_from_val(host, &u64::MAX).unwrap(),
672            Tag::I64Object => Val::try_from_val(host, &i64::MAX).unwrap(),
673            Tag::TimepointObject => {
674                Val::try_from_val(host, &ScVal::Timepoint(xdr::TimePoint(u64::MAX))).unwrap()
675            }
676            Tag::DurationObject => {
677                Val::try_from_val(host, &ScVal::Duration(xdr::Duration(u64::MAX))).unwrap()
678            }
679            Tag::U128Object => Val::try_from_val(host, &u128::MAX).unwrap(),
680            Tag::I128Object => Val::try_from_val(host, &i128::MAX).unwrap(),
681            Tag::U256Object => Val::try_from_val(
682                host,
683                &ScVal::U256(xdr::UInt256Parts {
684                    hi_hi: u64::MAX,
685                    hi_lo: u64::MAX,
686                    lo_hi: u64::MAX,
687                    lo_lo: u64::MAX,
688                }),
689            )
690            .unwrap(),
691            Tag::I256Object => Val::try_from_val(
692                host,
693                &ScVal::I256(xdr::Int256Parts {
694                    hi_hi: i64::MIN,
695                    hi_lo: u64::MAX,
696                    lo_hi: u64::MAX,
697                    lo_lo: u64::MAX,
698                }),
699            )
700            .unwrap(),
701            Tag::BytesObject => Val::try_from_val(host, &vec![1]).unwrap(),
702            Tag::StringObject => Val::try_from_val(host, &"foo").unwrap(),
703            Tag::SymbolObject => Val::try_from_val(
704                host,
705                &ScVal::Symbol(xdr::ScSymbol::try_from("a_very_big_symbol").unwrap()),
706            )
707            .unwrap(),
708            Tag::VecObject => {
709                Val::try_from_val(host, &ScVal::Vec(Some(xdr::ScVec::try_from((0,)).unwrap())))
710                    .unwrap()
711            }
712            Tag::MapObject => Val::try_from_val(
713                host,
714                &ScVal::Map(Some(xdr::ScMap::try_from(vec![]).unwrap())),
715            )
716            .unwrap(),
717            Tag::AddressObject => Val::try_from_val(
718                host,
719                &ScVal::Address(xdr::ScAddress::Contract(xdr::Hash([0; 32]))),
720            )
721            .unwrap(),
722            Tag::ObjectCodeUpperBound => panic!(),
723            Tag::Bad => panic!(),
724            // NB: do not add a fallthrough case here if new Tag variants are added.
725            // this test depends on the match being exhaustive in order to ensure
726            // the correctness of Tag discriminants.
727        };
728
729        assert_eq!(ex.get_tag(), tag);
730
731        ex
732    }
733}