Skip to main content

rain_metadata/meta/
mod.rs

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