Skip to main content

rain_metadata/meta/
mod.rs

1use super::error::Error;
2use super::subgraph::KnownSubgraphs;
3use alloy::primitives::{hex, keccak256};
4use futures::future;
5use graphql_client::GraphQLQuery;
6use rain_metadata_bindings::IDescribedByMetaV1;
7use reqwest::Client;
8use serde::de::{Deserialize, Deserializer, Visitor};
9use serde::ser::{Serialize, SerializeMap, Serializer};
10use std::{collections::HashMap, convert::TryFrom, fmt::Debug, sync::Arc};
11use strum::{EnumIter, EnumString};
12use types::authoring::v1::AuthoringMeta;
13use alloy::sol_types::private::Address;
14use alloy::providers::Provider;
15use alloy::rpc::types::TransactionRequest;
16use alloy::sol_types::SolCall;
17use rain_erc::erc165::{IERC165, XorSelectors, supports_erc165};
18
19pub mod magic;
20pub(crate) mod normalize;
21pub(crate) mod query;
22pub mod types;
23
24pub use magic::*;
25pub use query::*;
26
27/// All known meta identifiers
28#[derive(Copy, Clone, EnumString, EnumIter, strum::Display, Debug, PartialEq)]
29#[strum(serialize_all = "kebab-case")]
30pub enum KnownMeta {
31    OpV1,
32    DotrainV1,
33    RainlangV1,
34    SolidityAbiV2,
35    AuthoringMetaV1,
36    AuthoringMetaV2,
37    InterpreterCallerMetaV1,
38    ExpressionDeployerV2BytecodeV1,
39    RainlangSourceV1,
40    AddressList,
41    DotrainSourceV1,
42    OrderBuilderStateV1,
43    RaindexSignedContextOracleV1,
44}
45
46impl TryFrom<KnownMagic> for KnownMeta {
47    type Error = Error;
48    fn try_from(value: KnownMagic) -> Result<Self, Self::Error> {
49        match value {
50            KnownMagic::OpMetaV1 => Ok(KnownMeta::OpV1),
51            KnownMagic::DotrainV1 => Ok(KnownMeta::DotrainV1),
52            KnownMagic::RainlangV1 => Ok(KnownMeta::RainlangV1),
53            KnownMagic::SolidityAbiV2 => Ok(KnownMeta::SolidityAbiV2),
54            KnownMagic::AuthoringMetaV1 => Ok(KnownMeta::AuthoringMetaV1),
55            KnownMagic::AuthoringMetaV2 => Ok(KnownMeta::AuthoringMetaV2),
56            KnownMagic::AddressList => Ok(KnownMeta::AddressList),
57            KnownMagic::InterpreterCallerMetaV1 => Ok(KnownMeta::InterpreterCallerMetaV1),
58            KnownMagic::DotrainSourceV1 => Ok(KnownMeta::DotrainSourceV1),
59            KnownMagic::OrderBuilderStateV1 => Ok(KnownMeta::OrderBuilderStateV1),
60            KnownMagic::ExpressionDeployerV2BytecodeV1 => {
61                Ok(KnownMeta::ExpressionDeployerV2BytecodeV1)
62            }
63            KnownMagic::RainlangSourceV1 => Ok(KnownMeta::RainlangSourceV1),
64            KnownMagic::RaindexSignedContextOracleV1 => Ok(KnownMeta::RaindexSignedContextOracleV1),
65            _ => Err(Error::UnsupportedMeta),
66        }
67    }
68}
69
70/// Content type of a cbor meta map
71#[derive(
72    Copy,
73    Clone,
74    Debug,
75    EnumIter,
76    PartialEq,
77    EnumString,
78    strum::Display,
79    serde::Serialize,
80    serde::Deserialize,
81)]
82#[strum(serialize_all = "kebab-case")]
83pub enum ContentType {
84    None,
85    #[serde(rename = "application/json")]
86    Json,
87    #[serde(rename = "application/cbor")]
88    Cbor,
89    #[serde(rename = "application/octet-stream")]
90    OctetStream,
91}
92
93/// Content encoding of a cbor meta map
94#[derive(
95    Copy,
96    Clone,
97    Debug,
98    EnumIter,
99    PartialEq,
100    EnumString,
101    strum::Display,
102    serde::Serialize,
103    serde::Deserialize,
104)]
105#[serde(rename_all = "kebab-case")]
106#[strum(serialize_all = "kebab-case")]
107pub enum ContentEncoding {
108    None,
109    Identity,
110    Deflate,
111}
112
113impl ContentEncoding {
114    /// encode the data based on the variant
115    pub fn encode(&self, data: &[u8]) -> Vec<u8> {
116        match self {
117            ContentEncoding::None | ContentEncoding::Identity => data.to_vec(),
118            ContentEncoding::Deflate => deflate::deflate_bytes_zlib(data),
119        }
120    }
121
122    /// decode the data based on the variant
123    pub fn decode(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
124        Ok(match self {
125            ContentEncoding::None | ContentEncoding::Identity => data.to_vec(),
126            ContentEncoding::Deflate => match inflate::inflate_bytes_zlib(data) {
127                Ok(v) => v,
128                Err(error) => match inflate::inflate_bytes(data) {
129                    Ok(v) => v,
130                    Err(_) => Err(Error::InflateError(error))?,
131                },
132            },
133        })
134    }
135}
136
137/// Content language of a cbor meta map
138#[derive(
139    Copy,
140    Clone,
141    Debug,
142    EnumIter,
143    PartialEq,
144    EnumString,
145    strum::Display,
146    serde::Serialize,
147    serde::Deserialize,
148)]
149#[serde(rename_all = "kebab-case")]
150#[strum(serialize_all = "kebab-case")]
151pub enum ContentLanguage {
152    None,
153    En,
154}
155
156/// # Rain Meta Document v1 Item (meta map)
157///
158/// represents a rain meta data and configuration that can be cbor encoded or unpacked back to the meta types
159#[derive(PartialEq, Debug, Clone)]
160pub struct RainMetaDocumentV1Item {
161    pub payload: serde_bytes::ByteBuf,
162    pub magic: KnownMagic,
163    pub content_type: ContentType,
164    pub content_encoding: ContentEncoding,
165    pub content_language: ContentLanguage,
166    /// optional reference to the schema of the payload, encoded under the
167    /// [KnownMagic::OaSchema] magic number as an additional cbor map key
168    /// beyond the standard 0-4 keys
169    pub schema: Option<String>,
170}
171
172// this implementation is mainly used by Rainlang and Dotrain metas as they are aliased type for String
173impl TryFrom<RainMetaDocumentV1Item> for String {
174    type Error = Error;
175    fn try_from(value: RainMetaDocumentV1Item) -> Result<Self, Self::Error> {
176        Ok(String::from_utf8(value.unpack()?)?)
177    }
178}
179
180// this implementation is mainly used by ExpressionDeployerV2Bytecode meta as it is aliased type for Vec<u8>
181impl TryFrom<RainMetaDocumentV1Item> for Vec<u8> {
182    type Error = Error;
183    fn try_from(value: RainMetaDocumentV1Item) -> Result<Self, Self::Error> {
184        value.unpack()
185    }
186}
187
188impl RainMetaDocumentV1Item {
189    fn len(&self) -> usize {
190        let mut l = 2;
191        if !matches!(self.content_type, ContentType::None) {
192            l += 1;
193        }
194        if !matches!(self.content_encoding, ContentEncoding::None) {
195            l += 1;
196        }
197        if !matches!(self.content_language, ContentLanguage::None) {
198            l += 1;
199        }
200        if self.schema.is_some() {
201            l += 1;
202        }
203        l
204    }
205
206    /// method to hash(keccak256) the cbor encoded bytes of this instance
207    pub fn hash(&self, as_rain_meta_document: bool) -> Result<[u8; 32], Error> {
208        if as_rain_meta_document {
209            Ok(keccak256(Self::cbor_encode_seq(
210                &vec![self.clone()],
211                KnownMagic::RainMetaDocumentV1,
212            )?)
213            .0)
214        } else {
215            Ok(keccak256(self.cbor_encode()?).0)
216        }
217    }
218
219    /// method to cbor encode
220    pub fn cbor_encode(&self) -> Result<Vec<u8>, Error> {
221        let mut bytes: Vec<u8> = vec![];
222        Ok(serde_cbor::to_writer(&mut bytes, &self).map(|_| bytes)?)
223    }
224
225    /// builds a cbor sequence from given MetaMaps
226    pub fn cbor_encode_seq(
227        seq: &Vec<RainMetaDocumentV1Item>,
228        magic: KnownMagic,
229    ) -> Result<Vec<u8>, Error> {
230        let mut bytes: Vec<u8> = magic.to_prefix_bytes().to_vec();
231        for item in seq {
232            serde_cbor::to_writer(&mut bytes, &item)?;
233        }
234        Ok(bytes)
235    }
236
237    /// method to cbor decode from given bytes
238    pub fn cbor_decode(data: &[u8]) -> Result<Vec<RainMetaDocumentV1Item>, Error> {
239        let mut track: Vec<usize> = vec![];
240        let mut metas: Vec<RainMetaDocumentV1Item> = vec![];
241        let mut is_rain_document_meta = false;
242        let mut len = data.len();
243        if data.starts_with(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes()) {
244            is_rain_document_meta = true;
245            len -= 8;
246        }
247        let mut deserializer = match is_rain_document_meta {
248            true => serde_cbor::Deserializer::from_slice(&data[8..]),
249            false => serde_cbor::Deserializer::from_slice(data),
250        };
251        while match serde_cbor::Value::deserialize(&mut deserializer) {
252            Ok(cbor_map) => {
253                track.push(deserializer.byte_offset());
254                match serde_cbor::value::from_value(cbor_map) {
255                    Ok(meta) => metas.push(meta),
256                    Err(error) => Err(Error::SerdeCborError(error))?,
257                };
258                true
259            }
260            Err(error) => {
261                if error.is_eof() {
262                    if error.offset() == len as u64 {
263                        false
264                    } else {
265                        Err(Error::SerdeCborError(error))?
266                    }
267                } else {
268                    Err(Error::SerdeCborError(error))?
269                }
270            }
271        } {}
272
273        if metas.is_empty()
274            || track.is_empty()
275            || track.len() != metas.len()
276            || len != track[track.len() - 1]
277        {
278            Err(Error::CorruptMeta)?
279        }
280        Ok(metas)
281    }
282
283    // unpack the payload based on the configuration
284    pub fn unpack(&self) -> Result<Vec<u8>, Error> {
285        ContentEncoding::decode(&self.content_encoding, self.payload.as_ref())
286    }
287
288    // unpacks the payload to given meta type based on configuration
289    pub fn unpack_into<T: TryFrom<Self, Error = Error>>(self) -> Result<T, Error> {
290        match self.magic {
291            KnownMagic::OpMetaV1
292            | KnownMagic::DotrainV1
293            | KnownMagic::RainlangV1
294            | KnownMagic::SolidityAbiV2
295            | KnownMagic::AuthoringMetaV1
296            | KnownMagic::AuthoringMetaV2
297            | KnownMagic::AddressList
298            | KnownMagic::InterpreterCallerMetaV1
299            | KnownMagic::ExpressionDeployerV2BytecodeV1
300            | KnownMagic::DotrainSourceV1
301            | KnownMagic::OrderBuilderStateV1
302            | KnownMagic::RainlangSourceV1
303            | KnownMagic::RaindexSignedContextOracleV1 => T::try_from(self),
304            _ => Err(Error::UnsupportedMeta)?,
305        }
306    }
307}
308
309impl Serialize for RainMetaDocumentV1Item {
310    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
311        let mut map = serializer.serialize_map(Some(self.len()))?;
312        map.serialize_entry(&0, &self.payload)?;
313        map.serialize_entry(&1, &(self.magic as u64))?;
314        match self.content_type {
315            ContentType::None => {}
316            content_type => map.serialize_entry(&2, &content_type)?,
317        }
318        match self.content_encoding {
319            ContentEncoding::None => {}
320            content_encoding => map.serialize_entry(&3, &content_encoding)?,
321        }
322        match self.content_language {
323            ContentLanguage::None => {}
324            content_language => map.serialize_entry(&4, &content_language)?,
325        }
326        if let Some(schema) = &self.schema {
327            map.serialize_entry(&(KnownMagic::OaSchema as u64), schema)?;
328        }
329        map.end()
330    }
331}
332
333impl<'de> Deserialize<'de> for RainMetaDocumentV1Item {
334    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
335        struct EncodedMap;
336        impl<'de> Visitor<'de> for EncodedMap {
337            type Value = RainMetaDocumentV1Item;
338
339            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
340                formatter.write_str("rain meta cbor encoded bytes")
341            }
342
343            fn visit_map<T: serde::de::MapAccess<'de>>(
344                self,
345                mut map: T,
346            ) -> Result<Self::Value, T::Error> {
347                const OA_SCHEMA_KEY: u64 = KnownMagic::OaSchema as u64;
348                let mut payload = None;
349                let mut magic: Option<u64> = None;
350                let mut content_type = None;
351                let mut content_encoding = None;
352                let mut content_language = None;
353                let mut schema = None;
354                while match map.next_key::<u64>() {
355                    Ok(Some(key)) => {
356                        match key {
357                            0 => payload = Some(map.next_value()?),
358                            1 => magic = Some(map.next_value()?),
359                            2 => content_type = Some(map.next_value()?),
360                            3 => content_encoding = Some(map.next_value()?),
361                            4 => content_language = Some(map.next_value()?),
362                            OA_SCHEMA_KEY => schema = Some(map.next_value()?),
363                            other => Err(serde::de::Error::custom(format!(
364                                "found unexpected key in the map: {other}"
365                            )))?,
366                        };
367                        true
368                    }
369                    Ok(None) => false,
370                    Err(error) => Err(error)?,
371                } {}
372                let payload = payload.ok_or_else(|| serde::de::Error::missing_field("payload"))?;
373                let magic = match magic
374                    .ok_or_else(|| serde::de::Error::missing_field("magic number"))?
375                    .try_into()
376                {
377                    Ok(m) => m,
378                    _ => Err(serde::de::Error::custom("unknown magic number"))?,
379                };
380                let content_type = content_type.unwrap_or(ContentType::None);
381                let content_encoding = content_encoding.unwrap_or(ContentEncoding::None);
382                let content_language = content_language.unwrap_or(ContentLanguage::None);
383
384                Ok(RainMetaDocumentV1Item {
385                    payload,
386                    magic,
387                    content_type,
388                    content_encoding,
389                    content_language,
390                    schema,
391                })
392            }
393        }
394        deserializer.deserialize_map(EncodedMap)
395    }
396}
397
398/// searches for a meta matching the given hash in given subgraphs urls
399pub async fn search(hash: &str, subgraphs: &Vec<String>) -> Result<query::MetaResponse, Error> {
400    let request_body = query::MetaQuery::build_query(query::meta_query::Variables {
401        hash: Some(hash.to_ascii_lowercase()),
402    });
403    let mut promises = vec![];
404
405    let client = Arc::new(Client::builder().build().map_err(Error::ReqwestError)?);
406    for url in subgraphs {
407        promises.push(Box::pin(query::process_meta_query(
408            client.clone(),
409            &request_body,
410            url,
411        )));
412    }
413    let response_value = future::select_ok(promises.drain(..)).await?.0;
414    Ok(response_value)
415}
416
417/// searches for an ExpressionDeployer matching the given hash in given subgraphs urls
418pub async fn search_deployer(
419    hash: &str,
420    subgraphs: &Vec<String>,
421) -> Result<DeployerResponse, Error> {
422    let request_body = query::DeployerQuery::build_query(query::deployer_query::Variables {
423        hash: Some(hash.to_ascii_lowercase()),
424    });
425    let mut promises = vec![];
426
427    let client = Arc::new(Client::builder().build().map_err(Error::ReqwestError)?);
428    for url in subgraphs {
429        promises.push(Box::pin(query::process_deployer_query(
430            client.clone(),
431            &request_body,
432            url,
433        )));
434    }
435    let response_value = future::select_ok(promises.drain(..)).await?.0;
436    Ok(response_value)
437}
438
439/// checks if the given contract implements IDescribeByMetaV1 interface
440pub async fn implements_i_described_by_meta_v1<P: Provider>(
441    provider: &P,
442    contract_address: Address,
443) -> bool {
444    if !supports_erc165(provider, contract_address)
445        .await
446        .unwrap_or(false)
447    {
448        return false;
449    }
450
451    let interface_id_res = IDescribedByMetaV1::IDescribedByMetaV1Calls::xor_selectors();
452    if interface_id_res.is_err() {
453        return false;
454    }
455
456    let call = IERC165::supportsInterfaceCall {
457        interfaceID: interface_id_res.unwrap().into(),
458    };
459    let tx = TransactionRequest::default()
460        .to(contract_address)
461        .input(call.abi_encode().into());
462    match provider.call(tx).await {
463        Ok(bytes) => IERC165::supportsInterfaceCall::abi_decode_returns(&bytes).unwrap_or(false),
464        Err(_) => false,
465    }
466}
467
468/// All required NPE2 ExpressionDeployer data for reproducing it on a local evm
469#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Default)]
470#[serde(rename_all = "camelCase")]
471pub struct NPE2Deployer {
472    /// constructor meta hash
473    #[serde(with = "serde_bytes")]
474    pub meta_hash: Vec<u8>,
475    /// constructor meta bytes
476    #[serde(with = "serde_bytes")]
477    pub meta_bytes: Vec<u8>,
478    /// RainterpreterExpressionDeployerNPE2 contract bytecode
479    #[serde(with = "serde_bytes")]
480    pub bytecode: Vec<u8>,
481    /// RainterpreterParserNPE2 contract bytecode
482    #[serde(with = "serde_bytes")]
483    pub parser: Vec<u8>,
484    /// RainterpreterStoreNPE2 contract bytecode
485    #[serde(with = "serde_bytes")]
486    pub store: Vec<u8>,
487    /// RainterpreterNPE2 contract bytecode
488    #[serde(with = "serde_bytes")]
489    pub interpreter: Vec<u8>,
490    /// RainterpreterExpressionDeployerNPE2 authoring meta
491    pub authoring_meta: Option<AuthoringMeta>,
492}
493
494impl NPE2Deployer {
495    pub fn is_corrupt(&self) -> bool {
496        if self.meta_hash.is_empty() {
497            return true;
498        }
499        if self.meta_bytes.is_empty() {
500            return true;
501        }
502        if self.bytecode.is_empty() {
503            return true;
504        }
505        if self.parser.is_empty() {
506            return true;
507        }
508        if self.store.is_empty() {
509            return true;
510        }
511        if self.interpreter.is_empty() {
512            return true;
513        }
514        false
515    }
516}
517
518/// # Meta Storage(CAS)
519///
520/// In-memory CAS (content addressed storage) for Rain metadata which basically stores
521/// k/v pairs of meta hash, meta bytes and ExpressionDeployer reproducible data as well
522/// as providing functionalities to easliy read/write to the CAS.
523///
524/// Hashes are normal bytes and meta bytes are valid cbor encoded as data bytes.
525/// ExpressionDeployers data are in form of a struct mapped to deployedBytecode meta hash
526/// and deploy transaction hash.
527///
528/// ## Examples
529///
530/// ```
531/// use rain_metadata::Store;
532/// use std::collections::HashMap;
533///
534/// // to instantiate without any default subgraphs
535/// let mut store = Store::new();
536///
537/// // to instantiate with default rain subgraphs included
538/// let mut store = Store::default();
539///
540/// // or to instantiate with initial values
541/// let mut store = Store::create(
542///     &vec!["sg-url-1".to_string()],
543///     &HashMap::new(),
544///     &HashMap::new(),
545///     &HashMap::new(),
546///     true,
547/// );
548///
549/// // add a new subgraph endpoint url to the subgraph list
550/// store.add_subgraphs(&vec!["sg-url-2".to_string()]);
551///
552/// // merge another Store into this one
553/// store.merge(&Store::default());
554///
555/// // updates the meta store with a new meta hash and bytes
556/// let hash = vec![0u8, 1u8, 2u8];
557/// store.update_with(&hash, &vec![0u8, 1u8]);
558///
559/// // `Store::update(&hash)` is async; it searches each subgraph for `hash` and
560/// // populates the cache with the result. Call it from an async context with `.await`.
561///
562/// // to get a record from the store
563/// let _meta = store.get_meta(&hash);
564///
565/// // to get a deployer record from the store
566/// let _deployer_record = store.get_deployer(&hash);
567///
568/// // Store is agnostic to dotrain contents — it just maps the hash of the content
569/// // to the given uri and puts it as a new meta into the meta cache.
570/// let dotrain_uri = "path/to/file.rain";
571/// let dotrain_content = "/* some dotrain source */";
572/// let (_new_hash, _old_hash) = store
573///     .set_dotrain(dotrain_content, dotrain_uri, false)
574///     .unwrap();
575///
576/// // to get dotrain meta bytes given a uri
577/// let _dotrain_meta_bytes = store.get_dotrain_meta(dotrain_uri);
578/// ```
579#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
580pub struct Store {
581    subgraphs: Vec<String>,
582    cache: HashMap<Vec<u8>, Vec<u8>>,
583    dotrain_cache: HashMap<String, Vec<u8>>,
584    deployer_cache: HashMap<Vec<u8>, NPE2Deployer>,
585    deployer_hash_map: HashMap<Vec<u8>, Vec<u8>>,
586}
587
588impl Default for Store {
589    fn default() -> Self {
590        Store {
591            cache: HashMap::new(),
592            dotrain_cache: HashMap::new(),
593            deployer_cache: HashMap::new(),
594            subgraphs: KnownSubgraphs::NPE2.map(|url| url.to_string()).to_vec(),
595            deployer_hash_map: HashMap::new(),
596        }
597    }
598}
599
600impl Store {
601    /// lazily creates a new instance
602    /// it is recommended to use create() instead with initial values
603    pub fn new() -> Store {
604        Store {
605            subgraphs: vec![],
606            cache: HashMap::new(),
607            dotrain_cache: HashMap::new(),
608            deployer_cache: HashMap::new(),
609            deployer_hash_map: HashMap::new(),
610        }
611    }
612
613    /// creates new instance of Store with given initial values
614    /// it checks the validity of each item of the provided values and only stores those that are valid
615    pub fn create(
616        subgraphs: &Vec<String>,
617        cache: &HashMap<Vec<u8>, Vec<u8>>,
618        deployer_cache: &HashMap<Vec<u8>, NPE2Deployer>,
619        dotrain_cache: &HashMap<String, Vec<u8>>,
620        include_rain_subgraphs: bool,
621    ) -> Store {
622        let mut store;
623        if include_rain_subgraphs {
624            store = Store::default();
625        } else {
626            store = Store::new();
627        }
628        store.add_subgraphs(subgraphs);
629        for (hash, bytes) in cache {
630            store.update_with(hash, bytes);
631        }
632        for (hash, deployer) in deployer_cache {
633            store.set_deployer(hash, deployer, None);
634        }
635        for (uri, hash) in dotrain_cache {
636            if !store.dotrain_cache.contains_key(uri) && store.cache.contains_key(hash) {
637                store.dotrain_cache.insert(uri.clone(), hash.clone());
638            }
639        }
640        store
641    }
642
643    /// all subgraph endpoints in this instance
644    pub fn subgraphs(&self) -> &Vec<String> {
645        &self.subgraphs
646    }
647
648    /// add new subgraph endpoints
649    pub fn add_subgraphs(&mut self, subgraphs: &Vec<String>) {
650        for sg in subgraphs {
651            if !self.subgraphs.contains(sg) {
652                self.subgraphs.push(sg.to_string());
653            }
654        }
655    }
656
657    /// getter method for the whole meta cache
658    pub fn cache(&self) -> &HashMap<Vec<u8>, Vec<u8>> {
659        &self.cache
660    }
661
662    /// get the corresponding meta bytes of the given hash if it exists
663    pub fn get_meta(&self, hash: &[u8]) -> Option<&Vec<u8>> {
664        self.cache.get(hash)
665    }
666
667    /// getter method for the whole authoring meta cache
668    pub fn deployer_cache(&self) -> &HashMap<Vec<u8>, NPE2Deployer> {
669        &self.deployer_cache
670    }
671
672    /// get the corresponding DeployerNPRecord of the given deployer hash if it exists
673    pub fn get_deployer(&self, hash: &[u8]) -> Option<&NPE2Deployer> {
674        if self.deployer_cache.contains_key(hash) {
675            self.deployer_cache.get(hash)
676        } else if let Some(h) = self.deployer_hash_map.get(hash) {
677            self.deployer_cache.get(h)
678        } else {
679            None
680        }
681    }
682
683    /// searches for DeployerNPRecord in the subgraphs given the deployer hash
684    pub async fn search_deployer(&mut self, hash: &[u8]) -> Option<&NPE2Deployer> {
685        match search_deployer(&hex::encode_prefixed(hash), &self.subgraphs).await {
686            Ok(res) => {
687                self.cache
688                    .insert(res.meta_hash.clone(), res.meta_bytes.clone());
689                let authoring_meta = res.get_authoring_meta();
690                self.deployer_cache.insert(
691                    res.bytecode_meta_hash.clone(),
692                    NPE2Deployer {
693                        meta_hash: res.meta_hash.clone(),
694                        meta_bytes: res.meta_bytes,
695                        bytecode: res.bytecode,
696                        parser: res.parser,
697                        store: res.store,
698                        interpreter: res.interpreter,
699                        authoring_meta,
700                    },
701                );
702                self.deployer_hash_map.insert(res.tx_hash, res.meta_hash);
703                self.deployer_cache.get(hash)
704            }
705            Err(_e) => None,
706        }
707    }
708
709    /// if the NPE2Deployer record already is cached it returns it immediately else
710    /// searches for NPE2Deployer in the subgraphs given the deployer hash
711    pub async fn search_deployer_check(&mut self, hash: &[u8]) -> Option<&NPE2Deployer> {
712        if self.deployer_cache.contains_key(hash) {
713            self.get_deployer(hash)
714        } else if self.deployer_hash_map.contains_key(hash) {
715            let b_hash = self.deployer_hash_map.get(hash).unwrap();
716            self.get_deployer(b_hash)
717        } else {
718            self.search_deployer(hash).await
719        }
720    }
721
722    /// sets deployer record from the deployer query response
723    pub fn set_deployer_from_query_response(
724        &mut self,
725        deployer_query_response: DeployerResponse,
726    ) -> NPE2Deployer {
727        let authoring_meta = deployer_query_response.get_authoring_meta();
728        let tx_hash = deployer_query_response.tx_hash;
729        let bytecode_meta_hash = deployer_query_response.bytecode_meta_hash;
730        let result = NPE2Deployer {
731            meta_hash: deployer_query_response.meta_hash.clone(),
732            meta_bytes: deployer_query_response.meta_bytes,
733            bytecode: deployer_query_response.bytecode,
734            parser: deployer_query_response.parser,
735            store: deployer_query_response.store,
736            interpreter: deployer_query_response.interpreter,
737            authoring_meta,
738        };
739        self.cache
740            .insert(deployer_query_response.meta_hash, result.meta_bytes.clone());
741        self.deployer_hash_map
742            .insert(tx_hash, bytecode_meta_hash.clone());
743        self.deployer_cache
744            .insert(bytecode_meta_hash, result.clone());
745        result
746    }
747
748    /// sets NPE2Deployer record
749    /// skips if the given hash is invalid
750    pub fn set_deployer(
751        &mut self,
752        hash: &[u8],
753        npe2_deployer: &NPE2Deployer,
754        tx_hash: Option<&[u8]>,
755    ) {
756        self.cache.insert(
757            npe2_deployer.meta_hash.clone(),
758            npe2_deployer.meta_bytes.clone(),
759        );
760        self.deployer_cache
761            .insert(hash.to_vec(), npe2_deployer.clone());
762        if let Some(v) = tx_hash {
763            self.deployer_hash_map.insert(v.to_vec(), hash.to_vec());
764        }
765    }
766
767    /// getter method for the whole dotrain cache
768    pub fn dotrain_cache(&self) -> &HashMap<String, Vec<u8>> {
769        &self.dotrain_cache
770    }
771
772    /// get the corresponding dotrain hash of the given dotrain uri if it exists
773    pub fn get_dotrain_hash(&self, uri: &str) -> Option<&Vec<u8>> {
774        self.dotrain_cache.get(uri)
775    }
776
777    /// get the corresponding uri of the given dotrain hash if it exists
778    pub fn get_dotrain_uri(&self, hash: &[u8]) -> Option<&String> {
779        for (uri, h) in &self.dotrain_cache {
780            if h == hash {
781                return Some(uri);
782            }
783        }
784        None
785    }
786
787    /// get the corresponding meta bytes of the given dotrain uri if it exists
788    pub fn get_dotrain_meta(&self, uri: &str) -> Option<&Vec<u8>> {
789        self.get_meta(self.dotrain_cache.get(uri)?)
790    }
791
792    /// deletes a dotrain record given a uri
793    pub fn delete_dotrain(&mut self, uri: &str, keep_meta: bool) {
794        if let Some(kv) = self.dotrain_cache.remove_entry(uri) {
795            if !keep_meta {
796                self.cache.remove(&kv.1);
797            }
798        };
799    }
800
801    /// lazilly merges another Store to the current one, avoids duplicates
802    pub fn merge(&mut self, other: &Store) {
803        self.add_subgraphs(&other.subgraphs);
804        for (hash, bytes) in &other.cache {
805            if !self.cache.contains_key(hash) {
806                self.cache.insert(hash.clone(), bytes.clone());
807            }
808        }
809        for (hash, deployer) in &other.deployer_cache {
810            if !self.deployer_cache.contains_key(hash) {
811                self.deployer_cache.insert(hash.clone(), deployer.clone());
812            }
813        }
814        for (hash, tx_hash) in &other.deployer_hash_map {
815            self.deployer_hash_map.insert(hash.clone(), tx_hash.clone());
816        }
817        for (uri, hash) in &other.dotrain_cache {
818            if !self.dotrain_cache.contains_key(uri) {
819                self.dotrain_cache.insert(uri.clone(), hash.clone());
820            }
821        }
822    }
823
824    /// updates the meta cache by searching through all subgraphs for the given hash
825    /// returns the reference to the meta bytes in the cache if it was found
826    pub async fn update(&mut self, hash: &[u8]) -> Option<&Vec<u8>> {
827        if let Ok(meta) = search(&hex::encode_prefixed(hash), &self.subgraphs).await {
828            self.store_content(&meta.bytes);
829            self.cache.insert(hash.to_vec(), meta.bytes);
830            self.get_meta(hash)
831        } else {
832            None
833        }
834    }
835
836    /// first checks if the meta is stored, if not will perform update()
837    pub async fn update_check(&mut self, hash: &[u8]) -> Option<&Vec<u8>> {
838        if !self.cache.contains_key(hash) {
839            self.update(hash).await
840        } else {
841            self.get_meta(hash)
842        }
843    }
844
845    /// updates the meta cache by the given hash and meta bytes, checks the hash to bytes
846    /// validity returns the reference to the bytes if the updated meta bytes contained any
847    pub fn update_with(&mut self, hash: &[u8], bytes: &[u8]) -> Option<&Vec<u8>> {
848        if !self.cache.contains_key(hash) {
849            if keccak256(bytes).0 == hash {
850                self.store_content(bytes);
851                self.cache.insert(hash.to_vec(), bytes.to_vec());
852                self.cache.get(hash)
853            } else {
854                None
855            }
856        } else {
857            self.get_meta(hash)
858        }
859    }
860
861    /// stores (or updates in case the URI already exists) the given dotrain text as meta into the store cache
862    /// and maps it to the given uri (path), it should be noted that reading the content of the dotrain is not in
863    /// the scope of Store and handling and passing on a correct URI (path) for the given text must be handled
864    /// externally by the implementer
865    pub fn set_dotrain(
866        &mut self,
867        text: &str,
868        uri: &str,
869        keep_old: bool,
870    ) -> Result<(Vec<u8>, Vec<u8>), Error> {
871        let bytes = RainMetaDocumentV1Item {
872            payload: serde_bytes::ByteBuf::from(text.as_bytes()),
873            magic: KnownMagic::DotrainV1,
874            content_type: ContentType::OctetStream,
875            content_encoding: ContentEncoding::None,
876            content_language: ContentLanguage::None,
877            schema: None,
878        }
879        .cbor_encode()?;
880        let new_hash = keccak256(&bytes).0.to_vec();
881        if let Some(h) = self.dotrain_cache.get(uri) {
882            let old_hash = h.clone();
883            if new_hash == old_hash {
884                self.cache.insert(new_hash.clone(), bytes);
885                Ok((new_hash, vec![]))
886            } else {
887                self.cache.insert(new_hash.clone(), bytes);
888                self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
889                if !keep_old {
890                    self.cache.remove(&old_hash);
891                }
892                Ok((new_hash, old_hash))
893            }
894        } else {
895            self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
896            self.cache.insert(new_hash.clone(), bytes);
897            Ok((new_hash, vec![]))
898        }
899    }
900
901    /// decodes each meta and stores the inner meta items into the cache
902    /// if any of the inner items is an authoring meta, stores it in authoring meta cache as well
903    /// returns the reference to the authoring bytes if the meta bytes contained any
904    fn store_content(&mut self, bytes: &[u8]) {
905        if let Ok(meta_maps) = RainMetaDocumentV1Item::cbor_decode(bytes) {
906            if bytes.starts_with(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes()) {
907                for meta_map in &meta_maps {
908                    if let Ok(encoded_bytes) = meta_map.cbor_encode() {
909                        self.cache
910                            .insert(keccak256(&encoded_bytes).0.to_vec(), encoded_bytes);
911                    }
912                }
913            }
914        }
915    }
916}
917
918/// converts string to bytes32
919pub fn str_to_bytes32(text: &str) -> Result<[u8; 32], Error> {
920    let bytes: &[u8] = text.as_bytes();
921    if bytes.len() > 32 {
922        return Err(Error::BiggerThan32Bytes);
923    }
924    let mut b32 = [0u8; 32];
925    b32[..bytes.len()].copy_from_slice(bytes);
926    Ok(b32)
927}
928
929/// converts bytes32 to string
930pub fn bytes32_to_str(bytes: &[u8; 32]) -> Result<&str, Error> {
931    let mut len = 32;
932    if let Some((pos, _)) = itertools::Itertools::find_position(&mut bytes.iter(), |b| **b == 0u8) {
933        len = pos;
934    };
935    Ok(std::str::from_utf8(&bytes[..len])?)
936}
937
938#[cfg(all(test, not(target_family = "wasm")))]
939mod tests {
940    use super::{
941        *, bytes32_to_str, magic::KnownMagic, str_to_bytes32, types::authoring::v1::AuthoringMeta,
942        ContentEncoding, ContentLanguage, ContentType, Error, RainMetaDocumentV1Item,
943    };
944    use alloy::providers::ProviderBuilder;
945    use alloy::{providers::mock::Asserter, rpc::json_rpc::ErrorPayload, sol_types::SolType};
946    use serde_json::json;
947
948    /// Roundtrip test for an authoring meta
949    /// original content -> pack -> MetaMap -> cbor encode -> cbor decode -> MetaMap -> unpack -> original content,
950    #[test]
951    fn authoring_meta_roundtrip() -> Result<(), Error> {
952        let authoring_meta_content = r#"[
953            {
954                "word": "stack",
955                "description": "Copies an existing value from the stack.",
956                "operandParserOffset": 16
957            },
958            {
959                "word": "constant",
960                "description": "Copies a constant value onto the stack.",
961                "operandParserOffset": 16
962            }
963        ]"#;
964        let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
965
966        // abi encode the authoring meta with performing validation
967        let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
968        let expected_abi_encoded = <alloy::sol!((bytes32, uint8, string)[])>::abi_encode(&vec![
969            (
970                str_to_bytes32("stack")?,
971                16u8,
972                "Copies an existing value from the stack.".to_string(),
973            ),
974            (
975                str_to_bytes32("constant")?,
976                16u8,
977                "Copies a constant value onto the stack.".to_string(),
978            ),
979        ]);
980        // check the encoded bytes agaiinst the expected
981        assert_eq!(authoring_meta_abi_encoded, expected_abi_encoded);
982
983        let meta_map = RainMetaDocumentV1Item {
984            payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
985            magic: KnownMagic::AuthoringMetaV1,
986            content_type: ContentType::Cbor,
987            content_encoding: ContentEncoding::None,
988            content_language: ContentLanguage::None,
989            schema: None,
990        };
991        let cbor_encoded = meta_map.cbor_encode()?;
992
993        // cbor map with 3 keys
994        assert_eq!(cbor_encoded[0], 0xa3);
995        // key 0
996        assert_eq!(cbor_encoded[1], 0x00);
997        // major type 2 (bytes) length 512
998        assert_eq!(cbor_encoded[2], 0b010_11001);
999        assert_eq!(cbor_encoded[3], 0b000_00010);
1000        assert_eq!(cbor_encoded[4], 0b000_00000);
1001        // payload
1002        assert_eq!(cbor_encoded[5..517], authoring_meta_abi_encoded);
1003        // key 1
1004        assert_eq!(cbor_encoded[517], 0x01);
1005        // major type 0 (unsigned integer) value 27
1006        assert_eq!(cbor_encoded[518], 0b000_11011);
1007        // magic number
1008        assert_eq!(
1009            &cbor_encoded[519..527],
1010            KnownMagic::AuthoringMetaV1.to_prefix_bytes()
1011        );
1012        // key 2
1013        assert_eq!(cbor_encoded[527], 0x02);
1014        // text string application/cbor length 16
1015        assert_eq!(cbor_encoded[528], 0b011_10000);
1016        // the string application/cbor, must be the end of data
1017        assert_eq!(&cbor_encoded[529..], "application/cbor".as_bytes());
1018
1019        // decode the data back to MetaMap
1020        let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1021        // the length of decoded maps must be 1 as we only had 1 encoded item
1022        assert_eq!(cbor_decoded.len(), 1);
1023        // decoded item must be equal to the original meta_map
1024        assert_eq!(cbor_decoded[0], meta_map);
1025
1026        Ok(())
1027    }
1028
1029    /// Roundtrip test for a dotrain meta
1030    /// original content -> pack -> MetaMap -> cbor encode -> cbor decode -> MetaMap -> unpack -> original content,
1031    #[test]
1032    fn dotrain_meta_roundtrip() -> Result<(), Error> {
1033        let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
1034        let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
1035
1036        let content_encoding = ContentEncoding::Deflate;
1037        let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
1038
1039        let meta_map = RainMetaDocumentV1Item {
1040            payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
1041            magic: KnownMagic::DotrainV1,
1042            content_type: ContentType::OctetStream,
1043            content_encoding,
1044            content_language: ContentLanguage::En,
1045            schema: None,
1046        };
1047        let cbor_encoded = meta_map.cbor_encode()?;
1048
1049        // cbor map with 5 keys
1050        assert_eq!(cbor_encoded[0], 0xa5);
1051        // key 0
1052        assert_eq!(cbor_encoded[1], 0x00);
1053        // major type 2 (bytes) length 36
1054        assert_eq!(cbor_encoded[2], 0b010_11000);
1055        assert_eq!(cbor_encoded[3], 0b001_00100);
1056        // assert_eq!(cbor_encoded[4], 0b000_00000);
1057        // payload
1058        assert_eq!(cbor_encoded[4..40], deflated_payload);
1059        // key 1
1060        assert_eq!(cbor_encoded[40], 0x01);
1061        // major type 0 (unsigned integer) value 27
1062        assert_eq!(cbor_encoded[41], 0b000_11011);
1063        // magic number
1064        assert_eq!(
1065            &cbor_encoded[42..50],
1066            KnownMagic::DotrainV1.to_prefix_bytes()
1067        );
1068        // key 2
1069        assert_eq!(cbor_encoded[50], 0x02);
1070        // text string application/octet-stream length 24
1071        assert_eq!(cbor_encoded[51], 0b011_11000);
1072        assert_eq!(cbor_encoded[52], 0b000_11000);
1073        // the string application/octet-stream
1074        assert_eq!(&cbor_encoded[53..77], "application/octet-stream".as_bytes());
1075        // key 3
1076        assert_eq!(cbor_encoded[77], 0x03);
1077        // text string deflate length 7
1078        assert_eq!(cbor_encoded[78], 0b011_00111);
1079        // the string deflate
1080        assert_eq!(&cbor_encoded[79..86], "deflate".as_bytes());
1081        // key 4
1082        assert_eq!(cbor_encoded[86], 0x04);
1083        // text string en length 2
1084        assert_eq!(cbor_encoded[87], 0b011_00010);
1085        // the string identity, must be the end of data
1086        assert_eq!(&cbor_encoded[88..], "en".as_bytes());
1087
1088        // decode the data back to MetaMap
1089        let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1090        // the length of decoded maps must be 1 as we only had 1 encoded item
1091        assert_eq!(cbor_decoded.len(), 1);
1092        // decoded item must be equal to the original meta_map
1093        assert_eq!(cbor_decoded[0], meta_map);
1094
1095        Ok(())
1096    }
1097
1098    /// Roundtrip test for a meta sequence
1099    /// original content -> pack -> MetaMap -> cbor encode -> cbor decode -> MetaMap -> unpack -> original content,
1100    #[test]
1101    fn meta_seq_roundtrip() -> Result<(), Error> {
1102        let authoring_meta_content = r#"[
1103            {
1104                "word": "stack",
1105                "description": "Copies an existing value from the stack.",
1106                "operandParserOffset": 16
1107            },
1108            {
1109                "word": "constant",
1110                "description": "Copies a constant value onto the stack.",
1111                "operandParserOffset": 16
1112            }
1113        ]"#;
1114        let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
1115        let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
1116        let meta_map_1 = RainMetaDocumentV1Item {
1117            payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
1118            magic: KnownMagic::AuthoringMetaV1,
1119            content_type: ContentType::Cbor,
1120            content_encoding: ContentEncoding::None,
1121            content_language: ContentLanguage::None,
1122            schema: None,
1123        };
1124
1125        let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
1126        let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
1127        let content_encoding = ContentEncoding::Deflate;
1128        let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
1129        let meta_map_2 = RainMetaDocumentV1Item {
1130            payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
1131            magic: KnownMagic::DotrainV1,
1132            content_type: ContentType::OctetStream,
1133            content_encoding,
1134            content_language: ContentLanguage::En,
1135            schema: None,
1136        };
1137
1138        // cbor encode as RainMetaDocument sequence
1139        let cbor_encoded = RainMetaDocumentV1Item::cbor_encode_seq(
1140            &vec![meta_map_1.clone(), meta_map_2.clone()],
1141            KnownMagic::RainMetaDocumentV1,
1142        )?;
1143
1144        // 8 byte magic number prefix
1145        assert_eq!(
1146            &cbor_encoded[0..8],
1147            KnownMagic::RainMetaDocumentV1.to_prefix_bytes()
1148        );
1149
1150        // first item in the encoded bytes
1151        // cbor map with 3 keys
1152        assert_eq!(cbor_encoded[8], 0xa3);
1153        // key 0
1154        assert_eq!(cbor_encoded[9], 0x00);
1155        // major type 2 (bytes) length 512
1156        assert_eq!(cbor_encoded[10], 0b010_11001);
1157        assert_eq!(cbor_encoded[11], 0b000_00010);
1158        assert_eq!(cbor_encoded[12], 0b000_00000);
1159        // payload
1160        assert_eq!(cbor_encoded[13..525], authoring_meta_abi_encoded);
1161        // key 1
1162        assert_eq!(cbor_encoded[525], 0x01);
1163        // major type 0 (unsigned integer) value 27
1164        assert_eq!(cbor_encoded[526], 0b000_11011);
1165        // magic number
1166        assert_eq!(
1167            &cbor_encoded[527..535],
1168            KnownMagic::AuthoringMetaV1.to_prefix_bytes()
1169        );
1170        // key 2
1171        assert_eq!(cbor_encoded[535], 0x02);
1172        // text string application/cbor length 16
1173        assert_eq!(cbor_encoded[536], 0b011_10000);
1174        // the string application/cbor, must be the end of data
1175        assert_eq!(&cbor_encoded[537..553], "application/cbor".as_bytes());
1176
1177        // second item in the encoded bytes
1178        // cbor map with 5 keys
1179        assert_eq!(cbor_encoded[553], 0xa5);
1180        // key 0
1181        assert_eq!(cbor_encoded[554], 0x00);
1182        // major type 2 (bytes) length 36
1183        assert_eq!(cbor_encoded[555], 0b010_11000);
1184        assert_eq!(cbor_encoded[556], 0b001_00100);
1185        // assert_eq!(cbor_encoded[4], 0b000_00000);
1186        // payload
1187        assert_eq!(cbor_encoded[557..593], deflated_payload);
1188        // key 1
1189        assert_eq!(cbor_encoded[593], 0x01);
1190        // major type 0 (unsigned integer) value 27
1191        assert_eq!(cbor_encoded[594], 0b000_11011);
1192        // magic number
1193        assert_eq!(
1194            &cbor_encoded[595..603],
1195            KnownMagic::DotrainV1.to_prefix_bytes()
1196        );
1197        // key 2
1198        assert_eq!(cbor_encoded[603], 0x02);
1199        // text string application/octet-stream length 24
1200        assert_eq!(cbor_encoded[604], 0b011_11000);
1201        assert_eq!(cbor_encoded[605], 0b000_11000);
1202        // the string application/octet-stream
1203        assert_eq!(
1204            &cbor_encoded[606..630],
1205            "application/octet-stream".as_bytes()
1206        );
1207        // key 3
1208        assert_eq!(cbor_encoded[630], 0x03);
1209        // text string deflate length 7
1210        assert_eq!(cbor_encoded[631], 0b011_00111);
1211        // the string deflate
1212        assert_eq!(&cbor_encoded[632..639], "deflate".as_bytes());
1213        // key 4
1214        assert_eq!(cbor_encoded[639], 0x04);
1215        // text string en length 2
1216        assert_eq!(cbor_encoded[640], 0b011_00010);
1217        // the string identity, must be the end of data
1218        assert_eq!(&cbor_encoded[641..], "en".as_bytes());
1219
1220        // decode the data back to MetaMap
1221        let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1222        // the length of decoded maps must be 2 as we had 2 encoded item
1223        assert_eq!(cbor_decoded.len(), 2);
1224
1225        // decoded item 1 must be equal to the original meta_map_1
1226        assert_eq!(cbor_decoded[0], meta_map_1);
1227        // decoded item 2 must be equal to the original meta_map_2
1228        assert_eq!(cbor_decoded[1], meta_map_2);
1229
1230        Ok(())
1231    }
1232
1233    #[test]
1234    fn test_bytes32_to_str() {
1235        let text_bytes_list = vec![
1236            (
1237                "",
1238                hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1239            ),
1240            (
1241                "A",
1242                hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1243            ),
1244            (
1245                "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1246                hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1247            ),
1248            (
1249                "!@#$%^&*(),./;'[]",
1250                hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1251            ),
1252        ];
1253
1254        for (text, bytes) in text_bytes_list {
1255            assert_eq!(text, bytes32_to_str(&bytes).unwrap());
1256        }
1257    }
1258
1259    #[test]
1260    fn test_str_to_bytes32() {
1261        let text_bytes_list = vec![
1262            (
1263                "",
1264                hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1265            ),
1266            (
1267                "A",
1268                hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1269            ),
1270            (
1271                "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1272                hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1273            ),
1274            (
1275                "!@#$%^&*(),./;'[]",
1276                hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1277            ),
1278        ];
1279
1280        for (text, bytes) in text_bytes_list {
1281            assert_eq!(bytes, str_to_bytes32(text).unwrap());
1282        }
1283    }
1284
1285    #[test]
1286    fn test_str_to_bytes32_long() {
1287        assert!(matches!(
1288            str_to_bytes32("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456").unwrap_err(),
1289            Error::BiggerThan32Bytes
1290        ));
1291    }
1292
1293    #[tokio::test]
1294    async fn test_implements_i_describe_by_meta_v1() {
1295        // makes new server/client with success response for erc165 check
1296        async fn new_server_client() -> (Asserter, impl Provider) {
1297            let asserter = Asserter::new();
1298            let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
1299
1300            // Mock a responses for successful supports erc165 check
1301            asserter.push_success(
1302                &"0x0000000000000000000000000000000000000000000000000000000000000001",
1303            );
1304            asserter.push_success(
1305                &"0x0000000000000000000000000000000000000000000000000000000000000000",
1306            );
1307
1308            (asserter, provider)
1309        }
1310
1311        let address = Address::random();
1312
1313        // mock a true response for implements IDescribedByMetaV1
1314        let (asserter, provider) = new_server_client().await;
1315        asserter
1316            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
1317        let result = implements_i_described_by_meta_v1(&provider, address).await;
1318        assert!(result);
1319
1320        // mock a false response for implements IDescribedByMetaV1
1321        let (asserter, provider) = new_server_client().await;
1322        asserter
1323            .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
1324        let result = implements_i_described_by_meta_v1(&provider, address).await;
1325        assert!(!result);
1326
1327        // mock a revert response for implements IDescribedByMetaV1
1328        let (asserter, provider) = new_server_client().await;
1329        asserter.push_failure(ErrorPayload {
1330            code: -32003,
1331            message: "execution reverted".into(),
1332            data: Some(serde_json::value::to_raw_value(&json!("0x00")).unwrap()),
1333        });
1334        let result = implements_i_described_by_meta_v1(&provider, address).await;
1335        assert!(!result);
1336    }
1337
1338    /// Roundtrip test for a meta map carrying the OaSchema magic number as an
1339    /// additional CBOR map key beyond the standard 0-4 keys.
1340    /// MetaMap (with schema) -> cbor encode -> cbor decode -> MetaMap, assert equality
1341    #[test]
1342    fn oa_schema_map_key_roundtrip() -> Result<(), Error> {
1343        let payload = vec![0x01, 0x02, 0x03];
1344        // an IPFS hash referencing the schema of the payload, as written by
1345        // the SFT frontend under the OaSchema map key
1346        let schema = "QmSchemaHash1234567890abcdefghijklmnopqrstuvwx".to_string();
1347        assert_eq!(schema.len(), 46);
1348
1349        let meta_map = RainMetaDocumentV1Item {
1350            payload: serde_bytes::ByteBuf::from(payload.clone()),
1351            magic: KnownMagic::OaStructure,
1352            content_type: ContentType::Json,
1353            content_encoding: ContentEncoding::Deflate,
1354            content_language: ContentLanguage::None,
1355            schema: Some(schema.clone()),
1356        };
1357        let cbor_encoded = meta_map.cbor_encode()?;
1358
1359        // cbor map with 5 keys (0, 1, 2, 3 and the OaSchema magic)
1360        assert_eq!(cbor_encoded[0], 0xa5);
1361        // key 0
1362        assert_eq!(cbor_encoded[1], 0x00);
1363        // major type 2 (bytes) length 3
1364        assert_eq!(cbor_encoded[2], 0b010_00011);
1365        // payload
1366        assert_eq!(cbor_encoded[3..6], payload);
1367        // key 1
1368        assert_eq!(cbor_encoded[6], 0x01);
1369        // major type 0 (unsigned integer) value 27
1370        assert_eq!(cbor_encoded[7], 0b000_11011);
1371        // magic number
1372        assert_eq!(
1373            &cbor_encoded[8..16],
1374            KnownMagic::OaStructure.to_prefix_bytes()
1375        );
1376        // key 2
1377        assert_eq!(cbor_encoded[16], 0x02);
1378        // text string application/json length 16
1379        assert_eq!(cbor_encoded[17], 0b011_10000);
1380        assert_eq!(&cbor_encoded[18..34], "application/json".as_bytes());
1381        // key 3
1382        assert_eq!(cbor_encoded[34], 0x03);
1383        // text string deflate length 7
1384        assert_eq!(cbor_encoded[35], 0b011_00111);
1385        assert_eq!(&cbor_encoded[36..43], "deflate".as_bytes());
1386        // the OaSchema magic as key, major type 0 (unsigned integer) value 27
1387        assert_eq!(cbor_encoded[43], 0b000_11011);
1388        assert_eq!(
1389            &cbor_encoded[44..52],
1390            KnownMagic::OaSchema.to_prefix_bytes()
1391        );
1392        // schema value, text string length 46
1393        assert_eq!(cbor_encoded[52], 0b011_11000);
1394        assert_eq!(cbor_encoded[53], 46);
1395        // the schema hash string, must be the end of data
1396        assert_eq!(&cbor_encoded[54..], schema.as_bytes());
1397
1398        // decode the data back to MetaMap
1399        let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1400        // the length of decoded maps must be 1 as we only had 1 encoded item
1401        assert_eq!(cbor_decoded.len(), 1);
1402        // decoded item must be equal to the original meta_map
1403        assert_eq!(cbor_decoded[0], meta_map);
1404
1405        Ok(())
1406    }
1407
1408    /// A meta map without the schema key must keep encoding exactly as before
1409    /// (no schema entry on the wire) and roundtrip with schema None
1410    #[test]
1411    fn no_schema_key_encodes_as_before_roundtrip() -> Result<(), Error> {
1412        let payload = vec![0x0a, 0x0b];
1413        let meta_map = RainMetaDocumentV1Item {
1414            payload: serde_bytes::ByteBuf::from(payload.clone()),
1415            magic: KnownMagic::OaStructure,
1416            content_type: ContentType::None,
1417            content_encoding: ContentEncoding::None,
1418            content_language: ContentLanguage::None,
1419            schema: None,
1420        };
1421        let cbor_encoded = meta_map.cbor_encode()?;
1422
1423        // cbor map with only the 2 mandatory keys
1424        assert_eq!(cbor_encoded[0], 0xa2);
1425        // key 0
1426        assert_eq!(cbor_encoded[1], 0x00);
1427        // major type 2 (bytes) length 2
1428        assert_eq!(cbor_encoded[2], 0b010_00010);
1429        // payload
1430        assert_eq!(cbor_encoded[3..5], payload);
1431        // key 1
1432        assert_eq!(cbor_encoded[5], 0x01);
1433        // major type 0 (unsigned integer) value 27
1434        assert_eq!(cbor_encoded[6], 0b000_11011);
1435        // magic number, must be the end of data
1436        assert_eq!(
1437            &cbor_encoded[7..],
1438            KnownMagic::OaStructure.to_prefix_bytes()
1439        );
1440
1441        let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1442        assert_eq!(cbor_decoded.len(), 1);
1443        assert_eq!(cbor_decoded[0], meta_map);
1444
1445        Ok(())
1446    }
1447
1448    /// Any magic number other than OaSchema used as an extra map key must
1449    /// still be rejected on decode
1450    #[test]
1451    fn non_oa_schema_extra_map_key_errors() -> Result<(), Error> {
1452        // build a map identical to a valid 2 key meta map but with an extra
1453        // OaHashList magic key carrying a text string
1454        let mut bytes: Vec<u8> = vec![
1455            // cbor map with 3 keys
1456            0xa3, // key 0, bytes payload of length 1
1457            0x00, 0x41, 0xff, // key 1, unsigned integer magic number
1458            0x01, 0x1b,
1459        ];
1460        bytes.extend_from_slice(&KnownMagic::OaStructure.to_prefix_bytes());
1461        // the OaHashList magic as key
1462        bytes.push(0x1b);
1463        bytes.extend_from_slice(&KnownMagic::OaHashList.to_prefix_bytes());
1464        // text string value of length 2
1465        bytes.extend_from_slice(&[0x62, 0x68, 0x69]);
1466
1467        let result = RainMetaDocumentV1Item::cbor_decode(&bytes);
1468        assert!(matches!(result, Err(Error::SerdeCborError(_))));
1469
1470        Ok(())
1471    }
1472}