soroban_env_host/host/
comparison.rs

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