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
719    pub fn delete_dotrain(&mut self, uri: &str, keep_meta: bool) {
720        if let Some(kv) = self.dotrain_cache.remove_entry(uri) {
721            if !keep_meta {
722                self.cache.remove(&kv.1);
723            }
724        };
725    }
726
727    /// lazilly merges another Store to the current one, avoids duplicates
728    /// every map keeps the entry this Store already has on a key collision
729    pub fn merge(&mut self, other: &Store) {
730        self.add_subgraphs(&other.subgraphs);
731        for (hash, bytes) in other.cache.iter() {
732            if !self.cache.contains_key(hash) {
733                // entries are verified by construction, so copying one cannot
734                // introduce an unverified entry
735                let _ = self.cache.insert_verified(hash, bytes.clone());
736            }
737        }
738        for (uri, hash) in &other.dotrain_cache {
739            if !self.dotrain_cache.contains_key(uri) {
740                self.dotrain_cache.insert(uri.clone(), hash.clone());
741            }
742        }
743    }
744
745    /// Caches `bytes` under `hash` via [MetaCache::insert_verified], then
746    /// unpacks the items they carry into the cache too. The gate itself lives
747    /// on [MetaCache], which has no other way in.
748    fn insert_verified(&mut self, hash: &[u8], bytes: Vec<u8>) -> Result<&Vec<u8>, Error> {
749        self.cache.insert_verified(hash, bytes.clone())?;
750        self.store_content(&bytes);
751        self.get_meta(hash).ok_or(Error::NoRecordFound)
752    }
753
754    /// updates the meta cache by searching through all subgraphs for the given
755    /// hash, and returns the reference to the meta bytes in the cache if it was
756    /// found. Refreshes unconditionally; [Self::update_check] is the variant
757    /// that leaves an already cached hash alone.
758    pub async fn update(&mut self, hash: &[u8]) -> Result<&Vec<u8>, Error> {
759        let meta = search(&hex::encode_prefixed(hash), &self.subgraphs).await?;
760        self.insert_verified(hash, meta.bytes)
761    }
762
763    /// first checks if the meta is stored, if not will perform update()
764    pub async fn update_check(&mut self, hash: &[u8]) -> Result<&Vec<u8>, Error> {
765        // The NoRecordFound arm is unreachable, contains_key having just
766        // proved the key is present. It is spelled this way rather than as
767        // `if let Some(cached) = self.get_meta(hash)` because that holds an
768        // immutable borrow of self across the mutable call below it, which
769        // the borrow checker refuses.
770        if self.cache.contains_key(hash) {
771            return self.get_meta(hash).ok_or(Error::NoRecordFound);
772        }
773        self.update(hash).await
774    }
775
776    /// updates the meta cache with the given hash and meta bytes, and returns
777    /// the reference to the bytes if they were accepted. Leaves an already
778    /// cached hash alone, as [Self::update_check] does for the subgraph path.
779    pub fn update_with(&mut self, hash: &[u8], bytes: &[u8]) -> Result<&Vec<u8>, Error> {
780        // The NoRecordFound arm is unreachable, contains_key having just
781        // proved the key is present. It is spelled this way rather than as
782        // `if let Some(cached) = self.get_meta(hash)` because that holds an
783        // immutable borrow of self across the mutable call below it, which
784        // the borrow checker refuses.
785        if self.cache.contains_key(hash) {
786            return self.get_meta(hash).ok_or(Error::NoRecordFound);
787        }
788        self.insert_verified(hash, bytes.to_vec())
789    }
790
791    /// stores (or updates in case the URI already exists) the given dotrain text as meta into the store cache
792    /// and maps it to the given uri (path), it should be noted that reading the content of the dotrain is not in
793    /// the scope of Store and handling and passing on a correct URI (path) for the given text must be handled
794    /// externally by the implementer
795    pub fn set_dotrain(
796        &mut self,
797        text: &str,
798        uri: &str,
799        keep_old: bool,
800    ) -> Result<(Vec<u8>, Vec<u8>), Error> {
801        let bytes = RainMetaDocumentV1Item {
802            payload: serde_bytes::ByteBuf::from(text.as_bytes()),
803            magic: KnownMagic::DotrainV1,
804            content_type: ContentType::OctetStream,
805            content_encoding: ContentEncoding::None,
806            content_language: ContentLanguage::None,
807            schema: None,
808        }
809        .cbor_encode()?;
810        let new_hash = keccak256(&bytes).0.to_vec();
811        if let Some(h) = self.dotrain_cache.get(uri) {
812            let old_hash = h.clone();
813            if new_hash == old_hash {
814                self.cache.insert_verified(&new_hash, bytes)?;
815                Ok((new_hash, vec![]))
816            } else {
817                self.cache.insert_verified(&new_hash, bytes)?;
818                self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
819                if !keep_old {
820                    self.cache.remove(&old_hash);
821                }
822                Ok((new_hash, old_hash))
823            }
824        } else {
825            self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
826            self.cache.insert_verified(&new_hash, bytes)?;
827            Ok((new_hash, vec![]))
828        }
829    }
830
831    /// decodes each meta and stores the inner meta items into the cache
832    /// if any of the inner items is an authoring meta, stores it in authoring meta cache as well
833    /// returns the reference to the authoring bytes if the meta bytes contained any
834    fn store_content(&mut self, bytes: &[u8]) {
835        if let Ok(meta_maps) = RainMetaDocumentV1Item::cbor_decode(bytes) {
836            if bytes.starts_with(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes()) {
837                for meta_map in &meta_maps {
838                    if let Ok(encoded_bytes) = meta_map.cbor_encode() {
839                        // the key is this item's own digest, so the gate can
840                        // only pass - routing through it anyway means no
841                        // reader has to work that out
842                        let _ = self
843                            .cache
844                            .insert_verified(&keccak256(&encoded_bytes).0, encoded_bytes);
845                    }
846                }
847            }
848        }
849    }
850}
851
852/// converts string to bytes32
853///
854/// Right padding with `0u8` is the encoding, so [`bytes32_to_str`] ends the
855/// string at the first `0u8` and cannot carry one. An input holding a nul is
856/// rejected rather than round tripped into a shorter string.
857pub fn str_to_bytes32(text: &str) -> Result<[u8; 32], Error> {
858    let bytes: &[u8] = text.as_bytes();
859    if bytes.len() > 32 {
860        return Err(Error::BiggerThan32Bytes);
861    }
862    if bytes.contains(&0u8) {
863        return Err(Error::NulByteInInput);
864    }
865    let mut b32 = [0u8; 32];
866    b32[..bytes.len()].copy_from_slice(bytes);
867    Ok(b32)
868}
869
870/// converts bytes32 to string
871pub fn bytes32_to_str(bytes: &[u8; 32]) -> Result<&str, Error> {
872    let mut len = 32;
873    if let Some((pos, _)) = itertools::Itertools::find_position(&mut bytes.iter(), |b| **b == 0u8) {
874        len = pos;
875    };
876    Ok(std::str::from_utf8(&bytes[..len])?)
877}
878
879#[cfg(all(test, not(target_family = "wasm")))]
880mod tests {
881    use super::{
882        *, bytes32_to_str, magic::KnownMagic, str_to_bytes32, types::authoring::v1::AuthoringMeta,
883        ContentEncoding, ContentLanguage, ContentType, Error, RainMetaDocumentV1Item,
884    };
885    use alloy::providers::ProviderBuilder;
886    use alloy::{providers::mock::Asserter, rpc::json_rpc::ErrorPayload, sol_types::SolType};
887    use serde_json::json;
888
889    /// Roundtrip test for an authoring meta
890    /// original content -> pack -> MetaMap -> cbor encode -> cbor decode -> MetaMap -> unpack -> original content,
891    #[test]
892    fn authoring_meta_roundtrip() -> Result<(), Error> {
893        let authoring_meta_content = r#"[
894            {
895                "word": "stack",
896                "description": "Copies an existing value from the stack.",
897                "operandParserOffset": 16
898            },
899            {
900                "word": "constant",
901                "description": "Copies a constant value onto the stack.",
902                "operandParserOffset": 16
903            }
904        ]"#;
905        let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
906
907        // abi encode the authoring meta with performing validation
908        let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
909        let expected_abi_encoded = <alloy::sol!((bytes32, uint8, string)[])>::abi_encode(&vec![
910            (
911                str_to_bytes32("stack")?,
912                16u8,
913                "Copies an existing value from the stack.".to_string(),
914            ),
915            (
916                str_to_bytes32("constant")?,
917                16u8,
918                "Copies a constant value onto the stack.".to_string(),
919            ),
920        ]);
921        // check the encoded bytes agaiinst the expected
922        assert_eq!(authoring_meta_abi_encoded, expected_abi_encoded);
923
924        let meta_map = RainMetaDocumentV1Item {
925            payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
926            magic: KnownMagic::AuthoringMetaV1,
927            content_type: ContentType::Cbor,
928            content_encoding: ContentEncoding::None,
929            content_language: ContentLanguage::None,
930            schema: None,
931        };
932        let cbor_encoded = meta_map.cbor_encode()?;
933
934        // cbor map with 3 keys
935        assert_eq!(cbor_encoded[0], 0xa3);
936        // key 0
937        assert_eq!(cbor_encoded[1], 0x00);
938        // major type 2 (bytes) length 512
939        assert_eq!(cbor_encoded[2], 0b010_11001);
940        assert_eq!(cbor_encoded[3], 0b000_00010);
941        assert_eq!(cbor_encoded[4], 0b000_00000);
942        // payload
943        assert_eq!(cbor_encoded[5..517], authoring_meta_abi_encoded);
944        // key 1
945        assert_eq!(cbor_encoded[517], 0x01);
946        // major type 0 (unsigned integer) value 27
947        assert_eq!(cbor_encoded[518], 0b000_11011);
948        // magic number
949        assert_eq!(
950            &cbor_encoded[519..527],
951            KnownMagic::AuthoringMetaV1.to_prefix_bytes()
952        );
953        // key 2
954        assert_eq!(cbor_encoded[527], 0x02);
955        // text string application/cbor length 16
956        assert_eq!(cbor_encoded[528], 0b011_10000);
957        // the string application/cbor, must be the end of data
958        assert_eq!(&cbor_encoded[529..], "application/cbor".as_bytes());
959
960        // decode the data back to MetaMap
961        let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
962        // the length of decoded maps must be 1 as we only had 1 encoded item
963        assert_eq!(cbor_decoded.len(), 1);
964        // decoded item must be equal to the original meta_map
965        assert_eq!(cbor_decoded[0], meta_map);
966
967        Ok(())
968    }
969
970    /// Roundtrip test for a dotrain meta
971    /// original content -> pack -> MetaMap -> cbor encode -> cbor decode -> MetaMap -> unpack -> original content,
972    #[test]
973    fn dotrain_meta_roundtrip() -> Result<(), Error> {
974        let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
975        let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
976
977        let content_encoding = ContentEncoding::Deflate;
978        let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
979
980        let meta_map = RainMetaDocumentV1Item {
981            payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
982            magic: KnownMagic::DotrainV1,
983            content_type: ContentType::OctetStream,
984            content_encoding,
985            content_language: ContentLanguage::En,
986            schema: None,
987        };
988        let cbor_encoded = meta_map.cbor_encode()?;
989
990        // cbor map with 5 keys
991        assert_eq!(cbor_encoded[0], 0xa5);
992        // key 0
993        assert_eq!(cbor_encoded[1], 0x00);
994        // major type 2 (bytes) length 36
995        assert_eq!(cbor_encoded[2], 0b010_11000);
996        assert_eq!(cbor_encoded[3], 0b001_00100);
997        // assert_eq!(cbor_encoded[4], 0b000_00000);
998        // payload
999        assert_eq!(cbor_encoded[4..40], deflated_payload);
1000        // key 1
1001        assert_eq!(cbor_encoded[40], 0x01);
1002        // major type 0 (unsigned integer) value 27
1003        assert_eq!(cbor_encoded[41], 0b000_11011);
1004        // magic number
1005        assert_eq!(
1006            &cbor_encoded[42..50],
1007            KnownMagic::DotrainV1.to_prefix_bytes()
1008        );
1009        // key 2
1010        assert_eq!(cbor_encoded[50], 0x02);
1011        // text string application/octet-stream length 24
1012        assert_eq!(cbor_encoded[51], 0b011_11000);
1013        assert_eq!(cbor_encoded[52], 0b000_11000);
1014        // the string application/octet-stream
1015        assert_eq!(&cbor_encoded[53..77], "application/octet-stream".as_bytes());
1016        // key 3
1017        assert_eq!(cbor_encoded[77], 0x03);
1018        // text string deflate length 7
1019        assert_eq!(cbor_encoded[78], 0b011_00111);
1020        // the string deflate
1021        assert_eq!(&cbor_encoded[79..86], "deflate".as_bytes());
1022        // key 4
1023        assert_eq!(cbor_encoded[86], 0x04);
1024        // text string en length 2
1025        assert_eq!(cbor_encoded[87], 0b011_00010);
1026        // the string identity, must be the end of data
1027        assert_eq!(&cbor_encoded[88..], "en".as_bytes());
1028
1029        // decode the data back to MetaMap
1030        let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1031        // the length of decoded maps must be 1 as we only had 1 encoded item
1032        assert_eq!(cbor_decoded.len(), 1);
1033        // decoded item must be equal to the original meta_map
1034        assert_eq!(cbor_decoded[0], meta_map);
1035
1036        Ok(())
1037    }
1038
1039    /// Roundtrip test for a meta sequence
1040    /// original content -> pack -> MetaMap -> cbor encode -> cbor decode -> MetaMap -> unpack -> original content,
1041    #[test]
1042    fn meta_seq_roundtrip() -> Result<(), Error> {
1043        let authoring_meta_content = r#"[
1044            {
1045                "word": "stack",
1046                "description": "Copies an existing value from the stack.",
1047                "operandParserOffset": 16
1048            },
1049            {
1050                "word": "constant",
1051                "description": "Copies a constant value onto the stack.",
1052                "operandParserOffset": 16
1053            }
1054        ]"#;
1055        let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
1056        let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
1057        let meta_map_1 = RainMetaDocumentV1Item {
1058            payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
1059            magic: KnownMagic::AuthoringMetaV1,
1060            content_type: ContentType::Cbor,
1061            content_encoding: ContentEncoding::None,
1062            content_language: ContentLanguage::None,
1063            schema: None,
1064        };
1065
1066        let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
1067        let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
1068        let content_encoding = ContentEncoding::Deflate;
1069        let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
1070        let meta_map_2 = RainMetaDocumentV1Item {
1071            payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
1072            magic: KnownMagic::DotrainV1,
1073            content_type: ContentType::OctetStream,
1074            content_encoding,
1075            content_language: ContentLanguage::En,
1076            schema: None,
1077        };
1078
1079        // cbor encode as RainMetaDocument sequence
1080        let cbor_encoded = RainMetaDocumentV1Item::cbor_encode_seq(
1081            &vec![meta_map_1.clone(), meta_map_2.clone()],
1082            KnownMagic::RainMetaDocumentV1,
1083        )?;
1084
1085        // 8 byte magic number prefix
1086        assert_eq!(
1087            &cbor_encoded[0..8],
1088            KnownMagic::RainMetaDocumentV1.to_prefix_bytes()
1089        );
1090
1091        // first item in the encoded bytes
1092        // cbor map with 3 keys
1093        assert_eq!(cbor_encoded[8], 0xa3);
1094        // key 0
1095        assert_eq!(cbor_encoded[9], 0x00);
1096        // major type 2 (bytes) length 512
1097        assert_eq!(cbor_encoded[10], 0b010_11001);
1098        assert_eq!(cbor_encoded[11], 0b000_00010);
1099        assert_eq!(cbor_encoded[12], 0b000_00000);
1100        // payload
1101        assert_eq!(cbor_encoded[13..525], authoring_meta_abi_encoded);
1102        // key 1
1103        assert_eq!(cbor_encoded[525], 0x01);
1104        // major type 0 (unsigned integer) value 27
1105        assert_eq!(cbor_encoded[526], 0b000_11011);
1106        // magic number
1107        assert_eq!(
1108            &cbor_encoded[527..535],
1109            KnownMagic::AuthoringMetaV1.to_prefix_bytes()
1110        );
1111        // key 2
1112        assert_eq!(cbor_encoded[535], 0x02);
1113        // text string application/cbor length 16
1114        assert_eq!(cbor_encoded[536], 0b011_10000);
1115        // the string application/cbor, must be the end of data
1116        assert_eq!(&cbor_encoded[537..553], "application/cbor".as_bytes());
1117
1118        // second item in the encoded bytes
1119        // cbor map with 5 keys
1120        assert_eq!(cbor_encoded[553], 0xa5);
1121        // key 0
1122        assert_eq!(cbor_encoded[554], 0x00);
1123        // major type 2 (bytes) length 36
1124        assert_eq!(cbor_encoded[555], 0b010_11000);
1125        assert_eq!(cbor_encoded[556], 0b001_00100);
1126        // assert_eq!(cbor_encoded[4], 0b000_00000);
1127        // payload
1128        assert_eq!(cbor_encoded[557..593], deflated_payload);
1129        // key 1
1130        assert_eq!(cbor_encoded[593], 0x01);
1131        // major type 0 (unsigned integer) value 27
1132        assert_eq!(cbor_encoded[594], 0b000_11011);
1133        // magic number
1134        assert_eq!(
1135            &cbor_encoded[595..603],
1136            KnownMagic::DotrainV1.to_prefix_bytes()
1137        );
1138        // key 2
1139        assert_eq!(cbor_encoded[603], 0x02);
1140        // text string application/octet-stream length 24
1141        assert_eq!(cbor_encoded[604], 0b011_11000);
1142        assert_eq!(cbor_encoded[605], 0b000_11000);
1143        // the string application/octet-stream
1144        assert_eq!(
1145            &cbor_encoded[606..630],
1146            "application/octet-stream".as_bytes()
1147        );
1148        // key 3
1149        assert_eq!(cbor_encoded[630], 0x03);
1150        // text string deflate length 7
1151        assert_eq!(cbor_encoded[631], 0b011_00111);
1152        // the string deflate
1153        assert_eq!(&cbor_encoded[632..639], "deflate".as_bytes());
1154        // key 4
1155        assert_eq!(cbor_encoded[639], 0x04);
1156        // text string en length 2
1157        assert_eq!(cbor_encoded[640], 0b011_00010);
1158        // the string identity, must be the end of data
1159        assert_eq!(&cbor_encoded[641..], "en".as_bytes());
1160
1161        // decode the data back to MetaMap
1162        let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1163        // the length of decoded maps must be 2 as we had 2 encoded item
1164        assert_eq!(cbor_decoded.len(), 2);
1165
1166        // decoded item 1 must be equal to the original meta_map_1
1167        assert_eq!(cbor_decoded[0], meta_map_1);
1168        // decoded item 2 must be equal to the original meta_map_2
1169        assert_eq!(cbor_decoded[1], meta_map_2);
1170
1171        Ok(())
1172    }
1173
1174    #[test]
1175    fn test_bytes32_to_str() {
1176        let text_bytes_list = vec![
1177            (
1178                "",
1179                hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1180            ),
1181            (
1182                "A",
1183                hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1184            ),
1185            (
1186                "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1187                hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1188            ),
1189            (
1190                "!@#$%^&*(),./;'[]",
1191                hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1192            ),
1193        ];
1194
1195        for (text, bytes) in text_bytes_list {
1196            assert_eq!(text, bytes32_to_str(&bytes).unwrap());
1197        }
1198    }
1199
1200    #[test]
1201    fn test_str_to_bytes32() {
1202        let text_bytes_list = vec![
1203            (
1204                "",
1205                hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1206            ),
1207            (
1208                "A",
1209                hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1210            ),
1211            (
1212                "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1213                hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1214            ),
1215            (
1216                "!@#$%^&*(),./;'[]",
1217                hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1218            ),
1219        ];
1220
1221        for (text, bytes) in text_bytes_list {
1222            assert_eq!(bytes, str_to_bytes32(text).unwrap());
1223        }
1224    }
1225
1226    #[test]
1227    fn test_str_to_bytes32_long() {
1228        assert!(matches!(
1229            str_to_bytes32("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456").unwrap_err(),
1230            Error::BiggerThan32Bytes
1231        ));
1232    }
1233
1234    /// A nul cannot survive the padding convention bytes32_to_str decodes, so
1235    /// it is rejected on the way in wherever it sits, including the pair the
1236    /// issue collides ("a" and "a\0").
1237    #[test]
1238    fn test_str_to_bytes32_rejects_nul() {
1239        for text in [
1240            "\0",
1241            "\0a",
1242            "a\0",
1243            "a\0b",
1244            "abcdefghijklmnopqrstuvwxyz01234\0",
1245        ] {
1246            assert!(
1247                matches!(str_to_bytes32(text), Err(Error::NulByteInInput)),
1248                "nul bearing input {:?} accepted",
1249                text
1250            );
1251        }
1252    }
1253
1254    /// Everything str_to_bytes32 accepts comes back out of bytes32_to_str
1255    /// unchanged, and no two of them share a bytes32.
1256    #[test]
1257    fn test_str_to_bytes32_round_trip() -> Result<(), Error> {
1258        let mut seen: Vec<[u8; 32]> = vec![];
1259        for text in [
1260            "",
1261            "a",
1262            "stack",
1263            "!@#$%^&*(),./;'[]",
1264            "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1265        ] {
1266            let bytes = str_to_bytes32(text)?;
1267            assert_eq!(bytes32_to_str(&bytes)?, text);
1268            assert!(!seen.contains(&bytes), "input {:?} collided", text);
1269            seen.push(bytes);
1270        }
1271        Ok(())
1272    }
1273
1274    #[tokio::test]
1275    async fn test_implements_i_describe_by_meta_v1() {
1276        // makes new server/client with success response for erc165 check
1277        async fn new_server_client() -> (Asserter, impl Provider) {
1278            let asserter = Asserter::new();
1279            let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
1280
1281            // Mock a responses for successful supports erc165 check
1282            asserter.push_success(
1283                &"0x0000000000000000000000000000000000000000000000000000000000000001",
1284            );
1285            asserter.push_success(
1286                &"0x0000000000000000000000000000000000000000000000000000000000000000",
1287            );
1288
1289            (asserter, provider)
1290        }
1291
1292        let address = Address::random();
1293
1294        // mock a true response for implements IDescribedByMetaV1
1295        let (asserter, provider) = new_server_client().await;
1296        asserter
1297            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
1298        let result = implements_i_described_by_meta_v1(&provider, address)
1299            .await
1300            .unwrap();
1301        assert!(result);
1302
1303        // mock a false response for implements IDescribedByMetaV1
1304        let (asserter, provider) = new_server_client().await;
1305        asserter
1306            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
1307        let result = implements_i_described_by_meta_v1(&provider, address)
1308            .await
1309            .unwrap();
1310        assert!(!result);
1311
1312        // mock a revert response for implements IDescribedByMetaV1
1313        let (asserter, provider) = new_server_client().await;
1314        asserter.push_failure(ErrorPayload {
1315            code: -32003,
1316            message: "execution reverted".into(),
1317            data: Some(serde_json::value::to_raw_value(&json!("0x00")).unwrap()),
1318        });
1319        let result = implements_i_described_by_meta_v1(&provider, address)
1320            .await
1321            .unwrap();
1322        assert!(!result);
1323    }
1324
1325    /// Roundtrip test for a meta map carrying the OaSchema magic number as an
1326    /// additional CBOR map key beyond the standard 0-4 keys.
1327    /// MetaMap (with schema) -> cbor encode -> cbor decode -> MetaMap, assert equality
1328    #[test]
1329    fn oa_schema_map_key_roundtrip() -> Result<(), Error> {
1330        let payload = vec![0x01, 0x02, 0x03];
1331        // an IPFS hash referencing the schema of the payload, as written by
1332        // the SFT frontend under the OaSchema map key
1333        let schema = "QmSchemaHash1234567890abcdefghijklmnopqrstuvwx".to_string();
1334        assert_eq!(schema.len(), 46);
1335
1336        let meta_map = RainMetaDocumentV1Item {
1337            payload: serde_bytes::ByteBuf::from(payload.clone()),
1338            magic: KnownMagic::OaStructure,
1339            content_type: ContentType::Json,
1340            content_encoding: ContentEncoding::Deflate,
1341            content_language: ContentLanguage::None,
1342            schema: Some(schema.clone()),
1343        };
1344        let cbor_encoded = meta_map.cbor_encode()?;
1345
1346        // cbor map with 5 keys (0, 1, 2, 3 and the OaSchema magic)
1347        assert_eq!(cbor_encoded[0], 0xa5);
1348        // key 0
1349        assert_eq!(cbor_encoded[1], 0x00);
1350        // major type 2 (bytes) length 3
1351        assert_eq!(cbor_encoded[2], 0b010_00011);
1352        // payload
1353        assert_eq!(cbor_encoded[3..6], payload);
1354        // key 1
1355        assert_eq!(cbor_encoded[6], 0x01);
1356        // major type 0 (unsigned integer) value 27
1357        assert_eq!(cbor_encoded[7], 0b000_11011);
1358        // magic number
1359        assert_eq!(
1360            &cbor_encoded[8..16],
1361            KnownMagic::OaStructure.to_prefix_bytes()
1362        );
1363        // key 2
1364        assert_eq!(cbor_encoded[16], 0x02);
1365        // text string application/json length 16
1366        assert_eq!(cbor_encoded[17], 0b011_10000);
1367        assert_eq!(&cbor_encoded[18..34], "application/json".as_bytes());
1368        // key 3
1369        assert_eq!(cbor_encoded[34], 0x03);
1370        // text string deflate length 7
1371        assert_eq!(cbor_encoded[35], 0b011_00111);
1372        assert_eq!(&cbor_encoded[36..43], "deflate".as_bytes());
1373        // the OaSchema magic as key, major type 0 (unsigned integer) value 27
1374        assert_eq!(cbor_encoded[43], 0b000_11011);
1375        assert_eq!(
1376            &cbor_encoded[44..52],
1377            KnownMagic::OaSchema.to_prefix_bytes()
1378        );
1379        // schema value, text string length 46
1380        assert_eq!(cbor_encoded[52], 0b011_11000);
1381        assert_eq!(cbor_encoded[53], 46);
1382        // the schema hash string, must be the end of data
1383        assert_eq!(&cbor_encoded[54..], schema.as_bytes());
1384
1385        // decode the data back to MetaMap
1386        let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1387        // the length of decoded maps must be 1 as we only had 1 encoded item
1388        assert_eq!(cbor_decoded.len(), 1);
1389        // decoded item must be equal to the original meta_map
1390        assert_eq!(cbor_decoded[0], meta_map);
1391
1392        Ok(())
1393    }
1394
1395    /// A meta map without the schema key must keep encoding exactly as before
1396    /// (no schema entry on the wire) and roundtrip with schema None
1397    #[test]
1398    fn no_schema_key_encodes_as_before_roundtrip() -> Result<(), Error> {
1399        let payload = vec![0x0a, 0x0b];
1400        let meta_map = RainMetaDocumentV1Item {
1401            payload: serde_bytes::ByteBuf::from(payload.clone()),
1402            magic: KnownMagic::OaStructure,
1403            content_type: ContentType::None,
1404            content_encoding: ContentEncoding::None,
1405            content_language: ContentLanguage::None,
1406            schema: None,
1407        };
1408        let cbor_encoded = meta_map.cbor_encode()?;
1409
1410        // cbor map with only the 2 mandatory keys
1411        assert_eq!(cbor_encoded[0], 0xa2);
1412        // key 0
1413        assert_eq!(cbor_encoded[1], 0x00);
1414        // major type 2 (bytes) length 2
1415        assert_eq!(cbor_encoded[2], 0b010_00010);
1416        // payload
1417        assert_eq!(cbor_encoded[3..5], payload);
1418        // key 1
1419        assert_eq!(cbor_encoded[5], 0x01);
1420        // major type 0 (unsigned integer) value 27
1421        assert_eq!(cbor_encoded[6], 0b000_11011);
1422        // magic number, must be the end of data
1423        assert_eq!(
1424            &cbor_encoded[7..],
1425            KnownMagic::OaStructure.to_prefix_bytes()
1426        );
1427
1428        let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1429        assert_eq!(cbor_decoded.len(), 1);
1430        assert_eq!(cbor_decoded[0], meta_map);
1431
1432        Ok(())
1433    }
1434
1435    /// A map key this version has no meaning for is a future index, so it is
1436    /// skipped and the rest of the map decodes
1437    #[test]
1438    fn unknown_map_key_index_is_ignored() -> Result<(), Error> {
1439        let mut bytes: Vec<u8> = vec![
1440            // cbor map with 3 keys
1441            0xa3, // key 0, bytes payload of length 0
1442            0x00, 0x40, // key 1, unsigned integer magic number
1443            0x01, 0x1b,
1444        ];
1445        bytes.extend_from_slice(&KnownMagic::DotrainSourceV1.to_prefix_bytes());
1446        // key 5, a plausible future index, unsigned integer value 7
1447        bytes.extend_from_slice(&[0x05, 0x07]);
1448
1449        let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1450        assert_eq!(decoded.len(), 1);
1451        assert_eq!(decoded[0], plain_item(KnownMagic::DotrainSourceV1, vec![]));
1452
1453        Ok(())
1454    }
1455
1456    /// A magic number other than OaSchema used as an extra map key is a future
1457    /// magic keyed entry, skipped the same way, and leaves schema unset
1458    #[test]
1459    fn non_oa_schema_extra_map_key_is_ignored() -> Result<(), Error> {
1460        // build a map identical to a valid 2 key meta map but with an extra
1461        // OaHashList magic key carrying a text string
1462        let mut bytes: Vec<u8> = vec![
1463            // cbor map with 3 keys
1464            0xa3, // key 0, bytes payload of length 1
1465            0x00, 0x41, 0xff, // key 1, unsigned integer magic number
1466            0x01, 0x1b,
1467        ];
1468        bytes.extend_from_slice(&KnownMagic::OaStructure.to_prefix_bytes());
1469        // the OaHashList magic as key
1470        bytes.push(0x1b);
1471        bytes.extend_from_slice(&KnownMagic::OaHashList.to_prefix_bytes());
1472        // text string value of length 2
1473        bytes.extend_from_slice(&[0x62, 0x68, 0x69]);
1474
1475        let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1476        assert_eq!(decoded.len(), 1);
1477        let expected = plain_item(KnownMagic::OaStructure, vec![0xff]);
1478        assert_eq!(decoded[0], expected);
1479        assert_eq!(decoded[0].schema, None);
1480
1481        Ok(())
1482    }
1483
1484    /// The whole value of an unknown key is consumed however nested, so the
1485    /// item that follows it in the sequence still decodes
1486    #[test]
1487    fn unknown_map_key_consumes_its_whole_value() -> Result<(), Error> {
1488        let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1489        bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1490        // key 5, value {42: [1, 2]}
1491        bytes.extend_from_slice(&[0x05, 0xa1, 0x18, 0x2a, 0x82, 0x01, 0x02]);
1492        bytes.extend_from_slice(&handwritten_map());
1493
1494        let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1495        assert_eq!(decoded.len(), 2);
1496        let expected = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1497        assert_eq!(decoded[0], expected);
1498        assert_eq!(decoded[1], expected);
1499
1500        Ok(())
1501    }
1502
1503    /// An ignored key is not re-encoded, so the item's hash is the hash of what
1504    /// this version can represent and not of the bytes it decoded
1505    #[test]
1506    fn ignored_map_key_is_absent_from_the_reencoding() -> Result<(), Error> {
1507        let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1508        bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1509        bytes.extend_from_slice(&[0x05, 0x07]);
1510
1511        let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1512        assert_eq!(decoded[0].cbor_encode()?, handwritten_map());
1513        assert_eq!(decoded[0].hash(false)?, keccak256(handwritten_map()).0);
1514        assert_ne!(decoded[0].hash(false)?, keccak256(&bytes).0);
1515
1516        Ok(())
1517    }
1518
1519    /// Only integer keys are indexes. The spec rules out the HTTP header names
1520    /// as keys, so a key that is not an unsigned integer is not a future index
1521    /// to skip over
1522    #[test]
1523    fn non_integer_map_key_errors() {
1524        let mut text_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1525        text_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1526        // key "5", unsigned integer value 7
1527        text_key.extend_from_slice(&[0x61, 0x35, 0x07]);
1528        assert!(matches!(
1529            RainMetaDocumentV1Item::cbor_decode(&text_key),
1530            Err(Error::SerdeCborError(_))
1531        ));
1532
1533        let mut negative_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1534        negative_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1535        // key -1, unsigned integer value 7
1536        negative_key.extend_from_slice(&[0x20, 0x07]);
1537        assert!(matches!(
1538            RainMetaDocumentV1Item::cbor_decode(&negative_key),
1539            Err(Error::SerdeCborError(_))
1540        ));
1541    }
1542
1543    /// An unknown key is skipped, never counted as one of the mandatory keys,
1544    /// so a map carrying one instead of key 0 is still missing its payload and
1545    /// corrupts the document rather than decoding with the unknown key standing in.
1546    #[test]
1547    fn unknown_map_key_does_not_stand_in_for_a_mandatory_key() {
1548        let mut bytes: Vec<u8> = vec![0xa2, 0x05, 0x07, 0x01, 0x1b];
1549        bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1550        assert!(RainMetaDocumentV1Item::cbor_decode(&bytes).is_err());
1551    }
1552
1553    fn plain_item(magic: KnownMagic, payload: Vec<u8>) -> RainMetaDocumentV1Item {
1554        RainMetaDocumentV1Item {
1555            payload: serde_bytes::ByteBuf::from(payload),
1556            magic,
1557            content_type: ContentType::None,
1558            content_encoding: ContentEncoding::None,
1559            content_language: ContentLanguage::None,
1560            schema: None,
1561        }
1562    }
1563
1564    // ---- helpers for the CAS / search tests ----
1565
1566    fn sample_authoring_doc() -> (AuthoringMeta, Vec<u8>) {
1567        let authoring_meta: AuthoringMeta = serde_json::from_str(
1568            r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
1569        )
1570        .unwrap();
1571        let abi = authoring_meta.abi_encode_validate().unwrap();
1572        let item = RainMetaDocumentV1Item {
1573            payload: serde_bytes::ByteBuf::from(abi),
1574            magic: KnownMagic::AuthoringMetaV1,
1575            content_type: ContentType::Cbor,
1576            content_encoding: ContentEncoding::None,
1577            content_language: ContentLanguage::None,
1578            schema: None,
1579        };
1580        let doc =
1581            RainMetaDocumentV1Item::cbor_encode_seq(&vec![item], KnownMagic::RainMetaDocumentV1)
1582                .unwrap();
1583        (authoring_meta, doc)
1584    }
1585
1586    fn sample_dotrain_item() -> RainMetaDocumentV1Item {
1587        RainMetaDocumentV1Item {
1588            payload: serde_bytes::ByteBuf::from("some dotrain body".as_bytes()),
1589            magic: KnownMagic::DotrainV1,
1590            content_type: ContentType::OctetStream,
1591            content_encoding: ContentEncoding::None,
1592            content_language: ContentLanguage::None,
1593            schema: None,
1594        }
1595    }
1596
1597    /// Handwritten canonical cbor for {0: h'01', 1: DotrainV1 magic}, written
1598    /// out byte by byte from the cbor spec, independent of cbor_encode.
1599    fn handwritten_map() -> Vec<u8> {
1600        vec![
1601            0xa2, // map(2)
1602            0x00, // key 0
1603            0x41, 0x01, // bytes(1) 0x01
1604            0x01, // key 1
1605            0x1b, 0xff, 0xda, 0xc2, 0xf2, 0xf3, 0x7b, 0xe8, 0x94, // u64 DotrainV1
1606        ]
1607    }
1608
1609    /// hash(false) is keccak256 of the bare cbor map and hash(true) is
1610    /// keccak256 of the rain meta document prefix followed by the same map,
1611    /// pinned against independently handwritten bytes.
1612    #[test]
1613    fn test_hash_bare_vs_document() -> Result<(), Error> {
1614        let map_bytes = handwritten_map();
1615        let mut doc_bytes: Vec<u8> = vec![0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74];
1616        doc_bytes.extend_from_slice(&map_bytes);
1617
1618        let item = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1619        assert_eq!(item.hash(false)?, keccak256(&map_bytes).0);
1620        assert_eq!(item.hash(true)?, keccak256(&doc_bytes).0);
1621        assert_ne!(item.hash(false)?, item.hash(true)?);
1622        Ok(())
1623    }
1624
1625    /// Empty input and a bare document prefix with no items are corrupt metas.
1626    #[test]
1627    fn test_cbor_decode_empty_is_corrupt() {
1628        assert!(matches!(
1629            RainMetaDocumentV1Item::cbor_decode(&[]),
1630            Err(Error::CorruptMeta)
1631        ));
1632        let prefix = KnownMagic::RainMetaDocumentV1.to_prefix_bytes();
1633        assert!(matches!(
1634            RainMetaDocumentV1Item::cbor_decode(&prefix),
1635            Err(Error::CorruptMeta)
1636        ));
1637    }
1638
1639    /// A valid map followed by truncated trailing bytes must not decode: the
1640    /// data does not end exactly at the last complete item.
1641    #[test]
1642    fn test_cbor_decode_trailing_truncated_is_corrupt() {
1643        let mut bytes = handwritten_map();
1644        bytes.push(0x1b); // u64 header with all 8 payload bytes missing
1645        assert!(matches!(
1646            RainMetaDocumentV1Item::cbor_decode(&bytes),
1647            Err(Error::CorruptMeta)
1648        ));
1649    }
1650
1651    /// Every way an item can run out of bytes is corrupt meta, not a serde
1652    /// cbor error: a truncated sole item, a map header promising entries the
1653    /// input does not carry, and a truncated item after a complete one.
1654    #[test]
1655    fn test_cbor_decode_truncated_item_is_corrupt() {
1656        let mut sole = handwritten_map();
1657        sole.pop();
1658        assert!(matches!(
1659            RainMetaDocumentV1Item::cbor_decode(&sole),
1660            Err(Error::CorruptMeta)
1661        ));
1662
1663        assert!(matches!(
1664            RainMetaDocumentV1Item::cbor_decode(&[0xa2, 0x00, 0x41, 0x01]),
1665            Err(Error::CorruptMeta)
1666        ));
1667
1668        let mut after_complete = handwritten_map();
1669        after_complete.extend_from_slice(&[0xa2, 0x00]);
1670        assert!(matches!(
1671            RainMetaDocumentV1Item::cbor_decode(&after_complete),
1672            Err(Error::CorruptMeta)
1673        ));
1674    }
1675
1676    /// A valid map followed by a byte that is not valid cbor surfaces the
1677    /// serde cbor error.
1678    #[test]
1679    fn test_cbor_decode_trailing_garbage_errors() {
1680        let mut bytes = handwritten_map();
1681        bytes.push(0xff); // lone break byte
1682        assert!(matches!(
1683            RainMetaDocumentV1Item::cbor_decode(&bytes),
1684            Err(Error::SerdeCborError(_))
1685        ));
1686    }
1687
1688    /// A map without the mandatory payload key 0 corrupts the document, and
1689    /// takes an item beside it down too: one document is one emission from
1690    /// one emitter, and an emission carrying a broken claim is not mined for
1691    /// its readable parts.
1692    #[test]
1693    fn test_cbor_decode_missing_payload_is_corrupt() {
1694        let mut bytes: Vec<u8> = vec![0xa1, 0x01, 0x1b]; // {1: DotrainV1}
1695        bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1696        assert!(RainMetaDocumentV1Item::cbor_decode(&bytes).is_err());
1697
1698        // beside a good item, the good item is not recovered
1699        let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1700            .cbor_encode()
1701            .unwrap();
1702        let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1703        document.extend_from_slice(&bytes);
1704        document.extend_from_slice(&good);
1705        assert!(RainMetaDocumentV1Item::cbor_decode(&document).is_err());
1706    }
1707
1708    /// A map without the mandatory magic key 1, likewise.
1709    #[test]
1710    fn test_cbor_decode_missing_magic_is_corrupt() {
1711        let bytes: Vec<u8> = vec![0xa1, 0x00, 0x41, 0x01]; // {0: h'01'}
1712        assert!(RainMetaDocumentV1Item::cbor_decode(&bytes).is_err());
1713
1714        let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1715            .cbor_encode()
1716            .unwrap();
1717        let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1718        document.extend_from_slice(&bytes);
1719        document.extend_from_slice(&good);
1720        assert!(RainMetaDocumentV1Item::cbor_decode(&document).is_err());
1721    }
1722
1723    /// A map carrying a magic number this version does not know is dropped,
1724    /// not an error: the magic is the format's extension point, and somebody
1725    /// else's well formed item travelling in the same document is not
1726    /// corruption. Alone it leaves nothing to return, which is CorruptMeta.
1727    #[test]
1728    fn test_cbor_decode_unknown_magic_is_dropped() {
1729        let mut bytes: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1730        bytes.extend_from_slice(&0xdeadbeefdeadbeefu64.to_be_bytes());
1731        assert!(matches!(
1732            RainMetaDocumentV1Item::cbor_decode(&bytes),
1733            Err(Error::CorruptMeta)
1734        ));
1735    }
1736
1737    /// The unknown magic drop protects the items beside it: the foreign item
1738    /// first, so a decoder that stopped at it would return nothing, and the
1739    /// good item behind it still decodes.
1740    #[test]
1741    fn test_cbor_decode_drops_only_the_unknown_magic_item() {
1742        let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1743            .cbor_encode()
1744            .unwrap();
1745
1746        let mut unknown_magic: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1747        unknown_magic.extend_from_slice(&0xdeadbeefdeadbeefu64.to_be_bytes());
1748
1749        let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1750        document.extend_from_slice(&unknown_magic);
1751        document.extend_from_slice(&good);
1752
1753        let items = RainMetaDocumentV1Item::cbor_decode(&document).unwrap();
1754        assert_eq!(items.len(), 1, "expected only the good item");
1755        assert_eq!(items[0].magic, KnownMagic::DotrainV1);
1756        assert_eq!(items[0].payload.as_ref(), &[0x42]);
1757    }
1758
1759    /// The document prefix is not a magic number an item may carry: it is the
1760    /// first 8 bytes of a whole document. An item claiming it is malformed
1761    /// structure rather than a type this version does not read, so it stops
1762    /// the document instead of being dropped like an unknown number.
1763    #[test]
1764    fn test_cbor_decode_nested_document_magic_is_not_dropped() {
1765        let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1766            .cbor_encode()
1767            .unwrap();
1768
1769        let mut nested: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1770        nested.extend_from_slice(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes());
1771
1772        let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1773        document.extend_from_slice(&nested);
1774        document.extend_from_slice(&good);
1775
1776        assert!(matches!(
1777            RainMetaDocumentV1Item::cbor_decode(&document),
1778            Err(Error::SerdeCborError(_))
1779        ));
1780    }
1781
1782    /// A cbor map header plus the given already encoded key/value pairs,
1783    /// written per the cbor spec rather than through the encoder, so a map can
1784    /// carry a key twice which no encoder emits.
1785    fn handwritten_entries(entries: &[(Vec<u8>, Vec<u8>)]) -> Vec<u8> {
1786        assert!(entries.len() < 24);
1787        let mut bytes = vec![0xa0 | entries.len() as u8];
1788        for (key, value) in entries {
1789            bytes.extend_from_slice(key);
1790            bytes.extend_from_slice(value);
1791        }
1792        bytes
1793    }
1794
1795    /// cbor unsigned integer of the magic number, as a map key or a value.
1796    fn handwritten_magic(magic: KnownMagic) -> Vec<u8> {
1797        let mut bytes = vec![0x1b];
1798        bytes.extend_from_slice(&magic.to_prefix_bytes());
1799        bytes
1800    }
1801
1802    /// cbor text string of a string shorter than 24 bytes.
1803    fn handwritten_text(text: &str) -> Vec<u8> {
1804        assert!(text.len() < 24);
1805        let mut bytes = vec![0x60 | text.len() as u8];
1806        bytes.extend_from_slice(text.as_bytes());
1807        bytes
1808    }
1809
1810    /// The repro from #191: a map repeating key 0 must not decode with the
1811    /// last payload winning.
1812    #[test]
1813    fn test_cbor_decode_duplicate_payload_key_errors() {
1814        let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x00, 0x41, 0x02, 0x01, 0x1b];
1815        bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1816        let error = RainMetaDocumentV1Item::cbor_decode(&bytes).unwrap_err();
1817        assert!(matches!(error, Error::SerdeCborError(_)));
1818        assert!(
1819            error.to_string().contains("duplicate field `payload`"),
1820            "{error}"
1821        );
1822    }
1823
1824    /// RFC 8949 ยง5.6: every recognised key is rejected when the map carries it
1825    /// twice, whether the repeated value differs from the first or matches it.
1826    #[test]
1827    fn test_cbor_decode_duplicate_any_key_errors() -> Result<(), Error> {
1828        let cases: [(Vec<u8>, Vec<u8>, Vec<u8>); 6] = [
1829            (vec![0x00], vec![0x41, 0x01], vec![0x41, 0x02]),
1830            (
1831                vec![0x01],
1832                handwritten_magic(KnownMagic::DotrainV1),
1833                handwritten_magic(KnownMagic::RainlangV1),
1834            ),
1835            (
1836                vec![0x02],
1837                handwritten_text("application/cbor"),
1838                handwritten_text("application/json"),
1839            ),
1840            (
1841                vec![0x03],
1842                handwritten_text("identity"),
1843                handwritten_text("deflate"),
1844            ),
1845            (vec![0x04], handwritten_text("en"), handwritten_text("none")),
1846            (
1847                handwritten_magic(KnownMagic::OaSchema),
1848                handwritten_text("hi"),
1849                handwritten_text("bye"),
1850            ),
1851        ];
1852        let base: Vec<(Vec<u8>, Vec<u8>)> = cases
1853            .iter()
1854            .map(|(key, value, _)| (key.clone(), value.clone()))
1855            .collect();
1856
1857        let decoded = RainMetaDocumentV1Item::cbor_decode(&handwritten_entries(&base))?;
1858        assert_eq!(decoded.len(), 1);
1859        assert_eq!(decoded[0].payload.as_ref(), &[0x01]);
1860        assert_eq!(decoded[0].magic, KnownMagic::DotrainV1);
1861        assert_eq!(decoded[0].content_type, ContentType::Cbor);
1862        assert_eq!(decoded[0].content_encoding, ContentEncoding::Identity);
1863        assert_eq!(decoded[0].content_language, ContentLanguage::En);
1864        assert_eq!(decoded[0].schema.as_deref(), Some("hi"));
1865
1866        for (key, value, other_value) in &cases {
1867            for repeated in [value, other_value] {
1868                let mut entries = base.clone();
1869                entries.push((key.clone(), repeated.clone()));
1870                let error = RainMetaDocumentV1Item::cbor_decode(&handwritten_entries(&entries))
1871                    .unwrap_err();
1872                assert!(
1873                    matches!(error, Error::SerdeCborError(_)),
1874                    "{key:?} {error:?}"
1875                );
1876                assert!(
1877                    error.to_string().contains("duplicate field"),
1878                    "{key:?} {error}"
1879                );
1880            }
1881        }
1882        Ok(())
1883    }
1884
1885    /// An index this version has no meaning for is skipped rather than
1886    /// rejected, but RFC 8949 ยง5.6 does not ask whether a key is understood:
1887    /// a map that carries an unknown index twice is invalid too, whether that
1888    /// index is a plain integer or a magic number other than OaSchema.
1889    #[test]
1890    fn test_cbor_decode_duplicate_unknown_key_errors() -> Result<(), Error> {
1891        let base: Vec<(Vec<u8>, Vec<u8>)> = vec![
1892            (vec![0x00], vec![0x41, 0x01]),
1893            (vec![0x01], handwritten_magic(KnownMagic::DotrainV1)),
1894        ];
1895        // key 5 as a plausible future index and the OaHashList magic as a
1896        // future magic keyed entry, each with the value cbor to repeat it with
1897        let unknown: [(Vec<u8>, Vec<u8>, Vec<u8>); 2] = [
1898            (vec![0x05], vec![0x07], vec![0x08]),
1899            (
1900                handwritten_magic(KnownMagic::OaHashList),
1901                handwritten_text("hi"),
1902                handwritten_text("bye"),
1903            ),
1904        ];
1905
1906        for (key, value, other_value) in &unknown {
1907            let mut entries = base.clone();
1908            entries.push((key.clone(), value.clone()));
1909            let decoded = RainMetaDocumentV1Item::cbor_decode(&handwritten_entries(&entries))?;
1910            assert_eq!(decoded, vec![plain_item(KnownMagic::DotrainV1, vec![0x01])]);
1911
1912            for repeated in [value, other_value] {
1913                let mut duplicated = entries.clone();
1914                duplicated.push((key.clone(), repeated.clone()));
1915                let error = RainMetaDocumentV1Item::cbor_decode(&handwritten_entries(&duplicated))
1916                    .unwrap_err();
1917                assert!(
1918                    matches!(error, Error::SerdeCborError(_)),
1919                    "{key:?} {error:?}"
1920                );
1921                assert!(
1922                    error.to_string().contains("duplicate map key"),
1923                    "{key:?} {error}"
1924                );
1925            }
1926        }
1927        Ok(())
1928    }
1929
1930    /// A handwritten item map carrying the rain meta document magic under key
1931    /// 1 decodes, so accepting the document magic as an item magic is the
1932    /// decoder's own behaviour and not an artefact of this crate's encoder.
1933    #[test]
1934    fn test_cbor_decode_handwritten_document_magic_item() {
1935        let bytes: Vec<u8> = vec![
1936            0xa2, // map(2)
1937            0x00, // key 0
1938            0x41, 0x01, // bytes(1) 0x01
1939            0x01, // key 1
1940            0x1b, 0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74, // u64 RainMetaDocumentV1
1941        ];
1942        // The document magic in an item's magic position is structurally
1943        // invalid, so the meta carrying it does not decode.
1944        // rainlanguage/rain.metadata#204.
1945        assert!(RainMetaDocumentV1Item::cbor_decode(&bytes).is_err());
1946    }
1947
1948    /// The document magic as an item's own magic marks a payload that is
1949    /// itself a complete rain meta document, which
1950    /// `OrderBuilderStateV1::extract_from_meta` recurses into, so the codec
1951    /// must carry such an item in both directions and leave its payload byte
1952    /// for byte intact.
1953    #[test]
1954    fn test_document_magic_item_carries_a_nested_document() -> Result<(), Error> {
1955        let inner = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1956        let inner_doc = RainMetaDocumentV1Item::cbor_encode_seq(
1957            &vec![inner.clone()],
1958            KnownMagic::RainMetaDocumentV1,
1959        )?;
1960        let outer = plain_item(KnownMagic::RainMetaDocumentV1, inner_doc.clone());
1961        let outer_doc = RainMetaDocumentV1Item::cbor_encode_seq(
1962            &vec![outer.clone()],
1963            KnownMagic::RainMetaDocumentV1,
1964        )?;
1965
1966        // Encoding can still write the document magic into an item's magic
1967        // position, and the payload really is a whole document. Decoding
1968        // refuses it anyway: a nested document is not a shape to descend into,
1969        // it is a corrupt meta, and the usable item inside does not rescue it.
1970        // rainlanguage/rain.metadata#204.
1971        assert!(RainMetaDocumentV1Item::cbor_decode(&outer_doc).is_err());
1972        assert_eq!(
1973            RainMetaDocumentV1Item::cbor_decode(&inner_doc)?,
1974            vec![inner]
1975        );
1976        Ok(())
1977    }
1978
1979    /// Nesting is not a leaf meta type: the unpack layer rejects the document
1980    /// magic so that no payload conversion is ever handed a whole document.
1981    #[test]
1982    fn test_document_magic_item_is_not_unpackable() {
1983        assert!(matches!(
1984            KnownMeta::try_from(KnownMagic::RainMetaDocumentV1),
1985            Err(Error::UnsupportedMeta)
1986        ));
1987        assert!(matches!(
1988            plain_item(KnownMagic::RainMetaDocumentV1, vec![0x01]).unpack_into::<Vec<u8>>(),
1989            Err(Error::UnsupportedMeta)
1990        ));
1991    }
1992
1993    /// unpack decodes the payload according to the content encoding.
1994    #[test]
1995    fn test_unpack_decodes_content_encoding() -> Result<(), Error> {
1996        let content = b"unpack me via deflate".to_vec();
1997        let packed = ContentEncoding::Deflate.encode(&content);
1998        assert_ne!(packed, content);
1999        let mut item = plain_item(KnownMagic::DotrainV1, packed);
2000        item.content_encoding = ContentEncoding::Deflate;
2001        assert_eq!(item.unpack()?, content);
2002
2003        let item = plain_item(KnownMagic::DotrainV1, content.clone());
2004        assert_eq!(item.unpack()?, content);
2005        Ok(())
2006    }
2007
2008    /// The 13 meta magics unpack; the document magic, the web data magic and
2009    /// the Oa magics are rejected with UnsupportedMeta.
2010    #[test]
2011    fn test_unpack_into_whitelist() {
2012        use strum::IntoEnumIterator;
2013        let supported = [
2014            KnownMagic::OpMetaV1,
2015            KnownMagic::DotrainV1,
2016            KnownMagic::RainlangV1,
2017            KnownMagic::SolidityAbiV2,
2018            KnownMagic::AuthoringMetaV1,
2019            KnownMagic::AuthoringMetaV2,
2020            KnownMagic::AddressList,
2021            KnownMagic::InterpreterCallerMetaV1,
2022            KnownMagic::ExpressionDeployerV2BytecodeV1,
2023            KnownMagic::DotrainSourceV1,
2024            KnownMagic::OrderBuilderStateV1,
2025            KnownMagic::RainlangSourceV1,
2026            KnownMagic::RaindexSignedContextOracleV1,
2027        ];
2028        for magic in supported {
2029            let unpacked: Vec<u8> = plain_item(magic, vec![0x61]).unpack_into().unwrap();
2030            assert_eq!(unpacked, vec![0x61], "{:?}", magic);
2031        }
2032        let unsupported = [
2033            KnownMagic::RainMetaDocumentV1,
2034            KnownMagic::WebDataV1,
2035            KnownMagic::OaSchema,
2036            KnownMagic::OaHashList,
2037            KnownMagic::OaStructure,
2038            KnownMagic::OaTokenImage,
2039            KnownMagic::OaTokenCredentialLinks,
2040        ];
2041        for magic in unsupported {
2042            let result: Result<Vec<u8>, Error> = plain_item(magic, vec![0x61]).unpack_into();
2043            assert!(matches!(result, Err(Error::UnsupportedMeta)), "{:?}", magic);
2044        }
2045        // together the two lists cover every variant
2046        assert_eq!(
2047            supported.len() + unsupported.len(),
2048            KnownMagic::iter().count()
2049        );
2050    }
2051
2052    /// Invalid utf8 payloads error when unpacking into String rather than
2053    /// being replaced lossily.
2054    #[test]
2055    fn test_try_into_string_invalid_utf8_errors() {
2056        let item = plain_item(KnownMagic::DotrainV1, vec![0xff, 0xfe]);
2057        let result: Result<String, Error> = item.try_into();
2058        assert!(matches!(result, Err(Error::FromUtf8Error(_))));
2059    }
2060
2061    /// Unpacking into Vec<u8> decodes the content encoding first.
2062    #[test]
2063    fn test_try_into_vec_decodes_encoding() -> Result<(), Error> {
2064        let content = b"raw bytecode bytes \x00\x01\x02".to_vec();
2065        let packed = ContentEncoding::Deflate.encode(&content);
2066        let mut item = plain_item(KnownMagic::ExpressionDeployerV2BytecodeV1, packed.clone());
2067        item.content_encoding = ContentEncoding::Deflate;
2068        let unpacked: Vec<u8> = item.try_into()?;
2069        assert_eq!(unpacked, content);
2070        assert_ne!(unpacked, packed);
2071        Ok(())
2072    }
2073
2074    /// Deflate encode produces a zlib stream (RFC1950 CMF byte 0x78) that is
2075    /// actually compressed and roundtrips through decode.
2076    #[test]
2077    fn test_content_encoding_deflate_roundtrip() -> Result<(), Error> {
2078        let content = b"hello rain deflate fixture hello rain deflate fixture".to_vec();
2079        let encoded = ContentEncoding::Deflate.encode(&content);
2080        assert_ne!(encoded, content);
2081        assert_eq!(encoded[0], 0x78);
2082        assert_eq!(ContentEncoding::Deflate.decode(&encoded)?, content);
2083        Ok(())
2084    }
2085
2086    /// None and Identity pass data through unchanged on encode and decode.
2087    #[test]
2088    fn test_content_encoding_passthrough() -> Result<(), Error> {
2089        let data = vec![0x00, 0xff, 0x10];
2090        for encoding in [ContentEncoding::None, ContentEncoding::Identity] {
2091            assert_eq!(encoding.encode(&data), data, "{:?}", encoding);
2092            assert_eq!(encoding.decode(&data)?, data, "{:?}", encoding);
2093        }
2094        Ok(())
2095    }
2096
2097    /// Decode accepts a zlib stream and falls back to a raw deflate stream.
2098    /// Fixtures generated out of band from "hello rain deflate fixture".
2099    #[test]
2100    fn test_content_encoding_decode_fixtures() -> Result<(), Error> {
2101        let content = b"hello rain deflate fixture".to_vec();
2102        let zlib: Vec<u8> = vec![
2103            120, 156, 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44,
2104            73, 85, 72, 203, 172, 40, 41, 45, 74, 5, 0, 132, 64, 9, 251,
2105        ];
2106        let raw: Vec<u8> = vec![
2107            203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44, 73, 85, 72,
2108            203, 172, 40, 41, 45, 74, 5, 0,
2109        ];
2110        assert_eq!(ContentEncoding::Deflate.decode(&zlib)?, content);
2111        assert_eq!(ContentEncoding::Deflate.decode(&raw)?, content);
2112        Ok(())
2113    }
2114
2115    /// Data that is neither a zlib stream nor a raw deflate stream errors
2116    /// with InflateError instead of returning bytes.
2117    #[test]
2118    fn test_content_encoding_decode_garbage_errors() {
2119        let garbage = [0xffu8, 0xff, 0xff, 0xff];
2120        assert!(matches!(
2121            ContentEncoding::Deflate.decode(&garbage),
2122            Err(Error::InflateError(_))
2123        ));
2124    }
2125
2126    /// The CLI-facing strum names for the content headers are kebab-case.
2127    #[test]
2128    fn test_content_headers_strum_names() {
2129        use std::str::FromStr;
2130        assert_eq!(
2131            ContentEncoding::from_str("deflate").unwrap(),
2132            ContentEncoding::Deflate
2133        );
2134        assert_eq!(
2135            ContentEncoding::from_str("identity").unwrap(),
2136            ContentEncoding::Identity
2137        );
2138        assert_eq!(
2139            ContentEncoding::from_str("none").unwrap(),
2140            ContentEncoding::None
2141        );
2142        assert_eq!(ContentEncoding::Deflate.to_string(), "deflate");
2143        assert_eq!(
2144            ContentType::from_str("octet-stream").unwrap(),
2145            ContentType::OctetStream
2146        );
2147        assert_eq!(ContentType::from_str("json").unwrap(), ContentType::Json);
2148        assert_eq!(ContentType::Json.to_string(), "json");
2149        assert_eq!(
2150            ContentLanguage::from_str("en").unwrap(),
2151            ContentLanguage::En
2152        );
2153    }
2154
2155    /// Every documented meta magic maps to its KnownMeta while the document
2156    /// magic and the Oa magics are unsupported.
2157    #[test]
2158    fn test_known_meta_try_from_magic() {
2159        let cases: [(KnownMagic, KnownMeta); 13] = [
2160            (KnownMagic::OpMetaV1, KnownMeta::OpV1),
2161            (KnownMagic::DotrainV1, KnownMeta::DotrainV1),
2162            (KnownMagic::RainlangV1, KnownMeta::RainlangV1),
2163            (KnownMagic::SolidityAbiV2, KnownMeta::SolidityAbiV2),
2164            (KnownMagic::AuthoringMetaV1, KnownMeta::AuthoringMetaV1),
2165            (KnownMagic::AuthoringMetaV2, KnownMeta::AuthoringMetaV2),
2166            (KnownMagic::AddressList, KnownMeta::AddressList),
2167            (
2168                KnownMagic::InterpreterCallerMetaV1,
2169                KnownMeta::InterpreterCallerMetaV1,
2170            ),
2171            (
2172                KnownMagic::ExpressionDeployerV2BytecodeV1,
2173                KnownMeta::ExpressionDeployerV2BytecodeV1,
2174            ),
2175            (KnownMagic::RainlangSourceV1, KnownMeta::RainlangSourceV1),
2176            (KnownMagic::DotrainSourceV1, KnownMeta::DotrainSourceV1),
2177            (
2178                KnownMagic::OrderBuilderStateV1,
2179                KnownMeta::OrderBuilderStateV1,
2180            ),
2181            (
2182                KnownMagic::RaindexSignedContextOracleV1,
2183                KnownMeta::RaindexSignedContextOracleV1,
2184            ),
2185        ];
2186        for (magic, meta) in cases {
2187            assert_eq!(KnownMeta::try_from(magic).unwrap(), meta, "{:?}", magic);
2188        }
2189        for magic in [
2190            KnownMagic::RainMetaDocumentV1,
2191            KnownMagic::WebDataV1,
2192            KnownMagic::OaSchema,
2193            KnownMagic::OaHashList,
2194            KnownMagic::OaStructure,
2195            KnownMagic::OaTokenImage,
2196            KnownMagic::OaTokenCredentialLinks,
2197        ] {
2198            assert!(
2199                matches!(KnownMeta::try_from(magic), Err(Error::UnsupportedMeta)),
2200                "{:?}",
2201                magic
2202            );
2203        }
2204    }
2205
2206    /// KnownMeta parses from and displays as the kebab-case names used by the
2207    /// CLI (validate --meta, build, schema show).
2208    #[test]
2209    fn test_known_meta_strum_parse_display() {
2210        use std::str::FromStr;
2211        assert_eq!(
2212            KnownMeta::from_str("solidity-abi-v2").unwrap(),
2213            KnownMeta::SolidityAbiV2
2214        );
2215        assert_eq!(
2216            KnownMeta::from_str("interpreter-caller-meta-v1").unwrap(),
2217            KnownMeta::InterpreterCallerMetaV1
2218        );
2219        assert_eq!(KnownMeta::SolidityAbiV2.to_string(), "solidity-abi-v2");
2220    }
2221
2222    /// search() lowercases the hash before building the query variables.
2223    #[tokio::test]
2224    async fn test_search_lowercases_hash() {
2225        use httpmock::prelude::*;
2226        let (_, doc) = sample_authoring_doc();
2227        let hash_upper = format!("0x{}", "AB".repeat(32));
2228        let server = MockServer::start();
2229        let mock = server.mock(|when, then| {
2230            when.method(POST)
2231                .body_contains(hash_upper.to_ascii_lowercase());
2232            then.status(200).json_body(json!({
2233                "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2234            }));
2235        });
2236        let response = search(&hash_upper, &vec![server.url("/sg")]).await.unwrap();
2237        assert_eq!(response.bytes, doc);
2238        mock.assert();
2239    }
2240
2241    /// search() queries every subgraph and the first success wins even when
2242    /// an earlier subgraph fails.
2243    #[tokio::test]
2244    async fn test_search_first_success_wins() {
2245        use httpmock::prelude::*;
2246        let (_, doc) = sample_authoring_doc();
2247        let bad = MockServer::start();
2248        let _bad_mock = bad.mock(|when, then| {
2249            when.method(POST);
2250            then.status(500).body("subgraph down");
2251        });
2252        let good = MockServer::start();
2253        let _good_mock = good.mock(|when, then| {
2254            when.method(POST);
2255            then.status(200).json_body(json!({
2256                "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2257            }));
2258        });
2259        let response = search(
2260            &format!("0x{}", "11".repeat(32)),
2261            &vec![bad.url("/sg"), good.url("/sg")],
2262        )
2263        .await
2264        .unwrap();
2265        assert_eq!(response.bytes, doc);
2266    }
2267
2268    /// An empty subgraph list has nothing to fan out to, so the search
2269    /// reports a miss rather than reaching futures::select_ok, which panics on
2270    /// an empty iterator.
2271    #[tokio::test]
2272    async fn test_search_empty_subgraphs_is_a_miss() {
2273        let hash = format!("0x{}", "33".repeat(32));
2274        assert!(matches!(
2275            search(&hash, &vec![]).await,
2276            Err(Error::NoRecordFound)
2277        ));
2278    }
2279
2280    /// The erc165 gate short circuits: neither answer reaches the
2281    /// IDescribedByMetaV1 supportsInterface call, so the queued "true" is
2282    /// never consumed and cannot be mistaken for the contract's own answer.
2283    ///
2284    /// The two answers are not the same fact. A contract that says no is
2285    /// `Ok(false)`; a probe that could not finish is an error, because a
2286    /// transport failure is not a contract declining an interface. rain-erc
2287    /// makes that distinction itself - "callers can treat that as answer
2288    /// unknown rather than silently reading no support" - and an
2289    /// `unwrap_or(false)` here threw it away.
2290    #[tokio::test]
2291    async fn test_implements_erc165_gate_short_circuits() {
2292        let address = Address::random();
2293
2294        // erc165 check1 answers false
2295        let asserter = Asserter::new();
2296        let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2297        asserter
2298            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2299        asserter
2300            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2301        assert!(!implements_i_described_by_meta_v1(&provider, address)
2302            .await
2303            .unwrap());
2304
2305        // erc165 probe errors. Getting an error back is itself the proof of
2306        // the short circuit: had the queued "true" been consumed by the
2307        // interface probe, this would be Ok(true).
2308        let asserter = Asserter::new();
2309        let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2310        asserter.push_failure(ErrorPayload {
2311            code: -32000,
2312            message: "connection reset".into(),
2313            data: None,
2314        });
2315        asserter
2316            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2317        assert!(implements_i_described_by_meta_v1(&provider, address)
2318            .await
2319            .is_err());
2320    }
2321
2322    /// An empty eth_call response is a contract that did not answer, which
2323    /// ERC-165 reads as "does not implement".
2324    #[tokio::test]
2325    async fn test_implements_empty_response_is_false() {
2326        let address = Address::random();
2327        let asserter = Asserter::new();
2328        let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2329        asserter
2330            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2331        asserter
2332            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2333        asserter.push_success(&"0x");
2334        assert!(!implements_i_described_by_meta_v1(&provider, address)
2335            .await
2336            .unwrap());
2337    }
2338
2339    /// A non-revert failure of the IDescribedByMetaV1 supportsInterface call is
2340    /// "answer unknown": it propagates as Err rather than reading as "does not
2341    /// implement".
2342    #[tokio::test]
2343    async fn test_implements_described_by_call_error_is_unknown() {
2344        let address = Address::random();
2345        let asserter = Asserter::new();
2346        let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2347        asserter
2348            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2349        asserter
2350            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2351        asserter.push_failure(ErrorPayload {
2352            code: -32005,
2353            message: "rate limit exceeded".into(),
2354            data: None,
2355        });
2356        let error = implements_i_described_by_meta_v1(&provider, address)
2357            .await
2358            .unwrap_err();
2359        assert!(error.to_string().contains("rate limit exceeded"));
2360    }
2361
2362    /// A non-empty IDescribedByMetaV1 supportsInterface response that does not
2363    /// decode as bool is a decode failure, so "answer unknown" rather than
2364    /// "does not implement".
2365    #[tokio::test]
2366    async fn test_implements_undecodable_response_is_unknown() {
2367        let address = Address::random();
2368        let asserter = Asserter::new();
2369        let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2370        asserter
2371            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2372        asserter
2373            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2374        asserter.push_success(&"0xdeadbeef");
2375        implements_i_described_by_meta_v1(&provider, address)
2376            .await
2377            .unwrap_err();
2378    }
2379
2380    /// No constructor injects a subgraph the caller did not ask for, and a
2381    /// store with none resolves every network lookup to None rather than
2382    /// reaching the select_ok panic.
2383    #[tokio::test]
2384    async fn test_store_constructors_inject_no_subgraphs() {
2385        assert!(Store::new().subgraphs().is_empty());
2386        assert!(Store::default().subgraphs().is_empty());
2387        assert!(
2388            Store::create(&vec![], &MetaCache::default(), &HashMap::new())
2389                .subgraphs()
2390                .is_empty()
2391        );
2392
2393        let hash = [0u8; 32];
2394        let mut store = Store::default();
2395        assert!(store.update(&hash).await.is_err());
2396    }
2397
2398    /// create() takes only the given subgraphs, and keeps a dotrain uri only
2399    /// when its hash is present in the cache.
2400    ///
2401    /// This used to assert create() dropped a cache entry whose bytes did not
2402    /// hash to its key. That entry is no longer constructible: create() takes
2403    /// a [MetaCache], which has no way to hold one, so there is nothing left
2404    /// for create() to validate.
2405    #[test]
2406    fn test_store_create_validates_entries() {
2407        let (_, doc) = sample_authoring_doc();
2408        let good_hash = keccak256(&doc).0.to_vec();
2409        let mut cache = MetaCache::default();
2410        cache.insert_verified(&good_hash, doc.clone()).unwrap();
2411        let mut dotrain_cache = HashMap::new();
2412        dotrain_cache.insert("a.rain".to_string(), good_hash.clone());
2413        dotrain_cache.insert("missing.rain".to_string(), vec![0x44u8; 32]);
2414
2415        let store = Store::create(
2416            &vec!["https://example.com/custom-sg".to_string()],
2417            &cache,
2418            &dotrain_cache,
2419        );
2420
2421        assert_eq!(
2422            store.subgraphs(),
2423            &vec!["https://example.com/custom-sg".to_string()]
2424        );
2425        assert_eq!(store.get_meta(&good_hash), Some(&doc));
2426        assert_eq!(store.get_dotrain_hash("a.rain"), Some(&good_hash));
2427        assert_eq!(store.get_dotrain_hash("missing.rain"), None);
2428    }
2429
2430    /// add_subgraphs skips urls already present.
2431    #[test]
2432    fn test_store_add_subgraphs_dedupe() {
2433        let mut store = Store::new();
2434        store.add_subgraphs(&vec!["sg-a".to_string()]);
2435        store.add_subgraphs(&vec!["sg-a".to_string(), "sg-b".to_string()]);
2436        assert_eq!(
2437            store.subgraphs(),
2438            &vec!["sg-a".to_string(), "sg-b".to_string()]
2439        );
2440    }
2441
2442    /// set_dotrain on a fresh uri returns (new_hash, empty), keyed by the
2443    /// keccak of the cbor encoded DotrainV1 meta item, and every dotrain
2444    /// getter resolves it.
2445    #[test]
2446    fn test_store_dotrain_getters_and_set_fresh() {
2447        let mut store = Store::new();
2448        let text = "some dotrain content";
2449        let (hash, old) = store.set_dotrain(text, "file.rain", false).unwrap();
2450        assert!(old.is_empty());
2451        let expected_item = RainMetaDocumentV1Item {
2452            payload: serde_bytes::ByteBuf::from(text.as_bytes()),
2453            magic: KnownMagic::DotrainV1,
2454            content_type: ContentType::OctetStream,
2455            content_encoding: ContentEncoding::None,
2456            content_language: ContentLanguage::None,
2457            schema: None,
2458        };
2459        let expected_bytes = expected_item.cbor_encode().unwrap();
2460        assert_eq!(hash, keccak256(&expected_bytes).0.to_vec());
2461        assert_eq!(store.get_dotrain_hash("file.rain"), Some(&hash));
2462        assert_eq!(store.get_dotrain_uri(&hash), Some(&"file.rain".to_string()));
2463        assert_eq!(store.get_dotrain_meta("file.rain"), Some(&expected_bytes));
2464        assert_eq!(store.get_dotrain_hash("other.rain"), None);
2465        assert_eq!(store.get_dotrain_uri(&[0u8; 32]), None);
2466        assert_eq!(store.get_dotrain_meta("other.rain"), None);
2467    }
2468
2469    /// set_dotrain branches: same content keeps the meta and reports no old
2470    /// hash; different content remaps the uri and drops or keeps the old
2471    /// meta per keep_old.
2472    #[test]
2473    fn test_store_set_dotrain_branches() {
2474        let mut store = Store::new();
2475        let (hash_one, _) = store.set_dotrain("text one", "a.rain", false).unwrap();
2476
2477        // same content again: same hash, no old hash, meta retained
2478        let (hash_same, old_same) = store.set_dotrain("text one", "a.rain", false).unwrap();
2479        assert_eq!(hash_same, hash_one);
2480        assert!(old_same.is_empty());
2481        assert!(store.get_meta(&hash_one).is_some());
2482
2483        // different content, keep_old = false: remap and drop the old meta
2484        let (hash_two, old_two) = store.set_dotrain("text two", "a.rain", false).unwrap();
2485        assert_ne!(hash_two, hash_one);
2486        assert_eq!(old_two, hash_one);
2487        assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_two));
2488        assert!(store.get_meta(&hash_one).is_none());
2489        assert!(store.get_meta(&hash_two).is_some());
2490
2491        // different content, keep_old = true: old meta kept
2492        let (hash_three, old_three) = store.set_dotrain("text three", "a.rain", true).unwrap();
2493        assert_eq!(old_three, hash_two);
2494        assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_three));
2495        assert!(store.get_meta(&hash_two).is_some());
2496        assert!(store.get_meta(&hash_three).is_some());
2497    }
2498
2499    /// delete_dotrain removes the uri mapping and honors keep_meta for the
2500    /// cached meta bytes.
2501    #[test]
2502    fn test_store_delete_dotrain_keep_meta() {
2503        let mut store = Store::new();
2504        let (hash, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2505        store.delete_dotrain("d.rain", false);
2506        assert_eq!(store.get_dotrain_hash("d.rain"), None);
2507        assert!(store.get_meta(&hash).is_none());
2508
2509        let (hash_again, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2510        store.delete_dotrain("d.rain", true);
2511        assert_eq!(store.get_dotrain_hash("d.rain"), None);
2512        assert!(store.get_meta(&hash_again).is_some());
2513    }
2514
2515    /// merge keeps this store's entry in every map on a key collision, takes
2516    /// the keys it does not already hold, and unions the subgraphs.
2517    #[test]
2518    fn test_store_merge_semantics() {
2519        let mut ours = Store::new();
2520        let mut theirs = Store::new();
2521
2522        // meta cache: two different metas cannot share a key - the key IS
2523        // their digest - so merge takes the other store's entry rather than
2524        // choosing between them
2525        let mine = b"mine".to_vec();
2526        let yours = b"yours".to_vec();
2527        let mine_hash = keccak256(&mine).0.to_vec();
2528        let yours_hash = keccak256(&yours).0.to_vec();
2529        ours.update_with(&mine_hash, &mine).unwrap();
2530        theirs.update_with(&yours_hash, &yours).unwrap();
2531
2532        // same dotrain uri, different content
2533        let (hash_ours, _) = ours.set_dotrain("content a", "x.rain", false).unwrap();
2534        let (_hash_theirs, _) = theirs.set_dotrain("content b", "x.rain", false).unwrap();
2535
2536        theirs.add_subgraphs(&vec!["sg-their".to_string()]);
2537
2538        ours.merge(&theirs);
2539
2540        assert_eq!(ours.get_meta(&mine_hash), Some(&mine));
2541        assert_eq!(ours.get_meta(&yours_hash), Some(&yours));
2542        // dotrain: existing uri mapping wins
2543        assert_eq!(ours.get_dotrain_hash("x.rain"), Some(&hash_ours));
2544        // subgraphs merged
2545        assert!(ours.subgraphs().contains(&"sg-their".to_string()));
2546    }
2547
2548    /// update() stores the fetched bytes under the requested hash and each
2549    /// inner meta item under the keccak of its own encoding; update_check
2550    /// serves a cached hash without any network access.
2551    #[tokio::test]
2552    async fn test_store_update_and_update_check() {
2553        use httpmock::prelude::*;
2554        let authoring_meta: AuthoringMeta = serde_json::from_str(
2555            r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
2556        )
2557        .unwrap();
2558        let item_one = RainMetaDocumentV1Item {
2559            payload: serde_bytes::ByteBuf::from(authoring_meta.abi_encode_validate().unwrap()),
2560            magic: KnownMagic::AuthoringMetaV1,
2561            content_type: ContentType::Cbor,
2562            content_encoding: ContentEncoding::None,
2563            content_language: ContentLanguage::None,
2564            schema: None,
2565        };
2566        let item_two = sample_dotrain_item();
2567        let doc = RainMetaDocumentV1Item::cbor_encode_seq(
2568            &vec![item_one.clone(), item_two.clone()],
2569            KnownMagic::RainMetaDocumentV1,
2570        )
2571        .unwrap();
2572        let requested = keccak256(&doc).0.to_vec();
2573        let server = MockServer::start();
2574        let _mock = server.mock(|when, then| {
2575            when.method(POST);
2576            then.status(200).json_body(json!({
2577                "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2578            }));
2579        });
2580        let mut store = Store::new();
2581        store.add_subgraphs(&vec![server.url("/sg")]);
2582        let fetched = store.update(&requested).await.cloned().unwrap();
2583        assert_eq!(fetched, doc);
2584        assert_eq!(store.get_meta(&requested), Some(&doc));
2585        let inner_one = item_one.cbor_encode().unwrap();
2586        let inner_two = item_two.cbor_encode().unwrap();
2587        assert_eq!(
2588            store.get_meta(keccak256(&inner_one).0.as_ref()),
2589            Some(&inner_one)
2590        );
2591        assert_eq!(
2592            store.get_meta(keccak256(&inner_two).0.as_ref()),
2593            Some(&inner_two)
2594        );
2595
2596        // update_check: cached hash short-circuits, no subgraphs needed
2597        let mut cached_store = Store::new();
2598        let bytes = b"standalone meta bytes".to_vec();
2599        let hash = keccak256(&bytes).0.to_vec();
2600        assert!(cached_store.update_with(&hash, &bytes).is_ok());
2601        assert_eq!(cached_store.update_check(&hash).await.unwrap(), &bytes);
2602    }
2603
2604    /// update() applies the same keccak gate as update_with to the subgraph
2605    /// response, so bytes that do not hash to the requested hash poison
2606    /// neither the requested key nor the inner item keys.
2607    #[tokio::test]
2608    async fn test_store_update_rejects_hash_mismatch() {
2609        use httpmock::prelude::*;
2610        let (_, doc) = sample_authoring_doc();
2611        let requested = keccak256(b"the real content").0.to_vec();
2612        let server = MockServer::start();
2613        let _mock = server.mock(|when, then| {
2614            when.method(POST);
2615            then.status(200).json_body(json!({
2616                "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2617            }));
2618        });
2619        let mut store = Store::new();
2620        store.add_subgraphs(&vec![server.url("/sg")]);
2621        assert!(store.update(&requested).await.is_err());
2622        assert!(store.get_meta(&requested).is_none());
2623        assert!(store.cache().is_empty());
2624        // the miss is not cached either, so update_check retries and misses again
2625        assert!(store.update_check(&requested).await.is_err());
2626    }
2627
2628    /// Store::new() starts with no subgraphs, so every uncached lookup that
2629    /// reaches the network on it resolves to None instead of panicking.
2630    #[tokio::test]
2631    async fn test_store_no_subgraphs_lookups_return_none() {
2632        let hash = [0u8; 32];
2633        let mut store = Store::new();
2634        assert!(store.update(&hash).await.is_err());
2635        assert!(store.update_check(&hash).await.is_err());
2636        assert!(store.cache().is_empty());
2637    }
2638
2639    /// update_with enforces keccak(bytes) == hash, leaves an existing entry
2640    /// untouched, and unpacks inner items only for RainMetaDocumentV1
2641    /// prefixed bytes.
2642    #[test]
2643    fn test_store_update_with_validation_and_content() {
2644        // hash mismatch rejected
2645        let mut store = Store::new();
2646        let bytes = b"payload bytes".to_vec();
2647        let wrong_hash = vec![0x99u8; 32];
2648        // A mismatch is CorruptRecord, not NoRecordFound: the responder
2649        // answered about one hash with bytes that are another, which is not
2650        // the same fact as the hash being absent. #234 and #213 settled that
2651        // distinction for the query layer.
2652        match store.update_with(&wrong_hash, &bytes).unwrap_err() {
2653            Error::CorruptRecord(message) => {
2654                assert!(
2655                    message.contains(&hex::encode_prefixed(&wrong_hash)),
2656                    "{}",
2657                    message
2658                )
2659            }
2660            other => panic!("expected CorruptRecord, got {:?}", other),
2661        }
2662        assert!(store.get_meta(&wrong_hash).is_none());
2663        // valid pair stored
2664        let hash = keccak256(&bytes).0.to_vec();
2665        assert_eq!(store.update_with(&hash, &bytes).unwrap(), &bytes);
2666
2667        // an already cached key returns its entry rather than inserting again.
2668        // Note what is no longer expressible here: the old version of this
2669        // block seeded one hash with unrelated bytes and asserted a later write
2670        // did not overwrite them. Different bytes cannot share a key when the
2671        // key is their digest, so "overwritten with something else" is not a
2672        // state [MetaCache] can be in.
2673        let mut seeded = Store::new();
2674        let planted = b"planted value".to_vec();
2675        let planted_hash = keccak256(&planted).0.to_vec();
2676        seeded.update_with(&planted_hash, &planted).unwrap();
2677        assert_eq!(seeded.cache().len(), 1);
2678        assert_eq!(
2679            seeded.update_with(&planted_hash, &planted).unwrap(),
2680            &planted
2681        );
2682        assert_eq!(seeded.cache().len(), 1);
2683
2684        // prefixed document: inner item stored under keccak of its encoding
2685        let (_, doc) = sample_authoring_doc();
2686        let doc_hash = keccak256(&doc).0.to_vec();
2687        let mut doc_store = Store::new();
2688        assert!(doc_store.update_with(&doc_hash, &doc).is_ok());
2689        let inner = doc[8..].to_vec();
2690        assert_eq!(store_inner_lookup(&doc_store, &inner), Some(inner.clone()));
2691
2692        // bare cbor sequence without the document prefix: no inner extraction
2693        let item_a = sample_dotrain_item().cbor_encode().unwrap();
2694        let (_, doc_b) = sample_authoring_doc();
2695        let item_b = doc_b[8..].to_vec();
2696        let seq = [item_a.clone(), item_b].concat();
2697        let seq_hash = keccak256(&seq).0.to_vec();
2698        let mut seq_store = Store::new();
2699        assert!(seq_store.update_with(&seq_hash, &seq).is_ok());
2700        assert_eq!(store_inner_lookup(&seq_store, &item_a), None);
2701    }
2702
2703    fn store_inner_lookup(store: &Store, inner_encoded: &[u8]) -> Option<Vec<u8>> {
2704        store.get_meta(keccak256(inner_encoded).0.as_ref()).cloned()
2705    }
2706
2707    /// bytes32_to_str propagates invalid utf8 as an error instead of
2708    /// swallowing it.
2709    #[test]
2710    fn test_bytes32_to_str_invalid_utf8() {
2711        let mut bytes = [0u8; 32];
2712        bytes[0] = 0xf0;
2713        bytes[1] = 0x28;
2714        bytes[2] = 0x8c;
2715        bytes[3] = 0x28;
2716        assert!(matches!(bytes32_to_str(&bytes), Err(Error::Utf8Error(_))));
2717        let no_nul = [0xffu8; 32];
2718        assert!(matches!(bytes32_to_str(&no_nul), Err(Error::Utf8Error(_))));
2719    }
2720}