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() {
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        // The document magic in an item's magic position is structurally
1767        // invalid, so the meta carrying it does not decode.
1768        // rainlanguage/rain.metadata#204.
1769        assert!(RainMetaDocumentV1Item::cbor_decode(&bytes).is_err());
1770    }
1771
1772    /// The document magic as an item's own magic marks a payload that is
1773    /// itself a complete rain meta document, which
1774    /// `OrderBuilderStateV1::extract_from_meta` recurses into, so the codec
1775    /// must carry such an item in both directions and leave its payload byte
1776    /// for byte intact.
1777    #[test]
1778    fn test_document_magic_item_carries_a_nested_document() -> Result<(), Error> {
1779        let inner = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1780        let inner_doc = RainMetaDocumentV1Item::cbor_encode_seq(
1781            &vec![inner.clone()],
1782            KnownMagic::RainMetaDocumentV1,
1783        )?;
1784        let outer = plain_item(KnownMagic::RainMetaDocumentV1, inner_doc.clone());
1785        let outer_doc = RainMetaDocumentV1Item::cbor_encode_seq(
1786            &vec![outer.clone()],
1787            KnownMagic::RainMetaDocumentV1,
1788        )?;
1789
1790        // Encoding can still write the document magic into an item's magic
1791        // position, and the payload really is a whole document. Decoding
1792        // refuses it anyway: a nested document is not a shape to descend into,
1793        // it is a corrupt meta, and the usable item inside does not rescue it.
1794        // rainlanguage/rain.metadata#204.
1795        assert!(RainMetaDocumentV1Item::cbor_decode(&outer_doc).is_err());
1796        assert_eq!(
1797            RainMetaDocumentV1Item::cbor_decode(&inner_doc)?,
1798            vec![inner]
1799        );
1800        Ok(())
1801    }
1802
1803    /// Nesting is not a leaf meta type: the unpack layer rejects the document
1804    /// magic so that no payload conversion is ever handed a whole document.
1805    #[test]
1806    fn test_document_magic_item_is_not_unpackable() {
1807        assert!(matches!(
1808            KnownMeta::try_from(KnownMagic::RainMetaDocumentV1),
1809            Err(Error::UnsupportedMeta)
1810        ));
1811        assert!(matches!(
1812            plain_item(KnownMagic::RainMetaDocumentV1, vec![0x01]).unpack_into::<Vec<u8>>(),
1813            Err(Error::UnsupportedMeta)
1814        ));
1815    }
1816
1817    /// unpack decodes the payload according to the content encoding.
1818    #[test]
1819    fn test_unpack_decodes_content_encoding() -> Result<(), Error> {
1820        let content = b"unpack me via deflate".to_vec();
1821        let packed = ContentEncoding::Deflate.encode(&content);
1822        assert_ne!(packed, content);
1823        let mut item = plain_item(KnownMagic::DotrainV1, packed);
1824        item.content_encoding = ContentEncoding::Deflate;
1825        assert_eq!(item.unpack()?, content);
1826
1827        let item = plain_item(KnownMagic::DotrainV1, content.clone());
1828        assert_eq!(item.unpack()?, content);
1829        Ok(())
1830    }
1831
1832    /// The 13 meta magics unpack; the document magic, the web data magic and
1833    /// the Oa magics are rejected with UnsupportedMeta.
1834    #[test]
1835    fn test_unpack_into_whitelist() {
1836        use strum::IntoEnumIterator;
1837        let supported = [
1838            KnownMagic::OpMetaV1,
1839            KnownMagic::DotrainV1,
1840            KnownMagic::RainlangV1,
1841            KnownMagic::SolidityAbiV2,
1842            KnownMagic::AuthoringMetaV1,
1843            KnownMagic::AuthoringMetaV2,
1844            KnownMagic::AddressList,
1845            KnownMagic::InterpreterCallerMetaV1,
1846            KnownMagic::ExpressionDeployerV2BytecodeV1,
1847            KnownMagic::DotrainSourceV1,
1848            KnownMagic::OrderBuilderStateV1,
1849            KnownMagic::RainlangSourceV1,
1850            KnownMagic::RaindexSignedContextOracleV1,
1851        ];
1852        for magic in supported {
1853            let unpacked: Vec<u8> = plain_item(magic, vec![0x61]).unpack_into().unwrap();
1854            assert_eq!(unpacked, vec![0x61], "{:?}", magic);
1855        }
1856        let unsupported = [
1857            KnownMagic::RainMetaDocumentV1,
1858            KnownMagic::WebDataV1,
1859            KnownMagic::OaSchema,
1860            KnownMagic::OaHashList,
1861            KnownMagic::OaStructure,
1862            KnownMagic::OaTokenImage,
1863            KnownMagic::OaTokenCredentialLinks,
1864        ];
1865        for magic in unsupported {
1866            let result: Result<Vec<u8>, Error> = plain_item(magic, vec![0x61]).unpack_into();
1867            assert!(matches!(result, Err(Error::UnsupportedMeta)), "{:?}", magic);
1868        }
1869        // together the two lists cover every variant
1870        assert_eq!(
1871            supported.len() + unsupported.len(),
1872            KnownMagic::iter().count()
1873        );
1874    }
1875
1876    /// Invalid utf8 payloads error when unpacking into String rather than
1877    /// being replaced lossily.
1878    #[test]
1879    fn test_try_into_string_invalid_utf8_errors() {
1880        let item = plain_item(KnownMagic::DotrainV1, vec![0xff, 0xfe]);
1881        let result: Result<String, Error> = item.try_into();
1882        assert!(matches!(result, Err(Error::FromUtf8Error(_))));
1883    }
1884
1885    /// Unpacking into Vec<u8> decodes the content encoding first.
1886    #[test]
1887    fn test_try_into_vec_decodes_encoding() -> Result<(), Error> {
1888        let content = b"raw bytecode bytes \x00\x01\x02".to_vec();
1889        let packed = ContentEncoding::Deflate.encode(&content);
1890        let mut item = plain_item(KnownMagic::ExpressionDeployerV2BytecodeV1, packed.clone());
1891        item.content_encoding = ContentEncoding::Deflate;
1892        let unpacked: Vec<u8> = item.try_into()?;
1893        assert_eq!(unpacked, content);
1894        assert_ne!(unpacked, packed);
1895        Ok(())
1896    }
1897
1898    /// Deflate encode produces a zlib stream (RFC1950 CMF byte 0x78) that is
1899    /// actually compressed and roundtrips through decode.
1900    #[test]
1901    fn test_content_encoding_deflate_roundtrip() -> Result<(), Error> {
1902        let content = b"hello rain deflate fixture hello rain deflate fixture".to_vec();
1903        let encoded = ContentEncoding::Deflate.encode(&content);
1904        assert_ne!(encoded, content);
1905        assert_eq!(encoded[0], 0x78);
1906        assert_eq!(ContentEncoding::Deflate.decode(&encoded)?, content);
1907        Ok(())
1908    }
1909
1910    /// None and Identity pass data through unchanged on encode and decode.
1911    #[test]
1912    fn test_content_encoding_passthrough() -> Result<(), Error> {
1913        let data = vec![0x00, 0xff, 0x10];
1914        for encoding in [ContentEncoding::None, ContentEncoding::Identity] {
1915            assert_eq!(encoding.encode(&data), data, "{:?}", encoding);
1916            assert_eq!(encoding.decode(&data)?, data, "{:?}", encoding);
1917        }
1918        Ok(())
1919    }
1920
1921    /// Decode accepts a zlib stream and falls back to a raw deflate stream.
1922    /// Fixtures generated out of band from "hello rain deflate fixture".
1923    #[test]
1924    fn test_content_encoding_decode_fixtures() -> Result<(), Error> {
1925        let content = b"hello rain deflate fixture".to_vec();
1926        let zlib: Vec<u8> = vec![
1927            120, 156, 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44,
1928            73, 85, 72, 203, 172, 40, 41, 45, 74, 5, 0, 132, 64, 9, 251,
1929        ];
1930        let raw: Vec<u8> = vec![
1931            203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44, 73, 85, 72,
1932            203, 172, 40, 41, 45, 74, 5, 0,
1933        ];
1934        assert_eq!(ContentEncoding::Deflate.decode(&zlib)?, content);
1935        assert_eq!(ContentEncoding::Deflate.decode(&raw)?, content);
1936        Ok(())
1937    }
1938
1939    /// Data that is neither a zlib stream nor a raw deflate stream errors
1940    /// with InflateError instead of returning bytes.
1941    #[test]
1942    fn test_content_encoding_decode_garbage_errors() {
1943        let garbage = [0xffu8, 0xff, 0xff, 0xff];
1944        assert!(matches!(
1945            ContentEncoding::Deflate.decode(&garbage),
1946            Err(Error::InflateError(_))
1947        ));
1948    }
1949
1950    /// The CLI-facing strum names for the content headers are kebab-case.
1951    #[test]
1952    fn test_content_headers_strum_names() {
1953        use std::str::FromStr;
1954        assert_eq!(
1955            ContentEncoding::from_str("deflate").unwrap(),
1956            ContentEncoding::Deflate
1957        );
1958        assert_eq!(
1959            ContentEncoding::from_str("identity").unwrap(),
1960            ContentEncoding::Identity
1961        );
1962        assert_eq!(
1963            ContentEncoding::from_str("none").unwrap(),
1964            ContentEncoding::None
1965        );
1966        assert_eq!(ContentEncoding::Deflate.to_string(), "deflate");
1967        assert_eq!(
1968            ContentType::from_str("octet-stream").unwrap(),
1969            ContentType::OctetStream
1970        );
1971        assert_eq!(ContentType::from_str("json").unwrap(), ContentType::Json);
1972        assert_eq!(ContentType::Json.to_string(), "json");
1973        assert_eq!(
1974            ContentLanguage::from_str("en").unwrap(),
1975            ContentLanguage::En
1976        );
1977    }
1978
1979    /// Every documented meta magic maps to its KnownMeta while the document
1980    /// magic and the Oa magics are unsupported.
1981    #[test]
1982    fn test_known_meta_try_from_magic() {
1983        let cases: [(KnownMagic, KnownMeta); 13] = [
1984            (KnownMagic::OpMetaV1, KnownMeta::OpV1),
1985            (KnownMagic::DotrainV1, KnownMeta::DotrainV1),
1986            (KnownMagic::RainlangV1, KnownMeta::RainlangV1),
1987            (KnownMagic::SolidityAbiV2, KnownMeta::SolidityAbiV2),
1988            (KnownMagic::AuthoringMetaV1, KnownMeta::AuthoringMetaV1),
1989            (KnownMagic::AuthoringMetaV2, KnownMeta::AuthoringMetaV2),
1990            (KnownMagic::AddressList, KnownMeta::AddressList),
1991            (
1992                KnownMagic::InterpreterCallerMetaV1,
1993                KnownMeta::InterpreterCallerMetaV1,
1994            ),
1995            (
1996                KnownMagic::ExpressionDeployerV2BytecodeV1,
1997                KnownMeta::ExpressionDeployerV2BytecodeV1,
1998            ),
1999            (KnownMagic::RainlangSourceV1, KnownMeta::RainlangSourceV1),
2000            (KnownMagic::DotrainSourceV1, KnownMeta::DotrainSourceV1),
2001            (
2002                KnownMagic::OrderBuilderStateV1,
2003                KnownMeta::OrderBuilderStateV1,
2004            ),
2005            (
2006                KnownMagic::RaindexSignedContextOracleV1,
2007                KnownMeta::RaindexSignedContextOracleV1,
2008            ),
2009        ];
2010        for (magic, meta) in cases {
2011            assert_eq!(KnownMeta::try_from(magic).unwrap(), meta, "{:?}", magic);
2012        }
2013        for magic in [
2014            KnownMagic::RainMetaDocumentV1,
2015            KnownMagic::WebDataV1,
2016            KnownMagic::OaSchema,
2017            KnownMagic::OaHashList,
2018            KnownMagic::OaStructure,
2019            KnownMagic::OaTokenImage,
2020            KnownMagic::OaTokenCredentialLinks,
2021        ] {
2022            assert!(
2023                matches!(KnownMeta::try_from(magic), Err(Error::UnsupportedMeta)),
2024                "{:?}",
2025                magic
2026            );
2027        }
2028    }
2029
2030    /// KnownMeta parses from and displays as the kebab-case names used by the
2031    /// CLI (validate --meta, build, schema show).
2032    #[test]
2033    fn test_known_meta_strum_parse_display() {
2034        use std::str::FromStr;
2035        assert_eq!(KnownMeta::from_str("op-v1").unwrap(), KnownMeta::OpV1);
2036        assert_eq!(
2037            KnownMeta::from_str("solidity-abi-v2").unwrap(),
2038            KnownMeta::SolidityAbiV2
2039        );
2040        assert_eq!(
2041            KnownMeta::from_str("interpreter-caller-meta-v1").unwrap(),
2042            KnownMeta::InterpreterCallerMetaV1
2043        );
2044        assert_eq!(KnownMeta::SolidityAbiV2.to_string(), "solidity-abi-v2");
2045        assert_eq!(KnownMeta::OpV1.to_string(), "op-v1");
2046    }
2047
2048    fn sample_deployer(meta_hash: &[u8], meta_bytes: &[u8]) -> NPE2Deployer {
2049        NPE2Deployer {
2050            meta_hash: meta_hash.to_vec(),
2051            meta_bytes: meta_bytes.to_vec(),
2052            bytecode: vec![0xb1],
2053            parser: vec![0xb2],
2054            store: vec![0xb3],
2055            interpreter: vec![0xb4],
2056            authoring_meta: None,
2057        }
2058    }
2059
2060    fn deployer_json_body(
2061        meta_hash_hex: &str,
2062        meta_bytes_hex: &str,
2063        tx_hex: &str,
2064        bytecode_meta_id_hex: &str,
2065    ) -> serde_json::Value {
2066        json!({
2067            "data": {
2068                "expressionDeployers": [{
2069                    "constructorMetaHash": meta_hash_hex,
2070                    "constructorMeta": meta_bytes_hex,
2071                    "deployTransaction": {"id": tx_hex},
2072                    "bytecode": "0x01",
2073                    "parser": {"parser": {"deployedBytecode": "0x02"}},
2074                    "store": {"store": {"deployedBytecode": "0x03"}},
2075                    "interpreter": {"interpreter": {"deployedBytecode": "0x04"}},
2076                    "meta": [{"__typename": "RainMetaV1", "id": bytecode_meta_id_hex}]
2077                }]
2078            }
2079        })
2080    }
2081
2082    /// search() lowercases the hash before building the query variables.
2083    #[tokio::test]
2084    async fn test_search_lowercases_hash() {
2085        use httpmock::prelude::*;
2086        let (_, doc) = sample_authoring_doc();
2087        let hash_upper = format!("0x{}", "AB".repeat(32));
2088        let server = MockServer::start();
2089        let mock = server.mock(|when, then| {
2090            when.method(POST)
2091                .body_contains(hash_upper.to_ascii_lowercase());
2092            then.status(200).json_body(json!({
2093                "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2094            }));
2095        });
2096        let response = search(&hash_upper, &vec![server.url("/sg")]).await.unwrap();
2097        assert_eq!(response.bytes, doc);
2098        mock.assert();
2099    }
2100
2101    /// search() queries every subgraph and the first success wins even when
2102    /// an earlier subgraph fails.
2103    #[tokio::test]
2104    async fn test_search_first_success_wins() {
2105        use httpmock::prelude::*;
2106        let (_, doc) = sample_authoring_doc();
2107        let bad = MockServer::start();
2108        let _bad_mock = bad.mock(|when, then| {
2109            when.method(POST);
2110            then.status(500).body("subgraph down");
2111        });
2112        let good = MockServer::start();
2113        let _good_mock = good.mock(|when, then| {
2114            when.method(POST);
2115            then.status(200).json_body(json!({
2116                "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2117            }));
2118        });
2119        let response = search(
2120            &format!("0x{}", "11".repeat(32)),
2121            &vec![bad.url("/sg"), good.url("/sg")],
2122        )
2123        .await
2124        .unwrap();
2125        assert_eq!(response.bytes, doc);
2126    }
2127
2128    /// search_deployer() lowercases the hash before building the query
2129    /// variables.
2130    #[tokio::test]
2131    async fn test_search_deployer_lowercases_hash() {
2132        use httpmock::prelude::*;
2133        let (_, doc) = sample_authoring_doc();
2134        let meta_hash_hex = hex::encode_prefixed(keccak256(&doc).0);
2135        let hash_upper = format!("0x{}", "CD".repeat(32));
2136        let server = MockServer::start();
2137        let mock = server.mock(|when, then| {
2138            when.method(POST)
2139                .body_contains(hash_upper.to_ascii_lowercase());
2140            then.status(200).json_body(deployer_json_body(
2141                &meta_hash_hex,
2142                &hex::encode_prefixed(&doc),
2143                &format!("0x{}", "77".repeat(32)),
2144                &meta_hash_hex,
2145            ));
2146        });
2147        let response = search_deployer(&hash_upper, &vec![server.url("/sg")])
2148            .await
2149            .unwrap();
2150        assert_eq!(response.meta_bytes, doc);
2151        assert_eq!(response.bytecode, vec![0x01]);
2152        mock.assert();
2153    }
2154
2155    /// search_deployer() queries every subgraph and the first success wins
2156    /// even when an earlier subgraph fails.
2157    #[tokio::test]
2158    async fn test_search_deployer_first_success_wins() {
2159        use httpmock::prelude::*;
2160        let (_, doc) = sample_authoring_doc();
2161        let meta_hash_hex = hex::encode_prefixed(keccak256(&doc).0);
2162        let bad = MockServer::start();
2163        let _bad_mock = bad.mock(|when, then| {
2164            when.method(POST);
2165            then.status(500).body("subgraph down");
2166        });
2167        let good = MockServer::start();
2168        let _good_mock = good.mock(|when, then| {
2169            when.method(POST);
2170            then.status(200).json_body(deployer_json_body(
2171                &meta_hash_hex,
2172                &hex::encode_prefixed(&doc),
2173                &format!("0x{}", "77".repeat(32)),
2174                &meta_hash_hex,
2175            ));
2176        });
2177        let response = search_deployer(
2178            &format!("0x{}", "22".repeat(32)),
2179            &vec![bad.url("/sg"), good.url("/sg")],
2180        )
2181        .await
2182        .unwrap();
2183        assert_eq!(response.meta_bytes, doc);
2184    }
2185
2186    /// An empty subgraph list has nothing to fan out to, so both searches
2187    /// report a miss rather than reaching futures::select_ok, which panics on
2188    /// an empty iterator.
2189    #[tokio::test]
2190    async fn test_search_empty_subgraphs_is_a_miss() {
2191        let hash = format!("0x{}", "33".repeat(32));
2192        assert!(matches!(
2193            search(&hash, &vec![]).await,
2194            Err(Error::NoRecordFound)
2195        ));
2196        assert!(matches!(
2197            search_deployer(&hash, &vec![]).await,
2198            Err(Error::NoRecordFound)
2199        ));
2200    }
2201
2202    /// When the erc165 probe answers false or errors, the result is false
2203    /// WITHOUT making the IDescribedByMetaV1 supportsInterface call: a queued
2204    /// "true" response must never be consumed.
2205    #[tokio::test]
2206    async fn test_implements_erc165_gate_short_circuits() {
2207        let address = Address::random();
2208
2209        // erc165 check1 answers false
2210        let asserter = Asserter::new();
2211        let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2212        asserter
2213            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2214        asserter
2215            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2216        assert!(!implements_i_described_by_meta_v1(&provider, address).await);
2217
2218        // erc165 probe errors
2219        let asserter = Asserter::new();
2220        let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2221        asserter.push_failure(ErrorPayload {
2222            code: -32000,
2223            message: "connection reset".into(),
2224            data: None,
2225        });
2226        asserter
2227            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2228        assert!(!implements_i_described_by_meta_v1(&provider, address).await);
2229    }
2230
2231    /// An eth_call response that does not decode as bool must read as "does
2232    /// not implement", not silently as true.
2233    #[tokio::test]
2234    async fn test_implements_undecodable_response_is_false() {
2235        let address = Address::random();
2236        let asserter = Asserter::new();
2237        let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2238        asserter
2239            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2240        asserter
2241            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2242        asserter.push_success(&"0x");
2243        assert!(!implements_i_described_by_meta_v1(&provider, address).await);
2244    }
2245
2246    /// Each of the six required fields independently marks the record
2247    /// corrupt when empty; a fully populated record is not corrupt.
2248    #[test]
2249    fn test_npe2_deployer_is_corrupt_per_field() {
2250        let full = NPE2Deployer {
2251            meta_hash: vec![1],
2252            meta_bytes: vec![2],
2253            bytecode: vec![3],
2254            parser: vec![4],
2255            store: vec![5],
2256            interpreter: vec![6],
2257            authoring_meta: None,
2258        };
2259        assert!(!full.is_corrupt());
2260        for field in 0..6usize {
2261            let mut record = full.clone();
2262            match field {
2263                0 => record.meta_hash = vec![],
2264                1 => record.meta_bytes = vec![],
2265                2 => record.bytecode = vec![],
2266                3 => record.parser = vec![],
2267                4 => record.store = vec![],
2268                5 => record.interpreter = vec![],
2269                _ => unreachable!(),
2270            }
2271            assert!(record.is_corrupt(), "empty field {} must corrupt", field);
2272        }
2273    }
2274
2275    /// No constructor injects a subgraph the caller did not ask for, and a
2276    /// store with none resolves every network lookup to None rather than
2277    /// reaching the select_ok panic.
2278    #[tokio::test]
2279    async fn test_store_constructors_inject_no_subgraphs() {
2280        assert!(Store::new().subgraphs().is_empty());
2281        assert!(Store::default().subgraphs().is_empty());
2282        assert!(
2283            Store::create(&vec![], &HashMap::new(), &HashMap::new(), &HashMap::new())
2284                .subgraphs()
2285                .is_empty()
2286        );
2287
2288        let hash = [0u8; 32];
2289        let mut store = Store::default();
2290        assert!(store.update(&hash).await.is_none());
2291        assert!(store.search_deployer(&hash).await.is_none());
2292    }
2293
2294    /// create() takes only the given subgraphs, validates cache entries via
2295    /// the keccak gate, and keeps a dotrain uri only when its hash is present
2296    /// in the cache.
2297    #[test]
2298    fn test_store_create_validates_entries() {
2299        let (_, doc) = sample_authoring_doc();
2300        let good_hash = keccak256(&doc).0.to_vec();
2301        let bad_hash = vec![0xEEu8; 32];
2302        let mut cache = HashMap::new();
2303        cache.insert(good_hash.clone(), doc.clone());
2304        cache.insert(bad_hash.clone(), b"does not hash to bad_hash".to_vec());
2305        let mut deployer_cache = HashMap::new();
2306        let deployer = sample_deployer(&[0xAA; 32], b"dep-meta");
2307        let deployer_key = vec![0x33u8; 32];
2308        deployer_cache.insert(deployer_key.clone(), deployer.clone());
2309        let mut dotrain_cache = HashMap::new();
2310        dotrain_cache.insert("a.rain".to_string(), good_hash.clone());
2311        dotrain_cache.insert("missing.rain".to_string(), vec![0x44u8; 32]);
2312
2313        let store = Store::create(
2314            &vec!["https://example.com/custom-sg".to_string()],
2315            &cache,
2316            &deployer_cache,
2317            &dotrain_cache,
2318        );
2319
2320        assert_eq!(
2321            store.subgraphs(),
2322            &vec!["https://example.com/custom-sg".to_string()]
2323        );
2324        assert_eq!(store.get_meta(&good_hash), Some(&doc));
2325        assert_eq!(store.get_meta(&bad_hash), None);
2326        assert_eq!(store.get_deployer(&deployer_key), Some(&deployer));
2327        assert_eq!(store.get_dotrain_hash("a.rain"), Some(&good_hash));
2328        assert_eq!(store.get_dotrain_hash("missing.rain"), None);
2329    }
2330
2331    /// add_subgraphs skips urls already present.
2332    #[test]
2333    fn test_store_add_subgraphs_dedupe() {
2334        let mut store = Store::new();
2335        store.add_subgraphs(&vec!["sg-a".to_string()]);
2336        store.add_subgraphs(&vec!["sg-a".to_string(), "sg-b".to_string()]);
2337        assert_eq!(
2338            store.subgraphs(),
2339            &vec!["sg-a".to_string(), "sg-b".to_string()]
2340        );
2341    }
2342
2343    /// get_deployer resolves a direct cache hit, then the tx-hash
2344    /// indirection, then None; set_deployer populates all three maps.
2345    #[test]
2346    fn test_store_get_deployer_lookup_chain() {
2347        let mut store = Store::new();
2348        let deployer = sample_deployer(&[0xAB; 32], b"dep-meta-bytes");
2349        let key = vec![0x01u8; 32];
2350        let tx = vec![0x02u8; 32];
2351        store.set_deployer(&key, &deployer, Some(&tx));
2352        assert_eq!(store.get_deployer(&key), Some(&deployer));
2353        assert_eq!(store.get_deployer(&tx), Some(&deployer));
2354        assert_eq!(store.get_deployer(&[0x03u8; 32]), None);
2355        assert_eq!(
2356            store.get_meta(&deployer.meta_hash),
2357            Some(&deployer.meta_bytes)
2358        );
2359    }
2360
2361    /// A successful subgraph search populates the meta cache, the deployer
2362    /// cache keyed by the bytecode meta hash, and the tx-hash map, and
2363    /// returns the record for the searched hash.
2364    #[tokio::test]
2365    async fn test_store_search_deployer_populates_caches() {
2366        use httpmock::prelude::*;
2367        let (authoring_meta, doc) = sample_authoring_doc();
2368        let meta_hash = keccak256(&doc).0.to_vec();
2369        let meta_hash_hex = hex::encode_prefixed(&meta_hash);
2370        let tx = vec![0x77u8; 32];
2371        let server = MockServer::start();
2372        let _mock = server.mock(|when, then| {
2373            when.method(POST);
2374            then.status(200).json_body(deployer_json_body(
2375                &meta_hash_hex,
2376                &hex::encode_prefixed(&doc),
2377                &hex::encode_prefixed(&tx),
2378                &meta_hash_hex,
2379            ));
2380        });
2381        let mut store = Store::new();
2382        store.add_subgraphs(&vec![server.url("/sg")]);
2383
2384        let record = store.search_deployer(&meta_hash).await.cloned().unwrap();
2385        assert_eq!(record.meta_hash, meta_hash);
2386        assert_eq!(record.meta_bytes, doc);
2387        assert_eq!(record.bytecode, vec![0x01]);
2388        assert_eq!(record.parser, vec![0x02]);
2389        assert_eq!(record.store, vec![0x03]);
2390        assert_eq!(record.interpreter, vec![0x04]);
2391        assert_eq!(record.authoring_meta, Some(authoring_meta));
2392        assert_eq!(store.get_meta(&meta_hash), Some(&doc));
2393        assert_eq!(store.get_deployer(&tx), Some(&record));
2394    }
2395
2396    /// A failed subgraph search returns None and stores nothing.
2397    #[tokio::test]
2398    async fn test_store_search_deployer_error_returns_none() {
2399        use httpmock::prelude::*;
2400        let server = MockServer::start();
2401        let _mock = server.mock(|when, then| {
2402            when.method(POST);
2403            then.status(500).body("subgraph down");
2404        });
2405        let mut store = Store::new();
2406        store.add_subgraphs(&vec![server.url("/sg")]);
2407        assert!(store.search_deployer(&[0x0Du8; 32]).await.is_none());
2408        assert!(store.cache().is_empty());
2409        assert!(store.deployer_cache().is_empty());
2410    }
2411
2412    /// search_deployer_check returns from the deployer cache or the tx-hash
2413    /// map without any network round trip, and only falls back to the
2414    /// subgraphs when neither hits.
2415    #[tokio::test]
2416    async fn test_store_search_deployer_check_branches() {
2417        use httpmock::prelude::*;
2418        // cached branches: no subgraphs registered at all
2419        let mut store = Store::new();
2420        let deployer = sample_deployer(&[0xAC; 32], b"cached-meta");
2421        let key = vec![0x11u8; 32];
2422        let tx = vec![0x22u8; 32];
2423        store.set_deployer(&key, &deployer, Some(&tx));
2424        assert_eq!(store.search_deployer_check(&key).await, Some(&deployer));
2425        assert_eq!(store.search_deployer_check(&tx).await, Some(&deployer));
2426
2427        // network fallback
2428        let (_, doc) = sample_authoring_doc();
2429        let meta_hash = keccak256(&doc).0.to_vec();
2430        let meta_hash_hex = hex::encode_prefixed(&meta_hash);
2431        let server = MockServer::start();
2432        let _mock = server.mock(|when, then| {
2433            when.method(POST);
2434            then.status(200).json_body(deployer_json_body(
2435                &meta_hash_hex,
2436                &hex::encode_prefixed(&doc),
2437                &format!("0x{}", "66".repeat(32)),
2438                &meta_hash_hex,
2439            ));
2440        });
2441        let mut fresh = Store::new();
2442        fresh.add_subgraphs(&vec![server.url("/sg")]);
2443        let found = fresh
2444            .search_deployer_check(&meta_hash)
2445            .await
2446            .cloned()
2447            .unwrap();
2448        assert_eq!(found.meta_bytes, doc);
2449    }
2450
2451    /// set_deployer_from_query_response fills the meta cache, the tx-hash
2452    /// map and the deployer cache, and returns the assembled record.
2453    #[test]
2454    fn test_store_set_deployer_from_query_response() {
2455        let (authoring_meta, doc) = sample_authoring_doc();
2456        let meta_hash = vec![0x0Au8; 32];
2457        let bytecode_meta_hash = vec![0x0Bu8; 32];
2458        let tx = vec![0x0Cu8; 32];
2459        let response = DeployerResponse {
2460            tx_hash: tx.clone(),
2461            bytecode_meta_hash: bytecode_meta_hash.clone(),
2462            meta_hash: meta_hash.clone(),
2463            meta_bytes: doc.clone(),
2464            bytecode: vec![0xE1],
2465            parser: vec![0xE2],
2466            store: vec![0xE3],
2467            interpreter: vec![0xE4],
2468        };
2469        let mut store = Store::new();
2470        let record = store.set_deployer_from_query_response(response);
2471        assert_eq!(record.meta_hash, meta_hash);
2472        assert_eq!(record.meta_bytes, doc);
2473        assert_eq!(record.bytecode, vec![0xE1]);
2474        assert_eq!(record.authoring_meta, Some(authoring_meta));
2475        assert_eq!(store.get_meta(&meta_hash), Some(&doc));
2476        assert_eq!(store.get_deployer(&bytecode_meta_hash), Some(&record));
2477        assert_eq!(store.get_deployer(&tx), Some(&record));
2478    }
2479
2480    /// set_dotrain on a fresh uri returns (new_hash, empty), keyed by the
2481    /// keccak of the cbor encoded DotrainV1 meta item, and every dotrain
2482    /// getter resolves it.
2483    #[test]
2484    fn test_store_dotrain_getters_and_set_fresh() {
2485        let mut store = Store::new();
2486        let text = "some dotrain content";
2487        let (hash, old) = store.set_dotrain(text, "file.rain", false).unwrap();
2488        assert!(old.is_empty());
2489        let expected_item = RainMetaDocumentV1Item {
2490            payload: serde_bytes::ByteBuf::from(text.as_bytes()),
2491            magic: KnownMagic::DotrainV1,
2492            content_type: ContentType::OctetStream,
2493            content_encoding: ContentEncoding::None,
2494            content_language: ContentLanguage::None,
2495            schema: None,
2496        };
2497        let expected_bytes = expected_item.cbor_encode().unwrap();
2498        assert_eq!(hash, keccak256(&expected_bytes).0.to_vec());
2499        assert_eq!(store.get_dotrain_hash("file.rain"), Some(&hash));
2500        assert_eq!(store.get_dotrain_uri(&hash), Some(&"file.rain".to_string()));
2501        assert_eq!(store.get_dotrain_meta("file.rain"), Some(&expected_bytes));
2502        assert_eq!(store.get_dotrain_hash("other.rain"), None);
2503        assert_eq!(store.get_dotrain_uri(&[0u8; 32]), None);
2504        assert_eq!(store.get_dotrain_meta("other.rain"), None);
2505    }
2506
2507    /// set_dotrain branches: same content keeps the meta and reports no old
2508    /// hash; different content remaps the uri and drops or keeps the old
2509    /// meta per keep_old.
2510    #[test]
2511    fn test_store_set_dotrain_branches() {
2512        let mut store = Store::new();
2513        let (hash_one, _) = store.set_dotrain("text one", "a.rain", false).unwrap();
2514
2515        // same content again: same hash, no old hash, meta retained
2516        let (hash_same, old_same) = store.set_dotrain("text one", "a.rain", false).unwrap();
2517        assert_eq!(hash_same, hash_one);
2518        assert!(old_same.is_empty());
2519        assert!(store.get_meta(&hash_one).is_some());
2520
2521        // different content, keep_old = false: remap and drop the old meta
2522        let (hash_two, old_two) = store.set_dotrain("text two", "a.rain", false).unwrap();
2523        assert_ne!(hash_two, hash_one);
2524        assert_eq!(old_two, hash_one);
2525        assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_two));
2526        assert!(store.get_meta(&hash_one).is_none());
2527        assert!(store.get_meta(&hash_two).is_some());
2528
2529        // different content, keep_old = true: old meta kept
2530        let (hash_three, old_three) = store.set_dotrain("text three", "a.rain", true).unwrap();
2531        assert_eq!(old_three, hash_two);
2532        assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_three));
2533        assert!(store.get_meta(&hash_two).is_some());
2534        assert!(store.get_meta(&hash_three).is_some());
2535    }
2536
2537    /// delete_dotrain removes the uri mapping and honors keep_meta for the
2538    /// cached meta bytes.
2539    #[test]
2540    fn test_store_delete_dotrain_keep_meta() {
2541        let mut store = Store::new();
2542        let (hash, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2543        store.delete_dotrain("d.rain", false);
2544        assert_eq!(store.get_dotrain_hash("d.rain"), None);
2545        assert!(store.get_meta(&hash).is_none());
2546
2547        let (hash_again, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2548        store.delete_dotrain("d.rain", true);
2549        assert_eq!(store.get_dotrain_hash("d.rain"), None);
2550        assert!(store.get_meta(&hash_again).is_some());
2551    }
2552
2553    /// merge keeps this store's entry in every map on a key collision, takes
2554    /// the keys it does not already hold, and unions the subgraphs.
2555    #[test]
2556    fn test_store_merge_semantics() {
2557        let shared_meta_hash = vec![0x5Au8; 32];
2558        let deployer_ours = sample_deployer(&shared_meta_hash, b"ours");
2559        let deployer_theirs = sample_deployer(&shared_meta_hash, b"theirs");
2560        let shared_tx = vec![0x0Fu8; 32];
2561        let their_tx = vec![0x1Eu8; 32];
2562
2563        let mut ours = Store::new();
2564        let mut theirs = Store::new();
2565        ours.set_deployer(&[0x01u8; 32], &deployer_ours, Some(&shared_tx));
2566        theirs.set_deployer(&[0x02u8; 32], &deployer_theirs, Some(&shared_tx));
2567        theirs.set_deployer(&[0x02u8; 32], &deployer_theirs, Some(&their_tx));
2568
2569        // same deployer cache key in both stores
2570        let contested_key = vec![0x03u8; 32];
2571        let deployer_a = sample_deployer(&[0x04; 32], b"deployer-a");
2572        let deployer_b = sample_deployer(&[0x05; 32], b"deployer-b");
2573        ours.set_deployer(&contested_key, &deployer_a, None);
2574        theirs.set_deployer(&contested_key, &deployer_b, None);
2575
2576        // same dotrain uri, different content
2577        let (hash_ours, _) = ours.set_dotrain("content a", "x.rain", false).unwrap();
2578        let (_hash_theirs, _) = theirs.set_dotrain("content b", "x.rain", false).unwrap();
2579
2580        theirs.add_subgraphs(&vec!["sg-their".to_string()]);
2581
2582        ours.merge(&theirs);
2583
2584        // meta cache: existing entry wins
2585        assert_eq!(ours.get_meta(&shared_meta_hash), Some(&b"ours".to_vec()));
2586        // deployer cache: existing entry wins
2587        assert_eq!(ours.get_deployer(&contested_key), Some(&deployer_a));
2588        // tx-hash map: existing mapping wins
2589        assert_eq!(ours.get_deployer(&shared_tx), Some(&deployer_ours));
2590        // tx-hash map: a mapping only the other store holds is taken
2591        assert_eq!(ours.get_deployer(&their_tx), Some(&deployer_theirs));
2592        // dotrain: existing uri mapping wins
2593        assert_eq!(ours.get_dotrain_hash("x.rain"), Some(&hash_ours));
2594        // subgraphs merged
2595        assert!(ours.subgraphs().contains(&"sg-their".to_string()));
2596    }
2597
2598    /// update() stores the fetched bytes under the requested hash and each
2599    /// inner meta item under the keccak of its own encoding; update_check
2600    /// serves a cached hash without any network access.
2601    #[tokio::test]
2602    async fn test_store_update_and_update_check() {
2603        use httpmock::prelude::*;
2604        let authoring_meta: AuthoringMeta = serde_json::from_str(
2605            r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
2606        )
2607        .unwrap();
2608        let item_one = RainMetaDocumentV1Item {
2609            payload: serde_bytes::ByteBuf::from(authoring_meta.abi_encode_validate().unwrap()),
2610            magic: KnownMagic::AuthoringMetaV1,
2611            content_type: ContentType::Cbor,
2612            content_encoding: ContentEncoding::None,
2613            content_language: ContentLanguage::None,
2614            schema: None,
2615        };
2616        let item_two = sample_dotrain_item();
2617        let doc = RainMetaDocumentV1Item::cbor_encode_seq(
2618            &vec![item_one.clone(), item_two.clone()],
2619            KnownMagic::RainMetaDocumentV1,
2620        )
2621        .unwrap();
2622        let requested = keccak256(&doc).0.to_vec();
2623        let server = MockServer::start();
2624        let _mock = server.mock(|when, then| {
2625            when.method(POST);
2626            then.status(200).json_body(json!({
2627                "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2628            }));
2629        });
2630        let mut store = Store::new();
2631        store.add_subgraphs(&vec![server.url("/sg")]);
2632        let fetched = store.update(&requested).await.cloned().unwrap();
2633        assert_eq!(fetched, doc);
2634        assert_eq!(store.get_meta(&requested), Some(&doc));
2635        let inner_one = item_one.cbor_encode().unwrap();
2636        let inner_two = item_two.cbor_encode().unwrap();
2637        assert_eq!(
2638            store.get_meta(keccak256(&inner_one).0.as_ref()),
2639            Some(&inner_one)
2640        );
2641        assert_eq!(
2642            store.get_meta(keccak256(&inner_two).0.as_ref()),
2643            Some(&inner_two)
2644        );
2645
2646        // update_check: cached hash short-circuits, no subgraphs needed
2647        let mut cached_store = Store::new();
2648        let bytes = b"standalone meta bytes".to_vec();
2649        let hash = keccak256(&bytes).0.to_vec();
2650        assert!(cached_store.update_with(&hash, &bytes).is_some());
2651        assert_eq!(cached_store.update_check(&hash).await, Some(&bytes));
2652    }
2653
2654    /// Store::new() starts with no subgraphs, so every uncached lookup that
2655    /// reaches the network on it resolves to None instead of panicking.
2656    #[tokio::test]
2657    async fn test_store_no_subgraphs_lookups_return_none() {
2658        let hash = [0u8; 32];
2659        let mut store = Store::new();
2660        assert!(store.update(&hash).await.is_none());
2661        assert!(store.update_check(&hash).await.is_none());
2662        assert!(store.search_deployer(&hash).await.is_none());
2663        assert!(store.search_deployer_check(&hash).await.is_none());
2664        assert!(store.cache().is_empty());
2665        assert!(store.deployer_cache().is_empty());
2666    }
2667
2668    /// update_with enforces keccak(bytes) == hash, leaves an existing entry
2669    /// untouched, and unpacks inner items only for RainMetaDocumentV1
2670    /// prefixed bytes.
2671    #[test]
2672    fn test_store_update_with_validation_and_content() {
2673        // hash mismatch rejected
2674        let mut store = Store::new();
2675        let bytes = b"payload bytes".to_vec();
2676        let wrong_hash = vec![0x99u8; 32];
2677        assert!(store.update_with(&wrong_hash, &bytes).is_none());
2678        assert!(store.get_meta(&wrong_hash).is_none());
2679        // valid pair stored
2680        let hash = keccak256(&bytes).0.to_vec();
2681        assert_eq!(store.update_with(&hash, &bytes), Some(&bytes));
2682
2683        // existing entry is returned untouched, not overwritten
2684        let mut seeded = Store::new();
2685        let content = b"real content".to_vec();
2686        let content_hash = keccak256(&content).0.to_vec();
2687        let planted = sample_deployer(&content_hash, b"planted value");
2688        seeded.set_deployer(&[0x77u8; 32], &planted, None);
2689        assert_eq!(
2690            seeded.update_with(&content_hash, &content),
2691            Some(&b"planted value".to_vec())
2692        );
2693        assert_eq!(
2694            seeded.get_meta(&content_hash),
2695            Some(&b"planted value".to_vec())
2696        );
2697
2698        // prefixed document: inner item stored under keccak of its encoding
2699        let (_, doc) = sample_authoring_doc();
2700        let doc_hash = keccak256(&doc).0.to_vec();
2701        let mut doc_store = Store::new();
2702        assert!(doc_store.update_with(&doc_hash, &doc).is_some());
2703        let inner = doc[8..].to_vec();
2704        assert_eq!(store_inner_lookup(&doc_store, &inner), Some(inner.clone()));
2705
2706        // bare cbor sequence without the document prefix: no inner extraction
2707        let item_a = sample_dotrain_item().cbor_encode().unwrap();
2708        let (_, doc_b) = sample_authoring_doc();
2709        let item_b = doc_b[8..].to_vec();
2710        let seq = [item_a.clone(), item_b].concat();
2711        let seq_hash = keccak256(&seq).0.to_vec();
2712        let mut seq_store = Store::new();
2713        assert!(seq_store.update_with(&seq_hash, &seq).is_some());
2714        assert_eq!(store_inner_lookup(&seq_store, &item_a), None);
2715    }
2716
2717    fn store_inner_lookup(store: &Store, inner_encoded: &[u8]) -> Option<Vec<u8>> {
2718        store.get_meta(keccak256(inner_encoded).0.as_ref()).cloned()
2719    }
2720
2721    /// bytes32_to_str propagates invalid utf8 as an error instead of
2722    /// swallowing it.
2723    #[test]
2724    fn test_bytes32_to_str_invalid_utf8() {
2725        let mut bytes = [0u8; 32];
2726        bytes[0] = 0xf0;
2727        bytes[1] = 0x28;
2728        bytes[2] = 0x8c;
2729        bytes[3] = 0x28;
2730        assert!(matches!(bytes32_to_str(&bytes), Err(Error::Utf8Error(_))));
2731        let no_nul = [0xffu8; 32];
2732        assert!(matches!(bytes32_to_str(&no_nul), Err(Error::Utf8Error(_))));
2733    }
2734}