1use super::error::Error;
2use alloy::primitives::{hex, keccak256};
3use futures::future;
4use graphql_client::GraphQLQuery;
5use rain_metadata_bindings::IDescribedByMetaV1;
6use reqwest::Client;
7use serde::de::{Deserialize, Deserializer, Visitor};
8use serde::ser::{Serialize, SerializeMap, Serializer};
9use std::{collections::HashMap, convert::TryFrom, fmt::Debug, sync::Arc};
10use strum::{EnumIter, EnumString};
11use types::authoring::v1::AuthoringMeta;
12use alloy::sol_types::private::Address;
13use alloy::providers::Provider;
14use alloy::rpc::types::TransactionRequest;
15use alloy::sol_types::SolCall;
16use rain_erc::erc165::{IERC165, XorSelectors, supports_erc165};
17
18pub mod magic;
19pub(crate) mod normalize;
20pub(crate) mod query;
21pub mod types;
22
23pub use magic::*;
24pub use query::*;
25
26#[derive(Copy, Clone, EnumString, EnumIter, strum::Display, Debug, PartialEq)]
28#[strum(serialize_all = "kebab-case")]
29pub enum KnownMeta {
30 OpV1,
31 DotrainV1,
32 RainlangV1,
33 SolidityAbiV2,
34 AuthoringMetaV1,
35 AuthoringMetaV2,
36 InterpreterCallerMetaV1,
37 ExpressionDeployerV2BytecodeV1,
38 RainlangSourceV1,
39 AddressList,
40 DotrainSourceV1,
41 OrderBuilderStateV1,
42 RaindexSignedContextOracleV1,
43}
44
45impl TryFrom<KnownMagic> for KnownMeta {
46 type Error = Error;
47 fn try_from(value: KnownMagic) -> Result<Self, Self::Error> {
48 match value {
49 KnownMagic::OpMetaV1 => Ok(KnownMeta::OpV1),
50 KnownMagic::DotrainV1 => Ok(KnownMeta::DotrainV1),
51 KnownMagic::RainlangV1 => Ok(KnownMeta::RainlangV1),
52 KnownMagic::SolidityAbiV2 => Ok(KnownMeta::SolidityAbiV2),
53 KnownMagic::AuthoringMetaV1 => Ok(KnownMeta::AuthoringMetaV1),
54 KnownMagic::AuthoringMetaV2 => Ok(KnownMeta::AuthoringMetaV2),
55 KnownMagic::AddressList => Ok(KnownMeta::AddressList),
56 KnownMagic::InterpreterCallerMetaV1 => Ok(KnownMeta::InterpreterCallerMetaV1),
57 KnownMagic::DotrainSourceV1 => Ok(KnownMeta::DotrainSourceV1),
58 KnownMagic::OrderBuilderStateV1 => Ok(KnownMeta::OrderBuilderStateV1),
59 KnownMagic::ExpressionDeployerV2BytecodeV1 => {
60 Ok(KnownMeta::ExpressionDeployerV2BytecodeV1)
61 }
62 KnownMagic::RainlangSourceV1 => Ok(KnownMeta::RainlangSourceV1),
63 KnownMagic::RaindexSignedContextOracleV1 => Ok(KnownMeta::RaindexSignedContextOracleV1),
64 _ => Err(Error::UnsupportedMeta),
65 }
66 }
67}
68
69#[derive(
71 Copy,
72 Clone,
73 Debug,
74 EnumIter,
75 PartialEq,
76 EnumString,
77 strum::Display,
78 serde::Serialize,
79 serde::Deserialize,
80)]
81#[strum(serialize_all = "kebab-case")]
82pub enum ContentType {
83 None,
84 #[serde(rename = "application/json")]
85 Json,
86 #[serde(rename = "application/cbor")]
87 Cbor,
88 #[serde(rename = "application/octet-stream")]
89 OctetStream,
90}
91
92#[derive(
94 Copy,
95 Clone,
96 Debug,
97 EnumIter,
98 PartialEq,
99 EnumString,
100 strum::Display,
101 serde::Serialize,
102 serde::Deserialize,
103)]
104#[serde(rename_all = "kebab-case")]
105#[strum(serialize_all = "kebab-case")]
106pub enum ContentEncoding {
107 None,
108 Identity,
109 Deflate,
110}
111
112impl ContentEncoding {
113 pub fn encode(&self, data: &[u8]) -> Vec<u8> {
115 match self {
116 ContentEncoding::None | ContentEncoding::Identity => data.to_vec(),
117 ContentEncoding::Deflate => deflate::deflate_bytes_zlib(data),
118 }
119 }
120
121 pub fn decode(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
123 Ok(match self {
124 ContentEncoding::None | ContentEncoding::Identity => data.to_vec(),
125 ContentEncoding::Deflate => match inflate::inflate_bytes_zlib(data) {
126 Ok(v) => v,
127 Err(error) => match inflate::inflate_bytes(data) {
128 Ok(v) => v,
129 Err(_) => Err(Error::InflateError(error))?,
130 },
131 },
132 })
133 }
134}
135
136#[derive(
138 Copy,
139 Clone,
140 Debug,
141 EnumIter,
142 PartialEq,
143 EnumString,
144 strum::Display,
145 serde::Serialize,
146 serde::Deserialize,
147)]
148#[serde(rename_all = "kebab-case")]
149#[strum(serialize_all = "kebab-case")]
150pub enum ContentLanguage {
151 None,
152 En,
153}
154
155#[derive(PartialEq, Debug, Clone)]
159pub struct RainMetaDocumentV1Item {
160 pub payload: serde_bytes::ByteBuf,
161 pub magic: KnownMagic,
162 pub content_type: ContentType,
163 pub content_encoding: ContentEncoding,
164 pub content_language: ContentLanguage,
165 pub schema: Option<String>,
169}
170
171impl TryFrom<RainMetaDocumentV1Item> for String {
173 type Error = Error;
174 fn try_from(value: RainMetaDocumentV1Item) -> Result<Self, Self::Error> {
175 Ok(String::from_utf8(value.unpack()?)?)
176 }
177}
178
179impl TryFrom<RainMetaDocumentV1Item> for Vec<u8> {
181 type Error = Error;
182 fn try_from(value: RainMetaDocumentV1Item) -> Result<Self, Self::Error> {
183 value.unpack()
184 }
185}
186
187impl RainMetaDocumentV1Item {
188 fn len(&self) -> usize {
189 let mut l = 2;
190 if !matches!(self.content_type, ContentType::None) {
191 l += 1;
192 }
193 if !matches!(self.content_encoding, ContentEncoding::None) {
194 l += 1;
195 }
196 if !matches!(self.content_language, ContentLanguage::None) {
197 l += 1;
198 }
199 if self.schema.is_some() {
200 l += 1;
201 }
202 l
203 }
204
205 pub fn hash(&self, as_rain_meta_document: bool) -> Result<[u8; 32], Error> {
207 if as_rain_meta_document {
208 Ok(keccak256(Self::cbor_encode_seq(
209 &vec![self.clone()],
210 KnownMagic::RainMetaDocumentV1,
211 )?)
212 .0)
213 } else {
214 Ok(keccak256(self.cbor_encode()?).0)
215 }
216 }
217
218 pub fn cbor_encode(&self) -> Result<Vec<u8>, Error> {
220 let mut bytes: Vec<u8> = vec![];
221 Ok(serde_cbor::to_writer(&mut bytes, &self).map(|_| bytes)?)
222 }
223
224 pub fn cbor_encode_seq(
226 seq: &Vec<RainMetaDocumentV1Item>,
227 magic: KnownMagic,
228 ) -> Result<Vec<u8>, Error> {
229 let mut bytes: Vec<u8> = magic.to_prefix_bytes().to_vec();
230 for item in seq {
231 serde_cbor::to_writer(&mut bytes, &item)?;
232 }
233 Ok(bytes)
234 }
235
236 pub fn cbor_decode(data: &[u8]) -> Result<Vec<RainMetaDocumentV1Item>, Error> {
238 let mut track: Vec<usize> = vec![];
239 let mut metas: Vec<RainMetaDocumentV1Item> = vec![];
240 let mut is_rain_document_meta = false;
241 let mut len = data.len();
242 if data.starts_with(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes()) {
243 is_rain_document_meta = true;
244 len -= 8;
245 }
246 let mut deserializer = match is_rain_document_meta {
247 true => serde_cbor::Deserializer::from_slice(&data[8..]),
248 false => serde_cbor::Deserializer::from_slice(data),
249 };
250 while match serde_cbor::Value::deserialize(&mut deserializer) {
251 Ok(cbor_map) => {
252 track.push(deserializer.byte_offset());
253 match serde_cbor::value::from_value(cbor_map) {
254 Ok(meta) => metas.push(meta),
255 Err(error) => Err(Error::SerdeCborError(error))?,
256 };
257 true
258 }
259 Err(error) => {
260 if error.is_eof() {
261 if error.offset() == len as u64 {
262 false
263 } else {
264 Err(Error::SerdeCborError(error))?
265 }
266 } else {
267 Err(Error::SerdeCborError(error))?
268 }
269 }
270 } {}
271
272 if metas.is_empty()
273 || track.is_empty()
274 || track.len() != metas.len()
275 || len != track[track.len() - 1]
276 {
277 Err(Error::CorruptMeta)?
278 }
279 Ok(metas)
280 }
281
282 pub fn unpack(&self) -> Result<Vec<u8>, Error> {
284 ContentEncoding::decode(&self.content_encoding, self.payload.as_ref())
285 }
286
287 pub fn unpack_into<T: TryFrom<Self, Error = Error>>(self) -> Result<T, Error> {
289 match self.magic {
290 KnownMagic::OpMetaV1
291 | KnownMagic::DotrainV1
292 | KnownMagic::RainlangV1
293 | KnownMagic::SolidityAbiV2
294 | KnownMagic::AuthoringMetaV1
295 | KnownMagic::AuthoringMetaV2
296 | KnownMagic::AddressList
297 | KnownMagic::InterpreterCallerMetaV1
298 | KnownMagic::ExpressionDeployerV2BytecodeV1
299 | KnownMagic::DotrainSourceV1
300 | KnownMagic::OrderBuilderStateV1
301 | KnownMagic::RainlangSourceV1
302 | KnownMagic::RaindexSignedContextOracleV1 => T::try_from(self),
303 _ => Err(Error::UnsupportedMeta)?,
304 }
305 }
306}
307
308impl Serialize for RainMetaDocumentV1Item {
309 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
310 let mut map = serializer.serialize_map(Some(self.len()))?;
311 map.serialize_entry(&0, &self.payload)?;
312 map.serialize_entry(&1, &(self.magic as u64))?;
313 match self.content_type {
314 ContentType::None => {}
315 content_type => map.serialize_entry(&2, &content_type)?,
316 }
317 match self.content_encoding {
318 ContentEncoding::None => {}
319 content_encoding => map.serialize_entry(&3, &content_encoding)?,
320 }
321 match self.content_language {
322 ContentLanguage::None => {}
323 content_language => map.serialize_entry(&4, &content_language)?,
324 }
325 if let Some(schema) = &self.schema {
326 map.serialize_entry(&(KnownMagic::OaSchema as u64), schema)?;
327 }
328 map.end()
329 }
330}
331
332impl<'de> Deserialize<'de> for RainMetaDocumentV1Item {
333 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
334 struct EncodedMap;
335 impl<'de> Visitor<'de> for EncodedMap {
336 type Value = RainMetaDocumentV1Item;
337
338 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
339 formatter.write_str("rain meta cbor encoded bytes")
340 }
341
342 fn visit_map<T: serde::de::MapAccess<'de>>(
343 self,
344 mut map: T,
345 ) -> Result<Self::Value, T::Error> {
346 const OA_SCHEMA_KEY: u64 = KnownMagic::OaSchema as u64;
347 let mut payload = None;
348 let mut magic: Option<u64> = None;
349 let mut content_type = None;
350 let mut content_encoding = None;
351 let mut content_language = None;
352 let mut schema = None;
353 while match map.next_key::<u64>() {
354 Ok(Some(key)) => {
355 match key {
356 0 => payload = Some(map.next_value()?),
357 1 => magic = Some(map.next_value()?),
358 2 => content_type = Some(map.next_value()?),
359 3 => content_encoding = Some(map.next_value()?),
360 4 => content_language = Some(map.next_value()?),
361 OA_SCHEMA_KEY => schema = Some(map.next_value()?),
362 other => Err(serde::de::Error::custom(format!(
363 "found unexpected key in the map: {other}"
364 )))?,
365 };
366 true
367 }
368 Ok(None) => false,
369 Err(error) => Err(error)?,
370 } {}
371 let payload = payload.ok_or_else(|| serde::de::Error::missing_field("payload"))?;
372 let magic = match magic
373 .ok_or_else(|| serde::de::Error::missing_field("magic number"))?
374 .try_into()
375 {
376 Ok(m) => m,
377 _ => Err(serde::de::Error::custom("unknown magic number"))?,
378 };
379 let content_type = content_type.unwrap_or(ContentType::None);
380 let content_encoding = content_encoding.unwrap_or(ContentEncoding::None);
381 let content_language = content_language.unwrap_or(ContentLanguage::None);
382
383 Ok(RainMetaDocumentV1Item {
384 payload,
385 magic,
386 content_type,
387 content_encoding,
388 content_language,
389 schema,
390 })
391 }
392 }
393 deserializer.deserialize_map(EncodedMap)
394 }
395}
396
397pub async fn search(hash: &str, subgraphs: &Vec<String>) -> Result<query::MetaResponse, Error> {
399 if subgraphs.is_empty() {
401 return Err(Error::NoRecordFound);
402 }
403 let request_body = query::MetaQuery::build_query(query::meta_query::Variables {
404 hash: Some(hash.to_ascii_lowercase()),
405 });
406 let mut promises = vec![];
407
408 let client = Arc::new(Client::builder().build().map_err(Error::ReqwestError)?);
409 for url in subgraphs {
410 promises.push(Box::pin(query::process_meta_query(
411 client.clone(),
412 &request_body,
413 url,
414 )));
415 }
416 let response_value = future::select_ok(promises.drain(..)).await?.0;
417 Ok(response_value)
418}
419
420pub async fn search_deployer(
422 hash: &str,
423 subgraphs: &Vec<String>,
424) -> Result<DeployerResponse, Error> {
425 if subgraphs.is_empty() {
427 return Err(Error::NoRecordFound);
428 }
429 let request_body = query::DeployerQuery::build_query(query::deployer_query::Variables {
430 hash: Some(hash.to_ascii_lowercase()),
431 });
432 let mut promises = vec![];
433
434 let client = Arc::new(Client::builder().build().map_err(Error::ReqwestError)?);
435 for url in subgraphs {
436 promises.push(Box::pin(query::process_deployer_query(
437 client.clone(),
438 &request_body,
439 url,
440 )));
441 }
442 let response_value = future::select_ok(promises.drain(..)).await?.0;
443 Ok(response_value)
444}
445
446pub async fn implements_i_described_by_meta_v1<P: Provider>(
448 provider: &P,
449 contract_address: Address,
450) -> bool {
451 if !supports_erc165(provider, contract_address)
452 .await
453 .unwrap_or(false)
454 {
455 return false;
456 }
457
458 let interface_id_res = IDescribedByMetaV1::IDescribedByMetaV1Calls::xor_selectors();
459 if interface_id_res.is_err() {
460 return false;
461 }
462
463 let call = IERC165::supportsInterfaceCall {
464 interfaceID: interface_id_res.unwrap().into(),
465 };
466 let tx = TransactionRequest::default()
467 .to(contract_address)
468 .input(call.abi_encode().into());
469 match provider.call(tx).await {
470 Ok(bytes) => IERC165::supportsInterfaceCall::abi_decode_returns(&bytes).unwrap_or(false),
471 Err(_) => false,
472 }
473}
474
475#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Default)]
477#[serde(rename_all = "camelCase")]
478pub struct NPE2Deployer {
479 #[serde(with = "serde_bytes")]
481 pub meta_hash: Vec<u8>,
482 #[serde(with = "serde_bytes")]
484 pub meta_bytes: Vec<u8>,
485 #[serde(with = "serde_bytes")]
487 pub bytecode: Vec<u8>,
488 #[serde(with = "serde_bytes")]
490 pub parser: Vec<u8>,
491 #[serde(with = "serde_bytes")]
493 pub store: Vec<u8>,
494 #[serde(with = "serde_bytes")]
496 pub interpreter: Vec<u8>,
497 pub authoring_meta: Option<AuthoringMeta>,
499}
500
501impl NPE2Deployer {
502 pub fn is_corrupt(&self) -> bool {
503 if self.meta_hash.is_empty() {
504 return true;
505 }
506 if self.meta_bytes.is_empty() {
507 return true;
508 }
509 if self.bytecode.is_empty() {
510 return true;
511 }
512 if self.parser.is_empty() {
513 return true;
514 }
515 if self.store.is_empty() {
516 return true;
517 }
518 if self.interpreter.is_empty() {
519 return true;
520 }
521 false
522 }
523}
524
525#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
583pub struct Store {
584 subgraphs: Vec<String>,
585 cache: HashMap<Vec<u8>, Vec<u8>>,
586 dotrain_cache: HashMap<String, Vec<u8>>,
587 deployer_cache: HashMap<Vec<u8>, NPE2Deployer>,
588 deployer_hash_map: HashMap<Vec<u8>, Vec<u8>>,
589}
590
591impl Default for Store {
592 fn default() -> Self {
593 Store::new()
594 }
595}
596
597impl Store {
598 pub fn new() -> Store {
601 Store {
602 subgraphs: vec![],
603 cache: HashMap::new(),
604 dotrain_cache: HashMap::new(),
605 deployer_cache: HashMap::new(),
606 deployer_hash_map: HashMap::new(),
607 }
608 }
609
610 pub fn create(
613 subgraphs: &Vec<String>,
614 cache: &HashMap<Vec<u8>, Vec<u8>>,
615 deployer_cache: &HashMap<Vec<u8>, NPE2Deployer>,
616 dotrain_cache: &HashMap<String, Vec<u8>>,
617 ) -> Store {
618 let mut store = Store::new();
619 store.add_subgraphs(subgraphs);
620 for (hash, bytes) in cache {
621 store.update_with(hash, bytes);
622 }
623 for (hash, deployer) in deployer_cache {
624 store.set_deployer(hash, deployer, None);
625 }
626 for (uri, hash) in dotrain_cache {
627 if !store.dotrain_cache.contains_key(uri) && store.cache.contains_key(hash) {
628 store.dotrain_cache.insert(uri.clone(), hash.clone());
629 }
630 }
631 store
632 }
633
634 pub fn subgraphs(&self) -> &Vec<String> {
636 &self.subgraphs
637 }
638
639 pub fn add_subgraphs(&mut self, subgraphs: &Vec<String>) {
641 for sg in subgraphs {
642 if !self.subgraphs.contains(sg) {
643 self.subgraphs.push(sg.to_string());
644 }
645 }
646 }
647
648 pub fn cache(&self) -> &HashMap<Vec<u8>, Vec<u8>> {
650 &self.cache
651 }
652
653 pub fn get_meta(&self, hash: &[u8]) -> Option<&Vec<u8>> {
655 self.cache.get(hash)
656 }
657
658 pub fn deployer_cache(&self) -> &HashMap<Vec<u8>, NPE2Deployer> {
660 &self.deployer_cache
661 }
662
663 pub fn get_deployer(&self, hash: &[u8]) -> Option<&NPE2Deployer> {
665 if self.deployer_cache.contains_key(hash) {
666 self.deployer_cache.get(hash)
667 } else if let Some(h) = self.deployer_hash_map.get(hash) {
668 self.deployer_cache.get(h)
669 } else {
670 None
671 }
672 }
673
674 pub async fn search_deployer(&mut self, hash: &[u8]) -> Option<&NPE2Deployer> {
676 match search_deployer(&hex::encode_prefixed(hash), &self.subgraphs).await {
677 Ok(res) => {
678 self.cache
679 .insert(res.meta_hash.clone(), res.meta_bytes.clone());
680 let authoring_meta = res.get_authoring_meta();
681 self.deployer_cache.insert(
682 res.bytecode_meta_hash.clone(),
683 NPE2Deployer {
684 meta_hash: res.meta_hash.clone(),
685 meta_bytes: res.meta_bytes,
686 bytecode: res.bytecode,
687 parser: res.parser,
688 store: res.store,
689 interpreter: res.interpreter,
690 authoring_meta,
691 },
692 );
693 self.deployer_hash_map.insert(res.tx_hash, res.meta_hash);
694 self.deployer_cache.get(hash)
695 }
696 Err(_e) => None,
697 }
698 }
699
700 pub async fn search_deployer_check(&mut self, hash: &[u8]) -> Option<&NPE2Deployer> {
703 if self.deployer_cache.contains_key(hash) {
704 self.get_deployer(hash)
705 } else if self.deployer_hash_map.contains_key(hash) {
706 let b_hash = self.deployer_hash_map.get(hash).unwrap();
707 self.get_deployer(b_hash)
708 } else {
709 self.search_deployer(hash).await
710 }
711 }
712
713 pub fn set_deployer_from_query_response(
715 &mut self,
716 deployer_query_response: DeployerResponse,
717 ) -> NPE2Deployer {
718 let authoring_meta = deployer_query_response.get_authoring_meta();
719 let tx_hash = deployer_query_response.tx_hash;
720 let bytecode_meta_hash = deployer_query_response.bytecode_meta_hash;
721 let result = NPE2Deployer {
722 meta_hash: deployer_query_response.meta_hash.clone(),
723 meta_bytes: deployer_query_response.meta_bytes,
724 bytecode: deployer_query_response.bytecode,
725 parser: deployer_query_response.parser,
726 store: deployer_query_response.store,
727 interpreter: deployer_query_response.interpreter,
728 authoring_meta,
729 };
730 self.cache
731 .insert(deployer_query_response.meta_hash, result.meta_bytes.clone());
732 self.deployer_hash_map
733 .insert(tx_hash, bytecode_meta_hash.clone());
734 self.deployer_cache
735 .insert(bytecode_meta_hash, result.clone());
736 result
737 }
738
739 pub fn set_deployer(
742 &mut self,
743 hash: &[u8],
744 npe2_deployer: &NPE2Deployer,
745 tx_hash: Option<&[u8]>,
746 ) {
747 self.cache.insert(
748 npe2_deployer.meta_hash.clone(),
749 npe2_deployer.meta_bytes.clone(),
750 );
751 self.deployer_cache
752 .insert(hash.to_vec(), npe2_deployer.clone());
753 if let Some(v) = tx_hash {
754 self.deployer_hash_map.insert(v.to_vec(), hash.to_vec());
755 }
756 }
757
758 pub fn dotrain_cache(&self) -> &HashMap<String, Vec<u8>> {
760 &self.dotrain_cache
761 }
762
763 pub fn get_dotrain_hash(&self, uri: &str) -> Option<&Vec<u8>> {
765 self.dotrain_cache.get(uri)
766 }
767
768 pub fn get_dotrain_uri(&self, hash: &[u8]) -> Option<&String> {
770 for (uri, h) in &self.dotrain_cache {
771 if h == hash {
772 return Some(uri);
773 }
774 }
775 None
776 }
777
778 pub fn get_dotrain_meta(&self, uri: &str) -> Option<&Vec<u8>> {
780 self.get_meta(self.dotrain_cache.get(uri)?)
781 }
782
783 pub fn delete_dotrain(&mut self, uri: &str, keep_meta: bool) {
785 if let Some(kv) = self.dotrain_cache.remove_entry(uri) {
786 if !keep_meta {
787 self.cache.remove(&kv.1);
788 }
789 };
790 }
791
792 pub fn merge(&mut self, other: &Store) {
794 self.add_subgraphs(&other.subgraphs);
795 for (hash, bytes) in &other.cache {
796 if !self.cache.contains_key(hash) {
797 self.cache.insert(hash.clone(), bytes.clone());
798 }
799 }
800 for (hash, deployer) in &other.deployer_cache {
801 if !self.deployer_cache.contains_key(hash) {
802 self.deployer_cache.insert(hash.clone(), deployer.clone());
803 }
804 }
805 for (hash, tx_hash) in &other.deployer_hash_map {
806 self.deployer_hash_map.insert(hash.clone(), tx_hash.clone());
807 }
808 for (uri, hash) in &other.dotrain_cache {
809 if !self.dotrain_cache.contains_key(uri) {
810 self.dotrain_cache.insert(uri.clone(), hash.clone());
811 }
812 }
813 }
814
815 pub async fn update(&mut self, hash: &[u8]) -> Option<&Vec<u8>> {
818 if let Ok(meta) = search(&hex::encode_prefixed(hash), &self.subgraphs).await {
819 self.store_content(&meta.bytes);
820 self.cache.insert(hash.to_vec(), meta.bytes);
821 self.get_meta(hash)
822 } else {
823 None
824 }
825 }
826
827 pub async fn update_check(&mut self, hash: &[u8]) -> Option<&Vec<u8>> {
829 if !self.cache.contains_key(hash) {
830 self.update(hash).await
831 } else {
832 self.get_meta(hash)
833 }
834 }
835
836 pub fn update_with(&mut self, hash: &[u8], bytes: &[u8]) -> Option<&Vec<u8>> {
839 if !self.cache.contains_key(hash) {
840 if keccak256(bytes).0 == hash {
841 self.store_content(bytes);
842 self.cache.insert(hash.to_vec(), bytes.to_vec());
843 self.cache.get(hash)
844 } else {
845 None
846 }
847 } else {
848 self.get_meta(hash)
849 }
850 }
851
852 pub fn set_dotrain(
857 &mut self,
858 text: &str,
859 uri: &str,
860 keep_old: bool,
861 ) -> Result<(Vec<u8>, Vec<u8>), Error> {
862 let bytes = RainMetaDocumentV1Item {
863 payload: serde_bytes::ByteBuf::from(text.as_bytes()),
864 magic: KnownMagic::DotrainV1,
865 content_type: ContentType::OctetStream,
866 content_encoding: ContentEncoding::None,
867 content_language: ContentLanguage::None,
868 schema: None,
869 }
870 .cbor_encode()?;
871 let new_hash = keccak256(&bytes).0.to_vec();
872 if let Some(h) = self.dotrain_cache.get(uri) {
873 let old_hash = h.clone();
874 if new_hash == old_hash {
875 self.cache.insert(new_hash.clone(), bytes);
876 Ok((new_hash, vec![]))
877 } else {
878 self.cache.insert(new_hash.clone(), bytes);
879 self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
880 if !keep_old {
881 self.cache.remove(&old_hash);
882 }
883 Ok((new_hash, old_hash))
884 }
885 } else {
886 self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
887 self.cache.insert(new_hash.clone(), bytes);
888 Ok((new_hash, vec![]))
889 }
890 }
891
892 fn store_content(&mut self, bytes: &[u8]) {
896 if let Ok(meta_maps) = RainMetaDocumentV1Item::cbor_decode(bytes) {
897 if bytes.starts_with(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes()) {
898 for meta_map in &meta_maps {
899 if let Ok(encoded_bytes) = meta_map.cbor_encode() {
900 self.cache
901 .insert(keccak256(&encoded_bytes).0.to_vec(), encoded_bytes);
902 }
903 }
904 }
905 }
906 }
907}
908
909pub fn str_to_bytes32(text: &str) -> Result<[u8; 32], Error> {
911 let bytes: &[u8] = text.as_bytes();
912 if bytes.len() > 32 {
913 return Err(Error::BiggerThan32Bytes);
914 }
915 let mut b32 = [0u8; 32];
916 b32[..bytes.len()].copy_from_slice(bytes);
917 Ok(b32)
918}
919
920pub fn bytes32_to_str(bytes: &[u8; 32]) -> Result<&str, Error> {
922 let mut len = 32;
923 if let Some((pos, _)) = itertools::Itertools::find_position(&mut bytes.iter(), |b| **b == 0u8) {
924 len = pos;
925 };
926 Ok(std::str::from_utf8(&bytes[..len])?)
927}
928
929#[cfg(all(test, not(target_family = "wasm")))]
930mod tests {
931 use super::{
932 *, bytes32_to_str, magic::KnownMagic, str_to_bytes32, types::authoring::v1::AuthoringMeta,
933 ContentEncoding, ContentLanguage, ContentType, Error, RainMetaDocumentV1Item,
934 };
935 use alloy::providers::ProviderBuilder;
936 use alloy::{providers::mock::Asserter, rpc::json_rpc::ErrorPayload, sol_types::SolType};
937 use serde_json::json;
938
939 #[test]
942 fn authoring_meta_roundtrip() -> Result<(), Error> {
943 let authoring_meta_content = r#"[
944 {
945 "word": "stack",
946 "description": "Copies an existing value from the stack.",
947 "operandParserOffset": 16
948 },
949 {
950 "word": "constant",
951 "description": "Copies a constant value onto the stack.",
952 "operandParserOffset": 16
953 }
954 ]"#;
955 let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
956
957 let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
959 let expected_abi_encoded = <alloy::sol!((bytes32, uint8, string)[])>::abi_encode(&vec![
960 (
961 str_to_bytes32("stack")?,
962 16u8,
963 "Copies an existing value from the stack.".to_string(),
964 ),
965 (
966 str_to_bytes32("constant")?,
967 16u8,
968 "Copies a constant value onto the stack.".to_string(),
969 ),
970 ]);
971 assert_eq!(authoring_meta_abi_encoded, expected_abi_encoded);
973
974 let meta_map = RainMetaDocumentV1Item {
975 payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
976 magic: KnownMagic::AuthoringMetaV1,
977 content_type: ContentType::Cbor,
978 content_encoding: ContentEncoding::None,
979 content_language: ContentLanguage::None,
980 schema: None,
981 };
982 let cbor_encoded = meta_map.cbor_encode()?;
983
984 assert_eq!(cbor_encoded[0], 0xa3);
986 assert_eq!(cbor_encoded[1], 0x00);
988 assert_eq!(cbor_encoded[2], 0b010_11001);
990 assert_eq!(cbor_encoded[3], 0b000_00010);
991 assert_eq!(cbor_encoded[4], 0b000_00000);
992 assert_eq!(cbor_encoded[5..517], authoring_meta_abi_encoded);
994 assert_eq!(cbor_encoded[517], 0x01);
996 assert_eq!(cbor_encoded[518], 0b000_11011);
998 assert_eq!(
1000 &cbor_encoded[519..527],
1001 KnownMagic::AuthoringMetaV1.to_prefix_bytes()
1002 );
1003 assert_eq!(cbor_encoded[527], 0x02);
1005 assert_eq!(cbor_encoded[528], 0b011_10000);
1007 assert_eq!(&cbor_encoded[529..], "application/cbor".as_bytes());
1009
1010 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1012 assert_eq!(cbor_decoded.len(), 1);
1014 assert_eq!(cbor_decoded[0], meta_map);
1016
1017 Ok(())
1018 }
1019
1020 #[test]
1023 fn dotrain_meta_roundtrip() -> Result<(), Error> {
1024 let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
1025 let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
1026
1027 let content_encoding = ContentEncoding::Deflate;
1028 let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
1029
1030 let meta_map = RainMetaDocumentV1Item {
1031 payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
1032 magic: KnownMagic::DotrainV1,
1033 content_type: ContentType::OctetStream,
1034 content_encoding,
1035 content_language: ContentLanguage::En,
1036 schema: None,
1037 };
1038 let cbor_encoded = meta_map.cbor_encode()?;
1039
1040 assert_eq!(cbor_encoded[0], 0xa5);
1042 assert_eq!(cbor_encoded[1], 0x00);
1044 assert_eq!(cbor_encoded[2], 0b010_11000);
1046 assert_eq!(cbor_encoded[3], 0b001_00100);
1047 assert_eq!(cbor_encoded[4..40], deflated_payload);
1050 assert_eq!(cbor_encoded[40], 0x01);
1052 assert_eq!(cbor_encoded[41], 0b000_11011);
1054 assert_eq!(
1056 &cbor_encoded[42..50],
1057 KnownMagic::DotrainV1.to_prefix_bytes()
1058 );
1059 assert_eq!(cbor_encoded[50], 0x02);
1061 assert_eq!(cbor_encoded[51], 0b011_11000);
1063 assert_eq!(cbor_encoded[52], 0b000_11000);
1064 assert_eq!(&cbor_encoded[53..77], "application/octet-stream".as_bytes());
1066 assert_eq!(cbor_encoded[77], 0x03);
1068 assert_eq!(cbor_encoded[78], 0b011_00111);
1070 assert_eq!(&cbor_encoded[79..86], "deflate".as_bytes());
1072 assert_eq!(cbor_encoded[86], 0x04);
1074 assert_eq!(cbor_encoded[87], 0b011_00010);
1076 assert_eq!(&cbor_encoded[88..], "en".as_bytes());
1078
1079 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1081 assert_eq!(cbor_decoded.len(), 1);
1083 assert_eq!(cbor_decoded[0], meta_map);
1085
1086 Ok(())
1087 }
1088
1089 #[test]
1092 fn meta_seq_roundtrip() -> Result<(), Error> {
1093 let authoring_meta_content = r#"[
1094 {
1095 "word": "stack",
1096 "description": "Copies an existing value from the stack.",
1097 "operandParserOffset": 16
1098 },
1099 {
1100 "word": "constant",
1101 "description": "Copies a constant value onto the stack.",
1102 "operandParserOffset": 16
1103 }
1104 ]"#;
1105 let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
1106 let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
1107 let meta_map_1 = RainMetaDocumentV1Item {
1108 payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
1109 magic: KnownMagic::AuthoringMetaV1,
1110 content_type: ContentType::Cbor,
1111 content_encoding: ContentEncoding::None,
1112 content_language: ContentLanguage::None,
1113 schema: None,
1114 };
1115
1116 let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
1117 let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
1118 let content_encoding = ContentEncoding::Deflate;
1119 let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
1120 let meta_map_2 = RainMetaDocumentV1Item {
1121 payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
1122 magic: KnownMagic::DotrainV1,
1123 content_type: ContentType::OctetStream,
1124 content_encoding,
1125 content_language: ContentLanguage::En,
1126 schema: None,
1127 };
1128
1129 let cbor_encoded = RainMetaDocumentV1Item::cbor_encode_seq(
1131 &vec![meta_map_1.clone(), meta_map_2.clone()],
1132 KnownMagic::RainMetaDocumentV1,
1133 )?;
1134
1135 assert_eq!(
1137 &cbor_encoded[0..8],
1138 KnownMagic::RainMetaDocumentV1.to_prefix_bytes()
1139 );
1140
1141 assert_eq!(cbor_encoded[8], 0xa3);
1144 assert_eq!(cbor_encoded[9], 0x00);
1146 assert_eq!(cbor_encoded[10], 0b010_11001);
1148 assert_eq!(cbor_encoded[11], 0b000_00010);
1149 assert_eq!(cbor_encoded[12], 0b000_00000);
1150 assert_eq!(cbor_encoded[13..525], authoring_meta_abi_encoded);
1152 assert_eq!(cbor_encoded[525], 0x01);
1154 assert_eq!(cbor_encoded[526], 0b000_11011);
1156 assert_eq!(
1158 &cbor_encoded[527..535],
1159 KnownMagic::AuthoringMetaV1.to_prefix_bytes()
1160 );
1161 assert_eq!(cbor_encoded[535], 0x02);
1163 assert_eq!(cbor_encoded[536], 0b011_10000);
1165 assert_eq!(&cbor_encoded[537..553], "application/cbor".as_bytes());
1167
1168 assert_eq!(cbor_encoded[553], 0xa5);
1171 assert_eq!(cbor_encoded[554], 0x00);
1173 assert_eq!(cbor_encoded[555], 0b010_11000);
1175 assert_eq!(cbor_encoded[556], 0b001_00100);
1176 assert_eq!(cbor_encoded[557..593], deflated_payload);
1179 assert_eq!(cbor_encoded[593], 0x01);
1181 assert_eq!(cbor_encoded[594], 0b000_11011);
1183 assert_eq!(
1185 &cbor_encoded[595..603],
1186 KnownMagic::DotrainV1.to_prefix_bytes()
1187 );
1188 assert_eq!(cbor_encoded[603], 0x02);
1190 assert_eq!(cbor_encoded[604], 0b011_11000);
1192 assert_eq!(cbor_encoded[605], 0b000_11000);
1193 assert_eq!(
1195 &cbor_encoded[606..630],
1196 "application/octet-stream".as_bytes()
1197 );
1198 assert_eq!(cbor_encoded[630], 0x03);
1200 assert_eq!(cbor_encoded[631], 0b011_00111);
1202 assert_eq!(&cbor_encoded[632..639], "deflate".as_bytes());
1204 assert_eq!(cbor_encoded[639], 0x04);
1206 assert_eq!(cbor_encoded[640], 0b011_00010);
1208 assert_eq!(&cbor_encoded[641..], "en".as_bytes());
1210
1211 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1213 assert_eq!(cbor_decoded.len(), 2);
1215
1216 assert_eq!(cbor_decoded[0], meta_map_1);
1218 assert_eq!(cbor_decoded[1], meta_map_2);
1220
1221 Ok(())
1222 }
1223
1224 #[test]
1225 fn test_bytes32_to_str() {
1226 let text_bytes_list = vec![
1227 (
1228 "",
1229 hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1230 ),
1231 (
1232 "A",
1233 hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1234 ),
1235 (
1236 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1237 hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1238 ),
1239 (
1240 "!@#$%^&*(),./;'[]",
1241 hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1242 ),
1243 ];
1244
1245 for (text, bytes) in text_bytes_list {
1246 assert_eq!(text, bytes32_to_str(&bytes).unwrap());
1247 }
1248 }
1249
1250 #[test]
1251 fn test_str_to_bytes32() {
1252 let text_bytes_list = vec![
1253 (
1254 "",
1255 hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1256 ),
1257 (
1258 "A",
1259 hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1260 ),
1261 (
1262 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1263 hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1264 ),
1265 (
1266 "!@#$%^&*(),./;'[]",
1267 hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1268 ),
1269 ];
1270
1271 for (text, bytes) in text_bytes_list {
1272 assert_eq!(bytes, str_to_bytes32(text).unwrap());
1273 }
1274 }
1275
1276 #[test]
1277 fn test_str_to_bytes32_long() {
1278 assert!(matches!(
1279 str_to_bytes32("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456").unwrap_err(),
1280 Error::BiggerThan32Bytes
1281 ));
1282 }
1283
1284 #[tokio::test]
1285 async fn test_implements_i_describe_by_meta_v1() {
1286 async fn new_server_client() -> (Asserter, impl Provider) {
1288 let asserter = Asserter::new();
1289 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
1290
1291 asserter.push_success(
1293 &"0x0000000000000000000000000000000000000000000000000000000000000001",
1294 );
1295 asserter.push_success(
1296 &"0x0000000000000000000000000000000000000000000000000000000000000000",
1297 );
1298
1299 (asserter, provider)
1300 }
1301
1302 let address = Address::random();
1303
1304 let (asserter, provider) = new_server_client().await;
1306 asserter
1307 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
1308 let result = implements_i_described_by_meta_v1(&provider, address).await;
1309 assert!(result);
1310
1311 let (asserter, provider) = new_server_client().await;
1313 asserter
1314 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
1315 let result = implements_i_described_by_meta_v1(&provider, address).await;
1316 assert!(!result);
1317
1318 let (asserter, provider) = new_server_client().await;
1320 asserter.push_failure(ErrorPayload {
1321 code: -32003,
1322 message: "execution reverted".into(),
1323 data: Some(serde_json::value::to_raw_value(&json!("0x00")).unwrap()),
1324 });
1325 let result = implements_i_described_by_meta_v1(&provider, address).await;
1326 assert!(!result);
1327 }
1328
1329 #[test]
1333 fn oa_schema_map_key_roundtrip() -> Result<(), Error> {
1334 let payload = vec![0x01, 0x02, 0x03];
1335 let schema = "QmSchemaHash1234567890abcdefghijklmnopqrstuvwx".to_string();
1338 assert_eq!(schema.len(), 46);
1339
1340 let meta_map = RainMetaDocumentV1Item {
1341 payload: serde_bytes::ByteBuf::from(payload.clone()),
1342 magic: KnownMagic::OaStructure,
1343 content_type: ContentType::Json,
1344 content_encoding: ContentEncoding::Deflate,
1345 content_language: ContentLanguage::None,
1346 schema: Some(schema.clone()),
1347 };
1348 let cbor_encoded = meta_map.cbor_encode()?;
1349
1350 assert_eq!(cbor_encoded[0], 0xa5);
1352 assert_eq!(cbor_encoded[1], 0x00);
1354 assert_eq!(cbor_encoded[2], 0b010_00011);
1356 assert_eq!(cbor_encoded[3..6], payload);
1358 assert_eq!(cbor_encoded[6], 0x01);
1360 assert_eq!(cbor_encoded[7], 0b000_11011);
1362 assert_eq!(
1364 &cbor_encoded[8..16],
1365 KnownMagic::OaStructure.to_prefix_bytes()
1366 );
1367 assert_eq!(cbor_encoded[16], 0x02);
1369 assert_eq!(cbor_encoded[17], 0b011_10000);
1371 assert_eq!(&cbor_encoded[18..34], "application/json".as_bytes());
1372 assert_eq!(cbor_encoded[34], 0x03);
1374 assert_eq!(cbor_encoded[35], 0b011_00111);
1376 assert_eq!(&cbor_encoded[36..43], "deflate".as_bytes());
1377 assert_eq!(cbor_encoded[43], 0b000_11011);
1379 assert_eq!(
1380 &cbor_encoded[44..52],
1381 KnownMagic::OaSchema.to_prefix_bytes()
1382 );
1383 assert_eq!(cbor_encoded[52], 0b011_11000);
1385 assert_eq!(cbor_encoded[53], 46);
1386 assert_eq!(&cbor_encoded[54..], schema.as_bytes());
1388
1389 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1391 assert_eq!(cbor_decoded.len(), 1);
1393 assert_eq!(cbor_decoded[0], meta_map);
1395
1396 Ok(())
1397 }
1398
1399 #[test]
1402 fn no_schema_key_encodes_as_before_roundtrip() -> Result<(), Error> {
1403 let payload = vec![0x0a, 0x0b];
1404 let meta_map = RainMetaDocumentV1Item {
1405 payload: serde_bytes::ByteBuf::from(payload.clone()),
1406 magic: KnownMagic::OaStructure,
1407 content_type: ContentType::None,
1408 content_encoding: ContentEncoding::None,
1409 content_language: ContentLanguage::None,
1410 schema: None,
1411 };
1412 let cbor_encoded = meta_map.cbor_encode()?;
1413
1414 assert_eq!(cbor_encoded[0], 0xa2);
1416 assert_eq!(cbor_encoded[1], 0x00);
1418 assert_eq!(cbor_encoded[2], 0b010_00010);
1420 assert_eq!(cbor_encoded[3..5], payload);
1422 assert_eq!(cbor_encoded[5], 0x01);
1424 assert_eq!(cbor_encoded[6], 0b000_11011);
1426 assert_eq!(
1428 &cbor_encoded[7..],
1429 KnownMagic::OaStructure.to_prefix_bytes()
1430 );
1431
1432 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1433 assert_eq!(cbor_decoded.len(), 1);
1434 assert_eq!(cbor_decoded[0], meta_map);
1435
1436 Ok(())
1437 }
1438
1439 #[test]
1442 fn non_oa_schema_extra_map_key_errors() -> Result<(), Error> {
1443 let mut bytes: Vec<u8> = vec![
1446 0xa3, 0x00, 0x41, 0xff, 0x01, 0x1b,
1450 ];
1451 bytes.extend_from_slice(&KnownMagic::OaStructure.to_prefix_bytes());
1452 bytes.push(0x1b);
1454 bytes.extend_from_slice(&KnownMagic::OaHashList.to_prefix_bytes());
1455 bytes.extend_from_slice(&[0x62, 0x68, 0x69]);
1457
1458 let result = RainMetaDocumentV1Item::cbor_decode(&bytes);
1459 assert!(matches!(result, Err(Error::SerdeCborError(_))));
1460
1461 Ok(())
1462 }
1463
1464 fn plain_item(magic: KnownMagic, payload: Vec<u8>) -> RainMetaDocumentV1Item {
1465 RainMetaDocumentV1Item {
1466 payload: serde_bytes::ByteBuf::from(payload),
1467 magic,
1468 content_type: ContentType::None,
1469 content_encoding: ContentEncoding::None,
1470 content_language: ContentLanguage::None,
1471 schema: None,
1472 }
1473 }
1474
1475 fn sample_authoring_doc() -> (AuthoringMeta, Vec<u8>) {
1478 let authoring_meta: AuthoringMeta = serde_json::from_str(
1479 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
1480 )
1481 .unwrap();
1482 let abi = authoring_meta.abi_encode_validate().unwrap();
1483 let item = RainMetaDocumentV1Item {
1484 payload: serde_bytes::ByteBuf::from(abi),
1485 magic: KnownMagic::AuthoringMetaV1,
1486 content_type: ContentType::Cbor,
1487 content_encoding: ContentEncoding::None,
1488 content_language: ContentLanguage::None,
1489 schema: None,
1490 };
1491 let doc =
1492 RainMetaDocumentV1Item::cbor_encode_seq(&vec![item], KnownMagic::RainMetaDocumentV1)
1493 .unwrap();
1494 (authoring_meta, doc)
1495 }
1496
1497 fn sample_dotrain_item() -> RainMetaDocumentV1Item {
1498 RainMetaDocumentV1Item {
1499 payload: serde_bytes::ByteBuf::from("some dotrain body".as_bytes()),
1500 magic: KnownMagic::DotrainV1,
1501 content_type: ContentType::OctetStream,
1502 content_encoding: ContentEncoding::None,
1503 content_language: ContentLanguage::None,
1504 schema: None,
1505 }
1506 }
1507
1508 fn handwritten_map() -> Vec<u8> {
1511 vec![
1512 0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b, 0xff, 0xda, 0xc2, 0xf2, 0xf3, 0x7b, 0xe8, 0x94, ]
1518 }
1519
1520 #[test]
1524 fn test_hash_bare_vs_document() -> Result<(), Error> {
1525 let map_bytes = handwritten_map();
1526 let mut doc_bytes: Vec<u8> = vec![0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74];
1527 doc_bytes.extend_from_slice(&map_bytes);
1528
1529 let item = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1530 assert_eq!(item.hash(false)?, keccak256(&map_bytes).0);
1531 assert_eq!(item.hash(true)?, keccak256(&doc_bytes).0);
1532 assert_ne!(item.hash(false)?, item.hash(true)?);
1533 Ok(())
1534 }
1535
1536 #[test]
1538 fn test_cbor_decode_empty_is_corrupt() {
1539 assert!(matches!(
1540 RainMetaDocumentV1Item::cbor_decode(&[]),
1541 Err(Error::CorruptMeta)
1542 ));
1543 let prefix = KnownMagic::RainMetaDocumentV1.to_prefix_bytes();
1544 assert!(matches!(
1545 RainMetaDocumentV1Item::cbor_decode(&prefix),
1546 Err(Error::CorruptMeta)
1547 ));
1548 }
1549
1550 #[test]
1553 fn test_cbor_decode_trailing_truncated_is_corrupt() {
1554 let mut bytes = handwritten_map();
1555 bytes.push(0x1b); assert!(matches!(
1557 RainMetaDocumentV1Item::cbor_decode(&bytes),
1558 Err(Error::CorruptMeta)
1559 ));
1560 }
1561
1562 #[test]
1565 fn test_cbor_decode_trailing_garbage_errors() {
1566 let mut bytes = handwritten_map();
1567 bytes.push(0xff); assert!(matches!(
1569 RainMetaDocumentV1Item::cbor_decode(&bytes),
1570 Err(Error::SerdeCborError(_))
1571 ));
1572 }
1573
1574 #[test]
1576 fn test_cbor_decode_missing_payload_errors() {
1577 let mut bytes: Vec<u8> = vec![0xa1, 0x01, 0x1b]; bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1579 assert!(matches!(
1580 RainMetaDocumentV1Item::cbor_decode(&bytes),
1581 Err(Error::SerdeCborError(_))
1582 ));
1583 }
1584
1585 #[test]
1587 fn test_cbor_decode_missing_magic_errors() {
1588 let bytes: Vec<u8> = vec![0xa1, 0x00, 0x41, 0x01]; assert!(matches!(
1590 RainMetaDocumentV1Item::cbor_decode(&bytes),
1591 Err(Error::SerdeCborError(_))
1592 ));
1593 }
1594
1595 #[test]
1597 fn test_cbor_decode_unknown_magic_errors() {
1598 let mut bytes: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1599 bytes.extend_from_slice(&0xdeadbeefdeadbeefu64.to_be_bytes());
1600 assert!(matches!(
1601 RainMetaDocumentV1Item::cbor_decode(&bytes),
1602 Err(Error::SerdeCborError(_))
1603 ));
1604 }
1605
1606 #[test]
1610 fn test_cbor_decode_handwritten_document_magic_item() -> Result<(), Error> {
1611 let bytes: Vec<u8> = vec![
1612 0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b, 0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74, ];
1618 assert_eq!(
1619 RainMetaDocumentV1Item::cbor_decode(&bytes)?,
1620 vec![plain_item(KnownMagic::RainMetaDocumentV1, vec![0x01])]
1621 );
1622 Ok(())
1623 }
1624
1625 #[test]
1631 fn test_document_magic_item_carries_a_nested_document() -> Result<(), Error> {
1632 let inner = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1633 let inner_doc = RainMetaDocumentV1Item::cbor_encode_seq(
1634 &vec![inner.clone()],
1635 KnownMagic::RainMetaDocumentV1,
1636 )?;
1637 let outer = plain_item(KnownMagic::RainMetaDocumentV1, inner_doc.clone());
1638 let outer_doc = RainMetaDocumentV1Item::cbor_encode_seq(
1639 &vec![outer.clone()],
1640 KnownMagic::RainMetaDocumentV1,
1641 )?;
1642
1643 let decoded = RainMetaDocumentV1Item::cbor_decode(&outer_doc)?;
1644 assert_eq!(decoded, vec![outer]);
1645 assert_eq!(decoded[0].payload.as_ref(), inner_doc.as_slice());
1646 assert_eq!(
1647 RainMetaDocumentV1Item::cbor_decode(decoded[0].payload.as_ref())?,
1648 vec![inner]
1649 );
1650 Ok(())
1651 }
1652
1653 #[test]
1656 fn test_document_magic_item_is_not_unpackable() {
1657 assert!(matches!(
1658 KnownMeta::try_from(KnownMagic::RainMetaDocumentV1),
1659 Err(Error::UnsupportedMeta)
1660 ));
1661 assert!(matches!(
1662 plain_item(KnownMagic::RainMetaDocumentV1, vec![0x01]).unpack_into::<Vec<u8>>(),
1663 Err(Error::UnsupportedMeta)
1664 ));
1665 }
1666
1667 #[test]
1669 fn test_unpack_decodes_content_encoding() -> Result<(), Error> {
1670 let content = b"unpack me via deflate".to_vec();
1671 let packed = ContentEncoding::Deflate.encode(&content);
1672 assert_ne!(packed, content);
1673 let mut item = plain_item(KnownMagic::DotrainV1, packed);
1674 item.content_encoding = ContentEncoding::Deflate;
1675 assert_eq!(item.unpack()?, content);
1676
1677 let item = plain_item(KnownMagic::DotrainV1, content.clone());
1678 assert_eq!(item.unpack()?, content);
1679 Ok(())
1680 }
1681
1682 #[test]
1685 fn test_unpack_into_whitelist() {
1686 use strum::IntoEnumIterator;
1687 let supported = [
1688 KnownMagic::OpMetaV1,
1689 KnownMagic::DotrainV1,
1690 KnownMagic::RainlangV1,
1691 KnownMagic::SolidityAbiV2,
1692 KnownMagic::AuthoringMetaV1,
1693 KnownMagic::AuthoringMetaV2,
1694 KnownMagic::AddressList,
1695 KnownMagic::InterpreterCallerMetaV1,
1696 KnownMagic::ExpressionDeployerV2BytecodeV1,
1697 KnownMagic::DotrainSourceV1,
1698 KnownMagic::OrderBuilderStateV1,
1699 KnownMagic::RainlangSourceV1,
1700 KnownMagic::RaindexSignedContextOracleV1,
1701 ];
1702 for magic in supported {
1703 let unpacked: Vec<u8> = plain_item(magic, vec![0x61]).unpack_into().unwrap();
1704 assert_eq!(unpacked, vec![0x61], "{:?}", magic);
1705 }
1706 let unsupported = [
1707 KnownMagic::RainMetaDocumentV1,
1708 KnownMagic::OaSchema,
1709 KnownMagic::OaHashList,
1710 KnownMagic::OaStructure,
1711 KnownMagic::OaTokenImage,
1712 KnownMagic::OaTokenCredentialLinks,
1713 ];
1714 for magic in unsupported {
1715 let result: Result<Vec<u8>, Error> = plain_item(magic, vec![0x61]).unpack_into();
1716 assert!(matches!(result, Err(Error::UnsupportedMeta)), "{:?}", magic);
1717 }
1718 assert_eq!(
1720 supported.len() + unsupported.len(),
1721 KnownMagic::iter().count()
1722 );
1723 }
1724
1725 #[test]
1728 fn test_try_into_string_invalid_utf8_errors() {
1729 let item = plain_item(KnownMagic::DotrainV1, vec![0xff, 0xfe]);
1730 let result: Result<String, Error> = item.try_into();
1731 assert!(matches!(result, Err(Error::FromUtf8Error(_))));
1732 }
1733
1734 #[test]
1736 fn test_try_into_vec_decodes_encoding() -> Result<(), Error> {
1737 let content = b"raw bytecode bytes \x00\x01\x02".to_vec();
1738 let packed = ContentEncoding::Deflate.encode(&content);
1739 let mut item = plain_item(KnownMagic::ExpressionDeployerV2BytecodeV1, packed.clone());
1740 item.content_encoding = ContentEncoding::Deflate;
1741 let unpacked: Vec<u8> = item.try_into()?;
1742 assert_eq!(unpacked, content);
1743 assert_ne!(unpacked, packed);
1744 Ok(())
1745 }
1746
1747 #[test]
1750 fn test_content_encoding_deflate_roundtrip() -> Result<(), Error> {
1751 let content = b"hello rain deflate fixture hello rain deflate fixture".to_vec();
1752 let encoded = ContentEncoding::Deflate.encode(&content);
1753 assert_ne!(encoded, content);
1754 assert_eq!(encoded[0], 0x78);
1755 assert_eq!(ContentEncoding::Deflate.decode(&encoded)?, content);
1756 Ok(())
1757 }
1758
1759 #[test]
1761 fn test_content_encoding_passthrough() -> Result<(), Error> {
1762 let data = vec![0x00, 0xff, 0x10];
1763 for encoding in [ContentEncoding::None, ContentEncoding::Identity] {
1764 assert_eq!(encoding.encode(&data), data, "{:?}", encoding);
1765 assert_eq!(encoding.decode(&data)?, data, "{:?}", encoding);
1766 }
1767 Ok(())
1768 }
1769
1770 #[test]
1773 fn test_content_encoding_decode_fixtures() -> Result<(), Error> {
1774 let content = b"hello rain deflate fixture".to_vec();
1775 let zlib: Vec<u8> = vec![
1776 120, 156, 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44,
1777 73, 85, 72, 203, 172, 40, 41, 45, 74, 5, 0, 132, 64, 9, 251,
1778 ];
1779 let raw: Vec<u8> = vec![
1780 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44, 73, 85, 72,
1781 203, 172, 40, 41, 45, 74, 5, 0,
1782 ];
1783 assert_eq!(ContentEncoding::Deflate.decode(&zlib)?, content);
1784 assert_eq!(ContentEncoding::Deflate.decode(&raw)?, content);
1785 Ok(())
1786 }
1787
1788 #[test]
1791 fn test_content_encoding_decode_garbage_errors() {
1792 let garbage = [0xffu8, 0xff, 0xff, 0xff];
1793 assert!(matches!(
1794 ContentEncoding::Deflate.decode(&garbage),
1795 Err(Error::InflateError(_))
1796 ));
1797 }
1798
1799 #[test]
1801 fn test_content_headers_strum_names() {
1802 use std::str::FromStr;
1803 assert_eq!(
1804 ContentEncoding::from_str("deflate").unwrap(),
1805 ContentEncoding::Deflate
1806 );
1807 assert_eq!(
1808 ContentEncoding::from_str("identity").unwrap(),
1809 ContentEncoding::Identity
1810 );
1811 assert_eq!(
1812 ContentEncoding::from_str("none").unwrap(),
1813 ContentEncoding::None
1814 );
1815 assert_eq!(ContentEncoding::Deflate.to_string(), "deflate");
1816 assert_eq!(
1817 ContentType::from_str("octet-stream").unwrap(),
1818 ContentType::OctetStream
1819 );
1820 assert_eq!(ContentType::from_str("json").unwrap(), ContentType::Json);
1821 assert_eq!(ContentType::Json.to_string(), "json");
1822 assert_eq!(
1823 ContentLanguage::from_str("en").unwrap(),
1824 ContentLanguage::En
1825 );
1826 }
1827
1828 #[test]
1831 fn test_known_meta_try_from_magic() {
1832 let cases: [(KnownMagic, KnownMeta); 13] = [
1833 (KnownMagic::OpMetaV1, KnownMeta::OpV1),
1834 (KnownMagic::DotrainV1, KnownMeta::DotrainV1),
1835 (KnownMagic::RainlangV1, KnownMeta::RainlangV1),
1836 (KnownMagic::SolidityAbiV2, KnownMeta::SolidityAbiV2),
1837 (KnownMagic::AuthoringMetaV1, KnownMeta::AuthoringMetaV1),
1838 (KnownMagic::AuthoringMetaV2, KnownMeta::AuthoringMetaV2),
1839 (KnownMagic::AddressList, KnownMeta::AddressList),
1840 (
1841 KnownMagic::InterpreterCallerMetaV1,
1842 KnownMeta::InterpreterCallerMetaV1,
1843 ),
1844 (
1845 KnownMagic::ExpressionDeployerV2BytecodeV1,
1846 KnownMeta::ExpressionDeployerV2BytecodeV1,
1847 ),
1848 (KnownMagic::RainlangSourceV1, KnownMeta::RainlangSourceV1),
1849 (KnownMagic::DotrainSourceV1, KnownMeta::DotrainSourceV1),
1850 (
1851 KnownMagic::OrderBuilderStateV1,
1852 KnownMeta::OrderBuilderStateV1,
1853 ),
1854 (
1855 KnownMagic::RaindexSignedContextOracleV1,
1856 KnownMeta::RaindexSignedContextOracleV1,
1857 ),
1858 ];
1859 for (magic, meta) in cases {
1860 assert_eq!(KnownMeta::try_from(magic).unwrap(), meta, "{:?}", magic);
1861 }
1862 for magic in [
1863 KnownMagic::RainMetaDocumentV1,
1864 KnownMagic::OaSchema,
1865 KnownMagic::OaHashList,
1866 KnownMagic::OaStructure,
1867 KnownMagic::OaTokenImage,
1868 KnownMagic::OaTokenCredentialLinks,
1869 ] {
1870 assert!(
1871 matches!(KnownMeta::try_from(magic), Err(Error::UnsupportedMeta)),
1872 "{:?}",
1873 magic
1874 );
1875 }
1876 }
1877
1878 #[test]
1881 fn test_known_meta_strum_parse_display() {
1882 use std::str::FromStr;
1883 assert_eq!(KnownMeta::from_str("op-v1").unwrap(), KnownMeta::OpV1);
1884 assert_eq!(
1885 KnownMeta::from_str("solidity-abi-v2").unwrap(),
1886 KnownMeta::SolidityAbiV2
1887 );
1888 assert_eq!(
1889 KnownMeta::from_str("interpreter-caller-meta-v1").unwrap(),
1890 KnownMeta::InterpreterCallerMetaV1
1891 );
1892 assert_eq!(KnownMeta::SolidityAbiV2.to_string(), "solidity-abi-v2");
1893 assert_eq!(KnownMeta::OpV1.to_string(), "op-v1");
1894 }
1895
1896 fn sample_deployer(meta_hash: &[u8], meta_bytes: &[u8]) -> NPE2Deployer {
1897 NPE2Deployer {
1898 meta_hash: meta_hash.to_vec(),
1899 meta_bytes: meta_bytes.to_vec(),
1900 bytecode: vec![0xb1],
1901 parser: vec![0xb2],
1902 store: vec![0xb3],
1903 interpreter: vec![0xb4],
1904 authoring_meta: None,
1905 }
1906 }
1907
1908 fn deployer_json_body(
1909 meta_hash_hex: &str,
1910 meta_bytes_hex: &str,
1911 tx_hex: &str,
1912 bytecode_meta_id_hex: &str,
1913 ) -> serde_json::Value {
1914 json!({
1915 "data": {
1916 "expressionDeployers": [{
1917 "constructorMetaHash": meta_hash_hex,
1918 "constructorMeta": meta_bytes_hex,
1919 "deployTransaction": {"id": tx_hex},
1920 "bytecode": "0x01",
1921 "parser": {"parser": {"deployedBytecode": "0x02"}},
1922 "store": {"store": {"deployedBytecode": "0x03"}},
1923 "interpreter": {"interpreter": {"deployedBytecode": "0x04"}},
1924 "meta": [{"__typename": "RainMetaV1", "id": bytecode_meta_id_hex}]
1925 }]
1926 }
1927 })
1928 }
1929
1930 #[tokio::test]
1932 async fn test_search_lowercases_hash() {
1933 use httpmock::prelude::*;
1934 let (_, doc) = sample_authoring_doc();
1935 let hash_upper = format!("0x{}", "AB".repeat(32));
1936 let server = MockServer::start();
1937 let mock = server.mock(|when, then| {
1938 when.method(POST)
1939 .body_contains(hash_upper.to_ascii_lowercase());
1940 then.status(200).json_body(json!({
1941 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
1942 }));
1943 });
1944 let response = search(&hash_upper, &vec![server.url("/sg")]).await.unwrap();
1945 assert_eq!(response.bytes, doc);
1946 mock.assert();
1947 }
1948
1949 #[tokio::test]
1952 async fn test_search_first_success_wins() {
1953 use httpmock::prelude::*;
1954 let (_, doc) = sample_authoring_doc();
1955 let bad = MockServer::start();
1956 let _bad_mock = bad.mock(|when, then| {
1957 when.method(POST);
1958 then.status(500).body("subgraph down");
1959 });
1960 let good = MockServer::start();
1961 let _good_mock = good.mock(|when, then| {
1962 when.method(POST);
1963 then.status(200).json_body(json!({
1964 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
1965 }));
1966 });
1967 let response = search(
1968 &format!("0x{}", "11".repeat(32)),
1969 &vec![bad.url("/sg"), good.url("/sg")],
1970 )
1971 .await
1972 .unwrap();
1973 assert_eq!(response.bytes, doc);
1974 }
1975
1976 #[tokio::test]
1979 async fn test_search_deployer_lowercases_hash() {
1980 use httpmock::prelude::*;
1981 let (_, doc) = sample_authoring_doc();
1982 let meta_hash_hex = hex::encode_prefixed(keccak256(&doc).0);
1983 let hash_upper = format!("0x{}", "CD".repeat(32));
1984 let server = MockServer::start();
1985 let mock = server.mock(|when, then| {
1986 when.method(POST)
1987 .body_contains(hash_upper.to_ascii_lowercase());
1988 then.status(200).json_body(deployer_json_body(
1989 &meta_hash_hex,
1990 &hex::encode_prefixed(&doc),
1991 &format!("0x{}", "77".repeat(32)),
1992 &meta_hash_hex,
1993 ));
1994 });
1995 let response = search_deployer(&hash_upper, &vec![server.url("/sg")])
1996 .await
1997 .unwrap();
1998 assert_eq!(response.meta_bytes, doc);
1999 assert_eq!(response.bytecode, vec![0x01]);
2000 mock.assert();
2001 }
2002
2003 #[tokio::test]
2006 async fn test_search_deployer_first_success_wins() {
2007 use httpmock::prelude::*;
2008 let (_, doc) = sample_authoring_doc();
2009 let meta_hash_hex = hex::encode_prefixed(keccak256(&doc).0);
2010 let bad = MockServer::start();
2011 let _bad_mock = bad.mock(|when, then| {
2012 when.method(POST);
2013 then.status(500).body("subgraph down");
2014 });
2015 let good = MockServer::start();
2016 let _good_mock = good.mock(|when, then| {
2017 when.method(POST);
2018 then.status(200).json_body(deployer_json_body(
2019 &meta_hash_hex,
2020 &hex::encode_prefixed(&doc),
2021 &format!("0x{}", "77".repeat(32)),
2022 &meta_hash_hex,
2023 ));
2024 });
2025 let response = search_deployer(
2026 &format!("0x{}", "22".repeat(32)),
2027 &vec![bad.url("/sg"), good.url("/sg")],
2028 )
2029 .await
2030 .unwrap();
2031 assert_eq!(response.meta_bytes, doc);
2032 }
2033
2034 #[tokio::test]
2038 async fn test_implements_erc165_gate_short_circuits() {
2039 let address = Address::random();
2040
2041 let asserter = Asserter::new();
2043 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2044 asserter
2045 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2046 asserter
2047 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2048 assert!(!implements_i_described_by_meta_v1(&provider, address).await);
2049
2050 let asserter = Asserter::new();
2052 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2053 asserter.push_failure(ErrorPayload {
2054 code: -32000,
2055 message: "connection reset".into(),
2056 data: None,
2057 });
2058 asserter
2059 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2060 assert!(!implements_i_described_by_meta_v1(&provider, address).await);
2061 }
2062
2063 #[tokio::test]
2066 async fn test_implements_undecodable_response_is_false() {
2067 let address = Address::random();
2068 let asserter = Asserter::new();
2069 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2070 asserter
2071 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2072 asserter
2073 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2074 asserter.push_success(&"0x");
2075 assert!(!implements_i_described_by_meta_v1(&provider, address).await);
2076 }
2077
2078 #[test]
2081 fn test_npe2_deployer_is_corrupt_per_field() {
2082 let full = NPE2Deployer {
2083 meta_hash: vec![1],
2084 meta_bytes: vec![2],
2085 bytecode: vec![3],
2086 parser: vec![4],
2087 store: vec![5],
2088 interpreter: vec![6],
2089 authoring_meta: None,
2090 };
2091 assert!(!full.is_corrupt());
2092 for field in 0..6usize {
2093 let mut record = full.clone();
2094 match field {
2095 0 => record.meta_hash = vec![],
2096 1 => record.meta_bytes = vec![],
2097 2 => record.bytecode = vec![],
2098 3 => record.parser = vec![],
2099 4 => record.store = vec![],
2100 5 => record.interpreter = vec![],
2101 _ => unreachable!(),
2102 }
2103 assert!(record.is_corrupt(), "empty field {} must corrupt", field);
2104 }
2105 }
2106
2107 #[tokio::test]
2111 async fn test_store_constructors_inject_no_subgraphs() {
2112 assert!(Store::new().subgraphs().is_empty());
2113 assert!(Store::default().subgraphs().is_empty());
2114 assert!(
2115 Store::create(&vec![], &HashMap::new(), &HashMap::new(), &HashMap::new())
2116 .subgraphs()
2117 .is_empty()
2118 );
2119
2120 let hash = [0u8; 32];
2121 let mut store = Store::default();
2122 assert!(store.update(&hash).await.is_none());
2123 assert!(store.search_deployer(&hash).await.is_none());
2124 }
2125
2126 #[test]
2130 fn test_store_create_validates_entries() {
2131 let (_, doc) = sample_authoring_doc();
2132 let good_hash = keccak256(&doc).0.to_vec();
2133 let bad_hash = vec![0xEEu8; 32];
2134 let mut cache = HashMap::new();
2135 cache.insert(good_hash.clone(), doc.clone());
2136 cache.insert(bad_hash.clone(), b"does not hash to bad_hash".to_vec());
2137 let mut deployer_cache = HashMap::new();
2138 let deployer = sample_deployer(&[0xAA; 32], b"dep-meta");
2139 let deployer_key = vec![0x33u8; 32];
2140 deployer_cache.insert(deployer_key.clone(), deployer.clone());
2141 let mut dotrain_cache = HashMap::new();
2142 dotrain_cache.insert("a.rain".to_string(), good_hash.clone());
2143 dotrain_cache.insert("missing.rain".to_string(), vec![0x44u8; 32]);
2144
2145 let store = Store::create(
2146 &vec!["https://example.com/custom-sg".to_string()],
2147 &cache,
2148 &deployer_cache,
2149 &dotrain_cache,
2150 );
2151
2152 assert_eq!(
2153 store.subgraphs(),
2154 &vec!["https://example.com/custom-sg".to_string()]
2155 );
2156 assert_eq!(store.get_meta(&good_hash), Some(&doc));
2157 assert_eq!(store.get_meta(&bad_hash), None);
2158 assert_eq!(store.get_deployer(&deployer_key), Some(&deployer));
2159 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&good_hash));
2160 assert_eq!(store.get_dotrain_hash("missing.rain"), None);
2161 }
2162
2163 #[test]
2165 fn test_store_add_subgraphs_dedupe() {
2166 let mut store = Store::new();
2167 store.add_subgraphs(&vec!["sg-a".to_string()]);
2168 store.add_subgraphs(&vec!["sg-a".to_string(), "sg-b".to_string()]);
2169 assert_eq!(
2170 store.subgraphs(),
2171 &vec!["sg-a".to_string(), "sg-b".to_string()]
2172 );
2173 }
2174
2175 #[test]
2178 fn test_store_get_deployer_lookup_chain() {
2179 let mut store = Store::new();
2180 let deployer = sample_deployer(&[0xAB; 32], b"dep-meta-bytes");
2181 let key = vec![0x01u8; 32];
2182 let tx = vec![0x02u8; 32];
2183 store.set_deployer(&key, &deployer, Some(&tx));
2184 assert_eq!(store.get_deployer(&key), Some(&deployer));
2185 assert_eq!(store.get_deployer(&tx), Some(&deployer));
2186 assert_eq!(store.get_deployer(&[0x03u8; 32]), None);
2187 assert_eq!(
2188 store.get_meta(&deployer.meta_hash),
2189 Some(&deployer.meta_bytes)
2190 );
2191 }
2192
2193 #[tokio::test]
2197 async fn test_store_search_deployer_populates_caches() {
2198 use httpmock::prelude::*;
2199 let (authoring_meta, doc) = sample_authoring_doc();
2200 let meta_hash = keccak256(&doc).0.to_vec();
2201 let meta_hash_hex = hex::encode_prefixed(&meta_hash);
2202 let tx = vec![0x77u8; 32];
2203 let server = MockServer::start();
2204 let _mock = server.mock(|when, then| {
2205 when.method(POST);
2206 then.status(200).json_body(deployer_json_body(
2207 &meta_hash_hex,
2208 &hex::encode_prefixed(&doc),
2209 &hex::encode_prefixed(&tx),
2210 &meta_hash_hex,
2211 ));
2212 });
2213 let mut store = Store::new();
2214 store.add_subgraphs(&vec![server.url("/sg")]);
2215
2216 let record = store.search_deployer(&meta_hash).await.cloned().unwrap();
2217 assert_eq!(record.meta_hash, meta_hash);
2218 assert_eq!(record.meta_bytes, doc);
2219 assert_eq!(record.bytecode, vec![0x01]);
2220 assert_eq!(record.parser, vec![0x02]);
2221 assert_eq!(record.store, vec![0x03]);
2222 assert_eq!(record.interpreter, vec![0x04]);
2223 assert_eq!(record.authoring_meta, Some(authoring_meta));
2224 assert_eq!(store.get_meta(&meta_hash), Some(&doc));
2225 assert_eq!(store.get_deployer(&tx), Some(&record));
2226 }
2227
2228 #[tokio::test]
2230 async fn test_store_search_deployer_error_returns_none() {
2231 use httpmock::prelude::*;
2232 let server = MockServer::start();
2233 let _mock = server.mock(|when, then| {
2234 when.method(POST);
2235 then.status(500).body("subgraph down");
2236 });
2237 let mut store = Store::new();
2238 store.add_subgraphs(&vec![server.url("/sg")]);
2239 assert!(store.search_deployer(&[0x0Du8; 32]).await.is_none());
2240 assert!(store.cache().is_empty());
2241 assert!(store.deployer_cache().is_empty());
2242 }
2243
2244 #[tokio::test]
2248 async fn test_store_search_deployer_check_branches() {
2249 use httpmock::prelude::*;
2250 let mut store = Store::new();
2252 let deployer = sample_deployer(&[0xAC; 32], b"cached-meta");
2253 let key = vec![0x11u8; 32];
2254 let tx = vec![0x22u8; 32];
2255 store.set_deployer(&key, &deployer, Some(&tx));
2256 assert_eq!(store.search_deployer_check(&key).await, Some(&deployer));
2257 assert_eq!(store.search_deployer_check(&tx).await, Some(&deployer));
2258
2259 let (_, doc) = sample_authoring_doc();
2261 let meta_hash = keccak256(&doc).0.to_vec();
2262 let meta_hash_hex = hex::encode_prefixed(&meta_hash);
2263 let server = MockServer::start();
2264 let _mock = server.mock(|when, then| {
2265 when.method(POST);
2266 then.status(200).json_body(deployer_json_body(
2267 &meta_hash_hex,
2268 &hex::encode_prefixed(&doc),
2269 &format!("0x{}", "66".repeat(32)),
2270 &meta_hash_hex,
2271 ));
2272 });
2273 let mut fresh = Store::new();
2274 fresh.add_subgraphs(&vec![server.url("/sg")]);
2275 let found = fresh
2276 .search_deployer_check(&meta_hash)
2277 .await
2278 .cloned()
2279 .unwrap();
2280 assert_eq!(found.meta_bytes, doc);
2281 }
2282
2283 #[test]
2286 fn test_store_set_deployer_from_query_response() {
2287 let (authoring_meta, doc) = sample_authoring_doc();
2288 let meta_hash = vec![0x0Au8; 32];
2289 let bytecode_meta_hash = vec![0x0Bu8; 32];
2290 let tx = vec![0x0Cu8; 32];
2291 let response = DeployerResponse {
2292 tx_hash: tx.clone(),
2293 bytecode_meta_hash: bytecode_meta_hash.clone(),
2294 meta_hash: meta_hash.clone(),
2295 meta_bytes: doc.clone(),
2296 bytecode: vec![0xE1],
2297 parser: vec![0xE2],
2298 store: vec![0xE3],
2299 interpreter: vec![0xE4],
2300 };
2301 let mut store = Store::new();
2302 let record = store.set_deployer_from_query_response(response);
2303 assert_eq!(record.meta_hash, meta_hash);
2304 assert_eq!(record.meta_bytes, doc);
2305 assert_eq!(record.bytecode, vec![0xE1]);
2306 assert_eq!(record.authoring_meta, Some(authoring_meta));
2307 assert_eq!(store.get_meta(&meta_hash), Some(&doc));
2308 assert_eq!(store.get_deployer(&bytecode_meta_hash), Some(&record));
2309 assert_eq!(store.get_deployer(&tx), Some(&record));
2310 }
2311
2312 #[test]
2316 fn test_store_dotrain_getters_and_set_fresh() {
2317 let mut store = Store::new();
2318 let text = "some dotrain content";
2319 let (hash, old) = store.set_dotrain(text, "file.rain", false).unwrap();
2320 assert!(old.is_empty());
2321 let expected_item = RainMetaDocumentV1Item {
2322 payload: serde_bytes::ByteBuf::from(text.as_bytes()),
2323 magic: KnownMagic::DotrainV1,
2324 content_type: ContentType::OctetStream,
2325 content_encoding: ContentEncoding::None,
2326 content_language: ContentLanguage::None,
2327 schema: None,
2328 };
2329 let expected_bytes = expected_item.cbor_encode().unwrap();
2330 assert_eq!(hash, keccak256(&expected_bytes).0.to_vec());
2331 assert_eq!(store.get_dotrain_hash("file.rain"), Some(&hash));
2332 assert_eq!(store.get_dotrain_uri(&hash), Some(&"file.rain".to_string()));
2333 assert_eq!(store.get_dotrain_meta("file.rain"), Some(&expected_bytes));
2334 assert_eq!(store.get_dotrain_hash("other.rain"), None);
2335 assert_eq!(store.get_dotrain_uri(&[0u8; 32]), None);
2336 assert_eq!(store.get_dotrain_meta("other.rain"), None);
2337 }
2338
2339 #[test]
2343 fn test_store_set_dotrain_branches() {
2344 let mut store = Store::new();
2345 let (hash_one, _) = store.set_dotrain("text one", "a.rain", false).unwrap();
2346
2347 let (hash_same, old_same) = store.set_dotrain("text one", "a.rain", false).unwrap();
2349 assert_eq!(hash_same, hash_one);
2350 assert!(old_same.is_empty());
2351 assert!(store.get_meta(&hash_one).is_some());
2352
2353 let (hash_two, old_two) = store.set_dotrain("text two", "a.rain", false).unwrap();
2355 assert_ne!(hash_two, hash_one);
2356 assert_eq!(old_two, hash_one);
2357 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_two));
2358 assert!(store.get_meta(&hash_one).is_none());
2359 assert!(store.get_meta(&hash_two).is_some());
2360
2361 let (hash_three, old_three) = store.set_dotrain("text three", "a.rain", true).unwrap();
2363 assert_eq!(old_three, hash_two);
2364 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_three));
2365 assert!(store.get_meta(&hash_two).is_some());
2366 assert!(store.get_meta(&hash_three).is_some());
2367 }
2368
2369 #[test]
2372 fn test_store_delete_dotrain_keep_meta() {
2373 let mut store = Store::new();
2374 let (hash, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2375 store.delete_dotrain("d.rain", false);
2376 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2377 assert!(store.get_meta(&hash).is_none());
2378
2379 let (hash_again, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2380 store.delete_dotrain("d.rain", true);
2381 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2382 assert!(store.get_meta(&hash_again).is_some());
2383 }
2384
2385 #[test]
2389 fn test_store_merge_semantics() {
2390 let shared_meta_hash = vec![0x5Au8; 32];
2391 let deployer_ours = sample_deployer(&shared_meta_hash, b"ours");
2392 let deployer_theirs = sample_deployer(&shared_meta_hash, b"theirs");
2393 let shared_tx = vec![0x0Fu8; 32];
2394
2395 let mut ours = Store::new();
2396 let mut theirs = Store::new();
2397 ours.set_deployer(&[0x01u8; 32], &deployer_ours, Some(&shared_tx));
2398 theirs.set_deployer(&[0x02u8; 32], &deployer_theirs, Some(&shared_tx));
2399
2400 let contested_key = vec![0x03u8; 32];
2402 let deployer_a = sample_deployer(&[0x04; 32], b"deployer-a");
2403 let deployer_b = sample_deployer(&[0x05; 32], b"deployer-b");
2404 ours.set_deployer(&contested_key, &deployer_a, None);
2405 theirs.set_deployer(&contested_key, &deployer_b, None);
2406
2407 let (hash_ours, _) = ours.set_dotrain("content a", "x.rain", false).unwrap();
2409 let (_hash_theirs, _) = theirs.set_dotrain("content b", "x.rain", false).unwrap();
2410
2411 theirs.add_subgraphs(&vec!["sg-their".to_string()]);
2412
2413 ours.merge(&theirs);
2414
2415 assert_eq!(ours.get_meta(&shared_meta_hash), Some(&b"ours".to_vec()));
2417 assert_eq!(ours.get_deployer(&contested_key), Some(&deployer_a));
2419 assert_eq!(ours.get_deployer(&shared_tx), Some(&deployer_theirs));
2421 assert_eq!(ours.get_dotrain_hash("x.rain"), Some(&hash_ours));
2423 assert!(ours.subgraphs().contains(&"sg-their".to_string()));
2425 }
2426
2427 #[tokio::test]
2431 async fn test_store_update_and_update_check() {
2432 use httpmock::prelude::*;
2433 let authoring_meta: AuthoringMeta = serde_json::from_str(
2434 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
2435 )
2436 .unwrap();
2437 let item_one = RainMetaDocumentV1Item {
2438 payload: serde_bytes::ByteBuf::from(authoring_meta.abi_encode_validate().unwrap()),
2439 magic: KnownMagic::AuthoringMetaV1,
2440 content_type: ContentType::Cbor,
2441 content_encoding: ContentEncoding::None,
2442 content_language: ContentLanguage::None,
2443 schema: None,
2444 };
2445 let item_two = sample_dotrain_item();
2446 let doc = RainMetaDocumentV1Item::cbor_encode_seq(
2447 &vec![item_one.clone(), item_two.clone()],
2448 KnownMagic::RainMetaDocumentV1,
2449 )
2450 .unwrap();
2451 let requested = keccak256(&doc).0.to_vec();
2452 let server = MockServer::start();
2453 let _mock = server.mock(|when, then| {
2454 when.method(POST);
2455 then.status(200).json_body(json!({
2456 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2457 }));
2458 });
2459 let mut store = Store::new();
2460 store.add_subgraphs(&vec![server.url("/sg")]);
2461 let fetched = store.update(&requested).await.cloned().unwrap();
2462 assert_eq!(fetched, doc);
2463 assert_eq!(store.get_meta(&requested), Some(&doc));
2464 let inner_one = item_one.cbor_encode().unwrap();
2465 let inner_two = item_two.cbor_encode().unwrap();
2466 assert_eq!(
2467 store.get_meta(keccak256(&inner_one).0.as_ref()),
2468 Some(&inner_one)
2469 );
2470 assert_eq!(
2471 store.get_meta(keccak256(&inner_two).0.as_ref()),
2472 Some(&inner_two)
2473 );
2474
2475 let mut cached_store = Store::new();
2477 let bytes = b"standalone meta bytes".to_vec();
2478 let hash = keccak256(&bytes).0.to_vec();
2479 assert!(cached_store.update_with(&hash, &bytes).is_some());
2480 assert_eq!(cached_store.update_check(&hash).await, Some(&bytes));
2481 }
2482
2483 #[test]
2487 fn test_store_update_with_validation_and_content() {
2488 let mut store = Store::new();
2490 let bytes = b"payload bytes".to_vec();
2491 let wrong_hash = vec![0x99u8; 32];
2492 assert!(store.update_with(&wrong_hash, &bytes).is_none());
2493 assert!(store.get_meta(&wrong_hash).is_none());
2494 let hash = keccak256(&bytes).0.to_vec();
2496 assert_eq!(store.update_with(&hash, &bytes), Some(&bytes));
2497
2498 let mut seeded = Store::new();
2500 let content = b"real content".to_vec();
2501 let content_hash = keccak256(&content).0.to_vec();
2502 let planted = sample_deployer(&content_hash, b"planted value");
2503 seeded.set_deployer(&[0x77u8; 32], &planted, None);
2504 assert_eq!(
2505 seeded.update_with(&content_hash, &content),
2506 Some(&b"planted value".to_vec())
2507 );
2508 assert_eq!(
2509 seeded.get_meta(&content_hash),
2510 Some(&b"planted value".to_vec())
2511 );
2512
2513 let (_, doc) = sample_authoring_doc();
2515 let doc_hash = keccak256(&doc).0.to_vec();
2516 let mut doc_store = Store::new();
2517 assert!(doc_store.update_with(&doc_hash, &doc).is_some());
2518 let inner = doc[8..].to_vec();
2519 assert_eq!(store_inner_lookup(&doc_store, &inner), Some(inner.clone()));
2520
2521 let item_a = sample_dotrain_item().cbor_encode().unwrap();
2523 let (_, doc_b) = sample_authoring_doc();
2524 let item_b = doc_b[8..].to_vec();
2525 let seq = [item_a.clone(), item_b].concat();
2526 let seq_hash = keccak256(&seq).0.to_vec();
2527 let mut seq_store = Store::new();
2528 assert!(seq_store.update_with(&seq_hash, &seq).is_some());
2529 assert_eq!(store_inner_lookup(&seq_store, &item_a), None);
2530 }
2531
2532 fn store_inner_lookup(store: &Store, inner_encoded: &[u8]) -> Option<Vec<u8>> {
2533 store.get_meta(keccak256(inner_encoded).0.as_ref()).cloned()
2534 }
2535
2536 #[test]
2539 fn test_bytes32_to_str_invalid_utf8() {
2540 let mut bytes = [0u8; 32];
2541 bytes[0] = 0xf0;
2542 bytes[1] = 0x28;
2543 bytes[2] = 0x8c;
2544 bytes[3] = 0x28;
2545 assert!(matches!(bytes32_to_str(&bytes), Err(Error::Utf8Error(_))));
2546 let no_nul = [0xffu8; 32];
2547 assert!(matches!(bytes32_to_str(&no_nul), Err(Error::Utf8Error(_))));
2548 }
2549}