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,
49 AuthoringMetaV1,
50 AuthoringMetaV2,
51 InterpreterCallerMetaV1,
54 ExpressionDeployerV2BytecodeV1,
55 RainlangSourceV1,
56 AddressList,
57 DotrainSourceV1,
58 OrderBuilderStateV1,
59 RaindexSignedContextOracleV1,
60}
61
62impl TryFrom<KnownMagic> for KnownMeta {
63 type Error = Error;
64 fn try_from(value: KnownMagic) -> Result<Self, Self::Error> {
65 match value {
66 KnownMagic::DotrainV1 => Ok(KnownMeta::DotrainV1),
67 KnownMagic::RainlangV1 => Ok(KnownMeta::RainlangV1),
68 KnownMagic::SolidityAbiV2 => Ok(KnownMeta::SolidityAbiV2),
69 KnownMagic::OpMetaV1 => Ok(KnownMeta::OpV1),
70 KnownMagic::AuthoringMetaV1 => Ok(KnownMeta::AuthoringMetaV1),
71 KnownMagic::AuthoringMetaV2 => Ok(KnownMeta::AuthoringMetaV2),
72 KnownMagic::AddressList => Ok(KnownMeta::AddressList),
73 KnownMagic::InterpreterCallerMetaV1 => Ok(KnownMeta::InterpreterCallerMetaV1),
74 KnownMagic::DotrainSourceV1 => Ok(KnownMeta::DotrainSourceV1),
75 KnownMagic::OrderBuilderStateV1 => Ok(KnownMeta::OrderBuilderStateV1),
76 KnownMagic::ExpressionDeployerV2BytecodeV1 => {
77 Ok(KnownMeta::ExpressionDeployerV2BytecodeV1)
78 }
79 KnownMagic::RainlangSourceV1 => Ok(KnownMeta::RainlangSourceV1),
80 KnownMagic::RaindexSignedContextOracleV1 => Ok(KnownMeta::RaindexSignedContextOracleV1),
81 _ => Err(Error::UnsupportedMeta),
82 }
83 }
84}
85
86#[derive(
88 Copy,
89 Clone,
90 Debug,
91 EnumIter,
92 PartialEq,
93 EnumString,
94 strum::Display,
95 serde::Serialize,
96 serde::Deserialize,
97)]
98#[strum(serialize_all = "kebab-case")]
99pub enum ContentType {
100 None,
101 #[serde(rename = "application/json")]
102 Json,
103 #[serde(rename = "application/cbor")]
104 Cbor,
105 #[serde(rename = "application/octet-stream")]
106 OctetStream,
107}
108
109#[derive(
111 Copy,
112 Clone,
113 Debug,
114 EnumIter,
115 PartialEq,
116 EnumString,
117 strum::Display,
118 serde::Serialize,
119 serde::Deserialize,
120)]
121#[serde(rename_all = "kebab-case")]
122#[strum(serialize_all = "kebab-case")]
123pub enum ContentEncoding {
124 None,
125 Identity,
126 Deflate,
127}
128
129impl ContentEncoding {
130 pub fn encode(&self, data: &[u8]) -> Vec<u8> {
132 match self {
133 ContentEncoding::None | ContentEncoding::Identity => data.to_vec(),
134 ContentEncoding::Deflate => deflate::deflate_bytes_zlib(data),
135 }
136 }
137
138 pub fn decode(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
140 Ok(match self {
141 ContentEncoding::None | ContentEncoding::Identity => data.to_vec(),
142 ContentEncoding::Deflate => match inflate::inflate_bytes_zlib(data) {
143 Ok(v) => v,
144 Err(error) => match inflate::inflate_bytes(data) {
145 Ok(v) => v,
146 Err(_) => Err(Error::InflateError(error))?,
147 },
148 },
149 })
150 }
151}
152
153#[derive(
155 Copy,
156 Clone,
157 Debug,
158 EnumIter,
159 PartialEq,
160 EnumString,
161 strum::Display,
162 serde::Serialize,
163 serde::Deserialize,
164)]
165#[serde(rename_all = "kebab-case")]
166#[strum(serialize_all = "kebab-case")]
167pub enum ContentLanguage {
168 None,
169 En,
170}
171
172#[derive(PartialEq, Debug, Clone)]
176pub struct RainMetaDocumentV1Item {
177 pub payload: serde_bytes::ByteBuf,
178 pub magic: KnownMagic,
179 pub content_type: ContentType,
180 pub content_encoding: ContentEncoding,
181 pub content_language: ContentLanguage,
182 pub schema: Option<String>,
186}
187
188impl TryFrom<RainMetaDocumentV1Item> for String {
190 type Error = Error;
191 fn try_from(value: RainMetaDocumentV1Item) -> Result<Self, Self::Error> {
192 Ok(String::from_utf8(value.unpack()?)?)
193 }
194}
195
196impl TryFrom<RainMetaDocumentV1Item> for Vec<u8> {
198 type Error = Error;
199 fn try_from(value: RainMetaDocumentV1Item) -> Result<Self, Self::Error> {
200 value.unpack()
201 }
202}
203
204impl RainMetaDocumentV1Item {
205 fn len(&self) -> usize {
206 let mut l = 2;
207 if !matches!(self.content_type, ContentType::None) {
208 l += 1;
209 }
210 if !matches!(self.content_encoding, ContentEncoding::None) {
211 l += 1;
212 }
213 if !matches!(self.content_language, ContentLanguage::None) {
214 l += 1;
215 }
216 if self.schema.is_some() {
217 l += 1;
218 }
219 l
220 }
221
222 pub fn hash(&self, as_rain_meta_document: bool) -> Result<[u8; 32], Error> {
224 if as_rain_meta_document {
225 Ok(keccak256(Self::cbor_encode_seq(
226 &vec![self.clone()],
227 KnownMagic::RainMetaDocumentV1,
228 )?)
229 .0)
230 } else {
231 Ok(keccak256(self.cbor_encode()?).0)
232 }
233 }
234
235 pub fn cbor_encode(&self) -> Result<Vec<u8>, Error> {
237 let mut bytes: Vec<u8> = vec![];
238 Ok(serde_cbor::to_writer(&mut bytes, &self).map(|_| bytes)?)
239 }
240
241 pub fn cbor_encode_seq(
243 seq: &Vec<RainMetaDocumentV1Item>,
244 magic: KnownMagic,
245 ) -> Result<Vec<u8>, Error> {
246 let mut bytes: Vec<u8> = magic.to_prefix_bytes().to_vec();
247 for item in seq {
248 serde_cbor::to_writer(&mut bytes, &item)?;
249 }
250 Ok(bytes)
251 }
252
253 pub fn cbor_decode(data: &[u8]) -> Result<Vec<RainMetaDocumentV1Item>, Error> {
255 let Some(body) = data.strip_prefix(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes()[..])
264 else {
265 return Err(Error::NotRainMetaDocument);
266 };
267 let len = body.len();
268 let mut metas: Vec<RainMetaDocumentV1Item> = vec![];
269 let mut consumed: usize = 0;
270 let mut deserializer = serde_cbor::Deserializer::from_slice(body);
271 while match ItemOrDropped::deserialize(&mut deserializer) {
280 Ok(decoded) => {
281 consumed = deserializer.byte_offset();
282 if let ItemOrDropped::Item(meta) = decoded {
283 metas.push(meta);
284 }
285 true
286 }
287 Err(error) => {
288 if error.is_eof() {
289 false
290 } else {
291 Err(Error::SerdeCborError(error))?
292 }
293 }
294 } {}
295
296 if metas.is_empty() || len != consumed {
297 Err(Error::CorruptMeta)?
298 }
299 Ok(metas)
300 }
301
302 pub fn unpack(&self) -> Result<Vec<u8>, Error> {
304 ContentEncoding::decode(&self.content_encoding, self.payload.as_ref())
305 }
306
307 pub fn unpack_into<T: TryFrom<Self, Error = Error>>(self) -> Result<T, Error> {
309 match self.magic {
310 KnownMagic::OpMetaV1
311 | KnownMagic::DotrainV1
312 | KnownMagic::RainlangV1
313 | KnownMagic::SolidityAbiV2
314 | KnownMagic::AuthoringMetaV1
315 | KnownMagic::AuthoringMetaV2
316 | KnownMagic::AddressList
317 | KnownMagic::InterpreterCallerMetaV1
318 | KnownMagic::ExpressionDeployerV2BytecodeV1
319 | KnownMagic::DotrainSourceV1
320 | KnownMagic::OrderBuilderStateV1
321 | KnownMagic::RainlangSourceV1
322 | KnownMagic::RaindexSignedContextOracleV1 => T::try_from(self),
323 _ => Err(Error::UnsupportedMeta)?,
324 }
325 }
326}
327
328impl Serialize for RainMetaDocumentV1Item {
329 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
330 let mut map = serializer.serialize_map(Some(self.len()))?;
331 map.serialize_entry(&0, &self.payload)?;
332 map.serialize_entry(&1, &(self.magic as u64))?;
333 match self.content_type {
334 ContentType::None => {}
335 content_type => map.serialize_entry(&2, &content_type)?,
336 }
337 match self.content_encoding {
338 ContentEncoding::None => {}
339 content_encoding => map.serialize_entry(&3, &content_encoding)?,
340 }
341 match self.content_language {
342 ContentLanguage::None => {}
343 content_language => map.serialize_entry(&4, &content_language)?,
344 }
345 if let Some(schema) = &self.schema {
346 map.serialize_entry(&(KnownMagic::OaSchema as u64), schema)?;
347 }
348 map.end()
349 }
350}
351
352enum ItemOrDropped {
371 Item(RainMetaDocumentV1Item),
372 Dropped,
374}
375
376impl<'de> Deserialize<'de> for ItemOrDropped {
377 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
378 fn set_once<V, E: serde::de::Error>(
381 slot: &mut Option<V>,
382 value: V,
383 field: &'static str,
384 ) -> Result<(), E> {
385 if slot.is_some() {
386 return Err(serde::de::Error::duplicate_field(field));
387 }
388 *slot = Some(value);
389 Ok(())
390 }
391
392 struct EncodedMap;
393 impl<'de> Visitor<'de> for EncodedMap {
394 type Value = ItemOrDropped;
395
396 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
397 formatter.write_str("rain meta cbor encoded bytes")
398 }
399
400 fn visit_map<T: serde::de::MapAccess<'de>>(
401 self,
402 mut map: T,
403 ) -> Result<Self::Value, T::Error> {
404 const OA_SCHEMA_KEY: u64 = KnownMagic::OaSchema as u64;
405 let mut payload = None;
406 let mut magic: Option<u64> = None;
407 let mut content_type = None;
408 let mut content_encoding = None;
409 let mut content_language = None;
410 let mut schema = None;
411 let mut unknown_keys: BTreeSet<u64> = BTreeSet::new();
415 while match map.next_key::<u64>() {
416 Ok(Some(key)) => {
417 match key {
418 0 => set_once(&mut payload, map.next_value()?, "payload")?,
419 1 => set_once(&mut magic, map.next_value()?, "magic number")?,
420 2 => set_once(&mut content_type, map.next_value()?, "content type")?,
421 3 => set_once(
422 &mut content_encoding,
423 map.next_value()?,
424 "content encoding",
425 )?,
426 4 => set_once(
427 &mut content_language,
428 map.next_value()?,
429 "content language",
430 )?,
431 OA_SCHEMA_KEY => set_once(&mut schema, map.next_value()?, "schema")?,
432 _ => {
440 if !unknown_keys.insert(key) {
441 return Err(serde::de::Error::custom(format!(
442 "duplicate map key: {key}"
443 )));
444 }
445 map.next_value::<serde::de::IgnoredAny>()?;
446 }
447 };
448 true
449 }
450 Ok(None) => false,
451 Err(error) => Err(error)?,
452 } {}
453 let (Some(payload), Some(magic_number)) = (payload, magic) else {
463 return Err(serde::de::Error::custom(
464 "cbor item omits a mandatory key (0 payload, 1 magic)",
465 ));
466 };
467
468 if magic_number == KnownMagic::RainMetaDocumentV1 as u64 {
477 return Err(serde::de::Error::custom(
478 "rain meta document magic number as an item magic number",
479 ));
480 }
481
482 let Ok(magic) = KnownMagic::try_from(magic_number) else {
483 return Ok(ItemOrDropped::Dropped);
484 };
485
486 let content_type = content_type.unwrap_or(ContentType::None);
487 let content_encoding = content_encoding.unwrap_or(ContentEncoding::None);
488 let content_language = content_language.unwrap_or(ContentLanguage::None);
489
490 Ok(ItemOrDropped::Item(RainMetaDocumentV1Item {
491 payload,
492 magic,
493 content_type,
494 content_encoding,
495 content_language,
496 schema,
497 }))
498 }
499 }
500 deserializer.deserialize_map(EncodedMap)
501 }
502}
503
504impl<'de> Deserialize<'de> for RainMetaDocumentV1Item {
511 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
512 match ItemOrDropped::deserialize(deserializer)? {
513 ItemOrDropped::Item(item) => Ok(item),
514 ItemOrDropped::Dropped => Err(serde::de::Error::custom(
515 "not a rain meta item this version reads: a mandatory key is missing or the magic number is unknown",
516 )),
517 }
518 }
519}
520
521pub async fn search(hash: &str, subgraphs: &Vec<String>) -> Result<query::MetaResponse, Error> {
523 if subgraphs.is_empty() {
525 return Err(Error::NoRecordFound);
526 }
527 let request_body = query::MetaQuery::build_query(query::meta_query::Variables {
528 hash: Some(hash.to_ascii_lowercase()),
529 });
530 let mut promises = vec![];
531
532 let client = Arc::new(Client::builder().build().map_err(Error::ReqwestError)?);
533 for url in subgraphs {
534 promises.push(Box::pin(query::process_meta_query(
535 client.clone(),
536 &request_body,
537 url,
538 )));
539 }
540 let response_value = future::select_ok(promises.drain(..)).await?.0;
541 Ok(response_value)
542}
543
544pub async fn implements_i_described_by_meta_v1<P: Provider>(
552 provider: &P,
553 contract_address: Address,
554) -> Result<bool, Erc165Error> {
555 if !supports_erc165(provider, contract_address).await? {
556 return Ok(false);
557 }
558
559 let interface_id_res = IDescribedByMetaV1::IDescribedByMetaV1Calls::xor_selectors();
560 if interface_id_res.is_err() {
561 return Ok(false);
562 }
563
564 match IERC165::new(contract_address, provider)
565 .supportsInterface(interface_id_res.unwrap().into())
566 .call()
567 .await
568 {
569 Ok(supported) => Ok(supported),
570 Err(error)
571 if error.as_revert_data().is_some()
572 || matches!(error, ContractError::ZeroData(_, _)) =>
573 {
574 Ok(false)
575 }
576 Err(error) => Err(error.into()),
577 }
578}
579
580#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
635pub struct Store {
636 subgraphs: Vec<String>,
637 cache: MetaCache,
638 dotrain_cache: HashMap<String, Vec<u8>>,
639}
640
641impl Default for Store {
642 fn default() -> Self {
643 Store::new()
644 }
645}
646
647impl Store {
648 pub fn new() -> Store {
651 Store {
652 subgraphs: vec![],
653 cache: MetaCache::default(),
654 dotrain_cache: HashMap::new(),
655 }
656 }
657
658 pub fn create(
661 subgraphs: &Vec<String>,
662 cache: &MetaCache,
663 dotrain_cache: &HashMap<String, Vec<u8>>,
664 ) -> Store {
665 let mut store = Store::new();
666 store.add_subgraphs(subgraphs);
667 for (hash, bytes) in cache.iter() {
668 let _ = store.update_with(hash, bytes);
669 }
670 for (uri, hash) in dotrain_cache {
671 if !store.dotrain_cache.contains_key(uri) && store.cache.contains_key(hash) {
672 store.dotrain_cache.insert(uri.clone(), hash.clone());
673 }
674 }
675 store
676 }
677
678 pub fn subgraphs(&self) -> &Vec<String> {
680 &self.subgraphs
681 }
682
683 pub fn add_subgraphs(&mut self, subgraphs: &Vec<String>) {
685 for sg in subgraphs {
686 if !self.subgraphs.contains(sg) {
687 self.subgraphs.push(sg.to_string());
688 }
689 }
690 }
691
692 pub fn cache(&self) -> &MetaCache {
694 &self.cache
695 }
696
697 pub fn get_meta(&self, hash: &[u8]) -> Option<&Vec<u8>> {
699 self.cache.get(hash)
700 }
701
702 pub fn dotrain_cache(&self) -> &HashMap<String, Vec<u8>> {
704 &self.dotrain_cache
705 }
706
707 pub fn get_dotrain_hash(&self, uri: &str) -> Option<&Vec<u8>> {
709 self.dotrain_cache.get(uri)
710 }
711
712 pub fn get_dotrain_uri(&self, hash: &[u8]) -> Option<&String> {
714 for (uri, h) in &self.dotrain_cache {
715 if h == hash {
716 return Some(uri);
717 }
718 }
719 None
720 }
721
722 pub fn get_dotrain_meta(&self, uri: &str) -> Option<&Vec<u8>> {
724 self.get_meta(self.dotrain_cache.get(uri)?)
725 }
726
727 pub fn delete_dotrain(&mut self, uri: &str, keep_meta: bool) {
730 if let Some(kv) = self.dotrain_cache.remove_entry(uri) {
731 if !keep_meta {
732 self.remove_unreferenced_meta(&kv.1);
733 }
734 };
735 }
736
737 fn remove_unreferenced_meta(&mut self, hash: &[u8]) {
739 if !self.dotrain_cache.values().any(|h| h == hash) {
740 self.cache.remove(hash);
741 }
742 }
743
744 pub fn merge(&mut self, other: &Store) {
747 self.add_subgraphs(&other.subgraphs);
748 for (hash, bytes) in other.cache.iter() {
749 if !self.cache.contains_key(hash) {
750 let _ = self.cache.insert_verified(hash, bytes.clone());
753 }
754 }
755 for (uri, hash) in &other.dotrain_cache {
756 if !self.dotrain_cache.contains_key(uri) {
757 self.dotrain_cache.insert(uri.clone(), hash.clone());
758 }
759 }
760 }
761
762 fn insert_verified(&mut self, hash: &[u8], bytes: Vec<u8>) -> Result<&Vec<u8>, Error> {
766 self.cache.insert_verified(hash, bytes.clone())?;
767 self.store_content(&bytes);
768 self.get_meta(hash).ok_or(Error::NoRecordFound)
769 }
770
771 pub async fn update(&mut self, hash: &[u8]) -> Result<&Vec<u8>, Error> {
776 let meta = search(&hex::encode_prefixed(hash), &self.subgraphs).await?;
777 self.insert_verified(hash, meta.bytes)
778 }
779
780 pub async fn update_check(&mut self, hash: &[u8]) -> Result<&Vec<u8>, Error> {
782 if self.cache.contains_key(hash) {
788 return self.get_meta(hash).ok_or(Error::NoRecordFound);
789 }
790 self.update(hash).await
791 }
792
793 pub fn update_with(&mut self, hash: &[u8], bytes: &[u8]) -> Result<&Vec<u8>, Error> {
797 if self.cache.contains_key(hash) {
803 return self.get_meta(hash).ok_or(Error::NoRecordFound);
804 }
805 self.insert_verified(hash, bytes.to_vec())
806 }
807
808 pub fn set_dotrain(
814 &mut self,
815 text: &str,
816 uri: &str,
817 keep_old: bool,
818 ) -> Result<(Vec<u8>, Vec<u8>), Error> {
819 let bytes = RainMetaDocumentV1Item {
820 payload: serde_bytes::ByteBuf::from(text.as_bytes()),
821 magic: KnownMagic::DotrainV1,
822 content_type: ContentType::OctetStream,
823 content_encoding: ContentEncoding::None,
824 content_language: ContentLanguage::None,
825 schema: None,
826 }
827 .cbor_encode()?;
828 let new_hash = keccak256(&bytes).0.to_vec();
829 if let Some(h) = self.dotrain_cache.get(uri) {
830 let old_hash = h.clone();
831 if new_hash == old_hash {
832 self.cache.insert_verified(&new_hash, bytes)?;
833 Ok((new_hash, vec![]))
834 } else {
835 self.cache.insert_verified(&new_hash, bytes)?;
836 self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
837 if !keep_old {
838 self.remove_unreferenced_meta(&old_hash);
839 }
840 Ok((new_hash, old_hash))
841 }
842 } else {
843 self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
844 self.cache.insert_verified(&new_hash, bytes)?;
845 Ok((new_hash, vec![]))
846 }
847 }
848
849 fn store_content(&mut self, bytes: &[u8]) {
853 if let Ok(meta_maps) = RainMetaDocumentV1Item::cbor_decode(bytes) {
858 for meta_map in &meta_maps {
859 if let Ok(encoded_bytes) = meta_map.cbor_encode() {
860 let _ = self
864 .cache
865 .insert_verified(&keccak256(&encoded_bytes).0, encoded_bytes);
866 }
867 }
868 }
869 }
870}
871
872pub fn str_to_bytes32(text: &str) -> Result<[u8; 32], Error> {
878 let bytes: &[u8] = text.as_bytes();
879 if bytes.len() > 32 {
880 return Err(Error::BiggerThan32Bytes);
881 }
882 if bytes.contains(&0u8) {
883 return Err(Error::NulByteInInput);
884 }
885 let mut b32 = [0u8; 32];
886 b32[..bytes.len()].copy_from_slice(bytes);
887 Ok(b32)
888}
889
890pub fn bytes32_to_str(bytes: &[u8; 32]) -> Result<&str, Error> {
892 let mut len = 32;
893 if let Some((pos, _)) = itertools::Itertools::find_position(&mut bytes.iter(), |b| **b == 0u8) {
894 len = pos;
895 };
896 Ok(std::str::from_utf8(&bytes[..len])?)
897}
898
899#[cfg(all(test, not(target_family = "wasm")))]
900mod tests {
901 use super::{
902 *, bytes32_to_str, magic::KnownMagic, str_to_bytes32, types::authoring::v1::AuthoringMeta,
903 ContentEncoding, ContentLanguage, ContentType, Error, RainMetaDocumentV1Item,
904 };
905 use alloy::providers::ProviderBuilder;
906 use alloy::{providers::mock::Asserter, rpc::json_rpc::ErrorPayload, sol_types::SolType};
907 use serde_json::json;
908
909 #[test]
912 fn authoring_meta_roundtrip() -> Result<(), Error> {
913 let authoring_meta_content = r#"[
914 {
915 "word": "stack",
916 "description": "Copies an existing value from the stack.",
917 "operandParserOffset": 16
918 },
919 {
920 "word": "constant",
921 "description": "Copies a constant value onto the stack.",
922 "operandParserOffset": 16
923 }
924 ]"#;
925 let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
926
927 let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
929 let expected_abi_encoded = <alloy::sol!((bytes32, uint8, string)[])>::abi_encode(&vec![
930 (
931 str_to_bytes32("stack")?,
932 16u8,
933 "Copies an existing value from the stack.".to_string(),
934 ),
935 (
936 str_to_bytes32("constant")?,
937 16u8,
938 "Copies a constant value onto the stack.".to_string(),
939 ),
940 ]);
941 assert_eq!(authoring_meta_abi_encoded, expected_abi_encoded);
943
944 let meta_map = RainMetaDocumentV1Item {
945 payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
946 magic: KnownMagic::AuthoringMetaV1,
947 content_type: ContentType::Cbor,
948 content_encoding: ContentEncoding::None,
949 content_language: ContentLanguage::None,
950 schema: None,
951 };
952 let cbor_encoded = meta_map.cbor_encode()?;
953
954 assert_eq!(cbor_encoded[0], 0xa3);
956 assert_eq!(cbor_encoded[1], 0x00);
958 assert_eq!(cbor_encoded[2], 0b010_11001);
960 assert_eq!(cbor_encoded[3], 0b000_00010);
961 assert_eq!(cbor_encoded[4], 0b000_00000);
962 assert_eq!(cbor_encoded[5..517], authoring_meta_abi_encoded);
964 assert_eq!(cbor_encoded[517], 0x01);
966 assert_eq!(cbor_encoded[518], 0b000_11011);
968 assert_eq!(
970 &cbor_encoded[519..527],
971 KnownMagic::AuthoringMetaV1.to_prefix_bytes()
972 );
973 assert_eq!(cbor_encoded[527], 0x02);
975 assert_eq!(cbor_encoded[528], 0b011_10000);
977 assert_eq!(&cbor_encoded[529..], "application/cbor".as_bytes());
979
980 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&cbor_encoded))?;
982 assert_eq!(cbor_decoded.len(), 1);
984 assert_eq!(cbor_decoded[0], meta_map);
986
987 Ok(())
988 }
989
990 #[test]
993 fn dotrain_meta_roundtrip() -> Result<(), Error> {
994 let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
995 let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
996
997 let content_encoding = ContentEncoding::Deflate;
998 let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
999
1000 let meta_map = RainMetaDocumentV1Item {
1001 payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
1002 magic: KnownMagic::DotrainV1,
1003 content_type: ContentType::OctetStream,
1004 content_encoding,
1005 content_language: ContentLanguage::En,
1006 schema: None,
1007 };
1008 let cbor_encoded = meta_map.cbor_encode()?;
1009
1010 assert_eq!(cbor_encoded[0], 0xa5);
1012 assert_eq!(cbor_encoded[1], 0x00);
1014 assert_eq!(cbor_encoded[2], 0b010_11000);
1016 assert_eq!(cbor_encoded[3], 0b001_00100);
1017 assert_eq!(cbor_encoded[4..40], deflated_payload);
1020 assert_eq!(cbor_encoded[40], 0x01);
1022 assert_eq!(cbor_encoded[41], 0b000_11011);
1024 assert_eq!(
1026 &cbor_encoded[42..50],
1027 KnownMagic::DotrainV1.to_prefix_bytes()
1028 );
1029 assert_eq!(cbor_encoded[50], 0x02);
1031 assert_eq!(cbor_encoded[51], 0b011_11000);
1033 assert_eq!(cbor_encoded[52], 0b000_11000);
1034 assert_eq!(&cbor_encoded[53..77], "application/octet-stream".as_bytes());
1036 assert_eq!(cbor_encoded[77], 0x03);
1038 assert_eq!(cbor_encoded[78], 0b011_00111);
1040 assert_eq!(&cbor_encoded[79..86], "deflate".as_bytes());
1042 assert_eq!(cbor_encoded[86], 0x04);
1044 assert_eq!(cbor_encoded[87], 0b011_00010);
1046 assert_eq!(&cbor_encoded[88..], "en".as_bytes());
1048
1049 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&cbor_encoded))?;
1051 assert_eq!(cbor_decoded.len(), 1);
1053 assert_eq!(cbor_decoded[0], meta_map);
1055
1056 Ok(())
1057 }
1058
1059 #[test]
1062 fn meta_seq_roundtrip() -> Result<(), Error> {
1063 let authoring_meta_content = r#"[
1064 {
1065 "word": "stack",
1066 "description": "Copies an existing value from the stack.",
1067 "operandParserOffset": 16
1068 },
1069 {
1070 "word": "constant",
1071 "description": "Copies a constant value onto the stack.",
1072 "operandParserOffset": 16
1073 }
1074 ]"#;
1075 let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
1076 let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
1077 let meta_map_1 = RainMetaDocumentV1Item {
1078 payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
1079 magic: KnownMagic::AuthoringMetaV1,
1080 content_type: ContentType::Cbor,
1081 content_encoding: ContentEncoding::None,
1082 content_language: ContentLanguage::None,
1083 schema: None,
1084 };
1085
1086 let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
1087 let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
1088 let content_encoding = ContentEncoding::Deflate;
1089 let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
1090 let meta_map_2 = RainMetaDocumentV1Item {
1091 payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
1092 magic: KnownMagic::DotrainV1,
1093 content_type: ContentType::OctetStream,
1094 content_encoding,
1095 content_language: ContentLanguage::En,
1096 schema: None,
1097 };
1098
1099 let cbor_encoded = RainMetaDocumentV1Item::cbor_encode_seq(
1101 &vec![meta_map_1.clone(), meta_map_2.clone()],
1102 KnownMagic::RainMetaDocumentV1,
1103 )?;
1104
1105 assert_eq!(
1107 &cbor_encoded[0..8],
1108 KnownMagic::RainMetaDocumentV1.to_prefix_bytes()
1109 );
1110
1111 assert_eq!(cbor_encoded[8], 0xa3);
1114 assert_eq!(cbor_encoded[9], 0x00);
1116 assert_eq!(cbor_encoded[10], 0b010_11001);
1118 assert_eq!(cbor_encoded[11], 0b000_00010);
1119 assert_eq!(cbor_encoded[12], 0b000_00000);
1120 assert_eq!(cbor_encoded[13..525], authoring_meta_abi_encoded);
1122 assert_eq!(cbor_encoded[525], 0x01);
1124 assert_eq!(cbor_encoded[526], 0b000_11011);
1126 assert_eq!(
1128 &cbor_encoded[527..535],
1129 KnownMagic::AuthoringMetaV1.to_prefix_bytes()
1130 );
1131 assert_eq!(cbor_encoded[535], 0x02);
1133 assert_eq!(cbor_encoded[536], 0b011_10000);
1135 assert_eq!(&cbor_encoded[537..553], "application/cbor".as_bytes());
1137
1138 assert_eq!(cbor_encoded[553], 0xa5);
1141 assert_eq!(cbor_encoded[554], 0x00);
1143 assert_eq!(cbor_encoded[555], 0b010_11000);
1145 assert_eq!(cbor_encoded[556], 0b001_00100);
1146 assert_eq!(cbor_encoded[557..593], deflated_payload);
1149 assert_eq!(cbor_encoded[593], 0x01);
1151 assert_eq!(cbor_encoded[594], 0b000_11011);
1153 assert_eq!(
1155 &cbor_encoded[595..603],
1156 KnownMagic::DotrainV1.to_prefix_bytes()
1157 );
1158 assert_eq!(cbor_encoded[603], 0x02);
1160 assert_eq!(cbor_encoded[604], 0b011_11000);
1162 assert_eq!(cbor_encoded[605], 0b000_11000);
1163 assert_eq!(
1165 &cbor_encoded[606..630],
1166 "application/octet-stream".as_bytes()
1167 );
1168 assert_eq!(cbor_encoded[630], 0x03);
1170 assert_eq!(cbor_encoded[631], 0b011_00111);
1172 assert_eq!(&cbor_encoded[632..639], "deflate".as_bytes());
1174 assert_eq!(cbor_encoded[639], 0x04);
1176 assert_eq!(cbor_encoded[640], 0b011_00010);
1178 assert_eq!(&cbor_encoded[641..], "en".as_bytes());
1180
1181 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1183 assert_eq!(cbor_decoded.len(), 2);
1185
1186 assert_eq!(cbor_decoded[0], meta_map_1);
1188 assert_eq!(cbor_decoded[1], meta_map_2);
1190
1191 Ok(())
1192 }
1193
1194 #[test]
1195 fn test_bytes32_to_str() {
1196 let text_bytes_list = vec![
1197 (
1198 "",
1199 hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1200 ),
1201 (
1202 "A",
1203 hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1204 ),
1205 (
1206 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1207 hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1208 ),
1209 (
1210 "!@#$%^&*(),./;'[]",
1211 hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1212 ),
1213 ];
1214
1215 for (text, bytes) in text_bytes_list {
1216 assert_eq!(text, bytes32_to_str(&bytes).unwrap());
1217 }
1218 }
1219
1220 #[test]
1221 fn test_str_to_bytes32() {
1222 let text_bytes_list = vec![
1223 (
1224 "",
1225 hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1226 ),
1227 (
1228 "A",
1229 hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1230 ),
1231 (
1232 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1233 hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1234 ),
1235 (
1236 "!@#$%^&*(),./;'[]",
1237 hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1238 ),
1239 ];
1240
1241 for (text, bytes) in text_bytes_list {
1242 assert_eq!(bytes, str_to_bytes32(text).unwrap());
1243 }
1244 }
1245
1246 #[test]
1247 fn test_str_to_bytes32_long() {
1248 assert!(matches!(
1249 str_to_bytes32("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456").unwrap_err(),
1250 Error::BiggerThan32Bytes
1251 ));
1252 }
1253
1254 #[test]
1258 fn test_str_to_bytes32_rejects_nul() {
1259 for text in [
1260 "\0",
1261 "\0a",
1262 "a\0",
1263 "a\0b",
1264 "abcdefghijklmnopqrstuvwxyz01234\0",
1265 ] {
1266 assert!(
1267 matches!(str_to_bytes32(text), Err(Error::NulByteInInput)),
1268 "nul bearing input {:?} accepted",
1269 text
1270 );
1271 }
1272 }
1273
1274 #[test]
1277 fn test_str_to_bytes32_round_trip() -> Result<(), Error> {
1278 let mut seen: Vec<[u8; 32]> = vec![];
1279 for text in [
1280 "",
1281 "a",
1282 "stack",
1283 "!@#$%^&*(),./;'[]",
1284 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1285 ] {
1286 let bytes = str_to_bytes32(text)?;
1287 assert_eq!(bytes32_to_str(&bytes)?, text);
1288 assert!(!seen.contains(&bytes), "input {:?} collided", text);
1289 seen.push(bytes);
1290 }
1291 Ok(())
1292 }
1293
1294 #[tokio::test]
1295 async fn test_implements_i_describe_by_meta_v1() {
1296 async fn new_server_client() -> (Asserter, impl Provider) {
1298 let asserter = Asserter::new();
1299 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
1300
1301 asserter.push_success(
1303 &"0x0000000000000000000000000000000000000000000000000000000000000001",
1304 );
1305 asserter.push_success(
1306 &"0x0000000000000000000000000000000000000000000000000000000000000000",
1307 );
1308
1309 (asserter, provider)
1310 }
1311
1312 let address = Address::random();
1313
1314 let (asserter, provider) = new_server_client().await;
1316 asserter
1317 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
1318 let result = implements_i_described_by_meta_v1(&provider, address)
1319 .await
1320 .unwrap();
1321 assert!(result);
1322
1323 let (asserter, provider) = new_server_client().await;
1325 asserter
1326 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
1327 let result = implements_i_described_by_meta_v1(&provider, address)
1328 .await
1329 .unwrap();
1330 assert!(!result);
1331
1332 let (asserter, provider) = new_server_client().await;
1334 asserter.push_failure(ErrorPayload {
1335 code: -32003,
1336 message: "execution reverted".into(),
1337 data: Some(serde_json::value::to_raw_value(&json!("0x00")).unwrap()),
1338 });
1339 let result = implements_i_described_by_meta_v1(&provider, address)
1340 .await
1341 .unwrap();
1342 assert!(!result);
1343 }
1344
1345 #[test]
1349 fn oa_schema_map_key_roundtrip() -> Result<(), Error> {
1350 let payload = vec![0x01, 0x02, 0x03];
1351 let schema = "QmSchemaHash1234567890abcdefghijklmnopqrstuvwx".to_string();
1354 assert_eq!(schema.len(), 46);
1355
1356 let meta_map = RainMetaDocumentV1Item {
1357 payload: serde_bytes::ByteBuf::from(payload.clone()),
1358 magic: KnownMagic::OaStructure,
1359 content_type: ContentType::Json,
1360 content_encoding: ContentEncoding::Deflate,
1361 content_language: ContentLanguage::None,
1362 schema: Some(schema.clone()),
1363 };
1364 let cbor_encoded = meta_map.cbor_encode()?;
1365
1366 assert_eq!(cbor_encoded[0], 0xa5);
1368 assert_eq!(cbor_encoded[1], 0x00);
1370 assert_eq!(cbor_encoded[2], 0b010_00011);
1372 assert_eq!(cbor_encoded[3..6], payload);
1374 assert_eq!(cbor_encoded[6], 0x01);
1376 assert_eq!(cbor_encoded[7], 0b000_11011);
1378 assert_eq!(
1380 &cbor_encoded[8..16],
1381 KnownMagic::OaStructure.to_prefix_bytes()
1382 );
1383 assert_eq!(cbor_encoded[16], 0x02);
1385 assert_eq!(cbor_encoded[17], 0b011_10000);
1387 assert_eq!(&cbor_encoded[18..34], "application/json".as_bytes());
1388 assert_eq!(cbor_encoded[34], 0x03);
1390 assert_eq!(cbor_encoded[35], 0b011_00111);
1392 assert_eq!(&cbor_encoded[36..43], "deflate".as_bytes());
1393 assert_eq!(cbor_encoded[43], 0b000_11011);
1395 assert_eq!(
1396 &cbor_encoded[44..52],
1397 KnownMagic::OaSchema.to_prefix_bytes()
1398 );
1399 assert_eq!(cbor_encoded[52], 0b011_11000);
1401 assert_eq!(cbor_encoded[53], 46);
1402 assert_eq!(&cbor_encoded[54..], schema.as_bytes());
1404
1405 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&cbor_encoded))?;
1407 assert_eq!(cbor_decoded.len(), 1);
1409 assert_eq!(cbor_decoded[0], meta_map);
1411
1412 Ok(())
1413 }
1414
1415 #[test]
1418 fn no_schema_key_encodes_as_before_roundtrip() -> Result<(), Error> {
1419 let payload = vec![0x0a, 0x0b];
1420 let meta_map = RainMetaDocumentV1Item {
1421 payload: serde_bytes::ByteBuf::from(payload.clone()),
1422 magic: KnownMagic::OaStructure,
1423 content_type: ContentType::None,
1424 content_encoding: ContentEncoding::None,
1425 content_language: ContentLanguage::None,
1426 schema: None,
1427 };
1428 let cbor_encoded = meta_map.cbor_encode()?;
1429
1430 assert_eq!(cbor_encoded[0], 0xa2);
1432 assert_eq!(cbor_encoded[1], 0x00);
1434 assert_eq!(cbor_encoded[2], 0b010_00010);
1436 assert_eq!(cbor_encoded[3..5], payload);
1438 assert_eq!(cbor_encoded[5], 0x01);
1440 assert_eq!(cbor_encoded[6], 0b000_11011);
1442 assert_eq!(
1444 &cbor_encoded[7..],
1445 KnownMagic::OaStructure.to_prefix_bytes()
1446 );
1447
1448 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&cbor_encoded))?;
1449 assert_eq!(cbor_decoded.len(), 1);
1450 assert_eq!(cbor_decoded[0], meta_map);
1451
1452 Ok(())
1453 }
1454
1455 #[test]
1458 fn unknown_map_key_index_is_ignored() -> Result<(), Error> {
1459 let mut bytes: Vec<u8> = vec![
1460 0xa3, 0x00, 0x40, 0x01, 0x1b,
1464 ];
1465 bytes.extend_from_slice(&KnownMagic::DotrainSourceV1.to_prefix_bytes());
1466 bytes.extend_from_slice(&[0x05, 0x07]);
1468
1469 let decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes))?;
1470 assert_eq!(decoded.len(), 1);
1471 assert_eq!(decoded[0], plain_item(KnownMagic::DotrainSourceV1, vec![]));
1472
1473 Ok(())
1474 }
1475
1476 #[test]
1479 fn non_oa_schema_extra_map_key_is_ignored() -> Result<(), Error> {
1480 let mut bytes: Vec<u8> = vec![
1483 0xa3, 0x00, 0x41, 0xff, 0x01, 0x1b,
1487 ];
1488 bytes.extend_from_slice(&KnownMagic::OaStructure.to_prefix_bytes());
1489 bytes.push(0x1b);
1491 bytes.extend_from_slice(&KnownMagic::OaHashList.to_prefix_bytes());
1492 bytes.extend_from_slice(&[0x62, 0x68, 0x69]);
1494
1495 let decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes))?;
1496 assert_eq!(decoded.len(), 1);
1497 let expected = plain_item(KnownMagic::OaStructure, vec![0xff]);
1498 assert_eq!(decoded[0], expected);
1499 assert_eq!(decoded[0].schema, None);
1500
1501 Ok(())
1502 }
1503
1504 #[test]
1507 fn unknown_map_key_consumes_its_whole_value() -> Result<(), Error> {
1508 let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1509 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1510 bytes.extend_from_slice(&[0x05, 0xa1, 0x18, 0x2a, 0x82, 0x01, 0x02]);
1512 bytes.extend_from_slice(&handwritten_map());
1513
1514 let decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes))?;
1515 assert_eq!(decoded.len(), 2);
1516 let expected = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1517 assert_eq!(decoded[0], expected);
1518 assert_eq!(decoded[1], expected);
1519
1520 Ok(())
1521 }
1522
1523 #[test]
1526 fn ignored_map_key_is_absent_from_the_reencoding() -> Result<(), Error> {
1527 let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1528 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1529 bytes.extend_from_slice(&[0x05, 0x07]);
1530
1531 let decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes))?;
1532 assert_eq!(decoded[0].cbor_encode()?, handwritten_map());
1533 assert_eq!(decoded[0].hash(false)?, keccak256(handwritten_map()).0);
1534 assert_ne!(decoded[0].hash(false)?, keccak256(&bytes).0);
1535
1536 Ok(())
1537 }
1538
1539 #[test]
1543 fn non_integer_map_key_errors() {
1544 let mut text_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1545 text_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1546 text_key.extend_from_slice(&[0x61, 0x35, 0x07]);
1548 assert!(matches!(
1549 RainMetaDocumentV1Item::cbor_decode(&with_prefix(&text_key)),
1550 Err(Error::SerdeCborError(_))
1551 ));
1552
1553 let mut negative_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1554 negative_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1555 negative_key.extend_from_slice(&[0x20, 0x07]);
1557 assert!(matches!(
1558 RainMetaDocumentV1Item::cbor_decode(&with_prefix(&negative_key)),
1559 Err(Error::SerdeCborError(_))
1560 ));
1561 }
1562
1563 #[test]
1567 fn unknown_map_key_does_not_stand_in_for_a_mandatory_key() {
1568 let mut bytes: Vec<u8> = vec![0xa2, 0x05, 0x07, 0x01, 0x1b];
1569 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1570 assert!(RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)).is_err());
1571 }
1572
1573 fn with_prefix(bytes: &[u8]) -> Vec<u8> {
1577 let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1578 document.extend_from_slice(bytes);
1579 document
1580 }
1581
1582 fn plain_item(magic: KnownMagic, payload: Vec<u8>) -> RainMetaDocumentV1Item {
1583 RainMetaDocumentV1Item {
1584 payload: serde_bytes::ByteBuf::from(payload),
1585 magic,
1586 content_type: ContentType::None,
1587 content_encoding: ContentEncoding::None,
1588 content_language: ContentLanguage::None,
1589 schema: None,
1590 }
1591 }
1592
1593 fn sample_authoring_doc() -> (AuthoringMeta, Vec<u8>) {
1596 let authoring_meta: AuthoringMeta = serde_json::from_str(
1597 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
1598 )
1599 .unwrap();
1600 let abi = authoring_meta.abi_encode_validate().unwrap();
1601 let item = RainMetaDocumentV1Item {
1602 payload: serde_bytes::ByteBuf::from(abi),
1603 magic: KnownMagic::AuthoringMetaV1,
1604 content_type: ContentType::Cbor,
1605 content_encoding: ContentEncoding::None,
1606 content_language: ContentLanguage::None,
1607 schema: None,
1608 };
1609 let doc =
1610 RainMetaDocumentV1Item::cbor_encode_seq(&vec![item], KnownMagic::RainMetaDocumentV1)
1611 .unwrap();
1612 (authoring_meta, doc)
1613 }
1614
1615 fn sample_dotrain_item() -> RainMetaDocumentV1Item {
1616 RainMetaDocumentV1Item {
1617 payload: serde_bytes::ByteBuf::from("some dotrain body".as_bytes()),
1618 magic: KnownMagic::DotrainV1,
1619 content_type: ContentType::OctetStream,
1620 content_encoding: ContentEncoding::None,
1621 content_language: ContentLanguage::None,
1622 schema: None,
1623 }
1624 }
1625
1626 fn handwritten_map() -> Vec<u8> {
1629 vec![
1630 0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b, 0xff, 0xda, 0xc2, 0xf2, 0xf3, 0x7b, 0xe8, 0x94, ]
1636 }
1637
1638 #[test]
1642 fn test_hash_bare_vs_document() -> Result<(), Error> {
1643 let map_bytes = handwritten_map();
1644 let mut doc_bytes: Vec<u8> = vec![0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74];
1645 doc_bytes.extend_from_slice(&map_bytes);
1646
1647 let item = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1648 assert_eq!(item.hash(false)?, keccak256(&map_bytes).0);
1649 assert_eq!(item.hash(true)?, keccak256(&doc_bytes).0);
1650 assert_ne!(item.hash(false)?, item.hash(true)?);
1651 Ok(())
1652 }
1653
1654 #[test]
1656 fn test_cbor_decode_empty_is_corrupt() {
1657 assert!(matches!(
1659 RainMetaDocumentV1Item::cbor_decode(&[]),
1660 Err(Error::NotRainMetaDocument)
1661 ));
1662 let prefix = KnownMagic::RainMetaDocumentV1.to_prefix_bytes();
1664 assert!(matches!(
1665 RainMetaDocumentV1Item::cbor_decode(&prefix),
1666 Err(Error::CorruptMeta)
1667 ));
1668 }
1669
1670 #[test]
1676 fn test_cbor_decode_requires_the_document_prefix() {
1677 let bare = plain_item(KnownMagic::DotrainV1, vec![0x42])
1678 .cbor_encode()
1679 .unwrap();
1680 assert!(matches!(
1681 RainMetaDocumentV1Item::cbor_decode(&bare),
1682 Err(Error::NotRainMetaDocument)
1683 ));
1684 let items = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bare)).unwrap();
1687 assert_eq!(items.len(), 1);
1688 assert_eq!(items[0].payload.as_ref(), &[0x42]);
1689 }
1690
1691 #[test]
1694 fn test_cbor_decode_rejects_another_magic_as_the_prefix() {
1695 let bare = plain_item(KnownMagic::DotrainV1, vec![0x42])
1696 .cbor_encode()
1697 .unwrap();
1698 let mut document = KnownMagic::DotrainV1.to_prefix_bytes().to_vec();
1699 document.extend_from_slice(&bare);
1700 assert!(matches!(
1701 RainMetaDocumentV1Item::cbor_decode(&document),
1702 Err(Error::NotRainMetaDocument)
1703 ));
1704 }
1705
1706 #[test]
1709 fn test_cbor_decode_rejects_data_shorter_than_the_prefix() {
1710 let prefix = KnownMagic::RainMetaDocumentV1.to_prefix_bytes();
1711 for cut in 0..prefix.len() {
1712 assert!(
1713 matches!(
1714 RainMetaDocumentV1Item::cbor_decode(&prefix[..cut]),
1715 Err(Error::NotRainMetaDocument)
1716 ),
1717 "prefix cut to {} bytes",
1718 cut
1719 );
1720 }
1721 }
1722
1723 #[test]
1726 fn test_cbor_decode_trailing_truncated_is_corrupt() {
1727 let mut bytes = handwritten_map();
1728 bytes.push(0x1b); assert!(matches!(
1730 RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)),
1731 Err(Error::CorruptMeta)
1732 ));
1733 }
1734
1735 #[test]
1739 fn test_cbor_decode_truncated_item_is_corrupt() {
1740 let mut sole = handwritten_map();
1741 sole.pop();
1742 assert!(matches!(
1743 RainMetaDocumentV1Item::cbor_decode(&with_prefix(&sole)),
1744 Err(Error::CorruptMeta)
1745 ));
1746
1747 assert!(matches!(
1748 RainMetaDocumentV1Item::cbor_decode(&with_prefix(&[0xa2, 0x00, 0x41, 0x01])),
1749 Err(Error::CorruptMeta)
1750 ));
1751
1752 let mut after_complete = handwritten_map();
1753 after_complete.extend_from_slice(&[0xa2, 0x00]);
1754 assert!(matches!(
1755 RainMetaDocumentV1Item::cbor_decode(&with_prefix(&after_complete)),
1756 Err(Error::CorruptMeta)
1757 ));
1758 }
1759
1760 #[test]
1763 fn test_cbor_decode_trailing_garbage_errors() {
1764 let mut bytes = handwritten_map();
1765 bytes.push(0xff); assert!(matches!(
1767 RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)),
1768 Err(Error::SerdeCborError(_))
1769 ));
1770 }
1771
1772 #[test]
1777 fn test_cbor_decode_missing_payload_is_corrupt() {
1778 let mut bytes: Vec<u8> = vec![0xa1, 0x01, 0x1b]; bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1780 assert!(RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)).is_err());
1781
1782 let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1784 .cbor_encode()
1785 .unwrap();
1786 let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1787 document.extend_from_slice(&bytes);
1788 document.extend_from_slice(&good);
1789 assert!(RainMetaDocumentV1Item::cbor_decode(&document).is_err());
1790 }
1791
1792 #[test]
1794 fn test_cbor_decode_missing_magic_is_corrupt() {
1795 let bytes: Vec<u8> = vec![0xa1, 0x00, 0x41, 0x01]; assert!(RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)).is_err());
1797
1798 let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1799 .cbor_encode()
1800 .unwrap();
1801 let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1802 document.extend_from_slice(&bytes);
1803 document.extend_from_slice(&good);
1804 assert!(RainMetaDocumentV1Item::cbor_decode(&document).is_err());
1805 }
1806
1807 #[test]
1812 fn test_cbor_decode_unknown_magic_is_dropped() {
1813 let mut bytes: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1814 bytes.extend_from_slice(&0xdeadbeefdeadbeefu64.to_be_bytes());
1815 assert!(matches!(
1816 RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)),
1817 Err(Error::CorruptMeta)
1818 ));
1819 }
1820
1821 #[test]
1825 fn test_cbor_decode_drops_only_the_unknown_magic_item() {
1826 let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1827 .cbor_encode()
1828 .unwrap();
1829
1830 let mut unknown_magic: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1831 unknown_magic.extend_from_slice(&0xdeadbeefdeadbeefu64.to_be_bytes());
1832
1833 let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1834 document.extend_from_slice(&unknown_magic);
1835 document.extend_from_slice(&good);
1836
1837 let items = RainMetaDocumentV1Item::cbor_decode(&document).unwrap();
1838 assert_eq!(items.len(), 1, "expected only the good item");
1839 assert_eq!(items[0].magic, KnownMagic::DotrainV1);
1840 assert_eq!(items[0].payload.as_ref(), &[0x42]);
1841 }
1842
1843 #[test]
1848 fn test_cbor_decode_nested_document_magic_is_not_dropped() {
1849 let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1850 .cbor_encode()
1851 .unwrap();
1852
1853 let mut nested: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1854 nested.extend_from_slice(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes());
1855
1856 let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1857 document.extend_from_slice(&nested);
1858 document.extend_from_slice(&good);
1859
1860 assert!(matches!(
1861 RainMetaDocumentV1Item::cbor_decode(&document),
1862 Err(Error::SerdeCborError(_))
1863 ));
1864 }
1865
1866 fn handwritten_entries(entries: &[(Vec<u8>, Vec<u8>)]) -> Vec<u8> {
1870 assert!(entries.len() < 24);
1871 let mut bytes = vec![0xa0 | entries.len() as u8];
1872 for (key, value) in entries {
1873 bytes.extend_from_slice(key);
1874 bytes.extend_from_slice(value);
1875 }
1876 bytes
1877 }
1878
1879 fn handwritten_magic(magic: KnownMagic) -> Vec<u8> {
1881 let mut bytes = vec![0x1b];
1882 bytes.extend_from_slice(&magic.to_prefix_bytes());
1883 bytes
1884 }
1885
1886 fn handwritten_text(text: &str) -> Vec<u8> {
1888 assert!(text.len() < 24);
1889 let mut bytes = vec![0x60 | text.len() as u8];
1890 bytes.extend_from_slice(text.as_bytes());
1891 bytes
1892 }
1893
1894 #[test]
1897 fn test_cbor_decode_duplicate_payload_key_errors() {
1898 let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x00, 0x41, 0x02, 0x01, 0x1b];
1899 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1900 let error = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)).unwrap_err();
1901 assert!(matches!(error, Error::SerdeCborError(_)));
1902 assert!(
1903 error.to_string().contains("duplicate field `payload`"),
1904 "{error}"
1905 );
1906 }
1907
1908 #[test]
1911 fn test_cbor_decode_duplicate_any_key_errors() -> Result<(), Error> {
1912 let cases: [(Vec<u8>, Vec<u8>, Vec<u8>); 6] = [
1913 (vec![0x00], vec![0x41, 0x01], vec![0x41, 0x02]),
1914 (
1915 vec![0x01],
1916 handwritten_magic(KnownMagic::DotrainV1),
1917 handwritten_magic(KnownMagic::RainlangV1),
1918 ),
1919 (
1920 vec![0x02],
1921 handwritten_text("application/cbor"),
1922 handwritten_text("application/json"),
1923 ),
1924 (
1925 vec![0x03],
1926 handwritten_text("identity"),
1927 handwritten_text("deflate"),
1928 ),
1929 (vec![0x04], handwritten_text("en"), handwritten_text("none")),
1930 (
1931 handwritten_magic(KnownMagic::OaSchema),
1932 handwritten_text("hi"),
1933 handwritten_text("bye"),
1934 ),
1935 ];
1936 let base: Vec<(Vec<u8>, Vec<u8>)> = cases
1937 .iter()
1938 .map(|(key, value, _)| (key.clone(), value.clone()))
1939 .collect();
1940
1941 let decoded =
1942 RainMetaDocumentV1Item::cbor_decode(&with_prefix(&handwritten_entries(&base)))?;
1943 assert_eq!(decoded.len(), 1);
1944 assert_eq!(decoded[0].payload.as_ref(), &[0x01]);
1945 assert_eq!(decoded[0].magic, KnownMagic::DotrainV1);
1946 assert_eq!(decoded[0].content_type, ContentType::Cbor);
1947 assert_eq!(decoded[0].content_encoding, ContentEncoding::Identity);
1948 assert_eq!(decoded[0].content_language, ContentLanguage::En);
1949 assert_eq!(decoded[0].schema.as_deref(), Some("hi"));
1950
1951 for (key, value, other_value) in &cases {
1952 for repeated in [value, other_value] {
1953 let mut entries = base.clone();
1954 entries.push((key.clone(), repeated.clone()));
1955 let error = RainMetaDocumentV1Item::cbor_decode(&with_prefix(
1956 &handwritten_entries(&entries),
1957 ))
1958 .unwrap_err();
1959 assert!(
1960 matches!(error, Error::SerdeCborError(_)),
1961 "{key:?} {error:?}"
1962 );
1963 assert!(
1964 error.to_string().contains("duplicate field"),
1965 "{key:?} {error}"
1966 );
1967 }
1968 }
1969 Ok(())
1970 }
1971
1972 #[test]
1977 fn test_cbor_decode_duplicate_unknown_key_errors() -> Result<(), Error> {
1978 let base: Vec<(Vec<u8>, Vec<u8>)> = vec![
1979 (vec![0x00], vec![0x41, 0x01]),
1980 (vec![0x01], handwritten_magic(KnownMagic::DotrainV1)),
1981 ];
1982 let unknown: [(Vec<u8>, Vec<u8>, Vec<u8>); 2] = [
1985 (vec![0x05], vec![0x07], vec![0x08]),
1986 (
1987 handwritten_magic(KnownMagic::OaHashList),
1988 handwritten_text("hi"),
1989 handwritten_text("bye"),
1990 ),
1991 ];
1992
1993 for (key, value, other_value) in &unknown {
1994 let mut entries = base.clone();
1995 entries.push((key.clone(), value.clone()));
1996 let decoded =
1997 RainMetaDocumentV1Item::cbor_decode(&with_prefix(&handwritten_entries(&entries)))?;
1998 assert_eq!(decoded, vec![plain_item(KnownMagic::DotrainV1, vec![0x01])]);
1999
2000 for repeated in [value, other_value] {
2001 let mut duplicated = entries.clone();
2002 duplicated.push((key.clone(), repeated.clone()));
2003 let error = RainMetaDocumentV1Item::cbor_decode(&with_prefix(
2004 &handwritten_entries(&duplicated),
2005 ))
2006 .unwrap_err();
2007 assert!(
2008 matches!(error, Error::SerdeCborError(_)),
2009 "{key:?} {error:?}"
2010 );
2011 assert!(
2012 error.to_string().contains("duplicate map key"),
2013 "{key:?} {error}"
2014 );
2015 }
2016 }
2017 Ok(())
2018 }
2019
2020 #[test]
2024 fn test_cbor_decode_handwritten_document_magic_item() {
2025 let bytes: Vec<u8> = vec![
2026 0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b, 0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74, ];
2032 assert!(RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)).is_err());
2036 }
2037
2038 #[test]
2044 fn test_document_magic_item_carries_a_nested_document() -> Result<(), Error> {
2045 let inner = plain_item(KnownMagic::DotrainV1, vec![0x01]);
2046 let inner_doc = RainMetaDocumentV1Item::cbor_encode_seq(
2047 &vec![inner.clone()],
2048 KnownMagic::RainMetaDocumentV1,
2049 )?;
2050 let outer = plain_item(KnownMagic::RainMetaDocumentV1, inner_doc.clone());
2051 let outer_doc = RainMetaDocumentV1Item::cbor_encode_seq(
2052 &vec![outer.clone()],
2053 KnownMagic::RainMetaDocumentV1,
2054 )?;
2055
2056 assert!(RainMetaDocumentV1Item::cbor_decode(&outer_doc).is_err());
2062 assert_eq!(
2063 RainMetaDocumentV1Item::cbor_decode(&inner_doc)?,
2064 vec![inner]
2065 );
2066 Ok(())
2067 }
2068
2069 #[test]
2072 fn test_document_magic_item_is_not_unpackable() {
2073 assert!(matches!(
2074 KnownMeta::try_from(KnownMagic::RainMetaDocumentV1),
2075 Err(Error::UnsupportedMeta)
2076 ));
2077 assert!(matches!(
2078 plain_item(KnownMagic::RainMetaDocumentV1, vec![0x01]).unpack_into::<Vec<u8>>(),
2079 Err(Error::UnsupportedMeta)
2080 ));
2081 }
2082
2083 #[test]
2085 fn test_unpack_decodes_content_encoding() -> Result<(), Error> {
2086 let content = b"unpack me via deflate".to_vec();
2087 let packed = ContentEncoding::Deflate.encode(&content);
2088 assert_ne!(packed, content);
2089 let mut item = plain_item(KnownMagic::DotrainV1, packed);
2090 item.content_encoding = ContentEncoding::Deflate;
2091 assert_eq!(item.unpack()?, content);
2092
2093 let item = plain_item(KnownMagic::DotrainV1, content.clone());
2094 assert_eq!(item.unpack()?, content);
2095 Ok(())
2096 }
2097
2098 #[test]
2101 fn test_unpack_into_whitelist() {
2102 use strum::IntoEnumIterator;
2103 let supported = [
2104 KnownMagic::OpMetaV1,
2105 KnownMagic::DotrainV1,
2106 KnownMagic::RainlangV1,
2107 KnownMagic::SolidityAbiV2,
2108 KnownMagic::AuthoringMetaV1,
2109 KnownMagic::AuthoringMetaV2,
2110 KnownMagic::AddressList,
2111 KnownMagic::InterpreterCallerMetaV1,
2112 KnownMagic::ExpressionDeployerV2BytecodeV1,
2113 KnownMagic::DotrainSourceV1,
2114 KnownMagic::OrderBuilderStateV1,
2115 KnownMagic::RainlangSourceV1,
2116 KnownMagic::RaindexSignedContextOracleV1,
2117 ];
2118 for magic in supported {
2119 let unpacked: Vec<u8> = plain_item(magic, vec![0x61]).unpack_into().unwrap();
2120 assert_eq!(unpacked, vec![0x61], "{:?}", magic);
2121 }
2122 let unsupported = [
2123 KnownMagic::RainMetaDocumentV1,
2124 KnownMagic::WebDataV1,
2125 KnownMagic::OaSchema,
2126 KnownMagic::OaHashList,
2127 KnownMagic::OaStructure,
2128 KnownMagic::OaTokenImage,
2129 KnownMagic::OaTokenCredentialLinks,
2130 ];
2131 for magic in unsupported {
2132 let result: Result<Vec<u8>, Error> = plain_item(magic, vec![0x61]).unpack_into();
2133 assert!(matches!(result, Err(Error::UnsupportedMeta)), "{:?}", magic);
2134 }
2135 assert_eq!(
2137 supported.len() + unsupported.len(),
2138 KnownMagic::iter().count()
2139 );
2140 }
2141
2142 #[test]
2145 fn test_try_into_string_invalid_utf8_errors() {
2146 let item = plain_item(KnownMagic::DotrainV1, vec![0xff, 0xfe]);
2147 let result: Result<String, Error> = item.try_into();
2148 assert!(matches!(result, Err(Error::FromUtf8Error(_))));
2149 }
2150
2151 #[test]
2153 fn test_try_into_vec_decodes_encoding() -> Result<(), Error> {
2154 let content = b"raw bytecode bytes \x00\x01\x02".to_vec();
2155 let packed = ContentEncoding::Deflate.encode(&content);
2156 let mut item = plain_item(KnownMagic::ExpressionDeployerV2BytecodeV1, packed.clone());
2157 item.content_encoding = ContentEncoding::Deflate;
2158 let unpacked: Vec<u8> = item.try_into()?;
2159 assert_eq!(unpacked, content);
2160 assert_ne!(unpacked, packed);
2161 Ok(())
2162 }
2163
2164 #[test]
2167 fn test_content_encoding_deflate_roundtrip() -> Result<(), Error> {
2168 let content = b"hello rain deflate fixture hello rain deflate fixture".to_vec();
2169 let encoded = ContentEncoding::Deflate.encode(&content);
2170 assert_ne!(encoded, content);
2171 assert_eq!(encoded[0], 0x78);
2172 assert_eq!(ContentEncoding::Deflate.decode(&encoded)?, content);
2173 Ok(())
2174 }
2175
2176 #[test]
2178 fn test_content_encoding_passthrough() -> Result<(), Error> {
2179 let data = vec![0x00, 0xff, 0x10];
2180 for encoding in [ContentEncoding::None, ContentEncoding::Identity] {
2181 assert_eq!(encoding.encode(&data), data, "{:?}", encoding);
2182 assert_eq!(encoding.decode(&data)?, data, "{:?}", encoding);
2183 }
2184 Ok(())
2185 }
2186
2187 #[test]
2190 fn test_content_encoding_decode_fixtures() -> Result<(), Error> {
2191 let content = b"hello rain deflate fixture".to_vec();
2192 let zlib: Vec<u8> = vec![
2193 120, 156, 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44,
2194 73, 85, 72, 203, 172, 40, 41, 45, 74, 5, 0, 132, 64, 9, 251,
2195 ];
2196 let raw: Vec<u8> = vec![
2197 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44, 73, 85, 72,
2198 203, 172, 40, 41, 45, 74, 5, 0,
2199 ];
2200 assert_eq!(ContentEncoding::Deflate.decode(&zlib)?, content);
2201 assert_eq!(ContentEncoding::Deflate.decode(&raw)?, content);
2202 Ok(())
2203 }
2204
2205 #[test]
2208 fn test_content_encoding_decode_garbage_errors() {
2209 let garbage = [0xffu8, 0xff, 0xff, 0xff];
2210 assert!(matches!(
2211 ContentEncoding::Deflate.decode(&garbage),
2212 Err(Error::InflateError(_))
2213 ));
2214 }
2215
2216 #[test]
2218 fn test_content_headers_strum_names() {
2219 use std::str::FromStr;
2220 assert_eq!(
2221 ContentEncoding::from_str("deflate").unwrap(),
2222 ContentEncoding::Deflate
2223 );
2224 assert_eq!(
2225 ContentEncoding::from_str("identity").unwrap(),
2226 ContentEncoding::Identity
2227 );
2228 assert_eq!(
2229 ContentEncoding::from_str("none").unwrap(),
2230 ContentEncoding::None
2231 );
2232 assert_eq!(ContentEncoding::Deflate.to_string(), "deflate");
2233 assert_eq!(
2234 ContentType::from_str("octet-stream").unwrap(),
2235 ContentType::OctetStream
2236 );
2237 assert_eq!(ContentType::from_str("json").unwrap(), ContentType::Json);
2238 assert_eq!(ContentType::Json.to_string(), "json");
2239 assert_eq!(
2240 ContentLanguage::from_str("en").unwrap(),
2241 ContentLanguage::En
2242 );
2243 }
2244
2245 #[test]
2248 fn test_known_meta_try_from_magic() {
2249 let cases: [(KnownMagic, KnownMeta); 13] = [
2250 (KnownMagic::OpMetaV1, KnownMeta::OpV1),
2251 (KnownMagic::DotrainV1, KnownMeta::DotrainV1),
2252 (KnownMagic::RainlangV1, KnownMeta::RainlangV1),
2253 (KnownMagic::SolidityAbiV2, KnownMeta::SolidityAbiV2),
2254 (KnownMagic::AuthoringMetaV1, KnownMeta::AuthoringMetaV1),
2255 (KnownMagic::AuthoringMetaV2, KnownMeta::AuthoringMetaV2),
2256 (KnownMagic::AddressList, KnownMeta::AddressList),
2257 (
2258 KnownMagic::InterpreterCallerMetaV1,
2259 KnownMeta::InterpreterCallerMetaV1,
2260 ),
2261 (
2262 KnownMagic::ExpressionDeployerV2BytecodeV1,
2263 KnownMeta::ExpressionDeployerV2BytecodeV1,
2264 ),
2265 (KnownMagic::RainlangSourceV1, KnownMeta::RainlangSourceV1),
2266 (KnownMagic::DotrainSourceV1, KnownMeta::DotrainSourceV1),
2267 (
2268 KnownMagic::OrderBuilderStateV1,
2269 KnownMeta::OrderBuilderStateV1,
2270 ),
2271 (
2272 KnownMagic::RaindexSignedContextOracleV1,
2273 KnownMeta::RaindexSignedContextOracleV1,
2274 ),
2275 ];
2276 for (magic, meta) in cases {
2277 assert_eq!(KnownMeta::try_from(magic).unwrap(), meta, "{:?}", magic);
2278 }
2279 for magic in [
2280 KnownMagic::RainMetaDocumentV1,
2281 KnownMagic::WebDataV1,
2282 KnownMagic::OaSchema,
2283 KnownMagic::OaHashList,
2284 KnownMagic::OaStructure,
2285 KnownMagic::OaTokenImage,
2286 KnownMagic::OaTokenCredentialLinks,
2287 ] {
2288 assert!(
2289 matches!(KnownMeta::try_from(magic), Err(Error::UnsupportedMeta)),
2290 "{:?}",
2291 magic
2292 );
2293 }
2294 }
2295
2296 #[test]
2299 fn test_known_meta_strum_parse_display() {
2300 use std::str::FromStr;
2301 assert_eq!(
2302 KnownMeta::from_str("authoring-meta-v1").unwrap(),
2303 KnownMeta::AuthoringMetaV1
2304 );
2305 assert_eq!(
2306 KnownMeta::from_str("authoring-meta-v2").unwrap(),
2307 KnownMeta::AuthoringMetaV2
2308 );
2309 assert_eq!(KnownMeta::AuthoringMetaV1.to_string(), "authoring-meta-v1");
2310 }
2311
2312 #[tokio::test]
2314 async fn test_search_lowercases_hash() {
2315 use httpmock::prelude::*;
2316 let (_, doc) = sample_authoring_doc();
2317 let hash_upper = format!("0x{}", "AB".repeat(32));
2318 let server = MockServer::start();
2319 let mock = server.mock(|when, then| {
2320 when.method(POST)
2321 .body_contains(hash_upper.to_ascii_lowercase());
2322 then.status(200).json_body(json!({
2323 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2324 }));
2325 });
2326 let response = search(&hash_upper, &vec![server.url("/sg")]).await.unwrap();
2327 assert_eq!(response.bytes, doc);
2328 mock.assert();
2329 }
2330
2331 #[tokio::test]
2334 async fn test_search_first_success_wins() {
2335 use httpmock::prelude::*;
2336 let (_, doc) = sample_authoring_doc();
2337 let bad = MockServer::start();
2338 let _bad_mock = bad.mock(|when, then| {
2339 when.method(POST);
2340 then.status(500).body("subgraph down");
2341 });
2342 let good = MockServer::start();
2343 let _good_mock = good.mock(|when, then| {
2344 when.method(POST);
2345 then.status(200).json_body(json!({
2346 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2347 }));
2348 });
2349 let response = search(
2350 &format!("0x{}", "11".repeat(32)),
2351 &vec![bad.url("/sg"), good.url("/sg")],
2352 )
2353 .await
2354 .unwrap();
2355 assert_eq!(response.bytes, doc);
2356 }
2357
2358 #[tokio::test]
2362 async fn test_search_empty_subgraphs_is_a_miss() {
2363 let hash = format!("0x{}", "33".repeat(32));
2364 assert!(matches!(
2365 search(&hash, &vec![]).await,
2366 Err(Error::NoRecordFound)
2367 ));
2368 }
2369
2370 #[tokio::test]
2381 async fn test_implements_erc165_gate_short_circuits() {
2382 let address = Address::random();
2383
2384 let asserter = Asserter::new();
2386 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2387 asserter
2388 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2389 asserter
2390 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2391 assert!(!implements_i_described_by_meta_v1(&provider, address)
2392 .await
2393 .unwrap());
2394
2395 let asserter = Asserter::new();
2399 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2400 asserter.push_failure(ErrorPayload {
2401 code: -32000,
2402 message: "connection reset".into(),
2403 data: None,
2404 });
2405 asserter
2406 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2407 assert!(implements_i_described_by_meta_v1(&provider, address)
2408 .await
2409 .is_err());
2410 }
2411
2412 #[tokio::test]
2415 async fn test_implements_empty_response_is_false() {
2416 let address = Address::random();
2417 let asserter = Asserter::new();
2418 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2419 asserter
2420 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2421 asserter
2422 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2423 asserter.push_success(&"0x");
2424 assert!(!implements_i_described_by_meta_v1(&provider, address)
2425 .await
2426 .unwrap());
2427 }
2428
2429 #[tokio::test]
2433 async fn test_implements_described_by_call_error_is_unknown() {
2434 let address = Address::random();
2435 let asserter = Asserter::new();
2436 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2437 asserter
2438 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2439 asserter
2440 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2441 asserter.push_failure(ErrorPayload {
2442 code: -32005,
2443 message: "rate limit exceeded".into(),
2444 data: None,
2445 });
2446 let error = implements_i_described_by_meta_v1(&provider, address)
2447 .await
2448 .unwrap_err();
2449 assert!(error.to_string().contains("rate limit exceeded"));
2450 }
2451
2452 #[tokio::test]
2456 async fn test_implements_undecodable_response_is_unknown() {
2457 let address = Address::random();
2458 let asserter = Asserter::new();
2459 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2460 asserter
2461 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2462 asserter
2463 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2464 asserter.push_success(&"0xdeadbeef");
2465 implements_i_described_by_meta_v1(&provider, address)
2466 .await
2467 .unwrap_err();
2468 }
2469
2470 #[tokio::test]
2474 async fn test_store_constructors_inject_no_subgraphs() {
2475 assert!(Store::new().subgraphs().is_empty());
2476 assert!(Store::default().subgraphs().is_empty());
2477 assert!(
2478 Store::create(&vec![], &MetaCache::default(), &HashMap::new())
2479 .subgraphs()
2480 .is_empty()
2481 );
2482
2483 let hash = [0u8; 32];
2484 let mut store = Store::default();
2485 assert!(store.update(&hash).await.is_err());
2486 }
2487
2488 #[test]
2496 fn test_store_create_validates_entries() {
2497 let (_, doc) = sample_authoring_doc();
2498 let good_hash = keccak256(&doc).0.to_vec();
2499 let mut cache = MetaCache::default();
2500 cache.insert_verified(&good_hash, doc.clone()).unwrap();
2501 let mut dotrain_cache = HashMap::new();
2502 dotrain_cache.insert("a.rain".to_string(), good_hash.clone());
2503 dotrain_cache.insert("missing.rain".to_string(), vec![0x44u8; 32]);
2504
2505 let store = Store::create(
2506 &vec!["https://example.com/custom-sg".to_string()],
2507 &cache,
2508 &dotrain_cache,
2509 );
2510
2511 assert_eq!(
2512 store.subgraphs(),
2513 &vec!["https://example.com/custom-sg".to_string()]
2514 );
2515 assert_eq!(store.get_meta(&good_hash), Some(&doc));
2516 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&good_hash));
2517 assert_eq!(store.get_dotrain_hash("missing.rain"), None);
2518 }
2519
2520 #[test]
2522 fn test_store_add_subgraphs_dedupe() {
2523 let mut store = Store::new();
2524 store.add_subgraphs(&vec!["sg-a".to_string()]);
2525 store.add_subgraphs(&vec!["sg-a".to_string(), "sg-b".to_string()]);
2526 assert_eq!(
2527 store.subgraphs(),
2528 &vec!["sg-a".to_string(), "sg-b".to_string()]
2529 );
2530 }
2531
2532 #[test]
2536 fn test_store_dotrain_getters_and_set_fresh() {
2537 let mut store = Store::new();
2538 let text = "some dotrain content";
2539 let (hash, old) = store.set_dotrain(text, "file.rain", false).unwrap();
2540 assert!(old.is_empty());
2541 let expected_item = RainMetaDocumentV1Item {
2542 payload: serde_bytes::ByteBuf::from(text.as_bytes()),
2543 magic: KnownMagic::DotrainV1,
2544 content_type: ContentType::OctetStream,
2545 content_encoding: ContentEncoding::None,
2546 content_language: ContentLanguage::None,
2547 schema: None,
2548 };
2549 let expected_bytes = expected_item.cbor_encode().unwrap();
2550 assert_eq!(hash, keccak256(&expected_bytes).0.to_vec());
2551 assert_eq!(store.get_dotrain_hash("file.rain"), Some(&hash));
2552 assert_eq!(store.get_dotrain_uri(&hash), Some(&"file.rain".to_string()));
2553 assert_eq!(store.get_dotrain_meta("file.rain"), Some(&expected_bytes));
2554 assert_eq!(store.get_dotrain_hash("other.rain"), None);
2555 assert_eq!(store.get_dotrain_uri(&[0u8; 32]), None);
2556 assert_eq!(store.get_dotrain_meta("other.rain"), None);
2557 }
2558
2559 #[test]
2563 fn test_store_set_dotrain_branches() {
2564 let mut store = Store::new();
2565 let (hash_one, _) = store.set_dotrain("text one", "a.rain", false).unwrap();
2566
2567 let (hash_same, old_same) = store.set_dotrain("text one", "a.rain", false).unwrap();
2569 assert_eq!(hash_same, hash_one);
2570 assert!(old_same.is_empty());
2571 assert!(store.get_meta(&hash_one).is_some());
2572
2573 let (hash_two, old_two) = store.set_dotrain("text two", "a.rain", false).unwrap();
2575 assert_ne!(hash_two, hash_one);
2576 assert_eq!(old_two, hash_one);
2577 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_two));
2578 assert!(store.get_meta(&hash_one).is_none());
2579 assert!(store.get_meta(&hash_two).is_some());
2580
2581 let (hash_three, old_three) = store.set_dotrain("text three", "a.rain", true).unwrap();
2583 assert_eq!(old_three, hash_two);
2584 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_three));
2585 assert!(store.get_meta(&hash_two).is_some());
2586 assert!(store.get_meta(&hash_three).is_some());
2587 }
2588
2589 #[test]
2592 fn test_store_delete_dotrain_keep_meta() {
2593 let mut store = Store::new();
2594 let (hash, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2595 store.delete_dotrain("d.rain", false);
2596 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2597 assert!(store.get_meta(&hash).is_none());
2598
2599 let (hash_again, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2600 store.delete_dotrain("d.rain", true);
2601 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2602 assert!(store.get_meta(&hash_again).is_some());
2603 }
2604
2605 #[test]
2609 fn test_store_dotrain_shared_meta_survives_delete() {
2610 let mut store = Store::new();
2611 let (hash, _) = store.set_dotrain("same text", "a.rain", false).unwrap();
2612 let (hash_b, _) = store.set_dotrain("same text", "b.rain", false).unwrap();
2613 assert_eq!(hash_b, hash);
2614
2615 store.delete_dotrain("a.rain", false);
2616 assert_eq!(store.get_dotrain_hash("a.rain"), None);
2617 assert_eq!(store.get_dotrain_hash("b.rain"), Some(&hash));
2618 assert!(store.get_dotrain_meta("b.rain").is_some());
2619
2620 store.delete_dotrain("b.rain", false);
2621 assert!(store.get_meta(&hash).is_none());
2622 }
2623
2624 #[test]
2627 fn test_store_set_dotrain_keeps_shared_old_meta() {
2628 let mut store = Store::new();
2629 let (shared, _) = store.set_dotrain("same text", "a.rain", false).unwrap();
2630 store.set_dotrain("same text", "b.rain", false).unwrap();
2631
2632 let (new_hash, old_hash) = store.set_dotrain("other text", "a.rain", false).unwrap();
2633 assert_eq!(old_hash, shared);
2634 assert_ne!(new_hash, shared);
2635 assert!(store.get_dotrain_meta("b.rain").is_some());
2636
2637 store.set_dotrain("other text", "b.rain", false).unwrap();
2638 assert!(store.get_meta(&shared).is_none());
2639 }
2640
2641 #[test]
2644 fn test_store_merge_semantics() {
2645 let mut ours = Store::new();
2646 let mut theirs = Store::new();
2647
2648 let mine = b"mine".to_vec();
2652 let yours = b"yours".to_vec();
2653 let mine_hash = keccak256(&mine).0.to_vec();
2654 let yours_hash = keccak256(&yours).0.to_vec();
2655 ours.update_with(&mine_hash, &mine).unwrap();
2656 theirs.update_with(&yours_hash, &yours).unwrap();
2657
2658 let (hash_ours, _) = ours.set_dotrain("content a", "x.rain", false).unwrap();
2660 let (_hash_theirs, _) = theirs.set_dotrain("content b", "x.rain", false).unwrap();
2661
2662 theirs.add_subgraphs(&vec!["sg-their".to_string()]);
2663
2664 ours.merge(&theirs);
2665
2666 assert_eq!(ours.get_meta(&mine_hash), Some(&mine));
2667 assert_eq!(ours.get_meta(&yours_hash), Some(&yours));
2668 assert_eq!(ours.get_dotrain_hash("x.rain"), Some(&hash_ours));
2670 assert!(ours.subgraphs().contains(&"sg-their".to_string()));
2672 }
2673
2674 #[tokio::test]
2678 async fn test_store_update_and_update_check() {
2679 use httpmock::prelude::*;
2680 let authoring_meta: AuthoringMeta = serde_json::from_str(
2681 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
2682 )
2683 .unwrap();
2684 let item_one = RainMetaDocumentV1Item {
2685 payload: serde_bytes::ByteBuf::from(authoring_meta.abi_encode_validate().unwrap()),
2686 magic: KnownMagic::AuthoringMetaV1,
2687 content_type: ContentType::Cbor,
2688 content_encoding: ContentEncoding::None,
2689 content_language: ContentLanguage::None,
2690 schema: None,
2691 };
2692 let item_two = sample_dotrain_item();
2693 let doc = RainMetaDocumentV1Item::cbor_encode_seq(
2694 &vec![item_one.clone(), item_two.clone()],
2695 KnownMagic::RainMetaDocumentV1,
2696 )
2697 .unwrap();
2698 let requested = keccak256(&doc).0.to_vec();
2699 let server = MockServer::start();
2700 let _mock = server.mock(|when, then| {
2701 when.method(POST);
2702 then.status(200).json_body(json!({
2703 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2704 }));
2705 });
2706 let mut store = Store::new();
2707 store.add_subgraphs(&vec![server.url("/sg")]);
2708 let fetched = store.update(&requested).await.cloned().unwrap();
2709 assert_eq!(fetched, doc);
2710 assert_eq!(store.get_meta(&requested), Some(&doc));
2711 let inner_one = item_one.cbor_encode().unwrap();
2712 let inner_two = item_two.cbor_encode().unwrap();
2713 assert_eq!(
2714 store.get_meta(keccak256(&inner_one).0.as_ref()),
2715 Some(&inner_one)
2716 );
2717 assert_eq!(
2718 store.get_meta(keccak256(&inner_two).0.as_ref()),
2719 Some(&inner_two)
2720 );
2721
2722 let mut cached_store = Store::new();
2724 let bytes = b"standalone meta bytes".to_vec();
2725 let hash = keccak256(&bytes).0.to_vec();
2726 assert!(cached_store.update_with(&hash, &bytes).is_ok());
2727 assert_eq!(cached_store.update_check(&hash).await.unwrap(), &bytes);
2728 }
2729
2730 #[tokio::test]
2734 async fn test_store_update_rejects_hash_mismatch() {
2735 use httpmock::prelude::*;
2736 let (_, doc) = sample_authoring_doc();
2737 let requested = keccak256(b"the real content").0.to_vec();
2738 let server = MockServer::start();
2739 let _mock = server.mock(|when, then| {
2740 when.method(POST);
2741 then.status(200).json_body(json!({
2742 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2743 }));
2744 });
2745 let mut store = Store::new();
2746 store.add_subgraphs(&vec![server.url("/sg")]);
2747 assert!(store.update(&requested).await.is_err());
2748 assert!(store.get_meta(&requested).is_none());
2749 assert!(store.cache().is_empty());
2750 assert!(store.update_check(&requested).await.is_err());
2752 }
2753
2754 #[tokio::test]
2757 async fn test_store_no_subgraphs_lookups_return_none() {
2758 let hash = [0u8; 32];
2759 let mut store = Store::new();
2760 assert!(store.update(&hash).await.is_err());
2761 assert!(store.update_check(&hash).await.is_err());
2762 assert!(store.cache().is_empty());
2763 }
2764
2765 #[test]
2769 fn test_store_update_with_validation_and_content() {
2770 let mut store = Store::new();
2772 let bytes = b"payload bytes".to_vec();
2773 let wrong_hash = vec![0x99u8; 32];
2774 match store.update_with(&wrong_hash, &bytes).unwrap_err() {
2779 Error::CorruptRecord(message) => {
2780 assert!(
2781 message.contains(&hex::encode_prefixed(&wrong_hash)),
2782 "{}",
2783 message
2784 )
2785 }
2786 other => panic!("expected CorruptRecord, got {:?}", other),
2787 }
2788 assert!(store.get_meta(&wrong_hash).is_none());
2789 let hash = keccak256(&bytes).0.to_vec();
2791 assert_eq!(store.update_with(&hash, &bytes).unwrap(), &bytes);
2792
2793 let mut seeded = Store::new();
2800 let planted = b"planted value".to_vec();
2801 let planted_hash = keccak256(&planted).0.to_vec();
2802 seeded.update_with(&planted_hash, &planted).unwrap();
2803 assert_eq!(seeded.cache().len(), 1);
2804 assert_eq!(
2805 seeded.update_with(&planted_hash, &planted).unwrap(),
2806 &planted
2807 );
2808 assert_eq!(seeded.cache().len(), 1);
2809
2810 let (_, doc) = sample_authoring_doc();
2812 let doc_hash = keccak256(&doc).0.to_vec();
2813 let mut doc_store = Store::new();
2814 assert!(doc_store.update_with(&doc_hash, &doc).is_ok());
2815 let inner = doc[8..].to_vec();
2816 assert_eq!(store_inner_lookup(&doc_store, &inner), Some(inner.clone()));
2817
2818 let item_a = sample_dotrain_item().cbor_encode().unwrap();
2820 let (_, doc_b) = sample_authoring_doc();
2821 let item_b = doc_b[8..].to_vec();
2822 let seq = [item_a.clone(), item_b].concat();
2823 let seq_hash = keccak256(&seq).0.to_vec();
2824 let mut seq_store = Store::new();
2825 assert!(seq_store.update_with(&seq_hash, &seq).is_ok());
2826 assert_eq!(store_inner_lookup(&seq_store, &item_a), None);
2827 }
2828
2829 fn store_inner_lookup(store: &Store, inner_encoded: &[u8]) -> Option<Vec<u8>> {
2830 store.get_meta(keccak256(inner_encoded).0.as_ref()).cloned()
2831 }
2832
2833 #[test]
2836 fn test_bytes32_to_str_invalid_utf8() {
2837 let mut bytes = [0u8; 32];
2838 bytes[0] = 0xf0;
2839 bytes[1] = 0x28;
2840 bytes[2] = 0x8c;
2841 bytes[3] = 0x28;
2842 assert!(matches!(bytes32_to_str(&bytes), Err(Error::Utf8Error(_))));
2843 let no_nul = [0xffu8; 32];
2844 assert!(matches!(bytes32_to_str(&no_nul), Err(Error::Utf8Error(_))));
2845 }
2846}