Skip to main content

rain_metadata/meta/
mod.rs

1use super::error::Error;
2
3pub mod cache;
4use cache::MetaCache;
5use alloy::primitives::{hex, keccak256};
6use futures::future;
7use graphql_client::GraphQLQuery;
8use rain_metadata_bindings::IDescribedByMetaV1;
9use reqwest::Client;
10use serde::de::{Deserialize, Deserializer, Visitor};
11use serde::ser::{Serialize, SerializeMap, Serializer};
12use std::{
13    collections::{BTreeSet, HashMap},
14    convert::TryFrom,
15    fmt::Debug,
16    sync::Arc,
17};
18use strum::{EnumIter, EnumString};
19use alloy::sol_types::private::Address;
20use alloy::providers::Provider;
21use alloy::contract::Error as ContractError;
22use rain_erc::erc165::{Erc165Error, IERC165, XorSelectors, supports_erc165};
23
24pub mod magic;
25pub(crate) mod normalize;
26pub(crate) mod query;
27pub mod types;
28
29pub use magic::*;
30pub use query::*;
31
32/// All known meta identifiers
33#[derive(Copy, Clone, EnumString, EnumIter, strum::Display, Debug, PartialEq)]
34#[strum(serialize_all = "kebab-case")]
35pub enum KnownMeta {
36    /// Ops meta v1. Still a known meta - the magic number is in the
37    /// metadata-v1 table and an item can legitimately carry it - but this
38    /// crate no longer models or validates the payload. The interpreter
39    /// describes its words as AuthoringMetaV2 now (`LibAllStandardOps`
40    /// publishes exactly that), and the only surviving op meta references in
41    /// the org are deprecated IExpressionDeployer interfaces.
42    OpV1,
43    DotrainV1,
44    RainlangV1,
45    SolidityAbiV2,
46    AuthoringMetaV1,
47    AuthoringMetaV2,
48    InterpreterCallerMetaV1,
49    ExpressionDeployerV2BytecodeV1,
50    RainlangSourceV1,
51    AddressList,
52    DotrainSourceV1,
53    OrderBuilderStateV1,
54    RaindexSignedContextOracleV1,
55}
56
57impl TryFrom<KnownMagic> for KnownMeta {
58    type Error = Error;
59    fn try_from(value: KnownMagic) -> Result<Self, Self::Error> {
60        match value {
61            KnownMagic::DotrainV1 => Ok(KnownMeta::DotrainV1),
62            KnownMagic::RainlangV1 => Ok(KnownMeta::RainlangV1),
63            KnownMagic::SolidityAbiV2 => Ok(KnownMeta::SolidityAbiV2),
64            KnownMagic::OpMetaV1 => Ok(KnownMeta::OpV1),
65            KnownMagic::AuthoringMetaV1 => Ok(KnownMeta::AuthoringMetaV1),
66            KnownMagic::AuthoringMetaV2 => Ok(KnownMeta::AuthoringMetaV2),
67            KnownMagic::AddressList => Ok(KnownMeta::AddressList),
68            KnownMagic::InterpreterCallerMetaV1 => Ok(KnownMeta::InterpreterCallerMetaV1),
69            KnownMagic::DotrainSourceV1 => Ok(KnownMeta::DotrainSourceV1),
70            KnownMagic::OrderBuilderStateV1 => Ok(KnownMeta::OrderBuilderStateV1),
71            KnownMagic::ExpressionDeployerV2BytecodeV1 => {
72                Ok(KnownMeta::ExpressionDeployerV2BytecodeV1)
73            }
74            KnownMagic::RainlangSourceV1 => Ok(KnownMeta::RainlangSourceV1),
75            KnownMagic::RaindexSignedContextOracleV1 => Ok(KnownMeta::RaindexSignedContextOracleV1),
76            _ => Err(Error::UnsupportedMeta),
77        }
78    }
79}
80
81/// Content type of a cbor meta map
82#[derive(
83    Copy,
84    Clone,
85    Debug,
86    EnumIter,
87    PartialEq,
88    EnumString,
89    strum::Display,
90    serde::Serialize,
91    serde::Deserialize,
92)]
93#[strum(serialize_all = "kebab-case")]
94pub enum ContentType {
95    None,
96    #[serde(rename = "application/json")]
97    Json,
98    #[serde(rename = "application/cbor")]
99    Cbor,
100    #[serde(rename = "application/octet-stream")]
101    OctetStream,
102}
103
104/// Content encoding of a cbor meta map
105#[derive(
106    Copy,
107    Clone,
108    Debug,
109    EnumIter,
110    PartialEq,
111    EnumString,
112    strum::Display,
113    serde::Serialize,
114    serde::Deserialize,
115)]
116#[serde(rename_all = "kebab-case")]
117#[strum(serialize_all = "kebab-case")]
118pub enum ContentEncoding {
119    None,
120    Identity,
121    Deflate,
122}
123
124impl ContentEncoding {
125    /// encode the data based on the variant
126    pub fn encode(&self, data: &[u8]) -> Vec<u8> {
127        match self {
128            ContentEncoding::None | ContentEncoding::Identity => data.to_vec(),
129            ContentEncoding::Deflate => deflate::deflate_bytes_zlib(data),
130        }
131    }
132
133    /// decode the data based on the variant
134    pub fn decode(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
135        Ok(match self {
136            ContentEncoding::None | ContentEncoding::Identity => data.to_vec(),
137            ContentEncoding::Deflate => match inflate::inflate_bytes_zlib(data) {
138                Ok(v) => v,
139                Err(error) => match inflate::inflate_bytes(data) {
140                    Ok(v) => v,
141                    Err(_) => Err(Error::InflateError(error))?,
142                },
143            },
144        })
145    }
146}
147
148/// Content language of a cbor meta map
149#[derive(
150    Copy,
151    Clone,
152    Debug,
153    EnumIter,
154    PartialEq,
155    EnumString,
156    strum::Display,
157    serde::Serialize,
158    serde::Deserialize,
159)]
160#[serde(rename_all = "kebab-case")]
161#[strum(serialize_all = "kebab-case")]
162pub enum ContentLanguage {
163    None,
164    En,
165}
166
167/// # Rain Meta Document v1 Item (meta map)
168///
169/// represents a rain meta data and configuration that can be cbor encoded or unpacked back to the meta types
170#[derive(PartialEq, Debug, Clone)]
171pub struct RainMetaDocumentV1Item {
172    pub payload: serde_bytes::ByteBuf,
173    pub magic: KnownMagic,
174    pub content_type: ContentType,
175    pub content_encoding: ContentEncoding,
176    pub content_language: ContentLanguage,
177    /// optional reference to the schema of the payload, encoded under the
178    /// [KnownMagic::OaSchema] magic number as an additional cbor map key
179    /// beyond the standard 0-4 keys
180    pub schema: Option<String>,
181}
182
183// this implementation is mainly used by Rainlang and Dotrain metas as they are aliased type for String
184impl TryFrom<RainMetaDocumentV1Item> for String {
185    type Error = Error;
186    fn try_from(value: RainMetaDocumentV1Item) -> Result<Self, Self::Error> {
187        Ok(String::from_utf8(value.unpack()?)?)
188    }
189}
190
191// this implementation is mainly used by ExpressionDeployerV2Bytecode meta as it is aliased type for Vec<u8>
192impl TryFrom<RainMetaDocumentV1Item> for Vec<u8> {
193    type Error = Error;
194    fn try_from(value: RainMetaDocumentV1Item) -> Result<Self, Self::Error> {
195        value.unpack()
196    }
197}
198
199impl RainMetaDocumentV1Item {
200    fn len(&self) -> usize {
201        let mut l = 2;
202        if !matches!(self.content_type, ContentType::None) {
203            l += 1;
204        }
205        if !matches!(self.content_encoding, ContentEncoding::None) {
206            l += 1;
207        }
208        if !matches!(self.content_language, ContentLanguage::None) {
209            l += 1;
210        }
211        if self.schema.is_some() {
212            l += 1;
213        }
214        l
215    }
216
217    /// method to hash(keccak256) the cbor encoded bytes of this instance
218    pub fn hash(&self, as_rain_meta_document: bool) -> Result<[u8; 32], Error> {
219        if as_rain_meta_document {
220            Ok(keccak256(Self::cbor_encode_seq(
221                &vec![self.clone()],
222                KnownMagic::RainMetaDocumentV1,
223            )?)
224            .0)
225        } else {
226            Ok(keccak256(self.cbor_encode()?).0)
227        }
228    }
229
230    /// method to cbor encode
231    pub fn cbor_encode(&self) -> Result<Vec<u8>, Error> {
232        let mut bytes: Vec<u8> = vec![];
233        Ok(serde_cbor::to_writer(&mut bytes, &self).map(|_| bytes)?)
234    }
235
236    /// builds a cbor sequence from given MetaMaps
237    pub fn cbor_encode_seq(
238        seq: &Vec<RainMetaDocumentV1Item>,
239        magic: KnownMagic,
240    ) -> Result<Vec<u8>, Error> {
241        let mut bytes: Vec<u8> = magic.to_prefix_bytes().to_vec();
242        for item in seq {
243            serde_cbor::to_writer(&mut bytes, &item)?;
244        }
245        Ok(bytes)
246    }
247
248    /// method to cbor decode from given bytes
249    pub fn cbor_decode(data: &[u8]) -> Result<Vec<RainMetaDocumentV1Item>, Error> {
250        let mut metas: Vec<RainMetaDocumentV1Item> = vec![];
251        let mut consumed: usize = 0;
252        let mut is_rain_document_meta = false;
253        let mut len = data.len();
254        if data.starts_with(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes()) {
255            is_rain_document_meta = true;
256            len -= 8;
257        }
258        let mut deserializer = match is_rain_document_meta {
259            true => serde_cbor::Deserializer::from_slice(&data[8..]),
260            false => serde_cbor::Deserializer::from_slice(data),
261        };
262        // straight off the stream, not via serde_cbor::Value, whose BTreeMap
263        // silently collapses the duplicate keys the visitor has to reject.
264        //
265        // ItemOrDropped rather than Self: this is the sequence decoder, so an
266        // item this version does not read is skipped over instead of taking
267        // the items beside it down. Its bytes are still consumed and still
268        // counted, so the trailing len check below stays a statement about
269        // the whole document.
270        while match ItemOrDropped::deserialize(&mut deserializer) {
271            Ok(decoded) => {
272                consumed = deserializer.byte_offset();
273                if let ItemOrDropped::Item(meta) = decoded {
274                    metas.push(meta);
275                }
276                true
277            }
278            Err(error) => {
279                if error.is_eof() {
280                    false
281                } else {
282                    Err(Error::SerdeCborError(error))?
283                }
284            }
285        } {}
286
287        if metas.is_empty() || len != consumed {
288            Err(Error::CorruptMeta)?
289        }
290        Ok(metas)
291    }
292
293    // unpack the payload based on the configuration
294    pub fn unpack(&self) -> Result<Vec<u8>, Error> {
295        ContentEncoding::decode(&self.content_encoding, self.payload.as_ref())
296    }
297
298    // unpacks the payload to given meta type based on configuration
299    pub fn unpack_into<T: TryFrom<Self, Error = Error>>(self) -> Result<T, Error> {
300        match self.magic {
301            KnownMagic::OpMetaV1
302            | KnownMagic::DotrainV1
303            | KnownMagic::RainlangV1
304            | KnownMagic::SolidityAbiV2
305            | KnownMagic::AuthoringMetaV1
306            | KnownMagic::AuthoringMetaV2
307            | KnownMagic::AddressList
308            | KnownMagic::InterpreterCallerMetaV1
309            | KnownMagic::ExpressionDeployerV2BytecodeV1
310            | KnownMagic::DotrainSourceV1
311            | KnownMagic::OrderBuilderStateV1
312            | KnownMagic::RainlangSourceV1
313            | KnownMagic::RaindexSignedContextOracleV1 => T::try_from(self),
314            _ => Err(Error::UnsupportedMeta)?,
315        }
316    }
317}
318
319impl Serialize for RainMetaDocumentV1Item {
320    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
321        let mut map = serializer.serialize_map(Some(self.len()))?;
322        map.serialize_entry(&0, &self.payload)?;
323        map.serialize_entry(&1, &(self.magic as u64))?;
324        match self.content_type {
325            ContentType::None => {}
326            content_type => map.serialize_entry(&2, &content_type)?,
327        }
328        match self.content_encoding {
329            ContentEncoding::None => {}
330            content_encoding => map.serialize_entry(&3, &content_encoding)?,
331        }
332        match self.content_language {
333            ContentLanguage::None => {}
334            content_language => map.serialize_entry(&4, &content_language)?,
335        }
336        if let Some(schema) = &self.schema {
337            map.serialize_entry(&(KnownMagic::OaSchema as u64), schema)?;
338        }
339        map.end()
340    }
341}
342
343/// What a cbor item in a rain meta sequence turned out to be.
344///
345/// Dropped covers exactly one thing: a well formed item whose magic number
346/// this version does not know. The magic is the format's extension point -
347/// "Tooling can efficiently O(1) drop/ignore meta that it does not need or
348/// support decoding and parsing for", "feel free to build systems and
349/// applications with your own numbers and interpretations" - so somebody
350/// else's item travelling in the same document is skipped, not fatal to the
351/// items beside it. rainlanguage/rain.metadata#186.
352///
353/// Everything else malformed stops the document. One document is one emission
354/// from one emitter, whose prefix claims rain meta for the lot, and an
355/// emission carrying a broken claim is not mined for its readable parts.
356/// That is why an item omitting mandatory key 0 or 1 errors here even though
357/// metadata-v1.md line 237 reads as item level drop/ignore: it cannot even be
358/// identified, and recovering its siblings would accept selected data from an
359/// emitter who sent junk. rainlanguage/rain.metadata#188 is overruled on that
360/// line rather than implemented.
361enum ItemOrDropped {
362    Item(RainMetaDocumentV1Item),
363    /// Well formed cbor, but not an item this version reads.
364    Dropped,
365}
366
367impl<'de> Deserialize<'de> for ItemOrDropped {
368    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
369        /// RFC 8949 ยง5.6: a map with duplicate keys is not valid, so a second
370        /// sighting of a key is an error rather than an overwrite.
371        fn set_once<V, E: serde::de::Error>(
372            slot: &mut Option<V>,
373            value: V,
374            field: &'static str,
375        ) -> Result<(), E> {
376            if slot.is_some() {
377                return Err(serde::de::Error::duplicate_field(field));
378            }
379            *slot = Some(value);
380            Ok(())
381        }
382
383        struct EncodedMap;
384        impl<'de> Visitor<'de> for EncodedMap {
385            type Value = ItemOrDropped;
386
387            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
388                formatter.write_str("rain meta cbor encoded bytes")
389            }
390
391            fn visit_map<T: serde::de::MapAccess<'de>>(
392                self,
393                mut map: T,
394            ) -> Result<Self::Value, T::Error> {
395                const OA_SCHEMA_KEY: u64 = KnownMagic::OaSchema as u64;
396                let mut payload = None;
397                let mut magic: Option<u64> = None;
398                let mut content_type = None;
399                let mut content_encoding = None;
400                let mut content_language = None;
401                let mut schema = None;
402                // the recognised keys guard themselves through set_once; an
403                // unknown key has no slot to be occupied, so its repeats are
404                // tracked here
405                let mut unknown_keys: BTreeSet<u64> = BTreeSet::new();
406                while match map.next_key::<u64>() {
407                    Ok(Some(key)) => {
408                        match key {
409                            0 => set_once(&mut payload, map.next_value()?, "payload")?,
410                            1 => set_once(&mut magic, map.next_value()?, "magic number")?,
411                            2 => set_once(&mut content_type, map.next_value()?, "content type")?,
412                            3 => set_once(
413                                &mut content_encoding,
414                                map.next_value()?,
415                                "content encoding",
416                            )?,
417                            4 => set_once(
418                                &mut content_language,
419                                map.next_value()?,
420                                "content language",
421                            )?,
422                            OA_SCHEMA_KEY => set_once(&mut schema, map.next_value()?, "schema")?,
423                            // the map structure exists so later conventions can
424                            // add indexes that older tooling adopts "or not" in
425                            // a backwards compatible way, so an index this
426                            // version does not know is skipped, not an error.
427                            // ยง5.6 does not ask whether the decoder understands
428                            // a key, so the repeat of one is still invalid even
429                            // though the value behind it is never read.
430                            _ => {
431                                if !unknown_keys.insert(key) {
432                                    return Err(serde::de::Error::custom(format!(
433                                        "duplicate map key: {key}"
434                                    )));
435                                }
436                                map.next_value::<serde::de::IgnoredAny>()?;
437                            }
438                        };
439                        true
440                    }
441                    Ok(None) => false,
442                    Err(error) => Err(error)?,
443                } {}
444                // Indexes 0 and 1 are mandatory. An item without them is not
445                // dropped like an unknown magic below: it cannot even be
446                // identified, and it sits inside a document whose prefix is
447                // the emitter claiming rain meta. One document is one emission
448                // from one emitter, and an emission carrying a malformed item
449                // is not mined for its readable parts - the same rule
450                // fetch_by_subject applies to a failed claim at the item
451                // payload level. A foreign magic is somebody else's data
452                // travelling alongside ours; a keyless map is a broken claim.
453                let (Some(payload), Some(magic_number)) = (payload, magic) else {
454                    return Err(serde::de::Error::custom(
455                        "cbor item omits a mandatory key (0 payload, 1 magic)",
456                    ));
457                };
458
459                // A nested document prefix is not somebody else's magic
460                // number, it is this crate's own in a position it cannot
461                // occupy: the document prefix is the first 8 bytes of the
462                // whole document, never an item's cbor key 1. That is
463                // malformed structure rather than a type this version does
464                // not read, so it stops the document instead of being
465                // dropped with the unknown numbers below.
466                // rainlanguage/rain.metadata#204.
467                if magic_number == KnownMagic::RainMetaDocumentV1 as u64 {
468                    return Err(serde::de::Error::custom(
469                        "rain meta document magic number as an item magic number",
470                    ));
471                }
472
473                let Ok(magic) = KnownMagic::try_from(magic_number) else {
474                    return Ok(ItemOrDropped::Dropped);
475                };
476
477                let content_type = content_type.unwrap_or(ContentType::None);
478                let content_encoding = content_encoding.unwrap_or(ContentEncoding::None);
479                let content_language = content_language.unwrap_or(ContentLanguage::None);
480
481                Ok(ItemOrDropped::Item(RainMetaDocumentV1Item {
482                    payload,
483                    magic,
484                    content_type,
485                    content_encoding,
486                    content_language,
487                    schema,
488                }))
489            }
490        }
491        deserializer.deserialize_map(EncodedMap)
492    }
493}
494
495/// Decoding one item on its own is strict.
496///
497/// The drop rule is about a *sequence*: an item nobody claimed this tool would
498/// read must not take the items beside it down with it. Asking for a single
499/// item names the thing you want, so not getting it is an error rather than a
500/// silent absence, and there is nothing beside it to protect.
501impl<'de> Deserialize<'de> for RainMetaDocumentV1Item {
502    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
503        match ItemOrDropped::deserialize(deserializer)? {
504            ItemOrDropped::Item(item) => Ok(item),
505            ItemOrDropped::Dropped => Err(serde::de::Error::custom(
506                "not a rain meta item this version reads: a mandatory key is missing or the magic number is unknown",
507            )),
508        }
509    }
510}
511
512/// searches for a meta matching the given hash in given subgraphs urls
513pub async fn search(hash: &str, subgraphs: &Vec<String>) -> Result<query::MetaResponse, Error> {
514    // future::select_ok panics on an empty iterator.
515    if subgraphs.is_empty() {
516        return Err(Error::NoRecordFound);
517    }
518    let request_body = query::MetaQuery::build_query(query::meta_query::Variables {
519        hash: Some(hash.to_ascii_lowercase()),
520    });
521    let mut promises = vec![];
522
523    let client = Arc::new(Client::builder().build().map_err(Error::ReqwestError)?);
524    for url in subgraphs {
525        promises.push(Box::pin(query::process_meta_query(
526            client.clone(),
527            &request_body,
528            url,
529        )));
530    }
531    let response_value = future::select_ok(promises.drain(..)).await?.0;
532    Ok(response_value)
533}
534
535/// checks if the given contract implements IDescribeByMetaV1 interface
536///
537/// `Err` is rain-erc's "answer unknown": a transport or decode failure stopped
538/// the probe, never a contract that lacks the interface. Both probes here can
539/// raise it - the erc165 one this delegates to rain-erc, and the interface id
540/// one below - and neither may be flattened into `Ok(false)`, which is the
541/// contract answering "no".
542pub async fn implements_i_described_by_meta_v1<P: Provider>(
543    provider: &P,
544    contract_address: Address,
545) -> Result<bool, Erc165Error> {
546    if !supports_erc165(provider, contract_address).await? {
547        return Ok(false);
548    }
549
550    let interface_id_res = IDescribedByMetaV1::IDescribedByMetaV1Calls::xor_selectors();
551    if interface_id_res.is_err() {
552        return Ok(false);
553    }
554
555    match IERC165::new(contract_address, provider)
556        .supportsInterface(interface_id_res.unwrap().into())
557        .call()
558        .await
559    {
560        Ok(supported) => Ok(supported),
561        Err(error)
562            if error.as_revert_data().is_some()
563                || matches!(error, ContractError::ZeroData(_, _)) =>
564        {
565            Ok(false)
566        }
567        Err(error) => Err(error.into()),
568    }
569}
570
571/// # Meta Storage(CAS)
572///
573/// In-memory CAS (content addressed storage) for Rain metadata which basically stores
574/// k/v pairs of meta hash and meta bytes, as well as providing functionalities to
575/// easliy read/write to the CAS.
576///
577/// Hashes are normal bytes and meta bytes are valid cbor encoded as data bytes.
578///
579/// ## Examples
580///
581/// ```
582/// use rain_metadata::Store;
583/// use rain_metadata::meta::cache::MetaCache;
584/// use std::collections::HashMap;
585///
586/// // to instantiate with an empty subgraph list
587/// let mut store = Store::new();
588///
589/// // or to instantiate with initial values
590/// let mut store = Store::create(
591///     &vec!["sg-url-1".to_string()],
592///     &MetaCache::default(),
593///     &HashMap::new(),
594/// );
595///
596/// // add a new subgraph endpoint url to the subgraph list
597/// store.add_subgraphs(&vec!["sg-url-2".to_string()]);
598///
599/// // merge another Store into this one
600/// store.merge(&Store::new());
601///
602/// // updates the meta store with some bytes and the hash they hash to - a
603/// // pair that does not is refused, so the hash is derived rather than picked
604/// let bytes = vec![0u8, 1u8];
605/// let hash = alloy::primitives::keccak256(&bytes).0.to_vec();
606/// store.update_with(&hash, &bytes).unwrap();
607///
608/// // `Store::update(&hash)` is async; it searches each subgraph for `hash` and
609/// // populates the cache with the result. Call it from an async context with `.await`.
610///
611/// // to get a record from the store
612/// let _meta = store.get_meta(&hash);
613///
614/// // Store is agnostic to dotrain contents โ€” it just maps the hash of the content
615/// // to the given uri and puts it as a new meta into the meta cache.
616/// let dotrain_uri = "path/to/file.rain";
617/// let dotrain_content = "/* some dotrain source */";
618/// let (_new_hash, _old_hash) = store
619///     .set_dotrain(dotrain_content, dotrain_uri, false)
620///     .unwrap();
621///
622/// // to get dotrain meta bytes given a uri
623/// let _dotrain_meta_bytes = store.get_dotrain_meta(dotrain_uri);
624/// ```
625#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
626pub struct Store {
627    subgraphs: Vec<String>,
628    cache: MetaCache,
629    dotrain_cache: HashMap<String, Vec<u8>>,
630}
631
632impl Default for Store {
633    fn default() -> Self {
634        Store::new()
635    }
636}
637
638impl Store {
639    /// lazily creates a new instance with no subgraphs
640    /// it is recommended to use create() instead with initial values
641    pub fn new() -> Store {
642        Store {
643            subgraphs: vec![],
644            cache: MetaCache::default(),
645            dotrain_cache: HashMap::new(),
646        }
647    }
648
649    /// creates new instance of Store with given initial values
650    /// it checks the validity of each item of the provided values and only stores those that are valid
651    pub fn create(
652        subgraphs: &Vec<String>,
653        cache: &MetaCache,
654        dotrain_cache: &HashMap<String, Vec<u8>>,
655    ) -> Store {
656        let mut store = Store::new();
657        store.add_subgraphs(subgraphs);
658        for (hash, bytes) in cache.iter() {
659            let _ = store.update_with(hash, bytes);
660        }
661        for (uri, hash) in dotrain_cache {
662            if !store.dotrain_cache.contains_key(uri) && store.cache.contains_key(hash) {
663                store.dotrain_cache.insert(uri.clone(), hash.clone());
664            }
665        }
666        store
667    }
668
669    /// all subgraph endpoints in this instance
670    pub fn subgraphs(&self) -> &Vec<String> {
671        &self.subgraphs
672    }
673
674    /// add new subgraph endpoints
675    pub fn add_subgraphs(&mut self, subgraphs: &Vec<String>) {
676        for sg in subgraphs {
677            if !self.subgraphs.contains(sg) {
678                self.subgraphs.push(sg.to_string());
679            }
680        }
681    }
682
683    /// getter method for the whole meta cache
684    pub fn cache(&self) -> &MetaCache {
685        &self.cache
686    }
687
688    /// get the corresponding meta bytes of the given hash if it exists
689    pub fn get_meta(&self, hash: &[u8]) -> Option<&Vec<u8>> {
690        self.cache.get(hash)
691    }
692
693    /// getter method for the whole dotrain cache
694    pub fn dotrain_cache(&self) -> &HashMap<String, Vec<u8>> {
695        &self.dotrain_cache
696    }
697
698    /// get the corresponding dotrain hash of the given dotrain uri if it exists
699    pub fn get_dotrain_hash(&self, uri: &str) -> Option<&Vec<u8>> {
700        self.dotrain_cache.get(uri)
701    }
702
703    /// get the corresponding uri of the given dotrain hash if it exists
704    pub fn get_dotrain_uri(&self, hash: &[u8]) -> Option<&String> {
705        for (uri, h) in &self.dotrain_cache {
706            if h == hash {
707                return Some(uri);
708            }
709        }
710        None
711    }
712
713    /// get the corresponding meta bytes of the given dotrain uri if it exists
714    pub fn get_dotrain_meta(&self, uri: &str) -> Option<&Vec<u8>> {
715        self.get_meta(self.dotrain_cache.get(uri)?)
716    }
717
718    /// deletes a dotrain record given a uri, `keep_meta = false` drops the meta
719    /// bytes only when no other dotrain uri still maps to them
720    pub fn delete_dotrain(&mut self, uri: &str, keep_meta: bool) {
721        if let Some(kv) = self.dotrain_cache.remove_entry(uri) {
722            if !keep_meta {
723                self.remove_unreferenced_meta(&kv.1);
724            }
725        };
726    }
727
728    /// call only once `dotrain_cache` no longer maps the dropped uri to `hash`
729    fn remove_unreferenced_meta(&mut self, hash: &[u8]) {
730        if !self.dotrain_cache.values().any(|h| h == hash) {
731            self.cache.remove(hash);
732        }
733    }
734
735    /// lazilly merges another Store to the current one, avoids duplicates
736    /// every map keeps the entry this Store already has on a key collision
737    pub fn merge(&mut self, other: &Store) {
738        self.add_subgraphs(&other.subgraphs);
739        for (hash, bytes) in other.cache.iter() {
740            if !self.cache.contains_key(hash) {
741                // entries are verified by construction, so copying one cannot
742                // introduce an unverified entry
743                let _ = self.cache.insert_verified(hash, bytes.clone());
744            }
745        }
746        for (uri, hash) in &other.dotrain_cache {
747            if !self.dotrain_cache.contains_key(uri) {
748                self.dotrain_cache.insert(uri.clone(), hash.clone());
749            }
750        }
751    }
752
753    /// Caches `bytes` under `hash` via [MetaCache::insert_verified], then
754    /// unpacks the items they carry into the cache too. The gate itself lives
755    /// on [MetaCache], which has no other way in.
756    fn insert_verified(&mut self, hash: &[u8], bytes: Vec<u8>) -> Result<&Vec<u8>, Error> {
757        self.cache.insert_verified(hash, bytes.clone())?;
758        self.store_content(&bytes);
759        self.get_meta(hash).ok_or(Error::NoRecordFound)
760    }
761
762    /// updates the meta cache by searching through all subgraphs for the given
763    /// hash, and returns the reference to the meta bytes in the cache if it was
764    /// found. Refreshes unconditionally; [Self::update_check] is the variant
765    /// that leaves an already cached hash alone.
766    pub async fn update(&mut self, hash: &[u8]) -> Result<&Vec<u8>, Error> {
767        let meta = search(&hex::encode_prefixed(hash), &self.subgraphs).await?;
768        self.insert_verified(hash, meta.bytes)
769    }
770
771    /// first checks if the meta is stored, if not will perform update()
772    pub async fn update_check(&mut self, hash: &[u8]) -> Result<&Vec<u8>, Error> {
773        // The NoRecordFound arm is unreachable, contains_key having just
774        // proved the key is present. It is spelled this way rather than as
775        // `if let Some(cached) = self.get_meta(hash)` because that holds an
776        // immutable borrow of self across the mutable call below it, which
777        // the borrow checker refuses.
778        if self.cache.contains_key(hash) {
779            return self.get_meta(hash).ok_or(Error::NoRecordFound);
780        }
781        self.update(hash).await
782    }
783
784    /// updates the meta cache with the given hash and meta bytes, and returns
785    /// the reference to the bytes if they were accepted. Leaves an already
786    /// cached hash alone, as [Self::update_check] does for the subgraph path.
787    pub fn update_with(&mut self, hash: &[u8], bytes: &[u8]) -> Result<&Vec<u8>, Error> {
788        // The NoRecordFound arm is unreachable, contains_key having just
789        // proved the key is present. It is spelled this way rather than as
790        // `if let Some(cached) = self.get_meta(hash)` because that holds an
791        // immutable borrow of self across the mutable call below it, which
792        // the borrow checker refuses.
793        if self.cache.contains_key(hash) {
794            return self.get_meta(hash).ok_or(Error::NoRecordFound);
795        }
796        self.insert_verified(hash, bytes.to_vec())
797    }
798
799    /// stores (or updates in case the URI already exists) the given dotrain text as meta into the store cache
800    /// and maps it to the given uri (path), it should be noted that reading the content of the dotrain is not in
801    /// the scope of Store and handling and passing on a correct URI (path) for the given text must be handled
802    /// externally by the implementer, `keep_old = false` drops the replaced meta
803    /// bytes only when no other dotrain uri still maps to them
804    pub fn set_dotrain(
805        &mut self,
806        text: &str,
807        uri: &str,
808        keep_old: bool,
809    ) -> Result<(Vec<u8>, Vec<u8>), Error> {
810        let bytes = RainMetaDocumentV1Item {
811            payload: serde_bytes::ByteBuf::from(text.as_bytes()),
812            magic: KnownMagic::DotrainV1,
813            content_type: ContentType::OctetStream,
814            content_encoding: ContentEncoding::None,
815            content_language: ContentLanguage::None,
816            schema: None,
817        }
818        .cbor_encode()?;
819        let new_hash = keccak256(&bytes).0.to_vec();
820        if let Some(h) = self.dotrain_cache.get(uri) {
821            let old_hash = h.clone();
822            if new_hash == old_hash {
823                self.cache.insert_verified(&new_hash, bytes)?;
824                Ok((new_hash, vec![]))
825            } else {
826                self.cache.insert_verified(&new_hash, bytes)?;
827                self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
828                if !keep_old {
829                    self.remove_unreferenced_meta(&old_hash);
830                }
831                Ok((new_hash, old_hash))
832            }
833        } else {
834            self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
835            self.cache.insert_verified(&new_hash, bytes)?;
836            Ok((new_hash, vec![]))
837        }
838    }
839
840    /// decodes each meta and stores the inner meta items into the cache
841    /// if any of the inner items is an authoring meta, stores it in authoring meta cache as well
842    /// returns the reference to the authoring bytes if the meta bytes contained any
843    fn store_content(&mut self, bytes: &[u8]) {
844        if let Ok(meta_maps) = RainMetaDocumentV1Item::cbor_decode(bytes) {
845            if bytes.starts_with(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes()) {
846                for meta_map in &meta_maps {
847                    if let Ok(encoded_bytes) = meta_map.cbor_encode() {
848                        // the key is this item's own digest, so the gate can
849                        // only pass - routing through it anyway means no
850                        // reader has to work that out
851                        let _ = self
852                            .cache
853                            .insert_verified(&keccak256(&encoded_bytes).0, encoded_bytes);
854                    }
855                }
856            }
857        }
858    }
859}
860
861/// converts string to bytes32
862///
863/// Right padding with `0u8` is the encoding, so [`bytes32_to_str`] ends the
864/// string at the first `0u8` and cannot carry one. An input holding a nul is
865/// rejected rather than round tripped into a shorter string.
866pub fn str_to_bytes32(text: &str) -> Result<[u8; 32], Error> {
867    let bytes: &[u8] = text.as_bytes();
868    if bytes.len() > 32 {
869        return Err(Error::BiggerThan32Bytes);
870    }
871    if bytes.contains(&0u8) {
872        return Err(Error::NulByteInInput);
873    }
874    let mut b32 = [0u8; 32];
875    b32[..bytes.len()].copy_from_slice(bytes);
876    Ok(b32)
877}
878
879/// converts bytes32 to string
880pub fn bytes32_to_str(bytes: &[u8; 32]) -> Result<&str, Error> {
881    let mut len = 32;
882    if let Some((pos, _)) = itertools::Itertools::find_position(&mut bytes.iter(), |b| **b == 0u8) {
883        len = pos;
884    };
885    Ok(std::str::from_utf8(&bytes[..len])?)
886}
887
888#[cfg(all(test, not(target_family = "wasm")))]
889mod tests {
890    use super::{
891        *, bytes32_to_str, magic::KnownMagic, str_to_bytes32, types::authoring::v1::AuthoringMeta,
892        ContentEncoding, ContentLanguage, ContentType, Error, RainMetaDocumentV1Item,
893    };
894    use alloy::providers::ProviderBuilder;
895    use alloy::{providers::mock::Asserter, rpc::json_rpc::ErrorPayload, sol_types::SolType};
896    use serde_json::json;
897
898    /// Roundtrip test for an authoring meta
899    /// original content -> pack -> MetaMap -> cbor encode -> cbor decode -> MetaMap -> unpack -> original content,
900    #[test]
901    fn authoring_meta_roundtrip() -> Result<(), Error> {
902        let authoring_meta_content = r#"[
903            {
904                "word": "stack",
905                "description": "Copies an existing value from the stack.",
906                "operandParserOffset": 16
907            },
908            {
909                "word": "constant",
910                "description": "Copies a constant value onto the stack.",
911                "operandParserOffset": 16
912            }
913        ]"#;
914        let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
915
916        // abi encode the authoring meta with performing validation
917        let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
918        let expected_abi_encoded = <alloy::sol!((bytes32, uint8, string)[])>::abi_encode(&vec![
919            (
920                str_to_bytes32("stack")?,
921                16u8,
922                "Copies an existing value from the stack.".to_string(),
923            ),
924            (
925                str_to_bytes32("constant")?,
926                16u8,
927                "Copies a constant value onto the stack.".to_string(),
928            ),
929        ]);
930        // check the encoded bytes agaiinst the expected
931        assert_eq!(authoring_meta_abi_encoded, expected_abi_encoded);
932
933        let meta_map = RainMetaDocumentV1Item {
934            payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
935            magic: KnownMagic::AuthoringMetaV1,
936            content_type: ContentType::Cbor,
937            content_encoding: ContentEncoding::None,
938            content_language: ContentLanguage::None,
939            schema: None,
940        };
941        let cbor_encoded = meta_map.cbor_encode()?;
942
943        // cbor map with 3 keys
944        assert_eq!(cbor_encoded[0], 0xa3);
945        // key 0
946        assert_eq!(cbor_encoded[1], 0x00);
947        // major type 2 (bytes) length 512
948        assert_eq!(cbor_encoded[2], 0b010_11001);
949        assert_eq!(cbor_encoded[3], 0b000_00010);
950        assert_eq!(cbor_encoded[4], 0b000_00000);
951        // payload
952        assert_eq!(cbor_encoded[5..517], authoring_meta_abi_encoded);
953        // key 1
954        assert_eq!(cbor_encoded[517], 0x01);
955        // major type 0 (unsigned integer) value 27
956        assert_eq!(cbor_encoded[518], 0b000_11011);
957        // magic number
958        assert_eq!(
959            &cbor_encoded[519..527],
960            KnownMagic::AuthoringMetaV1.to_prefix_bytes()
961        );
962        // key 2
963        assert_eq!(cbor_encoded[527], 0x02);
964        // text string application/cbor length 16
965        assert_eq!(cbor_encoded[528], 0b011_10000);
966        // the string application/cbor, must be the end of data
967        assert_eq!(&cbor_encoded[529..], "application/cbor".as_bytes());
968
969        // decode the data back to MetaMap
970        let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
971        // the length of decoded maps must be 1 as we only had 1 encoded item
972        assert_eq!(cbor_decoded.len(), 1);
973        // decoded item must be equal to the original meta_map
974        assert_eq!(cbor_decoded[0], meta_map);
975
976        Ok(())
977    }
978
979    /// Roundtrip test for a dotrain meta
980    /// original content -> pack -> MetaMap -> cbor encode -> cbor decode -> MetaMap -> unpack -> original content,
981    #[test]
982    fn dotrain_meta_roundtrip() -> Result<(), Error> {
983        let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
984        let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
985
986        let content_encoding = ContentEncoding::Deflate;
987        let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
988
989        let meta_map = RainMetaDocumentV1Item {
990            payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
991            magic: KnownMagic::DotrainV1,
992            content_type: ContentType::OctetStream,
993            content_encoding,
994            content_language: ContentLanguage::En,
995            schema: None,
996        };
997        let cbor_encoded = meta_map.cbor_encode()?;
998
999        // cbor map with 5 keys
1000        assert_eq!(cbor_encoded[0], 0xa5);
1001        // key 0
1002        assert_eq!(cbor_encoded[1], 0x00);
1003        // major type 2 (bytes) length 36
1004        assert_eq!(cbor_encoded[2], 0b010_11000);
1005        assert_eq!(cbor_encoded[3], 0b001_00100);
1006        // assert_eq!(cbor_encoded[4], 0b000_00000);
1007        // payload
1008        assert_eq!(cbor_encoded[4..40], deflated_payload);
1009        // key 1
1010        assert_eq!(cbor_encoded[40], 0x01);
1011        // major type 0 (unsigned integer) value 27
1012        assert_eq!(cbor_encoded[41], 0b000_11011);
1013        // magic number
1014        assert_eq!(
1015            &cbor_encoded[42..50],
1016            KnownMagic::DotrainV1.to_prefix_bytes()
1017        );
1018        // key 2
1019        assert_eq!(cbor_encoded[50], 0x02);
1020        // text string application/octet-stream length 24
1021        assert_eq!(cbor_encoded[51], 0b011_11000);
1022        assert_eq!(cbor_encoded[52], 0b000_11000);
1023        // the string application/octet-stream
1024        assert_eq!(&cbor_encoded[53..77], "application/octet-stream".as_bytes());
1025        // key 3
1026        assert_eq!(cbor_encoded[77], 0x03);
1027        // text string deflate length 7
1028        assert_eq!(cbor_encoded[78], 0b011_00111);
1029        // the string deflate
1030        assert_eq!(&cbor_encoded[79..86], "deflate".as_bytes());
1031        // key 4
1032        assert_eq!(cbor_encoded[86], 0x04);
1033        // text string en length 2
1034        assert_eq!(cbor_encoded[87], 0b011_00010);
1035        // the string identity, must be the end of data
1036        assert_eq!(&cbor_encoded[88..], "en".as_bytes());
1037
1038        // decode the data back to MetaMap
1039        let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1040        // the length of decoded maps must be 1 as we only had 1 encoded item
1041        assert_eq!(cbor_decoded.len(), 1);
1042        // decoded item must be equal to the original meta_map
1043        assert_eq!(cbor_decoded[0], meta_map);
1044
1045        Ok(())
1046    }
1047
1048    /// Roundtrip test for a meta sequence
1049    /// original content -> pack -> MetaMap -> cbor encode -> cbor decode -> MetaMap -> unpack -> original content,
1050    #[test]
1051    fn meta_seq_roundtrip() -> Result<(), Error> {
1052        let authoring_meta_content = r#"[
1053            {
1054                "word": "stack",
1055                "description": "Copies an existing value from the stack.",
1056                "operandParserOffset": 16
1057            },
1058            {
1059                "word": "constant",
1060                "description": "Copies a constant value onto the stack.",
1061                "operandParserOffset": 16
1062            }
1063        ]"#;
1064        let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
1065        let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
1066        let meta_map_1 = RainMetaDocumentV1Item {
1067            payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
1068            magic: KnownMagic::AuthoringMetaV1,
1069            content_type: ContentType::Cbor,
1070            content_encoding: ContentEncoding::None,
1071            content_language: ContentLanguage::None,
1072            schema: None,
1073        };
1074
1075        let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
1076        let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
1077        let content_encoding = ContentEncoding::Deflate;
1078        let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
1079        let meta_map_2 = RainMetaDocumentV1Item {
1080            payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
1081            magic: KnownMagic::DotrainV1,
1082            content_type: ContentType::OctetStream,
1083            content_encoding,
1084            content_language: ContentLanguage::En,
1085            schema: None,
1086        };
1087
1088        // cbor encode as RainMetaDocument sequence
1089        let cbor_encoded = RainMetaDocumentV1Item::cbor_encode_seq(
1090            &vec![meta_map_1.clone(), meta_map_2.clone()],
1091            KnownMagic::RainMetaDocumentV1,
1092        )?;
1093
1094        // 8 byte magic number prefix
1095        assert_eq!(
1096            &cbor_encoded[0..8],
1097            KnownMagic::RainMetaDocumentV1.to_prefix_bytes()
1098        );
1099
1100        // first item in the encoded bytes
1101        // cbor map with 3 keys
1102        assert_eq!(cbor_encoded[8], 0xa3);
1103        // key 0
1104        assert_eq!(cbor_encoded[9], 0x00);
1105        // major type 2 (bytes) length 512
1106        assert_eq!(cbor_encoded[10], 0b010_11001);
1107        assert_eq!(cbor_encoded[11], 0b000_00010);
1108        assert_eq!(cbor_encoded[12], 0b000_00000);
1109        // payload
1110        assert_eq!(cbor_encoded[13..525], authoring_meta_abi_encoded);
1111        // key 1
1112        assert_eq!(cbor_encoded[525], 0x01);
1113        // major type 0 (unsigned integer) value 27
1114        assert_eq!(cbor_encoded[526], 0b000_11011);
1115        // magic number
1116        assert_eq!(
1117            &cbor_encoded[527..535],
1118            KnownMagic::AuthoringMetaV1.to_prefix_bytes()
1119        );
1120        // key 2
1121        assert_eq!(cbor_encoded[535], 0x02);
1122        // text string application/cbor length 16
1123        assert_eq!(cbor_encoded[536], 0b011_10000);
1124        // the string application/cbor, must be the end of data
1125        assert_eq!(&cbor_encoded[537..553], "application/cbor".as_bytes());
1126
1127        // second item in the encoded bytes
1128        // cbor map with 5 keys
1129        assert_eq!(cbor_encoded[553], 0xa5);
1130        // key 0
1131        assert_eq!(cbor_encoded[554], 0x00);
1132        // major type 2 (bytes) length 36
1133        assert_eq!(cbor_encoded[555], 0b010_11000);
1134        assert_eq!(cbor_encoded[556], 0b001_00100);
1135        // assert_eq!(cbor_encoded[4], 0b000_00000);
1136        // payload
1137        assert_eq!(cbor_encoded[557..593], deflated_payload);
1138        // key 1
1139        assert_eq!(cbor_encoded[593], 0x01);
1140        // major type 0 (unsigned integer) value 27
1141        assert_eq!(cbor_encoded[594], 0b000_11011);
1142        // magic number
1143        assert_eq!(
1144            &cbor_encoded[595..603],
1145            KnownMagic::DotrainV1.to_prefix_bytes()
1146        );
1147        // key 2
1148        assert_eq!(cbor_encoded[603], 0x02);
1149        // text string application/octet-stream length 24
1150        assert_eq!(cbor_encoded[604], 0b011_11000);
1151        assert_eq!(cbor_encoded[605], 0b000_11000);
1152        // the string application/octet-stream
1153        assert_eq!(
1154            &cbor_encoded[606..630],
1155            "application/octet-stream".as_bytes()
1156        );
1157        // key 3
1158        assert_eq!(cbor_encoded[630], 0x03);
1159        // text string deflate length 7
1160        assert_eq!(cbor_encoded[631], 0b011_00111);
1161        // the string deflate
1162        assert_eq!(&cbor_encoded[632..639], "deflate".as_bytes());
1163        // key 4
1164        assert_eq!(cbor_encoded[639], 0x04);
1165        // text string en length 2
1166        assert_eq!(cbor_encoded[640], 0b011_00010);
1167        // the string identity, must be the end of data
1168        assert_eq!(&cbor_encoded[641..], "en".as_bytes());
1169
1170        // decode the data back to MetaMap
1171        let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1172        // the length of decoded maps must be 2 as we had 2 encoded item
1173        assert_eq!(cbor_decoded.len(), 2);
1174
1175        // decoded item 1 must be equal to the original meta_map_1
1176        assert_eq!(cbor_decoded[0], meta_map_1);
1177        // decoded item 2 must be equal to the original meta_map_2
1178        assert_eq!(cbor_decoded[1], meta_map_2);
1179
1180        Ok(())
1181    }
1182
1183    #[test]
1184    fn test_bytes32_to_str() {
1185        let text_bytes_list = vec![
1186            (
1187                "",
1188                hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1189            ),
1190            (
1191                "A",
1192                hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1193            ),
1194            (
1195                "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1196                hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1197            ),
1198            (
1199                "!@#$%^&*(),./;'[]",
1200                hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1201            ),
1202        ];
1203
1204        for (text, bytes) in text_bytes_list {
1205            assert_eq!(text, bytes32_to_str(&bytes).unwrap());
1206        }
1207    }
1208
1209    #[test]
1210    fn test_str_to_bytes32() {
1211        let text_bytes_list = vec![
1212            (
1213                "",
1214                hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1215            ),
1216            (
1217                "A",
1218                hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1219            ),
1220            (
1221                "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1222                hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1223            ),
1224            (
1225                "!@#$%^&*(),./;'[]",
1226                hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1227            ),
1228        ];
1229
1230        for (text, bytes) in text_bytes_list {
1231            assert_eq!(bytes, str_to_bytes32(text).unwrap());
1232        }
1233    }
1234
1235    #[test]
1236    fn test_str_to_bytes32_long() {
1237        assert!(matches!(
1238            str_to_bytes32("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456").unwrap_err(),
1239            Error::BiggerThan32Bytes
1240        ));
1241    }
1242
1243    /// A nul cannot survive the padding convention bytes32_to_str decodes, so
1244    /// it is rejected on the way in wherever it sits, including the pair the
1245    /// issue collides ("a" and "a\0").
1246    #[test]
1247    fn test_str_to_bytes32_rejects_nul() {
1248        for text in [
1249            "\0",
1250            "\0a",
1251            "a\0",
1252            "a\0b",
1253            "abcdefghijklmnopqrstuvwxyz01234\0",
1254        ] {
1255            assert!(
1256                matches!(str_to_bytes32(text), Err(Error::NulByteInInput)),
1257                "nul bearing input {:?} accepted",
1258                text
1259            );
1260        }
1261    }
1262
1263    /// Everything str_to_bytes32 accepts comes back out of bytes32_to_str
1264    /// unchanged, and no two of them share a bytes32.
1265    #[test]
1266    fn test_str_to_bytes32_round_trip() -> Result<(), Error> {
1267        let mut seen: Vec<[u8; 32]> = vec![];
1268        for text in [
1269            "",
1270            "a",
1271            "stack",
1272            "!@#$%^&*(),./;'[]",
1273            "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1274        ] {
1275            let bytes = str_to_bytes32(text)?;
1276            assert_eq!(bytes32_to_str(&bytes)?, text);
1277            assert!(!seen.contains(&bytes), "input {:?} collided", text);
1278            seen.push(bytes);
1279        }
1280        Ok(())
1281    }
1282
1283    #[tokio::test]
1284    async fn test_implements_i_describe_by_meta_v1() {
1285        // makes new server/client with success response for erc165 check
1286        async fn new_server_client() -> (Asserter, impl Provider) {
1287            let asserter = Asserter::new();
1288            let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
1289
1290            // Mock a responses for successful supports erc165 check
1291            asserter.push_success(
1292                &"0x0000000000000000000000000000000000000000000000000000000000000001",
1293            );
1294            asserter.push_success(
1295                &"0x0000000000000000000000000000000000000000000000000000000000000000",
1296            );
1297
1298            (asserter, provider)
1299        }
1300
1301        let address = Address::random();
1302
1303        // mock a true response for implements IDescribedByMetaV1
1304        let (asserter, provider) = new_server_client().await;
1305        asserter
1306            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
1307        let result = implements_i_described_by_meta_v1(&provider, address)
1308            .await
1309            .unwrap();
1310        assert!(result);
1311
1312        // mock a false response for implements IDescribedByMetaV1
1313        let (asserter, provider) = new_server_client().await;
1314        asserter
1315            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
1316        let result = implements_i_described_by_meta_v1(&provider, address)
1317            .await
1318            .unwrap();
1319        assert!(!result);
1320
1321        // mock a revert response for implements IDescribedByMetaV1
1322        let (asserter, provider) = new_server_client().await;
1323        asserter.push_failure(ErrorPayload {
1324            code: -32003,
1325            message: "execution reverted".into(),
1326            data: Some(serde_json::value::to_raw_value(&json!("0x00")).unwrap()),
1327        });
1328        let result = implements_i_described_by_meta_v1(&provider, address)
1329            .await
1330            .unwrap();
1331        assert!(!result);
1332    }
1333
1334    /// Roundtrip test for a meta map carrying the OaSchema magic number as an
1335    /// additional CBOR map key beyond the standard 0-4 keys.
1336    /// MetaMap (with schema) -> cbor encode -> cbor decode -> MetaMap, assert equality
1337    #[test]
1338    fn oa_schema_map_key_roundtrip() -> Result<(), Error> {
1339        let payload = vec![0x01, 0x02, 0x03];
1340        // an IPFS hash referencing the schema of the payload, as written by
1341        // the SFT frontend under the OaSchema map key
1342        let schema = "QmSchemaHash1234567890abcdefghijklmnopqrstuvwx".to_string();
1343        assert_eq!(schema.len(), 46);
1344
1345        let meta_map = RainMetaDocumentV1Item {
1346            payload: serde_bytes::ByteBuf::from(payload.clone()),
1347            magic: KnownMagic::OaStructure,
1348            content_type: ContentType::Json,
1349            content_encoding: ContentEncoding::Deflate,
1350            content_language: ContentLanguage::None,
1351            schema: Some(schema.clone()),
1352        };
1353        let cbor_encoded = meta_map.cbor_encode()?;
1354
1355        // cbor map with 5 keys (0, 1, 2, 3 and the OaSchema magic)
1356        assert_eq!(cbor_encoded[0], 0xa5);
1357        // key 0
1358        assert_eq!(cbor_encoded[1], 0x00);
1359        // major type 2 (bytes) length 3
1360        assert_eq!(cbor_encoded[2], 0b010_00011);
1361        // payload
1362        assert_eq!(cbor_encoded[3..6], payload);
1363        // key 1
1364        assert_eq!(cbor_encoded[6], 0x01);
1365        // major type 0 (unsigned integer) value 27
1366        assert_eq!(cbor_encoded[7], 0b000_11011);
1367        // magic number
1368        assert_eq!(
1369            &cbor_encoded[8..16],
1370            KnownMagic::OaStructure.to_prefix_bytes()
1371        );
1372        // key 2
1373        assert_eq!(cbor_encoded[16], 0x02);
1374        // text string application/json length 16
1375        assert_eq!(cbor_encoded[17], 0b011_10000);
1376        assert_eq!(&cbor_encoded[18..34], "application/json".as_bytes());
1377        // key 3
1378        assert_eq!(cbor_encoded[34], 0x03);
1379        // text string deflate length 7
1380        assert_eq!(cbor_encoded[35], 0b011_00111);
1381        assert_eq!(&cbor_encoded[36..43], "deflate".as_bytes());
1382        // the OaSchema magic as key, major type 0 (unsigned integer) value 27
1383        assert_eq!(cbor_encoded[43], 0b000_11011);
1384        assert_eq!(
1385            &cbor_encoded[44..52],
1386            KnownMagic::OaSchema.to_prefix_bytes()
1387        );
1388        // schema value, text string length 46
1389        assert_eq!(cbor_encoded[52], 0b011_11000);
1390        assert_eq!(cbor_encoded[53], 46);
1391        // the schema hash string, must be the end of data
1392        assert_eq!(&cbor_encoded[54..], schema.as_bytes());
1393
1394        // decode the data back to MetaMap
1395        let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1396        // the length of decoded maps must be 1 as we only had 1 encoded item
1397        assert_eq!(cbor_decoded.len(), 1);
1398        // decoded item must be equal to the original meta_map
1399        assert_eq!(cbor_decoded[0], meta_map);
1400
1401        Ok(())
1402    }
1403
1404    /// A meta map without the schema key must keep encoding exactly as before
1405    /// (no schema entry on the wire) and roundtrip with schema None
1406    #[test]
1407    fn no_schema_key_encodes_as_before_roundtrip() -> Result<(), Error> {
1408        let payload = vec![0x0a, 0x0b];
1409        let meta_map = RainMetaDocumentV1Item {
1410            payload: serde_bytes::ByteBuf::from(payload.clone()),
1411            magic: KnownMagic::OaStructure,
1412            content_type: ContentType::None,
1413            content_encoding: ContentEncoding::None,
1414            content_language: ContentLanguage::None,
1415            schema: None,
1416        };
1417        let cbor_encoded = meta_map.cbor_encode()?;
1418
1419        // cbor map with only the 2 mandatory keys
1420        assert_eq!(cbor_encoded[0], 0xa2);
1421        // key 0
1422        assert_eq!(cbor_encoded[1], 0x00);
1423        // major type 2 (bytes) length 2
1424        assert_eq!(cbor_encoded[2], 0b010_00010);
1425        // payload
1426        assert_eq!(cbor_encoded[3..5], payload);
1427        // key 1
1428        assert_eq!(cbor_encoded[5], 0x01);
1429        // major type 0 (unsigned integer) value 27
1430        assert_eq!(cbor_encoded[6], 0b000_11011);
1431        // magic number, must be the end of data
1432        assert_eq!(
1433            &cbor_encoded[7..],
1434            KnownMagic::OaStructure.to_prefix_bytes()
1435        );
1436
1437        let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1438        assert_eq!(cbor_decoded.len(), 1);
1439        assert_eq!(cbor_decoded[0], meta_map);
1440
1441        Ok(())
1442    }
1443
1444    /// A map key this version has no meaning for is a future index, so it is
1445    /// skipped and the rest of the map decodes
1446    #[test]
1447    fn unknown_map_key_index_is_ignored() -> Result<(), Error> {
1448        let mut bytes: Vec<u8> = vec![
1449            // cbor map with 3 keys
1450            0xa3, // key 0, bytes payload of length 0
1451            0x00, 0x40, // key 1, unsigned integer magic number
1452            0x01, 0x1b,
1453        ];
1454        bytes.extend_from_slice(&KnownMagic::DotrainSourceV1.to_prefix_bytes());
1455        // key 5, a plausible future index, unsigned integer value 7
1456        bytes.extend_from_slice(&[0x05, 0x07]);
1457
1458        let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1459        assert_eq!(decoded.len(), 1);
1460        assert_eq!(decoded[0], plain_item(KnownMagic::DotrainSourceV1, vec![]));
1461
1462        Ok(())
1463    }
1464
1465    /// A magic number other than OaSchema used as an extra map key is a future
1466    /// magic keyed entry, skipped the same way, and leaves schema unset
1467    #[test]
1468    fn non_oa_schema_extra_map_key_is_ignored() -> Result<(), Error> {
1469        // build a map identical to a valid 2 key meta map but with an extra
1470        // OaHashList magic key carrying a text string
1471        let mut bytes: Vec<u8> = vec![
1472            // cbor map with 3 keys
1473            0xa3, // key 0, bytes payload of length 1
1474            0x00, 0x41, 0xff, // key 1, unsigned integer magic number
1475            0x01, 0x1b,
1476        ];
1477        bytes.extend_from_slice(&KnownMagic::OaStructure.to_prefix_bytes());
1478        // the OaHashList magic as key
1479        bytes.push(0x1b);
1480        bytes.extend_from_slice(&KnownMagic::OaHashList.to_prefix_bytes());
1481        // text string value of length 2
1482        bytes.extend_from_slice(&[0x62, 0x68, 0x69]);
1483
1484        let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1485        assert_eq!(decoded.len(), 1);
1486        let expected = plain_item(KnownMagic::OaStructure, vec![0xff]);
1487        assert_eq!(decoded[0], expected);
1488        assert_eq!(decoded[0].schema, None);
1489
1490        Ok(())
1491    }
1492
1493    /// The whole value of an unknown key is consumed however nested, so the
1494    /// item that follows it in the sequence still decodes
1495    #[test]
1496    fn unknown_map_key_consumes_its_whole_value() -> Result<(), Error> {
1497        let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1498        bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1499        // key 5, value {42: [1, 2]}
1500        bytes.extend_from_slice(&[0x05, 0xa1, 0x18, 0x2a, 0x82, 0x01, 0x02]);
1501        bytes.extend_from_slice(&handwritten_map());
1502
1503        let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1504        assert_eq!(decoded.len(), 2);
1505        let expected = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1506        assert_eq!(decoded[0], expected);
1507        assert_eq!(decoded[1], expected);
1508
1509        Ok(())
1510    }
1511
1512    /// An ignored key is not re-encoded, so the item's hash is the hash of what
1513    /// this version can represent and not of the bytes it decoded
1514    #[test]
1515    fn ignored_map_key_is_absent_from_the_reencoding() -> Result<(), Error> {
1516        let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1517        bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1518        bytes.extend_from_slice(&[0x05, 0x07]);
1519
1520        let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1521        assert_eq!(decoded[0].cbor_encode()?, handwritten_map());
1522        assert_eq!(decoded[0].hash(false)?, keccak256(handwritten_map()).0);
1523        assert_ne!(decoded[0].hash(false)?, keccak256(&bytes).0);
1524
1525        Ok(())
1526    }
1527
1528    /// Only integer keys are indexes. The spec rules out the HTTP header names
1529    /// as keys, so a key that is not an unsigned integer is not a future index
1530    /// to skip over
1531    #[test]
1532    fn non_integer_map_key_errors() {
1533        let mut text_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1534        text_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1535        // key "5", unsigned integer value 7
1536        text_key.extend_from_slice(&[0x61, 0x35, 0x07]);
1537        assert!(matches!(
1538            RainMetaDocumentV1Item::cbor_decode(&text_key),
1539            Err(Error::SerdeCborError(_))
1540        ));
1541
1542        let mut negative_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1543        negative_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1544        // key -1, unsigned integer value 7
1545        negative_key.extend_from_slice(&[0x20, 0x07]);
1546        assert!(matches!(
1547            RainMetaDocumentV1Item::cbor_decode(&negative_key),
1548            Err(Error::SerdeCborError(_))
1549        ));
1550    }
1551
1552    /// An unknown key is skipped, never counted as one of the mandatory keys,
1553    /// so a map carrying one instead of key 0 is still missing its payload and
1554    /// corrupts the document rather than decoding with the unknown key standing in.
1555    #[test]
1556    fn unknown_map_key_does_not_stand_in_for_a_mandatory_key() {
1557        let mut bytes: Vec<u8> = vec![0xa2, 0x05, 0x07, 0x01, 0x1b];
1558        bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1559        assert!(RainMetaDocumentV1Item::cbor_decode(&bytes).is_err());
1560    }
1561
1562    fn plain_item(magic: KnownMagic, payload: Vec<u8>) -> RainMetaDocumentV1Item {
1563        RainMetaDocumentV1Item {
1564            payload: serde_bytes::ByteBuf::from(payload),
1565            magic,
1566            content_type: ContentType::None,
1567            content_encoding: ContentEncoding::None,
1568            content_language: ContentLanguage::None,
1569            schema: None,
1570        }
1571    }
1572
1573    // ---- helpers for the CAS / search tests ----
1574
1575    fn sample_authoring_doc() -> (AuthoringMeta, Vec<u8>) {
1576        let authoring_meta: AuthoringMeta = serde_json::from_str(
1577            r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
1578        )
1579        .unwrap();
1580        let abi = authoring_meta.abi_encode_validate().unwrap();
1581        let item = RainMetaDocumentV1Item {
1582            payload: serde_bytes::ByteBuf::from(abi),
1583            magic: KnownMagic::AuthoringMetaV1,
1584            content_type: ContentType::Cbor,
1585            content_encoding: ContentEncoding::None,
1586            content_language: ContentLanguage::None,
1587            schema: None,
1588        };
1589        let doc =
1590            RainMetaDocumentV1Item::cbor_encode_seq(&vec![item], KnownMagic::RainMetaDocumentV1)
1591                .unwrap();
1592        (authoring_meta, doc)
1593    }
1594
1595    fn sample_dotrain_item() -> RainMetaDocumentV1Item {
1596        RainMetaDocumentV1Item {
1597            payload: serde_bytes::ByteBuf::from("some dotrain body".as_bytes()),
1598            magic: KnownMagic::DotrainV1,
1599            content_type: ContentType::OctetStream,
1600            content_encoding: ContentEncoding::None,
1601            content_language: ContentLanguage::None,
1602            schema: None,
1603        }
1604    }
1605
1606    /// Handwritten canonical cbor for {0: h'01', 1: DotrainV1 magic}, written
1607    /// out byte by byte from the cbor spec, independent of cbor_encode.
1608    fn handwritten_map() -> Vec<u8> {
1609        vec![
1610            0xa2, // map(2)
1611            0x00, // key 0
1612            0x41, 0x01, // bytes(1) 0x01
1613            0x01, // key 1
1614            0x1b, 0xff, 0xda, 0xc2, 0xf2, 0xf3, 0x7b, 0xe8, 0x94, // u64 DotrainV1
1615        ]
1616    }
1617
1618    /// hash(false) is keccak256 of the bare cbor map and hash(true) is
1619    /// keccak256 of the rain meta document prefix followed by the same map,
1620    /// pinned against independently handwritten bytes.
1621    #[test]
1622    fn test_hash_bare_vs_document() -> Result<(), Error> {
1623        let map_bytes = handwritten_map();
1624        let mut doc_bytes: Vec<u8> = vec![0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74];
1625        doc_bytes.extend_from_slice(&map_bytes);
1626
1627        let item = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1628        assert_eq!(item.hash(false)?, keccak256(&map_bytes).0);
1629        assert_eq!(item.hash(true)?, keccak256(&doc_bytes).0);
1630        assert_ne!(item.hash(false)?, item.hash(true)?);
1631        Ok(())
1632    }
1633
1634    /// Empty input and a bare document prefix with no items are corrupt metas.
1635    #[test]
1636    fn test_cbor_decode_empty_is_corrupt() {
1637        assert!(matches!(
1638            RainMetaDocumentV1Item::cbor_decode(&[]),
1639            Err(Error::CorruptMeta)
1640        ));
1641        let prefix = KnownMagic::RainMetaDocumentV1.to_prefix_bytes();
1642        assert!(matches!(
1643            RainMetaDocumentV1Item::cbor_decode(&prefix),
1644            Err(Error::CorruptMeta)
1645        ));
1646    }
1647
1648    /// A valid map followed by truncated trailing bytes must not decode: the
1649    /// data does not end exactly at the last complete item.
1650    #[test]
1651    fn test_cbor_decode_trailing_truncated_is_corrupt() {
1652        let mut bytes = handwritten_map();
1653        bytes.push(0x1b); // u64 header with all 8 payload bytes missing
1654        assert!(matches!(
1655            RainMetaDocumentV1Item::cbor_decode(&bytes),
1656            Err(Error::CorruptMeta)
1657        ));
1658    }
1659
1660    /// Every way an item can run out of bytes is corrupt meta, not a serde
1661    /// cbor error: a truncated sole item, a map header promising entries the
1662    /// input does not carry, and a truncated item after a complete one.
1663    #[test]
1664    fn test_cbor_decode_truncated_item_is_corrupt() {
1665        let mut sole = handwritten_map();
1666        sole.pop();
1667        assert!(matches!(
1668            RainMetaDocumentV1Item::cbor_decode(&sole),
1669            Err(Error::CorruptMeta)
1670        ));
1671
1672        assert!(matches!(
1673            RainMetaDocumentV1Item::cbor_decode(&[0xa2, 0x00, 0x41, 0x01]),
1674            Err(Error::CorruptMeta)
1675        ));
1676
1677        let mut after_complete = handwritten_map();
1678        after_complete.extend_from_slice(&[0xa2, 0x00]);
1679        assert!(matches!(
1680            RainMetaDocumentV1Item::cbor_decode(&after_complete),
1681            Err(Error::CorruptMeta)
1682        ));
1683    }
1684
1685    /// A valid map followed by a byte that is not valid cbor surfaces the
1686    /// serde cbor error.
1687    #[test]
1688    fn test_cbor_decode_trailing_garbage_errors() {
1689        let mut bytes = handwritten_map();
1690        bytes.push(0xff); // lone break byte
1691        assert!(matches!(
1692            RainMetaDocumentV1Item::cbor_decode(&bytes),
1693            Err(Error::SerdeCborError(_))
1694        ));
1695    }
1696
1697    /// A map without the mandatory payload key 0 corrupts the document, and
1698    /// takes an item beside it down too: one document is one emission from
1699    /// one emitter, and an emission carrying a broken claim is not mined for
1700    /// its readable parts.
1701    #[test]
1702    fn test_cbor_decode_missing_payload_is_corrupt() {
1703        let mut bytes: Vec<u8> = vec![0xa1, 0x01, 0x1b]; // {1: DotrainV1}
1704        bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1705        assert!(RainMetaDocumentV1Item::cbor_decode(&bytes).is_err());
1706
1707        // beside a good item, the good item is not recovered
1708        let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1709            .cbor_encode()
1710            .unwrap();
1711        let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1712        document.extend_from_slice(&bytes);
1713        document.extend_from_slice(&good);
1714        assert!(RainMetaDocumentV1Item::cbor_decode(&document).is_err());
1715    }
1716
1717    /// A map without the mandatory magic key 1, likewise.
1718    #[test]
1719    fn test_cbor_decode_missing_magic_is_corrupt() {
1720        let bytes: Vec<u8> = vec![0xa1, 0x00, 0x41, 0x01]; // {0: h'01'}
1721        assert!(RainMetaDocumentV1Item::cbor_decode(&bytes).is_err());
1722
1723        let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1724            .cbor_encode()
1725            .unwrap();
1726        let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1727        document.extend_from_slice(&bytes);
1728        document.extend_from_slice(&good);
1729        assert!(RainMetaDocumentV1Item::cbor_decode(&document).is_err());
1730    }
1731
1732    /// A map carrying a magic number this version does not know is dropped,
1733    /// not an error: the magic is the format's extension point, and somebody
1734    /// else's well formed item travelling in the same document is not
1735    /// corruption. Alone it leaves nothing to return, which is CorruptMeta.
1736    #[test]
1737    fn test_cbor_decode_unknown_magic_is_dropped() {
1738        let mut bytes: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1739        bytes.extend_from_slice(&0xdeadbeefdeadbeefu64.to_be_bytes());
1740        assert!(matches!(
1741            RainMetaDocumentV1Item::cbor_decode(&bytes),
1742            Err(Error::CorruptMeta)
1743        ));
1744    }
1745
1746    /// The unknown magic drop protects the items beside it: the foreign item
1747    /// first, so a decoder that stopped at it would return nothing, and the
1748    /// good item behind it still decodes.
1749    #[test]
1750    fn test_cbor_decode_drops_only_the_unknown_magic_item() {
1751        let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1752            .cbor_encode()
1753            .unwrap();
1754
1755        let mut unknown_magic: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1756        unknown_magic.extend_from_slice(&0xdeadbeefdeadbeefu64.to_be_bytes());
1757
1758        let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1759        document.extend_from_slice(&unknown_magic);
1760        document.extend_from_slice(&good);
1761
1762        let items = RainMetaDocumentV1Item::cbor_decode(&document).unwrap();
1763        assert_eq!(items.len(), 1, "expected only the good item");
1764        assert_eq!(items[0].magic, KnownMagic::DotrainV1);
1765        assert_eq!(items[0].payload.as_ref(), &[0x42]);
1766    }
1767
1768    /// The document prefix is not a magic number an item may carry: it is the
1769    /// first 8 bytes of a whole document. An item claiming it is malformed
1770    /// structure rather than a type this version does not read, so it stops
1771    /// the document instead of being dropped like an unknown number.
1772    #[test]
1773    fn test_cbor_decode_nested_document_magic_is_not_dropped() {
1774        let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1775            .cbor_encode()
1776            .unwrap();
1777
1778        let mut nested: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1779        nested.extend_from_slice(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes());
1780
1781        let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1782        document.extend_from_slice(&nested);
1783        document.extend_from_slice(&good);
1784
1785        assert!(matches!(
1786            RainMetaDocumentV1Item::cbor_decode(&document),
1787            Err(Error::SerdeCborError(_))
1788        ));
1789    }
1790
1791    /// A cbor map header plus the given already encoded key/value pairs,
1792    /// written per the cbor spec rather than through the encoder, so a map can
1793    /// carry a key twice which no encoder emits.
1794    fn handwritten_entries(entries: &[(Vec<u8>, Vec<u8>)]) -> Vec<u8> {
1795        assert!(entries.len() < 24);
1796        let mut bytes = vec![0xa0 | entries.len() as u8];
1797        for (key, value) in entries {
1798            bytes.extend_from_slice(key);
1799            bytes.extend_from_slice(value);
1800        }
1801        bytes
1802    }
1803
1804    /// cbor unsigned integer of the magic number, as a map key or a value.
1805    fn handwritten_magic(magic: KnownMagic) -> Vec<u8> {
1806        let mut bytes = vec![0x1b];
1807        bytes.extend_from_slice(&magic.to_prefix_bytes());
1808        bytes
1809    }
1810
1811    /// cbor text string of a string shorter than 24 bytes.
1812    fn handwritten_text(text: &str) -> Vec<u8> {
1813        assert!(text.len() < 24);
1814        let mut bytes = vec![0x60 | text.len() as u8];
1815        bytes.extend_from_slice(text.as_bytes());
1816        bytes
1817    }
1818
1819    /// The repro from #191: a map repeating key 0 must not decode with the
1820    /// last payload winning.
1821    #[test]
1822    fn test_cbor_decode_duplicate_payload_key_errors() {
1823        let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x00, 0x41, 0x02, 0x01, 0x1b];
1824        bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1825        let error = RainMetaDocumentV1Item::cbor_decode(&bytes).unwrap_err();
1826        assert!(matches!(error, Error::SerdeCborError(_)));
1827        assert!(
1828            error.to_string().contains("duplicate field `payload`"),
1829            "{error}"
1830        );
1831    }
1832
1833    /// RFC 8949 ยง5.6: every recognised key is rejected when the map carries it
1834    /// twice, whether the repeated value differs from the first or matches it.
1835    #[test]
1836    fn test_cbor_decode_duplicate_any_key_errors() -> Result<(), Error> {
1837        let cases: [(Vec<u8>, Vec<u8>, Vec<u8>); 6] = [
1838            (vec![0x00], vec![0x41, 0x01], vec![0x41, 0x02]),
1839            (
1840                vec![0x01],
1841                handwritten_magic(KnownMagic::DotrainV1),
1842                handwritten_magic(KnownMagic::RainlangV1),
1843            ),
1844            (
1845                vec![0x02],
1846                handwritten_text("application/cbor"),
1847                handwritten_text("application/json"),
1848            ),
1849            (
1850                vec![0x03],
1851                handwritten_text("identity"),
1852                handwritten_text("deflate"),
1853            ),
1854            (vec![0x04], handwritten_text("en"), handwritten_text("none")),
1855            (
1856                handwritten_magic(KnownMagic::OaSchema),
1857                handwritten_text("hi"),
1858                handwritten_text("bye"),
1859            ),
1860        ];
1861        let base: Vec<(Vec<u8>, Vec<u8>)> = cases
1862            .iter()
1863            .map(|(key, value, _)| (key.clone(), value.clone()))
1864            .collect();
1865
1866        let decoded = RainMetaDocumentV1Item::cbor_decode(&handwritten_entries(&base))?;
1867        assert_eq!(decoded.len(), 1);
1868        assert_eq!(decoded[0].payload.as_ref(), &[0x01]);
1869        assert_eq!(decoded[0].magic, KnownMagic::DotrainV1);
1870        assert_eq!(decoded[0].content_type, ContentType::Cbor);
1871        assert_eq!(decoded[0].content_encoding, ContentEncoding::Identity);
1872        assert_eq!(decoded[0].content_language, ContentLanguage::En);
1873        assert_eq!(decoded[0].schema.as_deref(), Some("hi"));
1874
1875        for (key, value, other_value) in &cases {
1876            for repeated in [value, other_value] {
1877                let mut entries = base.clone();
1878                entries.push((key.clone(), repeated.clone()));
1879                let error = RainMetaDocumentV1Item::cbor_decode(&handwritten_entries(&entries))
1880                    .unwrap_err();
1881                assert!(
1882                    matches!(error, Error::SerdeCborError(_)),
1883                    "{key:?} {error:?}"
1884                );
1885                assert!(
1886                    error.to_string().contains("duplicate field"),
1887                    "{key:?} {error}"
1888                );
1889            }
1890        }
1891        Ok(())
1892    }
1893
1894    /// An index this version has no meaning for is skipped rather than
1895    /// rejected, but RFC 8949 ยง5.6 does not ask whether a key is understood:
1896    /// a map that carries an unknown index twice is invalid too, whether that
1897    /// index is a plain integer or a magic number other than OaSchema.
1898    #[test]
1899    fn test_cbor_decode_duplicate_unknown_key_errors() -> Result<(), Error> {
1900        let base: Vec<(Vec<u8>, Vec<u8>)> = vec![
1901            (vec![0x00], vec![0x41, 0x01]),
1902            (vec![0x01], handwritten_magic(KnownMagic::DotrainV1)),
1903        ];
1904        // key 5 as a plausible future index and the OaHashList magic as a
1905        // future magic keyed entry, each with the value cbor to repeat it with
1906        let unknown: [(Vec<u8>, Vec<u8>, Vec<u8>); 2] = [
1907            (vec![0x05], vec![0x07], vec![0x08]),
1908            (
1909                handwritten_magic(KnownMagic::OaHashList),
1910                handwritten_text("hi"),
1911                handwritten_text("bye"),
1912            ),
1913        ];
1914
1915        for (key, value, other_value) in &unknown {
1916            let mut entries = base.clone();
1917            entries.push((key.clone(), value.clone()));
1918            let decoded = RainMetaDocumentV1Item::cbor_decode(&handwritten_entries(&entries))?;
1919            assert_eq!(decoded, vec![plain_item(KnownMagic::DotrainV1, vec![0x01])]);
1920
1921            for repeated in [value, other_value] {
1922                let mut duplicated = entries.clone();
1923                duplicated.push((key.clone(), repeated.clone()));
1924                let error = RainMetaDocumentV1Item::cbor_decode(&handwritten_entries(&duplicated))
1925                    .unwrap_err();
1926                assert!(
1927                    matches!(error, Error::SerdeCborError(_)),
1928                    "{key:?} {error:?}"
1929                );
1930                assert!(
1931                    error.to_string().contains("duplicate map key"),
1932                    "{key:?} {error}"
1933                );
1934            }
1935        }
1936        Ok(())
1937    }
1938
1939    /// A handwritten item map carrying the rain meta document magic under key
1940    /// 1 decodes, so accepting the document magic as an item magic is the
1941    /// decoder's own behaviour and not an artefact of this crate's encoder.
1942    #[test]
1943    fn test_cbor_decode_handwritten_document_magic_item() {
1944        let bytes: Vec<u8> = vec![
1945            0xa2, // map(2)
1946            0x00, // key 0
1947            0x41, 0x01, // bytes(1) 0x01
1948            0x01, // key 1
1949            0x1b, 0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74, // u64 RainMetaDocumentV1
1950        ];
1951        // The document magic in an item's magic position is structurally
1952        // invalid, so the meta carrying it does not decode.
1953        // rainlanguage/rain.metadata#204.
1954        assert!(RainMetaDocumentV1Item::cbor_decode(&bytes).is_err());
1955    }
1956
1957    /// The document magic as an item's own magic marks a payload that is
1958    /// itself a complete rain meta document, which
1959    /// `OrderBuilderStateV1::extract_from_meta` recurses into, so the codec
1960    /// must carry such an item in both directions and leave its payload byte
1961    /// for byte intact.
1962    #[test]
1963    fn test_document_magic_item_carries_a_nested_document() -> Result<(), Error> {
1964        let inner = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1965        let inner_doc = RainMetaDocumentV1Item::cbor_encode_seq(
1966            &vec![inner.clone()],
1967            KnownMagic::RainMetaDocumentV1,
1968        )?;
1969        let outer = plain_item(KnownMagic::RainMetaDocumentV1, inner_doc.clone());
1970        let outer_doc = RainMetaDocumentV1Item::cbor_encode_seq(
1971            &vec![outer.clone()],
1972            KnownMagic::RainMetaDocumentV1,
1973        )?;
1974
1975        // Encoding can still write the document magic into an item's magic
1976        // position, and the payload really is a whole document. Decoding
1977        // refuses it anyway: a nested document is not a shape to descend into,
1978        // it is a corrupt meta, and the usable item inside does not rescue it.
1979        // rainlanguage/rain.metadata#204.
1980        assert!(RainMetaDocumentV1Item::cbor_decode(&outer_doc).is_err());
1981        assert_eq!(
1982            RainMetaDocumentV1Item::cbor_decode(&inner_doc)?,
1983            vec![inner]
1984        );
1985        Ok(())
1986    }
1987
1988    /// Nesting is not a leaf meta type: the unpack layer rejects the document
1989    /// magic so that no payload conversion is ever handed a whole document.
1990    #[test]
1991    fn test_document_magic_item_is_not_unpackable() {
1992        assert!(matches!(
1993            KnownMeta::try_from(KnownMagic::RainMetaDocumentV1),
1994            Err(Error::UnsupportedMeta)
1995        ));
1996        assert!(matches!(
1997            plain_item(KnownMagic::RainMetaDocumentV1, vec![0x01]).unpack_into::<Vec<u8>>(),
1998            Err(Error::UnsupportedMeta)
1999        ));
2000    }
2001
2002    /// unpack decodes the payload according to the content encoding.
2003    #[test]
2004    fn test_unpack_decodes_content_encoding() -> Result<(), Error> {
2005        let content = b"unpack me via deflate".to_vec();
2006        let packed = ContentEncoding::Deflate.encode(&content);
2007        assert_ne!(packed, content);
2008        let mut item = plain_item(KnownMagic::DotrainV1, packed);
2009        item.content_encoding = ContentEncoding::Deflate;
2010        assert_eq!(item.unpack()?, content);
2011
2012        let item = plain_item(KnownMagic::DotrainV1, content.clone());
2013        assert_eq!(item.unpack()?, content);
2014        Ok(())
2015    }
2016
2017    /// The 13 meta magics unpack; the document magic, the web data magic and
2018    /// the Oa magics are rejected with UnsupportedMeta.
2019    #[test]
2020    fn test_unpack_into_whitelist() {
2021        use strum::IntoEnumIterator;
2022        let supported = [
2023            KnownMagic::OpMetaV1,
2024            KnownMagic::DotrainV1,
2025            KnownMagic::RainlangV1,
2026            KnownMagic::SolidityAbiV2,
2027            KnownMagic::AuthoringMetaV1,
2028            KnownMagic::AuthoringMetaV2,
2029            KnownMagic::AddressList,
2030            KnownMagic::InterpreterCallerMetaV1,
2031            KnownMagic::ExpressionDeployerV2BytecodeV1,
2032            KnownMagic::DotrainSourceV1,
2033            KnownMagic::OrderBuilderStateV1,
2034            KnownMagic::RainlangSourceV1,
2035            KnownMagic::RaindexSignedContextOracleV1,
2036        ];
2037        for magic in supported {
2038            let unpacked: Vec<u8> = plain_item(magic, vec![0x61]).unpack_into().unwrap();
2039            assert_eq!(unpacked, vec![0x61], "{:?}", magic);
2040        }
2041        let unsupported = [
2042            KnownMagic::RainMetaDocumentV1,
2043            KnownMagic::WebDataV1,
2044            KnownMagic::OaSchema,
2045            KnownMagic::OaHashList,
2046            KnownMagic::OaStructure,
2047            KnownMagic::OaTokenImage,
2048            KnownMagic::OaTokenCredentialLinks,
2049        ];
2050        for magic in unsupported {
2051            let result: Result<Vec<u8>, Error> = plain_item(magic, vec![0x61]).unpack_into();
2052            assert!(matches!(result, Err(Error::UnsupportedMeta)), "{:?}", magic);
2053        }
2054        // together the two lists cover every variant
2055        assert_eq!(
2056            supported.len() + unsupported.len(),
2057            KnownMagic::iter().count()
2058        );
2059    }
2060
2061    /// Invalid utf8 payloads error when unpacking into String rather than
2062    /// being replaced lossily.
2063    #[test]
2064    fn test_try_into_string_invalid_utf8_errors() {
2065        let item = plain_item(KnownMagic::DotrainV1, vec![0xff, 0xfe]);
2066        let result: Result<String, Error> = item.try_into();
2067        assert!(matches!(result, Err(Error::FromUtf8Error(_))));
2068    }
2069
2070    /// Unpacking into Vec<u8> decodes the content encoding first.
2071    #[test]
2072    fn test_try_into_vec_decodes_encoding() -> Result<(), Error> {
2073        let content = b"raw bytecode bytes \x00\x01\x02".to_vec();
2074        let packed = ContentEncoding::Deflate.encode(&content);
2075        let mut item = plain_item(KnownMagic::ExpressionDeployerV2BytecodeV1, packed.clone());
2076        item.content_encoding = ContentEncoding::Deflate;
2077        let unpacked: Vec<u8> = item.try_into()?;
2078        assert_eq!(unpacked, content);
2079        assert_ne!(unpacked, packed);
2080        Ok(())
2081    }
2082
2083    /// Deflate encode produces a zlib stream (RFC1950 CMF byte 0x78) that is
2084    /// actually compressed and roundtrips through decode.
2085    #[test]
2086    fn test_content_encoding_deflate_roundtrip() -> Result<(), Error> {
2087        let content = b"hello rain deflate fixture hello rain deflate fixture".to_vec();
2088        let encoded = ContentEncoding::Deflate.encode(&content);
2089        assert_ne!(encoded, content);
2090        assert_eq!(encoded[0], 0x78);
2091        assert_eq!(ContentEncoding::Deflate.decode(&encoded)?, content);
2092        Ok(())
2093    }
2094
2095    /// None and Identity pass data through unchanged on encode and decode.
2096    #[test]
2097    fn test_content_encoding_passthrough() -> Result<(), Error> {
2098        let data = vec![0x00, 0xff, 0x10];
2099        for encoding in [ContentEncoding::None, ContentEncoding::Identity] {
2100            assert_eq!(encoding.encode(&data), data, "{:?}", encoding);
2101            assert_eq!(encoding.decode(&data)?, data, "{:?}", encoding);
2102        }
2103        Ok(())
2104    }
2105
2106    /// Decode accepts a zlib stream and falls back to a raw deflate stream.
2107    /// Fixtures generated out of band from "hello rain deflate fixture".
2108    #[test]
2109    fn test_content_encoding_decode_fixtures() -> Result<(), Error> {
2110        let content = b"hello rain deflate fixture".to_vec();
2111        let zlib: Vec<u8> = vec![
2112            120, 156, 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44,
2113            73, 85, 72, 203, 172, 40, 41, 45, 74, 5, 0, 132, 64, 9, 251,
2114        ];
2115        let raw: Vec<u8> = vec![
2116            203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44, 73, 85, 72,
2117            203, 172, 40, 41, 45, 74, 5, 0,
2118        ];
2119        assert_eq!(ContentEncoding::Deflate.decode(&zlib)?, content);
2120        assert_eq!(ContentEncoding::Deflate.decode(&raw)?, content);
2121        Ok(())
2122    }
2123
2124    /// Data that is neither a zlib stream nor a raw deflate stream errors
2125    /// with InflateError instead of returning bytes.
2126    #[test]
2127    fn test_content_encoding_decode_garbage_errors() {
2128        let garbage = [0xffu8, 0xff, 0xff, 0xff];
2129        assert!(matches!(
2130            ContentEncoding::Deflate.decode(&garbage),
2131            Err(Error::InflateError(_))
2132        ));
2133    }
2134
2135    /// The CLI-facing strum names for the content headers are kebab-case.
2136    #[test]
2137    fn test_content_headers_strum_names() {
2138        use std::str::FromStr;
2139        assert_eq!(
2140            ContentEncoding::from_str("deflate").unwrap(),
2141            ContentEncoding::Deflate
2142        );
2143        assert_eq!(
2144            ContentEncoding::from_str("identity").unwrap(),
2145            ContentEncoding::Identity
2146        );
2147        assert_eq!(
2148            ContentEncoding::from_str("none").unwrap(),
2149            ContentEncoding::None
2150        );
2151        assert_eq!(ContentEncoding::Deflate.to_string(), "deflate");
2152        assert_eq!(
2153            ContentType::from_str("octet-stream").unwrap(),
2154            ContentType::OctetStream
2155        );
2156        assert_eq!(ContentType::from_str("json").unwrap(), ContentType::Json);
2157        assert_eq!(ContentType::Json.to_string(), "json");
2158        assert_eq!(
2159            ContentLanguage::from_str("en").unwrap(),
2160            ContentLanguage::En
2161        );
2162    }
2163
2164    /// Every documented meta magic maps to its KnownMeta while the document
2165    /// magic and the Oa magics are unsupported.
2166    #[test]
2167    fn test_known_meta_try_from_magic() {
2168        let cases: [(KnownMagic, KnownMeta); 13] = [
2169            (KnownMagic::OpMetaV1, KnownMeta::OpV1),
2170            (KnownMagic::DotrainV1, KnownMeta::DotrainV1),
2171            (KnownMagic::RainlangV1, KnownMeta::RainlangV1),
2172            (KnownMagic::SolidityAbiV2, KnownMeta::SolidityAbiV2),
2173            (KnownMagic::AuthoringMetaV1, KnownMeta::AuthoringMetaV1),
2174            (KnownMagic::AuthoringMetaV2, KnownMeta::AuthoringMetaV2),
2175            (KnownMagic::AddressList, KnownMeta::AddressList),
2176            (
2177                KnownMagic::InterpreterCallerMetaV1,
2178                KnownMeta::InterpreterCallerMetaV1,
2179            ),
2180            (
2181                KnownMagic::ExpressionDeployerV2BytecodeV1,
2182                KnownMeta::ExpressionDeployerV2BytecodeV1,
2183            ),
2184            (KnownMagic::RainlangSourceV1, KnownMeta::RainlangSourceV1),
2185            (KnownMagic::DotrainSourceV1, KnownMeta::DotrainSourceV1),
2186            (
2187                KnownMagic::OrderBuilderStateV1,
2188                KnownMeta::OrderBuilderStateV1,
2189            ),
2190            (
2191                KnownMagic::RaindexSignedContextOracleV1,
2192                KnownMeta::RaindexSignedContextOracleV1,
2193            ),
2194        ];
2195        for (magic, meta) in cases {
2196            assert_eq!(KnownMeta::try_from(magic).unwrap(), meta, "{:?}", magic);
2197        }
2198        for magic in [
2199            KnownMagic::RainMetaDocumentV1,
2200            KnownMagic::WebDataV1,
2201            KnownMagic::OaSchema,
2202            KnownMagic::OaHashList,
2203            KnownMagic::OaStructure,
2204            KnownMagic::OaTokenImage,
2205            KnownMagic::OaTokenCredentialLinks,
2206        ] {
2207            assert!(
2208                matches!(KnownMeta::try_from(magic), Err(Error::UnsupportedMeta)),
2209                "{:?}",
2210                magic
2211            );
2212        }
2213    }
2214
2215    /// KnownMeta parses from and displays as the kebab-case names used by the
2216    /// CLI (validate --meta, build, schema show).
2217    #[test]
2218    fn test_known_meta_strum_parse_display() {
2219        use std::str::FromStr;
2220        assert_eq!(
2221            KnownMeta::from_str("solidity-abi-v2").unwrap(),
2222            KnownMeta::SolidityAbiV2
2223        );
2224        assert_eq!(
2225            KnownMeta::from_str("interpreter-caller-meta-v1").unwrap(),
2226            KnownMeta::InterpreterCallerMetaV1
2227        );
2228        assert_eq!(KnownMeta::SolidityAbiV2.to_string(), "solidity-abi-v2");
2229    }
2230
2231    /// search() lowercases the hash before building the query variables.
2232    #[tokio::test]
2233    async fn test_search_lowercases_hash() {
2234        use httpmock::prelude::*;
2235        let (_, doc) = sample_authoring_doc();
2236        let hash_upper = format!("0x{}", "AB".repeat(32));
2237        let server = MockServer::start();
2238        let mock = server.mock(|when, then| {
2239            when.method(POST)
2240                .body_contains(hash_upper.to_ascii_lowercase());
2241            then.status(200).json_body(json!({
2242                "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2243            }));
2244        });
2245        let response = search(&hash_upper, &vec![server.url("/sg")]).await.unwrap();
2246        assert_eq!(response.bytes, doc);
2247        mock.assert();
2248    }
2249
2250    /// search() queries every subgraph and the first success wins even when
2251    /// an earlier subgraph fails.
2252    #[tokio::test]
2253    async fn test_search_first_success_wins() {
2254        use httpmock::prelude::*;
2255        let (_, doc) = sample_authoring_doc();
2256        let bad = MockServer::start();
2257        let _bad_mock = bad.mock(|when, then| {
2258            when.method(POST);
2259            then.status(500).body("subgraph down");
2260        });
2261        let good = MockServer::start();
2262        let _good_mock = good.mock(|when, then| {
2263            when.method(POST);
2264            then.status(200).json_body(json!({
2265                "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2266            }));
2267        });
2268        let response = search(
2269            &format!("0x{}", "11".repeat(32)),
2270            &vec![bad.url("/sg"), good.url("/sg")],
2271        )
2272        .await
2273        .unwrap();
2274        assert_eq!(response.bytes, doc);
2275    }
2276
2277    /// An empty subgraph list has nothing to fan out to, so the search
2278    /// reports a miss rather than reaching futures::select_ok, which panics on
2279    /// an empty iterator.
2280    #[tokio::test]
2281    async fn test_search_empty_subgraphs_is_a_miss() {
2282        let hash = format!("0x{}", "33".repeat(32));
2283        assert!(matches!(
2284            search(&hash, &vec![]).await,
2285            Err(Error::NoRecordFound)
2286        ));
2287    }
2288
2289    /// The erc165 gate short circuits: neither answer reaches the
2290    /// IDescribedByMetaV1 supportsInterface call, so the queued "true" is
2291    /// never consumed and cannot be mistaken for the contract's own answer.
2292    ///
2293    /// The two answers are not the same fact. A contract that says no is
2294    /// `Ok(false)`; a probe that could not finish is an error, because a
2295    /// transport failure is not a contract declining an interface. rain-erc
2296    /// makes that distinction itself - "callers can treat that as answer
2297    /// unknown rather than silently reading no support" - and an
2298    /// `unwrap_or(false)` here threw it away.
2299    #[tokio::test]
2300    async fn test_implements_erc165_gate_short_circuits() {
2301        let address = Address::random();
2302
2303        // erc165 check1 answers false
2304        let asserter = Asserter::new();
2305        let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2306        asserter
2307            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2308        asserter
2309            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2310        assert!(!implements_i_described_by_meta_v1(&provider, address)
2311            .await
2312            .unwrap());
2313
2314        // erc165 probe errors. Getting an error back is itself the proof of
2315        // the short circuit: had the queued "true" been consumed by the
2316        // interface probe, this would be Ok(true).
2317        let asserter = Asserter::new();
2318        let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2319        asserter.push_failure(ErrorPayload {
2320            code: -32000,
2321            message: "connection reset".into(),
2322            data: None,
2323        });
2324        asserter
2325            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2326        assert!(implements_i_described_by_meta_v1(&provider, address)
2327            .await
2328            .is_err());
2329    }
2330
2331    /// An empty eth_call response is a contract that did not answer, which
2332    /// ERC-165 reads as "does not implement".
2333    #[tokio::test]
2334    async fn test_implements_empty_response_is_false() {
2335        let address = Address::random();
2336        let asserter = Asserter::new();
2337        let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2338        asserter
2339            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2340        asserter
2341            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2342        asserter.push_success(&"0x");
2343        assert!(!implements_i_described_by_meta_v1(&provider, address)
2344            .await
2345            .unwrap());
2346    }
2347
2348    /// A non-revert failure of the IDescribedByMetaV1 supportsInterface call is
2349    /// "answer unknown": it propagates as Err rather than reading as "does not
2350    /// implement".
2351    #[tokio::test]
2352    async fn test_implements_described_by_call_error_is_unknown() {
2353        let address = Address::random();
2354        let asserter = Asserter::new();
2355        let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2356        asserter
2357            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2358        asserter
2359            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2360        asserter.push_failure(ErrorPayload {
2361            code: -32005,
2362            message: "rate limit exceeded".into(),
2363            data: None,
2364        });
2365        let error = implements_i_described_by_meta_v1(&provider, address)
2366            .await
2367            .unwrap_err();
2368        assert!(error.to_string().contains("rate limit exceeded"));
2369    }
2370
2371    /// A non-empty IDescribedByMetaV1 supportsInterface response that does not
2372    /// decode as bool is a decode failure, so "answer unknown" rather than
2373    /// "does not implement".
2374    #[tokio::test]
2375    async fn test_implements_undecodable_response_is_unknown() {
2376        let address = Address::random();
2377        let asserter = Asserter::new();
2378        let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2379        asserter
2380            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2381        asserter
2382            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2383        asserter.push_success(&"0xdeadbeef");
2384        implements_i_described_by_meta_v1(&provider, address)
2385            .await
2386            .unwrap_err();
2387    }
2388
2389    /// No constructor injects a subgraph the caller did not ask for, and a
2390    /// store with none resolves every network lookup to None rather than
2391    /// reaching the select_ok panic.
2392    #[tokio::test]
2393    async fn test_store_constructors_inject_no_subgraphs() {
2394        assert!(Store::new().subgraphs().is_empty());
2395        assert!(Store::default().subgraphs().is_empty());
2396        assert!(
2397            Store::create(&vec![], &MetaCache::default(), &HashMap::new())
2398                .subgraphs()
2399                .is_empty()
2400        );
2401
2402        let hash = [0u8; 32];
2403        let mut store = Store::default();
2404        assert!(store.update(&hash).await.is_err());
2405    }
2406
2407    /// create() takes only the given subgraphs, and keeps a dotrain uri only
2408    /// when its hash is present in the cache.
2409    ///
2410    /// This used to assert create() dropped a cache entry whose bytes did not
2411    /// hash to its key. That entry is no longer constructible: create() takes
2412    /// a [MetaCache], which has no way to hold one, so there is nothing left
2413    /// for create() to validate.
2414    #[test]
2415    fn test_store_create_validates_entries() {
2416        let (_, doc) = sample_authoring_doc();
2417        let good_hash = keccak256(&doc).0.to_vec();
2418        let mut cache = MetaCache::default();
2419        cache.insert_verified(&good_hash, doc.clone()).unwrap();
2420        let mut dotrain_cache = HashMap::new();
2421        dotrain_cache.insert("a.rain".to_string(), good_hash.clone());
2422        dotrain_cache.insert("missing.rain".to_string(), vec![0x44u8; 32]);
2423
2424        let store = Store::create(
2425            &vec!["https://example.com/custom-sg".to_string()],
2426            &cache,
2427            &dotrain_cache,
2428        );
2429
2430        assert_eq!(
2431            store.subgraphs(),
2432            &vec!["https://example.com/custom-sg".to_string()]
2433        );
2434        assert_eq!(store.get_meta(&good_hash), Some(&doc));
2435        assert_eq!(store.get_dotrain_hash("a.rain"), Some(&good_hash));
2436        assert_eq!(store.get_dotrain_hash("missing.rain"), None);
2437    }
2438
2439    /// add_subgraphs skips urls already present.
2440    #[test]
2441    fn test_store_add_subgraphs_dedupe() {
2442        let mut store = Store::new();
2443        store.add_subgraphs(&vec!["sg-a".to_string()]);
2444        store.add_subgraphs(&vec!["sg-a".to_string(), "sg-b".to_string()]);
2445        assert_eq!(
2446            store.subgraphs(),
2447            &vec!["sg-a".to_string(), "sg-b".to_string()]
2448        );
2449    }
2450
2451    /// set_dotrain on a fresh uri returns (new_hash, empty), keyed by the
2452    /// keccak of the cbor encoded DotrainV1 meta item, and every dotrain
2453    /// getter resolves it.
2454    #[test]
2455    fn test_store_dotrain_getters_and_set_fresh() {
2456        let mut store = Store::new();
2457        let text = "some dotrain content";
2458        let (hash, old) = store.set_dotrain(text, "file.rain", false).unwrap();
2459        assert!(old.is_empty());
2460        let expected_item = RainMetaDocumentV1Item {
2461            payload: serde_bytes::ByteBuf::from(text.as_bytes()),
2462            magic: KnownMagic::DotrainV1,
2463            content_type: ContentType::OctetStream,
2464            content_encoding: ContentEncoding::None,
2465            content_language: ContentLanguage::None,
2466            schema: None,
2467        };
2468        let expected_bytes = expected_item.cbor_encode().unwrap();
2469        assert_eq!(hash, keccak256(&expected_bytes).0.to_vec());
2470        assert_eq!(store.get_dotrain_hash("file.rain"), Some(&hash));
2471        assert_eq!(store.get_dotrain_uri(&hash), Some(&"file.rain".to_string()));
2472        assert_eq!(store.get_dotrain_meta("file.rain"), Some(&expected_bytes));
2473        assert_eq!(store.get_dotrain_hash("other.rain"), None);
2474        assert_eq!(store.get_dotrain_uri(&[0u8; 32]), None);
2475        assert_eq!(store.get_dotrain_meta("other.rain"), None);
2476    }
2477
2478    /// set_dotrain branches: same content keeps the meta and reports no old
2479    /// hash; different content remaps the uri and drops or keeps the old
2480    /// meta per keep_old.
2481    #[test]
2482    fn test_store_set_dotrain_branches() {
2483        let mut store = Store::new();
2484        let (hash_one, _) = store.set_dotrain("text one", "a.rain", false).unwrap();
2485
2486        // same content again: same hash, no old hash, meta retained
2487        let (hash_same, old_same) = store.set_dotrain("text one", "a.rain", false).unwrap();
2488        assert_eq!(hash_same, hash_one);
2489        assert!(old_same.is_empty());
2490        assert!(store.get_meta(&hash_one).is_some());
2491
2492        // different content, keep_old = false: remap and drop the old meta
2493        let (hash_two, old_two) = store.set_dotrain("text two", "a.rain", false).unwrap();
2494        assert_ne!(hash_two, hash_one);
2495        assert_eq!(old_two, hash_one);
2496        assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_two));
2497        assert!(store.get_meta(&hash_one).is_none());
2498        assert!(store.get_meta(&hash_two).is_some());
2499
2500        // different content, keep_old = true: old meta kept
2501        let (hash_three, old_three) = store.set_dotrain("text three", "a.rain", true).unwrap();
2502        assert_eq!(old_three, hash_two);
2503        assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_three));
2504        assert!(store.get_meta(&hash_two).is_some());
2505        assert!(store.get_meta(&hash_three).is_some());
2506    }
2507
2508    /// delete_dotrain removes the uri mapping and honors keep_meta for the
2509    /// cached meta bytes.
2510    #[test]
2511    fn test_store_delete_dotrain_keep_meta() {
2512        let mut store = Store::new();
2513        let (hash, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2514        store.delete_dotrain("d.rain", false);
2515        assert_eq!(store.get_dotrain_hash("d.rain"), None);
2516        assert!(store.get_meta(&hash).is_none());
2517
2518        let (hash_again, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2519        store.delete_dotrain("d.rain", true);
2520        assert_eq!(store.get_dotrain_hash("d.rain"), None);
2521        assert!(store.get_meta(&hash_again).is_some());
2522    }
2523
2524    /// two uris holding identical text share one cache entry, so dropping one
2525    /// of them must leave the other's meta resolvable, and only the last uri
2526    /// off the hash takes the meta with it.
2527    #[test]
2528    fn test_store_dotrain_shared_meta_survives_delete() {
2529        let mut store = Store::new();
2530        let (hash, _) = store.set_dotrain("same text", "a.rain", false).unwrap();
2531        let (hash_b, _) = store.set_dotrain("same text", "b.rain", false).unwrap();
2532        assert_eq!(hash_b, hash);
2533
2534        store.delete_dotrain("a.rain", false);
2535        assert_eq!(store.get_dotrain_hash("a.rain"), None);
2536        assert_eq!(store.get_dotrain_hash("b.rain"), Some(&hash));
2537        assert!(store.get_dotrain_meta("b.rain").is_some());
2538
2539        store.delete_dotrain("b.rain", false);
2540        assert!(store.get_meta(&hash).is_none());
2541    }
2542
2543    /// remapping one uri off a shared hash with keep_old = false must not take
2544    /// the meta the other uri still maps to.
2545    #[test]
2546    fn test_store_set_dotrain_keeps_shared_old_meta() {
2547        let mut store = Store::new();
2548        let (shared, _) = store.set_dotrain("same text", "a.rain", false).unwrap();
2549        store.set_dotrain("same text", "b.rain", false).unwrap();
2550
2551        let (new_hash, old_hash) = store.set_dotrain("other text", "a.rain", false).unwrap();
2552        assert_eq!(old_hash, shared);
2553        assert_ne!(new_hash, shared);
2554        assert!(store.get_dotrain_meta("b.rain").is_some());
2555
2556        store.set_dotrain("other text", "b.rain", false).unwrap();
2557        assert!(store.get_meta(&shared).is_none());
2558    }
2559
2560    /// merge keeps this store's entry in every map on a key collision, takes
2561    /// the keys it does not already hold, and unions the subgraphs.
2562    #[test]
2563    fn test_store_merge_semantics() {
2564        let mut ours = Store::new();
2565        let mut theirs = Store::new();
2566
2567        // meta cache: two different metas cannot share a key - the key IS
2568        // their digest - so merge takes the other store's entry rather than
2569        // choosing between them
2570        let mine = b"mine".to_vec();
2571        let yours = b"yours".to_vec();
2572        let mine_hash = keccak256(&mine).0.to_vec();
2573        let yours_hash = keccak256(&yours).0.to_vec();
2574        ours.update_with(&mine_hash, &mine).unwrap();
2575        theirs.update_with(&yours_hash, &yours).unwrap();
2576
2577        // same dotrain uri, different content
2578        let (hash_ours, _) = ours.set_dotrain("content a", "x.rain", false).unwrap();
2579        let (_hash_theirs, _) = theirs.set_dotrain("content b", "x.rain", false).unwrap();
2580
2581        theirs.add_subgraphs(&vec!["sg-their".to_string()]);
2582
2583        ours.merge(&theirs);
2584
2585        assert_eq!(ours.get_meta(&mine_hash), Some(&mine));
2586        assert_eq!(ours.get_meta(&yours_hash), Some(&yours));
2587        // dotrain: existing uri mapping wins
2588        assert_eq!(ours.get_dotrain_hash("x.rain"), Some(&hash_ours));
2589        // subgraphs merged
2590        assert!(ours.subgraphs().contains(&"sg-their".to_string()));
2591    }
2592
2593    /// update() stores the fetched bytes under the requested hash and each
2594    /// inner meta item under the keccak of its own encoding; update_check
2595    /// serves a cached hash without any network access.
2596    #[tokio::test]
2597    async fn test_store_update_and_update_check() {
2598        use httpmock::prelude::*;
2599        let authoring_meta: AuthoringMeta = serde_json::from_str(
2600            r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
2601        )
2602        .unwrap();
2603        let item_one = RainMetaDocumentV1Item {
2604            payload: serde_bytes::ByteBuf::from(authoring_meta.abi_encode_validate().unwrap()),
2605            magic: KnownMagic::AuthoringMetaV1,
2606            content_type: ContentType::Cbor,
2607            content_encoding: ContentEncoding::None,
2608            content_language: ContentLanguage::None,
2609            schema: None,
2610        };
2611        let item_two = sample_dotrain_item();
2612        let doc = RainMetaDocumentV1Item::cbor_encode_seq(
2613            &vec![item_one.clone(), item_two.clone()],
2614            KnownMagic::RainMetaDocumentV1,
2615        )
2616        .unwrap();
2617        let requested = keccak256(&doc).0.to_vec();
2618        let server = MockServer::start();
2619        let _mock = server.mock(|when, then| {
2620            when.method(POST);
2621            then.status(200).json_body(json!({
2622                "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2623            }));
2624        });
2625        let mut store = Store::new();
2626        store.add_subgraphs(&vec![server.url("/sg")]);
2627        let fetched = store.update(&requested).await.cloned().unwrap();
2628        assert_eq!(fetched, doc);
2629        assert_eq!(store.get_meta(&requested), Some(&doc));
2630        let inner_one = item_one.cbor_encode().unwrap();
2631        let inner_two = item_two.cbor_encode().unwrap();
2632        assert_eq!(
2633            store.get_meta(keccak256(&inner_one).0.as_ref()),
2634            Some(&inner_one)
2635        );
2636        assert_eq!(
2637            store.get_meta(keccak256(&inner_two).0.as_ref()),
2638            Some(&inner_two)
2639        );
2640
2641        // update_check: cached hash short-circuits, no subgraphs needed
2642        let mut cached_store = Store::new();
2643        let bytes = b"standalone meta bytes".to_vec();
2644        let hash = keccak256(&bytes).0.to_vec();
2645        assert!(cached_store.update_with(&hash, &bytes).is_ok());
2646        assert_eq!(cached_store.update_check(&hash).await.unwrap(), &bytes);
2647    }
2648
2649    /// update() applies the same keccak gate as update_with to the subgraph
2650    /// response, so bytes that do not hash to the requested hash poison
2651    /// neither the requested key nor the inner item keys.
2652    #[tokio::test]
2653    async fn test_store_update_rejects_hash_mismatch() {
2654        use httpmock::prelude::*;
2655        let (_, doc) = sample_authoring_doc();
2656        let requested = keccak256(b"the real content").0.to_vec();
2657        let server = MockServer::start();
2658        let _mock = server.mock(|when, then| {
2659            when.method(POST);
2660            then.status(200).json_body(json!({
2661                "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2662            }));
2663        });
2664        let mut store = Store::new();
2665        store.add_subgraphs(&vec![server.url("/sg")]);
2666        assert!(store.update(&requested).await.is_err());
2667        assert!(store.get_meta(&requested).is_none());
2668        assert!(store.cache().is_empty());
2669        // the miss is not cached either, so update_check retries and misses again
2670        assert!(store.update_check(&requested).await.is_err());
2671    }
2672
2673    /// Store::new() starts with no subgraphs, so every uncached lookup that
2674    /// reaches the network on it resolves to None instead of panicking.
2675    #[tokio::test]
2676    async fn test_store_no_subgraphs_lookups_return_none() {
2677        let hash = [0u8; 32];
2678        let mut store = Store::new();
2679        assert!(store.update(&hash).await.is_err());
2680        assert!(store.update_check(&hash).await.is_err());
2681        assert!(store.cache().is_empty());
2682    }
2683
2684    /// update_with enforces keccak(bytes) == hash, leaves an existing entry
2685    /// untouched, and unpacks inner items only for RainMetaDocumentV1
2686    /// prefixed bytes.
2687    #[test]
2688    fn test_store_update_with_validation_and_content() {
2689        // hash mismatch rejected
2690        let mut store = Store::new();
2691        let bytes = b"payload bytes".to_vec();
2692        let wrong_hash = vec![0x99u8; 32];
2693        // A mismatch is CorruptRecord, not NoRecordFound: the responder
2694        // answered about one hash with bytes that are another, which is not
2695        // the same fact as the hash being absent. #234 and #213 settled that
2696        // distinction for the query layer.
2697        match store.update_with(&wrong_hash, &bytes).unwrap_err() {
2698            Error::CorruptRecord(message) => {
2699                assert!(
2700                    message.contains(&hex::encode_prefixed(&wrong_hash)),
2701                    "{}",
2702                    message
2703                )
2704            }
2705            other => panic!("expected CorruptRecord, got {:?}", other),
2706        }
2707        assert!(store.get_meta(&wrong_hash).is_none());
2708        // valid pair stored
2709        let hash = keccak256(&bytes).0.to_vec();
2710        assert_eq!(store.update_with(&hash, &bytes).unwrap(), &bytes);
2711
2712        // an already cached key returns its entry rather than inserting again.
2713        // Note what is no longer expressible here: the old version of this
2714        // block seeded one hash with unrelated bytes and asserted a later write
2715        // did not overwrite them. Different bytes cannot share a key when the
2716        // key is their digest, so "overwritten with something else" is not a
2717        // state [MetaCache] can be in.
2718        let mut seeded = Store::new();
2719        let planted = b"planted value".to_vec();
2720        let planted_hash = keccak256(&planted).0.to_vec();
2721        seeded.update_with(&planted_hash, &planted).unwrap();
2722        assert_eq!(seeded.cache().len(), 1);
2723        assert_eq!(
2724            seeded.update_with(&planted_hash, &planted).unwrap(),
2725            &planted
2726        );
2727        assert_eq!(seeded.cache().len(), 1);
2728
2729        // prefixed document: inner item stored under keccak of its encoding
2730        let (_, doc) = sample_authoring_doc();
2731        let doc_hash = keccak256(&doc).0.to_vec();
2732        let mut doc_store = Store::new();
2733        assert!(doc_store.update_with(&doc_hash, &doc).is_ok());
2734        let inner = doc[8..].to_vec();
2735        assert_eq!(store_inner_lookup(&doc_store, &inner), Some(inner.clone()));
2736
2737        // bare cbor sequence without the document prefix: no inner extraction
2738        let item_a = sample_dotrain_item().cbor_encode().unwrap();
2739        let (_, doc_b) = sample_authoring_doc();
2740        let item_b = doc_b[8..].to_vec();
2741        let seq = [item_a.clone(), item_b].concat();
2742        let seq_hash = keccak256(&seq).0.to_vec();
2743        let mut seq_store = Store::new();
2744        assert!(seq_store.update_with(&seq_hash, &seq).is_ok());
2745        assert_eq!(store_inner_lookup(&seq_store, &item_a), None);
2746    }
2747
2748    fn store_inner_lookup(store: &Store, inner_encoded: &[u8]) -> Option<Vec<u8>> {
2749        store.get_meta(keccak256(inner_encoded).0.as_ref()).cloned()
2750    }
2751
2752    /// bytes32_to_str propagates invalid utf8 as an error instead of
2753    /// swallowing it.
2754    #[test]
2755    fn test_bytes32_to_str_invalid_utf8() {
2756        let mut bytes = [0u8; 32];
2757        bytes[0] = 0xf0;
2758        bytes[1] = 0x28;
2759        bytes[2] = 0x8c;
2760        bytes[3] = 0x28;
2761        assert!(matches!(bytes32_to_str(&bytes), Err(Error::Utf8Error(_))));
2762        let no_nul = [0xffu8; 32];
2763        assert!(matches!(bytes32_to_str(&no_nul), Err(Error::Utf8Error(_))));
2764    }
2765}