1use super::error::Error;
2
3pub mod cache;
4use cache::MetaCache;
5use alloy::primitives::{hex, keccak256};
6use futures::future;
7use graphql_client::GraphQLQuery;
8use rain_metadata_bindings::IDescribedByMetaV1;
9use reqwest::Client;
10use serde::de::{Deserialize, Deserializer, Visitor};
11use serde::ser::{Serialize, SerializeMap, Serializer};
12use std::{
13 collections::{BTreeSet, HashMap},
14 convert::TryFrom,
15 fmt::Debug,
16 sync::Arc,
17};
18use strum::{EnumIter, EnumString};
19use alloy::sol_types::private::Address;
20use alloy::providers::Provider;
21use alloy::contract::Error as ContractError;
22use rain_erc::erc165::{Erc165Error, IERC165, XorSelectors, supports_erc165};
23
24pub mod magic;
25pub(crate) mod normalize;
26pub(crate) mod query;
27pub mod types;
28
29pub use magic::*;
30pub use query::*;
31
32#[derive(Copy, Clone, EnumString, EnumIter, strum::Display, Debug, PartialEq)]
34#[strum(serialize_all = "kebab-case")]
35pub enum KnownMeta {
36 OpV1,
43 DotrainV1,
44 RainlangV1,
45 SolidityAbiV2,
46 AuthoringMetaV1,
47 AuthoringMetaV2,
48 InterpreterCallerMetaV1,
49 ExpressionDeployerV2BytecodeV1,
50 RainlangSourceV1,
51 AddressList,
52 DotrainSourceV1,
53 OrderBuilderStateV1,
54 RaindexSignedContextOracleV1,
55}
56
57impl TryFrom<KnownMagic> for KnownMeta {
58 type Error = Error;
59 fn try_from(value: KnownMagic) -> Result<Self, Self::Error> {
60 match value {
61 KnownMagic::DotrainV1 => Ok(KnownMeta::DotrainV1),
62 KnownMagic::RainlangV1 => Ok(KnownMeta::RainlangV1),
63 KnownMagic::SolidityAbiV2 => Ok(KnownMeta::SolidityAbiV2),
64 KnownMagic::OpMetaV1 => Ok(KnownMeta::OpV1),
65 KnownMagic::AuthoringMetaV1 => Ok(KnownMeta::AuthoringMetaV1),
66 KnownMagic::AuthoringMetaV2 => Ok(KnownMeta::AuthoringMetaV2),
67 KnownMagic::AddressList => Ok(KnownMeta::AddressList),
68 KnownMagic::InterpreterCallerMetaV1 => Ok(KnownMeta::InterpreterCallerMetaV1),
69 KnownMagic::DotrainSourceV1 => Ok(KnownMeta::DotrainSourceV1),
70 KnownMagic::OrderBuilderStateV1 => Ok(KnownMeta::OrderBuilderStateV1),
71 KnownMagic::ExpressionDeployerV2BytecodeV1 => {
72 Ok(KnownMeta::ExpressionDeployerV2BytecodeV1)
73 }
74 KnownMagic::RainlangSourceV1 => Ok(KnownMeta::RainlangSourceV1),
75 KnownMagic::RaindexSignedContextOracleV1 => Ok(KnownMeta::RaindexSignedContextOracleV1),
76 _ => Err(Error::UnsupportedMeta),
77 }
78 }
79}
80
81#[derive(
83 Copy,
84 Clone,
85 Debug,
86 EnumIter,
87 PartialEq,
88 EnumString,
89 strum::Display,
90 serde::Serialize,
91 serde::Deserialize,
92)]
93#[strum(serialize_all = "kebab-case")]
94pub enum ContentType {
95 None,
96 #[serde(rename = "application/json")]
97 Json,
98 #[serde(rename = "application/cbor")]
99 Cbor,
100 #[serde(rename = "application/octet-stream")]
101 OctetStream,
102}
103
104#[derive(
106 Copy,
107 Clone,
108 Debug,
109 EnumIter,
110 PartialEq,
111 EnumString,
112 strum::Display,
113 serde::Serialize,
114 serde::Deserialize,
115)]
116#[serde(rename_all = "kebab-case")]
117#[strum(serialize_all = "kebab-case")]
118pub enum ContentEncoding {
119 None,
120 Identity,
121 Deflate,
122}
123
124impl ContentEncoding {
125 pub fn encode(&self, data: &[u8]) -> Vec<u8> {
127 match self {
128 ContentEncoding::None | ContentEncoding::Identity => data.to_vec(),
129 ContentEncoding::Deflate => deflate::deflate_bytes_zlib(data),
130 }
131 }
132
133 pub fn decode(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
135 Ok(match self {
136 ContentEncoding::None | ContentEncoding::Identity => data.to_vec(),
137 ContentEncoding::Deflate => match inflate::inflate_bytes_zlib(data) {
138 Ok(v) => v,
139 Err(error) => match inflate::inflate_bytes(data) {
140 Ok(v) => v,
141 Err(_) => Err(Error::InflateError(error))?,
142 },
143 },
144 })
145 }
146}
147
148#[derive(
150 Copy,
151 Clone,
152 Debug,
153 EnumIter,
154 PartialEq,
155 EnumString,
156 strum::Display,
157 serde::Serialize,
158 serde::Deserialize,
159)]
160#[serde(rename_all = "kebab-case")]
161#[strum(serialize_all = "kebab-case")]
162pub enum ContentLanguage {
163 None,
164 En,
165}
166
167#[derive(PartialEq, Debug, Clone)]
171pub struct RainMetaDocumentV1Item {
172 pub payload: serde_bytes::ByteBuf,
173 pub magic: KnownMagic,
174 pub content_type: ContentType,
175 pub content_encoding: ContentEncoding,
176 pub content_language: ContentLanguage,
177 pub schema: Option<String>,
181}
182
183impl TryFrom<RainMetaDocumentV1Item> for String {
185 type Error = Error;
186 fn try_from(value: RainMetaDocumentV1Item) -> Result<Self, Self::Error> {
187 Ok(String::from_utf8(value.unpack()?)?)
188 }
189}
190
191impl TryFrom<RainMetaDocumentV1Item> for Vec<u8> {
193 type Error = Error;
194 fn try_from(value: RainMetaDocumentV1Item) -> Result<Self, Self::Error> {
195 value.unpack()
196 }
197}
198
199impl RainMetaDocumentV1Item {
200 fn len(&self) -> usize {
201 let mut l = 2;
202 if !matches!(self.content_type, ContentType::None) {
203 l += 1;
204 }
205 if !matches!(self.content_encoding, ContentEncoding::None) {
206 l += 1;
207 }
208 if !matches!(self.content_language, ContentLanguage::None) {
209 l += 1;
210 }
211 if self.schema.is_some() {
212 l += 1;
213 }
214 l
215 }
216
217 pub fn hash(&self, as_rain_meta_document: bool) -> Result<[u8; 32], Error> {
219 if as_rain_meta_document {
220 Ok(keccak256(Self::cbor_encode_seq(
221 &vec![self.clone()],
222 KnownMagic::RainMetaDocumentV1,
223 )?)
224 .0)
225 } else {
226 Ok(keccak256(self.cbor_encode()?).0)
227 }
228 }
229
230 pub fn cbor_encode(&self) -> Result<Vec<u8>, Error> {
232 let mut bytes: Vec<u8> = vec![];
233 Ok(serde_cbor::to_writer(&mut bytes, &self).map(|_| bytes)?)
234 }
235
236 pub fn cbor_encode_seq(
238 seq: &Vec<RainMetaDocumentV1Item>,
239 magic: KnownMagic,
240 ) -> Result<Vec<u8>, Error> {
241 let mut bytes: Vec<u8> = magic.to_prefix_bytes().to_vec();
242 for item in seq {
243 serde_cbor::to_writer(&mut bytes, &item)?;
244 }
245 Ok(bytes)
246 }
247
248 pub fn cbor_decode(data: &[u8]) -> Result<Vec<RainMetaDocumentV1Item>, Error> {
250 let mut metas: Vec<RainMetaDocumentV1Item> = vec![];
251 let mut consumed: usize = 0;
252 let mut is_rain_document_meta = false;
253 let mut len = data.len();
254 if data.starts_with(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes()) {
255 is_rain_document_meta = true;
256 len -= 8;
257 }
258 let mut deserializer = match is_rain_document_meta {
259 true => serde_cbor::Deserializer::from_slice(&data[8..]),
260 false => serde_cbor::Deserializer::from_slice(data),
261 };
262 while match Self::deserialize(&mut deserializer) {
265 Ok(meta) => {
266 consumed = deserializer.byte_offset();
267 metas.push(meta);
268 true
269 }
270 Err(error) => {
271 if error.is_eof() {
272 false
273 } else {
274 Err(Error::SerdeCborError(error))?
275 }
276 }
277 } {}
278
279 if metas.is_empty() || len != consumed {
280 Err(Error::CorruptMeta)?
281 }
282 Ok(metas)
283 }
284
285 pub fn unpack(&self) -> Result<Vec<u8>, Error> {
287 ContentEncoding::decode(&self.content_encoding, self.payload.as_ref())
288 }
289
290 pub fn unpack_into<T: TryFrom<Self, Error = Error>>(self) -> Result<T, Error> {
292 match self.magic {
293 KnownMagic::OpMetaV1
294 | KnownMagic::DotrainV1
295 | KnownMagic::RainlangV1
296 | KnownMagic::SolidityAbiV2
297 | KnownMagic::AuthoringMetaV1
298 | KnownMagic::AuthoringMetaV2
299 | KnownMagic::AddressList
300 | KnownMagic::InterpreterCallerMetaV1
301 | KnownMagic::ExpressionDeployerV2BytecodeV1
302 | KnownMagic::DotrainSourceV1
303 | KnownMagic::OrderBuilderStateV1
304 | KnownMagic::RainlangSourceV1
305 | KnownMagic::RaindexSignedContextOracleV1 => T::try_from(self),
306 _ => Err(Error::UnsupportedMeta)?,
307 }
308 }
309}
310
311impl Serialize for RainMetaDocumentV1Item {
312 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
313 let mut map = serializer.serialize_map(Some(self.len()))?;
314 map.serialize_entry(&0, &self.payload)?;
315 map.serialize_entry(&1, &(self.magic as u64))?;
316 match self.content_type {
317 ContentType::None => {}
318 content_type => map.serialize_entry(&2, &content_type)?,
319 }
320 match self.content_encoding {
321 ContentEncoding::None => {}
322 content_encoding => map.serialize_entry(&3, &content_encoding)?,
323 }
324 match self.content_language {
325 ContentLanguage::None => {}
326 content_language => map.serialize_entry(&4, &content_language)?,
327 }
328 if let Some(schema) = &self.schema {
329 map.serialize_entry(&(KnownMagic::OaSchema as u64), schema)?;
330 }
331 map.end()
332 }
333}
334
335impl<'de> Deserialize<'de> for RainMetaDocumentV1Item {
336 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
337 fn set_once<V, E: serde::de::Error>(
340 slot: &mut Option<V>,
341 value: V,
342 field: &'static str,
343 ) -> Result<(), E> {
344 if slot.is_some() {
345 return Err(serde::de::Error::duplicate_field(field));
346 }
347 *slot = Some(value);
348 Ok(())
349 }
350
351 struct EncodedMap;
352 impl<'de> Visitor<'de> for EncodedMap {
353 type Value = RainMetaDocumentV1Item;
354
355 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
356 formatter.write_str("rain meta cbor encoded bytes")
357 }
358
359 fn visit_map<T: serde::de::MapAccess<'de>>(
360 self,
361 mut map: T,
362 ) -> Result<Self::Value, T::Error> {
363 const OA_SCHEMA_KEY: u64 = KnownMagic::OaSchema as u64;
364 let mut payload = None;
365 let mut magic: Option<u64> = None;
366 let mut content_type = None;
367 let mut content_encoding = None;
368 let mut content_language = None;
369 let mut schema = None;
370 let mut unknown_keys: BTreeSet<u64> = BTreeSet::new();
374 while match map.next_key::<u64>() {
375 Ok(Some(key)) => {
376 match key {
377 0 => set_once(&mut payload, map.next_value()?, "payload")?,
378 1 => set_once(&mut magic, map.next_value()?, "magic number")?,
379 2 => set_once(&mut content_type, map.next_value()?, "content type")?,
380 3 => set_once(
381 &mut content_encoding,
382 map.next_value()?,
383 "content encoding",
384 )?,
385 4 => set_once(
386 &mut content_language,
387 map.next_value()?,
388 "content language",
389 )?,
390 OA_SCHEMA_KEY => set_once(&mut schema, map.next_value()?, "schema")?,
391 _ => {
399 if !unknown_keys.insert(key) {
400 return Err(serde::de::Error::custom(format!(
401 "duplicate map key: {key}"
402 )));
403 }
404 map.next_value::<serde::de::IgnoredAny>()?;
405 }
406 };
407 true
408 }
409 Ok(None) => false,
410 Err(error) => Err(error)?,
411 } {}
412 let payload = payload.ok_or_else(|| serde::de::Error::missing_field("payload"))?;
413 let magic = match magic
414 .ok_or_else(|| serde::de::Error::missing_field("magic number"))?
415 .try_into()
416 {
417 Ok(m) => m,
418 _ => Err(serde::de::Error::custom("unknown magic number"))?,
419 };
420 let content_type = content_type.unwrap_or(ContentType::None);
421 let content_encoding = content_encoding.unwrap_or(ContentEncoding::None);
422 let content_language = content_language.unwrap_or(ContentLanguage::None);
423
424 Ok(RainMetaDocumentV1Item {
425 payload,
426 magic,
427 content_type,
428 content_encoding,
429 content_language,
430 schema,
431 })
432 }
433 }
434 deserializer.deserialize_map(EncodedMap)
435 }
436}
437
438pub async fn search(hash: &str, subgraphs: &Vec<String>) -> Result<query::MetaResponse, Error> {
440 if subgraphs.is_empty() {
442 return Err(Error::NoRecordFound);
443 }
444 let request_body = query::MetaQuery::build_query(query::meta_query::Variables {
445 hash: Some(hash.to_ascii_lowercase()),
446 });
447 let mut promises = vec![];
448
449 let client = Arc::new(Client::builder().build().map_err(Error::ReqwestError)?);
450 for url in subgraphs {
451 promises.push(Box::pin(query::process_meta_query(
452 client.clone(),
453 &request_body,
454 url,
455 )));
456 }
457 let response_value = future::select_ok(promises.drain(..)).await?.0;
458 Ok(response_value)
459}
460
461pub async fn implements_i_described_by_meta_v1<P: Provider>(
469 provider: &P,
470 contract_address: Address,
471) -> Result<bool, Erc165Error> {
472 if !supports_erc165(provider, contract_address).await? {
473 return Ok(false);
474 }
475
476 let interface_id_res = IDescribedByMetaV1::IDescribedByMetaV1Calls::xor_selectors();
477 if interface_id_res.is_err() {
478 return Ok(false);
479 }
480
481 match IERC165::new(contract_address, provider)
482 .supportsInterface(interface_id_res.unwrap().into())
483 .call()
484 .await
485 {
486 Ok(supported) => Ok(supported),
487 Err(error)
488 if error.as_revert_data().is_some()
489 || matches!(error, ContractError::ZeroData(_, _)) =>
490 {
491 Ok(false)
492 }
493 Err(error) => Err(error.into()),
494 }
495}
496
497#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
552pub struct Store {
553 subgraphs: Vec<String>,
554 cache: MetaCache,
555 dotrain_cache: HashMap<String, Vec<u8>>,
556}
557
558impl Default for Store {
559 fn default() -> Self {
560 Store::new()
561 }
562}
563
564impl Store {
565 pub fn new() -> Store {
568 Store {
569 subgraphs: vec![],
570 cache: MetaCache::default(),
571 dotrain_cache: HashMap::new(),
572 }
573 }
574
575 pub fn create(
578 subgraphs: &Vec<String>,
579 cache: &MetaCache,
580 dotrain_cache: &HashMap<String, Vec<u8>>,
581 ) -> Store {
582 let mut store = Store::new();
583 store.add_subgraphs(subgraphs);
584 for (hash, bytes) in cache.iter() {
585 let _ = store.update_with(hash, bytes);
586 }
587 for (uri, hash) in dotrain_cache {
588 if !store.dotrain_cache.contains_key(uri) && store.cache.contains_key(hash) {
589 store.dotrain_cache.insert(uri.clone(), hash.clone());
590 }
591 }
592 store
593 }
594
595 pub fn subgraphs(&self) -> &Vec<String> {
597 &self.subgraphs
598 }
599
600 pub fn add_subgraphs(&mut self, subgraphs: &Vec<String>) {
602 for sg in subgraphs {
603 if !self.subgraphs.contains(sg) {
604 self.subgraphs.push(sg.to_string());
605 }
606 }
607 }
608
609 pub fn cache(&self) -> &MetaCache {
611 &self.cache
612 }
613
614 pub fn get_meta(&self, hash: &[u8]) -> Option<&Vec<u8>> {
616 self.cache.get(hash)
617 }
618
619 pub fn dotrain_cache(&self) -> &HashMap<String, Vec<u8>> {
621 &self.dotrain_cache
622 }
623
624 pub fn get_dotrain_hash(&self, uri: &str) -> Option<&Vec<u8>> {
626 self.dotrain_cache.get(uri)
627 }
628
629 pub fn get_dotrain_uri(&self, hash: &[u8]) -> Option<&String> {
631 for (uri, h) in &self.dotrain_cache {
632 if h == hash {
633 return Some(uri);
634 }
635 }
636 None
637 }
638
639 pub fn get_dotrain_meta(&self, uri: &str) -> Option<&Vec<u8>> {
641 self.get_meta(self.dotrain_cache.get(uri)?)
642 }
643
644 pub fn delete_dotrain(&mut self, uri: &str, keep_meta: bool) {
646 if let Some(kv) = self.dotrain_cache.remove_entry(uri) {
647 if !keep_meta {
648 self.cache.remove(&kv.1);
649 }
650 };
651 }
652
653 pub fn merge(&mut self, other: &Store) {
656 self.add_subgraphs(&other.subgraphs);
657 for (hash, bytes) in other.cache.iter() {
658 if !self.cache.contains_key(hash) {
659 let _ = self.cache.insert_verified(hash, bytes.clone());
662 }
663 }
664 for (uri, hash) in &other.dotrain_cache {
665 if !self.dotrain_cache.contains_key(uri) {
666 self.dotrain_cache.insert(uri.clone(), hash.clone());
667 }
668 }
669 }
670
671 fn insert_verified(&mut self, hash: &[u8], bytes: Vec<u8>) -> Result<&Vec<u8>, Error> {
675 self.cache.insert_verified(hash, bytes.clone())?;
676 self.store_content(&bytes);
677 self.get_meta(hash).ok_or(Error::NoRecordFound)
678 }
679
680 pub async fn update(&mut self, hash: &[u8]) -> Result<&Vec<u8>, Error> {
685 let meta = search(&hex::encode_prefixed(hash), &self.subgraphs).await?;
686 self.insert_verified(hash, meta.bytes)
687 }
688
689 pub async fn update_check(&mut self, hash: &[u8]) -> Result<&Vec<u8>, Error> {
691 if self.cache.contains_key(hash) {
697 return self.get_meta(hash).ok_or(Error::NoRecordFound);
698 }
699 self.update(hash).await
700 }
701
702 pub fn update_with(&mut self, hash: &[u8], bytes: &[u8]) -> Result<&Vec<u8>, Error> {
706 if self.cache.contains_key(hash) {
712 return self.get_meta(hash).ok_or(Error::NoRecordFound);
713 }
714 self.insert_verified(hash, bytes.to_vec())
715 }
716
717 pub fn set_dotrain(
722 &mut self,
723 text: &str,
724 uri: &str,
725 keep_old: bool,
726 ) -> Result<(Vec<u8>, Vec<u8>), Error> {
727 let bytes = RainMetaDocumentV1Item {
728 payload: serde_bytes::ByteBuf::from(text.as_bytes()),
729 magic: KnownMagic::DotrainV1,
730 content_type: ContentType::OctetStream,
731 content_encoding: ContentEncoding::None,
732 content_language: ContentLanguage::None,
733 schema: None,
734 }
735 .cbor_encode()?;
736 let new_hash = keccak256(&bytes).0.to_vec();
737 if let Some(h) = self.dotrain_cache.get(uri) {
738 let old_hash = h.clone();
739 if new_hash == old_hash {
740 self.cache.insert_verified(&new_hash, bytes)?;
741 Ok((new_hash, vec![]))
742 } else {
743 self.cache.insert_verified(&new_hash, bytes)?;
744 self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
745 if !keep_old {
746 self.cache.remove(&old_hash);
747 }
748 Ok((new_hash, old_hash))
749 }
750 } else {
751 self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
752 self.cache.insert_verified(&new_hash, bytes)?;
753 Ok((new_hash, vec![]))
754 }
755 }
756
757 fn store_content(&mut self, bytes: &[u8]) {
761 if let Ok(meta_maps) = RainMetaDocumentV1Item::cbor_decode(bytes) {
762 if bytes.starts_with(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes()) {
763 for meta_map in &meta_maps {
764 if let Ok(encoded_bytes) = meta_map.cbor_encode() {
765 let _ = self
769 .cache
770 .insert_verified(&keccak256(&encoded_bytes).0, encoded_bytes);
771 }
772 }
773 }
774 }
775 }
776}
777
778pub fn str_to_bytes32(text: &str) -> Result<[u8; 32], Error> {
784 let bytes: &[u8] = text.as_bytes();
785 if bytes.len() > 32 {
786 return Err(Error::BiggerThan32Bytes);
787 }
788 if bytes.contains(&0u8) {
789 return Err(Error::NulByteInInput);
790 }
791 let mut b32 = [0u8; 32];
792 b32[..bytes.len()].copy_from_slice(bytes);
793 Ok(b32)
794}
795
796pub fn bytes32_to_str(bytes: &[u8; 32]) -> Result<&str, Error> {
798 let mut len = 32;
799 if let Some((pos, _)) = itertools::Itertools::find_position(&mut bytes.iter(), |b| **b == 0u8) {
800 len = pos;
801 };
802 Ok(std::str::from_utf8(&bytes[..len])?)
803}
804
805#[cfg(all(test, not(target_family = "wasm")))]
806mod tests {
807 use super::{
808 *, bytes32_to_str, magic::KnownMagic, str_to_bytes32, types::authoring::v1::AuthoringMeta,
809 ContentEncoding, ContentLanguage, ContentType, Error, RainMetaDocumentV1Item,
810 };
811 use alloy::providers::ProviderBuilder;
812 use alloy::{providers::mock::Asserter, rpc::json_rpc::ErrorPayload, sol_types::SolType};
813 use serde_json::json;
814
815 #[test]
818 fn authoring_meta_roundtrip() -> Result<(), Error> {
819 let authoring_meta_content = r#"[
820 {
821 "word": "stack",
822 "description": "Copies an existing value from the stack.",
823 "operandParserOffset": 16
824 },
825 {
826 "word": "constant",
827 "description": "Copies a constant value onto the stack.",
828 "operandParserOffset": 16
829 }
830 ]"#;
831 let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
832
833 let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
835 let expected_abi_encoded = <alloy::sol!((bytes32, uint8, string)[])>::abi_encode(&vec![
836 (
837 str_to_bytes32("stack")?,
838 16u8,
839 "Copies an existing value from the stack.".to_string(),
840 ),
841 (
842 str_to_bytes32("constant")?,
843 16u8,
844 "Copies a constant value onto the stack.".to_string(),
845 ),
846 ]);
847 assert_eq!(authoring_meta_abi_encoded, expected_abi_encoded);
849
850 let meta_map = RainMetaDocumentV1Item {
851 payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
852 magic: KnownMagic::AuthoringMetaV1,
853 content_type: ContentType::Cbor,
854 content_encoding: ContentEncoding::None,
855 content_language: ContentLanguage::None,
856 schema: None,
857 };
858 let cbor_encoded = meta_map.cbor_encode()?;
859
860 assert_eq!(cbor_encoded[0], 0xa3);
862 assert_eq!(cbor_encoded[1], 0x00);
864 assert_eq!(cbor_encoded[2], 0b010_11001);
866 assert_eq!(cbor_encoded[3], 0b000_00010);
867 assert_eq!(cbor_encoded[4], 0b000_00000);
868 assert_eq!(cbor_encoded[5..517], authoring_meta_abi_encoded);
870 assert_eq!(cbor_encoded[517], 0x01);
872 assert_eq!(cbor_encoded[518], 0b000_11011);
874 assert_eq!(
876 &cbor_encoded[519..527],
877 KnownMagic::AuthoringMetaV1.to_prefix_bytes()
878 );
879 assert_eq!(cbor_encoded[527], 0x02);
881 assert_eq!(cbor_encoded[528], 0b011_10000);
883 assert_eq!(&cbor_encoded[529..], "application/cbor".as_bytes());
885
886 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
888 assert_eq!(cbor_decoded.len(), 1);
890 assert_eq!(cbor_decoded[0], meta_map);
892
893 Ok(())
894 }
895
896 #[test]
899 fn dotrain_meta_roundtrip() -> Result<(), Error> {
900 let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
901 let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
902
903 let content_encoding = ContentEncoding::Deflate;
904 let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
905
906 let meta_map = RainMetaDocumentV1Item {
907 payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
908 magic: KnownMagic::DotrainV1,
909 content_type: ContentType::OctetStream,
910 content_encoding,
911 content_language: ContentLanguage::En,
912 schema: None,
913 };
914 let cbor_encoded = meta_map.cbor_encode()?;
915
916 assert_eq!(cbor_encoded[0], 0xa5);
918 assert_eq!(cbor_encoded[1], 0x00);
920 assert_eq!(cbor_encoded[2], 0b010_11000);
922 assert_eq!(cbor_encoded[3], 0b001_00100);
923 assert_eq!(cbor_encoded[4..40], deflated_payload);
926 assert_eq!(cbor_encoded[40], 0x01);
928 assert_eq!(cbor_encoded[41], 0b000_11011);
930 assert_eq!(
932 &cbor_encoded[42..50],
933 KnownMagic::DotrainV1.to_prefix_bytes()
934 );
935 assert_eq!(cbor_encoded[50], 0x02);
937 assert_eq!(cbor_encoded[51], 0b011_11000);
939 assert_eq!(cbor_encoded[52], 0b000_11000);
940 assert_eq!(&cbor_encoded[53..77], "application/octet-stream".as_bytes());
942 assert_eq!(cbor_encoded[77], 0x03);
944 assert_eq!(cbor_encoded[78], 0b011_00111);
946 assert_eq!(&cbor_encoded[79..86], "deflate".as_bytes());
948 assert_eq!(cbor_encoded[86], 0x04);
950 assert_eq!(cbor_encoded[87], 0b011_00010);
952 assert_eq!(&cbor_encoded[88..], "en".as_bytes());
954
955 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
957 assert_eq!(cbor_decoded.len(), 1);
959 assert_eq!(cbor_decoded[0], meta_map);
961
962 Ok(())
963 }
964
965 #[test]
968 fn meta_seq_roundtrip() -> Result<(), Error> {
969 let authoring_meta_content = r#"[
970 {
971 "word": "stack",
972 "description": "Copies an existing value from the stack.",
973 "operandParserOffset": 16
974 },
975 {
976 "word": "constant",
977 "description": "Copies a constant value onto the stack.",
978 "operandParserOffset": 16
979 }
980 ]"#;
981 let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
982 let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
983 let meta_map_1 = 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
992 let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
993 let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
994 let content_encoding = ContentEncoding::Deflate;
995 let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
996 let meta_map_2 = RainMetaDocumentV1Item {
997 payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
998 magic: KnownMagic::DotrainV1,
999 content_type: ContentType::OctetStream,
1000 content_encoding,
1001 content_language: ContentLanguage::En,
1002 schema: None,
1003 };
1004
1005 let cbor_encoded = RainMetaDocumentV1Item::cbor_encode_seq(
1007 &vec![meta_map_1.clone(), meta_map_2.clone()],
1008 KnownMagic::RainMetaDocumentV1,
1009 )?;
1010
1011 assert_eq!(
1013 &cbor_encoded[0..8],
1014 KnownMagic::RainMetaDocumentV1.to_prefix_bytes()
1015 );
1016
1017 assert_eq!(cbor_encoded[8], 0xa3);
1020 assert_eq!(cbor_encoded[9], 0x00);
1022 assert_eq!(cbor_encoded[10], 0b010_11001);
1024 assert_eq!(cbor_encoded[11], 0b000_00010);
1025 assert_eq!(cbor_encoded[12], 0b000_00000);
1026 assert_eq!(cbor_encoded[13..525], authoring_meta_abi_encoded);
1028 assert_eq!(cbor_encoded[525], 0x01);
1030 assert_eq!(cbor_encoded[526], 0b000_11011);
1032 assert_eq!(
1034 &cbor_encoded[527..535],
1035 KnownMagic::AuthoringMetaV1.to_prefix_bytes()
1036 );
1037 assert_eq!(cbor_encoded[535], 0x02);
1039 assert_eq!(cbor_encoded[536], 0b011_10000);
1041 assert_eq!(&cbor_encoded[537..553], "application/cbor".as_bytes());
1043
1044 assert_eq!(cbor_encoded[553], 0xa5);
1047 assert_eq!(cbor_encoded[554], 0x00);
1049 assert_eq!(cbor_encoded[555], 0b010_11000);
1051 assert_eq!(cbor_encoded[556], 0b001_00100);
1052 assert_eq!(cbor_encoded[557..593], deflated_payload);
1055 assert_eq!(cbor_encoded[593], 0x01);
1057 assert_eq!(cbor_encoded[594], 0b000_11011);
1059 assert_eq!(
1061 &cbor_encoded[595..603],
1062 KnownMagic::DotrainV1.to_prefix_bytes()
1063 );
1064 assert_eq!(cbor_encoded[603], 0x02);
1066 assert_eq!(cbor_encoded[604], 0b011_11000);
1068 assert_eq!(cbor_encoded[605], 0b000_11000);
1069 assert_eq!(
1071 &cbor_encoded[606..630],
1072 "application/octet-stream".as_bytes()
1073 );
1074 assert_eq!(cbor_encoded[630], 0x03);
1076 assert_eq!(cbor_encoded[631], 0b011_00111);
1078 assert_eq!(&cbor_encoded[632..639], "deflate".as_bytes());
1080 assert_eq!(cbor_encoded[639], 0x04);
1082 assert_eq!(cbor_encoded[640], 0b011_00010);
1084 assert_eq!(&cbor_encoded[641..], "en".as_bytes());
1086
1087 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1089 assert_eq!(cbor_decoded.len(), 2);
1091
1092 assert_eq!(cbor_decoded[0], meta_map_1);
1094 assert_eq!(cbor_decoded[1], meta_map_2);
1096
1097 Ok(())
1098 }
1099
1100 #[test]
1101 fn test_bytes32_to_str() {
1102 let text_bytes_list = vec![
1103 (
1104 "",
1105 hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1106 ),
1107 (
1108 "A",
1109 hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1110 ),
1111 (
1112 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1113 hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1114 ),
1115 (
1116 "!@#$%^&*(),./;'[]",
1117 hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1118 ),
1119 ];
1120
1121 for (text, bytes) in text_bytes_list {
1122 assert_eq!(text, bytes32_to_str(&bytes).unwrap());
1123 }
1124 }
1125
1126 #[test]
1127 fn test_str_to_bytes32() {
1128 let text_bytes_list = vec![
1129 (
1130 "",
1131 hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1132 ),
1133 (
1134 "A",
1135 hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1136 ),
1137 (
1138 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1139 hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1140 ),
1141 (
1142 "!@#$%^&*(),./;'[]",
1143 hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1144 ),
1145 ];
1146
1147 for (text, bytes) in text_bytes_list {
1148 assert_eq!(bytes, str_to_bytes32(text).unwrap());
1149 }
1150 }
1151
1152 #[test]
1153 fn test_str_to_bytes32_long() {
1154 assert!(matches!(
1155 str_to_bytes32("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456").unwrap_err(),
1156 Error::BiggerThan32Bytes
1157 ));
1158 }
1159
1160 #[test]
1164 fn test_str_to_bytes32_rejects_nul() {
1165 for text in [
1166 "\0",
1167 "\0a",
1168 "a\0",
1169 "a\0b",
1170 "abcdefghijklmnopqrstuvwxyz01234\0",
1171 ] {
1172 assert!(
1173 matches!(str_to_bytes32(text), Err(Error::NulByteInInput)),
1174 "nul bearing input {:?} accepted",
1175 text
1176 );
1177 }
1178 }
1179
1180 #[test]
1183 fn test_str_to_bytes32_round_trip() -> Result<(), Error> {
1184 let mut seen: Vec<[u8; 32]> = vec![];
1185 for text in [
1186 "",
1187 "a",
1188 "stack",
1189 "!@#$%^&*(),./;'[]",
1190 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1191 ] {
1192 let bytes = str_to_bytes32(text)?;
1193 assert_eq!(bytes32_to_str(&bytes)?, text);
1194 assert!(!seen.contains(&bytes), "input {:?} collided", text);
1195 seen.push(bytes);
1196 }
1197 Ok(())
1198 }
1199
1200 #[tokio::test]
1201 async fn test_implements_i_describe_by_meta_v1() {
1202 async fn new_server_client() -> (Asserter, impl Provider) {
1204 let asserter = Asserter::new();
1205 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
1206
1207 asserter.push_success(
1209 &"0x0000000000000000000000000000000000000000000000000000000000000001",
1210 );
1211 asserter.push_success(
1212 &"0x0000000000000000000000000000000000000000000000000000000000000000",
1213 );
1214
1215 (asserter, provider)
1216 }
1217
1218 let address = Address::random();
1219
1220 let (asserter, provider) = new_server_client().await;
1222 asserter
1223 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
1224 let result = implements_i_described_by_meta_v1(&provider, address)
1225 .await
1226 .unwrap();
1227 assert!(result);
1228
1229 let (asserter, provider) = new_server_client().await;
1231 asserter
1232 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
1233 let result = implements_i_described_by_meta_v1(&provider, address)
1234 .await
1235 .unwrap();
1236 assert!(!result);
1237
1238 let (asserter, provider) = new_server_client().await;
1240 asserter.push_failure(ErrorPayload {
1241 code: -32003,
1242 message: "execution reverted".into(),
1243 data: Some(serde_json::value::to_raw_value(&json!("0x00")).unwrap()),
1244 });
1245 let result = implements_i_described_by_meta_v1(&provider, address)
1246 .await
1247 .unwrap();
1248 assert!(!result);
1249 }
1250
1251 #[test]
1255 fn oa_schema_map_key_roundtrip() -> Result<(), Error> {
1256 let payload = vec![0x01, 0x02, 0x03];
1257 let schema = "QmSchemaHash1234567890abcdefghijklmnopqrstuvwx".to_string();
1260 assert_eq!(schema.len(), 46);
1261
1262 let meta_map = RainMetaDocumentV1Item {
1263 payload: serde_bytes::ByteBuf::from(payload.clone()),
1264 magic: KnownMagic::OaStructure,
1265 content_type: ContentType::Json,
1266 content_encoding: ContentEncoding::Deflate,
1267 content_language: ContentLanguage::None,
1268 schema: Some(schema.clone()),
1269 };
1270 let cbor_encoded = meta_map.cbor_encode()?;
1271
1272 assert_eq!(cbor_encoded[0], 0xa5);
1274 assert_eq!(cbor_encoded[1], 0x00);
1276 assert_eq!(cbor_encoded[2], 0b010_00011);
1278 assert_eq!(cbor_encoded[3..6], payload);
1280 assert_eq!(cbor_encoded[6], 0x01);
1282 assert_eq!(cbor_encoded[7], 0b000_11011);
1284 assert_eq!(
1286 &cbor_encoded[8..16],
1287 KnownMagic::OaStructure.to_prefix_bytes()
1288 );
1289 assert_eq!(cbor_encoded[16], 0x02);
1291 assert_eq!(cbor_encoded[17], 0b011_10000);
1293 assert_eq!(&cbor_encoded[18..34], "application/json".as_bytes());
1294 assert_eq!(cbor_encoded[34], 0x03);
1296 assert_eq!(cbor_encoded[35], 0b011_00111);
1298 assert_eq!(&cbor_encoded[36..43], "deflate".as_bytes());
1299 assert_eq!(cbor_encoded[43], 0b000_11011);
1301 assert_eq!(
1302 &cbor_encoded[44..52],
1303 KnownMagic::OaSchema.to_prefix_bytes()
1304 );
1305 assert_eq!(cbor_encoded[52], 0b011_11000);
1307 assert_eq!(cbor_encoded[53], 46);
1308 assert_eq!(&cbor_encoded[54..], schema.as_bytes());
1310
1311 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1313 assert_eq!(cbor_decoded.len(), 1);
1315 assert_eq!(cbor_decoded[0], meta_map);
1317
1318 Ok(())
1319 }
1320
1321 #[test]
1324 fn no_schema_key_encodes_as_before_roundtrip() -> Result<(), Error> {
1325 let payload = vec![0x0a, 0x0b];
1326 let meta_map = RainMetaDocumentV1Item {
1327 payload: serde_bytes::ByteBuf::from(payload.clone()),
1328 magic: KnownMagic::OaStructure,
1329 content_type: ContentType::None,
1330 content_encoding: ContentEncoding::None,
1331 content_language: ContentLanguage::None,
1332 schema: None,
1333 };
1334 let cbor_encoded = meta_map.cbor_encode()?;
1335
1336 assert_eq!(cbor_encoded[0], 0xa2);
1338 assert_eq!(cbor_encoded[1], 0x00);
1340 assert_eq!(cbor_encoded[2], 0b010_00010);
1342 assert_eq!(cbor_encoded[3..5], payload);
1344 assert_eq!(cbor_encoded[5], 0x01);
1346 assert_eq!(cbor_encoded[6], 0b000_11011);
1348 assert_eq!(
1350 &cbor_encoded[7..],
1351 KnownMagic::OaStructure.to_prefix_bytes()
1352 );
1353
1354 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1355 assert_eq!(cbor_decoded.len(), 1);
1356 assert_eq!(cbor_decoded[0], meta_map);
1357
1358 Ok(())
1359 }
1360
1361 #[test]
1364 fn unknown_map_key_index_is_ignored() -> Result<(), Error> {
1365 let mut bytes: Vec<u8> = vec![
1366 0xa3, 0x00, 0x40, 0x01, 0x1b,
1370 ];
1371 bytes.extend_from_slice(&KnownMagic::DotrainSourceV1.to_prefix_bytes());
1372 bytes.extend_from_slice(&[0x05, 0x07]);
1374
1375 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1376 assert_eq!(decoded.len(), 1);
1377 assert_eq!(decoded[0], plain_item(KnownMagic::DotrainSourceV1, vec![]));
1378
1379 Ok(())
1380 }
1381
1382 #[test]
1385 fn non_oa_schema_extra_map_key_is_ignored() -> Result<(), Error> {
1386 let mut bytes: Vec<u8> = vec![
1389 0xa3, 0x00, 0x41, 0xff, 0x01, 0x1b,
1393 ];
1394 bytes.extend_from_slice(&KnownMagic::OaStructure.to_prefix_bytes());
1395 bytes.push(0x1b);
1397 bytes.extend_from_slice(&KnownMagic::OaHashList.to_prefix_bytes());
1398 bytes.extend_from_slice(&[0x62, 0x68, 0x69]);
1400
1401 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1402 assert_eq!(decoded.len(), 1);
1403 let expected = plain_item(KnownMagic::OaStructure, vec![0xff]);
1404 assert_eq!(decoded[0], expected);
1405 assert_eq!(decoded[0].schema, None);
1406
1407 Ok(())
1408 }
1409
1410 #[test]
1413 fn unknown_map_key_consumes_its_whole_value() -> Result<(), Error> {
1414 let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1415 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1416 bytes.extend_from_slice(&[0x05, 0xa1, 0x18, 0x2a, 0x82, 0x01, 0x02]);
1418 bytes.extend_from_slice(&handwritten_map());
1419
1420 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1421 assert_eq!(decoded.len(), 2);
1422 let expected = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1423 assert_eq!(decoded[0], expected);
1424 assert_eq!(decoded[1], expected);
1425
1426 Ok(())
1427 }
1428
1429 #[test]
1432 fn ignored_map_key_is_absent_from_the_reencoding() -> Result<(), Error> {
1433 let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1434 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1435 bytes.extend_from_slice(&[0x05, 0x07]);
1436
1437 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1438 assert_eq!(decoded[0].cbor_encode()?, handwritten_map());
1439 assert_eq!(decoded[0].hash(false)?, keccak256(handwritten_map()).0);
1440 assert_ne!(decoded[0].hash(false)?, keccak256(&bytes).0);
1441
1442 Ok(())
1443 }
1444
1445 #[test]
1449 fn non_integer_map_key_errors() {
1450 let mut text_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1451 text_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1452 text_key.extend_from_slice(&[0x61, 0x35, 0x07]);
1454 assert!(matches!(
1455 RainMetaDocumentV1Item::cbor_decode(&text_key),
1456 Err(Error::SerdeCborError(_))
1457 ));
1458
1459 let mut negative_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1460 negative_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1461 negative_key.extend_from_slice(&[0x20, 0x07]);
1463 assert!(matches!(
1464 RainMetaDocumentV1Item::cbor_decode(&negative_key),
1465 Err(Error::SerdeCborError(_))
1466 ));
1467 }
1468
1469 #[test]
1471 fn unknown_map_key_does_not_stand_in_for_a_mandatory_key() {
1472 let mut bytes: Vec<u8> = vec![0xa2, 0x05, 0x07, 0x01, 0x1b];
1473 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1474 assert!(matches!(
1475 RainMetaDocumentV1Item::cbor_decode(&bytes),
1476 Err(Error::SerdeCborError(_))
1477 ));
1478 }
1479
1480 fn plain_item(magic: KnownMagic, payload: Vec<u8>) -> RainMetaDocumentV1Item {
1481 RainMetaDocumentV1Item {
1482 payload: serde_bytes::ByteBuf::from(payload),
1483 magic,
1484 content_type: ContentType::None,
1485 content_encoding: ContentEncoding::None,
1486 content_language: ContentLanguage::None,
1487 schema: None,
1488 }
1489 }
1490
1491 fn sample_authoring_doc() -> (AuthoringMeta, Vec<u8>) {
1494 let authoring_meta: AuthoringMeta = serde_json::from_str(
1495 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
1496 )
1497 .unwrap();
1498 let abi = authoring_meta.abi_encode_validate().unwrap();
1499 let item = RainMetaDocumentV1Item {
1500 payload: serde_bytes::ByteBuf::from(abi),
1501 magic: KnownMagic::AuthoringMetaV1,
1502 content_type: ContentType::Cbor,
1503 content_encoding: ContentEncoding::None,
1504 content_language: ContentLanguage::None,
1505 schema: None,
1506 };
1507 let doc =
1508 RainMetaDocumentV1Item::cbor_encode_seq(&vec![item], KnownMagic::RainMetaDocumentV1)
1509 .unwrap();
1510 (authoring_meta, doc)
1511 }
1512
1513 fn sample_dotrain_item() -> RainMetaDocumentV1Item {
1514 RainMetaDocumentV1Item {
1515 payload: serde_bytes::ByteBuf::from("some dotrain body".as_bytes()),
1516 magic: KnownMagic::DotrainV1,
1517 content_type: ContentType::OctetStream,
1518 content_encoding: ContentEncoding::None,
1519 content_language: ContentLanguage::None,
1520 schema: None,
1521 }
1522 }
1523
1524 fn handwritten_map() -> Vec<u8> {
1527 vec![
1528 0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b, 0xff, 0xda, 0xc2, 0xf2, 0xf3, 0x7b, 0xe8, 0x94, ]
1534 }
1535
1536 #[test]
1540 fn test_hash_bare_vs_document() -> Result<(), Error> {
1541 let map_bytes = handwritten_map();
1542 let mut doc_bytes: Vec<u8> = vec![0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74];
1543 doc_bytes.extend_from_slice(&map_bytes);
1544
1545 let item = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1546 assert_eq!(item.hash(false)?, keccak256(&map_bytes).0);
1547 assert_eq!(item.hash(true)?, keccak256(&doc_bytes).0);
1548 assert_ne!(item.hash(false)?, item.hash(true)?);
1549 Ok(())
1550 }
1551
1552 #[test]
1554 fn test_cbor_decode_empty_is_corrupt() {
1555 assert!(matches!(
1556 RainMetaDocumentV1Item::cbor_decode(&[]),
1557 Err(Error::CorruptMeta)
1558 ));
1559 let prefix = KnownMagic::RainMetaDocumentV1.to_prefix_bytes();
1560 assert!(matches!(
1561 RainMetaDocumentV1Item::cbor_decode(&prefix),
1562 Err(Error::CorruptMeta)
1563 ));
1564 }
1565
1566 #[test]
1569 fn test_cbor_decode_trailing_truncated_is_corrupt() {
1570 let mut bytes = handwritten_map();
1571 bytes.push(0x1b); assert!(matches!(
1573 RainMetaDocumentV1Item::cbor_decode(&bytes),
1574 Err(Error::CorruptMeta)
1575 ));
1576 }
1577
1578 #[test]
1582 fn test_cbor_decode_truncated_item_is_corrupt() {
1583 let mut sole = handwritten_map();
1584 sole.pop();
1585 assert!(matches!(
1586 RainMetaDocumentV1Item::cbor_decode(&sole),
1587 Err(Error::CorruptMeta)
1588 ));
1589
1590 assert!(matches!(
1591 RainMetaDocumentV1Item::cbor_decode(&[0xa2, 0x00, 0x41, 0x01]),
1592 Err(Error::CorruptMeta)
1593 ));
1594
1595 let mut after_complete = handwritten_map();
1596 after_complete.extend_from_slice(&[0xa2, 0x00]);
1597 assert!(matches!(
1598 RainMetaDocumentV1Item::cbor_decode(&after_complete),
1599 Err(Error::CorruptMeta)
1600 ));
1601 }
1602
1603 #[test]
1606 fn test_cbor_decode_trailing_garbage_errors() {
1607 let mut bytes = handwritten_map();
1608 bytes.push(0xff); assert!(matches!(
1610 RainMetaDocumentV1Item::cbor_decode(&bytes),
1611 Err(Error::SerdeCborError(_))
1612 ));
1613 }
1614
1615 #[test]
1617 fn test_cbor_decode_missing_payload_errors() {
1618 let mut bytes: Vec<u8> = vec![0xa1, 0x01, 0x1b]; bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1620 assert!(matches!(
1621 RainMetaDocumentV1Item::cbor_decode(&bytes),
1622 Err(Error::SerdeCborError(_))
1623 ));
1624 }
1625
1626 #[test]
1628 fn test_cbor_decode_missing_magic_errors() {
1629 let bytes: Vec<u8> = vec![0xa1, 0x00, 0x41, 0x01]; assert!(matches!(
1631 RainMetaDocumentV1Item::cbor_decode(&bytes),
1632 Err(Error::SerdeCborError(_))
1633 ));
1634 }
1635
1636 #[test]
1638 fn test_cbor_decode_unknown_magic_errors() {
1639 let mut bytes: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1640 bytes.extend_from_slice(&0xdeadbeefdeadbeefu64.to_be_bytes());
1641 assert!(matches!(
1642 RainMetaDocumentV1Item::cbor_decode(&bytes),
1643 Err(Error::SerdeCborError(_))
1644 ));
1645 }
1646
1647 fn handwritten_entries(entries: &[(Vec<u8>, Vec<u8>)]) -> Vec<u8> {
1651 assert!(entries.len() < 24);
1652 let mut bytes = vec![0xa0 | entries.len() as u8];
1653 for (key, value) in entries {
1654 bytes.extend_from_slice(key);
1655 bytes.extend_from_slice(value);
1656 }
1657 bytes
1658 }
1659
1660 fn handwritten_magic(magic: KnownMagic) -> Vec<u8> {
1662 let mut bytes = vec![0x1b];
1663 bytes.extend_from_slice(&magic.to_prefix_bytes());
1664 bytes
1665 }
1666
1667 fn handwritten_text(text: &str) -> Vec<u8> {
1669 assert!(text.len() < 24);
1670 let mut bytes = vec![0x60 | text.len() as u8];
1671 bytes.extend_from_slice(text.as_bytes());
1672 bytes
1673 }
1674
1675 #[test]
1678 fn test_cbor_decode_duplicate_payload_key_errors() {
1679 let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x00, 0x41, 0x02, 0x01, 0x1b];
1680 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1681 let error = RainMetaDocumentV1Item::cbor_decode(&bytes).unwrap_err();
1682 assert!(matches!(error, Error::SerdeCborError(_)));
1683 assert!(
1684 error.to_string().contains("duplicate field `payload`"),
1685 "{error}"
1686 );
1687 }
1688
1689 #[test]
1692 fn test_cbor_decode_duplicate_any_key_errors() -> Result<(), Error> {
1693 let cases: [(Vec<u8>, Vec<u8>, Vec<u8>); 6] = [
1694 (vec![0x00], vec![0x41, 0x01], vec![0x41, 0x02]),
1695 (
1696 vec![0x01],
1697 handwritten_magic(KnownMagic::DotrainV1),
1698 handwritten_magic(KnownMagic::RainlangV1),
1699 ),
1700 (
1701 vec![0x02],
1702 handwritten_text("application/cbor"),
1703 handwritten_text("application/json"),
1704 ),
1705 (
1706 vec![0x03],
1707 handwritten_text("identity"),
1708 handwritten_text("deflate"),
1709 ),
1710 (vec![0x04], handwritten_text("en"), handwritten_text("none")),
1711 (
1712 handwritten_magic(KnownMagic::OaSchema),
1713 handwritten_text("hi"),
1714 handwritten_text("bye"),
1715 ),
1716 ];
1717 let base: Vec<(Vec<u8>, Vec<u8>)> = cases
1718 .iter()
1719 .map(|(key, value, _)| (key.clone(), value.clone()))
1720 .collect();
1721
1722 let decoded = RainMetaDocumentV1Item::cbor_decode(&handwritten_entries(&base))?;
1723 assert_eq!(decoded.len(), 1);
1724 assert_eq!(decoded[0].payload.as_ref(), &[0x01]);
1725 assert_eq!(decoded[0].magic, KnownMagic::DotrainV1);
1726 assert_eq!(decoded[0].content_type, ContentType::Cbor);
1727 assert_eq!(decoded[0].content_encoding, ContentEncoding::Identity);
1728 assert_eq!(decoded[0].content_language, ContentLanguage::En);
1729 assert_eq!(decoded[0].schema.as_deref(), Some("hi"));
1730
1731 for (key, value, other_value) in &cases {
1732 for repeated in [value, other_value] {
1733 let mut entries = base.clone();
1734 entries.push((key.clone(), repeated.clone()));
1735 let error = RainMetaDocumentV1Item::cbor_decode(&handwritten_entries(&entries))
1736 .unwrap_err();
1737 assert!(
1738 matches!(error, Error::SerdeCborError(_)),
1739 "{key:?} {error:?}"
1740 );
1741 assert!(
1742 error.to_string().contains("duplicate field"),
1743 "{key:?} {error}"
1744 );
1745 }
1746 }
1747 Ok(())
1748 }
1749
1750 #[test]
1755 fn test_cbor_decode_duplicate_unknown_key_errors() -> Result<(), Error> {
1756 let base: Vec<(Vec<u8>, Vec<u8>)> = vec![
1757 (vec![0x00], vec![0x41, 0x01]),
1758 (vec![0x01], handwritten_magic(KnownMagic::DotrainV1)),
1759 ];
1760 let unknown: [(Vec<u8>, Vec<u8>, Vec<u8>); 2] = [
1763 (vec![0x05], vec![0x07], vec![0x08]),
1764 (
1765 handwritten_magic(KnownMagic::OaHashList),
1766 handwritten_text("hi"),
1767 handwritten_text("bye"),
1768 ),
1769 ];
1770
1771 for (key, value, other_value) in &unknown {
1772 let mut entries = base.clone();
1773 entries.push((key.clone(), value.clone()));
1774 let decoded = RainMetaDocumentV1Item::cbor_decode(&handwritten_entries(&entries))?;
1775 assert_eq!(decoded, vec![plain_item(KnownMagic::DotrainV1, vec![0x01])]);
1776
1777 for repeated in [value, other_value] {
1778 let mut duplicated = entries.clone();
1779 duplicated.push((key.clone(), repeated.clone()));
1780 let error = RainMetaDocumentV1Item::cbor_decode(&handwritten_entries(&duplicated))
1781 .unwrap_err();
1782 assert!(
1783 matches!(error, Error::SerdeCborError(_)),
1784 "{key:?} {error:?}"
1785 );
1786 assert!(
1787 error.to_string().contains("duplicate map key"),
1788 "{key:?} {error}"
1789 );
1790 }
1791 }
1792 Ok(())
1793 }
1794
1795 #[test]
1799 fn test_cbor_decode_handwritten_document_magic_item() {
1800 let bytes: Vec<u8> = vec![
1801 0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b, 0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74, ];
1807 assert!(RainMetaDocumentV1Item::cbor_decode(&bytes).is_err());
1811 }
1812
1813 #[test]
1819 fn test_document_magic_item_carries_a_nested_document() -> Result<(), Error> {
1820 let inner = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1821 let inner_doc = RainMetaDocumentV1Item::cbor_encode_seq(
1822 &vec![inner.clone()],
1823 KnownMagic::RainMetaDocumentV1,
1824 )?;
1825 let outer = plain_item(KnownMagic::RainMetaDocumentV1, inner_doc.clone());
1826 let outer_doc = RainMetaDocumentV1Item::cbor_encode_seq(
1827 &vec![outer.clone()],
1828 KnownMagic::RainMetaDocumentV1,
1829 )?;
1830
1831 assert!(RainMetaDocumentV1Item::cbor_decode(&outer_doc).is_err());
1837 assert_eq!(
1838 RainMetaDocumentV1Item::cbor_decode(&inner_doc)?,
1839 vec![inner]
1840 );
1841 Ok(())
1842 }
1843
1844 #[test]
1847 fn test_document_magic_item_is_not_unpackable() {
1848 assert!(matches!(
1849 KnownMeta::try_from(KnownMagic::RainMetaDocumentV1),
1850 Err(Error::UnsupportedMeta)
1851 ));
1852 assert!(matches!(
1853 plain_item(KnownMagic::RainMetaDocumentV1, vec![0x01]).unpack_into::<Vec<u8>>(),
1854 Err(Error::UnsupportedMeta)
1855 ));
1856 }
1857
1858 #[test]
1860 fn test_unpack_decodes_content_encoding() -> Result<(), Error> {
1861 let content = b"unpack me via deflate".to_vec();
1862 let packed = ContentEncoding::Deflate.encode(&content);
1863 assert_ne!(packed, content);
1864 let mut item = plain_item(KnownMagic::DotrainV1, packed);
1865 item.content_encoding = ContentEncoding::Deflate;
1866 assert_eq!(item.unpack()?, content);
1867
1868 let item = plain_item(KnownMagic::DotrainV1, content.clone());
1869 assert_eq!(item.unpack()?, content);
1870 Ok(())
1871 }
1872
1873 #[test]
1876 fn test_unpack_into_whitelist() {
1877 use strum::IntoEnumIterator;
1878 let supported = [
1879 KnownMagic::OpMetaV1,
1880 KnownMagic::DotrainV1,
1881 KnownMagic::RainlangV1,
1882 KnownMagic::SolidityAbiV2,
1883 KnownMagic::AuthoringMetaV1,
1884 KnownMagic::AuthoringMetaV2,
1885 KnownMagic::AddressList,
1886 KnownMagic::InterpreterCallerMetaV1,
1887 KnownMagic::ExpressionDeployerV2BytecodeV1,
1888 KnownMagic::DotrainSourceV1,
1889 KnownMagic::OrderBuilderStateV1,
1890 KnownMagic::RainlangSourceV1,
1891 KnownMagic::RaindexSignedContextOracleV1,
1892 ];
1893 for magic in supported {
1894 let unpacked: Vec<u8> = plain_item(magic, vec![0x61]).unpack_into().unwrap();
1895 assert_eq!(unpacked, vec![0x61], "{:?}", magic);
1896 }
1897 let unsupported = [
1898 KnownMagic::RainMetaDocumentV1,
1899 KnownMagic::WebDataV1,
1900 KnownMagic::OaSchema,
1901 KnownMagic::OaHashList,
1902 KnownMagic::OaStructure,
1903 KnownMagic::OaTokenImage,
1904 KnownMagic::OaTokenCredentialLinks,
1905 ];
1906 for magic in unsupported {
1907 let result: Result<Vec<u8>, Error> = plain_item(magic, vec![0x61]).unpack_into();
1908 assert!(matches!(result, Err(Error::UnsupportedMeta)), "{:?}", magic);
1909 }
1910 assert_eq!(
1912 supported.len() + unsupported.len(),
1913 KnownMagic::iter().count()
1914 );
1915 }
1916
1917 #[test]
1920 fn test_try_into_string_invalid_utf8_errors() {
1921 let item = plain_item(KnownMagic::DotrainV1, vec![0xff, 0xfe]);
1922 let result: Result<String, Error> = item.try_into();
1923 assert!(matches!(result, Err(Error::FromUtf8Error(_))));
1924 }
1925
1926 #[test]
1928 fn test_try_into_vec_decodes_encoding() -> Result<(), Error> {
1929 let content = b"raw bytecode bytes \x00\x01\x02".to_vec();
1930 let packed = ContentEncoding::Deflate.encode(&content);
1931 let mut item = plain_item(KnownMagic::ExpressionDeployerV2BytecodeV1, packed.clone());
1932 item.content_encoding = ContentEncoding::Deflate;
1933 let unpacked: Vec<u8> = item.try_into()?;
1934 assert_eq!(unpacked, content);
1935 assert_ne!(unpacked, packed);
1936 Ok(())
1937 }
1938
1939 #[test]
1942 fn test_content_encoding_deflate_roundtrip() -> Result<(), Error> {
1943 let content = b"hello rain deflate fixture hello rain deflate fixture".to_vec();
1944 let encoded = ContentEncoding::Deflate.encode(&content);
1945 assert_ne!(encoded, content);
1946 assert_eq!(encoded[0], 0x78);
1947 assert_eq!(ContentEncoding::Deflate.decode(&encoded)?, content);
1948 Ok(())
1949 }
1950
1951 #[test]
1953 fn test_content_encoding_passthrough() -> Result<(), Error> {
1954 let data = vec![0x00, 0xff, 0x10];
1955 for encoding in [ContentEncoding::None, ContentEncoding::Identity] {
1956 assert_eq!(encoding.encode(&data), data, "{:?}", encoding);
1957 assert_eq!(encoding.decode(&data)?, data, "{:?}", encoding);
1958 }
1959 Ok(())
1960 }
1961
1962 #[test]
1965 fn test_content_encoding_decode_fixtures() -> Result<(), Error> {
1966 let content = b"hello rain deflate fixture".to_vec();
1967 let zlib: Vec<u8> = vec![
1968 120, 156, 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44,
1969 73, 85, 72, 203, 172, 40, 41, 45, 74, 5, 0, 132, 64, 9, 251,
1970 ];
1971 let raw: Vec<u8> = vec![
1972 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44, 73, 85, 72,
1973 203, 172, 40, 41, 45, 74, 5, 0,
1974 ];
1975 assert_eq!(ContentEncoding::Deflate.decode(&zlib)?, content);
1976 assert_eq!(ContentEncoding::Deflate.decode(&raw)?, content);
1977 Ok(())
1978 }
1979
1980 #[test]
1983 fn test_content_encoding_decode_garbage_errors() {
1984 let garbage = [0xffu8, 0xff, 0xff, 0xff];
1985 assert!(matches!(
1986 ContentEncoding::Deflate.decode(&garbage),
1987 Err(Error::InflateError(_))
1988 ));
1989 }
1990
1991 #[test]
1993 fn test_content_headers_strum_names() {
1994 use std::str::FromStr;
1995 assert_eq!(
1996 ContentEncoding::from_str("deflate").unwrap(),
1997 ContentEncoding::Deflate
1998 );
1999 assert_eq!(
2000 ContentEncoding::from_str("identity").unwrap(),
2001 ContentEncoding::Identity
2002 );
2003 assert_eq!(
2004 ContentEncoding::from_str("none").unwrap(),
2005 ContentEncoding::None
2006 );
2007 assert_eq!(ContentEncoding::Deflate.to_string(), "deflate");
2008 assert_eq!(
2009 ContentType::from_str("octet-stream").unwrap(),
2010 ContentType::OctetStream
2011 );
2012 assert_eq!(ContentType::from_str("json").unwrap(), ContentType::Json);
2013 assert_eq!(ContentType::Json.to_string(), "json");
2014 assert_eq!(
2015 ContentLanguage::from_str("en").unwrap(),
2016 ContentLanguage::En
2017 );
2018 }
2019
2020 #[test]
2023 fn test_known_meta_try_from_magic() {
2024 let cases: [(KnownMagic, KnownMeta); 13] = [
2025 (KnownMagic::OpMetaV1, KnownMeta::OpV1),
2026 (KnownMagic::DotrainV1, KnownMeta::DotrainV1),
2027 (KnownMagic::RainlangV1, KnownMeta::RainlangV1),
2028 (KnownMagic::SolidityAbiV2, KnownMeta::SolidityAbiV2),
2029 (KnownMagic::AuthoringMetaV1, KnownMeta::AuthoringMetaV1),
2030 (KnownMagic::AuthoringMetaV2, KnownMeta::AuthoringMetaV2),
2031 (KnownMagic::AddressList, KnownMeta::AddressList),
2032 (
2033 KnownMagic::InterpreterCallerMetaV1,
2034 KnownMeta::InterpreterCallerMetaV1,
2035 ),
2036 (
2037 KnownMagic::ExpressionDeployerV2BytecodeV1,
2038 KnownMeta::ExpressionDeployerV2BytecodeV1,
2039 ),
2040 (KnownMagic::RainlangSourceV1, KnownMeta::RainlangSourceV1),
2041 (KnownMagic::DotrainSourceV1, KnownMeta::DotrainSourceV1),
2042 (
2043 KnownMagic::OrderBuilderStateV1,
2044 KnownMeta::OrderBuilderStateV1,
2045 ),
2046 (
2047 KnownMagic::RaindexSignedContextOracleV1,
2048 KnownMeta::RaindexSignedContextOracleV1,
2049 ),
2050 ];
2051 for (magic, meta) in cases {
2052 assert_eq!(KnownMeta::try_from(magic).unwrap(), meta, "{:?}", magic);
2053 }
2054 for magic in [
2055 KnownMagic::RainMetaDocumentV1,
2056 KnownMagic::WebDataV1,
2057 KnownMagic::OaSchema,
2058 KnownMagic::OaHashList,
2059 KnownMagic::OaStructure,
2060 KnownMagic::OaTokenImage,
2061 KnownMagic::OaTokenCredentialLinks,
2062 ] {
2063 assert!(
2064 matches!(KnownMeta::try_from(magic), Err(Error::UnsupportedMeta)),
2065 "{:?}",
2066 magic
2067 );
2068 }
2069 }
2070
2071 #[test]
2074 fn test_known_meta_strum_parse_display() {
2075 use std::str::FromStr;
2076 assert_eq!(
2077 KnownMeta::from_str("solidity-abi-v2").unwrap(),
2078 KnownMeta::SolidityAbiV2
2079 );
2080 assert_eq!(
2081 KnownMeta::from_str("interpreter-caller-meta-v1").unwrap(),
2082 KnownMeta::InterpreterCallerMetaV1
2083 );
2084 assert_eq!(KnownMeta::SolidityAbiV2.to_string(), "solidity-abi-v2");
2085 }
2086
2087 #[tokio::test]
2089 async fn test_search_lowercases_hash() {
2090 use httpmock::prelude::*;
2091 let (_, doc) = sample_authoring_doc();
2092 let hash_upper = format!("0x{}", "AB".repeat(32));
2093 let server = MockServer::start();
2094 let mock = server.mock(|when, then| {
2095 when.method(POST)
2096 .body_contains(hash_upper.to_ascii_lowercase());
2097 then.status(200).json_body(json!({
2098 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2099 }));
2100 });
2101 let response = search(&hash_upper, &vec![server.url("/sg")]).await.unwrap();
2102 assert_eq!(response.bytes, doc);
2103 mock.assert();
2104 }
2105
2106 #[tokio::test]
2109 async fn test_search_first_success_wins() {
2110 use httpmock::prelude::*;
2111 let (_, doc) = sample_authoring_doc();
2112 let bad = MockServer::start();
2113 let _bad_mock = bad.mock(|when, then| {
2114 when.method(POST);
2115 then.status(500).body("subgraph down");
2116 });
2117 let good = MockServer::start();
2118 let _good_mock = good.mock(|when, then| {
2119 when.method(POST);
2120 then.status(200).json_body(json!({
2121 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2122 }));
2123 });
2124 let response = search(
2125 &format!("0x{}", "11".repeat(32)),
2126 &vec![bad.url("/sg"), good.url("/sg")],
2127 )
2128 .await
2129 .unwrap();
2130 assert_eq!(response.bytes, doc);
2131 }
2132
2133 #[tokio::test]
2137 async fn test_search_empty_subgraphs_is_a_miss() {
2138 let hash = format!("0x{}", "33".repeat(32));
2139 assert!(matches!(
2140 search(&hash, &vec![]).await,
2141 Err(Error::NoRecordFound)
2142 ));
2143 }
2144
2145 #[tokio::test]
2156 async fn test_implements_erc165_gate_short_circuits() {
2157 let address = Address::random();
2158
2159 let asserter = Asserter::new();
2161 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2162 asserter
2163 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2164 asserter
2165 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2166 assert!(!implements_i_described_by_meta_v1(&provider, address)
2167 .await
2168 .unwrap());
2169
2170 let asserter = Asserter::new();
2174 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2175 asserter.push_failure(ErrorPayload {
2176 code: -32000,
2177 message: "connection reset".into(),
2178 data: None,
2179 });
2180 asserter
2181 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2182 assert!(implements_i_described_by_meta_v1(&provider, address)
2183 .await
2184 .is_err());
2185 }
2186
2187 #[tokio::test]
2190 async fn test_implements_empty_response_is_false() {
2191 let address = Address::random();
2192 let asserter = Asserter::new();
2193 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2194 asserter
2195 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2196 asserter
2197 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2198 asserter.push_success(&"0x");
2199 assert!(!implements_i_described_by_meta_v1(&provider, address)
2200 .await
2201 .unwrap());
2202 }
2203
2204 #[tokio::test]
2208 async fn test_implements_described_by_call_error_is_unknown() {
2209 let address = Address::random();
2210 let asserter = Asserter::new();
2211 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2212 asserter
2213 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2214 asserter
2215 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2216 asserter.push_failure(ErrorPayload {
2217 code: -32005,
2218 message: "rate limit exceeded".into(),
2219 data: None,
2220 });
2221 let error = implements_i_described_by_meta_v1(&provider, address)
2222 .await
2223 .unwrap_err();
2224 assert!(error.to_string().contains("rate limit exceeded"));
2225 }
2226
2227 #[tokio::test]
2231 async fn test_implements_undecodable_response_is_unknown() {
2232 let address = Address::random();
2233 let asserter = Asserter::new();
2234 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2235 asserter
2236 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2237 asserter
2238 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2239 asserter.push_success(&"0xdeadbeef");
2240 implements_i_described_by_meta_v1(&provider, address)
2241 .await
2242 .unwrap_err();
2243 }
2244
2245 #[tokio::test]
2249 async fn test_store_constructors_inject_no_subgraphs() {
2250 assert!(Store::new().subgraphs().is_empty());
2251 assert!(Store::default().subgraphs().is_empty());
2252 assert!(
2253 Store::create(&vec![], &MetaCache::default(), &HashMap::new())
2254 .subgraphs()
2255 .is_empty()
2256 );
2257
2258 let hash = [0u8; 32];
2259 let mut store = Store::default();
2260 assert!(store.update(&hash).await.is_err());
2261 }
2262
2263 #[test]
2271 fn test_store_create_validates_entries() {
2272 let (_, doc) = sample_authoring_doc();
2273 let good_hash = keccak256(&doc).0.to_vec();
2274 let mut cache = MetaCache::default();
2275 cache.insert_verified(&good_hash, doc.clone()).unwrap();
2276 let mut dotrain_cache = HashMap::new();
2277 dotrain_cache.insert("a.rain".to_string(), good_hash.clone());
2278 dotrain_cache.insert("missing.rain".to_string(), vec![0x44u8; 32]);
2279
2280 let store = Store::create(
2281 &vec!["https://example.com/custom-sg".to_string()],
2282 &cache,
2283 &dotrain_cache,
2284 );
2285
2286 assert_eq!(
2287 store.subgraphs(),
2288 &vec!["https://example.com/custom-sg".to_string()]
2289 );
2290 assert_eq!(store.get_meta(&good_hash), Some(&doc));
2291 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&good_hash));
2292 assert_eq!(store.get_dotrain_hash("missing.rain"), None);
2293 }
2294
2295 #[test]
2297 fn test_store_add_subgraphs_dedupe() {
2298 let mut store = Store::new();
2299 store.add_subgraphs(&vec!["sg-a".to_string()]);
2300 store.add_subgraphs(&vec!["sg-a".to_string(), "sg-b".to_string()]);
2301 assert_eq!(
2302 store.subgraphs(),
2303 &vec!["sg-a".to_string(), "sg-b".to_string()]
2304 );
2305 }
2306
2307 #[test]
2311 fn test_store_dotrain_getters_and_set_fresh() {
2312 let mut store = Store::new();
2313 let text = "some dotrain content";
2314 let (hash, old) = store.set_dotrain(text, "file.rain", false).unwrap();
2315 assert!(old.is_empty());
2316 let expected_item = RainMetaDocumentV1Item {
2317 payload: serde_bytes::ByteBuf::from(text.as_bytes()),
2318 magic: KnownMagic::DotrainV1,
2319 content_type: ContentType::OctetStream,
2320 content_encoding: ContentEncoding::None,
2321 content_language: ContentLanguage::None,
2322 schema: None,
2323 };
2324 let expected_bytes = expected_item.cbor_encode().unwrap();
2325 assert_eq!(hash, keccak256(&expected_bytes).0.to_vec());
2326 assert_eq!(store.get_dotrain_hash("file.rain"), Some(&hash));
2327 assert_eq!(store.get_dotrain_uri(&hash), Some(&"file.rain".to_string()));
2328 assert_eq!(store.get_dotrain_meta("file.rain"), Some(&expected_bytes));
2329 assert_eq!(store.get_dotrain_hash("other.rain"), None);
2330 assert_eq!(store.get_dotrain_uri(&[0u8; 32]), None);
2331 assert_eq!(store.get_dotrain_meta("other.rain"), None);
2332 }
2333
2334 #[test]
2338 fn test_store_set_dotrain_branches() {
2339 let mut store = Store::new();
2340 let (hash_one, _) = store.set_dotrain("text one", "a.rain", false).unwrap();
2341
2342 let (hash_same, old_same) = store.set_dotrain("text one", "a.rain", false).unwrap();
2344 assert_eq!(hash_same, hash_one);
2345 assert!(old_same.is_empty());
2346 assert!(store.get_meta(&hash_one).is_some());
2347
2348 let (hash_two, old_two) = store.set_dotrain("text two", "a.rain", false).unwrap();
2350 assert_ne!(hash_two, hash_one);
2351 assert_eq!(old_two, hash_one);
2352 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_two));
2353 assert!(store.get_meta(&hash_one).is_none());
2354 assert!(store.get_meta(&hash_two).is_some());
2355
2356 let (hash_three, old_three) = store.set_dotrain("text three", "a.rain", true).unwrap();
2358 assert_eq!(old_three, hash_two);
2359 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_three));
2360 assert!(store.get_meta(&hash_two).is_some());
2361 assert!(store.get_meta(&hash_three).is_some());
2362 }
2363
2364 #[test]
2367 fn test_store_delete_dotrain_keep_meta() {
2368 let mut store = Store::new();
2369 let (hash, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2370 store.delete_dotrain("d.rain", false);
2371 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2372 assert!(store.get_meta(&hash).is_none());
2373
2374 let (hash_again, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2375 store.delete_dotrain("d.rain", true);
2376 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2377 assert!(store.get_meta(&hash_again).is_some());
2378 }
2379
2380 #[test]
2383 fn test_store_merge_semantics() {
2384 let mut ours = Store::new();
2385 let mut theirs = Store::new();
2386
2387 let mine = b"mine".to_vec();
2391 let yours = b"yours".to_vec();
2392 let mine_hash = keccak256(&mine).0.to_vec();
2393 let yours_hash = keccak256(&yours).0.to_vec();
2394 ours.update_with(&mine_hash, &mine).unwrap();
2395 theirs.update_with(&yours_hash, &yours).unwrap();
2396
2397 let (hash_ours, _) = ours.set_dotrain("content a", "x.rain", false).unwrap();
2399 let (_hash_theirs, _) = theirs.set_dotrain("content b", "x.rain", false).unwrap();
2400
2401 theirs.add_subgraphs(&vec!["sg-their".to_string()]);
2402
2403 ours.merge(&theirs);
2404
2405 assert_eq!(ours.get_meta(&mine_hash), Some(&mine));
2406 assert_eq!(ours.get_meta(&yours_hash), Some(&yours));
2407 assert_eq!(ours.get_dotrain_hash("x.rain"), Some(&hash_ours));
2409 assert!(ours.subgraphs().contains(&"sg-their".to_string()));
2411 }
2412
2413 #[tokio::test]
2417 async fn test_store_update_and_update_check() {
2418 use httpmock::prelude::*;
2419 let authoring_meta: AuthoringMeta = serde_json::from_str(
2420 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
2421 )
2422 .unwrap();
2423 let item_one = RainMetaDocumentV1Item {
2424 payload: serde_bytes::ByteBuf::from(authoring_meta.abi_encode_validate().unwrap()),
2425 magic: KnownMagic::AuthoringMetaV1,
2426 content_type: ContentType::Cbor,
2427 content_encoding: ContentEncoding::None,
2428 content_language: ContentLanguage::None,
2429 schema: None,
2430 };
2431 let item_two = sample_dotrain_item();
2432 let doc = RainMetaDocumentV1Item::cbor_encode_seq(
2433 &vec![item_one.clone(), item_two.clone()],
2434 KnownMagic::RainMetaDocumentV1,
2435 )
2436 .unwrap();
2437 let requested = keccak256(&doc).0.to_vec();
2438 let server = MockServer::start();
2439 let _mock = server.mock(|when, then| {
2440 when.method(POST);
2441 then.status(200).json_body(json!({
2442 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2443 }));
2444 });
2445 let mut store = Store::new();
2446 store.add_subgraphs(&vec![server.url("/sg")]);
2447 let fetched = store.update(&requested).await.cloned().unwrap();
2448 assert_eq!(fetched, doc);
2449 assert_eq!(store.get_meta(&requested), Some(&doc));
2450 let inner_one = item_one.cbor_encode().unwrap();
2451 let inner_two = item_two.cbor_encode().unwrap();
2452 assert_eq!(
2453 store.get_meta(keccak256(&inner_one).0.as_ref()),
2454 Some(&inner_one)
2455 );
2456 assert_eq!(
2457 store.get_meta(keccak256(&inner_two).0.as_ref()),
2458 Some(&inner_two)
2459 );
2460
2461 let mut cached_store = Store::new();
2463 let bytes = b"standalone meta bytes".to_vec();
2464 let hash = keccak256(&bytes).0.to_vec();
2465 assert!(cached_store.update_with(&hash, &bytes).is_ok());
2466 assert_eq!(cached_store.update_check(&hash).await.unwrap(), &bytes);
2467 }
2468
2469 #[tokio::test]
2473 async fn test_store_update_rejects_hash_mismatch() {
2474 use httpmock::prelude::*;
2475 let (_, doc) = sample_authoring_doc();
2476 let requested = keccak256(b"the real content").0.to_vec();
2477 let server = MockServer::start();
2478 let _mock = server.mock(|when, then| {
2479 when.method(POST);
2480 then.status(200).json_body(json!({
2481 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2482 }));
2483 });
2484 let mut store = Store::new();
2485 store.add_subgraphs(&vec![server.url("/sg")]);
2486 assert!(store.update(&requested).await.is_err());
2487 assert!(store.get_meta(&requested).is_none());
2488 assert!(store.cache().is_empty());
2489 assert!(store.update_check(&requested).await.is_err());
2491 }
2492
2493 #[tokio::test]
2496 async fn test_store_no_subgraphs_lookups_return_none() {
2497 let hash = [0u8; 32];
2498 let mut store = Store::new();
2499 assert!(store.update(&hash).await.is_err());
2500 assert!(store.update_check(&hash).await.is_err());
2501 assert!(store.cache().is_empty());
2502 }
2503
2504 #[test]
2508 fn test_store_update_with_validation_and_content() {
2509 let mut store = Store::new();
2511 let bytes = b"payload bytes".to_vec();
2512 let wrong_hash = vec![0x99u8; 32];
2513 match store.update_with(&wrong_hash, &bytes).unwrap_err() {
2518 Error::CorruptRecord(message) => {
2519 assert!(
2520 message.contains(&hex::encode_prefixed(&wrong_hash)),
2521 "{}",
2522 message
2523 )
2524 }
2525 other => panic!("expected CorruptRecord, got {:?}", other),
2526 }
2527 assert!(store.get_meta(&wrong_hash).is_none());
2528 let hash = keccak256(&bytes).0.to_vec();
2530 assert_eq!(store.update_with(&hash, &bytes).unwrap(), &bytes);
2531
2532 let mut seeded = Store::new();
2539 let planted = b"planted value".to_vec();
2540 let planted_hash = keccak256(&planted).0.to_vec();
2541 seeded.update_with(&planted_hash, &planted).unwrap();
2542 assert_eq!(seeded.cache().len(), 1);
2543 assert_eq!(
2544 seeded.update_with(&planted_hash, &planted).unwrap(),
2545 &planted
2546 );
2547 assert_eq!(seeded.cache().len(), 1);
2548
2549 let (_, doc) = sample_authoring_doc();
2551 let doc_hash = keccak256(&doc).0.to_vec();
2552 let mut doc_store = Store::new();
2553 assert!(doc_store.update_with(&doc_hash, &doc).is_ok());
2554 let inner = doc[8..].to_vec();
2555 assert_eq!(store_inner_lookup(&doc_store, &inner), Some(inner.clone()));
2556
2557 let item_a = sample_dotrain_item().cbor_encode().unwrap();
2559 let (_, doc_b) = sample_authoring_doc();
2560 let item_b = doc_b[8..].to_vec();
2561 let seq = [item_a.clone(), item_b].concat();
2562 let seq_hash = keccak256(&seq).0.to_vec();
2563 let mut seq_store = Store::new();
2564 assert!(seq_store.update_with(&seq_hash, &seq).is_ok());
2565 assert_eq!(store_inner_lookup(&seq_store, &item_a), None);
2566 }
2567
2568 fn store_inner_lookup(store: &Store, inner_encoded: &[u8]) -> Option<Vec<u8>> {
2569 store.get_meta(keccak256(inner_encoded).0.as_ref()).cloned()
2570 }
2571
2572 #[test]
2575 fn test_bytes32_to_str_invalid_utf8() {
2576 let mut bytes = [0u8; 32];
2577 bytes[0] = 0xf0;
2578 bytes[1] = 0x28;
2579 bytes[2] = 0x8c;
2580 bytes[3] = 0x28;
2581 assert!(matches!(bytes32_to_str(&bytes), Err(Error::Utf8Error(_))));
2582 let no_nul = [0xffu8; 32];
2583 assert!(matches!(bytes32_to_str(&no_nul), Err(Error::Utf8Error(_))));
2584 }
2585}