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 ItemOrDropped::deserialize(&mut deserializer) {
271 Ok(decoded) => {
272 consumed = deserializer.byte_offset();
273 if let ItemOrDropped::Item(meta) = decoded {
274 metas.push(meta);
275 }
276 true
277 }
278 Err(error) => {
279 if error.is_eof() {
280 false
281 } else {
282 Err(Error::SerdeCborError(error))?
283 }
284 }
285 } {}
286
287 if metas.is_empty() || len != consumed {
288 Err(Error::CorruptMeta)?
289 }
290 Ok(metas)
291 }
292
293 pub fn unpack(&self) -> Result<Vec<u8>, Error> {
295 ContentEncoding::decode(&self.content_encoding, self.payload.as_ref())
296 }
297
298 pub fn unpack_into<T: TryFrom<Self, Error = Error>>(self) -> Result<T, Error> {
300 match self.magic {
301 KnownMagic::OpMetaV1
302 | KnownMagic::DotrainV1
303 | KnownMagic::RainlangV1
304 | KnownMagic::SolidityAbiV2
305 | KnownMagic::AuthoringMetaV1
306 | KnownMagic::AuthoringMetaV2
307 | KnownMagic::AddressList
308 | KnownMagic::InterpreterCallerMetaV1
309 | KnownMagic::ExpressionDeployerV2BytecodeV1
310 | KnownMagic::DotrainSourceV1
311 | KnownMagic::OrderBuilderStateV1
312 | KnownMagic::RainlangSourceV1
313 | KnownMagic::RaindexSignedContextOracleV1 => T::try_from(self),
314 _ => Err(Error::UnsupportedMeta)?,
315 }
316 }
317}
318
319impl Serialize for RainMetaDocumentV1Item {
320 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
321 let mut map = serializer.serialize_map(Some(self.len()))?;
322 map.serialize_entry(&0, &self.payload)?;
323 map.serialize_entry(&1, &(self.magic as u64))?;
324 match self.content_type {
325 ContentType::None => {}
326 content_type => map.serialize_entry(&2, &content_type)?,
327 }
328 match self.content_encoding {
329 ContentEncoding::None => {}
330 content_encoding => map.serialize_entry(&3, &content_encoding)?,
331 }
332 match self.content_language {
333 ContentLanguage::None => {}
334 content_language => map.serialize_entry(&4, &content_language)?,
335 }
336 if let Some(schema) = &self.schema {
337 map.serialize_entry(&(KnownMagic::OaSchema as u64), schema)?;
338 }
339 map.end()
340 }
341}
342
343enum ItemOrDropped {
362 Item(RainMetaDocumentV1Item),
363 Dropped,
365}
366
367impl<'de> Deserialize<'de> for ItemOrDropped {
368 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
369 fn set_once<V, E: serde::de::Error>(
372 slot: &mut Option<V>,
373 value: V,
374 field: &'static str,
375 ) -> Result<(), E> {
376 if slot.is_some() {
377 return Err(serde::de::Error::duplicate_field(field));
378 }
379 *slot = Some(value);
380 Ok(())
381 }
382
383 struct EncodedMap;
384 impl<'de> Visitor<'de> for EncodedMap {
385 type Value = ItemOrDropped;
386
387 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
388 formatter.write_str("rain meta cbor encoded bytes")
389 }
390
391 fn visit_map<T: serde::de::MapAccess<'de>>(
392 self,
393 mut map: T,
394 ) -> Result<Self::Value, T::Error> {
395 const OA_SCHEMA_KEY: u64 = KnownMagic::OaSchema as u64;
396 let mut payload = None;
397 let mut magic: Option<u64> = None;
398 let mut content_type = None;
399 let mut content_encoding = None;
400 let mut content_language = None;
401 let mut schema = None;
402 let mut unknown_keys: BTreeSet<u64> = BTreeSet::new();
406 while match map.next_key::<u64>() {
407 Ok(Some(key)) => {
408 match key {
409 0 => set_once(&mut payload, map.next_value()?, "payload")?,
410 1 => set_once(&mut magic, map.next_value()?, "magic number")?,
411 2 => set_once(&mut content_type, map.next_value()?, "content type")?,
412 3 => set_once(
413 &mut content_encoding,
414 map.next_value()?,
415 "content encoding",
416 )?,
417 4 => set_once(
418 &mut content_language,
419 map.next_value()?,
420 "content language",
421 )?,
422 OA_SCHEMA_KEY => set_once(&mut schema, map.next_value()?, "schema")?,
423 _ => {
431 if !unknown_keys.insert(key) {
432 return Err(serde::de::Error::custom(format!(
433 "duplicate map key: {key}"
434 )));
435 }
436 map.next_value::<serde::de::IgnoredAny>()?;
437 }
438 };
439 true
440 }
441 Ok(None) => false,
442 Err(error) => Err(error)?,
443 } {}
444 let (Some(payload), Some(magic_number)) = (payload, magic) else {
454 return Err(serde::de::Error::custom(
455 "cbor item omits a mandatory key (0 payload, 1 magic)",
456 ));
457 };
458
459 if magic_number == KnownMagic::RainMetaDocumentV1 as u64 {
468 return Err(serde::de::Error::custom(
469 "rain meta document magic number as an item magic number",
470 ));
471 }
472
473 let Ok(magic) = KnownMagic::try_from(magic_number) else {
474 return Ok(ItemOrDropped::Dropped);
475 };
476
477 let content_type = content_type.unwrap_or(ContentType::None);
478 let content_encoding = content_encoding.unwrap_or(ContentEncoding::None);
479 let content_language = content_language.unwrap_or(ContentLanguage::None);
480
481 Ok(ItemOrDropped::Item(RainMetaDocumentV1Item {
482 payload,
483 magic,
484 content_type,
485 content_encoding,
486 content_language,
487 schema,
488 }))
489 }
490 }
491 deserializer.deserialize_map(EncodedMap)
492 }
493}
494
495impl<'de> Deserialize<'de> for RainMetaDocumentV1Item {
502 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
503 match ItemOrDropped::deserialize(deserializer)? {
504 ItemOrDropped::Item(item) => Ok(item),
505 ItemOrDropped::Dropped => Err(serde::de::Error::custom(
506 "not a rain meta item this version reads: a mandatory key is missing or the magic number is unknown",
507 )),
508 }
509 }
510}
511
512pub async fn search(hash: &str, subgraphs: &Vec<String>) -> Result<query::MetaResponse, Error> {
514 if subgraphs.is_empty() {
516 return Err(Error::NoRecordFound);
517 }
518 let request_body = query::MetaQuery::build_query(query::meta_query::Variables {
519 hash: Some(hash.to_ascii_lowercase()),
520 });
521 let mut promises = vec![];
522
523 let client = Arc::new(Client::builder().build().map_err(Error::ReqwestError)?);
524 for url in subgraphs {
525 promises.push(Box::pin(query::process_meta_query(
526 client.clone(),
527 &request_body,
528 url,
529 )));
530 }
531 let response_value = future::select_ok(promises.drain(..)).await?.0;
532 Ok(response_value)
533}
534
535pub async fn implements_i_described_by_meta_v1<P: Provider>(
543 provider: &P,
544 contract_address: Address,
545) -> Result<bool, Erc165Error> {
546 if !supports_erc165(provider, contract_address).await? {
547 return Ok(false);
548 }
549
550 let interface_id_res = IDescribedByMetaV1::IDescribedByMetaV1Calls::xor_selectors();
551 if interface_id_res.is_err() {
552 return Ok(false);
553 }
554
555 match IERC165::new(contract_address, provider)
556 .supportsInterface(interface_id_res.unwrap().into())
557 .call()
558 .await
559 {
560 Ok(supported) => Ok(supported),
561 Err(error)
562 if error.as_revert_data().is_some()
563 || matches!(error, ContractError::ZeroData(_, _)) =>
564 {
565 Ok(false)
566 }
567 Err(error) => Err(error.into()),
568 }
569}
570
571#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
626pub struct Store {
627 subgraphs: Vec<String>,
628 cache: MetaCache,
629 dotrain_cache: HashMap<String, Vec<u8>>,
630}
631
632impl Default for Store {
633 fn default() -> Self {
634 Store::new()
635 }
636}
637
638impl Store {
639 pub fn new() -> Store {
642 Store {
643 subgraphs: vec![],
644 cache: MetaCache::default(),
645 dotrain_cache: HashMap::new(),
646 }
647 }
648
649 pub fn create(
652 subgraphs: &Vec<String>,
653 cache: &MetaCache,
654 dotrain_cache: &HashMap<String, Vec<u8>>,
655 ) -> Store {
656 let mut store = Store::new();
657 store.add_subgraphs(subgraphs);
658 for (hash, bytes) in cache.iter() {
659 let _ = store.update_with(hash, bytes);
660 }
661 for (uri, hash) in dotrain_cache {
662 if !store.dotrain_cache.contains_key(uri) && store.cache.contains_key(hash) {
663 store.dotrain_cache.insert(uri.clone(), hash.clone());
664 }
665 }
666 store
667 }
668
669 pub fn subgraphs(&self) -> &Vec<String> {
671 &self.subgraphs
672 }
673
674 pub fn add_subgraphs(&mut self, subgraphs: &Vec<String>) {
676 for sg in subgraphs {
677 if !self.subgraphs.contains(sg) {
678 self.subgraphs.push(sg.to_string());
679 }
680 }
681 }
682
683 pub fn cache(&self) -> &MetaCache {
685 &self.cache
686 }
687
688 pub fn get_meta(&self, hash: &[u8]) -> Option<&Vec<u8>> {
690 self.cache.get(hash)
691 }
692
693 pub fn dotrain_cache(&self) -> &HashMap<String, Vec<u8>> {
695 &self.dotrain_cache
696 }
697
698 pub fn get_dotrain_hash(&self, uri: &str) -> Option<&Vec<u8>> {
700 self.dotrain_cache.get(uri)
701 }
702
703 pub fn get_dotrain_uri(&self, hash: &[u8]) -> Option<&String> {
705 for (uri, h) in &self.dotrain_cache {
706 if h == hash {
707 return Some(uri);
708 }
709 }
710 None
711 }
712
713 pub fn get_dotrain_meta(&self, uri: &str) -> Option<&Vec<u8>> {
715 self.get_meta(self.dotrain_cache.get(uri)?)
716 }
717
718 pub fn delete_dotrain(&mut self, uri: &str, keep_meta: bool) {
720 if let Some(kv) = self.dotrain_cache.remove_entry(uri) {
721 if !keep_meta {
722 self.cache.remove(&kv.1);
723 }
724 };
725 }
726
727 pub fn merge(&mut self, other: &Store) {
730 self.add_subgraphs(&other.subgraphs);
731 for (hash, bytes) in other.cache.iter() {
732 if !self.cache.contains_key(hash) {
733 let _ = self.cache.insert_verified(hash, bytes.clone());
736 }
737 }
738 for (uri, hash) in &other.dotrain_cache {
739 if !self.dotrain_cache.contains_key(uri) {
740 self.dotrain_cache.insert(uri.clone(), hash.clone());
741 }
742 }
743 }
744
745 fn insert_verified(&mut self, hash: &[u8], bytes: Vec<u8>) -> Result<&Vec<u8>, Error> {
749 self.cache.insert_verified(hash, bytes.clone())?;
750 self.store_content(&bytes);
751 self.get_meta(hash).ok_or(Error::NoRecordFound)
752 }
753
754 pub async fn update(&mut self, hash: &[u8]) -> Result<&Vec<u8>, Error> {
759 let meta = search(&hex::encode_prefixed(hash), &self.subgraphs).await?;
760 self.insert_verified(hash, meta.bytes)
761 }
762
763 pub async fn update_check(&mut self, hash: &[u8]) -> Result<&Vec<u8>, Error> {
765 if self.cache.contains_key(hash) {
771 return self.get_meta(hash).ok_or(Error::NoRecordFound);
772 }
773 self.update(hash).await
774 }
775
776 pub fn update_with(&mut self, hash: &[u8], bytes: &[u8]) -> Result<&Vec<u8>, Error> {
780 if self.cache.contains_key(hash) {
786 return self.get_meta(hash).ok_or(Error::NoRecordFound);
787 }
788 self.insert_verified(hash, bytes.to_vec())
789 }
790
791 pub fn set_dotrain(
796 &mut self,
797 text: &str,
798 uri: &str,
799 keep_old: bool,
800 ) -> Result<(Vec<u8>, Vec<u8>), Error> {
801 let bytes = RainMetaDocumentV1Item {
802 payload: serde_bytes::ByteBuf::from(text.as_bytes()),
803 magic: KnownMagic::DotrainV1,
804 content_type: ContentType::OctetStream,
805 content_encoding: ContentEncoding::None,
806 content_language: ContentLanguage::None,
807 schema: None,
808 }
809 .cbor_encode()?;
810 let new_hash = keccak256(&bytes).0.to_vec();
811 if let Some(h) = self.dotrain_cache.get(uri) {
812 let old_hash = h.clone();
813 if new_hash == old_hash {
814 self.cache.insert_verified(&new_hash, bytes)?;
815 Ok((new_hash, vec![]))
816 } else {
817 self.cache.insert_verified(&new_hash, bytes)?;
818 self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
819 if !keep_old {
820 self.cache.remove(&old_hash);
821 }
822 Ok((new_hash, old_hash))
823 }
824 } else {
825 self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
826 self.cache.insert_verified(&new_hash, bytes)?;
827 Ok((new_hash, vec![]))
828 }
829 }
830
831 fn store_content(&mut self, bytes: &[u8]) {
835 if let Ok(meta_maps) = RainMetaDocumentV1Item::cbor_decode(bytes) {
836 if bytes.starts_with(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes()) {
837 for meta_map in &meta_maps {
838 if let Ok(encoded_bytes) = meta_map.cbor_encode() {
839 let _ = self
843 .cache
844 .insert_verified(&keccak256(&encoded_bytes).0, encoded_bytes);
845 }
846 }
847 }
848 }
849 }
850}
851
852pub fn str_to_bytes32(text: &str) -> Result<[u8; 32], Error> {
858 let bytes: &[u8] = text.as_bytes();
859 if bytes.len() > 32 {
860 return Err(Error::BiggerThan32Bytes);
861 }
862 if bytes.contains(&0u8) {
863 return Err(Error::NulByteInInput);
864 }
865 let mut b32 = [0u8; 32];
866 b32[..bytes.len()].copy_from_slice(bytes);
867 Ok(b32)
868}
869
870pub fn bytes32_to_str(bytes: &[u8; 32]) -> Result<&str, Error> {
872 let mut len = 32;
873 if let Some((pos, _)) = itertools::Itertools::find_position(&mut bytes.iter(), |b| **b == 0u8) {
874 len = pos;
875 };
876 Ok(std::str::from_utf8(&bytes[..len])?)
877}
878
879#[cfg(all(test, not(target_family = "wasm")))]
880mod tests {
881 use super::{
882 *, bytes32_to_str, magic::KnownMagic, str_to_bytes32, types::authoring::v1::AuthoringMeta,
883 ContentEncoding, ContentLanguage, ContentType, Error, RainMetaDocumentV1Item,
884 };
885 use alloy::providers::ProviderBuilder;
886 use alloy::{providers::mock::Asserter, rpc::json_rpc::ErrorPayload, sol_types::SolType};
887 use serde_json::json;
888
889 #[test]
892 fn authoring_meta_roundtrip() -> Result<(), Error> {
893 let authoring_meta_content = r#"[
894 {
895 "word": "stack",
896 "description": "Copies an existing value from the stack.",
897 "operandParserOffset": 16
898 },
899 {
900 "word": "constant",
901 "description": "Copies a constant value onto the stack.",
902 "operandParserOffset": 16
903 }
904 ]"#;
905 let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
906
907 let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
909 let expected_abi_encoded = <alloy::sol!((bytes32, uint8, string)[])>::abi_encode(&vec![
910 (
911 str_to_bytes32("stack")?,
912 16u8,
913 "Copies an existing value from the stack.".to_string(),
914 ),
915 (
916 str_to_bytes32("constant")?,
917 16u8,
918 "Copies a constant value onto the stack.".to_string(),
919 ),
920 ]);
921 assert_eq!(authoring_meta_abi_encoded, expected_abi_encoded);
923
924 let meta_map = RainMetaDocumentV1Item {
925 payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
926 magic: KnownMagic::AuthoringMetaV1,
927 content_type: ContentType::Cbor,
928 content_encoding: ContentEncoding::None,
929 content_language: ContentLanguage::None,
930 schema: None,
931 };
932 let cbor_encoded = meta_map.cbor_encode()?;
933
934 assert_eq!(cbor_encoded[0], 0xa3);
936 assert_eq!(cbor_encoded[1], 0x00);
938 assert_eq!(cbor_encoded[2], 0b010_11001);
940 assert_eq!(cbor_encoded[3], 0b000_00010);
941 assert_eq!(cbor_encoded[4], 0b000_00000);
942 assert_eq!(cbor_encoded[5..517], authoring_meta_abi_encoded);
944 assert_eq!(cbor_encoded[517], 0x01);
946 assert_eq!(cbor_encoded[518], 0b000_11011);
948 assert_eq!(
950 &cbor_encoded[519..527],
951 KnownMagic::AuthoringMetaV1.to_prefix_bytes()
952 );
953 assert_eq!(cbor_encoded[527], 0x02);
955 assert_eq!(cbor_encoded[528], 0b011_10000);
957 assert_eq!(&cbor_encoded[529..], "application/cbor".as_bytes());
959
960 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
962 assert_eq!(cbor_decoded.len(), 1);
964 assert_eq!(cbor_decoded[0], meta_map);
966
967 Ok(())
968 }
969
970 #[test]
973 fn dotrain_meta_roundtrip() -> Result<(), Error> {
974 let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
975 let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
976
977 let content_encoding = ContentEncoding::Deflate;
978 let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
979
980 let meta_map = RainMetaDocumentV1Item {
981 payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
982 magic: KnownMagic::DotrainV1,
983 content_type: ContentType::OctetStream,
984 content_encoding,
985 content_language: ContentLanguage::En,
986 schema: None,
987 };
988 let cbor_encoded = meta_map.cbor_encode()?;
989
990 assert_eq!(cbor_encoded[0], 0xa5);
992 assert_eq!(cbor_encoded[1], 0x00);
994 assert_eq!(cbor_encoded[2], 0b010_11000);
996 assert_eq!(cbor_encoded[3], 0b001_00100);
997 assert_eq!(cbor_encoded[4..40], deflated_payload);
1000 assert_eq!(cbor_encoded[40], 0x01);
1002 assert_eq!(cbor_encoded[41], 0b000_11011);
1004 assert_eq!(
1006 &cbor_encoded[42..50],
1007 KnownMagic::DotrainV1.to_prefix_bytes()
1008 );
1009 assert_eq!(cbor_encoded[50], 0x02);
1011 assert_eq!(cbor_encoded[51], 0b011_11000);
1013 assert_eq!(cbor_encoded[52], 0b000_11000);
1014 assert_eq!(&cbor_encoded[53..77], "application/octet-stream".as_bytes());
1016 assert_eq!(cbor_encoded[77], 0x03);
1018 assert_eq!(cbor_encoded[78], 0b011_00111);
1020 assert_eq!(&cbor_encoded[79..86], "deflate".as_bytes());
1022 assert_eq!(cbor_encoded[86], 0x04);
1024 assert_eq!(cbor_encoded[87], 0b011_00010);
1026 assert_eq!(&cbor_encoded[88..], "en".as_bytes());
1028
1029 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1031 assert_eq!(cbor_decoded.len(), 1);
1033 assert_eq!(cbor_decoded[0], meta_map);
1035
1036 Ok(())
1037 }
1038
1039 #[test]
1042 fn meta_seq_roundtrip() -> Result<(), Error> {
1043 let authoring_meta_content = r#"[
1044 {
1045 "word": "stack",
1046 "description": "Copies an existing value from the stack.",
1047 "operandParserOffset": 16
1048 },
1049 {
1050 "word": "constant",
1051 "description": "Copies a constant value onto the stack.",
1052 "operandParserOffset": 16
1053 }
1054 ]"#;
1055 let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
1056 let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
1057 let meta_map_1 = RainMetaDocumentV1Item {
1058 payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
1059 magic: KnownMagic::AuthoringMetaV1,
1060 content_type: ContentType::Cbor,
1061 content_encoding: ContentEncoding::None,
1062 content_language: ContentLanguage::None,
1063 schema: None,
1064 };
1065
1066 let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
1067 let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
1068 let content_encoding = ContentEncoding::Deflate;
1069 let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
1070 let meta_map_2 = RainMetaDocumentV1Item {
1071 payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
1072 magic: KnownMagic::DotrainV1,
1073 content_type: ContentType::OctetStream,
1074 content_encoding,
1075 content_language: ContentLanguage::En,
1076 schema: None,
1077 };
1078
1079 let cbor_encoded = RainMetaDocumentV1Item::cbor_encode_seq(
1081 &vec![meta_map_1.clone(), meta_map_2.clone()],
1082 KnownMagic::RainMetaDocumentV1,
1083 )?;
1084
1085 assert_eq!(
1087 &cbor_encoded[0..8],
1088 KnownMagic::RainMetaDocumentV1.to_prefix_bytes()
1089 );
1090
1091 assert_eq!(cbor_encoded[8], 0xa3);
1094 assert_eq!(cbor_encoded[9], 0x00);
1096 assert_eq!(cbor_encoded[10], 0b010_11001);
1098 assert_eq!(cbor_encoded[11], 0b000_00010);
1099 assert_eq!(cbor_encoded[12], 0b000_00000);
1100 assert_eq!(cbor_encoded[13..525], authoring_meta_abi_encoded);
1102 assert_eq!(cbor_encoded[525], 0x01);
1104 assert_eq!(cbor_encoded[526], 0b000_11011);
1106 assert_eq!(
1108 &cbor_encoded[527..535],
1109 KnownMagic::AuthoringMetaV1.to_prefix_bytes()
1110 );
1111 assert_eq!(cbor_encoded[535], 0x02);
1113 assert_eq!(cbor_encoded[536], 0b011_10000);
1115 assert_eq!(&cbor_encoded[537..553], "application/cbor".as_bytes());
1117
1118 assert_eq!(cbor_encoded[553], 0xa5);
1121 assert_eq!(cbor_encoded[554], 0x00);
1123 assert_eq!(cbor_encoded[555], 0b010_11000);
1125 assert_eq!(cbor_encoded[556], 0b001_00100);
1126 assert_eq!(cbor_encoded[557..593], deflated_payload);
1129 assert_eq!(cbor_encoded[593], 0x01);
1131 assert_eq!(cbor_encoded[594], 0b000_11011);
1133 assert_eq!(
1135 &cbor_encoded[595..603],
1136 KnownMagic::DotrainV1.to_prefix_bytes()
1137 );
1138 assert_eq!(cbor_encoded[603], 0x02);
1140 assert_eq!(cbor_encoded[604], 0b011_11000);
1142 assert_eq!(cbor_encoded[605], 0b000_11000);
1143 assert_eq!(
1145 &cbor_encoded[606..630],
1146 "application/octet-stream".as_bytes()
1147 );
1148 assert_eq!(cbor_encoded[630], 0x03);
1150 assert_eq!(cbor_encoded[631], 0b011_00111);
1152 assert_eq!(&cbor_encoded[632..639], "deflate".as_bytes());
1154 assert_eq!(cbor_encoded[639], 0x04);
1156 assert_eq!(cbor_encoded[640], 0b011_00010);
1158 assert_eq!(&cbor_encoded[641..], "en".as_bytes());
1160
1161 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1163 assert_eq!(cbor_decoded.len(), 2);
1165
1166 assert_eq!(cbor_decoded[0], meta_map_1);
1168 assert_eq!(cbor_decoded[1], meta_map_2);
1170
1171 Ok(())
1172 }
1173
1174 #[test]
1175 fn test_bytes32_to_str() {
1176 let text_bytes_list = vec![
1177 (
1178 "",
1179 hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1180 ),
1181 (
1182 "A",
1183 hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1184 ),
1185 (
1186 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1187 hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1188 ),
1189 (
1190 "!@#$%^&*(),./;'[]",
1191 hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1192 ),
1193 ];
1194
1195 for (text, bytes) in text_bytes_list {
1196 assert_eq!(text, bytes32_to_str(&bytes).unwrap());
1197 }
1198 }
1199
1200 #[test]
1201 fn test_str_to_bytes32() {
1202 let text_bytes_list = vec![
1203 (
1204 "",
1205 hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1206 ),
1207 (
1208 "A",
1209 hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1210 ),
1211 (
1212 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1213 hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1214 ),
1215 (
1216 "!@#$%^&*(),./;'[]",
1217 hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1218 ),
1219 ];
1220
1221 for (text, bytes) in text_bytes_list {
1222 assert_eq!(bytes, str_to_bytes32(text).unwrap());
1223 }
1224 }
1225
1226 #[test]
1227 fn test_str_to_bytes32_long() {
1228 assert!(matches!(
1229 str_to_bytes32("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456").unwrap_err(),
1230 Error::BiggerThan32Bytes
1231 ));
1232 }
1233
1234 #[test]
1238 fn test_str_to_bytes32_rejects_nul() {
1239 for text in [
1240 "\0",
1241 "\0a",
1242 "a\0",
1243 "a\0b",
1244 "abcdefghijklmnopqrstuvwxyz01234\0",
1245 ] {
1246 assert!(
1247 matches!(str_to_bytes32(text), Err(Error::NulByteInInput)),
1248 "nul bearing input {:?} accepted",
1249 text
1250 );
1251 }
1252 }
1253
1254 #[test]
1257 fn test_str_to_bytes32_round_trip() -> Result<(), Error> {
1258 let mut seen: Vec<[u8; 32]> = vec![];
1259 for text in [
1260 "",
1261 "a",
1262 "stack",
1263 "!@#$%^&*(),./;'[]",
1264 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1265 ] {
1266 let bytes = str_to_bytes32(text)?;
1267 assert_eq!(bytes32_to_str(&bytes)?, text);
1268 assert!(!seen.contains(&bytes), "input {:?} collided", text);
1269 seen.push(bytes);
1270 }
1271 Ok(())
1272 }
1273
1274 #[tokio::test]
1275 async fn test_implements_i_describe_by_meta_v1() {
1276 async fn new_server_client() -> (Asserter, impl Provider) {
1278 let asserter = Asserter::new();
1279 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
1280
1281 asserter.push_success(
1283 &"0x0000000000000000000000000000000000000000000000000000000000000001",
1284 );
1285 asserter.push_success(
1286 &"0x0000000000000000000000000000000000000000000000000000000000000000",
1287 );
1288
1289 (asserter, provider)
1290 }
1291
1292 let address = Address::random();
1293
1294 let (asserter, provider) = new_server_client().await;
1296 asserter
1297 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
1298 let result = implements_i_described_by_meta_v1(&provider, address)
1299 .await
1300 .unwrap();
1301 assert!(result);
1302
1303 let (asserter, provider) = new_server_client().await;
1305 asserter
1306 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
1307 let result = implements_i_described_by_meta_v1(&provider, address)
1308 .await
1309 .unwrap();
1310 assert!(!result);
1311
1312 let (asserter, provider) = new_server_client().await;
1314 asserter.push_failure(ErrorPayload {
1315 code: -32003,
1316 message: "execution reverted".into(),
1317 data: Some(serde_json::value::to_raw_value(&json!("0x00")).unwrap()),
1318 });
1319 let result = implements_i_described_by_meta_v1(&provider, address)
1320 .await
1321 .unwrap();
1322 assert!(!result);
1323 }
1324
1325 #[test]
1329 fn oa_schema_map_key_roundtrip() -> Result<(), Error> {
1330 let payload = vec![0x01, 0x02, 0x03];
1331 let schema = "QmSchemaHash1234567890abcdefghijklmnopqrstuvwx".to_string();
1334 assert_eq!(schema.len(), 46);
1335
1336 let meta_map = RainMetaDocumentV1Item {
1337 payload: serde_bytes::ByteBuf::from(payload.clone()),
1338 magic: KnownMagic::OaStructure,
1339 content_type: ContentType::Json,
1340 content_encoding: ContentEncoding::Deflate,
1341 content_language: ContentLanguage::None,
1342 schema: Some(schema.clone()),
1343 };
1344 let cbor_encoded = meta_map.cbor_encode()?;
1345
1346 assert_eq!(cbor_encoded[0], 0xa5);
1348 assert_eq!(cbor_encoded[1], 0x00);
1350 assert_eq!(cbor_encoded[2], 0b010_00011);
1352 assert_eq!(cbor_encoded[3..6], payload);
1354 assert_eq!(cbor_encoded[6], 0x01);
1356 assert_eq!(cbor_encoded[7], 0b000_11011);
1358 assert_eq!(
1360 &cbor_encoded[8..16],
1361 KnownMagic::OaStructure.to_prefix_bytes()
1362 );
1363 assert_eq!(cbor_encoded[16], 0x02);
1365 assert_eq!(cbor_encoded[17], 0b011_10000);
1367 assert_eq!(&cbor_encoded[18..34], "application/json".as_bytes());
1368 assert_eq!(cbor_encoded[34], 0x03);
1370 assert_eq!(cbor_encoded[35], 0b011_00111);
1372 assert_eq!(&cbor_encoded[36..43], "deflate".as_bytes());
1373 assert_eq!(cbor_encoded[43], 0b000_11011);
1375 assert_eq!(
1376 &cbor_encoded[44..52],
1377 KnownMagic::OaSchema.to_prefix_bytes()
1378 );
1379 assert_eq!(cbor_encoded[52], 0b011_11000);
1381 assert_eq!(cbor_encoded[53], 46);
1382 assert_eq!(&cbor_encoded[54..], schema.as_bytes());
1384
1385 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1387 assert_eq!(cbor_decoded.len(), 1);
1389 assert_eq!(cbor_decoded[0], meta_map);
1391
1392 Ok(())
1393 }
1394
1395 #[test]
1398 fn no_schema_key_encodes_as_before_roundtrip() -> Result<(), Error> {
1399 let payload = vec![0x0a, 0x0b];
1400 let meta_map = RainMetaDocumentV1Item {
1401 payload: serde_bytes::ByteBuf::from(payload.clone()),
1402 magic: KnownMagic::OaStructure,
1403 content_type: ContentType::None,
1404 content_encoding: ContentEncoding::None,
1405 content_language: ContentLanguage::None,
1406 schema: None,
1407 };
1408 let cbor_encoded = meta_map.cbor_encode()?;
1409
1410 assert_eq!(cbor_encoded[0], 0xa2);
1412 assert_eq!(cbor_encoded[1], 0x00);
1414 assert_eq!(cbor_encoded[2], 0b010_00010);
1416 assert_eq!(cbor_encoded[3..5], payload);
1418 assert_eq!(cbor_encoded[5], 0x01);
1420 assert_eq!(cbor_encoded[6], 0b000_11011);
1422 assert_eq!(
1424 &cbor_encoded[7..],
1425 KnownMagic::OaStructure.to_prefix_bytes()
1426 );
1427
1428 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1429 assert_eq!(cbor_decoded.len(), 1);
1430 assert_eq!(cbor_decoded[0], meta_map);
1431
1432 Ok(())
1433 }
1434
1435 #[test]
1438 fn unknown_map_key_index_is_ignored() -> Result<(), Error> {
1439 let mut bytes: Vec<u8> = vec![
1440 0xa3, 0x00, 0x40, 0x01, 0x1b,
1444 ];
1445 bytes.extend_from_slice(&KnownMagic::DotrainSourceV1.to_prefix_bytes());
1446 bytes.extend_from_slice(&[0x05, 0x07]);
1448
1449 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1450 assert_eq!(decoded.len(), 1);
1451 assert_eq!(decoded[0], plain_item(KnownMagic::DotrainSourceV1, vec![]));
1452
1453 Ok(())
1454 }
1455
1456 #[test]
1459 fn non_oa_schema_extra_map_key_is_ignored() -> Result<(), Error> {
1460 let mut bytes: Vec<u8> = vec![
1463 0xa3, 0x00, 0x41, 0xff, 0x01, 0x1b,
1467 ];
1468 bytes.extend_from_slice(&KnownMagic::OaStructure.to_prefix_bytes());
1469 bytes.push(0x1b);
1471 bytes.extend_from_slice(&KnownMagic::OaHashList.to_prefix_bytes());
1472 bytes.extend_from_slice(&[0x62, 0x68, 0x69]);
1474
1475 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1476 assert_eq!(decoded.len(), 1);
1477 let expected = plain_item(KnownMagic::OaStructure, vec![0xff]);
1478 assert_eq!(decoded[0], expected);
1479 assert_eq!(decoded[0].schema, None);
1480
1481 Ok(())
1482 }
1483
1484 #[test]
1487 fn unknown_map_key_consumes_its_whole_value() -> Result<(), Error> {
1488 let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1489 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1490 bytes.extend_from_slice(&[0x05, 0xa1, 0x18, 0x2a, 0x82, 0x01, 0x02]);
1492 bytes.extend_from_slice(&handwritten_map());
1493
1494 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1495 assert_eq!(decoded.len(), 2);
1496 let expected = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1497 assert_eq!(decoded[0], expected);
1498 assert_eq!(decoded[1], expected);
1499
1500 Ok(())
1501 }
1502
1503 #[test]
1506 fn ignored_map_key_is_absent_from_the_reencoding() -> Result<(), Error> {
1507 let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1508 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1509 bytes.extend_from_slice(&[0x05, 0x07]);
1510
1511 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1512 assert_eq!(decoded[0].cbor_encode()?, handwritten_map());
1513 assert_eq!(decoded[0].hash(false)?, keccak256(handwritten_map()).0);
1514 assert_ne!(decoded[0].hash(false)?, keccak256(&bytes).0);
1515
1516 Ok(())
1517 }
1518
1519 #[test]
1523 fn non_integer_map_key_errors() {
1524 let mut text_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1525 text_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1526 text_key.extend_from_slice(&[0x61, 0x35, 0x07]);
1528 assert!(matches!(
1529 RainMetaDocumentV1Item::cbor_decode(&text_key),
1530 Err(Error::SerdeCborError(_))
1531 ));
1532
1533 let mut negative_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1534 negative_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1535 negative_key.extend_from_slice(&[0x20, 0x07]);
1537 assert!(matches!(
1538 RainMetaDocumentV1Item::cbor_decode(&negative_key),
1539 Err(Error::SerdeCborError(_))
1540 ));
1541 }
1542
1543 #[test]
1547 fn unknown_map_key_does_not_stand_in_for_a_mandatory_key() {
1548 let mut bytes: Vec<u8> = vec![0xa2, 0x05, 0x07, 0x01, 0x1b];
1549 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1550 assert!(RainMetaDocumentV1Item::cbor_decode(&bytes).is_err());
1551 }
1552
1553 fn plain_item(magic: KnownMagic, payload: Vec<u8>) -> RainMetaDocumentV1Item {
1554 RainMetaDocumentV1Item {
1555 payload: serde_bytes::ByteBuf::from(payload),
1556 magic,
1557 content_type: ContentType::None,
1558 content_encoding: ContentEncoding::None,
1559 content_language: ContentLanguage::None,
1560 schema: None,
1561 }
1562 }
1563
1564 fn sample_authoring_doc() -> (AuthoringMeta, Vec<u8>) {
1567 let authoring_meta: AuthoringMeta = serde_json::from_str(
1568 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
1569 )
1570 .unwrap();
1571 let abi = authoring_meta.abi_encode_validate().unwrap();
1572 let item = RainMetaDocumentV1Item {
1573 payload: serde_bytes::ByteBuf::from(abi),
1574 magic: KnownMagic::AuthoringMetaV1,
1575 content_type: ContentType::Cbor,
1576 content_encoding: ContentEncoding::None,
1577 content_language: ContentLanguage::None,
1578 schema: None,
1579 };
1580 let doc =
1581 RainMetaDocumentV1Item::cbor_encode_seq(&vec![item], KnownMagic::RainMetaDocumentV1)
1582 .unwrap();
1583 (authoring_meta, doc)
1584 }
1585
1586 fn sample_dotrain_item() -> RainMetaDocumentV1Item {
1587 RainMetaDocumentV1Item {
1588 payload: serde_bytes::ByteBuf::from("some dotrain body".as_bytes()),
1589 magic: KnownMagic::DotrainV1,
1590 content_type: ContentType::OctetStream,
1591 content_encoding: ContentEncoding::None,
1592 content_language: ContentLanguage::None,
1593 schema: None,
1594 }
1595 }
1596
1597 fn handwritten_map() -> Vec<u8> {
1600 vec![
1601 0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b, 0xff, 0xda, 0xc2, 0xf2, 0xf3, 0x7b, 0xe8, 0x94, ]
1607 }
1608
1609 #[test]
1613 fn test_hash_bare_vs_document() -> Result<(), Error> {
1614 let map_bytes = handwritten_map();
1615 let mut doc_bytes: Vec<u8> = vec![0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74];
1616 doc_bytes.extend_from_slice(&map_bytes);
1617
1618 let item = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1619 assert_eq!(item.hash(false)?, keccak256(&map_bytes).0);
1620 assert_eq!(item.hash(true)?, keccak256(&doc_bytes).0);
1621 assert_ne!(item.hash(false)?, item.hash(true)?);
1622 Ok(())
1623 }
1624
1625 #[test]
1627 fn test_cbor_decode_empty_is_corrupt() {
1628 assert!(matches!(
1629 RainMetaDocumentV1Item::cbor_decode(&[]),
1630 Err(Error::CorruptMeta)
1631 ));
1632 let prefix = KnownMagic::RainMetaDocumentV1.to_prefix_bytes();
1633 assert!(matches!(
1634 RainMetaDocumentV1Item::cbor_decode(&prefix),
1635 Err(Error::CorruptMeta)
1636 ));
1637 }
1638
1639 #[test]
1642 fn test_cbor_decode_trailing_truncated_is_corrupt() {
1643 let mut bytes = handwritten_map();
1644 bytes.push(0x1b); assert!(matches!(
1646 RainMetaDocumentV1Item::cbor_decode(&bytes),
1647 Err(Error::CorruptMeta)
1648 ));
1649 }
1650
1651 #[test]
1655 fn test_cbor_decode_truncated_item_is_corrupt() {
1656 let mut sole = handwritten_map();
1657 sole.pop();
1658 assert!(matches!(
1659 RainMetaDocumentV1Item::cbor_decode(&sole),
1660 Err(Error::CorruptMeta)
1661 ));
1662
1663 assert!(matches!(
1664 RainMetaDocumentV1Item::cbor_decode(&[0xa2, 0x00, 0x41, 0x01]),
1665 Err(Error::CorruptMeta)
1666 ));
1667
1668 let mut after_complete = handwritten_map();
1669 after_complete.extend_from_slice(&[0xa2, 0x00]);
1670 assert!(matches!(
1671 RainMetaDocumentV1Item::cbor_decode(&after_complete),
1672 Err(Error::CorruptMeta)
1673 ));
1674 }
1675
1676 #[test]
1679 fn test_cbor_decode_trailing_garbage_errors() {
1680 let mut bytes = handwritten_map();
1681 bytes.push(0xff); assert!(matches!(
1683 RainMetaDocumentV1Item::cbor_decode(&bytes),
1684 Err(Error::SerdeCborError(_))
1685 ));
1686 }
1687
1688 #[test]
1693 fn test_cbor_decode_missing_payload_is_corrupt() {
1694 let mut bytes: Vec<u8> = vec![0xa1, 0x01, 0x1b]; bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1696 assert!(RainMetaDocumentV1Item::cbor_decode(&bytes).is_err());
1697
1698 let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1700 .cbor_encode()
1701 .unwrap();
1702 let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1703 document.extend_from_slice(&bytes);
1704 document.extend_from_slice(&good);
1705 assert!(RainMetaDocumentV1Item::cbor_decode(&document).is_err());
1706 }
1707
1708 #[test]
1710 fn test_cbor_decode_missing_magic_is_corrupt() {
1711 let bytes: Vec<u8> = vec![0xa1, 0x00, 0x41, 0x01]; assert!(RainMetaDocumentV1Item::cbor_decode(&bytes).is_err());
1713
1714 let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1715 .cbor_encode()
1716 .unwrap();
1717 let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1718 document.extend_from_slice(&bytes);
1719 document.extend_from_slice(&good);
1720 assert!(RainMetaDocumentV1Item::cbor_decode(&document).is_err());
1721 }
1722
1723 #[test]
1728 fn test_cbor_decode_unknown_magic_is_dropped() {
1729 let mut bytes: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1730 bytes.extend_from_slice(&0xdeadbeefdeadbeefu64.to_be_bytes());
1731 assert!(matches!(
1732 RainMetaDocumentV1Item::cbor_decode(&bytes),
1733 Err(Error::CorruptMeta)
1734 ));
1735 }
1736
1737 #[test]
1741 fn test_cbor_decode_drops_only_the_unknown_magic_item() {
1742 let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1743 .cbor_encode()
1744 .unwrap();
1745
1746 let mut unknown_magic: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1747 unknown_magic.extend_from_slice(&0xdeadbeefdeadbeefu64.to_be_bytes());
1748
1749 let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1750 document.extend_from_slice(&unknown_magic);
1751 document.extend_from_slice(&good);
1752
1753 let items = RainMetaDocumentV1Item::cbor_decode(&document).unwrap();
1754 assert_eq!(items.len(), 1, "expected only the good item");
1755 assert_eq!(items[0].magic, KnownMagic::DotrainV1);
1756 assert_eq!(items[0].payload.as_ref(), &[0x42]);
1757 }
1758
1759 #[test]
1764 fn test_cbor_decode_nested_document_magic_is_not_dropped() {
1765 let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1766 .cbor_encode()
1767 .unwrap();
1768
1769 let mut nested: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1770 nested.extend_from_slice(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes());
1771
1772 let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1773 document.extend_from_slice(&nested);
1774 document.extend_from_slice(&good);
1775
1776 assert!(matches!(
1777 RainMetaDocumentV1Item::cbor_decode(&document),
1778 Err(Error::SerdeCborError(_))
1779 ));
1780 }
1781
1782 fn handwritten_entries(entries: &[(Vec<u8>, Vec<u8>)]) -> Vec<u8> {
1786 assert!(entries.len() < 24);
1787 let mut bytes = vec![0xa0 | entries.len() as u8];
1788 for (key, value) in entries {
1789 bytes.extend_from_slice(key);
1790 bytes.extend_from_slice(value);
1791 }
1792 bytes
1793 }
1794
1795 fn handwritten_magic(magic: KnownMagic) -> Vec<u8> {
1797 let mut bytes = vec![0x1b];
1798 bytes.extend_from_slice(&magic.to_prefix_bytes());
1799 bytes
1800 }
1801
1802 fn handwritten_text(text: &str) -> Vec<u8> {
1804 assert!(text.len() < 24);
1805 let mut bytes = vec![0x60 | text.len() as u8];
1806 bytes.extend_from_slice(text.as_bytes());
1807 bytes
1808 }
1809
1810 #[test]
1813 fn test_cbor_decode_duplicate_payload_key_errors() {
1814 let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x00, 0x41, 0x02, 0x01, 0x1b];
1815 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1816 let error = RainMetaDocumentV1Item::cbor_decode(&bytes).unwrap_err();
1817 assert!(matches!(error, Error::SerdeCborError(_)));
1818 assert!(
1819 error.to_string().contains("duplicate field `payload`"),
1820 "{error}"
1821 );
1822 }
1823
1824 #[test]
1827 fn test_cbor_decode_duplicate_any_key_errors() -> Result<(), Error> {
1828 let cases: [(Vec<u8>, Vec<u8>, Vec<u8>); 6] = [
1829 (vec![0x00], vec![0x41, 0x01], vec![0x41, 0x02]),
1830 (
1831 vec![0x01],
1832 handwritten_magic(KnownMagic::DotrainV1),
1833 handwritten_magic(KnownMagic::RainlangV1),
1834 ),
1835 (
1836 vec![0x02],
1837 handwritten_text("application/cbor"),
1838 handwritten_text("application/json"),
1839 ),
1840 (
1841 vec![0x03],
1842 handwritten_text("identity"),
1843 handwritten_text("deflate"),
1844 ),
1845 (vec![0x04], handwritten_text("en"), handwritten_text("none")),
1846 (
1847 handwritten_magic(KnownMagic::OaSchema),
1848 handwritten_text("hi"),
1849 handwritten_text("bye"),
1850 ),
1851 ];
1852 let base: Vec<(Vec<u8>, Vec<u8>)> = cases
1853 .iter()
1854 .map(|(key, value, _)| (key.clone(), value.clone()))
1855 .collect();
1856
1857 let decoded = RainMetaDocumentV1Item::cbor_decode(&handwritten_entries(&base))?;
1858 assert_eq!(decoded.len(), 1);
1859 assert_eq!(decoded[0].payload.as_ref(), &[0x01]);
1860 assert_eq!(decoded[0].magic, KnownMagic::DotrainV1);
1861 assert_eq!(decoded[0].content_type, ContentType::Cbor);
1862 assert_eq!(decoded[0].content_encoding, ContentEncoding::Identity);
1863 assert_eq!(decoded[0].content_language, ContentLanguage::En);
1864 assert_eq!(decoded[0].schema.as_deref(), Some("hi"));
1865
1866 for (key, value, other_value) in &cases {
1867 for repeated in [value, other_value] {
1868 let mut entries = base.clone();
1869 entries.push((key.clone(), repeated.clone()));
1870 let error = RainMetaDocumentV1Item::cbor_decode(&handwritten_entries(&entries))
1871 .unwrap_err();
1872 assert!(
1873 matches!(error, Error::SerdeCborError(_)),
1874 "{key:?} {error:?}"
1875 );
1876 assert!(
1877 error.to_string().contains("duplicate field"),
1878 "{key:?} {error}"
1879 );
1880 }
1881 }
1882 Ok(())
1883 }
1884
1885 #[test]
1890 fn test_cbor_decode_duplicate_unknown_key_errors() -> Result<(), Error> {
1891 let base: Vec<(Vec<u8>, Vec<u8>)> = vec![
1892 (vec![0x00], vec![0x41, 0x01]),
1893 (vec![0x01], handwritten_magic(KnownMagic::DotrainV1)),
1894 ];
1895 let unknown: [(Vec<u8>, Vec<u8>, Vec<u8>); 2] = [
1898 (vec![0x05], vec![0x07], vec![0x08]),
1899 (
1900 handwritten_magic(KnownMagic::OaHashList),
1901 handwritten_text("hi"),
1902 handwritten_text("bye"),
1903 ),
1904 ];
1905
1906 for (key, value, other_value) in &unknown {
1907 let mut entries = base.clone();
1908 entries.push((key.clone(), value.clone()));
1909 let decoded = RainMetaDocumentV1Item::cbor_decode(&handwritten_entries(&entries))?;
1910 assert_eq!(decoded, vec![plain_item(KnownMagic::DotrainV1, vec![0x01])]);
1911
1912 for repeated in [value, other_value] {
1913 let mut duplicated = entries.clone();
1914 duplicated.push((key.clone(), repeated.clone()));
1915 let error = RainMetaDocumentV1Item::cbor_decode(&handwritten_entries(&duplicated))
1916 .unwrap_err();
1917 assert!(
1918 matches!(error, Error::SerdeCborError(_)),
1919 "{key:?} {error:?}"
1920 );
1921 assert!(
1922 error.to_string().contains("duplicate map key"),
1923 "{key:?} {error}"
1924 );
1925 }
1926 }
1927 Ok(())
1928 }
1929
1930 #[test]
1934 fn test_cbor_decode_handwritten_document_magic_item() {
1935 let bytes: Vec<u8> = vec![
1936 0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b, 0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74, ];
1942 assert!(RainMetaDocumentV1Item::cbor_decode(&bytes).is_err());
1946 }
1947
1948 #[test]
1954 fn test_document_magic_item_carries_a_nested_document() -> Result<(), Error> {
1955 let inner = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1956 let inner_doc = RainMetaDocumentV1Item::cbor_encode_seq(
1957 &vec![inner.clone()],
1958 KnownMagic::RainMetaDocumentV1,
1959 )?;
1960 let outer = plain_item(KnownMagic::RainMetaDocumentV1, inner_doc.clone());
1961 let outer_doc = RainMetaDocumentV1Item::cbor_encode_seq(
1962 &vec![outer.clone()],
1963 KnownMagic::RainMetaDocumentV1,
1964 )?;
1965
1966 assert!(RainMetaDocumentV1Item::cbor_decode(&outer_doc).is_err());
1972 assert_eq!(
1973 RainMetaDocumentV1Item::cbor_decode(&inner_doc)?,
1974 vec![inner]
1975 );
1976 Ok(())
1977 }
1978
1979 #[test]
1982 fn test_document_magic_item_is_not_unpackable() {
1983 assert!(matches!(
1984 KnownMeta::try_from(KnownMagic::RainMetaDocumentV1),
1985 Err(Error::UnsupportedMeta)
1986 ));
1987 assert!(matches!(
1988 plain_item(KnownMagic::RainMetaDocumentV1, vec![0x01]).unpack_into::<Vec<u8>>(),
1989 Err(Error::UnsupportedMeta)
1990 ));
1991 }
1992
1993 #[test]
1995 fn test_unpack_decodes_content_encoding() -> Result<(), Error> {
1996 let content = b"unpack me via deflate".to_vec();
1997 let packed = ContentEncoding::Deflate.encode(&content);
1998 assert_ne!(packed, content);
1999 let mut item = plain_item(KnownMagic::DotrainV1, packed);
2000 item.content_encoding = ContentEncoding::Deflate;
2001 assert_eq!(item.unpack()?, content);
2002
2003 let item = plain_item(KnownMagic::DotrainV1, content.clone());
2004 assert_eq!(item.unpack()?, content);
2005 Ok(())
2006 }
2007
2008 #[test]
2011 fn test_unpack_into_whitelist() {
2012 use strum::IntoEnumIterator;
2013 let supported = [
2014 KnownMagic::OpMetaV1,
2015 KnownMagic::DotrainV1,
2016 KnownMagic::RainlangV1,
2017 KnownMagic::SolidityAbiV2,
2018 KnownMagic::AuthoringMetaV1,
2019 KnownMagic::AuthoringMetaV2,
2020 KnownMagic::AddressList,
2021 KnownMagic::InterpreterCallerMetaV1,
2022 KnownMagic::ExpressionDeployerV2BytecodeV1,
2023 KnownMagic::DotrainSourceV1,
2024 KnownMagic::OrderBuilderStateV1,
2025 KnownMagic::RainlangSourceV1,
2026 KnownMagic::RaindexSignedContextOracleV1,
2027 ];
2028 for magic in supported {
2029 let unpacked: Vec<u8> = plain_item(magic, vec![0x61]).unpack_into().unwrap();
2030 assert_eq!(unpacked, vec![0x61], "{:?}", magic);
2031 }
2032 let unsupported = [
2033 KnownMagic::RainMetaDocumentV1,
2034 KnownMagic::WebDataV1,
2035 KnownMagic::OaSchema,
2036 KnownMagic::OaHashList,
2037 KnownMagic::OaStructure,
2038 KnownMagic::OaTokenImage,
2039 KnownMagic::OaTokenCredentialLinks,
2040 ];
2041 for magic in unsupported {
2042 let result: Result<Vec<u8>, Error> = plain_item(magic, vec![0x61]).unpack_into();
2043 assert!(matches!(result, Err(Error::UnsupportedMeta)), "{:?}", magic);
2044 }
2045 assert_eq!(
2047 supported.len() + unsupported.len(),
2048 KnownMagic::iter().count()
2049 );
2050 }
2051
2052 #[test]
2055 fn test_try_into_string_invalid_utf8_errors() {
2056 let item = plain_item(KnownMagic::DotrainV1, vec![0xff, 0xfe]);
2057 let result: Result<String, Error> = item.try_into();
2058 assert!(matches!(result, Err(Error::FromUtf8Error(_))));
2059 }
2060
2061 #[test]
2063 fn test_try_into_vec_decodes_encoding() -> Result<(), Error> {
2064 let content = b"raw bytecode bytes \x00\x01\x02".to_vec();
2065 let packed = ContentEncoding::Deflate.encode(&content);
2066 let mut item = plain_item(KnownMagic::ExpressionDeployerV2BytecodeV1, packed.clone());
2067 item.content_encoding = ContentEncoding::Deflate;
2068 let unpacked: Vec<u8> = item.try_into()?;
2069 assert_eq!(unpacked, content);
2070 assert_ne!(unpacked, packed);
2071 Ok(())
2072 }
2073
2074 #[test]
2077 fn test_content_encoding_deflate_roundtrip() -> Result<(), Error> {
2078 let content = b"hello rain deflate fixture hello rain deflate fixture".to_vec();
2079 let encoded = ContentEncoding::Deflate.encode(&content);
2080 assert_ne!(encoded, content);
2081 assert_eq!(encoded[0], 0x78);
2082 assert_eq!(ContentEncoding::Deflate.decode(&encoded)?, content);
2083 Ok(())
2084 }
2085
2086 #[test]
2088 fn test_content_encoding_passthrough() -> Result<(), Error> {
2089 let data = vec![0x00, 0xff, 0x10];
2090 for encoding in [ContentEncoding::None, ContentEncoding::Identity] {
2091 assert_eq!(encoding.encode(&data), data, "{:?}", encoding);
2092 assert_eq!(encoding.decode(&data)?, data, "{:?}", encoding);
2093 }
2094 Ok(())
2095 }
2096
2097 #[test]
2100 fn test_content_encoding_decode_fixtures() -> Result<(), Error> {
2101 let content = b"hello rain deflate fixture".to_vec();
2102 let zlib: Vec<u8> = vec![
2103 120, 156, 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44,
2104 73, 85, 72, 203, 172, 40, 41, 45, 74, 5, 0, 132, 64, 9, 251,
2105 ];
2106 let raw: Vec<u8> = vec![
2107 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44, 73, 85, 72,
2108 203, 172, 40, 41, 45, 74, 5, 0,
2109 ];
2110 assert_eq!(ContentEncoding::Deflate.decode(&zlib)?, content);
2111 assert_eq!(ContentEncoding::Deflate.decode(&raw)?, content);
2112 Ok(())
2113 }
2114
2115 #[test]
2118 fn test_content_encoding_decode_garbage_errors() {
2119 let garbage = [0xffu8, 0xff, 0xff, 0xff];
2120 assert!(matches!(
2121 ContentEncoding::Deflate.decode(&garbage),
2122 Err(Error::InflateError(_))
2123 ));
2124 }
2125
2126 #[test]
2128 fn test_content_headers_strum_names() {
2129 use std::str::FromStr;
2130 assert_eq!(
2131 ContentEncoding::from_str("deflate").unwrap(),
2132 ContentEncoding::Deflate
2133 );
2134 assert_eq!(
2135 ContentEncoding::from_str("identity").unwrap(),
2136 ContentEncoding::Identity
2137 );
2138 assert_eq!(
2139 ContentEncoding::from_str("none").unwrap(),
2140 ContentEncoding::None
2141 );
2142 assert_eq!(ContentEncoding::Deflate.to_string(), "deflate");
2143 assert_eq!(
2144 ContentType::from_str("octet-stream").unwrap(),
2145 ContentType::OctetStream
2146 );
2147 assert_eq!(ContentType::from_str("json").unwrap(), ContentType::Json);
2148 assert_eq!(ContentType::Json.to_string(), "json");
2149 assert_eq!(
2150 ContentLanguage::from_str("en").unwrap(),
2151 ContentLanguage::En
2152 );
2153 }
2154
2155 #[test]
2158 fn test_known_meta_try_from_magic() {
2159 let cases: [(KnownMagic, KnownMeta); 13] = [
2160 (KnownMagic::OpMetaV1, KnownMeta::OpV1),
2161 (KnownMagic::DotrainV1, KnownMeta::DotrainV1),
2162 (KnownMagic::RainlangV1, KnownMeta::RainlangV1),
2163 (KnownMagic::SolidityAbiV2, KnownMeta::SolidityAbiV2),
2164 (KnownMagic::AuthoringMetaV1, KnownMeta::AuthoringMetaV1),
2165 (KnownMagic::AuthoringMetaV2, KnownMeta::AuthoringMetaV2),
2166 (KnownMagic::AddressList, KnownMeta::AddressList),
2167 (
2168 KnownMagic::InterpreterCallerMetaV1,
2169 KnownMeta::InterpreterCallerMetaV1,
2170 ),
2171 (
2172 KnownMagic::ExpressionDeployerV2BytecodeV1,
2173 KnownMeta::ExpressionDeployerV2BytecodeV1,
2174 ),
2175 (KnownMagic::RainlangSourceV1, KnownMeta::RainlangSourceV1),
2176 (KnownMagic::DotrainSourceV1, KnownMeta::DotrainSourceV1),
2177 (
2178 KnownMagic::OrderBuilderStateV1,
2179 KnownMeta::OrderBuilderStateV1,
2180 ),
2181 (
2182 KnownMagic::RaindexSignedContextOracleV1,
2183 KnownMeta::RaindexSignedContextOracleV1,
2184 ),
2185 ];
2186 for (magic, meta) in cases {
2187 assert_eq!(KnownMeta::try_from(magic).unwrap(), meta, "{:?}", magic);
2188 }
2189 for magic in [
2190 KnownMagic::RainMetaDocumentV1,
2191 KnownMagic::WebDataV1,
2192 KnownMagic::OaSchema,
2193 KnownMagic::OaHashList,
2194 KnownMagic::OaStructure,
2195 KnownMagic::OaTokenImage,
2196 KnownMagic::OaTokenCredentialLinks,
2197 ] {
2198 assert!(
2199 matches!(KnownMeta::try_from(magic), Err(Error::UnsupportedMeta)),
2200 "{:?}",
2201 magic
2202 );
2203 }
2204 }
2205
2206 #[test]
2209 fn test_known_meta_strum_parse_display() {
2210 use std::str::FromStr;
2211 assert_eq!(
2212 KnownMeta::from_str("solidity-abi-v2").unwrap(),
2213 KnownMeta::SolidityAbiV2
2214 );
2215 assert_eq!(
2216 KnownMeta::from_str("interpreter-caller-meta-v1").unwrap(),
2217 KnownMeta::InterpreterCallerMetaV1
2218 );
2219 assert_eq!(KnownMeta::SolidityAbiV2.to_string(), "solidity-abi-v2");
2220 }
2221
2222 #[tokio::test]
2224 async fn test_search_lowercases_hash() {
2225 use httpmock::prelude::*;
2226 let (_, doc) = sample_authoring_doc();
2227 let hash_upper = format!("0x{}", "AB".repeat(32));
2228 let server = MockServer::start();
2229 let mock = server.mock(|when, then| {
2230 when.method(POST)
2231 .body_contains(hash_upper.to_ascii_lowercase());
2232 then.status(200).json_body(json!({
2233 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2234 }));
2235 });
2236 let response = search(&hash_upper, &vec![server.url("/sg")]).await.unwrap();
2237 assert_eq!(response.bytes, doc);
2238 mock.assert();
2239 }
2240
2241 #[tokio::test]
2244 async fn test_search_first_success_wins() {
2245 use httpmock::prelude::*;
2246 let (_, doc) = sample_authoring_doc();
2247 let bad = MockServer::start();
2248 let _bad_mock = bad.mock(|when, then| {
2249 when.method(POST);
2250 then.status(500).body("subgraph down");
2251 });
2252 let good = MockServer::start();
2253 let _good_mock = good.mock(|when, then| {
2254 when.method(POST);
2255 then.status(200).json_body(json!({
2256 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2257 }));
2258 });
2259 let response = search(
2260 &format!("0x{}", "11".repeat(32)),
2261 &vec![bad.url("/sg"), good.url("/sg")],
2262 )
2263 .await
2264 .unwrap();
2265 assert_eq!(response.bytes, doc);
2266 }
2267
2268 #[tokio::test]
2272 async fn test_search_empty_subgraphs_is_a_miss() {
2273 let hash = format!("0x{}", "33".repeat(32));
2274 assert!(matches!(
2275 search(&hash, &vec![]).await,
2276 Err(Error::NoRecordFound)
2277 ));
2278 }
2279
2280 #[tokio::test]
2291 async fn test_implements_erc165_gate_short_circuits() {
2292 let address = Address::random();
2293
2294 let asserter = Asserter::new();
2296 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2297 asserter
2298 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2299 asserter
2300 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2301 assert!(!implements_i_described_by_meta_v1(&provider, address)
2302 .await
2303 .unwrap());
2304
2305 let asserter = Asserter::new();
2309 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2310 asserter.push_failure(ErrorPayload {
2311 code: -32000,
2312 message: "connection reset".into(),
2313 data: None,
2314 });
2315 asserter
2316 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2317 assert!(implements_i_described_by_meta_v1(&provider, address)
2318 .await
2319 .is_err());
2320 }
2321
2322 #[tokio::test]
2325 async fn test_implements_empty_response_is_false() {
2326 let address = Address::random();
2327 let asserter = Asserter::new();
2328 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2329 asserter
2330 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2331 asserter
2332 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2333 asserter.push_success(&"0x");
2334 assert!(!implements_i_described_by_meta_v1(&provider, address)
2335 .await
2336 .unwrap());
2337 }
2338
2339 #[tokio::test]
2343 async fn test_implements_described_by_call_error_is_unknown() {
2344 let address = Address::random();
2345 let asserter = Asserter::new();
2346 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2347 asserter
2348 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2349 asserter
2350 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2351 asserter.push_failure(ErrorPayload {
2352 code: -32005,
2353 message: "rate limit exceeded".into(),
2354 data: None,
2355 });
2356 let error = implements_i_described_by_meta_v1(&provider, address)
2357 .await
2358 .unwrap_err();
2359 assert!(error.to_string().contains("rate limit exceeded"));
2360 }
2361
2362 #[tokio::test]
2366 async fn test_implements_undecodable_response_is_unknown() {
2367 let address = Address::random();
2368 let asserter = Asserter::new();
2369 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2370 asserter
2371 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2372 asserter
2373 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2374 asserter.push_success(&"0xdeadbeef");
2375 implements_i_described_by_meta_v1(&provider, address)
2376 .await
2377 .unwrap_err();
2378 }
2379
2380 #[tokio::test]
2384 async fn test_store_constructors_inject_no_subgraphs() {
2385 assert!(Store::new().subgraphs().is_empty());
2386 assert!(Store::default().subgraphs().is_empty());
2387 assert!(
2388 Store::create(&vec![], &MetaCache::default(), &HashMap::new())
2389 .subgraphs()
2390 .is_empty()
2391 );
2392
2393 let hash = [0u8; 32];
2394 let mut store = Store::default();
2395 assert!(store.update(&hash).await.is_err());
2396 }
2397
2398 #[test]
2406 fn test_store_create_validates_entries() {
2407 let (_, doc) = sample_authoring_doc();
2408 let good_hash = keccak256(&doc).0.to_vec();
2409 let mut cache = MetaCache::default();
2410 cache.insert_verified(&good_hash, doc.clone()).unwrap();
2411 let mut dotrain_cache = HashMap::new();
2412 dotrain_cache.insert("a.rain".to_string(), good_hash.clone());
2413 dotrain_cache.insert("missing.rain".to_string(), vec![0x44u8; 32]);
2414
2415 let store = Store::create(
2416 &vec!["https://example.com/custom-sg".to_string()],
2417 &cache,
2418 &dotrain_cache,
2419 );
2420
2421 assert_eq!(
2422 store.subgraphs(),
2423 &vec!["https://example.com/custom-sg".to_string()]
2424 );
2425 assert_eq!(store.get_meta(&good_hash), Some(&doc));
2426 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&good_hash));
2427 assert_eq!(store.get_dotrain_hash("missing.rain"), None);
2428 }
2429
2430 #[test]
2432 fn test_store_add_subgraphs_dedupe() {
2433 let mut store = Store::new();
2434 store.add_subgraphs(&vec!["sg-a".to_string()]);
2435 store.add_subgraphs(&vec!["sg-a".to_string(), "sg-b".to_string()]);
2436 assert_eq!(
2437 store.subgraphs(),
2438 &vec!["sg-a".to_string(), "sg-b".to_string()]
2439 );
2440 }
2441
2442 #[test]
2446 fn test_store_dotrain_getters_and_set_fresh() {
2447 let mut store = Store::new();
2448 let text = "some dotrain content";
2449 let (hash, old) = store.set_dotrain(text, "file.rain", false).unwrap();
2450 assert!(old.is_empty());
2451 let expected_item = RainMetaDocumentV1Item {
2452 payload: serde_bytes::ByteBuf::from(text.as_bytes()),
2453 magic: KnownMagic::DotrainV1,
2454 content_type: ContentType::OctetStream,
2455 content_encoding: ContentEncoding::None,
2456 content_language: ContentLanguage::None,
2457 schema: None,
2458 };
2459 let expected_bytes = expected_item.cbor_encode().unwrap();
2460 assert_eq!(hash, keccak256(&expected_bytes).0.to_vec());
2461 assert_eq!(store.get_dotrain_hash("file.rain"), Some(&hash));
2462 assert_eq!(store.get_dotrain_uri(&hash), Some(&"file.rain".to_string()));
2463 assert_eq!(store.get_dotrain_meta("file.rain"), Some(&expected_bytes));
2464 assert_eq!(store.get_dotrain_hash("other.rain"), None);
2465 assert_eq!(store.get_dotrain_uri(&[0u8; 32]), None);
2466 assert_eq!(store.get_dotrain_meta("other.rain"), None);
2467 }
2468
2469 #[test]
2473 fn test_store_set_dotrain_branches() {
2474 let mut store = Store::new();
2475 let (hash_one, _) = store.set_dotrain("text one", "a.rain", false).unwrap();
2476
2477 let (hash_same, old_same) = store.set_dotrain("text one", "a.rain", false).unwrap();
2479 assert_eq!(hash_same, hash_one);
2480 assert!(old_same.is_empty());
2481 assert!(store.get_meta(&hash_one).is_some());
2482
2483 let (hash_two, old_two) = store.set_dotrain("text two", "a.rain", false).unwrap();
2485 assert_ne!(hash_two, hash_one);
2486 assert_eq!(old_two, hash_one);
2487 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_two));
2488 assert!(store.get_meta(&hash_one).is_none());
2489 assert!(store.get_meta(&hash_two).is_some());
2490
2491 let (hash_three, old_three) = store.set_dotrain("text three", "a.rain", true).unwrap();
2493 assert_eq!(old_three, hash_two);
2494 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_three));
2495 assert!(store.get_meta(&hash_two).is_some());
2496 assert!(store.get_meta(&hash_three).is_some());
2497 }
2498
2499 #[test]
2502 fn test_store_delete_dotrain_keep_meta() {
2503 let mut store = Store::new();
2504 let (hash, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2505 store.delete_dotrain("d.rain", false);
2506 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2507 assert!(store.get_meta(&hash).is_none());
2508
2509 let (hash_again, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2510 store.delete_dotrain("d.rain", true);
2511 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2512 assert!(store.get_meta(&hash_again).is_some());
2513 }
2514
2515 #[test]
2518 fn test_store_merge_semantics() {
2519 let mut ours = Store::new();
2520 let mut theirs = Store::new();
2521
2522 let mine = b"mine".to_vec();
2526 let yours = b"yours".to_vec();
2527 let mine_hash = keccak256(&mine).0.to_vec();
2528 let yours_hash = keccak256(&yours).0.to_vec();
2529 ours.update_with(&mine_hash, &mine).unwrap();
2530 theirs.update_with(&yours_hash, &yours).unwrap();
2531
2532 let (hash_ours, _) = ours.set_dotrain("content a", "x.rain", false).unwrap();
2534 let (_hash_theirs, _) = theirs.set_dotrain("content b", "x.rain", false).unwrap();
2535
2536 theirs.add_subgraphs(&vec!["sg-their".to_string()]);
2537
2538 ours.merge(&theirs);
2539
2540 assert_eq!(ours.get_meta(&mine_hash), Some(&mine));
2541 assert_eq!(ours.get_meta(&yours_hash), Some(&yours));
2542 assert_eq!(ours.get_dotrain_hash("x.rain"), Some(&hash_ours));
2544 assert!(ours.subgraphs().contains(&"sg-their".to_string()));
2546 }
2547
2548 #[tokio::test]
2552 async fn test_store_update_and_update_check() {
2553 use httpmock::prelude::*;
2554 let authoring_meta: AuthoringMeta = serde_json::from_str(
2555 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
2556 )
2557 .unwrap();
2558 let item_one = RainMetaDocumentV1Item {
2559 payload: serde_bytes::ByteBuf::from(authoring_meta.abi_encode_validate().unwrap()),
2560 magic: KnownMagic::AuthoringMetaV1,
2561 content_type: ContentType::Cbor,
2562 content_encoding: ContentEncoding::None,
2563 content_language: ContentLanguage::None,
2564 schema: None,
2565 };
2566 let item_two = sample_dotrain_item();
2567 let doc = RainMetaDocumentV1Item::cbor_encode_seq(
2568 &vec![item_one.clone(), item_two.clone()],
2569 KnownMagic::RainMetaDocumentV1,
2570 )
2571 .unwrap();
2572 let requested = keccak256(&doc).0.to_vec();
2573 let server = MockServer::start();
2574 let _mock = server.mock(|when, then| {
2575 when.method(POST);
2576 then.status(200).json_body(json!({
2577 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2578 }));
2579 });
2580 let mut store = Store::new();
2581 store.add_subgraphs(&vec![server.url("/sg")]);
2582 let fetched = store.update(&requested).await.cloned().unwrap();
2583 assert_eq!(fetched, doc);
2584 assert_eq!(store.get_meta(&requested), Some(&doc));
2585 let inner_one = item_one.cbor_encode().unwrap();
2586 let inner_two = item_two.cbor_encode().unwrap();
2587 assert_eq!(
2588 store.get_meta(keccak256(&inner_one).0.as_ref()),
2589 Some(&inner_one)
2590 );
2591 assert_eq!(
2592 store.get_meta(keccak256(&inner_two).0.as_ref()),
2593 Some(&inner_two)
2594 );
2595
2596 let mut cached_store = Store::new();
2598 let bytes = b"standalone meta bytes".to_vec();
2599 let hash = keccak256(&bytes).0.to_vec();
2600 assert!(cached_store.update_with(&hash, &bytes).is_ok());
2601 assert_eq!(cached_store.update_check(&hash).await.unwrap(), &bytes);
2602 }
2603
2604 #[tokio::test]
2608 async fn test_store_update_rejects_hash_mismatch() {
2609 use httpmock::prelude::*;
2610 let (_, doc) = sample_authoring_doc();
2611 let requested = keccak256(b"the real content").0.to_vec();
2612 let server = MockServer::start();
2613 let _mock = server.mock(|when, then| {
2614 when.method(POST);
2615 then.status(200).json_body(json!({
2616 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2617 }));
2618 });
2619 let mut store = Store::new();
2620 store.add_subgraphs(&vec![server.url("/sg")]);
2621 assert!(store.update(&requested).await.is_err());
2622 assert!(store.get_meta(&requested).is_none());
2623 assert!(store.cache().is_empty());
2624 assert!(store.update_check(&requested).await.is_err());
2626 }
2627
2628 #[tokio::test]
2631 async fn test_store_no_subgraphs_lookups_return_none() {
2632 let hash = [0u8; 32];
2633 let mut store = Store::new();
2634 assert!(store.update(&hash).await.is_err());
2635 assert!(store.update_check(&hash).await.is_err());
2636 assert!(store.cache().is_empty());
2637 }
2638
2639 #[test]
2643 fn test_store_update_with_validation_and_content() {
2644 let mut store = Store::new();
2646 let bytes = b"payload bytes".to_vec();
2647 let wrong_hash = vec![0x99u8; 32];
2648 match store.update_with(&wrong_hash, &bytes).unwrap_err() {
2653 Error::CorruptRecord(message) => {
2654 assert!(
2655 message.contains(&hex::encode_prefixed(&wrong_hash)),
2656 "{}",
2657 message
2658 )
2659 }
2660 other => panic!("expected CorruptRecord, got {:?}", other),
2661 }
2662 assert!(store.get_meta(&wrong_hash).is_none());
2663 let hash = keccak256(&bytes).0.to_vec();
2665 assert_eq!(store.update_with(&hash, &bytes).unwrap(), &bytes);
2666
2667 let mut seeded = Store::new();
2674 let planted = b"planted value".to_vec();
2675 let planted_hash = keccak256(&planted).0.to_vec();
2676 seeded.update_with(&planted_hash, &planted).unwrap();
2677 assert_eq!(seeded.cache().len(), 1);
2678 assert_eq!(
2679 seeded.update_with(&planted_hash, &planted).unwrap(),
2680 &planted
2681 );
2682 assert_eq!(seeded.cache().len(), 1);
2683
2684 let (_, doc) = sample_authoring_doc();
2686 let doc_hash = keccak256(&doc).0.to_vec();
2687 let mut doc_store = Store::new();
2688 assert!(doc_store.update_with(&doc_hash, &doc).is_ok());
2689 let inner = doc[8..].to_vec();
2690 assert_eq!(store_inner_lookup(&doc_store, &inner), Some(inner.clone()));
2691
2692 let item_a = sample_dotrain_item().cbor_encode().unwrap();
2694 let (_, doc_b) = sample_authoring_doc();
2695 let item_b = doc_b[8..].to_vec();
2696 let seq = [item_a.clone(), item_b].concat();
2697 let seq_hash = keccak256(&seq).0.to_vec();
2698 let mut seq_store = Store::new();
2699 assert!(seq_store.update_with(&seq_hash, &seq).is_ok());
2700 assert_eq!(store_inner_lookup(&seq_store, &item_a), None);
2701 }
2702
2703 fn store_inner_lookup(store: &Store, inner_encoded: &[u8]) -> Option<Vec<u8>> {
2704 store.get_meta(keccak256(inner_encoded).0.as_ref()).cloned()
2705 }
2706
2707 #[test]
2710 fn test_bytes32_to_str_invalid_utf8() {
2711 let mut bytes = [0u8; 32];
2712 bytes[0] = 0xf0;
2713 bytes[1] = 0x28;
2714 bytes[2] = 0x8c;
2715 bytes[3] = 0x28;
2716 assert!(matches!(bytes32_to_str(&bytes), Err(Error::Utf8Error(_))));
2717 let no_nul = [0xffu8; 32];
2718 assert!(matches!(bytes32_to_str(&no_nul), Err(Error::Utf8Error(_))));
2719 }
2720}