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