Skip to main content

avalanche_types/ids/
mod.rs

1//! Implements the Avalanche ID type (32-byte).
2//!
3//! ```
4//! use avalanche_types::ids;
5//!
6//! assert_eq!(format!("{}", ids::Id::default()), "11111111111111111111111111111111LpoYY");
7//! ```
8
9pub mod bag;
10pub mod bits;
11pub mod node;
12pub mod short;
13
14use std::{
15    cmp::Ordering,
16    collections::HashSet,
17    fmt,
18    hash::{Hash, Hasher},
19    str::FromStr,
20};
21
22use crate::{
23    errors::{Error, Result},
24    formatting, hash, packer,
25};
26use lazy_static::lazy_static;
27use serde::{self, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
28use zerocopy::{AsBytes, FromBytes, FromZeroes, Unaligned};
29
30pub const LEN: usize = 32;
31
32lazy_static! {
33    static ref EMPTY: Vec<u8> = vec![0; LEN];
34}
35
36/// ref. <https://pkg.go.dev/github.com/ava-labs/avalanchego/ids#ID>
37/// ref. <https://docs.rs/zerocopy/latest/zerocopy/trait.AsBytes.html#safety>
38#[derive(Debug, Clone, Copy, Eq, AsBytes, FromZeroes, FromBytes, Unaligned)]
39#[repr(transparent)]
40pub struct Id([u8; LEN]);
41
42impl Default for Id {
43    fn default() -> Self {
44        Self::empty()
45    }
46}
47
48impl Id {
49    pub fn empty() -> Self {
50        Id([0; LEN])
51    }
52
53    pub fn is_empty(&self) -> bool {
54        (*self) == Self::empty()
55    }
56
57    pub fn to_vec(&self) -> Vec<u8> {
58        self.0.to_vec()
59    }
60
61    /// SHA256-hashes the given byte slice into an Id.
62    /// ref. <https://pkg.go.dev/github.com/ava-labs/avalanchego/ids#ToID>
63    pub fn sha256(d: impl AsRef<[u8]>) -> Self {
64        Id::from_slice(&hash::sha256(d))
65    }
66
67    /// If the passed array is shorter than the LEN,
68    /// it fills in with zero.
69    pub fn from_slice(d: &[u8]) -> Self {
70        assert!(d.len() <= LEN);
71        let mut d: Vec<u8> = Vec::from(d);
72        if d.len() < LEN {
73            d.resize(LEN, 0);
74        }
75        let d: [u8; LEN] = d.try_into().unwrap();
76        Id(d)
77    }
78
79    /// ref. "ids.ID.Prefix(output_index)"
80    pub fn prefix(&self, prefixes: &[u64]) -> Result<Self> {
81        let n = prefixes.len() + packer::U64_LEN + 32;
82        let packer = packer::Packer::new(n, n);
83        for pfx in prefixes {
84            packer.pack_u64(*pfx)?;
85        }
86        packer.pack_bytes(&self.0)?;
87
88        let b = packer.take_bytes();
89        let d = hash::sha256(&b);
90        Ok(Self::from_slice(&d))
91    }
92
93    /// Returns the bit value at the i-th index of the byte array (0 or 1).
94    /// ref. <https://pkg.go.dev/github.com/ava-labs/avalanchego/ids#ID.Bit>
95    pub fn bit(&self, i: usize) -> bits::Bit {
96        let byte_index = i / 8;
97        let bit_index = i % 8;
98
99        let mut b = self.0[byte_index];
100
101        // b = [7, 6, 5, 4, 3, 2, 1, 0]
102
103        b >>= bit_index;
104
105        // b = [0, ..., bit_index + 1, bit_index]
106        // 1 = [0, 0, 0, 0, 0, 0, 0, 1]
107
108        b &= 1;
109
110        // b = [0, 0, 0, 0, 0, 0, 0, bit_index]
111
112        // must be either 0 or 1
113        bits::Bit::from(b as usize)
114    }
115}
116
117impl AsRef<[u8]> for Id {
118    fn as_ref(&self) -> &[u8] {
119        &self.0
120    }
121}
122
123/// ref. <https://doc.rust-lang.org/std/string/trait.ToString.html>
124/// ref. <https://doc.rust-lang.org/std/fmt/trait.Display.html>
125/// Use "Self.to_string()" to directly invoke this.
126impl fmt::Display for Id {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        let s = formatting::encode_cb58_with_checksum_string(&self.0);
129        write!(f, "{}", s)
130    }
131}
132
133/// ref. <https://doc.rust-lang.org/std/str/trait.FromStr.html>
134impl FromStr for Id {
135    type Err = std::io::Error;
136    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
137        // trim in case it's parsed from list
138        let decoded = formatting::decode_cb58_with_checksum(s.trim()).map_err(|e| {
139            std::io::Error::new(
140                std::io::ErrorKind::Other,
141                format!("failed decode_cb58_with_checksum '{}'", e),
142            )
143        })?;
144        Ok(Self::from_slice(&decoded))
145    }
146}
147
148impl From<std::borrow::Cow<'static, str>> for Id {
149    fn from(v: std::borrow::Cow<'static, str>) -> Self {
150        Id::from_str(v.as_ref()).unwrap()
151    }
152}
153
154/// Custom serializer.
155/// ref. <https://serde.rs/impl-serialize.html>
156impl Serialize for Id {
157    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
158    where
159        S: Serializer,
160    {
161        serializer.serialize_str(&self.to_string())
162    }
163}
164
165/// Custom deserializer.
166/// ref. <https://serde.rs/impl-deserialize.html>
167impl<'de> Deserialize<'de> for Id {
168    fn deserialize<D>(deserializer: D) -> std::result::Result<Id, D::Error>
169    where
170        D: Deserializer<'de>,
171    {
172        struct IdVisitor;
173
174        impl<'de> Visitor<'de> for IdVisitor {
175            type Value = Id;
176
177            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
178                formatter.write_str("a base-58 encoded ID-string with checksum")
179            }
180
181            fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
182            where
183                E: serde::de::Error,
184            {
185                Id::from_str(v).map_err(E::custom)
186            }
187        }
188
189        deserializer.deserialize_any(IdVisitor)
190    }
191}
192
193/// RUST_LOG=debug cargo test --package avalanche-types --lib -- ids::test_custom_de_serializer --exact --show-output
194#[test]
195fn test_custom_de_serializer() {
196    #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
197    struct Data {
198        id: Id,
199    }
200
201    let d = Data {
202        id: Id::from_str("g25v3qDyAaHfR7kBev8tLUHouSgN5BJuZjy1BYS1oiHd2vres").unwrap(),
203    };
204
205    let yaml_encoded = serde_yaml::to_string(&d).unwrap();
206    println!("yaml_encoded:\n{}", yaml_encoded);
207    let yaml_decoded = serde_yaml::from_str(&yaml_encoded).unwrap();
208    assert_eq!(d, yaml_decoded);
209
210    let json_encoded = serde_json::to_string(&d).unwrap();
211    println!("json_encoded:\n{}", json_encoded);
212    let json_decoded = serde_json::from_str(&json_encoded).unwrap();
213    assert_eq!(d, json_decoded);
214
215    let json_decoded_2: Data = serde_json::from_str(
216        "
217
218{
219    \"id\":\"g25v3qDyAaHfR7kBev8tLUHouSgN5BJuZjy1BYS1oiHd2vres\"
220}
221
222",
223    )
224    .unwrap();
225    assert_eq!(d, json_decoded_2);
226
227    let json_encoded_3 = serde_json::json!(
228        {
229            "id": "g25v3qDyAaHfR7kBev8tLUHouSgN5BJuZjy1BYS1oiHd2vres"
230        }
231    );
232    let json_decoded_3: Data = serde_json::from_value(json_encoded_3).unwrap();
233    assert_eq!(d, json_decoded_3);
234}
235
236fn fmt_id<'de, D>(deserializer: D) -> std::result::Result<Id, D::Error>
237where
238    D: Deserializer<'de>,
239{
240    let s = String::deserialize(deserializer)?;
241    Id::from_str(&s).map_err(serde::de::Error::custom)
242}
243
244/// Custom deserializer.
245/// ref. <https://serde.rs/impl-deserialize.html>
246pub fn deserialize_id<'de, D>(deserializer: D) -> std::result::Result<Option<Id>, D::Error>
247where
248    D: Deserializer<'de>,
249{
250    #[derive(Deserialize)]
251    struct Wrapper(#[serde(deserialize_with = "fmt_id")] Id);
252    let v = Option::deserialize(deserializer)?;
253    Ok(v.map(|Wrapper(a)| a))
254}
255
256/// Custom deserializer.
257/// Use #[serde(deserialize_with = "ids::must_deserialize_id")] to serde without derive.
258/// ref. <https://serde.rs/impl-deserialize.html>
259pub fn must_deserialize_id<'de, D>(deserializer: D) -> std::result::Result<Id, D::Error>
260where
261    D: Deserializer<'de>,
262{
263    #[derive(Deserialize)]
264    struct Wrapper(#[serde(deserialize_with = "fmt_id")] Id);
265    let v = Option::deserialize(deserializer)?;
266    match v.map(|Wrapper(a)| a) {
267        Some(unwrapped) => Ok(unwrapped),
268        None => Err(serde::de::Error::custom("empty Id from deserialization")),
269    }
270}
271
272/// Custom deserializer.
273/// ref. <https://serde.rs/impl-deserialize.html>
274pub fn deserialize_ids<'de, D>(deserializer: D) -> std::result::Result<Option<Vec<Id>>, D::Error>
275where
276    D: Deserializer<'de>,
277{
278    #[derive(Deserialize)]
279    struct Wrapper(#[serde(deserialize_with = "fmt_ids")] Vec<Id>);
280    let v = Option::deserialize(deserializer)?;
281    Ok(v.map(|Wrapper(a)| a))
282}
283
284/// Custom deserializer.
285/// Use #[serde(deserialize_with = "short::must_deserialize_ids")] to serde with derive.
286/// ref. <https://serde.rs/impl-deserialize.html>
287pub fn must_deserialize_ids<'de, D>(deserializer: D) -> std::result::Result<Vec<Id>, D::Error>
288where
289    D: Deserializer<'de>,
290{
291    #[derive(Deserialize)]
292    struct Wrapper(#[serde(deserialize_with = "fmt_ids")] Vec<Id>);
293    let v = Option::deserialize(deserializer)?;
294    match v.map(|Wrapper(a)| a) {
295        Some(unwrapped) => Ok(unwrapped),
296        None => Err(serde::de::Error::custom("empty Ids from deserialization")),
297    }
298}
299
300fn fmt_ids<'de, D>(deserializer: D) -> std::result::Result<Vec<Id>, D::Error>
301where
302    D: serde::Deserializer<'de>,
303{
304    type Strings = Vec<String>;
305    let ss = Strings::deserialize(deserializer)?;
306    match ss
307        .iter()
308        .map(|x| x.parse::<Id>())
309        .collect::<std::result::Result<Vec<Id>, std::io::Error>>()
310    {
311        Ok(x) => Ok(x),
312        Err(e) => Err(serde::de::Error::custom(format!(
313            "failed to deserialize Ids {}",
314            e
315        ))),
316    }
317}
318
319/// RUST_LOG=debug cargo test --package avalanche-types --lib -- ids::test_serialize --exact --show-output
320#[test]
321fn test_serialize() {
322    let id = Id::from_slice(&<Vec<u8>>::from([
323        0x3d, 0x0a, 0xd1, 0x2b, 0x8e, 0xe8, 0x92, 0x8e, 0xdf, 0x24, //
324        0x8c, 0xa9, 0x1c, 0xa5, 0x56, 0x00, 0xfb, 0x38, 0x3f, 0x07, //
325        0xc3, 0x2b, 0xff, 0x1d, 0x6d, 0xec, 0x47, 0x2b, 0x25, 0xcf, //
326        0x59, 0xa7,
327    ]));
328    assert_eq!(
329        id.to_string(),
330        "TtF4d2QWbk5vzQGTEPrN48x6vwgAoAmKQ9cbp79inpQmcRKES"
331    );
332
333    use serde::{Deserialize, Serialize};
334    #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
335    struct Data {
336        id: Id,
337        id2: Option<Id>,
338        ids: Vec<Id>,
339    }
340    let d = Data {
341        id,
342        id2: Some(id),
343        ids: vec![id, id, id, id, id],
344    };
345
346    let yaml_encoded = serde_yaml::to_string(&d).unwrap();
347    assert!(yaml_encoded.contains("TtF4d2QWbk5vzQGTEPrN48x6vwgAoAmKQ9cbp79inpQmcRKES"));
348    let yaml_decoded = serde_yaml::from_str(&yaml_encoded).unwrap();
349    assert_eq!(d, yaml_decoded);
350
351    let json_encoded = serde_json::to_string(&d).unwrap();
352    assert!(json_encoded.contains("TtF4d2QWbk5vzQGTEPrN48x6vwgAoAmKQ9cbp79inpQmcRKES"));
353    let json_decoded = serde_json::from_str(&json_encoded).unwrap();
354    assert_eq!(d, json_decoded);
355}
356
357/// Set is a set of Ids.
358/// <https://pkg.go.dev/github.com/ava-labs/avalanchego/ids#Set>
359pub type Set = HashSet<Id>;
360
361/// Return a new set with initial capacity \[size\].
362/// More or less than \[size\] elements can be added to this set.
363pub fn new_set(size: usize) -> Set {
364    let set: HashSet<Id> = HashSet::with_capacity(size);
365    set
366}
367
368/// RUST_LOG=debug cargo test --package avalanche-types --lib -- ids::test_id --exact --show-output
369/// ref. "avalanchego/ids.TestIDMarshalJSON"
370#[test]
371fn test_id() {
372    let id = Id::from_slice(&<Vec<u8>>::from([
373        0x3d, 0x0a, 0xd1, 0x2b, 0x8e, 0xe8, 0x92, 0x8e, 0xdf, 0x24, //
374        0x8c, 0xa9, 0x1c, 0xa5, 0x56, 0x00, 0xfb, 0x38, 0x3f, 0x07, //
375        0xc3, 0x2b, 0xff, 0x1d, 0x6d, 0xec, 0x47, 0x2b, 0x25, 0xcf, //
376        0x59, 0xa7,
377    ]));
378    assert_eq!(
379        id.to_string(),
380        "TtF4d2QWbk5vzQGTEPrN48x6vwgAoAmKQ9cbp79inpQmcRKES"
381    );
382    assert_eq!(
383        id.to_vec(),
384        <Vec<u8>>::from([
385            0x3d, 0x0a, 0xd1, 0x2b, 0x8e, 0xe8, 0x92, 0x8e, 0xdf, 0x24, //
386            0x8c, 0xa9, 0x1c, 0xa5, 0x56, 0x00, 0xfb, 0x38, 0x3f, 0x07, //
387            0xc3, 0x2b, 0xff, 0x1d, 0x6d, 0xec, 0x47, 0x2b, 0x25, 0xcf, //
388            0x59, 0xa7,
389        ])
390    );
391
392    let id_from_str = Id::from_str("TtF4d2QWbk5vzQGTEPrN48x6vwgAoAmKQ9cbp79inpQmcRKES").unwrap();
393    assert_eq!(id, id_from_str);
394
395    let id = Id::from_slice(&<Vec<u8>>::from([
396        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
397        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
398        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
399        0x00, 0x00,
400    ]));
401    assert_eq!(id.to_string(), "11111111111111111111111111111111LpoYY");
402    let id_from_str = Id::from_str("11111111111111111111111111111111LpoYY").unwrap();
403    assert_eq!(id, id_from_str);
404}
405
406impl Ord for Id {
407    fn cmp(&self, other: &Id) -> Ordering {
408        self.0.cmp(&(other.0))
409    }
410}
411
412impl PartialOrd for Id {
413    fn partial_cmp(&self, other: &Id) -> Option<Ordering> {
414        Some(self.cmp(other))
415    }
416}
417
418impl PartialEq for Id {
419    fn eq(&self, other: &Id) -> bool {
420        self.cmp(other) == Ordering::Equal
421    }
422}
423
424/// ref. <https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq>
425impl Hash for Id {
426    fn hash<H: Hasher>(&self, state: &mut H) {
427        self.0.hash(state);
428    }
429}
430
431#[derive(Debug, Eq, Clone)]
432pub struct Ids(Vec<Id>);
433
434impl Ids {
435    pub fn new(ids: &[Id]) -> Self {
436        Ids(Vec::from(ids))
437    }
438}
439
440impl From<Vec<Id>> for Ids {
441    fn from(ids: Vec<Id>) -> Self {
442        Self::new(&ids)
443    }
444}
445
446impl Ord for Ids {
447    fn cmp(&self, other: &Ids) -> Ordering {
448        // packer encodes the array length first
449        // so if the lengths differ, the ordering is decided
450        let l1 = self.0.len();
451        let l2 = other.0.len();
452        l1.cmp(&l2) // returns when lengths are not Equal
453            .then_with(
454                || self.0.cmp(&other.0), // if lengths are Equal, compare the ids
455            )
456    }
457}
458
459impl PartialOrd for Ids {
460    fn partial_cmp(&self, other: &Ids) -> Option<Ordering> {
461        Some(self.cmp(other))
462    }
463}
464
465impl PartialEq for Ids {
466    fn eq(&self, other: &Ids) -> bool {
467        self.cmp(other) == Ordering::Equal
468    }
469}
470
471/// RUST_LOG=debug cargo test --package avalanche-types --lib -- ids::test_sort --exact --show-output
472#[test]
473fn test_sort() {
474    let id1 = Id::from_slice(&<Vec<u8>>::from([0x01]));
475    let id2 = Id::from_slice(&<Vec<u8>>::from([0x02]));
476    let id3 = Id::from_slice(&<Vec<u8>>::from([0x03]));
477    assert!(id1 < id2);
478    assert!(id2 < id3);
479    assert!(id1 < id3);
480
481    // lengths of individual ids do not matter since all are fixed-sized
482    let id1 = Id::from_slice(&<Vec<u8>>::from([0x01, 0x00, 0x00, 0x00]));
483    let id2 = Id::from_slice(&<Vec<u8>>::from([0x01, 0x00, 0x00, 0x00, 0x00]));
484    assert!(id1 == id2);
485
486    // lengths of individual ids do not matter since all are fixed-sized
487    let id1 = Id::from_slice(&<Vec<u8>>::from([0x01, 0x00, 0x00, 0x00, 0x00]));
488    let id2 = Id::from_slice(&<Vec<u8>>::from([0x02]));
489    assert!(id1 < id2);
490
491    // lengths of individual ids do not matter since all are fixed-sized
492    let id1 = Id::from_slice(&<Vec<u8>>::from([0x02, 0x00, 0x00, 0x00, 0x00]));
493    let id2 = Id::from_slice(&<Vec<u8>>::from([0x01, 0x00, 0x00, 0x00, 0x00]));
494    assert!(id1 > id2);
495
496    // lengths of Ids matter
497    let ids1 = Ids(vec![
498        Id::from_slice(&<Vec<u8>>::from([0x01])),
499        Id::from_slice(&<Vec<u8>>::from([0x02])),
500        Id::from_slice(&<Vec<u8>>::from([0x03])),
501    ]);
502    let ids2 = Ids(vec![
503        Id::from_slice(&<Vec<u8>>::from([0x01])),
504        Id::from_slice(&<Vec<u8>>::from([0x02])),
505        Id::from_slice(&<Vec<u8>>::from([0x03])),
506    ]);
507    assert!(ids1 == ids2);
508
509    // lengths of Ids matter
510    let ids1 = Ids(vec![
511        Id::from_slice(&<Vec<u8>>::from([0x05])),
512        Id::from_slice(&<Vec<u8>>::from([0x06])),
513        Id::from_slice(&<Vec<u8>>::from([0x07])),
514    ]);
515    let ids2 = Ids(vec![
516        Id::from_slice(&<Vec<u8>>::from([0x01])),
517        Id::from_slice(&<Vec<u8>>::from([0x02])),
518        Id::from_slice(&<Vec<u8>>::from([0x03])),
519        Id::from_slice(&<Vec<u8>>::from([0x04])),
520    ]);
521    assert!(ids1 < ids2);
522
523    // lengths of Ids matter
524    let ids1 = Ids(vec![
525        Id::from_slice(&<Vec<u8>>::from([0x01])),
526        Id::from_slice(&<Vec<u8>>::from([0x02])),
527        Id::from_slice(&<Vec<u8>>::from([0x03])),
528        Id::from_slice(&<Vec<u8>>::from([0x04])),
529    ]);
530    let ids2 = Ids(vec![
531        Id::from_slice(&<Vec<u8>>::from([0x09])),
532        Id::from_slice(&<Vec<u8>>::from([0x09])),
533        Id::from_slice(&<Vec<u8>>::from([0x09])),
534    ]);
535    assert!(ids1 > ids2);
536
537    // lengths of Ids matter
538    let ids1 = Ids(vec![
539        Id::from_slice(&<Vec<u8>>::from([0x01])),
540        Id::from_slice(&<Vec<u8>>::from([0x02])),
541        Id::from_slice(&<Vec<u8>>::from([0x03])),
542    ]);
543    let ids2 = Ids(vec![
544        Id::from_slice(&<Vec<u8>>::from([0x01])),
545        Id::from_slice(&<Vec<u8>>::from([0x02])),
546        Id::from_slice(&<Vec<u8>>::from([0x05])),
547    ]);
548    assert!(ids1 < ids2);
549
550    let mut ids1 = Ids(vec![
551        Id::from_slice(&<Vec<u8>>::from([0x03])),
552        Id::from_slice(&<Vec<u8>>::from([0x02])),
553        Id::from_slice(&<Vec<u8>>::from([0x01])),
554    ]);
555    ids1.0.sort();
556    let ids2 = Ids(vec![
557        Id::from_slice(&<Vec<u8>>::from([0x01])),
558        Id::from_slice(&<Vec<u8>>::from([0x02])),
559        Id::from_slice(&<Vec<u8>>::from([0x03])),
560    ]);
561    assert!(ids1 == ids2);
562
563    let mut ids1 = vec![
564        Id::from_slice(&<Vec<u8>>::from([0x03])),
565        Id::from_slice(&<Vec<u8>>::from([0x02])),
566        Id::from_slice(&<Vec<u8>>::from([0x01])),
567    ];
568    ids1.sort();
569    let ids2 = vec![
570        Id::from_slice(&<Vec<u8>>::from([0x01])),
571        Id::from_slice(&<Vec<u8>>::from([0x02])),
572        Id::from_slice(&<Vec<u8>>::from([0x03])),
573    ];
574    assert!(ids1 == ids2);
575}
576
577/// Generates VM ID based on the name.
578pub fn encode_vm_name_to_id(name: &str) -> Result<Id> {
579    let n = name.len();
580    if n > LEN {
581        return Err(Error::Other {
582            message: format!("can't id {} bytes (>{})", n, LEN),
583            retryable: false,
584        });
585    }
586
587    let input = name.as_bytes().to_vec();
588    Ok(Id::from_slice(&input))
589}
590
591/// RUST_LOG=debug cargo test --package avalanche-types --lib -- ids::test_vm_id --exact --show-output
592#[test]
593fn test_vm_id() {
594    use log::info;
595    let _ = env_logger::builder()
596        .filter_level(log::LevelFilter::Info)
597        .is_test(true)
598        .try_init();
599
600    let subnet_evm_id = encode_vm_name_to_id("subnetevm").expect("failed to generate id from str");
601    assert_eq!(
602        format!("{}", subnet_evm_id),
603        "srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy"
604    );
605
606    let contents = random_manager::secure_string(30);
607    let v = encode_vm_name_to_id(&contents).expect("failed to generate id from str");
608    info!("vm_id_from_str: {}", v);
609}