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