Skip to main content

rain_metadata/meta/
mod.rs

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