Skip to main content

rain_metadata/meta/
mod.rs

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