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