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