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) {
721 if let Some(kv) = self.dotrain_cache.remove_entry(uri) {
722 if !keep_meta {
723 self.remove_unreferenced_meta(&kv.1);
724 }
725 };
726 }
727
728 fn remove_unreferenced_meta(&mut self, hash: &[u8]) {
730 if !self.dotrain_cache.values().any(|h| h == hash) {
731 self.cache.remove(hash);
732 }
733 }
734
735 pub fn merge(&mut self, other: &Store) {
738 self.add_subgraphs(&other.subgraphs);
739 for (hash, bytes) in other.cache.iter() {
740 if !self.cache.contains_key(hash) {
741 let _ = self.cache.insert_verified(hash, bytes.clone());
744 }
745 }
746 for (uri, hash) in &other.dotrain_cache {
747 if !self.dotrain_cache.contains_key(uri) {
748 self.dotrain_cache.insert(uri.clone(), hash.clone());
749 }
750 }
751 }
752
753 fn insert_verified(&mut self, hash: &[u8], bytes: Vec<u8>) -> Result<&Vec<u8>, Error> {
757 self.cache.insert_verified(hash, bytes.clone())?;
758 self.store_content(&bytes);
759 self.get_meta(hash).ok_or(Error::NoRecordFound)
760 }
761
762 pub async fn update(&mut self, hash: &[u8]) -> Result<&Vec<u8>, Error> {
767 let meta = search(&hex::encode_prefixed(hash), &self.subgraphs).await?;
768 self.insert_verified(hash, meta.bytes)
769 }
770
771 pub async fn update_check(&mut self, hash: &[u8]) -> Result<&Vec<u8>, Error> {
773 if self.cache.contains_key(hash) {
779 return self.get_meta(hash).ok_or(Error::NoRecordFound);
780 }
781 self.update(hash).await
782 }
783
784 pub fn update_with(&mut self, hash: &[u8], bytes: &[u8]) -> Result<&Vec<u8>, Error> {
788 if self.cache.contains_key(hash) {
794 return self.get_meta(hash).ok_or(Error::NoRecordFound);
795 }
796 self.insert_verified(hash, bytes.to_vec())
797 }
798
799 pub fn set_dotrain(
805 &mut self,
806 text: &str,
807 uri: &str,
808 keep_old: bool,
809 ) -> Result<(Vec<u8>, Vec<u8>), Error> {
810 let bytes = RainMetaDocumentV1Item {
811 payload: serde_bytes::ByteBuf::from(text.as_bytes()),
812 magic: KnownMagic::DotrainV1,
813 content_type: ContentType::OctetStream,
814 content_encoding: ContentEncoding::None,
815 content_language: ContentLanguage::None,
816 schema: None,
817 }
818 .cbor_encode()?;
819 let new_hash = keccak256(&bytes).0.to_vec();
820 if let Some(h) = self.dotrain_cache.get(uri) {
821 let old_hash = h.clone();
822 if new_hash == old_hash {
823 self.cache.insert_verified(&new_hash, bytes)?;
824 Ok((new_hash, vec![]))
825 } else {
826 self.cache.insert_verified(&new_hash, bytes)?;
827 self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
828 if !keep_old {
829 self.remove_unreferenced_meta(&old_hash);
830 }
831 Ok((new_hash, old_hash))
832 }
833 } else {
834 self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
835 self.cache.insert_verified(&new_hash, bytes)?;
836 Ok((new_hash, vec![]))
837 }
838 }
839
840 fn store_content(&mut self, bytes: &[u8]) {
844 if let Ok(meta_maps) = RainMetaDocumentV1Item::cbor_decode(bytes) {
845 if bytes.starts_with(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes()) {
846 for meta_map in &meta_maps {
847 if let Ok(encoded_bytes) = meta_map.cbor_encode() {
848 let _ = self
852 .cache
853 .insert_verified(&keccak256(&encoded_bytes).0, encoded_bytes);
854 }
855 }
856 }
857 }
858 }
859}
860
861pub fn str_to_bytes32(text: &str) -> Result<[u8; 32], Error> {
867 let bytes: &[u8] = text.as_bytes();
868 if bytes.len() > 32 {
869 return Err(Error::BiggerThan32Bytes);
870 }
871 if bytes.contains(&0u8) {
872 return Err(Error::NulByteInInput);
873 }
874 let mut b32 = [0u8; 32];
875 b32[..bytes.len()].copy_from_slice(bytes);
876 Ok(b32)
877}
878
879pub fn bytes32_to_str(bytes: &[u8; 32]) -> Result<&str, Error> {
881 let mut len = 32;
882 if let Some((pos, _)) = itertools::Itertools::find_position(&mut bytes.iter(), |b| **b == 0u8) {
883 len = pos;
884 };
885 Ok(std::str::from_utf8(&bytes[..len])?)
886}
887
888#[cfg(all(test, not(target_family = "wasm")))]
889mod tests {
890 use super::{
891 *, bytes32_to_str, magic::KnownMagic, str_to_bytes32, types::authoring::v1::AuthoringMeta,
892 ContentEncoding, ContentLanguage, ContentType, Error, RainMetaDocumentV1Item,
893 };
894 use alloy::providers::ProviderBuilder;
895 use alloy::{providers::mock::Asserter, rpc::json_rpc::ErrorPayload, sol_types::SolType};
896 use serde_json::json;
897
898 #[test]
901 fn authoring_meta_roundtrip() -> Result<(), Error> {
902 let authoring_meta_content = r#"[
903 {
904 "word": "stack",
905 "description": "Copies an existing value from the stack.",
906 "operandParserOffset": 16
907 },
908 {
909 "word": "constant",
910 "description": "Copies a constant value onto the stack.",
911 "operandParserOffset": 16
912 }
913 ]"#;
914 let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
915
916 let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
918 let expected_abi_encoded = <alloy::sol!((bytes32, uint8, string)[])>::abi_encode(&vec![
919 (
920 str_to_bytes32("stack")?,
921 16u8,
922 "Copies an existing value from the stack.".to_string(),
923 ),
924 (
925 str_to_bytes32("constant")?,
926 16u8,
927 "Copies a constant value onto the stack.".to_string(),
928 ),
929 ]);
930 assert_eq!(authoring_meta_abi_encoded, expected_abi_encoded);
932
933 let meta_map = RainMetaDocumentV1Item {
934 payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
935 magic: KnownMagic::AuthoringMetaV1,
936 content_type: ContentType::Cbor,
937 content_encoding: ContentEncoding::None,
938 content_language: ContentLanguage::None,
939 schema: None,
940 };
941 let cbor_encoded = meta_map.cbor_encode()?;
942
943 assert_eq!(cbor_encoded[0], 0xa3);
945 assert_eq!(cbor_encoded[1], 0x00);
947 assert_eq!(cbor_encoded[2], 0b010_11001);
949 assert_eq!(cbor_encoded[3], 0b000_00010);
950 assert_eq!(cbor_encoded[4], 0b000_00000);
951 assert_eq!(cbor_encoded[5..517], authoring_meta_abi_encoded);
953 assert_eq!(cbor_encoded[517], 0x01);
955 assert_eq!(cbor_encoded[518], 0b000_11011);
957 assert_eq!(
959 &cbor_encoded[519..527],
960 KnownMagic::AuthoringMetaV1.to_prefix_bytes()
961 );
962 assert_eq!(cbor_encoded[527], 0x02);
964 assert_eq!(cbor_encoded[528], 0b011_10000);
966 assert_eq!(&cbor_encoded[529..], "application/cbor".as_bytes());
968
969 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
971 assert_eq!(cbor_decoded.len(), 1);
973 assert_eq!(cbor_decoded[0], meta_map);
975
976 Ok(())
977 }
978
979 #[test]
982 fn dotrain_meta_roundtrip() -> Result<(), Error> {
983 let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
984 let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
985
986 let content_encoding = ContentEncoding::Deflate;
987 let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
988
989 let meta_map = RainMetaDocumentV1Item {
990 payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
991 magic: KnownMagic::DotrainV1,
992 content_type: ContentType::OctetStream,
993 content_encoding,
994 content_language: ContentLanguage::En,
995 schema: None,
996 };
997 let cbor_encoded = meta_map.cbor_encode()?;
998
999 assert_eq!(cbor_encoded[0], 0xa5);
1001 assert_eq!(cbor_encoded[1], 0x00);
1003 assert_eq!(cbor_encoded[2], 0b010_11000);
1005 assert_eq!(cbor_encoded[3], 0b001_00100);
1006 assert_eq!(cbor_encoded[4..40], deflated_payload);
1009 assert_eq!(cbor_encoded[40], 0x01);
1011 assert_eq!(cbor_encoded[41], 0b000_11011);
1013 assert_eq!(
1015 &cbor_encoded[42..50],
1016 KnownMagic::DotrainV1.to_prefix_bytes()
1017 );
1018 assert_eq!(cbor_encoded[50], 0x02);
1020 assert_eq!(cbor_encoded[51], 0b011_11000);
1022 assert_eq!(cbor_encoded[52], 0b000_11000);
1023 assert_eq!(&cbor_encoded[53..77], "application/octet-stream".as_bytes());
1025 assert_eq!(cbor_encoded[77], 0x03);
1027 assert_eq!(cbor_encoded[78], 0b011_00111);
1029 assert_eq!(&cbor_encoded[79..86], "deflate".as_bytes());
1031 assert_eq!(cbor_encoded[86], 0x04);
1033 assert_eq!(cbor_encoded[87], 0b011_00010);
1035 assert_eq!(&cbor_encoded[88..], "en".as_bytes());
1037
1038 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1040 assert_eq!(cbor_decoded.len(), 1);
1042 assert_eq!(cbor_decoded[0], meta_map);
1044
1045 Ok(())
1046 }
1047
1048 #[test]
1051 fn meta_seq_roundtrip() -> Result<(), Error> {
1052 let authoring_meta_content = r#"[
1053 {
1054 "word": "stack",
1055 "description": "Copies an existing value from the stack.",
1056 "operandParserOffset": 16
1057 },
1058 {
1059 "word": "constant",
1060 "description": "Copies a constant value onto the stack.",
1061 "operandParserOffset": 16
1062 }
1063 ]"#;
1064 let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
1065 let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
1066 let meta_map_1 = RainMetaDocumentV1Item {
1067 payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
1068 magic: KnownMagic::AuthoringMetaV1,
1069 content_type: ContentType::Cbor,
1070 content_encoding: ContentEncoding::None,
1071 content_language: ContentLanguage::None,
1072 schema: None,
1073 };
1074
1075 let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
1076 let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
1077 let content_encoding = ContentEncoding::Deflate;
1078 let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
1079 let meta_map_2 = RainMetaDocumentV1Item {
1080 payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
1081 magic: KnownMagic::DotrainV1,
1082 content_type: ContentType::OctetStream,
1083 content_encoding,
1084 content_language: ContentLanguage::En,
1085 schema: None,
1086 };
1087
1088 let cbor_encoded = RainMetaDocumentV1Item::cbor_encode_seq(
1090 &vec![meta_map_1.clone(), meta_map_2.clone()],
1091 KnownMagic::RainMetaDocumentV1,
1092 )?;
1093
1094 assert_eq!(
1096 &cbor_encoded[0..8],
1097 KnownMagic::RainMetaDocumentV1.to_prefix_bytes()
1098 );
1099
1100 assert_eq!(cbor_encoded[8], 0xa3);
1103 assert_eq!(cbor_encoded[9], 0x00);
1105 assert_eq!(cbor_encoded[10], 0b010_11001);
1107 assert_eq!(cbor_encoded[11], 0b000_00010);
1108 assert_eq!(cbor_encoded[12], 0b000_00000);
1109 assert_eq!(cbor_encoded[13..525], authoring_meta_abi_encoded);
1111 assert_eq!(cbor_encoded[525], 0x01);
1113 assert_eq!(cbor_encoded[526], 0b000_11011);
1115 assert_eq!(
1117 &cbor_encoded[527..535],
1118 KnownMagic::AuthoringMetaV1.to_prefix_bytes()
1119 );
1120 assert_eq!(cbor_encoded[535], 0x02);
1122 assert_eq!(cbor_encoded[536], 0b011_10000);
1124 assert_eq!(&cbor_encoded[537..553], "application/cbor".as_bytes());
1126
1127 assert_eq!(cbor_encoded[553], 0xa5);
1130 assert_eq!(cbor_encoded[554], 0x00);
1132 assert_eq!(cbor_encoded[555], 0b010_11000);
1134 assert_eq!(cbor_encoded[556], 0b001_00100);
1135 assert_eq!(cbor_encoded[557..593], deflated_payload);
1138 assert_eq!(cbor_encoded[593], 0x01);
1140 assert_eq!(cbor_encoded[594], 0b000_11011);
1142 assert_eq!(
1144 &cbor_encoded[595..603],
1145 KnownMagic::DotrainV1.to_prefix_bytes()
1146 );
1147 assert_eq!(cbor_encoded[603], 0x02);
1149 assert_eq!(cbor_encoded[604], 0b011_11000);
1151 assert_eq!(cbor_encoded[605], 0b000_11000);
1152 assert_eq!(
1154 &cbor_encoded[606..630],
1155 "application/octet-stream".as_bytes()
1156 );
1157 assert_eq!(cbor_encoded[630], 0x03);
1159 assert_eq!(cbor_encoded[631], 0b011_00111);
1161 assert_eq!(&cbor_encoded[632..639], "deflate".as_bytes());
1163 assert_eq!(cbor_encoded[639], 0x04);
1165 assert_eq!(cbor_encoded[640], 0b011_00010);
1167 assert_eq!(&cbor_encoded[641..], "en".as_bytes());
1169
1170 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1172 assert_eq!(cbor_decoded.len(), 2);
1174
1175 assert_eq!(cbor_decoded[0], meta_map_1);
1177 assert_eq!(cbor_decoded[1], meta_map_2);
1179
1180 Ok(())
1181 }
1182
1183 #[test]
1184 fn test_bytes32_to_str() {
1185 let text_bytes_list = vec![
1186 (
1187 "",
1188 hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1189 ),
1190 (
1191 "A",
1192 hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1193 ),
1194 (
1195 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1196 hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1197 ),
1198 (
1199 "!@#$%^&*(),./;'[]",
1200 hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1201 ),
1202 ];
1203
1204 for (text, bytes) in text_bytes_list {
1205 assert_eq!(text, bytes32_to_str(&bytes).unwrap());
1206 }
1207 }
1208
1209 #[test]
1210 fn test_str_to_bytes32() {
1211 let text_bytes_list = vec![
1212 (
1213 "",
1214 hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1215 ),
1216 (
1217 "A",
1218 hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1219 ),
1220 (
1221 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1222 hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1223 ),
1224 (
1225 "!@#$%^&*(),./;'[]",
1226 hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1227 ),
1228 ];
1229
1230 for (text, bytes) in text_bytes_list {
1231 assert_eq!(bytes, str_to_bytes32(text).unwrap());
1232 }
1233 }
1234
1235 #[test]
1236 fn test_str_to_bytes32_long() {
1237 assert!(matches!(
1238 str_to_bytes32("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456").unwrap_err(),
1239 Error::BiggerThan32Bytes
1240 ));
1241 }
1242
1243 #[test]
1247 fn test_str_to_bytes32_rejects_nul() {
1248 for text in [
1249 "\0",
1250 "\0a",
1251 "a\0",
1252 "a\0b",
1253 "abcdefghijklmnopqrstuvwxyz01234\0",
1254 ] {
1255 assert!(
1256 matches!(str_to_bytes32(text), Err(Error::NulByteInInput)),
1257 "nul bearing input {:?} accepted",
1258 text
1259 );
1260 }
1261 }
1262
1263 #[test]
1266 fn test_str_to_bytes32_round_trip() -> Result<(), Error> {
1267 let mut seen: Vec<[u8; 32]> = vec![];
1268 for text in [
1269 "",
1270 "a",
1271 "stack",
1272 "!@#$%^&*(),./;'[]",
1273 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1274 ] {
1275 let bytes = str_to_bytes32(text)?;
1276 assert_eq!(bytes32_to_str(&bytes)?, text);
1277 assert!(!seen.contains(&bytes), "input {:?} collided", text);
1278 seen.push(bytes);
1279 }
1280 Ok(())
1281 }
1282
1283 #[tokio::test]
1284 async fn test_implements_i_describe_by_meta_v1() {
1285 async fn new_server_client() -> (Asserter, impl Provider) {
1287 let asserter = Asserter::new();
1288 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
1289
1290 asserter.push_success(
1292 &"0x0000000000000000000000000000000000000000000000000000000000000001",
1293 );
1294 asserter.push_success(
1295 &"0x0000000000000000000000000000000000000000000000000000000000000000",
1296 );
1297
1298 (asserter, provider)
1299 }
1300
1301 let address = Address::random();
1302
1303 let (asserter, provider) = new_server_client().await;
1305 asserter
1306 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
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
1315 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
1316 let result = implements_i_described_by_meta_v1(&provider, address)
1317 .await
1318 .unwrap();
1319 assert!(!result);
1320
1321 let (asserter, provider) = new_server_client().await;
1323 asserter.push_failure(ErrorPayload {
1324 code: -32003,
1325 message: "execution reverted".into(),
1326 data: Some(serde_json::value::to_raw_value(&json!("0x00")).unwrap()),
1327 });
1328 let result = implements_i_described_by_meta_v1(&provider, address)
1329 .await
1330 .unwrap();
1331 assert!(!result);
1332 }
1333
1334 #[test]
1338 fn oa_schema_map_key_roundtrip() -> Result<(), Error> {
1339 let payload = vec![0x01, 0x02, 0x03];
1340 let schema = "QmSchemaHash1234567890abcdefghijklmnopqrstuvwx".to_string();
1343 assert_eq!(schema.len(), 46);
1344
1345 let meta_map = RainMetaDocumentV1Item {
1346 payload: serde_bytes::ByteBuf::from(payload.clone()),
1347 magic: KnownMagic::OaStructure,
1348 content_type: ContentType::Json,
1349 content_encoding: ContentEncoding::Deflate,
1350 content_language: ContentLanguage::None,
1351 schema: Some(schema.clone()),
1352 };
1353 let cbor_encoded = meta_map.cbor_encode()?;
1354
1355 assert_eq!(cbor_encoded[0], 0xa5);
1357 assert_eq!(cbor_encoded[1], 0x00);
1359 assert_eq!(cbor_encoded[2], 0b010_00011);
1361 assert_eq!(cbor_encoded[3..6], payload);
1363 assert_eq!(cbor_encoded[6], 0x01);
1365 assert_eq!(cbor_encoded[7], 0b000_11011);
1367 assert_eq!(
1369 &cbor_encoded[8..16],
1370 KnownMagic::OaStructure.to_prefix_bytes()
1371 );
1372 assert_eq!(cbor_encoded[16], 0x02);
1374 assert_eq!(cbor_encoded[17], 0b011_10000);
1376 assert_eq!(&cbor_encoded[18..34], "application/json".as_bytes());
1377 assert_eq!(cbor_encoded[34], 0x03);
1379 assert_eq!(cbor_encoded[35], 0b011_00111);
1381 assert_eq!(&cbor_encoded[36..43], "deflate".as_bytes());
1382 assert_eq!(cbor_encoded[43], 0b000_11011);
1384 assert_eq!(
1385 &cbor_encoded[44..52],
1386 KnownMagic::OaSchema.to_prefix_bytes()
1387 );
1388 assert_eq!(cbor_encoded[52], 0b011_11000);
1390 assert_eq!(cbor_encoded[53], 46);
1391 assert_eq!(&cbor_encoded[54..], schema.as_bytes());
1393
1394 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1396 assert_eq!(cbor_decoded.len(), 1);
1398 assert_eq!(cbor_decoded[0], meta_map);
1400
1401 Ok(())
1402 }
1403
1404 #[test]
1407 fn no_schema_key_encodes_as_before_roundtrip() -> Result<(), Error> {
1408 let payload = vec![0x0a, 0x0b];
1409 let meta_map = RainMetaDocumentV1Item {
1410 payload: serde_bytes::ByteBuf::from(payload.clone()),
1411 magic: KnownMagic::OaStructure,
1412 content_type: ContentType::None,
1413 content_encoding: ContentEncoding::None,
1414 content_language: ContentLanguage::None,
1415 schema: None,
1416 };
1417 let cbor_encoded = meta_map.cbor_encode()?;
1418
1419 assert_eq!(cbor_encoded[0], 0xa2);
1421 assert_eq!(cbor_encoded[1], 0x00);
1423 assert_eq!(cbor_encoded[2], 0b010_00010);
1425 assert_eq!(cbor_encoded[3..5], payload);
1427 assert_eq!(cbor_encoded[5], 0x01);
1429 assert_eq!(cbor_encoded[6], 0b000_11011);
1431 assert_eq!(
1433 &cbor_encoded[7..],
1434 KnownMagic::OaStructure.to_prefix_bytes()
1435 );
1436
1437 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1438 assert_eq!(cbor_decoded.len(), 1);
1439 assert_eq!(cbor_decoded[0], meta_map);
1440
1441 Ok(())
1442 }
1443
1444 #[test]
1447 fn unknown_map_key_index_is_ignored() -> Result<(), Error> {
1448 let mut bytes: Vec<u8> = vec![
1449 0xa3, 0x00, 0x40, 0x01, 0x1b,
1453 ];
1454 bytes.extend_from_slice(&KnownMagic::DotrainSourceV1.to_prefix_bytes());
1455 bytes.extend_from_slice(&[0x05, 0x07]);
1457
1458 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1459 assert_eq!(decoded.len(), 1);
1460 assert_eq!(decoded[0], plain_item(KnownMagic::DotrainSourceV1, vec![]));
1461
1462 Ok(())
1463 }
1464
1465 #[test]
1468 fn non_oa_schema_extra_map_key_is_ignored() -> Result<(), Error> {
1469 let mut bytes: Vec<u8> = vec![
1472 0xa3, 0x00, 0x41, 0xff, 0x01, 0x1b,
1476 ];
1477 bytes.extend_from_slice(&KnownMagic::OaStructure.to_prefix_bytes());
1478 bytes.push(0x1b);
1480 bytes.extend_from_slice(&KnownMagic::OaHashList.to_prefix_bytes());
1481 bytes.extend_from_slice(&[0x62, 0x68, 0x69]);
1483
1484 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1485 assert_eq!(decoded.len(), 1);
1486 let expected = plain_item(KnownMagic::OaStructure, vec![0xff]);
1487 assert_eq!(decoded[0], expected);
1488 assert_eq!(decoded[0].schema, None);
1489
1490 Ok(())
1491 }
1492
1493 #[test]
1496 fn unknown_map_key_consumes_its_whole_value() -> Result<(), Error> {
1497 let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1498 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1499 bytes.extend_from_slice(&[0x05, 0xa1, 0x18, 0x2a, 0x82, 0x01, 0x02]);
1501 bytes.extend_from_slice(&handwritten_map());
1502
1503 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1504 assert_eq!(decoded.len(), 2);
1505 let expected = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1506 assert_eq!(decoded[0], expected);
1507 assert_eq!(decoded[1], expected);
1508
1509 Ok(())
1510 }
1511
1512 #[test]
1515 fn ignored_map_key_is_absent_from_the_reencoding() -> Result<(), Error> {
1516 let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1517 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1518 bytes.extend_from_slice(&[0x05, 0x07]);
1519
1520 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1521 assert_eq!(decoded[0].cbor_encode()?, handwritten_map());
1522 assert_eq!(decoded[0].hash(false)?, keccak256(handwritten_map()).0);
1523 assert_ne!(decoded[0].hash(false)?, keccak256(&bytes).0);
1524
1525 Ok(())
1526 }
1527
1528 #[test]
1532 fn non_integer_map_key_errors() {
1533 let mut text_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1534 text_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1535 text_key.extend_from_slice(&[0x61, 0x35, 0x07]);
1537 assert!(matches!(
1538 RainMetaDocumentV1Item::cbor_decode(&text_key),
1539 Err(Error::SerdeCborError(_))
1540 ));
1541
1542 let mut negative_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1543 negative_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1544 negative_key.extend_from_slice(&[0x20, 0x07]);
1546 assert!(matches!(
1547 RainMetaDocumentV1Item::cbor_decode(&negative_key),
1548 Err(Error::SerdeCborError(_))
1549 ));
1550 }
1551
1552 #[test]
1556 fn unknown_map_key_does_not_stand_in_for_a_mandatory_key() {
1557 let mut bytes: Vec<u8> = vec![0xa2, 0x05, 0x07, 0x01, 0x1b];
1558 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1559 assert!(RainMetaDocumentV1Item::cbor_decode(&bytes).is_err());
1560 }
1561
1562 fn plain_item(magic: KnownMagic, payload: Vec<u8>) -> RainMetaDocumentV1Item {
1563 RainMetaDocumentV1Item {
1564 payload: serde_bytes::ByteBuf::from(payload),
1565 magic,
1566 content_type: ContentType::None,
1567 content_encoding: ContentEncoding::None,
1568 content_language: ContentLanguage::None,
1569 schema: None,
1570 }
1571 }
1572
1573 fn sample_authoring_doc() -> (AuthoringMeta, Vec<u8>) {
1576 let authoring_meta: AuthoringMeta = serde_json::from_str(
1577 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
1578 )
1579 .unwrap();
1580 let abi = authoring_meta.abi_encode_validate().unwrap();
1581 let item = RainMetaDocumentV1Item {
1582 payload: serde_bytes::ByteBuf::from(abi),
1583 magic: KnownMagic::AuthoringMetaV1,
1584 content_type: ContentType::Cbor,
1585 content_encoding: ContentEncoding::None,
1586 content_language: ContentLanguage::None,
1587 schema: None,
1588 };
1589 let doc =
1590 RainMetaDocumentV1Item::cbor_encode_seq(&vec![item], KnownMagic::RainMetaDocumentV1)
1591 .unwrap();
1592 (authoring_meta, doc)
1593 }
1594
1595 fn sample_dotrain_item() -> RainMetaDocumentV1Item {
1596 RainMetaDocumentV1Item {
1597 payload: serde_bytes::ByteBuf::from("some dotrain body".as_bytes()),
1598 magic: KnownMagic::DotrainV1,
1599 content_type: ContentType::OctetStream,
1600 content_encoding: ContentEncoding::None,
1601 content_language: ContentLanguage::None,
1602 schema: None,
1603 }
1604 }
1605
1606 fn handwritten_map() -> Vec<u8> {
1609 vec![
1610 0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b, 0xff, 0xda, 0xc2, 0xf2, 0xf3, 0x7b, 0xe8, 0x94, ]
1616 }
1617
1618 #[test]
1622 fn test_hash_bare_vs_document() -> Result<(), Error> {
1623 let map_bytes = handwritten_map();
1624 let mut doc_bytes: Vec<u8> = vec![0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74];
1625 doc_bytes.extend_from_slice(&map_bytes);
1626
1627 let item = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1628 assert_eq!(item.hash(false)?, keccak256(&map_bytes).0);
1629 assert_eq!(item.hash(true)?, keccak256(&doc_bytes).0);
1630 assert_ne!(item.hash(false)?, item.hash(true)?);
1631 Ok(())
1632 }
1633
1634 #[test]
1636 fn test_cbor_decode_empty_is_corrupt() {
1637 assert!(matches!(
1638 RainMetaDocumentV1Item::cbor_decode(&[]),
1639 Err(Error::CorruptMeta)
1640 ));
1641 let prefix = KnownMagic::RainMetaDocumentV1.to_prefix_bytes();
1642 assert!(matches!(
1643 RainMetaDocumentV1Item::cbor_decode(&prefix),
1644 Err(Error::CorruptMeta)
1645 ));
1646 }
1647
1648 #[test]
1651 fn test_cbor_decode_trailing_truncated_is_corrupt() {
1652 let mut bytes = handwritten_map();
1653 bytes.push(0x1b); assert!(matches!(
1655 RainMetaDocumentV1Item::cbor_decode(&bytes),
1656 Err(Error::CorruptMeta)
1657 ));
1658 }
1659
1660 #[test]
1664 fn test_cbor_decode_truncated_item_is_corrupt() {
1665 let mut sole = handwritten_map();
1666 sole.pop();
1667 assert!(matches!(
1668 RainMetaDocumentV1Item::cbor_decode(&sole),
1669 Err(Error::CorruptMeta)
1670 ));
1671
1672 assert!(matches!(
1673 RainMetaDocumentV1Item::cbor_decode(&[0xa2, 0x00, 0x41, 0x01]),
1674 Err(Error::CorruptMeta)
1675 ));
1676
1677 let mut after_complete = handwritten_map();
1678 after_complete.extend_from_slice(&[0xa2, 0x00]);
1679 assert!(matches!(
1680 RainMetaDocumentV1Item::cbor_decode(&after_complete),
1681 Err(Error::CorruptMeta)
1682 ));
1683 }
1684
1685 #[test]
1688 fn test_cbor_decode_trailing_garbage_errors() {
1689 let mut bytes = handwritten_map();
1690 bytes.push(0xff); assert!(matches!(
1692 RainMetaDocumentV1Item::cbor_decode(&bytes),
1693 Err(Error::SerdeCborError(_))
1694 ));
1695 }
1696
1697 #[test]
1702 fn test_cbor_decode_missing_payload_is_corrupt() {
1703 let mut bytes: Vec<u8> = vec![0xa1, 0x01, 0x1b]; bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1705 assert!(RainMetaDocumentV1Item::cbor_decode(&bytes).is_err());
1706
1707 let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1709 .cbor_encode()
1710 .unwrap();
1711 let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1712 document.extend_from_slice(&bytes);
1713 document.extend_from_slice(&good);
1714 assert!(RainMetaDocumentV1Item::cbor_decode(&document).is_err());
1715 }
1716
1717 #[test]
1719 fn test_cbor_decode_missing_magic_is_corrupt() {
1720 let bytes: Vec<u8> = vec![0xa1, 0x00, 0x41, 0x01]; assert!(RainMetaDocumentV1Item::cbor_decode(&bytes).is_err());
1722
1723 let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1724 .cbor_encode()
1725 .unwrap();
1726 let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1727 document.extend_from_slice(&bytes);
1728 document.extend_from_slice(&good);
1729 assert!(RainMetaDocumentV1Item::cbor_decode(&document).is_err());
1730 }
1731
1732 #[test]
1737 fn test_cbor_decode_unknown_magic_is_dropped() {
1738 let mut bytes: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1739 bytes.extend_from_slice(&0xdeadbeefdeadbeefu64.to_be_bytes());
1740 assert!(matches!(
1741 RainMetaDocumentV1Item::cbor_decode(&bytes),
1742 Err(Error::CorruptMeta)
1743 ));
1744 }
1745
1746 #[test]
1750 fn test_cbor_decode_drops_only_the_unknown_magic_item() {
1751 let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1752 .cbor_encode()
1753 .unwrap();
1754
1755 let mut unknown_magic: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1756 unknown_magic.extend_from_slice(&0xdeadbeefdeadbeefu64.to_be_bytes());
1757
1758 let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1759 document.extend_from_slice(&unknown_magic);
1760 document.extend_from_slice(&good);
1761
1762 let items = RainMetaDocumentV1Item::cbor_decode(&document).unwrap();
1763 assert_eq!(items.len(), 1, "expected only the good item");
1764 assert_eq!(items[0].magic, KnownMagic::DotrainV1);
1765 assert_eq!(items[0].payload.as_ref(), &[0x42]);
1766 }
1767
1768 #[test]
1773 fn test_cbor_decode_nested_document_magic_is_not_dropped() {
1774 let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1775 .cbor_encode()
1776 .unwrap();
1777
1778 let mut nested: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1779 nested.extend_from_slice(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes());
1780
1781 let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1782 document.extend_from_slice(&nested);
1783 document.extend_from_slice(&good);
1784
1785 assert!(matches!(
1786 RainMetaDocumentV1Item::cbor_decode(&document),
1787 Err(Error::SerdeCborError(_))
1788 ));
1789 }
1790
1791 fn handwritten_entries(entries: &[(Vec<u8>, Vec<u8>)]) -> Vec<u8> {
1795 assert!(entries.len() < 24);
1796 let mut bytes = vec![0xa0 | entries.len() as u8];
1797 for (key, value) in entries {
1798 bytes.extend_from_slice(key);
1799 bytes.extend_from_slice(value);
1800 }
1801 bytes
1802 }
1803
1804 fn handwritten_magic(magic: KnownMagic) -> Vec<u8> {
1806 let mut bytes = vec![0x1b];
1807 bytes.extend_from_slice(&magic.to_prefix_bytes());
1808 bytes
1809 }
1810
1811 fn handwritten_text(text: &str) -> Vec<u8> {
1813 assert!(text.len() < 24);
1814 let mut bytes = vec![0x60 | text.len() as u8];
1815 bytes.extend_from_slice(text.as_bytes());
1816 bytes
1817 }
1818
1819 #[test]
1822 fn test_cbor_decode_duplicate_payload_key_errors() {
1823 let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x00, 0x41, 0x02, 0x01, 0x1b];
1824 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1825 let error = RainMetaDocumentV1Item::cbor_decode(&bytes).unwrap_err();
1826 assert!(matches!(error, Error::SerdeCborError(_)));
1827 assert!(
1828 error.to_string().contains("duplicate field `payload`"),
1829 "{error}"
1830 );
1831 }
1832
1833 #[test]
1836 fn test_cbor_decode_duplicate_any_key_errors() -> Result<(), Error> {
1837 let cases: [(Vec<u8>, Vec<u8>, Vec<u8>); 6] = [
1838 (vec![0x00], vec![0x41, 0x01], vec![0x41, 0x02]),
1839 (
1840 vec![0x01],
1841 handwritten_magic(KnownMagic::DotrainV1),
1842 handwritten_magic(KnownMagic::RainlangV1),
1843 ),
1844 (
1845 vec![0x02],
1846 handwritten_text("application/cbor"),
1847 handwritten_text("application/json"),
1848 ),
1849 (
1850 vec![0x03],
1851 handwritten_text("identity"),
1852 handwritten_text("deflate"),
1853 ),
1854 (vec![0x04], handwritten_text("en"), handwritten_text("none")),
1855 (
1856 handwritten_magic(KnownMagic::OaSchema),
1857 handwritten_text("hi"),
1858 handwritten_text("bye"),
1859 ),
1860 ];
1861 let base: Vec<(Vec<u8>, Vec<u8>)> = cases
1862 .iter()
1863 .map(|(key, value, _)| (key.clone(), value.clone()))
1864 .collect();
1865
1866 let decoded = RainMetaDocumentV1Item::cbor_decode(&handwritten_entries(&base))?;
1867 assert_eq!(decoded.len(), 1);
1868 assert_eq!(decoded[0].payload.as_ref(), &[0x01]);
1869 assert_eq!(decoded[0].magic, KnownMagic::DotrainV1);
1870 assert_eq!(decoded[0].content_type, ContentType::Cbor);
1871 assert_eq!(decoded[0].content_encoding, ContentEncoding::Identity);
1872 assert_eq!(decoded[0].content_language, ContentLanguage::En);
1873 assert_eq!(decoded[0].schema.as_deref(), Some("hi"));
1874
1875 for (key, value, other_value) in &cases {
1876 for repeated in [value, other_value] {
1877 let mut entries = base.clone();
1878 entries.push((key.clone(), repeated.clone()));
1879 let error = RainMetaDocumentV1Item::cbor_decode(&handwritten_entries(&entries))
1880 .unwrap_err();
1881 assert!(
1882 matches!(error, Error::SerdeCborError(_)),
1883 "{key:?} {error:?}"
1884 );
1885 assert!(
1886 error.to_string().contains("duplicate field"),
1887 "{key:?} {error}"
1888 );
1889 }
1890 }
1891 Ok(())
1892 }
1893
1894 #[test]
1899 fn test_cbor_decode_duplicate_unknown_key_errors() -> Result<(), Error> {
1900 let base: Vec<(Vec<u8>, Vec<u8>)> = vec![
1901 (vec![0x00], vec![0x41, 0x01]),
1902 (vec![0x01], handwritten_magic(KnownMagic::DotrainV1)),
1903 ];
1904 let unknown: [(Vec<u8>, Vec<u8>, Vec<u8>); 2] = [
1907 (vec![0x05], vec![0x07], vec![0x08]),
1908 (
1909 handwritten_magic(KnownMagic::OaHashList),
1910 handwritten_text("hi"),
1911 handwritten_text("bye"),
1912 ),
1913 ];
1914
1915 for (key, value, other_value) in &unknown {
1916 let mut entries = base.clone();
1917 entries.push((key.clone(), value.clone()));
1918 let decoded = RainMetaDocumentV1Item::cbor_decode(&handwritten_entries(&entries))?;
1919 assert_eq!(decoded, vec![plain_item(KnownMagic::DotrainV1, vec![0x01])]);
1920
1921 for repeated in [value, other_value] {
1922 let mut duplicated = entries.clone();
1923 duplicated.push((key.clone(), repeated.clone()));
1924 let error = RainMetaDocumentV1Item::cbor_decode(&handwritten_entries(&duplicated))
1925 .unwrap_err();
1926 assert!(
1927 matches!(error, Error::SerdeCborError(_)),
1928 "{key:?} {error:?}"
1929 );
1930 assert!(
1931 error.to_string().contains("duplicate map key"),
1932 "{key:?} {error}"
1933 );
1934 }
1935 }
1936 Ok(())
1937 }
1938
1939 #[test]
1943 fn test_cbor_decode_handwritten_document_magic_item() {
1944 let bytes: Vec<u8> = vec![
1945 0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b, 0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74, ];
1951 assert!(RainMetaDocumentV1Item::cbor_decode(&bytes).is_err());
1955 }
1956
1957 #[test]
1963 fn test_document_magic_item_carries_a_nested_document() -> Result<(), Error> {
1964 let inner = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1965 let inner_doc = RainMetaDocumentV1Item::cbor_encode_seq(
1966 &vec![inner.clone()],
1967 KnownMagic::RainMetaDocumentV1,
1968 )?;
1969 let outer = plain_item(KnownMagic::RainMetaDocumentV1, inner_doc.clone());
1970 let outer_doc = RainMetaDocumentV1Item::cbor_encode_seq(
1971 &vec![outer.clone()],
1972 KnownMagic::RainMetaDocumentV1,
1973 )?;
1974
1975 assert!(RainMetaDocumentV1Item::cbor_decode(&outer_doc).is_err());
1981 assert_eq!(
1982 RainMetaDocumentV1Item::cbor_decode(&inner_doc)?,
1983 vec![inner]
1984 );
1985 Ok(())
1986 }
1987
1988 #[test]
1991 fn test_document_magic_item_is_not_unpackable() {
1992 assert!(matches!(
1993 KnownMeta::try_from(KnownMagic::RainMetaDocumentV1),
1994 Err(Error::UnsupportedMeta)
1995 ));
1996 assert!(matches!(
1997 plain_item(KnownMagic::RainMetaDocumentV1, vec![0x01]).unpack_into::<Vec<u8>>(),
1998 Err(Error::UnsupportedMeta)
1999 ));
2000 }
2001
2002 #[test]
2004 fn test_unpack_decodes_content_encoding() -> Result<(), Error> {
2005 let content = b"unpack me via deflate".to_vec();
2006 let packed = ContentEncoding::Deflate.encode(&content);
2007 assert_ne!(packed, content);
2008 let mut item = plain_item(KnownMagic::DotrainV1, packed);
2009 item.content_encoding = ContentEncoding::Deflate;
2010 assert_eq!(item.unpack()?, content);
2011
2012 let item = plain_item(KnownMagic::DotrainV1, content.clone());
2013 assert_eq!(item.unpack()?, content);
2014 Ok(())
2015 }
2016
2017 #[test]
2020 fn test_unpack_into_whitelist() {
2021 use strum::IntoEnumIterator;
2022 let supported = [
2023 KnownMagic::OpMetaV1,
2024 KnownMagic::DotrainV1,
2025 KnownMagic::RainlangV1,
2026 KnownMagic::SolidityAbiV2,
2027 KnownMagic::AuthoringMetaV1,
2028 KnownMagic::AuthoringMetaV2,
2029 KnownMagic::AddressList,
2030 KnownMagic::InterpreterCallerMetaV1,
2031 KnownMagic::ExpressionDeployerV2BytecodeV1,
2032 KnownMagic::DotrainSourceV1,
2033 KnownMagic::OrderBuilderStateV1,
2034 KnownMagic::RainlangSourceV1,
2035 KnownMagic::RaindexSignedContextOracleV1,
2036 ];
2037 for magic in supported {
2038 let unpacked: Vec<u8> = plain_item(magic, vec![0x61]).unpack_into().unwrap();
2039 assert_eq!(unpacked, vec![0x61], "{:?}", magic);
2040 }
2041 let unsupported = [
2042 KnownMagic::RainMetaDocumentV1,
2043 KnownMagic::WebDataV1,
2044 KnownMagic::OaSchema,
2045 KnownMagic::OaHashList,
2046 KnownMagic::OaStructure,
2047 KnownMagic::OaTokenImage,
2048 KnownMagic::OaTokenCredentialLinks,
2049 ];
2050 for magic in unsupported {
2051 let result: Result<Vec<u8>, Error> = plain_item(magic, vec![0x61]).unpack_into();
2052 assert!(matches!(result, Err(Error::UnsupportedMeta)), "{:?}", magic);
2053 }
2054 assert_eq!(
2056 supported.len() + unsupported.len(),
2057 KnownMagic::iter().count()
2058 );
2059 }
2060
2061 #[test]
2064 fn test_try_into_string_invalid_utf8_errors() {
2065 let item = plain_item(KnownMagic::DotrainV1, vec![0xff, 0xfe]);
2066 let result: Result<String, Error> = item.try_into();
2067 assert!(matches!(result, Err(Error::FromUtf8Error(_))));
2068 }
2069
2070 #[test]
2072 fn test_try_into_vec_decodes_encoding() -> Result<(), Error> {
2073 let content = b"raw bytecode bytes \x00\x01\x02".to_vec();
2074 let packed = ContentEncoding::Deflate.encode(&content);
2075 let mut item = plain_item(KnownMagic::ExpressionDeployerV2BytecodeV1, packed.clone());
2076 item.content_encoding = ContentEncoding::Deflate;
2077 let unpacked: Vec<u8> = item.try_into()?;
2078 assert_eq!(unpacked, content);
2079 assert_ne!(unpacked, packed);
2080 Ok(())
2081 }
2082
2083 #[test]
2086 fn test_content_encoding_deflate_roundtrip() -> Result<(), Error> {
2087 let content = b"hello rain deflate fixture hello rain deflate fixture".to_vec();
2088 let encoded = ContentEncoding::Deflate.encode(&content);
2089 assert_ne!(encoded, content);
2090 assert_eq!(encoded[0], 0x78);
2091 assert_eq!(ContentEncoding::Deflate.decode(&encoded)?, content);
2092 Ok(())
2093 }
2094
2095 #[test]
2097 fn test_content_encoding_passthrough() -> Result<(), Error> {
2098 let data = vec![0x00, 0xff, 0x10];
2099 for encoding in [ContentEncoding::None, ContentEncoding::Identity] {
2100 assert_eq!(encoding.encode(&data), data, "{:?}", encoding);
2101 assert_eq!(encoding.decode(&data)?, data, "{:?}", encoding);
2102 }
2103 Ok(())
2104 }
2105
2106 #[test]
2109 fn test_content_encoding_decode_fixtures() -> Result<(), Error> {
2110 let content = b"hello rain deflate fixture".to_vec();
2111 let zlib: Vec<u8> = vec![
2112 120, 156, 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44,
2113 73, 85, 72, 203, 172, 40, 41, 45, 74, 5, 0, 132, 64, 9, 251,
2114 ];
2115 let raw: Vec<u8> = vec![
2116 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44, 73, 85, 72,
2117 203, 172, 40, 41, 45, 74, 5, 0,
2118 ];
2119 assert_eq!(ContentEncoding::Deflate.decode(&zlib)?, content);
2120 assert_eq!(ContentEncoding::Deflate.decode(&raw)?, content);
2121 Ok(())
2122 }
2123
2124 #[test]
2127 fn test_content_encoding_decode_garbage_errors() {
2128 let garbage = [0xffu8, 0xff, 0xff, 0xff];
2129 assert!(matches!(
2130 ContentEncoding::Deflate.decode(&garbage),
2131 Err(Error::InflateError(_))
2132 ));
2133 }
2134
2135 #[test]
2137 fn test_content_headers_strum_names() {
2138 use std::str::FromStr;
2139 assert_eq!(
2140 ContentEncoding::from_str("deflate").unwrap(),
2141 ContentEncoding::Deflate
2142 );
2143 assert_eq!(
2144 ContentEncoding::from_str("identity").unwrap(),
2145 ContentEncoding::Identity
2146 );
2147 assert_eq!(
2148 ContentEncoding::from_str("none").unwrap(),
2149 ContentEncoding::None
2150 );
2151 assert_eq!(ContentEncoding::Deflate.to_string(), "deflate");
2152 assert_eq!(
2153 ContentType::from_str("octet-stream").unwrap(),
2154 ContentType::OctetStream
2155 );
2156 assert_eq!(ContentType::from_str("json").unwrap(), ContentType::Json);
2157 assert_eq!(ContentType::Json.to_string(), "json");
2158 assert_eq!(
2159 ContentLanguage::from_str("en").unwrap(),
2160 ContentLanguage::En
2161 );
2162 }
2163
2164 #[test]
2167 fn test_known_meta_try_from_magic() {
2168 let cases: [(KnownMagic, KnownMeta); 13] = [
2169 (KnownMagic::OpMetaV1, KnownMeta::OpV1),
2170 (KnownMagic::DotrainV1, KnownMeta::DotrainV1),
2171 (KnownMagic::RainlangV1, KnownMeta::RainlangV1),
2172 (KnownMagic::SolidityAbiV2, KnownMeta::SolidityAbiV2),
2173 (KnownMagic::AuthoringMetaV1, KnownMeta::AuthoringMetaV1),
2174 (KnownMagic::AuthoringMetaV2, KnownMeta::AuthoringMetaV2),
2175 (KnownMagic::AddressList, KnownMeta::AddressList),
2176 (
2177 KnownMagic::InterpreterCallerMetaV1,
2178 KnownMeta::InterpreterCallerMetaV1,
2179 ),
2180 (
2181 KnownMagic::ExpressionDeployerV2BytecodeV1,
2182 KnownMeta::ExpressionDeployerV2BytecodeV1,
2183 ),
2184 (KnownMagic::RainlangSourceV1, KnownMeta::RainlangSourceV1),
2185 (KnownMagic::DotrainSourceV1, KnownMeta::DotrainSourceV1),
2186 (
2187 KnownMagic::OrderBuilderStateV1,
2188 KnownMeta::OrderBuilderStateV1,
2189 ),
2190 (
2191 KnownMagic::RaindexSignedContextOracleV1,
2192 KnownMeta::RaindexSignedContextOracleV1,
2193 ),
2194 ];
2195 for (magic, meta) in cases {
2196 assert_eq!(KnownMeta::try_from(magic).unwrap(), meta, "{:?}", magic);
2197 }
2198 for magic in [
2199 KnownMagic::RainMetaDocumentV1,
2200 KnownMagic::WebDataV1,
2201 KnownMagic::OaSchema,
2202 KnownMagic::OaHashList,
2203 KnownMagic::OaStructure,
2204 KnownMagic::OaTokenImage,
2205 KnownMagic::OaTokenCredentialLinks,
2206 ] {
2207 assert!(
2208 matches!(KnownMeta::try_from(magic), Err(Error::UnsupportedMeta)),
2209 "{:?}",
2210 magic
2211 );
2212 }
2213 }
2214
2215 #[test]
2218 fn test_known_meta_strum_parse_display() {
2219 use std::str::FromStr;
2220 assert_eq!(
2221 KnownMeta::from_str("solidity-abi-v2").unwrap(),
2222 KnownMeta::SolidityAbiV2
2223 );
2224 assert_eq!(
2225 KnownMeta::from_str("interpreter-caller-meta-v1").unwrap(),
2226 KnownMeta::InterpreterCallerMetaV1
2227 );
2228 assert_eq!(KnownMeta::SolidityAbiV2.to_string(), "solidity-abi-v2");
2229 }
2230
2231 #[tokio::test]
2233 async fn test_search_lowercases_hash() {
2234 use httpmock::prelude::*;
2235 let (_, doc) = sample_authoring_doc();
2236 let hash_upper = format!("0x{}", "AB".repeat(32));
2237 let server = MockServer::start();
2238 let mock = server.mock(|when, then| {
2239 when.method(POST)
2240 .body_contains(hash_upper.to_ascii_lowercase());
2241 then.status(200).json_body(json!({
2242 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2243 }));
2244 });
2245 let response = search(&hash_upper, &vec![server.url("/sg")]).await.unwrap();
2246 assert_eq!(response.bytes, doc);
2247 mock.assert();
2248 }
2249
2250 #[tokio::test]
2253 async fn test_search_first_success_wins() {
2254 use httpmock::prelude::*;
2255 let (_, doc) = sample_authoring_doc();
2256 let bad = MockServer::start();
2257 let _bad_mock = bad.mock(|when, then| {
2258 when.method(POST);
2259 then.status(500).body("subgraph down");
2260 });
2261 let good = MockServer::start();
2262 let _good_mock = good.mock(|when, then| {
2263 when.method(POST);
2264 then.status(200).json_body(json!({
2265 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2266 }));
2267 });
2268 let response = search(
2269 &format!("0x{}", "11".repeat(32)),
2270 &vec![bad.url("/sg"), good.url("/sg")],
2271 )
2272 .await
2273 .unwrap();
2274 assert_eq!(response.bytes, doc);
2275 }
2276
2277 #[tokio::test]
2281 async fn test_search_empty_subgraphs_is_a_miss() {
2282 let hash = format!("0x{}", "33".repeat(32));
2283 assert!(matches!(
2284 search(&hash, &vec![]).await,
2285 Err(Error::NoRecordFound)
2286 ));
2287 }
2288
2289 #[tokio::test]
2300 async fn test_implements_erc165_gate_short_circuits() {
2301 let address = Address::random();
2302
2303 let asserter = Asserter::new();
2305 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2306 asserter
2307 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2308 asserter
2309 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2310 assert!(!implements_i_described_by_meta_v1(&provider, address)
2311 .await
2312 .unwrap());
2313
2314 let asserter = Asserter::new();
2318 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2319 asserter.push_failure(ErrorPayload {
2320 code: -32000,
2321 message: "connection reset".into(),
2322 data: None,
2323 });
2324 asserter
2325 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2326 assert!(implements_i_described_by_meta_v1(&provider, address)
2327 .await
2328 .is_err());
2329 }
2330
2331 #[tokio::test]
2334 async fn test_implements_empty_response_is_false() {
2335 let address = Address::random();
2336 let asserter = Asserter::new();
2337 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2338 asserter
2339 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2340 asserter
2341 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2342 asserter.push_success(&"0x");
2343 assert!(!implements_i_described_by_meta_v1(&provider, address)
2344 .await
2345 .unwrap());
2346 }
2347
2348 #[tokio::test]
2352 async fn test_implements_described_by_call_error_is_unknown() {
2353 let address = Address::random();
2354 let asserter = Asserter::new();
2355 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2356 asserter
2357 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2358 asserter
2359 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2360 asserter.push_failure(ErrorPayload {
2361 code: -32005,
2362 message: "rate limit exceeded".into(),
2363 data: None,
2364 });
2365 let error = implements_i_described_by_meta_v1(&provider, address)
2366 .await
2367 .unwrap_err();
2368 assert!(error.to_string().contains("rate limit exceeded"));
2369 }
2370
2371 #[tokio::test]
2375 async fn test_implements_undecodable_response_is_unknown() {
2376 let address = Address::random();
2377 let asserter = Asserter::new();
2378 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2379 asserter
2380 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2381 asserter
2382 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2383 asserter.push_success(&"0xdeadbeef");
2384 implements_i_described_by_meta_v1(&provider, address)
2385 .await
2386 .unwrap_err();
2387 }
2388
2389 #[tokio::test]
2393 async fn test_store_constructors_inject_no_subgraphs() {
2394 assert!(Store::new().subgraphs().is_empty());
2395 assert!(Store::default().subgraphs().is_empty());
2396 assert!(
2397 Store::create(&vec![], &MetaCache::default(), &HashMap::new())
2398 .subgraphs()
2399 .is_empty()
2400 );
2401
2402 let hash = [0u8; 32];
2403 let mut store = Store::default();
2404 assert!(store.update(&hash).await.is_err());
2405 }
2406
2407 #[test]
2415 fn test_store_create_validates_entries() {
2416 let (_, doc) = sample_authoring_doc();
2417 let good_hash = keccak256(&doc).0.to_vec();
2418 let mut cache = MetaCache::default();
2419 cache.insert_verified(&good_hash, doc.clone()).unwrap();
2420 let mut dotrain_cache = HashMap::new();
2421 dotrain_cache.insert("a.rain".to_string(), good_hash.clone());
2422 dotrain_cache.insert("missing.rain".to_string(), vec![0x44u8; 32]);
2423
2424 let store = Store::create(
2425 &vec!["https://example.com/custom-sg".to_string()],
2426 &cache,
2427 &dotrain_cache,
2428 );
2429
2430 assert_eq!(
2431 store.subgraphs(),
2432 &vec!["https://example.com/custom-sg".to_string()]
2433 );
2434 assert_eq!(store.get_meta(&good_hash), Some(&doc));
2435 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&good_hash));
2436 assert_eq!(store.get_dotrain_hash("missing.rain"), None);
2437 }
2438
2439 #[test]
2441 fn test_store_add_subgraphs_dedupe() {
2442 let mut store = Store::new();
2443 store.add_subgraphs(&vec!["sg-a".to_string()]);
2444 store.add_subgraphs(&vec!["sg-a".to_string(), "sg-b".to_string()]);
2445 assert_eq!(
2446 store.subgraphs(),
2447 &vec!["sg-a".to_string(), "sg-b".to_string()]
2448 );
2449 }
2450
2451 #[test]
2455 fn test_store_dotrain_getters_and_set_fresh() {
2456 let mut store = Store::new();
2457 let text = "some dotrain content";
2458 let (hash, old) = store.set_dotrain(text, "file.rain", false).unwrap();
2459 assert!(old.is_empty());
2460 let expected_item = RainMetaDocumentV1Item {
2461 payload: serde_bytes::ByteBuf::from(text.as_bytes()),
2462 magic: KnownMagic::DotrainV1,
2463 content_type: ContentType::OctetStream,
2464 content_encoding: ContentEncoding::None,
2465 content_language: ContentLanguage::None,
2466 schema: None,
2467 };
2468 let expected_bytes = expected_item.cbor_encode().unwrap();
2469 assert_eq!(hash, keccak256(&expected_bytes).0.to_vec());
2470 assert_eq!(store.get_dotrain_hash("file.rain"), Some(&hash));
2471 assert_eq!(store.get_dotrain_uri(&hash), Some(&"file.rain".to_string()));
2472 assert_eq!(store.get_dotrain_meta("file.rain"), Some(&expected_bytes));
2473 assert_eq!(store.get_dotrain_hash("other.rain"), None);
2474 assert_eq!(store.get_dotrain_uri(&[0u8; 32]), None);
2475 assert_eq!(store.get_dotrain_meta("other.rain"), None);
2476 }
2477
2478 #[test]
2482 fn test_store_set_dotrain_branches() {
2483 let mut store = Store::new();
2484 let (hash_one, _) = store.set_dotrain("text one", "a.rain", false).unwrap();
2485
2486 let (hash_same, old_same) = store.set_dotrain("text one", "a.rain", false).unwrap();
2488 assert_eq!(hash_same, hash_one);
2489 assert!(old_same.is_empty());
2490 assert!(store.get_meta(&hash_one).is_some());
2491
2492 let (hash_two, old_two) = store.set_dotrain("text two", "a.rain", false).unwrap();
2494 assert_ne!(hash_two, hash_one);
2495 assert_eq!(old_two, hash_one);
2496 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_two));
2497 assert!(store.get_meta(&hash_one).is_none());
2498 assert!(store.get_meta(&hash_two).is_some());
2499
2500 let (hash_three, old_three) = store.set_dotrain("text three", "a.rain", true).unwrap();
2502 assert_eq!(old_three, hash_two);
2503 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_three));
2504 assert!(store.get_meta(&hash_two).is_some());
2505 assert!(store.get_meta(&hash_three).is_some());
2506 }
2507
2508 #[test]
2511 fn test_store_delete_dotrain_keep_meta() {
2512 let mut store = Store::new();
2513 let (hash, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2514 store.delete_dotrain("d.rain", false);
2515 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2516 assert!(store.get_meta(&hash).is_none());
2517
2518 let (hash_again, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2519 store.delete_dotrain("d.rain", true);
2520 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2521 assert!(store.get_meta(&hash_again).is_some());
2522 }
2523
2524 #[test]
2528 fn test_store_dotrain_shared_meta_survives_delete() {
2529 let mut store = Store::new();
2530 let (hash, _) = store.set_dotrain("same text", "a.rain", false).unwrap();
2531 let (hash_b, _) = store.set_dotrain("same text", "b.rain", false).unwrap();
2532 assert_eq!(hash_b, hash);
2533
2534 store.delete_dotrain("a.rain", false);
2535 assert_eq!(store.get_dotrain_hash("a.rain"), None);
2536 assert_eq!(store.get_dotrain_hash("b.rain"), Some(&hash));
2537 assert!(store.get_dotrain_meta("b.rain").is_some());
2538
2539 store.delete_dotrain("b.rain", false);
2540 assert!(store.get_meta(&hash).is_none());
2541 }
2542
2543 #[test]
2546 fn test_store_set_dotrain_keeps_shared_old_meta() {
2547 let mut store = Store::new();
2548 let (shared, _) = store.set_dotrain("same text", "a.rain", false).unwrap();
2549 store.set_dotrain("same text", "b.rain", false).unwrap();
2550
2551 let (new_hash, old_hash) = store.set_dotrain("other text", "a.rain", false).unwrap();
2552 assert_eq!(old_hash, shared);
2553 assert_ne!(new_hash, shared);
2554 assert!(store.get_dotrain_meta("b.rain").is_some());
2555
2556 store.set_dotrain("other text", "b.rain", false).unwrap();
2557 assert!(store.get_meta(&shared).is_none());
2558 }
2559
2560 #[test]
2563 fn test_store_merge_semantics() {
2564 let mut ours = Store::new();
2565 let mut theirs = Store::new();
2566
2567 let mine = b"mine".to_vec();
2571 let yours = b"yours".to_vec();
2572 let mine_hash = keccak256(&mine).0.to_vec();
2573 let yours_hash = keccak256(&yours).0.to_vec();
2574 ours.update_with(&mine_hash, &mine).unwrap();
2575 theirs.update_with(&yours_hash, &yours).unwrap();
2576
2577 let (hash_ours, _) = ours.set_dotrain("content a", "x.rain", false).unwrap();
2579 let (_hash_theirs, _) = theirs.set_dotrain("content b", "x.rain", false).unwrap();
2580
2581 theirs.add_subgraphs(&vec!["sg-their".to_string()]);
2582
2583 ours.merge(&theirs);
2584
2585 assert_eq!(ours.get_meta(&mine_hash), Some(&mine));
2586 assert_eq!(ours.get_meta(&yours_hash), Some(&yours));
2587 assert_eq!(ours.get_dotrain_hash("x.rain"), Some(&hash_ours));
2589 assert!(ours.subgraphs().contains(&"sg-their".to_string()));
2591 }
2592
2593 #[tokio::test]
2597 async fn test_store_update_and_update_check() {
2598 use httpmock::prelude::*;
2599 let authoring_meta: AuthoringMeta = serde_json::from_str(
2600 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
2601 )
2602 .unwrap();
2603 let item_one = RainMetaDocumentV1Item {
2604 payload: serde_bytes::ByteBuf::from(authoring_meta.abi_encode_validate().unwrap()),
2605 magic: KnownMagic::AuthoringMetaV1,
2606 content_type: ContentType::Cbor,
2607 content_encoding: ContentEncoding::None,
2608 content_language: ContentLanguage::None,
2609 schema: None,
2610 };
2611 let item_two = sample_dotrain_item();
2612 let doc = RainMetaDocumentV1Item::cbor_encode_seq(
2613 &vec![item_one.clone(), item_two.clone()],
2614 KnownMagic::RainMetaDocumentV1,
2615 )
2616 .unwrap();
2617 let requested = keccak256(&doc).0.to_vec();
2618 let server = MockServer::start();
2619 let _mock = server.mock(|when, then| {
2620 when.method(POST);
2621 then.status(200).json_body(json!({
2622 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2623 }));
2624 });
2625 let mut store = Store::new();
2626 store.add_subgraphs(&vec![server.url("/sg")]);
2627 let fetched = store.update(&requested).await.cloned().unwrap();
2628 assert_eq!(fetched, doc);
2629 assert_eq!(store.get_meta(&requested), Some(&doc));
2630 let inner_one = item_one.cbor_encode().unwrap();
2631 let inner_two = item_two.cbor_encode().unwrap();
2632 assert_eq!(
2633 store.get_meta(keccak256(&inner_one).0.as_ref()),
2634 Some(&inner_one)
2635 );
2636 assert_eq!(
2637 store.get_meta(keccak256(&inner_two).0.as_ref()),
2638 Some(&inner_two)
2639 );
2640
2641 let mut cached_store = Store::new();
2643 let bytes = b"standalone meta bytes".to_vec();
2644 let hash = keccak256(&bytes).0.to_vec();
2645 assert!(cached_store.update_with(&hash, &bytes).is_ok());
2646 assert_eq!(cached_store.update_check(&hash).await.unwrap(), &bytes);
2647 }
2648
2649 #[tokio::test]
2653 async fn test_store_update_rejects_hash_mismatch() {
2654 use httpmock::prelude::*;
2655 let (_, doc) = sample_authoring_doc();
2656 let requested = keccak256(b"the real content").0.to_vec();
2657 let server = MockServer::start();
2658 let _mock = server.mock(|when, then| {
2659 when.method(POST);
2660 then.status(200).json_body(json!({
2661 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2662 }));
2663 });
2664 let mut store = Store::new();
2665 store.add_subgraphs(&vec![server.url("/sg")]);
2666 assert!(store.update(&requested).await.is_err());
2667 assert!(store.get_meta(&requested).is_none());
2668 assert!(store.cache().is_empty());
2669 assert!(store.update_check(&requested).await.is_err());
2671 }
2672
2673 #[tokio::test]
2676 async fn test_store_no_subgraphs_lookups_return_none() {
2677 let hash = [0u8; 32];
2678 let mut store = Store::new();
2679 assert!(store.update(&hash).await.is_err());
2680 assert!(store.update_check(&hash).await.is_err());
2681 assert!(store.cache().is_empty());
2682 }
2683
2684 #[test]
2688 fn test_store_update_with_validation_and_content() {
2689 let mut store = Store::new();
2691 let bytes = b"payload bytes".to_vec();
2692 let wrong_hash = vec![0x99u8; 32];
2693 match store.update_with(&wrong_hash, &bytes).unwrap_err() {
2698 Error::CorruptRecord(message) => {
2699 assert!(
2700 message.contains(&hex::encode_prefixed(&wrong_hash)),
2701 "{}",
2702 message
2703 )
2704 }
2705 other => panic!("expected CorruptRecord, got {:?}", other),
2706 }
2707 assert!(store.get_meta(&wrong_hash).is_none());
2708 let hash = keccak256(&bytes).0.to_vec();
2710 assert_eq!(store.update_with(&hash, &bytes).unwrap(), &bytes);
2711
2712 let mut seeded = Store::new();
2719 let planted = b"planted value".to_vec();
2720 let planted_hash = keccak256(&planted).0.to_vec();
2721 seeded.update_with(&planted_hash, &planted).unwrap();
2722 assert_eq!(seeded.cache().len(), 1);
2723 assert_eq!(
2724 seeded.update_with(&planted_hash, &planted).unwrap(),
2725 &planted
2726 );
2727 assert_eq!(seeded.cache().len(), 1);
2728
2729 let (_, doc) = sample_authoring_doc();
2731 let doc_hash = keccak256(&doc).0.to_vec();
2732 let mut doc_store = Store::new();
2733 assert!(doc_store.update_with(&doc_hash, &doc).is_ok());
2734 let inner = doc[8..].to_vec();
2735 assert_eq!(store_inner_lookup(&doc_store, &inner), Some(inner.clone()));
2736
2737 let item_a = sample_dotrain_item().cbor_encode().unwrap();
2739 let (_, doc_b) = sample_authoring_doc();
2740 let item_b = doc_b[8..].to_vec();
2741 let seq = [item_a.clone(), item_b].concat();
2742 let seq_hash = keccak256(&seq).0.to_vec();
2743 let mut seq_store = Store::new();
2744 assert!(seq_store.update_with(&seq_hash, &seq).is_ok());
2745 assert_eq!(store_inner_lookup(&seq_store, &item_a), None);
2746 }
2747
2748 fn store_inner_lookup(store: &Store, inner_encoded: &[u8]) -> Option<Vec<u8>> {
2749 store.get_meta(keccak256(inner_encoded).0.as_ref()).cloned()
2750 }
2751
2752 #[test]
2755 fn test_bytes32_to_str_invalid_utf8() {
2756 let mut bytes = [0u8; 32];
2757 bytes[0] = 0xf0;
2758 bytes[1] = 0x28;
2759 bytes[2] = 0x8c;
2760 bytes[3] = 0x28;
2761 assert!(matches!(bytes32_to_str(&bytes), Err(Error::Utf8Error(_))));
2762 let no_nul = [0xffu8; 32];
2763 assert!(matches!(bytes32_to_str(&no_nul), Err(Error::Utf8Error(_))));
2764 }
2765}