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