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