1use super::error::Error;
2
3pub mod cache;
4use cache::{DeployerCache, 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::{collections::HashMap, convert::TryFrom, fmt::Debug, sync::Arc};
13use strum::{EnumIter, EnumString};
14use types::authoring::v1::AuthoringMeta;
15use alloy::sol_types::private::Address;
16use alloy::providers::Provider;
17use alloy::rpc::types::TransactionRequest;
18use alloy::sol_types::SolCall;
19use rain_erc::erc165::{IERC165, XorSelectors, supports_erc165};
20
21pub mod magic;
22pub(crate) mod normalize;
23pub(crate) mod query;
24pub mod types;
25
26pub use magic::*;
27pub use query::*;
28
29#[derive(Copy, Clone, EnumString, EnumIter, strum::Display, Debug, PartialEq)]
31#[strum(serialize_all = "kebab-case")]
32pub enum KnownMeta {
33 OpV1,
40 DotrainV1,
41 RainlangV1,
42 SolidityAbiV2,
43 AuthoringMetaV1,
44 AuthoringMetaV2,
45 InterpreterCallerMetaV1,
46 ExpressionDeployerV2BytecodeV1,
47 RainlangSourceV1,
48 AddressList,
49 DotrainSourceV1,
50 OrderBuilderStateV1,
51 RaindexSignedContextOracleV1,
52}
53
54impl TryFrom<KnownMagic> for KnownMeta {
55 type Error = Error;
56 fn try_from(value: KnownMagic) -> Result<Self, Self::Error> {
57 match value {
58 KnownMagic::DotrainV1 => Ok(KnownMeta::DotrainV1),
59 KnownMagic::RainlangV1 => Ok(KnownMeta::RainlangV1),
60 KnownMagic::SolidityAbiV2 => Ok(KnownMeta::SolidityAbiV2),
61 KnownMagic::OpMetaV1 => Ok(KnownMeta::OpV1),
62 KnownMagic::AuthoringMetaV1 => Ok(KnownMeta::AuthoringMetaV1),
63 KnownMagic::AuthoringMetaV2 => Ok(KnownMeta::AuthoringMetaV2),
64 KnownMagic::AddressList => Ok(KnownMeta::AddressList),
65 KnownMagic::InterpreterCallerMetaV1 => Ok(KnownMeta::InterpreterCallerMetaV1),
66 KnownMagic::DotrainSourceV1 => Ok(KnownMeta::DotrainSourceV1),
67 KnownMagic::OrderBuilderStateV1 => Ok(KnownMeta::OrderBuilderStateV1),
68 KnownMagic::ExpressionDeployerV2BytecodeV1 => {
69 Ok(KnownMeta::ExpressionDeployerV2BytecodeV1)
70 }
71 KnownMagic::RainlangSourceV1 => Ok(KnownMeta::RainlangSourceV1),
72 KnownMagic::RaindexSignedContextOracleV1 => Ok(KnownMeta::RaindexSignedContextOracleV1),
73 _ => Err(Error::UnsupportedMeta),
74 }
75 }
76}
77
78#[derive(
80 Copy,
81 Clone,
82 Debug,
83 EnumIter,
84 PartialEq,
85 EnumString,
86 strum::Display,
87 serde::Serialize,
88 serde::Deserialize,
89)]
90#[strum(serialize_all = "kebab-case")]
91pub enum ContentType {
92 None,
93 #[serde(rename = "application/json")]
94 Json,
95 #[serde(rename = "application/cbor")]
96 Cbor,
97 #[serde(rename = "application/octet-stream")]
98 OctetStream,
99}
100
101#[derive(
103 Copy,
104 Clone,
105 Debug,
106 EnumIter,
107 PartialEq,
108 EnumString,
109 strum::Display,
110 serde::Serialize,
111 serde::Deserialize,
112)]
113#[serde(rename_all = "kebab-case")]
114#[strum(serialize_all = "kebab-case")]
115pub enum ContentEncoding {
116 None,
117 Identity,
118 Deflate,
119}
120
121impl ContentEncoding {
122 pub fn encode(&self, data: &[u8]) -> Vec<u8> {
124 match self {
125 ContentEncoding::None | ContentEncoding::Identity => data.to_vec(),
126 ContentEncoding::Deflate => deflate::deflate_bytes_zlib(data),
127 }
128 }
129
130 pub fn decode(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
132 Ok(match self {
133 ContentEncoding::None | ContentEncoding::Identity => data.to_vec(),
134 ContentEncoding::Deflate => match inflate::inflate_bytes_zlib(data) {
135 Ok(v) => v,
136 Err(error) => match inflate::inflate_bytes(data) {
137 Ok(v) => v,
138 Err(_) => Err(Error::InflateError(error))?,
139 },
140 },
141 })
142 }
143}
144
145#[derive(
147 Copy,
148 Clone,
149 Debug,
150 EnumIter,
151 PartialEq,
152 EnumString,
153 strum::Display,
154 serde::Serialize,
155 serde::Deserialize,
156)]
157#[serde(rename_all = "kebab-case")]
158#[strum(serialize_all = "kebab-case")]
159pub enum ContentLanguage {
160 None,
161 En,
162}
163
164#[derive(PartialEq, Debug, Clone)]
168pub struct RainMetaDocumentV1Item {
169 pub payload: serde_bytes::ByteBuf,
170 pub magic: KnownMagic,
171 pub content_type: ContentType,
172 pub content_encoding: ContentEncoding,
173 pub content_language: ContentLanguage,
174 pub schema: Option<String>,
178}
179
180impl TryFrom<RainMetaDocumentV1Item> for String {
182 type Error = Error;
183 fn try_from(value: RainMetaDocumentV1Item) -> Result<Self, Self::Error> {
184 Ok(String::from_utf8(value.unpack()?)?)
185 }
186}
187
188impl TryFrom<RainMetaDocumentV1Item> for Vec<u8> {
190 type Error = Error;
191 fn try_from(value: RainMetaDocumentV1Item) -> Result<Self, Self::Error> {
192 value.unpack()
193 }
194}
195
196impl RainMetaDocumentV1Item {
197 fn len(&self) -> usize {
198 let mut l = 2;
199 if !matches!(self.content_type, ContentType::None) {
200 l += 1;
201 }
202 if !matches!(self.content_encoding, ContentEncoding::None) {
203 l += 1;
204 }
205 if !matches!(self.content_language, ContentLanguage::None) {
206 l += 1;
207 }
208 if self.schema.is_some() {
209 l += 1;
210 }
211 l
212 }
213
214 pub fn hash(&self, as_rain_meta_document: bool) -> Result<[u8; 32], Error> {
216 if as_rain_meta_document {
217 Ok(keccak256(Self::cbor_encode_seq(
218 &vec![self.clone()],
219 KnownMagic::RainMetaDocumentV1,
220 )?)
221 .0)
222 } else {
223 Ok(keccak256(self.cbor_encode()?).0)
224 }
225 }
226
227 pub fn cbor_encode(&self) -> Result<Vec<u8>, Error> {
229 let mut bytes: Vec<u8> = vec![];
230 Ok(serde_cbor::to_writer(&mut bytes, &self).map(|_| bytes)?)
231 }
232
233 pub fn cbor_encode_seq(
235 seq: &Vec<RainMetaDocumentV1Item>,
236 magic: KnownMagic,
237 ) -> Result<Vec<u8>, Error> {
238 let mut bytes: Vec<u8> = magic.to_prefix_bytes().to_vec();
239 for item in seq {
240 serde_cbor::to_writer(&mut bytes, &item)?;
241 }
242 Ok(bytes)
243 }
244
245 pub fn cbor_decode(data: &[u8]) -> Result<Vec<RainMetaDocumentV1Item>, Error> {
247 let mut metas: Vec<RainMetaDocumentV1Item> = vec![];
248 let mut consumed: usize = 0;
249 let mut is_rain_document_meta = false;
250 let mut len = data.len();
251 if data.starts_with(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes()) {
252 is_rain_document_meta = true;
253 len -= 8;
254 }
255 let mut deserializer = match is_rain_document_meta {
256 true => serde_cbor::Deserializer::from_slice(&data[8..]),
257 false => serde_cbor::Deserializer::from_slice(data),
258 };
259 while match serde_cbor::Value::deserialize(&mut deserializer) {
260 Ok(cbor_map) => {
261 consumed = deserializer.byte_offset();
262 match serde_cbor::value::from_value(cbor_map) {
263 Ok(meta) => metas.push(meta),
264 Err(error) => Err(Error::SerdeCborError(error))?,
265 };
266 true
267 }
268 Err(error) => {
269 if error.is_eof() {
270 false
271 } else {
272 Err(Error::SerdeCborError(error))?
273 }
274 }
275 } {}
276
277 if metas.is_empty() || len != consumed {
278 Err(Error::CorruptMeta)?
279 }
280 Ok(metas)
281 }
282
283 pub fn unpack(&self) -> Result<Vec<u8>, Error> {
285 ContentEncoding::decode(&self.content_encoding, self.payload.as_ref())
286 }
287
288 pub fn unpack_into<T: TryFrom<Self, Error = Error>>(self) -> Result<T, Error> {
290 match self.magic {
291 KnownMagic::OpMetaV1
292 | KnownMagic::DotrainV1
293 | KnownMagic::RainlangV1
294 | KnownMagic::SolidityAbiV2
295 | KnownMagic::AuthoringMetaV1
296 | KnownMagic::AuthoringMetaV2
297 | KnownMagic::AddressList
298 | KnownMagic::InterpreterCallerMetaV1
299 | KnownMagic::ExpressionDeployerV2BytecodeV1
300 | KnownMagic::DotrainSourceV1
301 | KnownMagic::OrderBuilderStateV1
302 | KnownMagic::RainlangSourceV1
303 | KnownMagic::RaindexSignedContextOracleV1 => T::try_from(self),
304 _ => Err(Error::UnsupportedMeta)?,
305 }
306 }
307}
308
309impl Serialize for RainMetaDocumentV1Item {
310 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
311 let mut map = serializer.serialize_map(Some(self.len()))?;
312 map.serialize_entry(&0, &self.payload)?;
313 map.serialize_entry(&1, &(self.magic as u64))?;
314 match self.content_type {
315 ContentType::None => {}
316 content_type => map.serialize_entry(&2, &content_type)?,
317 }
318 match self.content_encoding {
319 ContentEncoding::None => {}
320 content_encoding => map.serialize_entry(&3, &content_encoding)?,
321 }
322 match self.content_language {
323 ContentLanguage::None => {}
324 content_language => map.serialize_entry(&4, &content_language)?,
325 }
326 if let Some(schema) = &self.schema {
327 map.serialize_entry(&(KnownMagic::OaSchema as u64), schema)?;
328 }
329 map.end()
330 }
331}
332
333impl<'de> Deserialize<'de> for RainMetaDocumentV1Item {
334 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
335 struct EncodedMap;
336 impl<'de> Visitor<'de> for EncodedMap {
337 type Value = RainMetaDocumentV1Item;
338
339 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
340 formatter.write_str("rain meta cbor encoded bytes")
341 }
342
343 fn visit_map<T: serde::de::MapAccess<'de>>(
344 self,
345 mut map: T,
346 ) -> Result<Self::Value, T::Error> {
347 const OA_SCHEMA_KEY: u64 = KnownMagic::OaSchema as u64;
348 let mut payload = None;
349 let mut magic: Option<u64> = None;
350 let mut content_type = None;
351 let mut content_encoding = None;
352 let mut content_language = None;
353 let mut schema = None;
354 while match map.next_key::<u64>() {
355 Ok(Some(key)) => {
356 match key {
357 0 => payload = Some(map.next_value()?),
358 1 => magic = Some(map.next_value()?),
359 2 => content_type = Some(map.next_value()?),
360 3 => content_encoding = Some(map.next_value()?),
361 4 => content_language = Some(map.next_value()?),
362 OA_SCHEMA_KEY => schema = Some(map.next_value()?),
363 _ => {
368 map.next_value::<serde::de::IgnoredAny>()?;
369 }
370 };
371 true
372 }
373 Ok(None) => false,
374 Err(error) => Err(error)?,
375 } {}
376 let payload = payload.ok_or_else(|| serde::de::Error::missing_field("payload"))?;
377 let magic = match magic
378 .ok_or_else(|| serde::de::Error::missing_field("magic number"))?
379 .try_into()
380 {
381 Ok(m) => m,
382 _ => Err(serde::de::Error::custom("unknown magic number"))?,
383 };
384 let content_type = content_type.unwrap_or(ContentType::None);
385 let content_encoding = content_encoding.unwrap_or(ContentEncoding::None);
386 let content_language = content_language.unwrap_or(ContentLanguage::None);
387
388 Ok(RainMetaDocumentV1Item {
389 payload,
390 magic,
391 content_type,
392 content_encoding,
393 content_language,
394 schema,
395 })
396 }
397 }
398 deserializer.deserialize_map(EncodedMap)
399 }
400}
401
402pub async fn search(hash: &str, subgraphs: &Vec<String>) -> Result<query::MetaResponse, Error> {
404 if subgraphs.is_empty() {
406 return Err(Error::NoRecordFound);
407 }
408 let request_body = query::MetaQuery::build_query(query::meta_query::Variables {
409 hash: Some(hash.to_ascii_lowercase()),
410 });
411 let mut promises = vec![];
412
413 let client = Arc::new(Client::builder().build().map_err(Error::ReqwestError)?);
414 for url in subgraphs {
415 promises.push(Box::pin(query::process_meta_query(
416 client.clone(),
417 &request_body,
418 url,
419 )));
420 }
421 let response_value = future::select_ok(promises.drain(..)).await?.0;
422 Ok(response_value)
423}
424
425pub async fn search_deployer(
427 hash: &str,
428 subgraphs: &Vec<String>,
429) -> Result<DeployerResponse, Error> {
430 if subgraphs.is_empty() {
432 return Err(Error::NoRecordFound);
433 }
434 let request_body = query::DeployerQuery::build_query(query::deployer_query::Variables {
435 hash: Some(hash.to_ascii_lowercase()),
436 });
437 let mut promises = vec![];
438
439 let client = Arc::new(Client::builder().build().map_err(Error::ReqwestError)?);
440 for url in subgraphs {
441 promises.push(Box::pin(query::process_deployer_query(
442 client.clone(),
443 &request_body,
444 url,
445 )));
446 }
447 let response_value = future::select_ok(promises.drain(..)).await?.0;
448 Ok(response_value)
449}
450
451pub async fn implements_i_described_by_meta_v1<P: Provider>(
453 provider: &P,
454 contract_address: Address,
455) -> bool {
456 if !supports_erc165(provider, contract_address)
457 .await
458 .unwrap_or(false)
459 {
460 return false;
461 }
462
463 let interface_id_res = IDescribedByMetaV1::IDescribedByMetaV1Calls::xor_selectors();
464 if interface_id_res.is_err() {
465 return false;
466 }
467
468 let call = IERC165::supportsInterfaceCall {
469 interfaceID: interface_id_res.unwrap().into(),
470 };
471 let tx = TransactionRequest::default()
472 .to(contract_address)
473 .input(call.abi_encode().into());
474 match provider.call(tx).await {
475 Ok(bytes) => IERC165::supportsInterfaceCall::abi_decode_returns(&bytes).unwrap_or(false),
476 Err(_) => false,
477 }
478}
479
480#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Default)]
482#[serde(rename_all = "camelCase")]
483pub struct NPE2Deployer {
484 #[serde(with = "serde_bytes")]
486 pub meta_hash: Vec<u8>,
487 #[serde(with = "serde_bytes")]
489 pub meta_bytes: Vec<u8>,
490 #[serde(with = "serde_bytes")]
492 pub bytecode: Vec<u8>,
493 #[serde(with = "serde_bytes")]
495 pub parser: Vec<u8>,
496 #[serde(with = "serde_bytes")]
498 pub store: Vec<u8>,
499 #[serde(with = "serde_bytes")]
501 pub interpreter: Vec<u8>,
502 pub authoring_meta: Option<AuthoringMeta>,
504}
505
506impl NPE2Deployer {
507 pub fn is_corrupt(&self) -> bool {
508 if self.meta_hash.is_empty() {
509 return true;
510 }
511 if self.meta_bytes.is_empty() {
512 return true;
513 }
514 if self.bytecode.is_empty() {
515 return true;
516 }
517 if self.parser.is_empty() {
518 return true;
519 }
520 if self.store.is_empty() {
521 return true;
522 }
523 if self.interpreter.is_empty() {
524 return true;
525 }
526 false
527 }
528}
529
530#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
591pub struct Store {
592 subgraphs: Vec<String>,
593 cache: MetaCache,
594 dotrain_cache: HashMap<String, Vec<u8>>,
595 deployer_cache: DeployerCache,
596 deployer_hash_map: HashMap<Vec<u8>, Vec<u8>>,
597}
598
599impl Default for Store {
600 fn default() -> Self {
601 Store::new()
602 }
603}
604
605impl Store {
606 pub fn new() -> Store {
609 Store {
610 subgraphs: vec![],
611 cache: MetaCache::default(),
612 dotrain_cache: HashMap::new(),
613 deployer_cache: DeployerCache::default(),
614 deployer_hash_map: HashMap::new(),
615 }
616 }
617
618 pub fn create(
621 subgraphs: &Vec<String>,
622 cache: &MetaCache,
623 deployer_cache: &DeployerCache,
624 dotrain_cache: &HashMap<String, Vec<u8>>,
625 ) -> Store {
626 let mut store = Store::new();
627 store.add_subgraphs(subgraphs);
628 for (hash, bytes) in cache.iter() {
629 let _ = store.update_with(hash, bytes);
630 }
631 for (hash, deployer) in deployer_cache.iter() {
632 let _ = store.set_deployer(hash, deployer, None);
633 }
634 for (uri, hash) in dotrain_cache {
635 if !store.dotrain_cache.contains_key(uri) && store.cache.contains_key(hash) {
636 store.dotrain_cache.insert(uri.clone(), hash.clone());
637 }
638 }
639 store
640 }
641
642 pub fn subgraphs(&self) -> &Vec<String> {
644 &self.subgraphs
645 }
646
647 pub fn add_subgraphs(&mut self, subgraphs: &Vec<String>) {
649 for sg in subgraphs {
650 if !self.subgraphs.contains(sg) {
651 self.subgraphs.push(sg.to_string());
652 }
653 }
654 }
655
656 pub fn cache(&self) -> &MetaCache {
658 &self.cache
659 }
660
661 pub fn get_meta(&self, hash: &[u8]) -> Option<&Vec<u8>> {
663 self.cache.get(hash)
664 }
665
666 pub fn deployer_cache(&self) -> &DeployerCache {
668 &self.deployer_cache
669 }
670
671 pub fn get_deployer(&self, hash: &[u8]) -> Option<&NPE2Deployer> {
673 if self.deployer_cache.contains_key(hash) {
674 self.deployer_cache.get(hash)
675 } else if let Some(h) = self.deployer_hash_map.get(hash) {
676 self.deployer_cache.get(h)
677 } else {
678 None
679 }
680 }
681
682 pub async fn search_deployer(&mut self, hash: &[u8]) -> Result<&NPE2Deployer, Error> {
687 match search_deployer(&hex::encode_prefixed(hash), &self.subgraphs).await {
688 Ok(res) => {
689 self.insert_verified(&res.meta_hash.clone(), res.meta_bytes.clone())?;
690 let authoring_meta = res.get_authoring_meta();
691 self.deployer_cache.insert_verified(
692 &res.bytecode_meta_hash.clone(),
693 NPE2Deployer {
694 meta_hash: res.meta_hash.clone(),
695 meta_bytes: res.meta_bytes,
696 bytecode: res.bytecode,
697 parser: res.parser,
698 store: res.store,
699 interpreter: res.interpreter,
700 authoring_meta,
701 },
702 )?;
703 self.deployer_hash_map.insert(res.tx_hash, res.meta_hash);
704 self.deployer_cache.get(hash).ok_or(Error::NoRecordFound)
705 }
706 Err(e) => Err(e),
707 }
708 }
709
710 pub async fn search_deployer_check(&mut self, hash: &[u8]) -> Result<&NPE2Deployer, Error> {
713 if self.deployer_cache.contains_key(hash) {
714 self.get_deployer(hash).ok_or(Error::NoRecordFound)
715 } else if self.deployer_hash_map.contains_key(hash) {
716 let b_hash = self.deployer_hash_map.get(hash).unwrap().clone();
717 self.get_deployer(&b_hash).ok_or(Error::NoRecordFound)
718 } else {
719 self.search_deployer(hash).await
720 }
721 }
722
723 pub fn set_deployer_from_query_response(
725 &mut self,
726 deployer_query_response: DeployerResponse,
727 ) -> Result<NPE2Deployer, Error> {
728 let authoring_meta = deployer_query_response.get_authoring_meta();
729 let tx_hash = deployer_query_response.tx_hash;
730 let bytecode_meta_hash = deployer_query_response.bytecode_meta_hash;
731 let result = NPE2Deployer {
732 meta_hash: deployer_query_response.meta_hash.clone(),
733 meta_bytes: deployer_query_response.meta_bytes,
734 bytecode: deployer_query_response.bytecode,
735 parser: deployer_query_response.parser,
736 store: deployer_query_response.store,
737 interpreter: deployer_query_response.interpreter,
738 authoring_meta,
739 };
740 self.cache.insert_verified(
741 &deployer_query_response.meta_hash,
742 result.meta_bytes.clone(),
743 )?;
744 self.deployer_hash_map
745 .insert(tx_hash, bytecode_meta_hash.clone());
746 self.deployer_cache
747 .insert_verified(&bytecode_meta_hash, result.clone())?;
748 Ok(result)
749 }
750
751 pub fn set_deployer(
754 &mut self,
755 hash: &[u8],
756 npe2_deployer: &NPE2Deployer,
757 tx_hash: Option<&[u8]>,
758 ) -> Result<(), Error> {
759 self.cache
760 .insert_verified(&npe2_deployer.meta_hash, npe2_deployer.meta_bytes.clone())?;
761 self.deployer_cache
762 .insert_verified(hash, npe2_deployer.clone())?;
763 if let Some(v) = tx_hash {
764 self.deployer_hash_map.insert(v.to_vec(), hash.to_vec());
765 }
766 Ok(())
767 }
768
769 pub fn dotrain_cache(&self) -> &HashMap<String, Vec<u8>> {
771 &self.dotrain_cache
772 }
773
774 pub fn get_dotrain_hash(&self, uri: &str) -> Option<&Vec<u8>> {
776 self.dotrain_cache.get(uri)
777 }
778
779 pub fn get_dotrain_uri(&self, hash: &[u8]) -> Option<&String> {
781 for (uri, h) in &self.dotrain_cache {
782 if h == hash {
783 return Some(uri);
784 }
785 }
786 None
787 }
788
789 pub fn get_dotrain_meta(&self, uri: &str) -> Option<&Vec<u8>> {
791 self.get_meta(self.dotrain_cache.get(uri)?)
792 }
793
794 pub fn delete_dotrain(&mut self, uri: &str, keep_meta: bool) {
796 if let Some(kv) = self.dotrain_cache.remove_entry(uri) {
797 if !keep_meta {
798 self.cache.remove(&kv.1);
799 }
800 };
801 }
802
803 pub fn merge(&mut self, other: &Store) {
806 self.add_subgraphs(&other.subgraphs);
807 for (hash, bytes) in other.cache.iter() {
808 if !self.cache.contains_key(hash) {
809 let _ = self.cache.insert_verified(hash, bytes.clone());
812 }
813 }
814 for (hash, deployer) in other.deployer_cache.iter() {
815 if !self.deployer_cache.contains_key(hash) {
816 let _ = self.deployer_cache.insert_verified(hash, deployer.clone());
818 }
819 }
820 for (tx_hash, hash) in &other.deployer_hash_map {
821 if !self.deployer_hash_map.contains_key(tx_hash) {
822 self.deployer_hash_map.insert(tx_hash.clone(), hash.clone());
823 }
824 }
825 for (uri, hash) in &other.dotrain_cache {
826 if !self.dotrain_cache.contains_key(uri) {
827 self.dotrain_cache.insert(uri.clone(), hash.clone());
828 }
829 }
830 }
831
832 fn insert_verified(&mut self, hash: &[u8], bytes: Vec<u8>) -> Result<&Vec<u8>, Error> {
836 self.cache.insert_verified(hash, bytes.clone())?;
837 self.store_content(&bytes);
838 self.get_meta(hash).ok_or(Error::NoRecordFound)
839 }
840
841 pub async fn update(&mut self, hash: &[u8]) -> Result<&Vec<u8>, Error> {
846 let meta = search(&hex::encode_prefixed(hash), &self.subgraphs).await?;
847 self.insert_verified(hash, meta.bytes)
848 }
849
850 pub async fn update_check(&mut self, hash: &[u8]) -> Result<&Vec<u8>, Error> {
852 if self.cache.contains_key(hash) {
858 return self.get_meta(hash).ok_or(Error::NoRecordFound);
859 }
860 self.update(hash).await
861 }
862
863 pub fn update_with(&mut self, hash: &[u8], bytes: &[u8]) -> Result<&Vec<u8>, Error> {
867 if self.cache.contains_key(hash) {
873 return self.get_meta(hash).ok_or(Error::NoRecordFound);
874 }
875 self.insert_verified(hash, bytes.to_vec())
876 }
877
878 pub fn set_dotrain(
883 &mut self,
884 text: &str,
885 uri: &str,
886 keep_old: bool,
887 ) -> Result<(Vec<u8>, Vec<u8>), Error> {
888 let bytes = RainMetaDocumentV1Item {
889 payload: serde_bytes::ByteBuf::from(text.as_bytes()),
890 magic: KnownMagic::DotrainV1,
891 content_type: ContentType::OctetStream,
892 content_encoding: ContentEncoding::None,
893 content_language: ContentLanguage::None,
894 schema: None,
895 }
896 .cbor_encode()?;
897 let new_hash = keccak256(&bytes).0.to_vec();
898 if let Some(h) = self.dotrain_cache.get(uri) {
899 let old_hash = h.clone();
900 if new_hash == old_hash {
901 self.cache.insert_verified(&new_hash, bytes)?;
902 Ok((new_hash, vec![]))
903 } else {
904 self.cache.insert_verified(&new_hash, bytes)?;
905 self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
906 if !keep_old {
907 self.cache.remove(&old_hash);
908 }
909 Ok((new_hash, old_hash))
910 }
911 } else {
912 self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
913 self.cache.insert_verified(&new_hash, bytes)?;
914 Ok((new_hash, vec![]))
915 }
916 }
917
918 fn store_content(&mut self, bytes: &[u8]) {
922 if let Ok(meta_maps) = RainMetaDocumentV1Item::cbor_decode(bytes) {
923 if bytes.starts_with(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes()) {
924 for meta_map in &meta_maps {
925 if let Ok(encoded_bytes) = meta_map.cbor_encode() {
926 let _ = self
930 .cache
931 .insert_verified(&keccak256(&encoded_bytes).0, encoded_bytes);
932 }
933 }
934 }
935 }
936 }
937}
938
939pub fn str_to_bytes32(text: &str) -> Result<[u8; 32], Error> {
945 let bytes: &[u8] = text.as_bytes();
946 if bytes.len() > 32 {
947 return Err(Error::BiggerThan32Bytes);
948 }
949 if bytes.contains(&0u8) {
950 return Err(Error::NulByteInInput);
951 }
952 let mut b32 = [0u8; 32];
953 b32[..bytes.len()].copy_from_slice(bytes);
954 Ok(b32)
955}
956
957pub fn bytes32_to_str(bytes: &[u8; 32]) -> Result<&str, Error> {
959 let mut len = 32;
960 if let Some((pos, _)) = itertools::Itertools::find_position(&mut bytes.iter(), |b| **b == 0u8) {
961 len = pos;
962 };
963 Ok(std::str::from_utf8(&bytes[..len])?)
964}
965
966#[cfg(all(test, not(target_family = "wasm")))]
967mod tests {
968 use super::{
969 *, bytes32_to_str, magic::KnownMagic, str_to_bytes32, types::authoring::v1::AuthoringMeta,
970 ContentEncoding, ContentLanguage, ContentType, Error, RainMetaDocumentV1Item,
971 };
972 use alloy::providers::ProviderBuilder;
973 use alloy::{providers::mock::Asserter, rpc::json_rpc::ErrorPayload, sol_types::SolType};
974 use serde_json::json;
975
976 #[test]
979 fn authoring_meta_roundtrip() -> Result<(), Error> {
980 let authoring_meta_content = r#"[
981 {
982 "word": "stack",
983 "description": "Copies an existing value from the stack.",
984 "operandParserOffset": 16
985 },
986 {
987 "word": "constant",
988 "description": "Copies a constant value onto the stack.",
989 "operandParserOffset": 16
990 }
991 ]"#;
992 let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
993
994 let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
996 let expected_abi_encoded = <alloy::sol!((bytes32, uint8, string)[])>::abi_encode(&vec![
997 (
998 str_to_bytes32("stack")?,
999 16u8,
1000 "Copies an existing value from the stack.".to_string(),
1001 ),
1002 (
1003 str_to_bytes32("constant")?,
1004 16u8,
1005 "Copies a constant value onto the stack.".to_string(),
1006 ),
1007 ]);
1008 assert_eq!(authoring_meta_abi_encoded, expected_abi_encoded);
1010
1011 let meta_map = RainMetaDocumentV1Item {
1012 payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
1013 magic: KnownMagic::AuthoringMetaV1,
1014 content_type: ContentType::Cbor,
1015 content_encoding: ContentEncoding::None,
1016 content_language: ContentLanguage::None,
1017 schema: None,
1018 };
1019 let cbor_encoded = meta_map.cbor_encode()?;
1020
1021 assert_eq!(cbor_encoded[0], 0xa3);
1023 assert_eq!(cbor_encoded[1], 0x00);
1025 assert_eq!(cbor_encoded[2], 0b010_11001);
1027 assert_eq!(cbor_encoded[3], 0b000_00010);
1028 assert_eq!(cbor_encoded[4], 0b000_00000);
1029 assert_eq!(cbor_encoded[5..517], authoring_meta_abi_encoded);
1031 assert_eq!(cbor_encoded[517], 0x01);
1033 assert_eq!(cbor_encoded[518], 0b000_11011);
1035 assert_eq!(
1037 &cbor_encoded[519..527],
1038 KnownMagic::AuthoringMetaV1.to_prefix_bytes()
1039 );
1040 assert_eq!(cbor_encoded[527], 0x02);
1042 assert_eq!(cbor_encoded[528], 0b011_10000);
1044 assert_eq!(&cbor_encoded[529..], "application/cbor".as_bytes());
1046
1047 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1049 assert_eq!(cbor_decoded.len(), 1);
1051 assert_eq!(cbor_decoded[0], meta_map);
1053
1054 Ok(())
1055 }
1056
1057 #[test]
1060 fn dotrain_meta_roundtrip() -> Result<(), Error> {
1061 let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
1062 let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
1063
1064 let content_encoding = ContentEncoding::Deflate;
1065 let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
1066
1067 let meta_map = RainMetaDocumentV1Item {
1068 payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
1069 magic: KnownMagic::DotrainV1,
1070 content_type: ContentType::OctetStream,
1071 content_encoding,
1072 content_language: ContentLanguage::En,
1073 schema: None,
1074 };
1075 let cbor_encoded = meta_map.cbor_encode()?;
1076
1077 assert_eq!(cbor_encoded[0], 0xa5);
1079 assert_eq!(cbor_encoded[1], 0x00);
1081 assert_eq!(cbor_encoded[2], 0b010_11000);
1083 assert_eq!(cbor_encoded[3], 0b001_00100);
1084 assert_eq!(cbor_encoded[4..40], deflated_payload);
1087 assert_eq!(cbor_encoded[40], 0x01);
1089 assert_eq!(cbor_encoded[41], 0b000_11011);
1091 assert_eq!(
1093 &cbor_encoded[42..50],
1094 KnownMagic::DotrainV1.to_prefix_bytes()
1095 );
1096 assert_eq!(cbor_encoded[50], 0x02);
1098 assert_eq!(cbor_encoded[51], 0b011_11000);
1100 assert_eq!(cbor_encoded[52], 0b000_11000);
1101 assert_eq!(&cbor_encoded[53..77], "application/octet-stream".as_bytes());
1103 assert_eq!(cbor_encoded[77], 0x03);
1105 assert_eq!(cbor_encoded[78], 0b011_00111);
1107 assert_eq!(&cbor_encoded[79..86], "deflate".as_bytes());
1109 assert_eq!(cbor_encoded[86], 0x04);
1111 assert_eq!(cbor_encoded[87], 0b011_00010);
1113 assert_eq!(&cbor_encoded[88..], "en".as_bytes());
1115
1116 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1118 assert_eq!(cbor_decoded.len(), 1);
1120 assert_eq!(cbor_decoded[0], meta_map);
1122
1123 Ok(())
1124 }
1125
1126 #[test]
1129 fn meta_seq_roundtrip() -> Result<(), Error> {
1130 let authoring_meta_content = r#"[
1131 {
1132 "word": "stack",
1133 "description": "Copies an existing value from the stack.",
1134 "operandParserOffset": 16
1135 },
1136 {
1137 "word": "constant",
1138 "description": "Copies a constant value onto the stack.",
1139 "operandParserOffset": 16
1140 }
1141 ]"#;
1142 let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
1143 let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
1144 let meta_map_1 = RainMetaDocumentV1Item {
1145 payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
1146 magic: KnownMagic::AuthoringMetaV1,
1147 content_type: ContentType::Cbor,
1148 content_encoding: ContentEncoding::None,
1149 content_language: ContentLanguage::None,
1150 schema: None,
1151 };
1152
1153 let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
1154 let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
1155 let content_encoding = ContentEncoding::Deflate;
1156 let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
1157 let meta_map_2 = RainMetaDocumentV1Item {
1158 payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
1159 magic: KnownMagic::DotrainV1,
1160 content_type: ContentType::OctetStream,
1161 content_encoding,
1162 content_language: ContentLanguage::En,
1163 schema: None,
1164 };
1165
1166 let cbor_encoded = RainMetaDocumentV1Item::cbor_encode_seq(
1168 &vec![meta_map_1.clone(), meta_map_2.clone()],
1169 KnownMagic::RainMetaDocumentV1,
1170 )?;
1171
1172 assert_eq!(
1174 &cbor_encoded[0..8],
1175 KnownMagic::RainMetaDocumentV1.to_prefix_bytes()
1176 );
1177
1178 assert_eq!(cbor_encoded[8], 0xa3);
1181 assert_eq!(cbor_encoded[9], 0x00);
1183 assert_eq!(cbor_encoded[10], 0b010_11001);
1185 assert_eq!(cbor_encoded[11], 0b000_00010);
1186 assert_eq!(cbor_encoded[12], 0b000_00000);
1187 assert_eq!(cbor_encoded[13..525], authoring_meta_abi_encoded);
1189 assert_eq!(cbor_encoded[525], 0x01);
1191 assert_eq!(cbor_encoded[526], 0b000_11011);
1193 assert_eq!(
1195 &cbor_encoded[527..535],
1196 KnownMagic::AuthoringMetaV1.to_prefix_bytes()
1197 );
1198 assert_eq!(cbor_encoded[535], 0x02);
1200 assert_eq!(cbor_encoded[536], 0b011_10000);
1202 assert_eq!(&cbor_encoded[537..553], "application/cbor".as_bytes());
1204
1205 assert_eq!(cbor_encoded[553], 0xa5);
1208 assert_eq!(cbor_encoded[554], 0x00);
1210 assert_eq!(cbor_encoded[555], 0b010_11000);
1212 assert_eq!(cbor_encoded[556], 0b001_00100);
1213 assert_eq!(cbor_encoded[557..593], deflated_payload);
1216 assert_eq!(cbor_encoded[593], 0x01);
1218 assert_eq!(cbor_encoded[594], 0b000_11011);
1220 assert_eq!(
1222 &cbor_encoded[595..603],
1223 KnownMagic::DotrainV1.to_prefix_bytes()
1224 );
1225 assert_eq!(cbor_encoded[603], 0x02);
1227 assert_eq!(cbor_encoded[604], 0b011_11000);
1229 assert_eq!(cbor_encoded[605], 0b000_11000);
1230 assert_eq!(
1232 &cbor_encoded[606..630],
1233 "application/octet-stream".as_bytes()
1234 );
1235 assert_eq!(cbor_encoded[630], 0x03);
1237 assert_eq!(cbor_encoded[631], 0b011_00111);
1239 assert_eq!(&cbor_encoded[632..639], "deflate".as_bytes());
1241 assert_eq!(cbor_encoded[639], 0x04);
1243 assert_eq!(cbor_encoded[640], 0b011_00010);
1245 assert_eq!(&cbor_encoded[641..], "en".as_bytes());
1247
1248 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1250 assert_eq!(cbor_decoded.len(), 2);
1252
1253 assert_eq!(cbor_decoded[0], meta_map_1);
1255 assert_eq!(cbor_decoded[1], meta_map_2);
1257
1258 Ok(())
1259 }
1260
1261 #[test]
1262 fn test_bytes32_to_str() {
1263 let text_bytes_list = vec![
1264 (
1265 "",
1266 hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1267 ),
1268 (
1269 "A",
1270 hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1271 ),
1272 (
1273 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1274 hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1275 ),
1276 (
1277 "!@#$%^&*(),./;'[]",
1278 hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1279 ),
1280 ];
1281
1282 for (text, bytes) in text_bytes_list {
1283 assert_eq!(text, bytes32_to_str(&bytes).unwrap());
1284 }
1285 }
1286
1287 #[test]
1288 fn test_str_to_bytes32() {
1289 let text_bytes_list = vec![
1290 (
1291 "",
1292 hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1293 ),
1294 (
1295 "A",
1296 hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1297 ),
1298 (
1299 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1300 hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1301 ),
1302 (
1303 "!@#$%^&*(),./;'[]",
1304 hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1305 ),
1306 ];
1307
1308 for (text, bytes) in text_bytes_list {
1309 assert_eq!(bytes, str_to_bytes32(text).unwrap());
1310 }
1311 }
1312
1313 #[test]
1314 fn test_str_to_bytes32_long() {
1315 assert!(matches!(
1316 str_to_bytes32("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456").unwrap_err(),
1317 Error::BiggerThan32Bytes
1318 ));
1319 }
1320
1321 #[test]
1325 fn test_str_to_bytes32_rejects_nul() {
1326 for text in [
1327 "\0",
1328 "\0a",
1329 "a\0",
1330 "a\0b",
1331 "abcdefghijklmnopqrstuvwxyz01234\0",
1332 ] {
1333 assert!(
1334 matches!(str_to_bytes32(text), Err(Error::NulByteInInput)),
1335 "nul bearing input {:?} accepted",
1336 text
1337 );
1338 }
1339 }
1340
1341 #[test]
1344 fn test_str_to_bytes32_round_trip() -> Result<(), Error> {
1345 let mut seen: Vec<[u8; 32]> = vec![];
1346 for text in [
1347 "",
1348 "a",
1349 "stack",
1350 "!@#$%^&*(),./;'[]",
1351 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1352 ] {
1353 let bytes = str_to_bytes32(text)?;
1354 assert_eq!(bytes32_to_str(&bytes)?, text);
1355 assert!(!seen.contains(&bytes), "input {:?} collided", text);
1356 seen.push(bytes);
1357 }
1358 Ok(())
1359 }
1360
1361 #[tokio::test]
1362 async fn test_implements_i_describe_by_meta_v1() {
1363 async fn new_server_client() -> (Asserter, impl Provider) {
1365 let asserter = Asserter::new();
1366 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
1367
1368 asserter.push_success(
1370 &"0x0000000000000000000000000000000000000000000000000000000000000001",
1371 );
1372 asserter.push_success(
1373 &"0x0000000000000000000000000000000000000000000000000000000000000000",
1374 );
1375
1376 (asserter, provider)
1377 }
1378
1379 let address = Address::random();
1380
1381 let (asserter, provider) = new_server_client().await;
1383 asserter
1384 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
1385 let result = implements_i_described_by_meta_v1(&provider, address).await;
1386 assert!(result);
1387
1388 let (asserter, provider) = new_server_client().await;
1390 asserter
1391 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
1392 let result = implements_i_described_by_meta_v1(&provider, address).await;
1393 assert!(!result);
1394
1395 let (asserter, provider) = new_server_client().await;
1397 asserter.push_failure(ErrorPayload {
1398 code: -32003,
1399 message: "execution reverted".into(),
1400 data: Some(serde_json::value::to_raw_value(&json!("0x00")).unwrap()),
1401 });
1402 let result = implements_i_described_by_meta_v1(&provider, address).await;
1403 assert!(!result);
1404 }
1405
1406 #[test]
1410 fn oa_schema_map_key_roundtrip() -> Result<(), Error> {
1411 let payload = vec![0x01, 0x02, 0x03];
1412 let schema = "QmSchemaHash1234567890abcdefghijklmnopqrstuvwx".to_string();
1415 assert_eq!(schema.len(), 46);
1416
1417 let meta_map = RainMetaDocumentV1Item {
1418 payload: serde_bytes::ByteBuf::from(payload.clone()),
1419 magic: KnownMagic::OaStructure,
1420 content_type: ContentType::Json,
1421 content_encoding: ContentEncoding::Deflate,
1422 content_language: ContentLanguage::None,
1423 schema: Some(schema.clone()),
1424 };
1425 let cbor_encoded = meta_map.cbor_encode()?;
1426
1427 assert_eq!(cbor_encoded[0], 0xa5);
1429 assert_eq!(cbor_encoded[1], 0x00);
1431 assert_eq!(cbor_encoded[2], 0b010_00011);
1433 assert_eq!(cbor_encoded[3..6], payload);
1435 assert_eq!(cbor_encoded[6], 0x01);
1437 assert_eq!(cbor_encoded[7], 0b000_11011);
1439 assert_eq!(
1441 &cbor_encoded[8..16],
1442 KnownMagic::OaStructure.to_prefix_bytes()
1443 );
1444 assert_eq!(cbor_encoded[16], 0x02);
1446 assert_eq!(cbor_encoded[17], 0b011_10000);
1448 assert_eq!(&cbor_encoded[18..34], "application/json".as_bytes());
1449 assert_eq!(cbor_encoded[34], 0x03);
1451 assert_eq!(cbor_encoded[35], 0b011_00111);
1453 assert_eq!(&cbor_encoded[36..43], "deflate".as_bytes());
1454 assert_eq!(cbor_encoded[43], 0b000_11011);
1456 assert_eq!(
1457 &cbor_encoded[44..52],
1458 KnownMagic::OaSchema.to_prefix_bytes()
1459 );
1460 assert_eq!(cbor_encoded[52], 0b011_11000);
1462 assert_eq!(cbor_encoded[53], 46);
1463 assert_eq!(&cbor_encoded[54..], schema.as_bytes());
1465
1466 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1468 assert_eq!(cbor_decoded.len(), 1);
1470 assert_eq!(cbor_decoded[0], meta_map);
1472
1473 Ok(())
1474 }
1475
1476 #[test]
1479 fn no_schema_key_encodes_as_before_roundtrip() -> Result<(), Error> {
1480 let payload = vec![0x0a, 0x0b];
1481 let meta_map = RainMetaDocumentV1Item {
1482 payload: serde_bytes::ByteBuf::from(payload.clone()),
1483 magic: KnownMagic::OaStructure,
1484 content_type: ContentType::None,
1485 content_encoding: ContentEncoding::None,
1486 content_language: ContentLanguage::None,
1487 schema: None,
1488 };
1489 let cbor_encoded = meta_map.cbor_encode()?;
1490
1491 assert_eq!(cbor_encoded[0], 0xa2);
1493 assert_eq!(cbor_encoded[1], 0x00);
1495 assert_eq!(cbor_encoded[2], 0b010_00010);
1497 assert_eq!(cbor_encoded[3..5], payload);
1499 assert_eq!(cbor_encoded[5], 0x01);
1501 assert_eq!(cbor_encoded[6], 0b000_11011);
1503 assert_eq!(
1505 &cbor_encoded[7..],
1506 KnownMagic::OaStructure.to_prefix_bytes()
1507 );
1508
1509 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1510 assert_eq!(cbor_decoded.len(), 1);
1511 assert_eq!(cbor_decoded[0], meta_map);
1512
1513 Ok(())
1514 }
1515
1516 #[test]
1519 fn unknown_map_key_index_is_ignored() -> Result<(), Error> {
1520 let mut bytes: Vec<u8> = vec![
1521 0xa3, 0x00, 0x40, 0x01, 0x1b,
1525 ];
1526 bytes.extend_from_slice(&KnownMagic::DotrainSourceV1.to_prefix_bytes());
1527 bytes.extend_from_slice(&[0x05, 0x07]);
1529
1530 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1531 assert_eq!(decoded.len(), 1);
1532 assert_eq!(decoded[0], plain_item(KnownMagic::DotrainSourceV1, vec![]));
1533
1534 Ok(())
1535 }
1536
1537 #[test]
1540 fn non_oa_schema_extra_map_key_is_ignored() -> Result<(), Error> {
1541 let mut bytes: Vec<u8> = vec![
1544 0xa3, 0x00, 0x41, 0xff, 0x01, 0x1b,
1548 ];
1549 bytes.extend_from_slice(&KnownMagic::OaStructure.to_prefix_bytes());
1550 bytes.push(0x1b);
1552 bytes.extend_from_slice(&KnownMagic::OaHashList.to_prefix_bytes());
1553 bytes.extend_from_slice(&[0x62, 0x68, 0x69]);
1555
1556 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1557 assert_eq!(decoded.len(), 1);
1558 let expected = plain_item(KnownMagic::OaStructure, vec![0xff]);
1559 assert_eq!(decoded[0], expected);
1560 assert_eq!(decoded[0].schema, None);
1561
1562 Ok(())
1563 }
1564
1565 #[test]
1568 fn unknown_map_key_consumes_its_whole_value() -> Result<(), Error> {
1569 let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1570 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1571 bytes.extend_from_slice(&[0x05, 0xa1, 0x18, 0x2a, 0x82, 0x01, 0x02]);
1573 bytes.extend_from_slice(&handwritten_map());
1574
1575 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1576 assert_eq!(decoded.len(), 2);
1577 let expected = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1578 assert_eq!(decoded[0], expected);
1579 assert_eq!(decoded[1], expected);
1580
1581 Ok(())
1582 }
1583
1584 #[test]
1587 fn ignored_map_key_is_absent_from_the_reencoding() -> Result<(), Error> {
1588 let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1589 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1590 bytes.extend_from_slice(&[0x05, 0x07]);
1591
1592 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1593 assert_eq!(decoded[0].cbor_encode()?, handwritten_map());
1594 assert_eq!(decoded[0].hash(false)?, keccak256(handwritten_map()).0);
1595 assert_ne!(decoded[0].hash(false)?, keccak256(&bytes).0);
1596
1597 Ok(())
1598 }
1599
1600 #[test]
1604 fn non_integer_map_key_errors() {
1605 let mut text_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1606 text_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1607 text_key.extend_from_slice(&[0x61, 0x35, 0x07]);
1609 assert!(matches!(
1610 RainMetaDocumentV1Item::cbor_decode(&text_key),
1611 Err(Error::SerdeCborError(_))
1612 ));
1613
1614 let mut negative_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1615 negative_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1616 negative_key.extend_from_slice(&[0x20, 0x07]);
1618 assert!(matches!(
1619 RainMetaDocumentV1Item::cbor_decode(&negative_key),
1620 Err(Error::SerdeCborError(_))
1621 ));
1622 }
1623
1624 #[test]
1626 fn unknown_map_key_does_not_stand_in_for_a_mandatory_key() {
1627 let mut bytes: Vec<u8> = vec![0xa2, 0x05, 0x07, 0x01, 0x1b];
1628 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1629 assert!(matches!(
1630 RainMetaDocumentV1Item::cbor_decode(&bytes),
1631 Err(Error::SerdeCborError(_))
1632 ));
1633 }
1634
1635 fn plain_item(magic: KnownMagic, payload: Vec<u8>) -> RainMetaDocumentV1Item {
1636 RainMetaDocumentV1Item {
1637 payload: serde_bytes::ByteBuf::from(payload),
1638 magic,
1639 content_type: ContentType::None,
1640 content_encoding: ContentEncoding::None,
1641 content_language: ContentLanguage::None,
1642 schema: None,
1643 }
1644 }
1645
1646 fn sample_authoring_doc() -> (AuthoringMeta, Vec<u8>) {
1649 let authoring_meta: AuthoringMeta = serde_json::from_str(
1650 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
1651 )
1652 .unwrap();
1653 let abi = authoring_meta.abi_encode_validate().unwrap();
1654 let item = RainMetaDocumentV1Item {
1655 payload: serde_bytes::ByteBuf::from(abi),
1656 magic: KnownMagic::AuthoringMetaV1,
1657 content_type: ContentType::Cbor,
1658 content_encoding: ContentEncoding::None,
1659 content_language: ContentLanguage::None,
1660 schema: None,
1661 };
1662 let doc =
1663 RainMetaDocumentV1Item::cbor_encode_seq(&vec![item], KnownMagic::RainMetaDocumentV1)
1664 .unwrap();
1665 (authoring_meta, doc)
1666 }
1667
1668 fn sample_dotrain_item() -> RainMetaDocumentV1Item {
1669 RainMetaDocumentV1Item {
1670 payload: serde_bytes::ByteBuf::from("some dotrain body".as_bytes()),
1671 magic: KnownMagic::DotrainV1,
1672 content_type: ContentType::OctetStream,
1673 content_encoding: ContentEncoding::None,
1674 content_language: ContentLanguage::None,
1675 schema: None,
1676 }
1677 }
1678
1679 fn handwritten_map() -> Vec<u8> {
1682 vec![
1683 0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b, 0xff, 0xda, 0xc2, 0xf2, 0xf3, 0x7b, 0xe8, 0x94, ]
1689 }
1690
1691 #[test]
1695 fn test_hash_bare_vs_document() -> Result<(), Error> {
1696 let map_bytes = handwritten_map();
1697 let mut doc_bytes: Vec<u8> = vec![0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74];
1698 doc_bytes.extend_from_slice(&map_bytes);
1699
1700 let item = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1701 assert_eq!(item.hash(false)?, keccak256(&map_bytes).0);
1702 assert_eq!(item.hash(true)?, keccak256(&doc_bytes).0);
1703 assert_ne!(item.hash(false)?, item.hash(true)?);
1704 Ok(())
1705 }
1706
1707 #[test]
1709 fn test_cbor_decode_empty_is_corrupt() {
1710 assert!(matches!(
1711 RainMetaDocumentV1Item::cbor_decode(&[]),
1712 Err(Error::CorruptMeta)
1713 ));
1714 let prefix = KnownMagic::RainMetaDocumentV1.to_prefix_bytes();
1715 assert!(matches!(
1716 RainMetaDocumentV1Item::cbor_decode(&prefix),
1717 Err(Error::CorruptMeta)
1718 ));
1719 }
1720
1721 #[test]
1724 fn test_cbor_decode_trailing_truncated_is_corrupt() {
1725 let mut bytes = handwritten_map();
1726 bytes.push(0x1b); assert!(matches!(
1728 RainMetaDocumentV1Item::cbor_decode(&bytes),
1729 Err(Error::CorruptMeta)
1730 ));
1731 }
1732
1733 #[test]
1737 fn test_cbor_decode_truncated_item_is_corrupt() {
1738 let mut sole = handwritten_map();
1739 sole.pop();
1740 assert!(matches!(
1741 RainMetaDocumentV1Item::cbor_decode(&sole),
1742 Err(Error::CorruptMeta)
1743 ));
1744
1745 assert!(matches!(
1746 RainMetaDocumentV1Item::cbor_decode(&[0xa2, 0x00, 0x41, 0x01]),
1747 Err(Error::CorruptMeta)
1748 ));
1749
1750 let mut after_complete = handwritten_map();
1751 after_complete.extend_from_slice(&[0xa2, 0x00]);
1752 assert!(matches!(
1753 RainMetaDocumentV1Item::cbor_decode(&after_complete),
1754 Err(Error::CorruptMeta)
1755 ));
1756 }
1757
1758 #[test]
1761 fn test_cbor_decode_trailing_garbage_errors() {
1762 let mut bytes = handwritten_map();
1763 bytes.push(0xff); assert!(matches!(
1765 RainMetaDocumentV1Item::cbor_decode(&bytes),
1766 Err(Error::SerdeCborError(_))
1767 ));
1768 }
1769
1770 #[test]
1772 fn test_cbor_decode_missing_payload_errors() {
1773 let mut bytes: Vec<u8> = vec![0xa1, 0x01, 0x1b]; bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1775 assert!(matches!(
1776 RainMetaDocumentV1Item::cbor_decode(&bytes),
1777 Err(Error::SerdeCborError(_))
1778 ));
1779 }
1780
1781 #[test]
1783 fn test_cbor_decode_missing_magic_errors() {
1784 let bytes: Vec<u8> = vec![0xa1, 0x00, 0x41, 0x01]; assert!(matches!(
1786 RainMetaDocumentV1Item::cbor_decode(&bytes),
1787 Err(Error::SerdeCborError(_))
1788 ));
1789 }
1790
1791 #[test]
1793 fn test_cbor_decode_unknown_magic_errors() {
1794 let mut bytes: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1795 bytes.extend_from_slice(&0xdeadbeefdeadbeefu64.to_be_bytes());
1796 assert!(matches!(
1797 RainMetaDocumentV1Item::cbor_decode(&bytes),
1798 Err(Error::SerdeCborError(_))
1799 ));
1800 }
1801
1802 #[test]
1806 fn test_cbor_decode_handwritten_document_magic_item() {
1807 let bytes: Vec<u8> = vec![
1808 0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b, 0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74, ];
1814 assert!(RainMetaDocumentV1Item::cbor_decode(&bytes).is_err());
1818 }
1819
1820 #[test]
1826 fn test_document_magic_item_carries_a_nested_document() -> Result<(), Error> {
1827 let inner = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1828 let inner_doc = RainMetaDocumentV1Item::cbor_encode_seq(
1829 &vec![inner.clone()],
1830 KnownMagic::RainMetaDocumentV1,
1831 )?;
1832 let outer = plain_item(KnownMagic::RainMetaDocumentV1, inner_doc.clone());
1833 let outer_doc = RainMetaDocumentV1Item::cbor_encode_seq(
1834 &vec![outer.clone()],
1835 KnownMagic::RainMetaDocumentV1,
1836 )?;
1837
1838 assert!(RainMetaDocumentV1Item::cbor_decode(&outer_doc).is_err());
1844 assert_eq!(
1845 RainMetaDocumentV1Item::cbor_decode(&inner_doc)?,
1846 vec![inner]
1847 );
1848 Ok(())
1849 }
1850
1851 #[test]
1854 fn test_document_magic_item_is_not_unpackable() {
1855 assert!(matches!(
1856 KnownMeta::try_from(KnownMagic::RainMetaDocumentV1),
1857 Err(Error::UnsupportedMeta)
1858 ));
1859 assert!(matches!(
1860 plain_item(KnownMagic::RainMetaDocumentV1, vec![0x01]).unpack_into::<Vec<u8>>(),
1861 Err(Error::UnsupportedMeta)
1862 ));
1863 }
1864
1865 #[test]
1867 fn test_unpack_decodes_content_encoding() -> Result<(), Error> {
1868 let content = b"unpack me via deflate".to_vec();
1869 let packed = ContentEncoding::Deflate.encode(&content);
1870 assert_ne!(packed, content);
1871 let mut item = plain_item(KnownMagic::DotrainV1, packed);
1872 item.content_encoding = ContentEncoding::Deflate;
1873 assert_eq!(item.unpack()?, content);
1874
1875 let item = plain_item(KnownMagic::DotrainV1, content.clone());
1876 assert_eq!(item.unpack()?, content);
1877 Ok(())
1878 }
1879
1880 #[test]
1883 fn test_unpack_into_whitelist() {
1884 use strum::IntoEnumIterator;
1885 let supported = [
1886 KnownMagic::OpMetaV1,
1887 KnownMagic::DotrainV1,
1888 KnownMagic::RainlangV1,
1889 KnownMagic::SolidityAbiV2,
1890 KnownMagic::AuthoringMetaV1,
1891 KnownMagic::AuthoringMetaV2,
1892 KnownMagic::AddressList,
1893 KnownMagic::InterpreterCallerMetaV1,
1894 KnownMagic::ExpressionDeployerV2BytecodeV1,
1895 KnownMagic::DotrainSourceV1,
1896 KnownMagic::OrderBuilderStateV1,
1897 KnownMagic::RainlangSourceV1,
1898 KnownMagic::RaindexSignedContextOracleV1,
1899 ];
1900 for magic in supported {
1901 let unpacked: Vec<u8> = plain_item(magic, vec![0x61]).unpack_into().unwrap();
1902 assert_eq!(unpacked, vec![0x61], "{:?}", magic);
1903 }
1904 let unsupported = [
1905 KnownMagic::RainMetaDocumentV1,
1906 KnownMagic::WebDataV1,
1907 KnownMagic::OaSchema,
1908 KnownMagic::OaHashList,
1909 KnownMagic::OaStructure,
1910 KnownMagic::OaTokenImage,
1911 KnownMagic::OaTokenCredentialLinks,
1912 ];
1913 for magic in unsupported {
1914 let result: Result<Vec<u8>, Error> = plain_item(magic, vec![0x61]).unpack_into();
1915 assert!(matches!(result, Err(Error::UnsupportedMeta)), "{:?}", magic);
1916 }
1917 assert_eq!(
1919 supported.len() + unsupported.len(),
1920 KnownMagic::iter().count()
1921 );
1922 }
1923
1924 #[test]
1927 fn test_try_into_string_invalid_utf8_errors() {
1928 let item = plain_item(KnownMagic::DotrainV1, vec![0xff, 0xfe]);
1929 let result: Result<String, Error> = item.try_into();
1930 assert!(matches!(result, Err(Error::FromUtf8Error(_))));
1931 }
1932
1933 #[test]
1935 fn test_try_into_vec_decodes_encoding() -> Result<(), Error> {
1936 let content = b"raw bytecode bytes \x00\x01\x02".to_vec();
1937 let packed = ContentEncoding::Deflate.encode(&content);
1938 let mut item = plain_item(KnownMagic::ExpressionDeployerV2BytecodeV1, packed.clone());
1939 item.content_encoding = ContentEncoding::Deflate;
1940 let unpacked: Vec<u8> = item.try_into()?;
1941 assert_eq!(unpacked, content);
1942 assert_ne!(unpacked, packed);
1943 Ok(())
1944 }
1945
1946 #[test]
1949 fn test_content_encoding_deflate_roundtrip() -> Result<(), Error> {
1950 let content = b"hello rain deflate fixture hello rain deflate fixture".to_vec();
1951 let encoded = ContentEncoding::Deflate.encode(&content);
1952 assert_ne!(encoded, content);
1953 assert_eq!(encoded[0], 0x78);
1954 assert_eq!(ContentEncoding::Deflate.decode(&encoded)?, content);
1955 Ok(())
1956 }
1957
1958 #[test]
1960 fn test_content_encoding_passthrough() -> Result<(), Error> {
1961 let data = vec![0x00, 0xff, 0x10];
1962 for encoding in [ContentEncoding::None, ContentEncoding::Identity] {
1963 assert_eq!(encoding.encode(&data), data, "{:?}", encoding);
1964 assert_eq!(encoding.decode(&data)?, data, "{:?}", encoding);
1965 }
1966 Ok(())
1967 }
1968
1969 #[test]
1972 fn test_content_encoding_decode_fixtures() -> Result<(), Error> {
1973 let content = b"hello rain deflate fixture".to_vec();
1974 let zlib: Vec<u8> = vec![
1975 120, 156, 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44,
1976 73, 85, 72, 203, 172, 40, 41, 45, 74, 5, 0, 132, 64, 9, 251,
1977 ];
1978 let raw: Vec<u8> = vec![
1979 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44, 73, 85, 72,
1980 203, 172, 40, 41, 45, 74, 5, 0,
1981 ];
1982 assert_eq!(ContentEncoding::Deflate.decode(&zlib)?, content);
1983 assert_eq!(ContentEncoding::Deflate.decode(&raw)?, content);
1984 Ok(())
1985 }
1986
1987 #[test]
1990 fn test_content_encoding_decode_garbage_errors() {
1991 let garbage = [0xffu8, 0xff, 0xff, 0xff];
1992 assert!(matches!(
1993 ContentEncoding::Deflate.decode(&garbage),
1994 Err(Error::InflateError(_))
1995 ));
1996 }
1997
1998 #[test]
2000 fn test_content_headers_strum_names() {
2001 use std::str::FromStr;
2002 assert_eq!(
2003 ContentEncoding::from_str("deflate").unwrap(),
2004 ContentEncoding::Deflate
2005 );
2006 assert_eq!(
2007 ContentEncoding::from_str("identity").unwrap(),
2008 ContentEncoding::Identity
2009 );
2010 assert_eq!(
2011 ContentEncoding::from_str("none").unwrap(),
2012 ContentEncoding::None
2013 );
2014 assert_eq!(ContentEncoding::Deflate.to_string(), "deflate");
2015 assert_eq!(
2016 ContentType::from_str("octet-stream").unwrap(),
2017 ContentType::OctetStream
2018 );
2019 assert_eq!(ContentType::from_str("json").unwrap(), ContentType::Json);
2020 assert_eq!(ContentType::Json.to_string(), "json");
2021 assert_eq!(
2022 ContentLanguage::from_str("en").unwrap(),
2023 ContentLanguage::En
2024 );
2025 }
2026
2027 #[test]
2030 fn test_known_meta_try_from_magic() {
2031 let cases: [(KnownMagic, KnownMeta); 13] = [
2032 (KnownMagic::OpMetaV1, KnownMeta::OpV1),
2033 (KnownMagic::DotrainV1, KnownMeta::DotrainV1),
2034 (KnownMagic::RainlangV1, KnownMeta::RainlangV1),
2035 (KnownMagic::SolidityAbiV2, KnownMeta::SolidityAbiV2),
2036 (KnownMagic::AuthoringMetaV1, KnownMeta::AuthoringMetaV1),
2037 (KnownMagic::AuthoringMetaV2, KnownMeta::AuthoringMetaV2),
2038 (KnownMagic::AddressList, KnownMeta::AddressList),
2039 (
2040 KnownMagic::InterpreterCallerMetaV1,
2041 KnownMeta::InterpreterCallerMetaV1,
2042 ),
2043 (
2044 KnownMagic::ExpressionDeployerV2BytecodeV1,
2045 KnownMeta::ExpressionDeployerV2BytecodeV1,
2046 ),
2047 (KnownMagic::RainlangSourceV1, KnownMeta::RainlangSourceV1),
2048 (KnownMagic::DotrainSourceV1, KnownMeta::DotrainSourceV1),
2049 (
2050 KnownMagic::OrderBuilderStateV1,
2051 KnownMeta::OrderBuilderStateV1,
2052 ),
2053 (
2054 KnownMagic::RaindexSignedContextOracleV1,
2055 KnownMeta::RaindexSignedContextOracleV1,
2056 ),
2057 ];
2058 for (magic, meta) in cases {
2059 assert_eq!(KnownMeta::try_from(magic).unwrap(), meta, "{:?}", magic);
2060 }
2061 for magic in [
2062 KnownMagic::RainMetaDocumentV1,
2063 KnownMagic::WebDataV1,
2064 KnownMagic::OaSchema,
2065 KnownMagic::OaHashList,
2066 KnownMagic::OaStructure,
2067 KnownMagic::OaTokenImage,
2068 KnownMagic::OaTokenCredentialLinks,
2069 ] {
2070 assert!(
2071 matches!(KnownMeta::try_from(magic), Err(Error::UnsupportedMeta)),
2072 "{:?}",
2073 magic
2074 );
2075 }
2076 }
2077
2078 #[test]
2081 fn test_known_meta_strum_parse_display() {
2082 use std::str::FromStr;
2083 assert_eq!(
2084 KnownMeta::from_str("solidity-abi-v2").unwrap(),
2085 KnownMeta::SolidityAbiV2
2086 );
2087 assert_eq!(
2088 KnownMeta::from_str("interpreter-caller-meta-v1").unwrap(),
2089 KnownMeta::InterpreterCallerMetaV1
2090 );
2091 assert_eq!(KnownMeta::SolidityAbiV2.to_string(), "solidity-abi-v2");
2092 }
2093
2094 fn sample_deployer(meta_bytes: &[u8]) -> NPE2Deployer {
2098 NPE2Deployer {
2099 meta_hash: keccak256(meta_bytes).0.to_vec(),
2100 meta_bytes: meta_bytes.to_vec(),
2101 bytecode: vec![0xb1],
2102 parser: vec![0xb2],
2103 store: vec![0xb3],
2104 interpreter: vec![0xb4],
2105 authoring_meta: None,
2106 }
2107 }
2108
2109 fn deployer_json_body(
2110 meta_hash_hex: &str,
2111 meta_bytes_hex: &str,
2112 tx_hex: &str,
2113 bytecode_meta_id_hex: &str,
2114 ) -> serde_json::Value {
2115 json!({
2116 "data": {
2117 "expressionDeployers": [{
2118 "constructorMetaHash": meta_hash_hex,
2119 "constructorMeta": meta_bytes_hex,
2120 "deployTransaction": {"id": tx_hex},
2121 "bytecode": "0x01",
2122 "parser": {"parser": {"deployedBytecode": "0x02"}},
2123 "store": {"store": {"deployedBytecode": "0x03"}},
2124 "interpreter": {"interpreter": {"deployedBytecode": "0x04"}},
2125 "meta": [{"__typename": "RainMetaV1", "id": bytecode_meta_id_hex}]
2126 }]
2127 }
2128 })
2129 }
2130
2131 #[tokio::test]
2133 async fn test_search_lowercases_hash() {
2134 use httpmock::prelude::*;
2135 let (_, doc) = sample_authoring_doc();
2136 let hash_upper = format!("0x{}", "AB".repeat(32));
2137 let server = MockServer::start();
2138 let mock = server.mock(|when, then| {
2139 when.method(POST)
2140 .body_contains(hash_upper.to_ascii_lowercase());
2141 then.status(200).json_body(json!({
2142 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2143 }));
2144 });
2145 let response = search(&hash_upper, &vec![server.url("/sg")]).await.unwrap();
2146 assert_eq!(response.bytes, doc);
2147 mock.assert();
2148 }
2149
2150 #[tokio::test]
2153 async fn test_search_first_success_wins() {
2154 use httpmock::prelude::*;
2155 let (_, doc) = sample_authoring_doc();
2156 let bad = MockServer::start();
2157 let _bad_mock = bad.mock(|when, then| {
2158 when.method(POST);
2159 then.status(500).body("subgraph down");
2160 });
2161 let good = MockServer::start();
2162 let _good_mock = good.mock(|when, then| {
2163 when.method(POST);
2164 then.status(200).json_body(json!({
2165 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2166 }));
2167 });
2168 let response = search(
2169 &format!("0x{}", "11".repeat(32)),
2170 &vec![bad.url("/sg"), good.url("/sg")],
2171 )
2172 .await
2173 .unwrap();
2174 assert_eq!(response.bytes, doc);
2175 }
2176
2177 #[tokio::test]
2180 async fn test_search_deployer_lowercases_hash() {
2181 use httpmock::prelude::*;
2182 let (_, doc) = sample_authoring_doc();
2183 let meta_hash_hex = hex::encode_prefixed(keccak256(&doc).0);
2184 let hash_upper = format!("0x{}", "CD".repeat(32));
2185 let server = MockServer::start();
2186 let mock = server.mock(|when, then| {
2187 when.method(POST)
2188 .body_contains(hash_upper.to_ascii_lowercase());
2189 then.status(200).json_body(deployer_json_body(
2190 &meta_hash_hex,
2191 &hex::encode_prefixed(&doc),
2192 &format!("0x{}", "77".repeat(32)),
2193 &meta_hash_hex,
2194 ));
2195 });
2196 let response = search_deployer(&hash_upper, &vec![server.url("/sg")])
2197 .await
2198 .unwrap();
2199 assert_eq!(response.meta_bytes, doc);
2200 assert_eq!(response.bytecode, vec![0x01]);
2201 mock.assert();
2202 }
2203
2204 #[tokio::test]
2207 async fn test_search_deployer_first_success_wins() {
2208 use httpmock::prelude::*;
2209 let (_, doc) = sample_authoring_doc();
2210 let meta_hash_hex = hex::encode_prefixed(keccak256(&doc).0);
2211 let bad = MockServer::start();
2212 let _bad_mock = bad.mock(|when, then| {
2213 when.method(POST);
2214 then.status(500).body("subgraph down");
2215 });
2216 let good = MockServer::start();
2217 let _good_mock = good.mock(|when, then| {
2218 when.method(POST);
2219 then.status(200).json_body(deployer_json_body(
2220 &meta_hash_hex,
2221 &hex::encode_prefixed(&doc),
2222 &format!("0x{}", "77".repeat(32)),
2223 &meta_hash_hex,
2224 ));
2225 });
2226 let response = search_deployer(
2227 &format!("0x{}", "22".repeat(32)),
2228 &vec![bad.url("/sg"), good.url("/sg")],
2229 )
2230 .await
2231 .unwrap();
2232 assert_eq!(response.meta_bytes, doc);
2233 }
2234
2235 #[tokio::test]
2239 async fn test_search_empty_subgraphs_is_a_miss() {
2240 let hash = format!("0x{}", "33".repeat(32));
2241 assert!(matches!(
2242 search(&hash, &vec![]).await,
2243 Err(Error::NoRecordFound)
2244 ));
2245 assert!(matches!(
2246 search_deployer(&hash, &vec![]).await,
2247 Err(Error::NoRecordFound)
2248 ));
2249 }
2250
2251 #[tokio::test]
2255 async fn test_implements_erc165_gate_short_circuits() {
2256 let address = Address::random();
2257
2258 let asserter = Asserter::new();
2260 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2261 asserter
2262 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2263 asserter
2264 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2265 assert!(!implements_i_described_by_meta_v1(&provider, address).await);
2266
2267 let asserter = Asserter::new();
2269 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2270 asserter.push_failure(ErrorPayload {
2271 code: -32000,
2272 message: "connection reset".into(),
2273 data: None,
2274 });
2275 asserter
2276 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2277 assert!(!implements_i_described_by_meta_v1(&provider, address).await);
2278 }
2279
2280 #[tokio::test]
2283 async fn test_implements_undecodable_response_is_false() {
2284 let address = Address::random();
2285 let asserter = Asserter::new();
2286 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2287 asserter
2288 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2289 asserter
2290 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2291 asserter.push_success(&"0x");
2292 assert!(!implements_i_described_by_meta_v1(&provider, address).await);
2293 }
2294
2295 #[test]
2298 fn test_npe2_deployer_is_corrupt_per_field() {
2299 let full = NPE2Deployer {
2300 meta_hash: vec![1],
2301 meta_bytes: vec![2],
2302 bytecode: vec![3],
2303 parser: vec![4],
2304 store: vec![5],
2305 interpreter: vec![6],
2306 authoring_meta: None,
2307 };
2308 assert!(!full.is_corrupt());
2309 for field in 0..6usize {
2310 let mut record = full.clone();
2311 match field {
2312 0 => record.meta_hash = vec![],
2313 1 => record.meta_bytes = vec![],
2314 2 => record.bytecode = vec![],
2315 3 => record.parser = vec![],
2316 4 => record.store = vec![],
2317 5 => record.interpreter = vec![],
2318 _ => unreachable!(),
2319 }
2320 assert!(record.is_corrupt(), "empty field {} must corrupt", field);
2321 }
2322 }
2323
2324 #[tokio::test]
2328 async fn test_store_constructors_inject_no_subgraphs() {
2329 assert!(Store::new().subgraphs().is_empty());
2330 assert!(Store::default().subgraphs().is_empty());
2331 assert!(Store::create(
2332 &vec![],
2333 &MetaCache::default(),
2334 &DeployerCache::default(),
2335 &HashMap::new()
2336 )
2337 .subgraphs()
2338 .is_empty());
2339
2340 let hash = [0u8; 32];
2341 let mut store = Store::default();
2342 assert!(store.update(&hash).await.is_err());
2343 assert!(store.search_deployer(&hash).await.is_err());
2344 }
2345
2346 #[test]
2354 fn test_store_create_validates_entries() {
2355 let (_, doc) = sample_authoring_doc();
2356 let good_hash = keccak256(&doc).0.to_vec();
2357 let mut cache = MetaCache::default();
2358 cache.insert_verified(&good_hash, doc.clone()).unwrap();
2359 let mut deployer_cache = DeployerCache::default();
2360 let deployer = sample_deployer(b"dep-meta");
2361 let deployer_key = vec![0x33u8; 32];
2362 deployer_cache
2363 .insert_verified(&deployer_key, deployer.clone())
2364 .unwrap();
2365 let mut dotrain_cache = HashMap::new();
2366 dotrain_cache.insert("a.rain".to_string(), good_hash.clone());
2367 dotrain_cache.insert("missing.rain".to_string(), vec![0x44u8; 32]);
2368
2369 let store = Store::create(
2370 &vec!["https://example.com/custom-sg".to_string()],
2371 &cache,
2372 &deployer_cache,
2373 &dotrain_cache,
2374 );
2375
2376 assert_eq!(
2377 store.subgraphs(),
2378 &vec!["https://example.com/custom-sg".to_string()]
2379 );
2380 assert_eq!(store.get_meta(&good_hash), Some(&doc));
2381 assert_eq!(store.get_deployer(&deployer_key), Some(&deployer));
2382 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&good_hash));
2383 assert_eq!(store.get_dotrain_hash("missing.rain"), None);
2384 }
2385
2386 #[test]
2388 fn test_store_add_subgraphs_dedupe() {
2389 let mut store = Store::new();
2390 store.add_subgraphs(&vec!["sg-a".to_string()]);
2391 store.add_subgraphs(&vec!["sg-a".to_string(), "sg-b".to_string()]);
2392 assert_eq!(
2393 store.subgraphs(),
2394 &vec!["sg-a".to_string(), "sg-b".to_string()]
2395 );
2396 }
2397
2398 #[test]
2401 fn test_store_get_deployer_lookup_chain() {
2402 let mut store = Store::new();
2403 let deployer = sample_deployer(b"dep-meta-bytes");
2404 let key = vec![0x01u8; 32];
2405 let tx = vec![0x02u8; 32];
2406 store.set_deployer(&key, &deployer, Some(&tx)).unwrap();
2407 assert_eq!(store.get_deployer(&key), Some(&deployer));
2408 assert_eq!(store.get_deployer(&tx), Some(&deployer));
2409 assert_eq!(store.get_deployer(&[0x03u8; 32]), None);
2410 assert_eq!(
2411 store.get_meta(&deployer.meta_hash),
2412 Some(&deployer.meta_bytes)
2413 );
2414 }
2415
2416 #[tokio::test]
2420 async fn test_store_search_deployer_populates_caches() {
2421 use httpmock::prelude::*;
2422 let (authoring_meta, doc) = sample_authoring_doc();
2423 let meta_hash = keccak256(&doc).0.to_vec();
2424 let meta_hash_hex = hex::encode_prefixed(&meta_hash);
2425 let tx = vec![0x77u8; 32];
2426 let server = MockServer::start();
2427 let _mock = server.mock(|when, then| {
2428 when.method(POST);
2429 then.status(200).json_body(deployer_json_body(
2430 &meta_hash_hex,
2431 &hex::encode_prefixed(&doc),
2432 &hex::encode_prefixed(&tx),
2433 &meta_hash_hex,
2434 ));
2435 });
2436 let mut store = Store::new();
2437 store.add_subgraphs(&vec![server.url("/sg")]);
2438
2439 let record = store.search_deployer(&meta_hash).await.cloned().unwrap();
2440 assert_eq!(record.meta_hash, meta_hash);
2441 assert_eq!(record.meta_bytes, doc);
2442 assert_eq!(record.bytecode, vec![0x01]);
2443 assert_eq!(record.parser, vec![0x02]);
2444 assert_eq!(record.store, vec![0x03]);
2445 assert_eq!(record.interpreter, vec![0x04]);
2446 assert_eq!(record.authoring_meta, Some(authoring_meta));
2447 assert_eq!(store.get_meta(&meta_hash), Some(&doc));
2448 assert_eq!(store.get_deployer(&tx), Some(&record));
2449 }
2450
2451 #[tokio::test]
2453 async fn test_store_search_deployer_error_returns_none() {
2454 use httpmock::prelude::*;
2455 let server = MockServer::start();
2456 let _mock = server.mock(|when, then| {
2457 when.method(POST);
2458 then.status(500).body("subgraph down");
2459 });
2460 let mut store = Store::new();
2461 store.add_subgraphs(&vec![server.url("/sg")]);
2462 assert!(store.search_deployer(&[0x0Du8; 32]).await.is_err());
2463 assert!(store.cache().is_empty());
2464 assert!(store.deployer_cache().is_empty());
2465 }
2466
2467 #[tokio::test]
2471 async fn test_store_search_deployer_check_branches() {
2472 use httpmock::prelude::*;
2473 let mut store = Store::new();
2475 let deployer = sample_deployer(b"cached-meta");
2476 let key = vec![0x11u8; 32];
2477 let tx = vec![0x22u8; 32];
2478 store.set_deployer(&key, &deployer, Some(&tx)).unwrap();
2479 assert_eq!(store.search_deployer_check(&key).await.unwrap(), &deployer);
2480 assert_eq!(store.search_deployer_check(&tx).await.unwrap(), &deployer);
2481
2482 let (_, doc) = sample_authoring_doc();
2484 let meta_hash = keccak256(&doc).0.to_vec();
2485 let meta_hash_hex = hex::encode_prefixed(&meta_hash);
2486 let server = MockServer::start();
2487 let _mock = server.mock(|when, then| {
2488 when.method(POST);
2489 then.status(200).json_body(deployer_json_body(
2490 &meta_hash_hex,
2491 &hex::encode_prefixed(&doc),
2492 &format!("0x{}", "66".repeat(32)),
2493 &meta_hash_hex,
2494 ));
2495 });
2496 let mut fresh = Store::new();
2497 fresh.add_subgraphs(&vec![server.url("/sg")]);
2498 let found = fresh
2499 .search_deployer_check(&meta_hash)
2500 .await
2501 .cloned()
2502 .unwrap();
2503 assert_eq!(found.meta_bytes, doc);
2504 }
2505
2506 #[test]
2509 fn test_store_set_deployer_from_query_response() {
2510 let (authoring_meta, doc) = sample_authoring_doc();
2511 let meta_hash = keccak256(&doc).0.to_vec();
2513 let bytecode_meta_hash = vec![0x0Bu8; 32];
2514 let tx = vec![0x0Cu8; 32];
2515 let response = DeployerResponse {
2516 tx_hash: tx.clone(),
2517 bytecode_meta_hash: bytecode_meta_hash.clone(),
2518 meta_hash: meta_hash.clone(),
2519 meta_bytes: doc.clone(),
2520 bytecode: vec![0xE1],
2521 parser: vec![0xE2],
2522 store: vec![0xE3],
2523 interpreter: vec![0xE4],
2524 };
2525 let mut store = Store::new();
2526 let record = store.set_deployer_from_query_response(response).unwrap();
2527 assert_eq!(record.meta_hash, meta_hash);
2528 assert_eq!(record.meta_bytes, doc);
2529 assert_eq!(record.bytecode, vec![0xE1]);
2530 assert_eq!(record.authoring_meta, Some(authoring_meta));
2531 assert_eq!(store.get_meta(&meta_hash), Some(&doc));
2532 assert_eq!(store.get_deployer(&bytecode_meta_hash), Some(&record));
2533 assert_eq!(store.get_deployer(&tx), Some(&record));
2534 }
2535
2536 #[test]
2540 fn test_store_dotrain_getters_and_set_fresh() {
2541 let mut store = Store::new();
2542 let text = "some dotrain content";
2543 let (hash, old) = store.set_dotrain(text, "file.rain", false).unwrap();
2544 assert!(old.is_empty());
2545 let expected_item = RainMetaDocumentV1Item {
2546 payload: serde_bytes::ByteBuf::from(text.as_bytes()),
2547 magic: KnownMagic::DotrainV1,
2548 content_type: ContentType::OctetStream,
2549 content_encoding: ContentEncoding::None,
2550 content_language: ContentLanguage::None,
2551 schema: None,
2552 };
2553 let expected_bytes = expected_item.cbor_encode().unwrap();
2554 assert_eq!(hash, keccak256(&expected_bytes).0.to_vec());
2555 assert_eq!(store.get_dotrain_hash("file.rain"), Some(&hash));
2556 assert_eq!(store.get_dotrain_uri(&hash), Some(&"file.rain".to_string()));
2557 assert_eq!(store.get_dotrain_meta("file.rain"), Some(&expected_bytes));
2558 assert_eq!(store.get_dotrain_hash("other.rain"), None);
2559 assert_eq!(store.get_dotrain_uri(&[0u8; 32]), None);
2560 assert_eq!(store.get_dotrain_meta("other.rain"), None);
2561 }
2562
2563 #[test]
2567 fn test_store_set_dotrain_branches() {
2568 let mut store = Store::new();
2569 let (hash_one, _) = store.set_dotrain("text one", "a.rain", false).unwrap();
2570
2571 let (hash_same, old_same) = store.set_dotrain("text one", "a.rain", false).unwrap();
2573 assert_eq!(hash_same, hash_one);
2574 assert!(old_same.is_empty());
2575 assert!(store.get_meta(&hash_one).is_some());
2576
2577 let (hash_two, old_two) = store.set_dotrain("text two", "a.rain", false).unwrap();
2579 assert_ne!(hash_two, hash_one);
2580 assert_eq!(old_two, hash_one);
2581 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_two));
2582 assert!(store.get_meta(&hash_one).is_none());
2583 assert!(store.get_meta(&hash_two).is_some());
2584
2585 let (hash_three, old_three) = store.set_dotrain("text three", "a.rain", true).unwrap();
2587 assert_eq!(old_three, hash_two);
2588 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_three));
2589 assert!(store.get_meta(&hash_two).is_some());
2590 assert!(store.get_meta(&hash_three).is_some());
2591 }
2592
2593 #[test]
2596 fn test_store_delete_dotrain_keep_meta() {
2597 let mut store = Store::new();
2598 let (hash, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2599 store.delete_dotrain("d.rain", false);
2600 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2601 assert!(store.get_meta(&hash).is_none());
2602
2603 let (hash_again, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2604 store.delete_dotrain("d.rain", true);
2605 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2606 assert!(store.get_meta(&hash_again).is_some());
2607 }
2608
2609 #[test]
2612 fn test_store_merge_semantics() {
2613 let deployer_ours = sample_deployer(b"ours");
2614 let deployer_theirs = sample_deployer(b"theirs");
2615 let shared_tx = vec![0x0Fu8; 32];
2616 let their_tx = vec![0x1Eu8; 32];
2617
2618 let mut ours = Store::new();
2619 let mut theirs = Store::new();
2620 ours.set_deployer(&[0x01u8; 32], &deployer_ours, Some(&shared_tx))
2621 .unwrap();
2622 theirs
2623 .set_deployer(&[0x02u8; 32], &deployer_theirs, Some(&shared_tx))
2624 .unwrap();
2625 theirs
2626 .set_deployer(&[0x02u8; 32], &deployer_theirs, Some(&their_tx))
2627 .unwrap();
2628
2629 let contested_key = vec![0x03u8; 32];
2631 let deployer_a = sample_deployer(b"deployer-a");
2632 let deployer_b = sample_deployer(b"deployer-b");
2633 ours.set_deployer(&contested_key, &deployer_a, None)
2634 .unwrap();
2635 theirs
2636 .set_deployer(&contested_key, &deployer_b, None)
2637 .unwrap();
2638
2639 let (hash_ours, _) = ours.set_dotrain("content a", "x.rain", false).unwrap();
2641 let (_hash_theirs, _) = theirs.set_dotrain("content b", "x.rain", false).unwrap();
2642
2643 theirs.add_subgraphs(&vec!["sg-their".to_string()]);
2644
2645 ours.merge(&theirs);
2646
2647 assert_eq!(
2651 ours.get_meta(&deployer_ours.meta_hash),
2652 Some(&b"ours".to_vec())
2653 );
2654 assert_eq!(
2655 ours.get_meta(&deployer_theirs.meta_hash),
2656 Some(&b"theirs".to_vec())
2657 );
2658 assert_eq!(ours.get_deployer(&contested_key), Some(&deployer_a));
2660 assert_eq!(ours.get_deployer(&shared_tx), Some(&deployer_ours));
2662 assert_eq!(ours.get_deployer(&their_tx), Some(&deployer_theirs));
2664 assert_eq!(ours.get_dotrain_hash("x.rain"), Some(&hash_ours));
2666 assert!(ours.subgraphs().contains(&"sg-their".to_string()));
2668 }
2669
2670 #[tokio::test]
2674 async fn test_store_update_and_update_check() {
2675 use httpmock::prelude::*;
2676 let authoring_meta: AuthoringMeta = serde_json::from_str(
2677 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
2678 )
2679 .unwrap();
2680 let item_one = RainMetaDocumentV1Item {
2681 payload: serde_bytes::ByteBuf::from(authoring_meta.abi_encode_validate().unwrap()),
2682 magic: KnownMagic::AuthoringMetaV1,
2683 content_type: ContentType::Cbor,
2684 content_encoding: ContentEncoding::None,
2685 content_language: ContentLanguage::None,
2686 schema: None,
2687 };
2688 let item_two = sample_dotrain_item();
2689 let doc = RainMetaDocumentV1Item::cbor_encode_seq(
2690 &vec![item_one.clone(), item_two.clone()],
2691 KnownMagic::RainMetaDocumentV1,
2692 )
2693 .unwrap();
2694 let requested = keccak256(&doc).0.to_vec();
2695 let server = MockServer::start();
2696 let _mock = server.mock(|when, then| {
2697 when.method(POST);
2698 then.status(200).json_body(json!({
2699 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2700 }));
2701 });
2702 let mut store = Store::new();
2703 store.add_subgraphs(&vec![server.url("/sg")]);
2704 let fetched = store.update(&requested).await.cloned().unwrap();
2705 assert_eq!(fetched, doc);
2706 assert_eq!(store.get_meta(&requested), Some(&doc));
2707 let inner_one = item_one.cbor_encode().unwrap();
2708 let inner_two = item_two.cbor_encode().unwrap();
2709 assert_eq!(
2710 store.get_meta(keccak256(&inner_one).0.as_ref()),
2711 Some(&inner_one)
2712 );
2713 assert_eq!(
2714 store.get_meta(keccak256(&inner_two).0.as_ref()),
2715 Some(&inner_two)
2716 );
2717
2718 let mut cached_store = Store::new();
2720 let bytes = b"standalone meta bytes".to_vec();
2721 let hash = keccak256(&bytes).0.to_vec();
2722 assert!(cached_store.update_with(&hash, &bytes).is_ok());
2723 assert_eq!(cached_store.update_check(&hash).await.unwrap(), &bytes);
2724 }
2725
2726 #[tokio::test]
2730 async fn test_store_update_rejects_hash_mismatch() {
2731 use httpmock::prelude::*;
2732 let (_, doc) = sample_authoring_doc();
2733 let requested = keccak256(b"the real content").0.to_vec();
2734 let server = MockServer::start();
2735 let _mock = server.mock(|when, then| {
2736 when.method(POST);
2737 then.status(200).json_body(json!({
2738 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2739 }));
2740 });
2741 let mut store = Store::new();
2742 store.add_subgraphs(&vec![server.url("/sg")]);
2743 assert!(store.update(&requested).await.is_err());
2744 assert!(store.get_meta(&requested).is_none());
2745 assert!(store.cache().is_empty());
2746 assert!(store.update_check(&requested).await.is_err());
2748 }
2749
2750 #[tokio::test]
2753 async fn test_store_no_subgraphs_lookups_return_none() {
2754 let hash = [0u8; 32];
2755 let mut store = Store::new();
2756 assert!(store.update(&hash).await.is_err());
2757 assert!(store.update_check(&hash).await.is_err());
2758 assert!(store.search_deployer(&hash).await.is_err());
2759 assert!(store.search_deployer_check(&hash).await.is_err());
2760 assert!(store.cache().is_empty());
2761 assert!(store.deployer_cache().is_empty());
2762 }
2763
2764 #[test]
2768 fn test_store_update_with_validation_and_content() {
2769 let mut store = Store::new();
2771 let bytes = b"payload bytes".to_vec();
2772 let wrong_hash = vec![0x99u8; 32];
2773 match store.update_with(&wrong_hash, &bytes).unwrap_err() {
2778 Error::CorruptRecord(message) => {
2779 assert!(
2780 message.contains(&hex::encode_prefixed(&wrong_hash)),
2781 "{}",
2782 message
2783 )
2784 }
2785 other => panic!("expected CorruptRecord, got {:?}", other),
2786 }
2787 assert!(store.get_meta(&wrong_hash).is_none());
2788 let hash = keccak256(&bytes).0.to_vec();
2790 assert_eq!(store.update_with(&hash, &bytes).unwrap(), &bytes);
2791
2792 let mut seeded = Store::new();
2799 let planted = b"planted value".to_vec();
2800 let planted_hash = keccak256(&planted).0.to_vec();
2801 seeded.update_with(&planted_hash, &planted).unwrap();
2802 assert_eq!(seeded.cache().len(), 1);
2803 assert_eq!(
2804 seeded.update_with(&planted_hash, &planted).unwrap(),
2805 &planted
2806 );
2807 assert_eq!(seeded.cache().len(), 1);
2808
2809 let (_, doc) = sample_authoring_doc();
2811 let doc_hash = keccak256(&doc).0.to_vec();
2812 let mut doc_store = Store::new();
2813 assert!(doc_store.update_with(&doc_hash, &doc).is_ok());
2814 let inner = doc[8..].to_vec();
2815 assert_eq!(store_inner_lookup(&doc_store, &inner), Some(inner.clone()));
2816
2817 let item_a = sample_dotrain_item().cbor_encode().unwrap();
2819 let (_, doc_b) = sample_authoring_doc();
2820 let item_b = doc_b[8..].to_vec();
2821 let seq = [item_a.clone(), item_b].concat();
2822 let seq_hash = keccak256(&seq).0.to_vec();
2823 let mut seq_store = Store::new();
2824 assert!(seq_store.update_with(&seq_hash, &seq).is_ok());
2825 assert_eq!(store_inner_lookup(&seq_store, &item_a), None);
2826 }
2827
2828 fn store_inner_lookup(store: &Store, inner_encoded: &[u8]) -> Option<Vec<u8>> {
2829 store.get_meta(keccak256(inner_encoded).0.as_ref()).cloned()
2830 }
2831
2832 #[test]
2835 fn test_bytes32_to_str_invalid_utf8() {
2836 let mut bytes = [0u8; 32];
2837 bytes[0] = 0xf0;
2838 bytes[1] = 0x28;
2839 bytes[2] = 0x8c;
2840 bytes[3] = 0x28;
2841 assert!(matches!(bytes32_to_str(&bytes), Err(Error::Utf8Error(_))));
2842 let no_nul = [0xffu8; 32];
2843 assert!(matches!(bytes32_to_str(&no_nul), Err(Error::Utf8Error(_))));
2844 }
2845}