Skip to main content

rain_metadata/meta/
mod.rs

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