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 Some(body) = data.strip_prefix(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes()[..])
259 else {
260 return Err(Error::NotRainMetaDocument);
261 };
262 let len = body.len();
263 let mut metas: Vec<RainMetaDocumentV1Item> = vec![];
264 let mut consumed: usize = 0;
265 let mut deserializer = serde_cbor::Deserializer::from_slice(body);
266 while match ItemOrDropped::deserialize(&mut deserializer) {
275 Ok(decoded) => {
276 consumed = deserializer.byte_offset();
277 if let ItemOrDropped::Item(meta) = decoded {
278 metas.push(meta);
279 }
280 true
281 }
282 Err(error) => {
283 if error.is_eof() {
284 false
285 } else {
286 Err(Error::SerdeCborError(error))?
287 }
288 }
289 } {}
290
291 if metas.is_empty() || len != consumed {
292 Err(Error::CorruptMeta)?
293 }
294 Ok(metas)
295 }
296
297 pub fn unpack(&self) -> Result<Vec<u8>, Error> {
299 ContentEncoding::decode(&self.content_encoding, self.payload.as_ref())
300 }
301
302 pub fn unpack_into<T: TryFrom<Self, Error = Error>>(self) -> Result<T, Error> {
304 match self.magic {
305 KnownMagic::OpMetaV1
306 | KnownMagic::DotrainV1
307 | KnownMagic::RainlangV1
308 | KnownMagic::SolidityAbiV2
309 | KnownMagic::AuthoringMetaV1
310 | KnownMagic::AuthoringMetaV2
311 | KnownMagic::AddressList
312 | KnownMagic::InterpreterCallerMetaV1
313 | KnownMagic::ExpressionDeployerV2BytecodeV1
314 | KnownMagic::DotrainSourceV1
315 | KnownMagic::OrderBuilderStateV1
316 | KnownMagic::RainlangSourceV1
317 | KnownMagic::RaindexSignedContextOracleV1 => T::try_from(self),
318 _ => Err(Error::UnsupportedMeta)?,
319 }
320 }
321}
322
323impl Serialize for RainMetaDocumentV1Item {
324 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
325 let mut map = serializer.serialize_map(Some(self.len()))?;
326 map.serialize_entry(&0, &self.payload)?;
327 map.serialize_entry(&1, &(self.magic as u64))?;
328 match self.content_type {
329 ContentType::None => {}
330 content_type => map.serialize_entry(&2, &content_type)?,
331 }
332 match self.content_encoding {
333 ContentEncoding::None => {}
334 content_encoding => map.serialize_entry(&3, &content_encoding)?,
335 }
336 match self.content_language {
337 ContentLanguage::None => {}
338 content_language => map.serialize_entry(&4, &content_language)?,
339 }
340 if let Some(schema) = &self.schema {
341 map.serialize_entry(&(KnownMagic::OaSchema as u64), schema)?;
342 }
343 map.end()
344 }
345}
346
347enum ItemOrDropped {
366 Item(RainMetaDocumentV1Item),
367 Dropped,
369}
370
371impl<'de> Deserialize<'de> for ItemOrDropped {
372 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
373 fn set_once<V, E: serde::de::Error>(
376 slot: &mut Option<V>,
377 value: V,
378 field: &'static str,
379 ) -> Result<(), E> {
380 if slot.is_some() {
381 return Err(serde::de::Error::duplicate_field(field));
382 }
383 *slot = Some(value);
384 Ok(())
385 }
386
387 struct EncodedMap;
388 impl<'de> Visitor<'de> for EncodedMap {
389 type Value = ItemOrDropped;
390
391 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
392 formatter.write_str("rain meta cbor encoded bytes")
393 }
394
395 fn visit_map<T: serde::de::MapAccess<'de>>(
396 self,
397 mut map: T,
398 ) -> Result<Self::Value, T::Error> {
399 const OA_SCHEMA_KEY: u64 = KnownMagic::OaSchema as u64;
400 let mut payload = None;
401 let mut magic: Option<u64> = None;
402 let mut content_type = None;
403 let mut content_encoding = None;
404 let mut content_language = None;
405 let mut schema = None;
406 let mut unknown_keys: BTreeSet<u64> = BTreeSet::new();
410 while match map.next_key::<u64>() {
411 Ok(Some(key)) => {
412 match key {
413 0 => set_once(&mut payload, map.next_value()?, "payload")?,
414 1 => set_once(&mut magic, map.next_value()?, "magic number")?,
415 2 => set_once(&mut content_type, map.next_value()?, "content type")?,
416 3 => set_once(
417 &mut content_encoding,
418 map.next_value()?,
419 "content encoding",
420 )?,
421 4 => set_once(
422 &mut content_language,
423 map.next_value()?,
424 "content language",
425 )?,
426 OA_SCHEMA_KEY => set_once(&mut schema, map.next_value()?, "schema")?,
427 _ => {
435 if !unknown_keys.insert(key) {
436 return Err(serde::de::Error::custom(format!(
437 "duplicate map key: {key}"
438 )));
439 }
440 map.next_value::<serde::de::IgnoredAny>()?;
441 }
442 };
443 true
444 }
445 Ok(None) => false,
446 Err(error) => Err(error)?,
447 } {}
448 let (Some(payload), Some(magic_number)) = (payload, magic) else {
458 return Err(serde::de::Error::custom(
459 "cbor item omits a mandatory key (0 payload, 1 magic)",
460 ));
461 };
462
463 if magic_number == KnownMagic::RainMetaDocumentV1 as u64 {
472 return Err(serde::de::Error::custom(
473 "rain meta document magic number as an item magic number",
474 ));
475 }
476
477 let Ok(magic) = KnownMagic::try_from(magic_number) else {
478 return Ok(ItemOrDropped::Dropped);
479 };
480
481 let content_type = content_type.unwrap_or(ContentType::None);
482 let content_encoding = content_encoding.unwrap_or(ContentEncoding::None);
483 let content_language = content_language.unwrap_or(ContentLanguage::None);
484
485 Ok(ItemOrDropped::Item(RainMetaDocumentV1Item {
486 payload,
487 magic,
488 content_type,
489 content_encoding,
490 content_language,
491 schema,
492 }))
493 }
494 }
495 deserializer.deserialize_map(EncodedMap)
496 }
497}
498
499impl<'de> Deserialize<'de> for RainMetaDocumentV1Item {
506 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
507 match ItemOrDropped::deserialize(deserializer)? {
508 ItemOrDropped::Item(item) => Ok(item),
509 ItemOrDropped::Dropped => Err(serde::de::Error::custom(
510 "not a rain meta item this version reads: a mandatory key is missing or the magic number is unknown",
511 )),
512 }
513 }
514}
515
516pub async fn search(hash: &str, subgraphs: &Vec<String>) -> Result<query::MetaResponse, Error> {
518 if subgraphs.is_empty() {
520 return Err(Error::NoRecordFound);
521 }
522 let request_body = query::MetaQuery::build_query(query::meta_query::Variables {
523 hash: Some(hash.to_ascii_lowercase()),
524 });
525 let mut promises = vec![];
526
527 let client = Arc::new(Client::builder().build().map_err(Error::ReqwestError)?);
528 for url in subgraphs {
529 promises.push(Box::pin(query::process_meta_query(
530 client.clone(),
531 &request_body,
532 url,
533 )));
534 }
535 let response_value = future::select_ok(promises.drain(..)).await?.0;
536 Ok(response_value)
537}
538
539pub async fn implements_i_described_by_meta_v1<P: Provider>(
547 provider: &P,
548 contract_address: Address,
549) -> Result<bool, Erc165Error> {
550 if !supports_erc165(provider, contract_address).await? {
551 return Ok(false);
552 }
553
554 let interface_id_res = IDescribedByMetaV1::IDescribedByMetaV1Calls::xor_selectors();
555 if interface_id_res.is_err() {
556 return Ok(false);
557 }
558
559 match IERC165::new(contract_address, provider)
560 .supportsInterface(interface_id_res.unwrap().into())
561 .call()
562 .await
563 {
564 Ok(supported) => Ok(supported),
565 Err(error)
566 if error.as_revert_data().is_some()
567 || matches!(error, ContractError::ZeroData(_, _)) =>
568 {
569 Ok(false)
570 }
571 Err(error) => Err(error.into()),
572 }
573}
574
575#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
630pub struct Store {
631 subgraphs: Vec<String>,
632 cache: MetaCache,
633 dotrain_cache: HashMap<String, Vec<u8>>,
634}
635
636impl Default for Store {
637 fn default() -> Self {
638 Store::new()
639 }
640}
641
642impl Store {
643 pub fn new() -> Store {
646 Store {
647 subgraphs: vec![],
648 cache: MetaCache::default(),
649 dotrain_cache: HashMap::new(),
650 }
651 }
652
653 pub fn create(
656 subgraphs: &Vec<String>,
657 cache: &MetaCache,
658 dotrain_cache: &HashMap<String, Vec<u8>>,
659 ) -> Store {
660 let mut store = Store::new();
661 store.add_subgraphs(subgraphs);
662 for (hash, bytes) in cache.iter() {
663 let _ = store.update_with(hash, bytes);
664 }
665 for (uri, hash) in dotrain_cache {
666 if !store.dotrain_cache.contains_key(uri) && store.cache.contains_key(hash) {
667 store.dotrain_cache.insert(uri.clone(), hash.clone());
668 }
669 }
670 store
671 }
672
673 pub fn subgraphs(&self) -> &Vec<String> {
675 &self.subgraphs
676 }
677
678 pub fn add_subgraphs(&mut self, subgraphs: &Vec<String>) {
680 for sg in subgraphs {
681 if !self.subgraphs.contains(sg) {
682 self.subgraphs.push(sg.to_string());
683 }
684 }
685 }
686
687 pub fn cache(&self) -> &MetaCache {
689 &self.cache
690 }
691
692 pub fn get_meta(&self, hash: &[u8]) -> Option<&Vec<u8>> {
694 self.cache.get(hash)
695 }
696
697 pub fn dotrain_cache(&self) -> &HashMap<String, Vec<u8>> {
699 &self.dotrain_cache
700 }
701
702 pub fn get_dotrain_hash(&self, uri: &str) -> Option<&Vec<u8>> {
704 self.dotrain_cache.get(uri)
705 }
706
707 pub fn get_dotrain_uri(&self, hash: &[u8]) -> Option<&String> {
709 for (uri, h) in &self.dotrain_cache {
710 if h == hash {
711 return Some(uri);
712 }
713 }
714 None
715 }
716
717 pub fn get_dotrain_meta(&self, uri: &str) -> Option<&Vec<u8>> {
719 self.get_meta(self.dotrain_cache.get(uri)?)
720 }
721
722 pub fn delete_dotrain(&mut self, uri: &str, keep_meta: bool) {
725 if let Some(kv) = self.dotrain_cache.remove_entry(uri) {
726 if !keep_meta {
727 self.remove_unreferenced_meta(&kv.1);
728 }
729 };
730 }
731
732 fn remove_unreferenced_meta(&mut self, hash: &[u8]) {
734 if !self.dotrain_cache.values().any(|h| h == hash) {
735 self.cache.remove(hash);
736 }
737 }
738
739 pub fn merge(&mut self, other: &Store) {
742 self.add_subgraphs(&other.subgraphs);
743 for (hash, bytes) in other.cache.iter() {
744 if !self.cache.contains_key(hash) {
745 let _ = self.cache.insert_verified(hash, bytes.clone());
748 }
749 }
750 for (uri, hash) in &other.dotrain_cache {
751 if !self.dotrain_cache.contains_key(uri) {
752 self.dotrain_cache.insert(uri.clone(), hash.clone());
753 }
754 }
755 }
756
757 fn insert_verified(&mut self, hash: &[u8], bytes: Vec<u8>) -> Result<&Vec<u8>, Error> {
761 self.cache.insert_verified(hash, bytes.clone())?;
762 self.store_content(&bytes);
763 self.get_meta(hash).ok_or(Error::NoRecordFound)
764 }
765
766 pub async fn update(&mut self, hash: &[u8]) -> Result<&Vec<u8>, Error> {
771 let meta = search(&hex::encode_prefixed(hash), &self.subgraphs).await?;
772 self.insert_verified(hash, meta.bytes)
773 }
774
775 pub async fn update_check(&mut self, hash: &[u8]) -> Result<&Vec<u8>, Error> {
777 if self.cache.contains_key(hash) {
783 return self.get_meta(hash).ok_or(Error::NoRecordFound);
784 }
785 self.update(hash).await
786 }
787
788 pub fn update_with(&mut self, hash: &[u8], bytes: &[u8]) -> Result<&Vec<u8>, Error> {
792 if self.cache.contains_key(hash) {
798 return self.get_meta(hash).ok_or(Error::NoRecordFound);
799 }
800 self.insert_verified(hash, bytes.to_vec())
801 }
802
803 pub fn set_dotrain(
809 &mut self,
810 text: &str,
811 uri: &str,
812 keep_old: bool,
813 ) -> Result<(Vec<u8>, Vec<u8>), Error> {
814 let bytes = RainMetaDocumentV1Item {
815 payload: serde_bytes::ByteBuf::from(text.as_bytes()),
816 magic: KnownMagic::DotrainV1,
817 content_type: ContentType::OctetStream,
818 content_encoding: ContentEncoding::None,
819 content_language: ContentLanguage::None,
820 schema: None,
821 }
822 .cbor_encode()?;
823 let new_hash = keccak256(&bytes).0.to_vec();
824 if let Some(h) = self.dotrain_cache.get(uri) {
825 let old_hash = h.clone();
826 if new_hash == old_hash {
827 self.cache.insert_verified(&new_hash, bytes)?;
828 Ok((new_hash, vec![]))
829 } else {
830 self.cache.insert_verified(&new_hash, bytes)?;
831 self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
832 if !keep_old {
833 self.remove_unreferenced_meta(&old_hash);
834 }
835 Ok((new_hash, old_hash))
836 }
837 } else {
838 self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
839 self.cache.insert_verified(&new_hash, bytes)?;
840 Ok((new_hash, vec![]))
841 }
842 }
843
844 fn store_content(&mut self, bytes: &[u8]) {
848 if let Ok(meta_maps) = RainMetaDocumentV1Item::cbor_decode(bytes) {
853 for meta_map in &meta_maps {
854 if let Ok(encoded_bytes) = meta_map.cbor_encode() {
855 let _ = self
859 .cache
860 .insert_verified(&keccak256(&encoded_bytes).0, encoded_bytes);
861 }
862 }
863 }
864 }
865}
866
867pub fn str_to_bytes32(text: &str) -> Result<[u8; 32], Error> {
873 let bytes: &[u8] = text.as_bytes();
874 if bytes.len() > 32 {
875 return Err(Error::BiggerThan32Bytes);
876 }
877 if bytes.contains(&0u8) {
878 return Err(Error::NulByteInInput);
879 }
880 let mut b32 = [0u8; 32];
881 b32[..bytes.len()].copy_from_slice(bytes);
882 Ok(b32)
883}
884
885pub fn bytes32_to_str(bytes: &[u8; 32]) -> Result<&str, Error> {
887 let mut len = 32;
888 if let Some((pos, _)) = itertools::Itertools::find_position(&mut bytes.iter(), |b| **b == 0u8) {
889 len = pos;
890 };
891 Ok(std::str::from_utf8(&bytes[..len])?)
892}
893
894#[cfg(all(test, not(target_family = "wasm")))]
895mod tests {
896 use super::{
897 *, bytes32_to_str, magic::KnownMagic, str_to_bytes32, types::authoring::v1::AuthoringMeta,
898 ContentEncoding, ContentLanguage, ContentType, Error, RainMetaDocumentV1Item,
899 };
900 use alloy::providers::ProviderBuilder;
901 use alloy::{providers::mock::Asserter, rpc::json_rpc::ErrorPayload, sol_types::SolType};
902 use serde_json::json;
903
904 #[test]
907 fn authoring_meta_roundtrip() -> Result<(), Error> {
908 let authoring_meta_content = r#"[
909 {
910 "word": "stack",
911 "description": "Copies an existing value from the stack.",
912 "operandParserOffset": 16
913 },
914 {
915 "word": "constant",
916 "description": "Copies a constant value onto the stack.",
917 "operandParserOffset": 16
918 }
919 ]"#;
920 let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
921
922 let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
924 let expected_abi_encoded = <alloy::sol!((bytes32, uint8, string)[])>::abi_encode(&vec![
925 (
926 str_to_bytes32("stack")?,
927 16u8,
928 "Copies an existing value from the stack.".to_string(),
929 ),
930 (
931 str_to_bytes32("constant")?,
932 16u8,
933 "Copies a constant value onto the stack.".to_string(),
934 ),
935 ]);
936 assert_eq!(authoring_meta_abi_encoded, expected_abi_encoded);
938
939 let meta_map = RainMetaDocumentV1Item {
940 payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
941 magic: KnownMagic::AuthoringMetaV1,
942 content_type: ContentType::Cbor,
943 content_encoding: ContentEncoding::None,
944 content_language: ContentLanguage::None,
945 schema: None,
946 };
947 let cbor_encoded = meta_map.cbor_encode()?;
948
949 assert_eq!(cbor_encoded[0], 0xa3);
951 assert_eq!(cbor_encoded[1], 0x00);
953 assert_eq!(cbor_encoded[2], 0b010_11001);
955 assert_eq!(cbor_encoded[3], 0b000_00010);
956 assert_eq!(cbor_encoded[4], 0b000_00000);
957 assert_eq!(cbor_encoded[5..517], authoring_meta_abi_encoded);
959 assert_eq!(cbor_encoded[517], 0x01);
961 assert_eq!(cbor_encoded[518], 0b000_11011);
963 assert_eq!(
965 &cbor_encoded[519..527],
966 KnownMagic::AuthoringMetaV1.to_prefix_bytes()
967 );
968 assert_eq!(cbor_encoded[527], 0x02);
970 assert_eq!(cbor_encoded[528], 0b011_10000);
972 assert_eq!(&cbor_encoded[529..], "application/cbor".as_bytes());
974
975 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&cbor_encoded))?;
977 assert_eq!(cbor_decoded.len(), 1);
979 assert_eq!(cbor_decoded[0], meta_map);
981
982 Ok(())
983 }
984
985 #[test]
988 fn dotrain_meta_roundtrip() -> Result<(), Error> {
989 let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
990 let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
991
992 let content_encoding = ContentEncoding::Deflate;
993 let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
994
995 let meta_map = RainMetaDocumentV1Item {
996 payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
997 magic: KnownMagic::DotrainV1,
998 content_type: ContentType::OctetStream,
999 content_encoding,
1000 content_language: ContentLanguage::En,
1001 schema: None,
1002 };
1003 let cbor_encoded = meta_map.cbor_encode()?;
1004
1005 assert_eq!(cbor_encoded[0], 0xa5);
1007 assert_eq!(cbor_encoded[1], 0x00);
1009 assert_eq!(cbor_encoded[2], 0b010_11000);
1011 assert_eq!(cbor_encoded[3], 0b001_00100);
1012 assert_eq!(cbor_encoded[4..40], deflated_payload);
1015 assert_eq!(cbor_encoded[40], 0x01);
1017 assert_eq!(cbor_encoded[41], 0b000_11011);
1019 assert_eq!(
1021 &cbor_encoded[42..50],
1022 KnownMagic::DotrainV1.to_prefix_bytes()
1023 );
1024 assert_eq!(cbor_encoded[50], 0x02);
1026 assert_eq!(cbor_encoded[51], 0b011_11000);
1028 assert_eq!(cbor_encoded[52], 0b000_11000);
1029 assert_eq!(&cbor_encoded[53..77], "application/octet-stream".as_bytes());
1031 assert_eq!(cbor_encoded[77], 0x03);
1033 assert_eq!(cbor_encoded[78], 0b011_00111);
1035 assert_eq!(&cbor_encoded[79..86], "deflate".as_bytes());
1037 assert_eq!(cbor_encoded[86], 0x04);
1039 assert_eq!(cbor_encoded[87], 0b011_00010);
1041 assert_eq!(&cbor_encoded[88..], "en".as_bytes());
1043
1044 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&cbor_encoded))?;
1046 assert_eq!(cbor_decoded.len(), 1);
1048 assert_eq!(cbor_decoded[0], meta_map);
1050
1051 Ok(())
1052 }
1053
1054 #[test]
1057 fn meta_seq_roundtrip() -> Result<(), Error> {
1058 let authoring_meta_content = r#"[
1059 {
1060 "word": "stack",
1061 "description": "Copies an existing value from the stack.",
1062 "operandParserOffset": 16
1063 },
1064 {
1065 "word": "constant",
1066 "description": "Copies a constant value onto the stack.",
1067 "operandParserOffset": 16
1068 }
1069 ]"#;
1070 let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
1071 let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
1072 let meta_map_1 = RainMetaDocumentV1Item {
1073 payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
1074 magic: KnownMagic::AuthoringMetaV1,
1075 content_type: ContentType::Cbor,
1076 content_encoding: ContentEncoding::None,
1077 content_language: ContentLanguage::None,
1078 schema: None,
1079 };
1080
1081 let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
1082 let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
1083 let content_encoding = ContentEncoding::Deflate;
1084 let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
1085 let meta_map_2 = RainMetaDocumentV1Item {
1086 payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
1087 magic: KnownMagic::DotrainV1,
1088 content_type: ContentType::OctetStream,
1089 content_encoding,
1090 content_language: ContentLanguage::En,
1091 schema: None,
1092 };
1093
1094 let cbor_encoded = RainMetaDocumentV1Item::cbor_encode_seq(
1096 &vec![meta_map_1.clone(), meta_map_2.clone()],
1097 KnownMagic::RainMetaDocumentV1,
1098 )?;
1099
1100 assert_eq!(
1102 &cbor_encoded[0..8],
1103 KnownMagic::RainMetaDocumentV1.to_prefix_bytes()
1104 );
1105
1106 assert_eq!(cbor_encoded[8], 0xa3);
1109 assert_eq!(cbor_encoded[9], 0x00);
1111 assert_eq!(cbor_encoded[10], 0b010_11001);
1113 assert_eq!(cbor_encoded[11], 0b000_00010);
1114 assert_eq!(cbor_encoded[12], 0b000_00000);
1115 assert_eq!(cbor_encoded[13..525], authoring_meta_abi_encoded);
1117 assert_eq!(cbor_encoded[525], 0x01);
1119 assert_eq!(cbor_encoded[526], 0b000_11011);
1121 assert_eq!(
1123 &cbor_encoded[527..535],
1124 KnownMagic::AuthoringMetaV1.to_prefix_bytes()
1125 );
1126 assert_eq!(cbor_encoded[535], 0x02);
1128 assert_eq!(cbor_encoded[536], 0b011_10000);
1130 assert_eq!(&cbor_encoded[537..553], "application/cbor".as_bytes());
1132
1133 assert_eq!(cbor_encoded[553], 0xa5);
1136 assert_eq!(cbor_encoded[554], 0x00);
1138 assert_eq!(cbor_encoded[555], 0b010_11000);
1140 assert_eq!(cbor_encoded[556], 0b001_00100);
1141 assert_eq!(cbor_encoded[557..593], deflated_payload);
1144 assert_eq!(cbor_encoded[593], 0x01);
1146 assert_eq!(cbor_encoded[594], 0b000_11011);
1148 assert_eq!(
1150 &cbor_encoded[595..603],
1151 KnownMagic::DotrainV1.to_prefix_bytes()
1152 );
1153 assert_eq!(cbor_encoded[603], 0x02);
1155 assert_eq!(cbor_encoded[604], 0b011_11000);
1157 assert_eq!(cbor_encoded[605], 0b000_11000);
1158 assert_eq!(
1160 &cbor_encoded[606..630],
1161 "application/octet-stream".as_bytes()
1162 );
1163 assert_eq!(cbor_encoded[630], 0x03);
1165 assert_eq!(cbor_encoded[631], 0b011_00111);
1167 assert_eq!(&cbor_encoded[632..639], "deflate".as_bytes());
1169 assert_eq!(cbor_encoded[639], 0x04);
1171 assert_eq!(cbor_encoded[640], 0b011_00010);
1173 assert_eq!(&cbor_encoded[641..], "en".as_bytes());
1175
1176 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1178 assert_eq!(cbor_decoded.len(), 2);
1180
1181 assert_eq!(cbor_decoded[0], meta_map_1);
1183 assert_eq!(cbor_decoded[1], meta_map_2);
1185
1186 Ok(())
1187 }
1188
1189 #[test]
1190 fn test_bytes32_to_str() {
1191 let text_bytes_list = vec![
1192 (
1193 "",
1194 hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1195 ),
1196 (
1197 "A",
1198 hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1199 ),
1200 (
1201 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1202 hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1203 ),
1204 (
1205 "!@#$%^&*(),./;'[]",
1206 hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1207 ),
1208 ];
1209
1210 for (text, bytes) in text_bytes_list {
1211 assert_eq!(text, bytes32_to_str(&bytes).unwrap());
1212 }
1213 }
1214
1215 #[test]
1216 fn test_str_to_bytes32() {
1217 let text_bytes_list = vec![
1218 (
1219 "",
1220 hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1221 ),
1222 (
1223 "A",
1224 hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1225 ),
1226 (
1227 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1228 hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1229 ),
1230 (
1231 "!@#$%^&*(),./;'[]",
1232 hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1233 ),
1234 ];
1235
1236 for (text, bytes) in text_bytes_list {
1237 assert_eq!(bytes, str_to_bytes32(text).unwrap());
1238 }
1239 }
1240
1241 #[test]
1242 fn test_str_to_bytes32_long() {
1243 assert!(matches!(
1244 str_to_bytes32("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456").unwrap_err(),
1245 Error::BiggerThan32Bytes
1246 ));
1247 }
1248
1249 #[test]
1253 fn test_str_to_bytes32_rejects_nul() {
1254 for text in [
1255 "\0",
1256 "\0a",
1257 "a\0",
1258 "a\0b",
1259 "abcdefghijklmnopqrstuvwxyz01234\0",
1260 ] {
1261 assert!(
1262 matches!(str_to_bytes32(text), Err(Error::NulByteInInput)),
1263 "nul bearing input {:?} accepted",
1264 text
1265 );
1266 }
1267 }
1268
1269 #[test]
1272 fn test_str_to_bytes32_round_trip() -> Result<(), Error> {
1273 let mut seen: Vec<[u8; 32]> = vec![];
1274 for text in [
1275 "",
1276 "a",
1277 "stack",
1278 "!@#$%^&*(),./;'[]",
1279 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1280 ] {
1281 let bytes = str_to_bytes32(text)?;
1282 assert_eq!(bytes32_to_str(&bytes)?, text);
1283 assert!(!seen.contains(&bytes), "input {:?} collided", text);
1284 seen.push(bytes);
1285 }
1286 Ok(())
1287 }
1288
1289 #[tokio::test]
1290 async fn test_implements_i_describe_by_meta_v1() {
1291 async fn new_server_client() -> (Asserter, impl Provider) {
1293 let asserter = Asserter::new();
1294 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
1295
1296 asserter.push_success(
1298 &"0x0000000000000000000000000000000000000000000000000000000000000001",
1299 );
1300 asserter.push_success(
1301 &"0x0000000000000000000000000000000000000000000000000000000000000000",
1302 );
1303
1304 (asserter, provider)
1305 }
1306
1307 let address = Address::random();
1308
1309 let (asserter, provider) = new_server_client().await;
1311 asserter
1312 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
1313 let result = implements_i_described_by_meta_v1(&provider, address)
1314 .await
1315 .unwrap();
1316 assert!(result);
1317
1318 let (asserter, provider) = new_server_client().await;
1320 asserter
1321 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
1322 let result = implements_i_described_by_meta_v1(&provider, address)
1323 .await
1324 .unwrap();
1325 assert!(!result);
1326
1327 let (asserter, provider) = new_server_client().await;
1329 asserter.push_failure(ErrorPayload {
1330 code: -32003,
1331 message: "execution reverted".into(),
1332 data: Some(serde_json::value::to_raw_value(&json!("0x00")).unwrap()),
1333 });
1334 let result = implements_i_described_by_meta_v1(&provider, address)
1335 .await
1336 .unwrap();
1337 assert!(!result);
1338 }
1339
1340 #[test]
1344 fn oa_schema_map_key_roundtrip() -> Result<(), Error> {
1345 let payload = vec![0x01, 0x02, 0x03];
1346 let schema = "QmSchemaHash1234567890abcdefghijklmnopqrstuvwx".to_string();
1349 assert_eq!(schema.len(), 46);
1350
1351 let meta_map = RainMetaDocumentV1Item {
1352 payload: serde_bytes::ByteBuf::from(payload.clone()),
1353 magic: KnownMagic::OaStructure,
1354 content_type: ContentType::Json,
1355 content_encoding: ContentEncoding::Deflate,
1356 content_language: ContentLanguage::None,
1357 schema: Some(schema.clone()),
1358 };
1359 let cbor_encoded = meta_map.cbor_encode()?;
1360
1361 assert_eq!(cbor_encoded[0], 0xa5);
1363 assert_eq!(cbor_encoded[1], 0x00);
1365 assert_eq!(cbor_encoded[2], 0b010_00011);
1367 assert_eq!(cbor_encoded[3..6], payload);
1369 assert_eq!(cbor_encoded[6], 0x01);
1371 assert_eq!(cbor_encoded[7], 0b000_11011);
1373 assert_eq!(
1375 &cbor_encoded[8..16],
1376 KnownMagic::OaStructure.to_prefix_bytes()
1377 );
1378 assert_eq!(cbor_encoded[16], 0x02);
1380 assert_eq!(cbor_encoded[17], 0b011_10000);
1382 assert_eq!(&cbor_encoded[18..34], "application/json".as_bytes());
1383 assert_eq!(cbor_encoded[34], 0x03);
1385 assert_eq!(cbor_encoded[35], 0b011_00111);
1387 assert_eq!(&cbor_encoded[36..43], "deflate".as_bytes());
1388 assert_eq!(cbor_encoded[43], 0b000_11011);
1390 assert_eq!(
1391 &cbor_encoded[44..52],
1392 KnownMagic::OaSchema.to_prefix_bytes()
1393 );
1394 assert_eq!(cbor_encoded[52], 0b011_11000);
1396 assert_eq!(cbor_encoded[53], 46);
1397 assert_eq!(&cbor_encoded[54..], schema.as_bytes());
1399
1400 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&cbor_encoded))?;
1402 assert_eq!(cbor_decoded.len(), 1);
1404 assert_eq!(cbor_decoded[0], meta_map);
1406
1407 Ok(())
1408 }
1409
1410 #[test]
1413 fn no_schema_key_encodes_as_before_roundtrip() -> Result<(), Error> {
1414 let payload = vec![0x0a, 0x0b];
1415 let meta_map = RainMetaDocumentV1Item {
1416 payload: serde_bytes::ByteBuf::from(payload.clone()),
1417 magic: KnownMagic::OaStructure,
1418 content_type: ContentType::None,
1419 content_encoding: ContentEncoding::None,
1420 content_language: ContentLanguage::None,
1421 schema: None,
1422 };
1423 let cbor_encoded = meta_map.cbor_encode()?;
1424
1425 assert_eq!(cbor_encoded[0], 0xa2);
1427 assert_eq!(cbor_encoded[1], 0x00);
1429 assert_eq!(cbor_encoded[2], 0b010_00010);
1431 assert_eq!(cbor_encoded[3..5], payload);
1433 assert_eq!(cbor_encoded[5], 0x01);
1435 assert_eq!(cbor_encoded[6], 0b000_11011);
1437 assert_eq!(
1439 &cbor_encoded[7..],
1440 KnownMagic::OaStructure.to_prefix_bytes()
1441 );
1442
1443 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&cbor_encoded))?;
1444 assert_eq!(cbor_decoded.len(), 1);
1445 assert_eq!(cbor_decoded[0], meta_map);
1446
1447 Ok(())
1448 }
1449
1450 #[test]
1453 fn unknown_map_key_index_is_ignored() -> Result<(), Error> {
1454 let mut bytes: Vec<u8> = vec![
1455 0xa3, 0x00, 0x40, 0x01, 0x1b,
1459 ];
1460 bytes.extend_from_slice(&KnownMagic::DotrainSourceV1.to_prefix_bytes());
1461 bytes.extend_from_slice(&[0x05, 0x07]);
1463
1464 let decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes))?;
1465 assert_eq!(decoded.len(), 1);
1466 assert_eq!(decoded[0], plain_item(KnownMagic::DotrainSourceV1, vec![]));
1467
1468 Ok(())
1469 }
1470
1471 #[test]
1474 fn non_oa_schema_extra_map_key_is_ignored() -> Result<(), Error> {
1475 let mut bytes: Vec<u8> = vec![
1478 0xa3, 0x00, 0x41, 0xff, 0x01, 0x1b,
1482 ];
1483 bytes.extend_from_slice(&KnownMagic::OaStructure.to_prefix_bytes());
1484 bytes.push(0x1b);
1486 bytes.extend_from_slice(&KnownMagic::OaHashList.to_prefix_bytes());
1487 bytes.extend_from_slice(&[0x62, 0x68, 0x69]);
1489
1490 let decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes))?;
1491 assert_eq!(decoded.len(), 1);
1492 let expected = plain_item(KnownMagic::OaStructure, vec![0xff]);
1493 assert_eq!(decoded[0], expected);
1494 assert_eq!(decoded[0].schema, None);
1495
1496 Ok(())
1497 }
1498
1499 #[test]
1502 fn unknown_map_key_consumes_its_whole_value() -> Result<(), Error> {
1503 let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1504 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1505 bytes.extend_from_slice(&[0x05, 0xa1, 0x18, 0x2a, 0x82, 0x01, 0x02]);
1507 bytes.extend_from_slice(&handwritten_map());
1508
1509 let decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes))?;
1510 assert_eq!(decoded.len(), 2);
1511 let expected = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1512 assert_eq!(decoded[0], expected);
1513 assert_eq!(decoded[1], expected);
1514
1515 Ok(())
1516 }
1517
1518 #[test]
1521 fn ignored_map_key_is_absent_from_the_reencoding() -> Result<(), Error> {
1522 let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1523 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1524 bytes.extend_from_slice(&[0x05, 0x07]);
1525
1526 let decoded = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes))?;
1527 assert_eq!(decoded[0].cbor_encode()?, handwritten_map());
1528 assert_eq!(decoded[0].hash(false)?, keccak256(handwritten_map()).0);
1529 assert_ne!(decoded[0].hash(false)?, keccak256(&bytes).0);
1530
1531 Ok(())
1532 }
1533
1534 #[test]
1538 fn non_integer_map_key_errors() {
1539 let mut text_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1540 text_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1541 text_key.extend_from_slice(&[0x61, 0x35, 0x07]);
1543 assert!(matches!(
1544 RainMetaDocumentV1Item::cbor_decode(&with_prefix(&text_key)),
1545 Err(Error::SerdeCborError(_))
1546 ));
1547
1548 let mut negative_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1549 negative_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1550 negative_key.extend_from_slice(&[0x20, 0x07]);
1552 assert!(matches!(
1553 RainMetaDocumentV1Item::cbor_decode(&with_prefix(&negative_key)),
1554 Err(Error::SerdeCborError(_))
1555 ));
1556 }
1557
1558 #[test]
1562 fn unknown_map_key_does_not_stand_in_for_a_mandatory_key() {
1563 let mut bytes: Vec<u8> = vec![0xa2, 0x05, 0x07, 0x01, 0x1b];
1564 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1565 assert!(RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)).is_err());
1566 }
1567
1568 fn with_prefix(bytes: &[u8]) -> Vec<u8> {
1572 let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1573 document.extend_from_slice(bytes);
1574 document
1575 }
1576
1577 fn plain_item(magic: KnownMagic, payload: Vec<u8>) -> RainMetaDocumentV1Item {
1578 RainMetaDocumentV1Item {
1579 payload: serde_bytes::ByteBuf::from(payload),
1580 magic,
1581 content_type: ContentType::None,
1582 content_encoding: ContentEncoding::None,
1583 content_language: ContentLanguage::None,
1584 schema: None,
1585 }
1586 }
1587
1588 fn sample_authoring_doc() -> (AuthoringMeta, Vec<u8>) {
1591 let authoring_meta: AuthoringMeta = serde_json::from_str(
1592 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
1593 )
1594 .unwrap();
1595 let abi = authoring_meta.abi_encode_validate().unwrap();
1596 let item = RainMetaDocumentV1Item {
1597 payload: serde_bytes::ByteBuf::from(abi),
1598 magic: KnownMagic::AuthoringMetaV1,
1599 content_type: ContentType::Cbor,
1600 content_encoding: ContentEncoding::None,
1601 content_language: ContentLanguage::None,
1602 schema: None,
1603 };
1604 let doc =
1605 RainMetaDocumentV1Item::cbor_encode_seq(&vec![item], KnownMagic::RainMetaDocumentV1)
1606 .unwrap();
1607 (authoring_meta, doc)
1608 }
1609
1610 fn sample_dotrain_item() -> RainMetaDocumentV1Item {
1611 RainMetaDocumentV1Item {
1612 payload: serde_bytes::ByteBuf::from("some dotrain body".as_bytes()),
1613 magic: KnownMagic::DotrainV1,
1614 content_type: ContentType::OctetStream,
1615 content_encoding: ContentEncoding::None,
1616 content_language: ContentLanguage::None,
1617 schema: None,
1618 }
1619 }
1620
1621 fn handwritten_map() -> Vec<u8> {
1624 vec![
1625 0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b, 0xff, 0xda, 0xc2, 0xf2, 0xf3, 0x7b, 0xe8, 0x94, ]
1631 }
1632
1633 #[test]
1637 fn test_hash_bare_vs_document() -> Result<(), Error> {
1638 let map_bytes = handwritten_map();
1639 let mut doc_bytes: Vec<u8> = vec![0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74];
1640 doc_bytes.extend_from_slice(&map_bytes);
1641
1642 let item = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1643 assert_eq!(item.hash(false)?, keccak256(&map_bytes).0);
1644 assert_eq!(item.hash(true)?, keccak256(&doc_bytes).0);
1645 assert_ne!(item.hash(false)?, item.hash(true)?);
1646 Ok(())
1647 }
1648
1649 #[test]
1651 fn test_cbor_decode_empty_is_corrupt() {
1652 assert!(matches!(
1654 RainMetaDocumentV1Item::cbor_decode(&[]),
1655 Err(Error::NotRainMetaDocument)
1656 ));
1657 let prefix = KnownMagic::RainMetaDocumentV1.to_prefix_bytes();
1659 assert!(matches!(
1660 RainMetaDocumentV1Item::cbor_decode(&prefix),
1661 Err(Error::CorruptMeta)
1662 ));
1663 }
1664
1665 #[test]
1671 fn test_cbor_decode_requires_the_document_prefix() {
1672 let bare = plain_item(KnownMagic::DotrainV1, vec![0x42])
1673 .cbor_encode()
1674 .unwrap();
1675 assert!(matches!(
1676 RainMetaDocumentV1Item::cbor_decode(&bare),
1677 Err(Error::NotRainMetaDocument)
1678 ));
1679 let items = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bare)).unwrap();
1682 assert_eq!(items.len(), 1);
1683 assert_eq!(items[0].payload.as_ref(), &[0x42]);
1684 }
1685
1686 #[test]
1689 fn test_cbor_decode_rejects_another_magic_as_the_prefix() {
1690 let bare = plain_item(KnownMagic::DotrainV1, vec![0x42])
1691 .cbor_encode()
1692 .unwrap();
1693 let mut document = KnownMagic::DotrainV1.to_prefix_bytes().to_vec();
1694 document.extend_from_slice(&bare);
1695 assert!(matches!(
1696 RainMetaDocumentV1Item::cbor_decode(&document),
1697 Err(Error::NotRainMetaDocument)
1698 ));
1699 }
1700
1701 #[test]
1704 fn test_cbor_decode_rejects_data_shorter_than_the_prefix() {
1705 let prefix = KnownMagic::RainMetaDocumentV1.to_prefix_bytes();
1706 for cut in 0..prefix.len() {
1707 assert!(
1708 matches!(
1709 RainMetaDocumentV1Item::cbor_decode(&prefix[..cut]),
1710 Err(Error::NotRainMetaDocument)
1711 ),
1712 "prefix cut to {} bytes",
1713 cut
1714 );
1715 }
1716 }
1717
1718 #[test]
1721 fn test_cbor_decode_trailing_truncated_is_corrupt() {
1722 let mut bytes = handwritten_map();
1723 bytes.push(0x1b); assert!(matches!(
1725 RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)),
1726 Err(Error::CorruptMeta)
1727 ));
1728 }
1729
1730 #[test]
1734 fn test_cbor_decode_truncated_item_is_corrupt() {
1735 let mut sole = handwritten_map();
1736 sole.pop();
1737 assert!(matches!(
1738 RainMetaDocumentV1Item::cbor_decode(&with_prefix(&sole)),
1739 Err(Error::CorruptMeta)
1740 ));
1741
1742 assert!(matches!(
1743 RainMetaDocumentV1Item::cbor_decode(&with_prefix(&[0xa2, 0x00, 0x41, 0x01])),
1744 Err(Error::CorruptMeta)
1745 ));
1746
1747 let mut after_complete = handwritten_map();
1748 after_complete.extend_from_slice(&[0xa2, 0x00]);
1749 assert!(matches!(
1750 RainMetaDocumentV1Item::cbor_decode(&with_prefix(&after_complete)),
1751 Err(Error::CorruptMeta)
1752 ));
1753 }
1754
1755 #[test]
1758 fn test_cbor_decode_trailing_garbage_errors() {
1759 let mut bytes = handwritten_map();
1760 bytes.push(0xff); assert!(matches!(
1762 RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)),
1763 Err(Error::SerdeCborError(_))
1764 ));
1765 }
1766
1767 #[test]
1772 fn test_cbor_decode_missing_payload_is_corrupt() {
1773 let mut bytes: Vec<u8> = vec![0xa1, 0x01, 0x1b]; bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1775 assert!(RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)).is_err());
1776
1777 let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1779 .cbor_encode()
1780 .unwrap();
1781 let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1782 document.extend_from_slice(&bytes);
1783 document.extend_from_slice(&good);
1784 assert!(RainMetaDocumentV1Item::cbor_decode(&document).is_err());
1785 }
1786
1787 #[test]
1789 fn test_cbor_decode_missing_magic_is_corrupt() {
1790 let bytes: Vec<u8> = vec![0xa1, 0x00, 0x41, 0x01]; assert!(RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)).is_err());
1792
1793 let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1794 .cbor_encode()
1795 .unwrap();
1796 let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1797 document.extend_from_slice(&bytes);
1798 document.extend_from_slice(&good);
1799 assert!(RainMetaDocumentV1Item::cbor_decode(&document).is_err());
1800 }
1801
1802 #[test]
1807 fn test_cbor_decode_unknown_magic_is_dropped() {
1808 let mut bytes: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1809 bytes.extend_from_slice(&0xdeadbeefdeadbeefu64.to_be_bytes());
1810 assert!(matches!(
1811 RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)),
1812 Err(Error::CorruptMeta)
1813 ));
1814 }
1815
1816 #[test]
1820 fn test_cbor_decode_drops_only_the_unknown_magic_item() {
1821 let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1822 .cbor_encode()
1823 .unwrap();
1824
1825 let mut unknown_magic: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1826 unknown_magic.extend_from_slice(&0xdeadbeefdeadbeefu64.to_be_bytes());
1827
1828 let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1829 document.extend_from_slice(&unknown_magic);
1830 document.extend_from_slice(&good);
1831
1832 let items = RainMetaDocumentV1Item::cbor_decode(&document).unwrap();
1833 assert_eq!(items.len(), 1, "expected only the good item");
1834 assert_eq!(items[0].magic, KnownMagic::DotrainV1);
1835 assert_eq!(items[0].payload.as_ref(), &[0x42]);
1836 }
1837
1838 #[test]
1843 fn test_cbor_decode_nested_document_magic_is_not_dropped() {
1844 let good = plain_item(KnownMagic::DotrainV1, vec![0x42])
1845 .cbor_encode()
1846 .unwrap();
1847
1848 let mut nested: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1849 nested.extend_from_slice(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes());
1850
1851 let mut document = KnownMagic::RainMetaDocumentV1.to_prefix_bytes().to_vec();
1852 document.extend_from_slice(&nested);
1853 document.extend_from_slice(&good);
1854
1855 assert!(matches!(
1856 RainMetaDocumentV1Item::cbor_decode(&document),
1857 Err(Error::SerdeCborError(_))
1858 ));
1859 }
1860
1861 fn handwritten_entries(entries: &[(Vec<u8>, Vec<u8>)]) -> Vec<u8> {
1865 assert!(entries.len() < 24);
1866 let mut bytes = vec![0xa0 | entries.len() as u8];
1867 for (key, value) in entries {
1868 bytes.extend_from_slice(key);
1869 bytes.extend_from_slice(value);
1870 }
1871 bytes
1872 }
1873
1874 fn handwritten_magic(magic: KnownMagic) -> Vec<u8> {
1876 let mut bytes = vec![0x1b];
1877 bytes.extend_from_slice(&magic.to_prefix_bytes());
1878 bytes
1879 }
1880
1881 fn handwritten_text(text: &str) -> Vec<u8> {
1883 assert!(text.len() < 24);
1884 let mut bytes = vec![0x60 | text.len() as u8];
1885 bytes.extend_from_slice(text.as_bytes());
1886 bytes
1887 }
1888
1889 #[test]
1892 fn test_cbor_decode_duplicate_payload_key_errors() {
1893 let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x00, 0x41, 0x02, 0x01, 0x1b];
1894 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1895 let error = RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)).unwrap_err();
1896 assert!(matches!(error, Error::SerdeCborError(_)));
1897 assert!(
1898 error.to_string().contains("duplicate field `payload`"),
1899 "{error}"
1900 );
1901 }
1902
1903 #[test]
1906 fn test_cbor_decode_duplicate_any_key_errors() -> Result<(), Error> {
1907 let cases: [(Vec<u8>, Vec<u8>, Vec<u8>); 6] = [
1908 (vec![0x00], vec![0x41, 0x01], vec![0x41, 0x02]),
1909 (
1910 vec![0x01],
1911 handwritten_magic(KnownMagic::DotrainV1),
1912 handwritten_magic(KnownMagic::RainlangV1),
1913 ),
1914 (
1915 vec![0x02],
1916 handwritten_text("application/cbor"),
1917 handwritten_text("application/json"),
1918 ),
1919 (
1920 vec![0x03],
1921 handwritten_text("identity"),
1922 handwritten_text("deflate"),
1923 ),
1924 (vec![0x04], handwritten_text("en"), handwritten_text("none")),
1925 (
1926 handwritten_magic(KnownMagic::OaSchema),
1927 handwritten_text("hi"),
1928 handwritten_text("bye"),
1929 ),
1930 ];
1931 let base: Vec<(Vec<u8>, Vec<u8>)> = cases
1932 .iter()
1933 .map(|(key, value, _)| (key.clone(), value.clone()))
1934 .collect();
1935
1936 let decoded =
1937 RainMetaDocumentV1Item::cbor_decode(&with_prefix(&handwritten_entries(&base)))?;
1938 assert_eq!(decoded.len(), 1);
1939 assert_eq!(decoded[0].payload.as_ref(), &[0x01]);
1940 assert_eq!(decoded[0].magic, KnownMagic::DotrainV1);
1941 assert_eq!(decoded[0].content_type, ContentType::Cbor);
1942 assert_eq!(decoded[0].content_encoding, ContentEncoding::Identity);
1943 assert_eq!(decoded[0].content_language, ContentLanguage::En);
1944 assert_eq!(decoded[0].schema.as_deref(), Some("hi"));
1945
1946 for (key, value, other_value) in &cases {
1947 for repeated in [value, other_value] {
1948 let mut entries = base.clone();
1949 entries.push((key.clone(), repeated.clone()));
1950 let error = RainMetaDocumentV1Item::cbor_decode(&with_prefix(
1951 &handwritten_entries(&entries),
1952 ))
1953 .unwrap_err();
1954 assert!(
1955 matches!(error, Error::SerdeCborError(_)),
1956 "{key:?} {error:?}"
1957 );
1958 assert!(
1959 error.to_string().contains("duplicate field"),
1960 "{key:?} {error}"
1961 );
1962 }
1963 }
1964 Ok(())
1965 }
1966
1967 #[test]
1972 fn test_cbor_decode_duplicate_unknown_key_errors() -> Result<(), Error> {
1973 let base: Vec<(Vec<u8>, Vec<u8>)> = vec![
1974 (vec![0x00], vec![0x41, 0x01]),
1975 (vec![0x01], handwritten_magic(KnownMagic::DotrainV1)),
1976 ];
1977 let unknown: [(Vec<u8>, Vec<u8>, Vec<u8>); 2] = [
1980 (vec![0x05], vec![0x07], vec![0x08]),
1981 (
1982 handwritten_magic(KnownMagic::OaHashList),
1983 handwritten_text("hi"),
1984 handwritten_text("bye"),
1985 ),
1986 ];
1987
1988 for (key, value, other_value) in &unknown {
1989 let mut entries = base.clone();
1990 entries.push((key.clone(), value.clone()));
1991 let decoded =
1992 RainMetaDocumentV1Item::cbor_decode(&with_prefix(&handwritten_entries(&entries)))?;
1993 assert_eq!(decoded, vec![plain_item(KnownMagic::DotrainV1, vec![0x01])]);
1994
1995 for repeated in [value, other_value] {
1996 let mut duplicated = entries.clone();
1997 duplicated.push((key.clone(), repeated.clone()));
1998 let error = RainMetaDocumentV1Item::cbor_decode(&with_prefix(
1999 &handwritten_entries(&duplicated),
2000 ))
2001 .unwrap_err();
2002 assert!(
2003 matches!(error, Error::SerdeCborError(_)),
2004 "{key:?} {error:?}"
2005 );
2006 assert!(
2007 error.to_string().contains("duplicate map key"),
2008 "{key:?} {error}"
2009 );
2010 }
2011 }
2012 Ok(())
2013 }
2014
2015 #[test]
2019 fn test_cbor_decode_handwritten_document_magic_item() {
2020 let bytes: Vec<u8> = vec![
2021 0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b, 0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74, ];
2027 assert!(RainMetaDocumentV1Item::cbor_decode(&with_prefix(&bytes)).is_err());
2031 }
2032
2033 #[test]
2039 fn test_document_magic_item_carries_a_nested_document() -> Result<(), Error> {
2040 let inner = plain_item(KnownMagic::DotrainV1, vec![0x01]);
2041 let inner_doc = RainMetaDocumentV1Item::cbor_encode_seq(
2042 &vec![inner.clone()],
2043 KnownMagic::RainMetaDocumentV1,
2044 )?;
2045 let outer = plain_item(KnownMagic::RainMetaDocumentV1, inner_doc.clone());
2046 let outer_doc = RainMetaDocumentV1Item::cbor_encode_seq(
2047 &vec![outer.clone()],
2048 KnownMagic::RainMetaDocumentV1,
2049 )?;
2050
2051 assert!(RainMetaDocumentV1Item::cbor_decode(&outer_doc).is_err());
2057 assert_eq!(
2058 RainMetaDocumentV1Item::cbor_decode(&inner_doc)?,
2059 vec![inner]
2060 );
2061 Ok(())
2062 }
2063
2064 #[test]
2067 fn test_document_magic_item_is_not_unpackable() {
2068 assert!(matches!(
2069 KnownMeta::try_from(KnownMagic::RainMetaDocumentV1),
2070 Err(Error::UnsupportedMeta)
2071 ));
2072 assert!(matches!(
2073 plain_item(KnownMagic::RainMetaDocumentV1, vec![0x01]).unpack_into::<Vec<u8>>(),
2074 Err(Error::UnsupportedMeta)
2075 ));
2076 }
2077
2078 #[test]
2080 fn test_unpack_decodes_content_encoding() -> Result<(), Error> {
2081 let content = b"unpack me via deflate".to_vec();
2082 let packed = ContentEncoding::Deflate.encode(&content);
2083 assert_ne!(packed, content);
2084 let mut item = plain_item(KnownMagic::DotrainV1, packed);
2085 item.content_encoding = ContentEncoding::Deflate;
2086 assert_eq!(item.unpack()?, content);
2087
2088 let item = plain_item(KnownMagic::DotrainV1, content.clone());
2089 assert_eq!(item.unpack()?, content);
2090 Ok(())
2091 }
2092
2093 #[test]
2096 fn test_unpack_into_whitelist() {
2097 use strum::IntoEnumIterator;
2098 let supported = [
2099 KnownMagic::OpMetaV1,
2100 KnownMagic::DotrainV1,
2101 KnownMagic::RainlangV1,
2102 KnownMagic::SolidityAbiV2,
2103 KnownMagic::AuthoringMetaV1,
2104 KnownMagic::AuthoringMetaV2,
2105 KnownMagic::AddressList,
2106 KnownMagic::InterpreterCallerMetaV1,
2107 KnownMagic::ExpressionDeployerV2BytecodeV1,
2108 KnownMagic::DotrainSourceV1,
2109 KnownMagic::OrderBuilderStateV1,
2110 KnownMagic::RainlangSourceV1,
2111 KnownMagic::RaindexSignedContextOracleV1,
2112 ];
2113 for magic in supported {
2114 let unpacked: Vec<u8> = plain_item(magic, vec![0x61]).unpack_into().unwrap();
2115 assert_eq!(unpacked, vec![0x61], "{:?}", magic);
2116 }
2117 let unsupported = [
2118 KnownMagic::RainMetaDocumentV1,
2119 KnownMagic::WebDataV1,
2120 KnownMagic::OaSchema,
2121 KnownMagic::OaHashList,
2122 KnownMagic::OaStructure,
2123 KnownMagic::OaTokenImage,
2124 KnownMagic::OaTokenCredentialLinks,
2125 ];
2126 for magic in unsupported {
2127 let result: Result<Vec<u8>, Error> = plain_item(magic, vec![0x61]).unpack_into();
2128 assert!(matches!(result, Err(Error::UnsupportedMeta)), "{:?}", magic);
2129 }
2130 assert_eq!(
2132 supported.len() + unsupported.len(),
2133 KnownMagic::iter().count()
2134 );
2135 }
2136
2137 #[test]
2140 fn test_try_into_string_invalid_utf8_errors() {
2141 let item = plain_item(KnownMagic::DotrainV1, vec![0xff, 0xfe]);
2142 let result: Result<String, Error> = item.try_into();
2143 assert!(matches!(result, Err(Error::FromUtf8Error(_))));
2144 }
2145
2146 #[test]
2148 fn test_try_into_vec_decodes_encoding() -> Result<(), Error> {
2149 let content = b"raw bytecode bytes \x00\x01\x02".to_vec();
2150 let packed = ContentEncoding::Deflate.encode(&content);
2151 let mut item = plain_item(KnownMagic::ExpressionDeployerV2BytecodeV1, packed.clone());
2152 item.content_encoding = ContentEncoding::Deflate;
2153 let unpacked: Vec<u8> = item.try_into()?;
2154 assert_eq!(unpacked, content);
2155 assert_ne!(unpacked, packed);
2156 Ok(())
2157 }
2158
2159 #[test]
2162 fn test_content_encoding_deflate_roundtrip() -> Result<(), Error> {
2163 let content = b"hello rain deflate fixture hello rain deflate fixture".to_vec();
2164 let encoded = ContentEncoding::Deflate.encode(&content);
2165 assert_ne!(encoded, content);
2166 assert_eq!(encoded[0], 0x78);
2167 assert_eq!(ContentEncoding::Deflate.decode(&encoded)?, content);
2168 Ok(())
2169 }
2170
2171 #[test]
2173 fn test_content_encoding_passthrough() -> Result<(), Error> {
2174 let data = vec![0x00, 0xff, 0x10];
2175 for encoding in [ContentEncoding::None, ContentEncoding::Identity] {
2176 assert_eq!(encoding.encode(&data), data, "{:?}", encoding);
2177 assert_eq!(encoding.decode(&data)?, data, "{:?}", encoding);
2178 }
2179 Ok(())
2180 }
2181
2182 #[test]
2185 fn test_content_encoding_decode_fixtures() -> Result<(), Error> {
2186 let content = b"hello rain deflate fixture".to_vec();
2187 let zlib: Vec<u8> = vec![
2188 120, 156, 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44,
2189 73, 85, 72, 203, 172, 40, 41, 45, 74, 5, 0, 132, 64, 9, 251,
2190 ];
2191 let raw: Vec<u8> = vec![
2192 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44, 73, 85, 72,
2193 203, 172, 40, 41, 45, 74, 5, 0,
2194 ];
2195 assert_eq!(ContentEncoding::Deflate.decode(&zlib)?, content);
2196 assert_eq!(ContentEncoding::Deflate.decode(&raw)?, content);
2197 Ok(())
2198 }
2199
2200 #[test]
2203 fn test_content_encoding_decode_garbage_errors() {
2204 let garbage = [0xffu8, 0xff, 0xff, 0xff];
2205 assert!(matches!(
2206 ContentEncoding::Deflate.decode(&garbage),
2207 Err(Error::InflateError(_))
2208 ));
2209 }
2210
2211 #[test]
2213 fn test_content_headers_strum_names() {
2214 use std::str::FromStr;
2215 assert_eq!(
2216 ContentEncoding::from_str("deflate").unwrap(),
2217 ContentEncoding::Deflate
2218 );
2219 assert_eq!(
2220 ContentEncoding::from_str("identity").unwrap(),
2221 ContentEncoding::Identity
2222 );
2223 assert_eq!(
2224 ContentEncoding::from_str("none").unwrap(),
2225 ContentEncoding::None
2226 );
2227 assert_eq!(ContentEncoding::Deflate.to_string(), "deflate");
2228 assert_eq!(
2229 ContentType::from_str("octet-stream").unwrap(),
2230 ContentType::OctetStream
2231 );
2232 assert_eq!(ContentType::from_str("json").unwrap(), ContentType::Json);
2233 assert_eq!(ContentType::Json.to_string(), "json");
2234 assert_eq!(
2235 ContentLanguage::from_str("en").unwrap(),
2236 ContentLanguage::En
2237 );
2238 }
2239
2240 #[test]
2243 fn test_known_meta_try_from_magic() {
2244 let cases: [(KnownMagic, KnownMeta); 13] = [
2245 (KnownMagic::OpMetaV1, KnownMeta::OpV1),
2246 (KnownMagic::DotrainV1, KnownMeta::DotrainV1),
2247 (KnownMagic::RainlangV1, KnownMeta::RainlangV1),
2248 (KnownMagic::SolidityAbiV2, KnownMeta::SolidityAbiV2),
2249 (KnownMagic::AuthoringMetaV1, KnownMeta::AuthoringMetaV1),
2250 (KnownMagic::AuthoringMetaV2, KnownMeta::AuthoringMetaV2),
2251 (KnownMagic::AddressList, KnownMeta::AddressList),
2252 (
2253 KnownMagic::InterpreterCallerMetaV1,
2254 KnownMeta::InterpreterCallerMetaV1,
2255 ),
2256 (
2257 KnownMagic::ExpressionDeployerV2BytecodeV1,
2258 KnownMeta::ExpressionDeployerV2BytecodeV1,
2259 ),
2260 (KnownMagic::RainlangSourceV1, KnownMeta::RainlangSourceV1),
2261 (KnownMagic::DotrainSourceV1, KnownMeta::DotrainSourceV1),
2262 (
2263 KnownMagic::OrderBuilderStateV1,
2264 KnownMeta::OrderBuilderStateV1,
2265 ),
2266 (
2267 KnownMagic::RaindexSignedContextOracleV1,
2268 KnownMeta::RaindexSignedContextOracleV1,
2269 ),
2270 ];
2271 for (magic, meta) in cases {
2272 assert_eq!(KnownMeta::try_from(magic).unwrap(), meta, "{:?}", magic);
2273 }
2274 for magic in [
2275 KnownMagic::RainMetaDocumentV1,
2276 KnownMagic::WebDataV1,
2277 KnownMagic::OaSchema,
2278 KnownMagic::OaHashList,
2279 KnownMagic::OaStructure,
2280 KnownMagic::OaTokenImage,
2281 KnownMagic::OaTokenCredentialLinks,
2282 ] {
2283 assert!(
2284 matches!(KnownMeta::try_from(magic), Err(Error::UnsupportedMeta)),
2285 "{:?}",
2286 magic
2287 );
2288 }
2289 }
2290
2291 #[test]
2294 fn test_known_meta_strum_parse_display() {
2295 use std::str::FromStr;
2296 assert_eq!(
2297 KnownMeta::from_str("solidity-abi-v2").unwrap(),
2298 KnownMeta::SolidityAbiV2
2299 );
2300 assert_eq!(
2301 KnownMeta::from_str("interpreter-caller-meta-v1").unwrap(),
2302 KnownMeta::InterpreterCallerMetaV1
2303 );
2304 assert_eq!(KnownMeta::SolidityAbiV2.to_string(), "solidity-abi-v2");
2305 }
2306
2307 #[tokio::test]
2309 async fn test_search_lowercases_hash() {
2310 use httpmock::prelude::*;
2311 let (_, doc) = sample_authoring_doc();
2312 let hash_upper = format!("0x{}", "AB".repeat(32));
2313 let server = MockServer::start();
2314 let mock = server.mock(|when, then| {
2315 when.method(POST)
2316 .body_contains(hash_upper.to_ascii_lowercase());
2317 then.status(200).json_body(json!({
2318 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2319 }));
2320 });
2321 let response = search(&hash_upper, &vec![server.url("/sg")]).await.unwrap();
2322 assert_eq!(response.bytes, doc);
2323 mock.assert();
2324 }
2325
2326 #[tokio::test]
2329 async fn test_search_first_success_wins() {
2330 use httpmock::prelude::*;
2331 let (_, doc) = sample_authoring_doc();
2332 let bad = MockServer::start();
2333 let _bad_mock = bad.mock(|when, then| {
2334 when.method(POST);
2335 then.status(500).body("subgraph down");
2336 });
2337 let good = MockServer::start();
2338 let _good_mock = good.mock(|when, then| {
2339 when.method(POST);
2340 then.status(200).json_body(json!({
2341 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2342 }));
2343 });
2344 let response = search(
2345 &format!("0x{}", "11".repeat(32)),
2346 &vec![bad.url("/sg"), good.url("/sg")],
2347 )
2348 .await
2349 .unwrap();
2350 assert_eq!(response.bytes, doc);
2351 }
2352
2353 #[tokio::test]
2357 async fn test_search_empty_subgraphs_is_a_miss() {
2358 let hash = format!("0x{}", "33".repeat(32));
2359 assert!(matches!(
2360 search(&hash, &vec![]).await,
2361 Err(Error::NoRecordFound)
2362 ));
2363 }
2364
2365 #[tokio::test]
2376 async fn test_implements_erc165_gate_short_circuits() {
2377 let address = Address::random();
2378
2379 let asserter = Asserter::new();
2381 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2382 asserter
2383 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2384 asserter
2385 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2386 assert!(!implements_i_described_by_meta_v1(&provider, address)
2387 .await
2388 .unwrap());
2389
2390 let asserter = Asserter::new();
2394 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2395 asserter.push_failure(ErrorPayload {
2396 code: -32000,
2397 message: "connection reset".into(),
2398 data: None,
2399 });
2400 asserter
2401 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2402 assert!(implements_i_described_by_meta_v1(&provider, address)
2403 .await
2404 .is_err());
2405 }
2406
2407 #[tokio::test]
2410 async fn test_implements_empty_response_is_false() {
2411 let address = Address::random();
2412 let asserter = Asserter::new();
2413 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2414 asserter
2415 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2416 asserter
2417 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2418 asserter.push_success(&"0x");
2419 assert!(!implements_i_described_by_meta_v1(&provider, address)
2420 .await
2421 .unwrap());
2422 }
2423
2424 #[tokio::test]
2428 async fn test_implements_described_by_call_error_is_unknown() {
2429 let address = Address::random();
2430 let asserter = Asserter::new();
2431 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2432 asserter
2433 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2434 asserter
2435 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2436 asserter.push_failure(ErrorPayload {
2437 code: -32005,
2438 message: "rate limit exceeded".into(),
2439 data: None,
2440 });
2441 let error = implements_i_described_by_meta_v1(&provider, address)
2442 .await
2443 .unwrap_err();
2444 assert!(error.to_string().contains("rate limit exceeded"));
2445 }
2446
2447 #[tokio::test]
2451 async fn test_implements_undecodable_response_is_unknown() {
2452 let address = Address::random();
2453 let asserter = Asserter::new();
2454 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2455 asserter
2456 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2457 asserter
2458 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2459 asserter.push_success(&"0xdeadbeef");
2460 implements_i_described_by_meta_v1(&provider, address)
2461 .await
2462 .unwrap_err();
2463 }
2464
2465 #[tokio::test]
2469 async fn test_store_constructors_inject_no_subgraphs() {
2470 assert!(Store::new().subgraphs().is_empty());
2471 assert!(Store::default().subgraphs().is_empty());
2472 assert!(
2473 Store::create(&vec![], &MetaCache::default(), &HashMap::new())
2474 .subgraphs()
2475 .is_empty()
2476 );
2477
2478 let hash = [0u8; 32];
2479 let mut store = Store::default();
2480 assert!(store.update(&hash).await.is_err());
2481 }
2482
2483 #[test]
2491 fn test_store_create_validates_entries() {
2492 let (_, doc) = sample_authoring_doc();
2493 let good_hash = keccak256(&doc).0.to_vec();
2494 let mut cache = MetaCache::default();
2495 cache.insert_verified(&good_hash, doc.clone()).unwrap();
2496 let mut dotrain_cache = HashMap::new();
2497 dotrain_cache.insert("a.rain".to_string(), good_hash.clone());
2498 dotrain_cache.insert("missing.rain".to_string(), vec![0x44u8; 32]);
2499
2500 let store = Store::create(
2501 &vec!["https://example.com/custom-sg".to_string()],
2502 &cache,
2503 &dotrain_cache,
2504 );
2505
2506 assert_eq!(
2507 store.subgraphs(),
2508 &vec!["https://example.com/custom-sg".to_string()]
2509 );
2510 assert_eq!(store.get_meta(&good_hash), Some(&doc));
2511 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&good_hash));
2512 assert_eq!(store.get_dotrain_hash("missing.rain"), None);
2513 }
2514
2515 #[test]
2517 fn test_store_add_subgraphs_dedupe() {
2518 let mut store = Store::new();
2519 store.add_subgraphs(&vec!["sg-a".to_string()]);
2520 store.add_subgraphs(&vec!["sg-a".to_string(), "sg-b".to_string()]);
2521 assert_eq!(
2522 store.subgraphs(),
2523 &vec!["sg-a".to_string(), "sg-b".to_string()]
2524 );
2525 }
2526
2527 #[test]
2531 fn test_store_dotrain_getters_and_set_fresh() {
2532 let mut store = Store::new();
2533 let text = "some dotrain content";
2534 let (hash, old) = store.set_dotrain(text, "file.rain", false).unwrap();
2535 assert!(old.is_empty());
2536 let expected_item = RainMetaDocumentV1Item {
2537 payload: serde_bytes::ByteBuf::from(text.as_bytes()),
2538 magic: KnownMagic::DotrainV1,
2539 content_type: ContentType::OctetStream,
2540 content_encoding: ContentEncoding::None,
2541 content_language: ContentLanguage::None,
2542 schema: None,
2543 };
2544 let expected_bytes = expected_item.cbor_encode().unwrap();
2545 assert_eq!(hash, keccak256(&expected_bytes).0.to_vec());
2546 assert_eq!(store.get_dotrain_hash("file.rain"), Some(&hash));
2547 assert_eq!(store.get_dotrain_uri(&hash), Some(&"file.rain".to_string()));
2548 assert_eq!(store.get_dotrain_meta("file.rain"), Some(&expected_bytes));
2549 assert_eq!(store.get_dotrain_hash("other.rain"), None);
2550 assert_eq!(store.get_dotrain_uri(&[0u8; 32]), None);
2551 assert_eq!(store.get_dotrain_meta("other.rain"), None);
2552 }
2553
2554 #[test]
2558 fn test_store_set_dotrain_branches() {
2559 let mut store = Store::new();
2560 let (hash_one, _) = store.set_dotrain("text one", "a.rain", false).unwrap();
2561
2562 let (hash_same, old_same) = store.set_dotrain("text one", "a.rain", false).unwrap();
2564 assert_eq!(hash_same, hash_one);
2565 assert!(old_same.is_empty());
2566 assert!(store.get_meta(&hash_one).is_some());
2567
2568 let (hash_two, old_two) = store.set_dotrain("text two", "a.rain", false).unwrap();
2570 assert_ne!(hash_two, hash_one);
2571 assert_eq!(old_two, hash_one);
2572 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_two));
2573 assert!(store.get_meta(&hash_one).is_none());
2574 assert!(store.get_meta(&hash_two).is_some());
2575
2576 let (hash_three, old_three) = store.set_dotrain("text three", "a.rain", true).unwrap();
2578 assert_eq!(old_three, hash_two);
2579 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_three));
2580 assert!(store.get_meta(&hash_two).is_some());
2581 assert!(store.get_meta(&hash_three).is_some());
2582 }
2583
2584 #[test]
2587 fn test_store_delete_dotrain_keep_meta() {
2588 let mut store = Store::new();
2589 let (hash, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2590 store.delete_dotrain("d.rain", false);
2591 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2592 assert!(store.get_meta(&hash).is_none());
2593
2594 let (hash_again, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2595 store.delete_dotrain("d.rain", true);
2596 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2597 assert!(store.get_meta(&hash_again).is_some());
2598 }
2599
2600 #[test]
2604 fn test_store_dotrain_shared_meta_survives_delete() {
2605 let mut store = Store::new();
2606 let (hash, _) = store.set_dotrain("same text", "a.rain", false).unwrap();
2607 let (hash_b, _) = store.set_dotrain("same text", "b.rain", false).unwrap();
2608 assert_eq!(hash_b, hash);
2609
2610 store.delete_dotrain("a.rain", false);
2611 assert_eq!(store.get_dotrain_hash("a.rain"), None);
2612 assert_eq!(store.get_dotrain_hash("b.rain"), Some(&hash));
2613 assert!(store.get_dotrain_meta("b.rain").is_some());
2614
2615 store.delete_dotrain("b.rain", false);
2616 assert!(store.get_meta(&hash).is_none());
2617 }
2618
2619 #[test]
2622 fn test_store_set_dotrain_keeps_shared_old_meta() {
2623 let mut store = Store::new();
2624 let (shared, _) = store.set_dotrain("same text", "a.rain", false).unwrap();
2625 store.set_dotrain("same text", "b.rain", false).unwrap();
2626
2627 let (new_hash, old_hash) = store.set_dotrain("other text", "a.rain", false).unwrap();
2628 assert_eq!(old_hash, shared);
2629 assert_ne!(new_hash, shared);
2630 assert!(store.get_dotrain_meta("b.rain").is_some());
2631
2632 store.set_dotrain("other text", "b.rain", false).unwrap();
2633 assert!(store.get_meta(&shared).is_none());
2634 }
2635
2636 #[test]
2639 fn test_store_merge_semantics() {
2640 let mut ours = Store::new();
2641 let mut theirs = Store::new();
2642
2643 let mine = b"mine".to_vec();
2647 let yours = b"yours".to_vec();
2648 let mine_hash = keccak256(&mine).0.to_vec();
2649 let yours_hash = keccak256(&yours).0.to_vec();
2650 ours.update_with(&mine_hash, &mine).unwrap();
2651 theirs.update_with(&yours_hash, &yours).unwrap();
2652
2653 let (hash_ours, _) = ours.set_dotrain("content a", "x.rain", false).unwrap();
2655 let (_hash_theirs, _) = theirs.set_dotrain("content b", "x.rain", false).unwrap();
2656
2657 theirs.add_subgraphs(&vec!["sg-their".to_string()]);
2658
2659 ours.merge(&theirs);
2660
2661 assert_eq!(ours.get_meta(&mine_hash), Some(&mine));
2662 assert_eq!(ours.get_meta(&yours_hash), Some(&yours));
2663 assert_eq!(ours.get_dotrain_hash("x.rain"), Some(&hash_ours));
2665 assert!(ours.subgraphs().contains(&"sg-their".to_string()));
2667 }
2668
2669 #[tokio::test]
2673 async fn test_store_update_and_update_check() {
2674 use httpmock::prelude::*;
2675 let authoring_meta: AuthoringMeta = serde_json::from_str(
2676 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
2677 )
2678 .unwrap();
2679 let item_one = RainMetaDocumentV1Item {
2680 payload: serde_bytes::ByteBuf::from(authoring_meta.abi_encode_validate().unwrap()),
2681 magic: KnownMagic::AuthoringMetaV1,
2682 content_type: ContentType::Cbor,
2683 content_encoding: ContentEncoding::None,
2684 content_language: ContentLanguage::None,
2685 schema: None,
2686 };
2687 let item_two = sample_dotrain_item();
2688 let doc = RainMetaDocumentV1Item::cbor_encode_seq(
2689 &vec![item_one.clone(), item_two.clone()],
2690 KnownMagic::RainMetaDocumentV1,
2691 )
2692 .unwrap();
2693 let requested = keccak256(&doc).0.to_vec();
2694 let server = MockServer::start();
2695 let _mock = server.mock(|when, then| {
2696 when.method(POST);
2697 then.status(200).json_body(json!({
2698 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2699 }));
2700 });
2701 let mut store = Store::new();
2702 store.add_subgraphs(&vec![server.url("/sg")]);
2703 let fetched = store.update(&requested).await.cloned().unwrap();
2704 assert_eq!(fetched, doc);
2705 assert_eq!(store.get_meta(&requested), Some(&doc));
2706 let inner_one = item_one.cbor_encode().unwrap();
2707 let inner_two = item_two.cbor_encode().unwrap();
2708 assert_eq!(
2709 store.get_meta(keccak256(&inner_one).0.as_ref()),
2710 Some(&inner_one)
2711 );
2712 assert_eq!(
2713 store.get_meta(keccak256(&inner_two).0.as_ref()),
2714 Some(&inner_two)
2715 );
2716
2717 let mut cached_store = Store::new();
2719 let bytes = b"standalone meta bytes".to_vec();
2720 let hash = keccak256(&bytes).0.to_vec();
2721 assert!(cached_store.update_with(&hash, &bytes).is_ok());
2722 assert_eq!(cached_store.update_check(&hash).await.unwrap(), &bytes);
2723 }
2724
2725 #[tokio::test]
2729 async fn test_store_update_rejects_hash_mismatch() {
2730 use httpmock::prelude::*;
2731 let (_, doc) = sample_authoring_doc();
2732 let requested = keccak256(b"the real content").0.to_vec();
2733 let server = MockServer::start();
2734 let _mock = server.mock(|when, then| {
2735 when.method(POST);
2736 then.status(200).json_body(json!({
2737 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2738 }));
2739 });
2740 let mut store = Store::new();
2741 store.add_subgraphs(&vec![server.url("/sg")]);
2742 assert!(store.update(&requested).await.is_err());
2743 assert!(store.get_meta(&requested).is_none());
2744 assert!(store.cache().is_empty());
2745 assert!(store.update_check(&requested).await.is_err());
2747 }
2748
2749 #[tokio::test]
2752 async fn test_store_no_subgraphs_lookups_return_none() {
2753 let hash = [0u8; 32];
2754 let mut store = Store::new();
2755 assert!(store.update(&hash).await.is_err());
2756 assert!(store.update_check(&hash).await.is_err());
2757 assert!(store.cache().is_empty());
2758 }
2759
2760 #[test]
2764 fn test_store_update_with_validation_and_content() {
2765 let mut store = Store::new();
2767 let bytes = b"payload bytes".to_vec();
2768 let wrong_hash = vec![0x99u8; 32];
2769 match store.update_with(&wrong_hash, &bytes).unwrap_err() {
2774 Error::CorruptRecord(message) => {
2775 assert!(
2776 message.contains(&hex::encode_prefixed(&wrong_hash)),
2777 "{}",
2778 message
2779 )
2780 }
2781 other => panic!("expected CorruptRecord, got {:?}", other),
2782 }
2783 assert!(store.get_meta(&wrong_hash).is_none());
2784 let hash = keccak256(&bytes).0.to_vec();
2786 assert_eq!(store.update_with(&hash, &bytes).unwrap(), &bytes);
2787
2788 let mut seeded = Store::new();
2795 let planted = b"planted value".to_vec();
2796 let planted_hash = keccak256(&planted).0.to_vec();
2797 seeded.update_with(&planted_hash, &planted).unwrap();
2798 assert_eq!(seeded.cache().len(), 1);
2799 assert_eq!(
2800 seeded.update_with(&planted_hash, &planted).unwrap(),
2801 &planted
2802 );
2803 assert_eq!(seeded.cache().len(), 1);
2804
2805 let (_, doc) = sample_authoring_doc();
2807 let doc_hash = keccak256(&doc).0.to_vec();
2808 let mut doc_store = Store::new();
2809 assert!(doc_store.update_with(&doc_hash, &doc).is_ok());
2810 let inner = doc[8..].to_vec();
2811 assert_eq!(store_inner_lookup(&doc_store, &inner), Some(inner.clone()));
2812
2813 let item_a = sample_dotrain_item().cbor_encode().unwrap();
2815 let (_, doc_b) = sample_authoring_doc();
2816 let item_b = doc_b[8..].to_vec();
2817 let seq = [item_a.clone(), item_b].concat();
2818 let seq_hash = keccak256(&seq).0.to_vec();
2819 let mut seq_store = Store::new();
2820 assert!(seq_store.update_with(&seq_hash, &seq).is_ok());
2821 assert_eq!(store_inner_lookup(&seq_store, &item_a), None);
2822 }
2823
2824 fn store_inner_lookup(store: &Store, inner_encoded: &[u8]) -> Option<Vec<u8>> {
2825 store.get_meta(keccak256(inner_encoded).0.as_ref()).cloned()
2826 }
2827
2828 #[test]
2831 fn test_bytes32_to_str_invalid_utf8() {
2832 let mut bytes = [0u8; 32];
2833 bytes[0] = 0xf0;
2834 bytes[1] = 0x28;
2835 bytes[2] = 0x8c;
2836 bytes[3] = 0x28;
2837 assert!(matches!(bytes32_to_str(&bytes), Err(Error::Utf8Error(_))));
2838 let no_nul = [0xffu8; 32];
2839 assert!(matches!(bytes32_to_str(&no_nul), Err(Error::Utf8Error(_))));
2840 }
2841}