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 _ => {
367 map.next_value::<serde::de::IgnoredAny>()?;
368 }
369 };
370 true
371 }
372 Ok(None) => false,
373 Err(error) => Err(error)?,
374 } {}
375 let payload = payload.ok_or_else(|| serde::de::Error::missing_field("payload"))?;
376 let magic = match magic
377 .ok_or_else(|| serde::de::Error::missing_field("magic number"))?
378 .try_into()
379 {
380 Ok(m) => m,
381 _ => Err(serde::de::Error::custom("unknown magic number"))?,
382 };
383 let content_type = content_type.unwrap_or(ContentType::None);
384 let content_encoding = content_encoding.unwrap_or(ContentEncoding::None);
385 let content_language = content_language.unwrap_or(ContentLanguage::None);
386
387 Ok(RainMetaDocumentV1Item {
388 payload,
389 magic,
390 content_type,
391 content_encoding,
392 content_language,
393 schema,
394 })
395 }
396 }
397 deserializer.deserialize_map(EncodedMap)
398 }
399}
400
401pub async fn search(hash: &str, subgraphs: &Vec<String>) -> Result<query::MetaResponse, Error> {
403 if subgraphs.is_empty() {
405 return Err(Error::NoRecordFound);
406 }
407 let request_body = query::MetaQuery::build_query(query::meta_query::Variables {
408 hash: Some(hash.to_ascii_lowercase()),
409 });
410 let mut promises = vec![];
411
412 let client = Arc::new(Client::builder().build().map_err(Error::ReqwestError)?);
413 for url in subgraphs {
414 promises.push(Box::pin(query::process_meta_query(
415 client.clone(),
416 &request_body,
417 url,
418 )));
419 }
420 let response_value = future::select_ok(promises.drain(..)).await?.0;
421 Ok(response_value)
422}
423
424pub async fn search_deployer(
426 hash: &str,
427 subgraphs: &Vec<String>,
428) -> Result<DeployerResponse, Error> {
429 if subgraphs.is_empty() {
431 return Err(Error::NoRecordFound);
432 }
433 let request_body = query::DeployerQuery::build_query(query::deployer_query::Variables {
434 hash: Some(hash.to_ascii_lowercase()),
435 });
436 let mut promises = vec![];
437
438 let client = Arc::new(Client::builder().build().map_err(Error::ReqwestError)?);
439 for url in subgraphs {
440 promises.push(Box::pin(query::process_deployer_query(
441 client.clone(),
442 &request_body,
443 url,
444 )));
445 }
446 let response_value = future::select_ok(promises.drain(..)).await?.0;
447 Ok(response_value)
448}
449
450pub async fn implements_i_described_by_meta_v1<P: Provider>(
452 provider: &P,
453 contract_address: Address,
454) -> bool {
455 if !supports_erc165(provider, contract_address)
456 .await
457 .unwrap_or(false)
458 {
459 return false;
460 }
461
462 let interface_id_res = IDescribedByMetaV1::IDescribedByMetaV1Calls::xor_selectors();
463 if interface_id_res.is_err() {
464 return false;
465 }
466
467 let call = IERC165::supportsInterfaceCall {
468 interfaceID: interface_id_res.unwrap().into(),
469 };
470 let tx = TransactionRequest::default()
471 .to(contract_address)
472 .input(call.abi_encode().into());
473 match provider.call(tx).await {
474 Ok(bytes) => IERC165::supportsInterfaceCall::abi_decode_returns(&bytes).unwrap_or(false),
475 Err(_) => false,
476 }
477}
478
479#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Default)]
481#[serde(rename_all = "camelCase")]
482pub struct NPE2Deployer {
483 #[serde(with = "serde_bytes")]
485 pub meta_hash: Vec<u8>,
486 #[serde(with = "serde_bytes")]
488 pub meta_bytes: Vec<u8>,
489 #[serde(with = "serde_bytes")]
491 pub bytecode: Vec<u8>,
492 #[serde(with = "serde_bytes")]
494 pub parser: Vec<u8>,
495 #[serde(with = "serde_bytes")]
497 pub store: Vec<u8>,
498 #[serde(with = "serde_bytes")]
500 pub interpreter: Vec<u8>,
501 pub authoring_meta: Option<AuthoringMeta>,
503}
504
505impl NPE2Deployer {
506 pub fn is_corrupt(&self) -> bool {
507 if self.meta_hash.is_empty() {
508 return true;
509 }
510 if self.meta_bytes.is_empty() {
511 return true;
512 }
513 if self.bytecode.is_empty() {
514 return true;
515 }
516 if self.parser.is_empty() {
517 return true;
518 }
519 if self.store.is_empty() {
520 return true;
521 }
522 if self.interpreter.is_empty() {
523 return true;
524 }
525 false
526 }
527}
528
529#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
587pub struct Store {
588 subgraphs: Vec<String>,
589 cache: HashMap<Vec<u8>, Vec<u8>>,
590 dotrain_cache: HashMap<String, Vec<u8>>,
591 deployer_cache: HashMap<Vec<u8>, NPE2Deployer>,
592 deployer_hash_map: HashMap<Vec<u8>, Vec<u8>>,
593}
594
595impl Default for Store {
596 fn default() -> Self {
597 Store::new()
598 }
599}
600
601impl Store {
602 pub fn new() -> Store {
605 Store {
606 subgraphs: vec![],
607 cache: HashMap::new(),
608 dotrain_cache: HashMap::new(),
609 deployer_cache: HashMap::new(),
610 deployer_hash_map: HashMap::new(),
611 }
612 }
613
614 pub fn create(
617 subgraphs: &Vec<String>,
618 cache: &HashMap<Vec<u8>, Vec<u8>>,
619 deployer_cache: &HashMap<Vec<u8>, NPE2Deployer>,
620 dotrain_cache: &HashMap<String, Vec<u8>>,
621 ) -> Store {
622 let mut store = Store::new();
623 store.add_subgraphs(subgraphs);
624 for (hash, bytes) in cache {
625 store.update_with(hash, bytes);
626 }
627 for (hash, deployer) in deployer_cache {
628 store.set_deployer(hash, deployer, None);
629 }
630 for (uri, hash) in dotrain_cache {
631 if !store.dotrain_cache.contains_key(uri) && store.cache.contains_key(hash) {
632 store.dotrain_cache.insert(uri.clone(), hash.clone());
633 }
634 }
635 store
636 }
637
638 pub fn subgraphs(&self) -> &Vec<String> {
640 &self.subgraphs
641 }
642
643 pub fn add_subgraphs(&mut self, subgraphs: &Vec<String>) {
645 for sg in subgraphs {
646 if !self.subgraphs.contains(sg) {
647 self.subgraphs.push(sg.to_string());
648 }
649 }
650 }
651
652 pub fn cache(&self) -> &HashMap<Vec<u8>, Vec<u8>> {
654 &self.cache
655 }
656
657 pub fn get_meta(&self, hash: &[u8]) -> Option<&Vec<u8>> {
659 self.cache.get(hash)
660 }
661
662 pub fn deployer_cache(&self) -> &HashMap<Vec<u8>, NPE2Deployer> {
664 &self.deployer_cache
665 }
666
667 pub fn get_deployer(&self, hash: &[u8]) -> Option<&NPE2Deployer> {
669 if self.deployer_cache.contains_key(hash) {
670 self.deployer_cache.get(hash)
671 } else if let Some(h) = self.deployer_hash_map.get(hash) {
672 self.deployer_cache.get(h)
673 } else {
674 None
675 }
676 }
677
678 pub async fn search_deployer(&mut self, hash: &[u8]) -> Option<&NPE2Deployer> {
680 match search_deployer(&hex::encode_prefixed(hash), &self.subgraphs).await {
681 Ok(res) => {
682 self.cache
683 .insert(res.meta_hash.clone(), res.meta_bytes.clone());
684 let authoring_meta = res.get_authoring_meta();
685 self.deployer_cache.insert(
686 res.bytecode_meta_hash.clone(),
687 NPE2Deployer {
688 meta_hash: res.meta_hash.clone(),
689 meta_bytes: res.meta_bytes,
690 bytecode: res.bytecode,
691 parser: res.parser,
692 store: res.store,
693 interpreter: res.interpreter,
694 authoring_meta,
695 },
696 );
697 self.deployer_hash_map.insert(res.tx_hash, res.meta_hash);
698 self.deployer_cache.get(hash)
699 }
700 Err(_e) => None,
701 }
702 }
703
704 pub async fn search_deployer_check(&mut self, hash: &[u8]) -> Option<&NPE2Deployer> {
707 if self.deployer_cache.contains_key(hash) {
708 self.get_deployer(hash)
709 } else if self.deployer_hash_map.contains_key(hash) {
710 let b_hash = self.deployer_hash_map.get(hash).unwrap();
711 self.get_deployer(b_hash)
712 } else {
713 self.search_deployer(hash).await
714 }
715 }
716
717 pub fn set_deployer_from_query_response(
719 &mut self,
720 deployer_query_response: DeployerResponse,
721 ) -> NPE2Deployer {
722 let authoring_meta = deployer_query_response.get_authoring_meta();
723 let tx_hash = deployer_query_response.tx_hash;
724 let bytecode_meta_hash = deployer_query_response.bytecode_meta_hash;
725 let result = NPE2Deployer {
726 meta_hash: deployer_query_response.meta_hash.clone(),
727 meta_bytes: deployer_query_response.meta_bytes,
728 bytecode: deployer_query_response.bytecode,
729 parser: deployer_query_response.parser,
730 store: deployer_query_response.store,
731 interpreter: deployer_query_response.interpreter,
732 authoring_meta,
733 };
734 self.cache
735 .insert(deployer_query_response.meta_hash, result.meta_bytes.clone());
736 self.deployer_hash_map
737 .insert(tx_hash, bytecode_meta_hash.clone());
738 self.deployer_cache
739 .insert(bytecode_meta_hash, result.clone());
740 result
741 }
742
743 pub fn set_deployer(
746 &mut self,
747 hash: &[u8],
748 npe2_deployer: &NPE2Deployer,
749 tx_hash: Option<&[u8]>,
750 ) {
751 self.cache.insert(
752 npe2_deployer.meta_hash.clone(),
753 npe2_deployer.meta_bytes.clone(),
754 );
755 self.deployer_cache
756 .insert(hash.to_vec(), npe2_deployer.clone());
757 if let Some(v) = tx_hash {
758 self.deployer_hash_map.insert(v.to_vec(), hash.to_vec());
759 }
760 }
761
762 pub fn dotrain_cache(&self) -> &HashMap<String, Vec<u8>> {
764 &self.dotrain_cache
765 }
766
767 pub fn get_dotrain_hash(&self, uri: &str) -> Option<&Vec<u8>> {
769 self.dotrain_cache.get(uri)
770 }
771
772 pub fn get_dotrain_uri(&self, hash: &[u8]) -> Option<&String> {
774 for (uri, h) in &self.dotrain_cache {
775 if h == hash {
776 return Some(uri);
777 }
778 }
779 None
780 }
781
782 pub fn get_dotrain_meta(&self, uri: &str) -> Option<&Vec<u8>> {
784 self.get_meta(self.dotrain_cache.get(uri)?)
785 }
786
787 pub fn delete_dotrain(&mut self, uri: &str, keep_meta: bool) {
789 if let Some(kv) = self.dotrain_cache.remove_entry(uri) {
790 if !keep_meta {
791 self.cache.remove(&kv.1);
792 }
793 };
794 }
795
796 pub fn merge(&mut self, other: &Store) {
799 self.add_subgraphs(&other.subgraphs);
800 for (hash, bytes) in &other.cache {
801 if !self.cache.contains_key(hash) {
802 self.cache.insert(hash.clone(), bytes.clone());
803 }
804 }
805 for (hash, deployer) in &other.deployer_cache {
806 if !self.deployer_cache.contains_key(hash) {
807 self.deployer_cache.insert(hash.clone(), deployer.clone());
808 }
809 }
810 for (tx_hash, hash) in &other.deployer_hash_map {
811 if !self.deployer_hash_map.contains_key(tx_hash) {
812 self.deployer_hash_map.insert(tx_hash.clone(), hash.clone());
813 }
814 }
815 for (uri, hash) in &other.dotrain_cache {
816 if !self.dotrain_cache.contains_key(uri) {
817 self.dotrain_cache.insert(uri.clone(), hash.clone());
818 }
819 }
820 }
821
822 pub async fn update(&mut self, hash: &[u8]) -> Option<&Vec<u8>> {
825 if let Ok(meta) = search(&hex::encode_prefixed(hash), &self.subgraphs).await {
826 self.store_content(&meta.bytes);
827 self.cache.insert(hash.to_vec(), meta.bytes);
828 self.get_meta(hash)
829 } else {
830 None
831 }
832 }
833
834 pub async fn update_check(&mut self, hash: &[u8]) -> Option<&Vec<u8>> {
836 if !self.cache.contains_key(hash) {
837 self.update(hash).await
838 } else {
839 self.get_meta(hash)
840 }
841 }
842
843 pub fn update_with(&mut self, hash: &[u8], bytes: &[u8]) -> Option<&Vec<u8>> {
846 if !self.cache.contains_key(hash) {
847 if keccak256(bytes).0 == hash {
848 self.store_content(bytes);
849 self.cache.insert(hash.to_vec(), bytes.to_vec());
850 self.cache.get(hash)
851 } else {
852 None
853 }
854 } else {
855 self.get_meta(hash)
856 }
857 }
858
859 pub fn set_dotrain(
864 &mut self,
865 text: &str,
866 uri: &str,
867 keep_old: bool,
868 ) -> Result<(Vec<u8>, Vec<u8>), Error> {
869 let bytes = RainMetaDocumentV1Item {
870 payload: serde_bytes::ByteBuf::from(text.as_bytes()),
871 magic: KnownMagic::DotrainV1,
872 content_type: ContentType::OctetStream,
873 content_encoding: ContentEncoding::None,
874 content_language: ContentLanguage::None,
875 schema: None,
876 }
877 .cbor_encode()?;
878 let new_hash = keccak256(&bytes).0.to_vec();
879 if let Some(h) = self.dotrain_cache.get(uri) {
880 let old_hash = h.clone();
881 if new_hash == old_hash {
882 self.cache.insert(new_hash.clone(), bytes);
883 Ok((new_hash, vec![]))
884 } else {
885 self.cache.insert(new_hash.clone(), bytes);
886 self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
887 if !keep_old {
888 self.cache.remove(&old_hash);
889 }
890 Ok((new_hash, old_hash))
891 }
892 } else {
893 self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
894 self.cache.insert(new_hash.clone(), bytes);
895 Ok((new_hash, vec![]))
896 }
897 }
898
899 fn store_content(&mut self, bytes: &[u8]) {
903 if let Ok(meta_maps) = RainMetaDocumentV1Item::cbor_decode(bytes) {
904 if bytes.starts_with(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes()) {
905 for meta_map in &meta_maps {
906 if let Ok(encoded_bytes) = meta_map.cbor_encode() {
907 self.cache
908 .insert(keccak256(&encoded_bytes).0.to_vec(), encoded_bytes);
909 }
910 }
911 }
912 }
913 }
914}
915
916pub fn str_to_bytes32(text: &str) -> Result<[u8; 32], Error> {
922 let bytes: &[u8] = text.as_bytes();
923 if bytes.len() > 32 {
924 return Err(Error::BiggerThan32Bytes);
925 }
926 if bytes.contains(&0u8) {
927 return Err(Error::NulByteInInput);
928 }
929 let mut b32 = [0u8; 32];
930 b32[..bytes.len()].copy_from_slice(bytes);
931 Ok(b32)
932}
933
934pub fn bytes32_to_str(bytes: &[u8; 32]) -> Result<&str, Error> {
936 let mut len = 32;
937 if let Some((pos, _)) = itertools::Itertools::find_position(&mut bytes.iter(), |b| **b == 0u8) {
938 len = pos;
939 };
940 Ok(std::str::from_utf8(&bytes[..len])?)
941}
942
943#[cfg(all(test, not(target_family = "wasm")))]
944mod tests {
945 use super::{
946 *, bytes32_to_str, magic::KnownMagic, str_to_bytes32, types::authoring::v1::AuthoringMeta,
947 ContentEncoding, ContentLanguage, ContentType, Error, RainMetaDocumentV1Item,
948 };
949 use alloy::providers::ProviderBuilder;
950 use alloy::{providers::mock::Asserter, rpc::json_rpc::ErrorPayload, sol_types::SolType};
951 use serde_json::json;
952
953 #[test]
956 fn authoring_meta_roundtrip() -> Result<(), Error> {
957 let authoring_meta_content = r#"[
958 {
959 "word": "stack",
960 "description": "Copies an existing value from the stack.",
961 "operandParserOffset": 16
962 },
963 {
964 "word": "constant",
965 "description": "Copies a constant value onto the stack.",
966 "operandParserOffset": 16
967 }
968 ]"#;
969 let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
970
971 let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
973 let expected_abi_encoded = <alloy::sol!((bytes32, uint8, string)[])>::abi_encode(&vec![
974 (
975 str_to_bytes32("stack")?,
976 16u8,
977 "Copies an existing value from the stack.".to_string(),
978 ),
979 (
980 str_to_bytes32("constant")?,
981 16u8,
982 "Copies a constant value onto the stack.".to_string(),
983 ),
984 ]);
985 assert_eq!(authoring_meta_abi_encoded, expected_abi_encoded);
987
988 let meta_map = RainMetaDocumentV1Item {
989 payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
990 magic: KnownMagic::AuthoringMetaV1,
991 content_type: ContentType::Cbor,
992 content_encoding: ContentEncoding::None,
993 content_language: ContentLanguage::None,
994 schema: None,
995 };
996 let cbor_encoded = meta_map.cbor_encode()?;
997
998 assert_eq!(cbor_encoded[0], 0xa3);
1000 assert_eq!(cbor_encoded[1], 0x00);
1002 assert_eq!(cbor_encoded[2], 0b010_11001);
1004 assert_eq!(cbor_encoded[3], 0b000_00010);
1005 assert_eq!(cbor_encoded[4], 0b000_00000);
1006 assert_eq!(cbor_encoded[5..517], authoring_meta_abi_encoded);
1008 assert_eq!(cbor_encoded[517], 0x01);
1010 assert_eq!(cbor_encoded[518], 0b000_11011);
1012 assert_eq!(
1014 &cbor_encoded[519..527],
1015 KnownMagic::AuthoringMetaV1.to_prefix_bytes()
1016 );
1017 assert_eq!(cbor_encoded[527], 0x02);
1019 assert_eq!(cbor_encoded[528], 0b011_10000);
1021 assert_eq!(&cbor_encoded[529..], "application/cbor".as_bytes());
1023
1024 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1026 assert_eq!(cbor_decoded.len(), 1);
1028 assert_eq!(cbor_decoded[0], meta_map);
1030
1031 Ok(())
1032 }
1033
1034 #[test]
1037 fn dotrain_meta_roundtrip() -> Result<(), Error> {
1038 let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
1039 let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
1040
1041 let content_encoding = ContentEncoding::Deflate;
1042 let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
1043
1044 let meta_map = RainMetaDocumentV1Item {
1045 payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
1046 magic: KnownMagic::DotrainV1,
1047 content_type: ContentType::OctetStream,
1048 content_encoding,
1049 content_language: ContentLanguage::En,
1050 schema: None,
1051 };
1052 let cbor_encoded = meta_map.cbor_encode()?;
1053
1054 assert_eq!(cbor_encoded[0], 0xa5);
1056 assert_eq!(cbor_encoded[1], 0x00);
1058 assert_eq!(cbor_encoded[2], 0b010_11000);
1060 assert_eq!(cbor_encoded[3], 0b001_00100);
1061 assert_eq!(cbor_encoded[4..40], deflated_payload);
1064 assert_eq!(cbor_encoded[40], 0x01);
1066 assert_eq!(cbor_encoded[41], 0b000_11011);
1068 assert_eq!(
1070 &cbor_encoded[42..50],
1071 KnownMagic::DotrainV1.to_prefix_bytes()
1072 );
1073 assert_eq!(cbor_encoded[50], 0x02);
1075 assert_eq!(cbor_encoded[51], 0b011_11000);
1077 assert_eq!(cbor_encoded[52], 0b000_11000);
1078 assert_eq!(&cbor_encoded[53..77], "application/octet-stream".as_bytes());
1080 assert_eq!(cbor_encoded[77], 0x03);
1082 assert_eq!(cbor_encoded[78], 0b011_00111);
1084 assert_eq!(&cbor_encoded[79..86], "deflate".as_bytes());
1086 assert_eq!(cbor_encoded[86], 0x04);
1088 assert_eq!(cbor_encoded[87], 0b011_00010);
1090 assert_eq!(&cbor_encoded[88..], "en".as_bytes());
1092
1093 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1095 assert_eq!(cbor_decoded.len(), 1);
1097 assert_eq!(cbor_decoded[0], meta_map);
1099
1100 Ok(())
1101 }
1102
1103 #[test]
1106 fn meta_seq_roundtrip() -> Result<(), Error> {
1107 let authoring_meta_content = r#"[
1108 {
1109 "word": "stack",
1110 "description": "Copies an existing value from the stack.",
1111 "operandParserOffset": 16
1112 },
1113 {
1114 "word": "constant",
1115 "description": "Copies a constant value onto the stack.",
1116 "operandParserOffset": 16
1117 }
1118 ]"#;
1119 let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
1120 let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
1121 let meta_map_1 = RainMetaDocumentV1Item {
1122 payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
1123 magic: KnownMagic::AuthoringMetaV1,
1124 content_type: ContentType::Cbor,
1125 content_encoding: ContentEncoding::None,
1126 content_language: ContentLanguage::None,
1127 schema: None,
1128 };
1129
1130 let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
1131 let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
1132 let content_encoding = ContentEncoding::Deflate;
1133 let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
1134 let meta_map_2 = RainMetaDocumentV1Item {
1135 payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
1136 magic: KnownMagic::DotrainV1,
1137 content_type: ContentType::OctetStream,
1138 content_encoding,
1139 content_language: ContentLanguage::En,
1140 schema: None,
1141 };
1142
1143 let cbor_encoded = RainMetaDocumentV1Item::cbor_encode_seq(
1145 &vec![meta_map_1.clone(), meta_map_2.clone()],
1146 KnownMagic::RainMetaDocumentV1,
1147 )?;
1148
1149 assert_eq!(
1151 &cbor_encoded[0..8],
1152 KnownMagic::RainMetaDocumentV1.to_prefix_bytes()
1153 );
1154
1155 assert_eq!(cbor_encoded[8], 0xa3);
1158 assert_eq!(cbor_encoded[9], 0x00);
1160 assert_eq!(cbor_encoded[10], 0b010_11001);
1162 assert_eq!(cbor_encoded[11], 0b000_00010);
1163 assert_eq!(cbor_encoded[12], 0b000_00000);
1164 assert_eq!(cbor_encoded[13..525], authoring_meta_abi_encoded);
1166 assert_eq!(cbor_encoded[525], 0x01);
1168 assert_eq!(cbor_encoded[526], 0b000_11011);
1170 assert_eq!(
1172 &cbor_encoded[527..535],
1173 KnownMagic::AuthoringMetaV1.to_prefix_bytes()
1174 );
1175 assert_eq!(cbor_encoded[535], 0x02);
1177 assert_eq!(cbor_encoded[536], 0b011_10000);
1179 assert_eq!(&cbor_encoded[537..553], "application/cbor".as_bytes());
1181
1182 assert_eq!(cbor_encoded[553], 0xa5);
1185 assert_eq!(cbor_encoded[554], 0x00);
1187 assert_eq!(cbor_encoded[555], 0b010_11000);
1189 assert_eq!(cbor_encoded[556], 0b001_00100);
1190 assert_eq!(cbor_encoded[557..593], deflated_payload);
1193 assert_eq!(cbor_encoded[593], 0x01);
1195 assert_eq!(cbor_encoded[594], 0b000_11011);
1197 assert_eq!(
1199 &cbor_encoded[595..603],
1200 KnownMagic::DotrainV1.to_prefix_bytes()
1201 );
1202 assert_eq!(cbor_encoded[603], 0x02);
1204 assert_eq!(cbor_encoded[604], 0b011_11000);
1206 assert_eq!(cbor_encoded[605], 0b000_11000);
1207 assert_eq!(
1209 &cbor_encoded[606..630],
1210 "application/octet-stream".as_bytes()
1211 );
1212 assert_eq!(cbor_encoded[630], 0x03);
1214 assert_eq!(cbor_encoded[631], 0b011_00111);
1216 assert_eq!(&cbor_encoded[632..639], "deflate".as_bytes());
1218 assert_eq!(cbor_encoded[639], 0x04);
1220 assert_eq!(cbor_encoded[640], 0b011_00010);
1222 assert_eq!(&cbor_encoded[641..], "en".as_bytes());
1224
1225 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1227 assert_eq!(cbor_decoded.len(), 2);
1229
1230 assert_eq!(cbor_decoded[0], meta_map_1);
1232 assert_eq!(cbor_decoded[1], meta_map_2);
1234
1235 Ok(())
1236 }
1237
1238 #[test]
1239 fn test_bytes32_to_str() {
1240 let text_bytes_list = vec![
1241 (
1242 "",
1243 hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1244 ),
1245 (
1246 "A",
1247 hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1248 ),
1249 (
1250 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1251 hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1252 ),
1253 (
1254 "!@#$%^&*(),./;'[]",
1255 hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1256 ),
1257 ];
1258
1259 for (text, bytes) in text_bytes_list {
1260 assert_eq!(text, bytes32_to_str(&bytes).unwrap());
1261 }
1262 }
1263
1264 #[test]
1265 fn test_str_to_bytes32() {
1266 let text_bytes_list = vec![
1267 (
1268 "",
1269 hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1270 ),
1271 (
1272 "A",
1273 hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1274 ),
1275 (
1276 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1277 hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1278 ),
1279 (
1280 "!@#$%^&*(),./;'[]",
1281 hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1282 ),
1283 ];
1284
1285 for (text, bytes) in text_bytes_list {
1286 assert_eq!(bytes, str_to_bytes32(text).unwrap());
1287 }
1288 }
1289
1290 #[test]
1291 fn test_str_to_bytes32_long() {
1292 assert!(matches!(
1293 str_to_bytes32("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456").unwrap_err(),
1294 Error::BiggerThan32Bytes
1295 ));
1296 }
1297
1298 #[test]
1302 fn test_str_to_bytes32_rejects_nul() {
1303 for text in [
1304 "\0",
1305 "\0a",
1306 "a\0",
1307 "a\0b",
1308 "abcdefghijklmnopqrstuvwxyz01234\0",
1309 ] {
1310 assert!(
1311 matches!(str_to_bytes32(text), Err(Error::NulByteInInput)),
1312 "nul bearing input {:?} accepted",
1313 text
1314 );
1315 }
1316 }
1317
1318 #[test]
1321 fn test_str_to_bytes32_round_trip() -> Result<(), Error> {
1322 let mut seen: Vec<[u8; 32]> = vec![];
1323 for text in [
1324 "",
1325 "a",
1326 "stack",
1327 "!@#$%^&*(),./;'[]",
1328 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1329 ] {
1330 let bytes = str_to_bytes32(text)?;
1331 assert_eq!(bytes32_to_str(&bytes)?, text);
1332 assert!(!seen.contains(&bytes), "input {:?} collided", text);
1333 seen.push(bytes);
1334 }
1335 Ok(())
1336 }
1337
1338 #[tokio::test]
1339 async fn test_implements_i_describe_by_meta_v1() {
1340 async fn new_server_client() -> (Asserter, impl Provider) {
1342 let asserter = Asserter::new();
1343 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
1344
1345 asserter.push_success(
1347 &"0x0000000000000000000000000000000000000000000000000000000000000001",
1348 );
1349 asserter.push_success(
1350 &"0x0000000000000000000000000000000000000000000000000000000000000000",
1351 );
1352
1353 (asserter, provider)
1354 }
1355
1356 let address = Address::random();
1357
1358 let (asserter, provider) = new_server_client().await;
1360 asserter
1361 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
1362 let result = implements_i_described_by_meta_v1(&provider, address).await;
1363 assert!(result);
1364
1365 let (asserter, provider) = new_server_client().await;
1367 asserter
1368 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
1369 let result = implements_i_described_by_meta_v1(&provider, address).await;
1370 assert!(!result);
1371
1372 let (asserter, provider) = new_server_client().await;
1374 asserter.push_failure(ErrorPayload {
1375 code: -32003,
1376 message: "execution reverted".into(),
1377 data: Some(serde_json::value::to_raw_value(&json!("0x00")).unwrap()),
1378 });
1379 let result = implements_i_described_by_meta_v1(&provider, address).await;
1380 assert!(!result);
1381 }
1382
1383 #[test]
1387 fn oa_schema_map_key_roundtrip() -> Result<(), Error> {
1388 let payload = vec![0x01, 0x02, 0x03];
1389 let schema = "QmSchemaHash1234567890abcdefghijklmnopqrstuvwx".to_string();
1392 assert_eq!(schema.len(), 46);
1393
1394 let meta_map = RainMetaDocumentV1Item {
1395 payload: serde_bytes::ByteBuf::from(payload.clone()),
1396 magic: KnownMagic::OaStructure,
1397 content_type: ContentType::Json,
1398 content_encoding: ContentEncoding::Deflate,
1399 content_language: ContentLanguage::None,
1400 schema: Some(schema.clone()),
1401 };
1402 let cbor_encoded = meta_map.cbor_encode()?;
1403
1404 assert_eq!(cbor_encoded[0], 0xa5);
1406 assert_eq!(cbor_encoded[1], 0x00);
1408 assert_eq!(cbor_encoded[2], 0b010_00011);
1410 assert_eq!(cbor_encoded[3..6], payload);
1412 assert_eq!(cbor_encoded[6], 0x01);
1414 assert_eq!(cbor_encoded[7], 0b000_11011);
1416 assert_eq!(
1418 &cbor_encoded[8..16],
1419 KnownMagic::OaStructure.to_prefix_bytes()
1420 );
1421 assert_eq!(cbor_encoded[16], 0x02);
1423 assert_eq!(cbor_encoded[17], 0b011_10000);
1425 assert_eq!(&cbor_encoded[18..34], "application/json".as_bytes());
1426 assert_eq!(cbor_encoded[34], 0x03);
1428 assert_eq!(cbor_encoded[35], 0b011_00111);
1430 assert_eq!(&cbor_encoded[36..43], "deflate".as_bytes());
1431 assert_eq!(cbor_encoded[43], 0b000_11011);
1433 assert_eq!(
1434 &cbor_encoded[44..52],
1435 KnownMagic::OaSchema.to_prefix_bytes()
1436 );
1437 assert_eq!(cbor_encoded[52], 0b011_11000);
1439 assert_eq!(cbor_encoded[53], 46);
1440 assert_eq!(&cbor_encoded[54..], schema.as_bytes());
1442
1443 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1445 assert_eq!(cbor_decoded.len(), 1);
1447 assert_eq!(cbor_decoded[0], meta_map);
1449
1450 Ok(())
1451 }
1452
1453 #[test]
1456 fn no_schema_key_encodes_as_before_roundtrip() -> Result<(), Error> {
1457 let payload = vec![0x0a, 0x0b];
1458 let meta_map = RainMetaDocumentV1Item {
1459 payload: serde_bytes::ByteBuf::from(payload.clone()),
1460 magic: KnownMagic::OaStructure,
1461 content_type: ContentType::None,
1462 content_encoding: ContentEncoding::None,
1463 content_language: ContentLanguage::None,
1464 schema: None,
1465 };
1466 let cbor_encoded = meta_map.cbor_encode()?;
1467
1468 assert_eq!(cbor_encoded[0], 0xa2);
1470 assert_eq!(cbor_encoded[1], 0x00);
1472 assert_eq!(cbor_encoded[2], 0b010_00010);
1474 assert_eq!(cbor_encoded[3..5], payload);
1476 assert_eq!(cbor_encoded[5], 0x01);
1478 assert_eq!(cbor_encoded[6], 0b000_11011);
1480 assert_eq!(
1482 &cbor_encoded[7..],
1483 KnownMagic::OaStructure.to_prefix_bytes()
1484 );
1485
1486 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1487 assert_eq!(cbor_decoded.len(), 1);
1488 assert_eq!(cbor_decoded[0], meta_map);
1489
1490 Ok(())
1491 }
1492
1493 #[test]
1496 fn unknown_map_key_index_is_ignored() -> Result<(), Error> {
1497 let mut bytes: Vec<u8> = vec![
1498 0xa3, 0x00, 0x40, 0x01, 0x1b,
1502 ];
1503 bytes.extend_from_slice(&KnownMagic::DotrainSourceV1.to_prefix_bytes());
1504 bytes.extend_from_slice(&[0x05, 0x07]);
1506
1507 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1508 assert_eq!(decoded.len(), 1);
1509 assert_eq!(decoded[0], plain_item(KnownMagic::DotrainSourceV1, vec![]));
1510
1511 Ok(())
1512 }
1513
1514 #[test]
1517 fn non_oa_schema_extra_map_key_is_ignored() -> Result<(), Error> {
1518 let mut bytes: Vec<u8> = vec![
1521 0xa3, 0x00, 0x41, 0xff, 0x01, 0x1b,
1525 ];
1526 bytes.extend_from_slice(&KnownMagic::OaStructure.to_prefix_bytes());
1527 bytes.push(0x1b);
1529 bytes.extend_from_slice(&KnownMagic::OaHashList.to_prefix_bytes());
1530 bytes.extend_from_slice(&[0x62, 0x68, 0x69]);
1532
1533 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1534 assert_eq!(decoded.len(), 1);
1535 let expected = plain_item(KnownMagic::OaStructure, vec![0xff]);
1536 assert_eq!(decoded[0], expected);
1537 assert_eq!(decoded[0].schema, None);
1538
1539 Ok(())
1540 }
1541
1542 #[test]
1545 fn unknown_map_key_consumes_its_whole_value() -> Result<(), Error> {
1546 let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1547 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1548 bytes.extend_from_slice(&[0x05, 0xa1, 0x18, 0x2a, 0x82, 0x01, 0x02]);
1550 bytes.extend_from_slice(&handwritten_map());
1551
1552 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1553 assert_eq!(decoded.len(), 2);
1554 let expected = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1555 assert_eq!(decoded[0], expected);
1556 assert_eq!(decoded[1], expected);
1557
1558 Ok(())
1559 }
1560
1561 #[test]
1564 fn ignored_map_key_is_absent_from_the_reencoding() -> Result<(), Error> {
1565 let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1566 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1567 bytes.extend_from_slice(&[0x05, 0x07]);
1568
1569 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1570 assert_eq!(decoded[0].cbor_encode()?, handwritten_map());
1571 assert_eq!(decoded[0].hash(false)?, keccak256(handwritten_map()).0);
1572 assert_ne!(decoded[0].hash(false)?, keccak256(&bytes).0);
1573
1574 Ok(())
1575 }
1576
1577 #[test]
1581 fn non_integer_map_key_errors() {
1582 let mut text_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1583 text_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1584 text_key.extend_from_slice(&[0x61, 0x35, 0x07]);
1586 assert!(matches!(
1587 RainMetaDocumentV1Item::cbor_decode(&text_key),
1588 Err(Error::SerdeCborError(_))
1589 ));
1590
1591 let mut negative_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1592 negative_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1593 negative_key.extend_from_slice(&[0x20, 0x07]);
1595 assert!(matches!(
1596 RainMetaDocumentV1Item::cbor_decode(&negative_key),
1597 Err(Error::SerdeCborError(_))
1598 ));
1599 }
1600
1601 #[test]
1603 fn unknown_map_key_does_not_stand_in_for_a_mandatory_key() {
1604 let mut bytes: Vec<u8> = vec![0xa2, 0x05, 0x07, 0x01, 0x1b];
1605 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1606 assert!(matches!(
1607 RainMetaDocumentV1Item::cbor_decode(&bytes),
1608 Err(Error::SerdeCborError(_))
1609 ));
1610 }
1611
1612 fn plain_item(magic: KnownMagic, payload: Vec<u8>) -> RainMetaDocumentV1Item {
1613 RainMetaDocumentV1Item {
1614 payload: serde_bytes::ByteBuf::from(payload),
1615 magic,
1616 content_type: ContentType::None,
1617 content_encoding: ContentEncoding::None,
1618 content_language: ContentLanguage::None,
1619 schema: None,
1620 }
1621 }
1622
1623 fn sample_authoring_doc() -> (AuthoringMeta, Vec<u8>) {
1626 let authoring_meta: AuthoringMeta = serde_json::from_str(
1627 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
1628 )
1629 .unwrap();
1630 let abi = authoring_meta.abi_encode_validate().unwrap();
1631 let item = RainMetaDocumentV1Item {
1632 payload: serde_bytes::ByteBuf::from(abi),
1633 magic: KnownMagic::AuthoringMetaV1,
1634 content_type: ContentType::Cbor,
1635 content_encoding: ContentEncoding::None,
1636 content_language: ContentLanguage::None,
1637 schema: None,
1638 };
1639 let doc =
1640 RainMetaDocumentV1Item::cbor_encode_seq(&vec![item], KnownMagic::RainMetaDocumentV1)
1641 .unwrap();
1642 (authoring_meta, doc)
1643 }
1644
1645 fn sample_dotrain_item() -> RainMetaDocumentV1Item {
1646 RainMetaDocumentV1Item {
1647 payload: serde_bytes::ByteBuf::from("some dotrain body".as_bytes()),
1648 magic: KnownMagic::DotrainV1,
1649 content_type: ContentType::OctetStream,
1650 content_encoding: ContentEncoding::None,
1651 content_language: ContentLanguage::None,
1652 schema: None,
1653 }
1654 }
1655
1656 fn handwritten_map() -> Vec<u8> {
1659 vec![
1660 0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b, 0xff, 0xda, 0xc2, 0xf2, 0xf3, 0x7b, 0xe8, 0x94, ]
1666 }
1667
1668 #[test]
1672 fn test_hash_bare_vs_document() -> Result<(), Error> {
1673 let map_bytes = handwritten_map();
1674 let mut doc_bytes: Vec<u8> = vec![0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74];
1675 doc_bytes.extend_from_slice(&map_bytes);
1676
1677 let item = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1678 assert_eq!(item.hash(false)?, keccak256(&map_bytes).0);
1679 assert_eq!(item.hash(true)?, keccak256(&doc_bytes).0);
1680 assert_ne!(item.hash(false)?, item.hash(true)?);
1681 Ok(())
1682 }
1683
1684 #[test]
1686 fn test_cbor_decode_empty_is_corrupt() {
1687 assert!(matches!(
1688 RainMetaDocumentV1Item::cbor_decode(&[]),
1689 Err(Error::CorruptMeta)
1690 ));
1691 let prefix = KnownMagic::RainMetaDocumentV1.to_prefix_bytes();
1692 assert!(matches!(
1693 RainMetaDocumentV1Item::cbor_decode(&prefix),
1694 Err(Error::CorruptMeta)
1695 ));
1696 }
1697
1698 #[test]
1701 fn test_cbor_decode_trailing_truncated_is_corrupt() {
1702 let mut bytes = handwritten_map();
1703 bytes.push(0x1b); assert!(matches!(
1705 RainMetaDocumentV1Item::cbor_decode(&bytes),
1706 Err(Error::CorruptMeta)
1707 ));
1708 }
1709
1710 #[test]
1713 fn test_cbor_decode_trailing_garbage_errors() {
1714 let mut bytes = handwritten_map();
1715 bytes.push(0xff); assert!(matches!(
1717 RainMetaDocumentV1Item::cbor_decode(&bytes),
1718 Err(Error::SerdeCborError(_))
1719 ));
1720 }
1721
1722 #[test]
1724 fn test_cbor_decode_missing_payload_errors() {
1725 let mut bytes: Vec<u8> = vec![0xa1, 0x01, 0x1b]; bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1727 assert!(matches!(
1728 RainMetaDocumentV1Item::cbor_decode(&bytes),
1729 Err(Error::SerdeCborError(_))
1730 ));
1731 }
1732
1733 #[test]
1735 fn test_cbor_decode_missing_magic_errors() {
1736 let bytes: Vec<u8> = vec![0xa1, 0x00, 0x41, 0x01]; assert!(matches!(
1738 RainMetaDocumentV1Item::cbor_decode(&bytes),
1739 Err(Error::SerdeCborError(_))
1740 ));
1741 }
1742
1743 #[test]
1745 fn test_cbor_decode_unknown_magic_errors() {
1746 let mut bytes: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1747 bytes.extend_from_slice(&0xdeadbeefdeadbeefu64.to_be_bytes());
1748 assert!(matches!(
1749 RainMetaDocumentV1Item::cbor_decode(&bytes),
1750 Err(Error::SerdeCborError(_))
1751 ));
1752 }
1753
1754 #[test]
1758 fn test_cbor_decode_handwritten_document_magic_item() {
1759 let bytes: Vec<u8> = vec![
1760 0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b, 0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74, ];
1766 assert!(RainMetaDocumentV1Item::cbor_decode(&bytes).is_err());
1770 }
1771
1772 #[test]
1778 fn test_document_magic_item_carries_a_nested_document() -> Result<(), Error> {
1779 let inner = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1780 let inner_doc = RainMetaDocumentV1Item::cbor_encode_seq(
1781 &vec![inner.clone()],
1782 KnownMagic::RainMetaDocumentV1,
1783 )?;
1784 let outer = plain_item(KnownMagic::RainMetaDocumentV1, inner_doc.clone());
1785 let outer_doc = RainMetaDocumentV1Item::cbor_encode_seq(
1786 &vec![outer.clone()],
1787 KnownMagic::RainMetaDocumentV1,
1788 )?;
1789
1790 assert!(RainMetaDocumentV1Item::cbor_decode(&outer_doc).is_err());
1796 assert_eq!(
1797 RainMetaDocumentV1Item::cbor_decode(&inner_doc)?,
1798 vec![inner]
1799 );
1800 Ok(())
1801 }
1802
1803 #[test]
1806 fn test_document_magic_item_is_not_unpackable() {
1807 assert!(matches!(
1808 KnownMeta::try_from(KnownMagic::RainMetaDocumentV1),
1809 Err(Error::UnsupportedMeta)
1810 ));
1811 assert!(matches!(
1812 plain_item(KnownMagic::RainMetaDocumentV1, vec![0x01]).unpack_into::<Vec<u8>>(),
1813 Err(Error::UnsupportedMeta)
1814 ));
1815 }
1816
1817 #[test]
1819 fn test_unpack_decodes_content_encoding() -> Result<(), Error> {
1820 let content = b"unpack me via deflate".to_vec();
1821 let packed = ContentEncoding::Deflate.encode(&content);
1822 assert_ne!(packed, content);
1823 let mut item = plain_item(KnownMagic::DotrainV1, packed);
1824 item.content_encoding = ContentEncoding::Deflate;
1825 assert_eq!(item.unpack()?, content);
1826
1827 let item = plain_item(KnownMagic::DotrainV1, content.clone());
1828 assert_eq!(item.unpack()?, content);
1829 Ok(())
1830 }
1831
1832 #[test]
1835 fn test_unpack_into_whitelist() {
1836 use strum::IntoEnumIterator;
1837 let supported = [
1838 KnownMagic::OpMetaV1,
1839 KnownMagic::DotrainV1,
1840 KnownMagic::RainlangV1,
1841 KnownMagic::SolidityAbiV2,
1842 KnownMagic::AuthoringMetaV1,
1843 KnownMagic::AuthoringMetaV2,
1844 KnownMagic::AddressList,
1845 KnownMagic::InterpreterCallerMetaV1,
1846 KnownMagic::ExpressionDeployerV2BytecodeV1,
1847 KnownMagic::DotrainSourceV1,
1848 KnownMagic::OrderBuilderStateV1,
1849 KnownMagic::RainlangSourceV1,
1850 KnownMagic::RaindexSignedContextOracleV1,
1851 ];
1852 for magic in supported {
1853 let unpacked: Vec<u8> = plain_item(magic, vec![0x61]).unpack_into().unwrap();
1854 assert_eq!(unpacked, vec![0x61], "{:?}", magic);
1855 }
1856 let unsupported = [
1857 KnownMagic::RainMetaDocumentV1,
1858 KnownMagic::WebDataV1,
1859 KnownMagic::OaSchema,
1860 KnownMagic::OaHashList,
1861 KnownMagic::OaStructure,
1862 KnownMagic::OaTokenImage,
1863 KnownMagic::OaTokenCredentialLinks,
1864 ];
1865 for magic in unsupported {
1866 let result: Result<Vec<u8>, Error> = plain_item(magic, vec![0x61]).unpack_into();
1867 assert!(matches!(result, Err(Error::UnsupportedMeta)), "{:?}", magic);
1868 }
1869 assert_eq!(
1871 supported.len() + unsupported.len(),
1872 KnownMagic::iter().count()
1873 );
1874 }
1875
1876 #[test]
1879 fn test_try_into_string_invalid_utf8_errors() {
1880 let item = plain_item(KnownMagic::DotrainV1, vec![0xff, 0xfe]);
1881 let result: Result<String, Error> = item.try_into();
1882 assert!(matches!(result, Err(Error::FromUtf8Error(_))));
1883 }
1884
1885 #[test]
1887 fn test_try_into_vec_decodes_encoding() -> Result<(), Error> {
1888 let content = b"raw bytecode bytes \x00\x01\x02".to_vec();
1889 let packed = ContentEncoding::Deflate.encode(&content);
1890 let mut item = plain_item(KnownMagic::ExpressionDeployerV2BytecodeV1, packed.clone());
1891 item.content_encoding = ContentEncoding::Deflate;
1892 let unpacked: Vec<u8> = item.try_into()?;
1893 assert_eq!(unpacked, content);
1894 assert_ne!(unpacked, packed);
1895 Ok(())
1896 }
1897
1898 #[test]
1901 fn test_content_encoding_deflate_roundtrip() -> Result<(), Error> {
1902 let content = b"hello rain deflate fixture hello rain deflate fixture".to_vec();
1903 let encoded = ContentEncoding::Deflate.encode(&content);
1904 assert_ne!(encoded, content);
1905 assert_eq!(encoded[0], 0x78);
1906 assert_eq!(ContentEncoding::Deflate.decode(&encoded)?, content);
1907 Ok(())
1908 }
1909
1910 #[test]
1912 fn test_content_encoding_passthrough() -> Result<(), Error> {
1913 let data = vec![0x00, 0xff, 0x10];
1914 for encoding in [ContentEncoding::None, ContentEncoding::Identity] {
1915 assert_eq!(encoding.encode(&data), data, "{:?}", encoding);
1916 assert_eq!(encoding.decode(&data)?, data, "{:?}", encoding);
1917 }
1918 Ok(())
1919 }
1920
1921 #[test]
1924 fn test_content_encoding_decode_fixtures() -> Result<(), Error> {
1925 let content = b"hello rain deflate fixture".to_vec();
1926 let zlib: Vec<u8> = vec![
1927 120, 156, 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44,
1928 73, 85, 72, 203, 172, 40, 41, 45, 74, 5, 0, 132, 64, 9, 251,
1929 ];
1930 let raw: Vec<u8> = vec![
1931 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44, 73, 85, 72,
1932 203, 172, 40, 41, 45, 74, 5, 0,
1933 ];
1934 assert_eq!(ContentEncoding::Deflate.decode(&zlib)?, content);
1935 assert_eq!(ContentEncoding::Deflate.decode(&raw)?, content);
1936 Ok(())
1937 }
1938
1939 #[test]
1942 fn test_content_encoding_decode_garbage_errors() {
1943 let garbage = [0xffu8, 0xff, 0xff, 0xff];
1944 assert!(matches!(
1945 ContentEncoding::Deflate.decode(&garbage),
1946 Err(Error::InflateError(_))
1947 ));
1948 }
1949
1950 #[test]
1952 fn test_content_headers_strum_names() {
1953 use std::str::FromStr;
1954 assert_eq!(
1955 ContentEncoding::from_str("deflate").unwrap(),
1956 ContentEncoding::Deflate
1957 );
1958 assert_eq!(
1959 ContentEncoding::from_str("identity").unwrap(),
1960 ContentEncoding::Identity
1961 );
1962 assert_eq!(
1963 ContentEncoding::from_str("none").unwrap(),
1964 ContentEncoding::None
1965 );
1966 assert_eq!(ContentEncoding::Deflate.to_string(), "deflate");
1967 assert_eq!(
1968 ContentType::from_str("octet-stream").unwrap(),
1969 ContentType::OctetStream
1970 );
1971 assert_eq!(ContentType::from_str("json").unwrap(), ContentType::Json);
1972 assert_eq!(ContentType::Json.to_string(), "json");
1973 assert_eq!(
1974 ContentLanguage::from_str("en").unwrap(),
1975 ContentLanguage::En
1976 );
1977 }
1978
1979 #[test]
1982 fn test_known_meta_try_from_magic() {
1983 let cases: [(KnownMagic, KnownMeta); 13] = [
1984 (KnownMagic::OpMetaV1, KnownMeta::OpV1),
1985 (KnownMagic::DotrainV1, KnownMeta::DotrainV1),
1986 (KnownMagic::RainlangV1, KnownMeta::RainlangV1),
1987 (KnownMagic::SolidityAbiV2, KnownMeta::SolidityAbiV2),
1988 (KnownMagic::AuthoringMetaV1, KnownMeta::AuthoringMetaV1),
1989 (KnownMagic::AuthoringMetaV2, KnownMeta::AuthoringMetaV2),
1990 (KnownMagic::AddressList, KnownMeta::AddressList),
1991 (
1992 KnownMagic::InterpreterCallerMetaV1,
1993 KnownMeta::InterpreterCallerMetaV1,
1994 ),
1995 (
1996 KnownMagic::ExpressionDeployerV2BytecodeV1,
1997 KnownMeta::ExpressionDeployerV2BytecodeV1,
1998 ),
1999 (KnownMagic::RainlangSourceV1, KnownMeta::RainlangSourceV1),
2000 (KnownMagic::DotrainSourceV1, KnownMeta::DotrainSourceV1),
2001 (
2002 KnownMagic::OrderBuilderStateV1,
2003 KnownMeta::OrderBuilderStateV1,
2004 ),
2005 (
2006 KnownMagic::RaindexSignedContextOracleV1,
2007 KnownMeta::RaindexSignedContextOracleV1,
2008 ),
2009 ];
2010 for (magic, meta) in cases {
2011 assert_eq!(KnownMeta::try_from(magic).unwrap(), meta, "{:?}", magic);
2012 }
2013 for magic in [
2014 KnownMagic::RainMetaDocumentV1,
2015 KnownMagic::WebDataV1,
2016 KnownMagic::OaSchema,
2017 KnownMagic::OaHashList,
2018 KnownMagic::OaStructure,
2019 KnownMagic::OaTokenImage,
2020 KnownMagic::OaTokenCredentialLinks,
2021 ] {
2022 assert!(
2023 matches!(KnownMeta::try_from(magic), Err(Error::UnsupportedMeta)),
2024 "{:?}",
2025 magic
2026 );
2027 }
2028 }
2029
2030 #[test]
2033 fn test_known_meta_strum_parse_display() {
2034 use std::str::FromStr;
2035 assert_eq!(KnownMeta::from_str("op-v1").unwrap(), KnownMeta::OpV1);
2036 assert_eq!(
2037 KnownMeta::from_str("solidity-abi-v2").unwrap(),
2038 KnownMeta::SolidityAbiV2
2039 );
2040 assert_eq!(
2041 KnownMeta::from_str("interpreter-caller-meta-v1").unwrap(),
2042 KnownMeta::InterpreterCallerMetaV1
2043 );
2044 assert_eq!(KnownMeta::SolidityAbiV2.to_string(), "solidity-abi-v2");
2045 assert_eq!(KnownMeta::OpV1.to_string(), "op-v1");
2046 }
2047
2048 fn sample_deployer(meta_hash: &[u8], meta_bytes: &[u8]) -> NPE2Deployer {
2049 NPE2Deployer {
2050 meta_hash: meta_hash.to_vec(),
2051 meta_bytes: meta_bytes.to_vec(),
2052 bytecode: vec![0xb1],
2053 parser: vec![0xb2],
2054 store: vec![0xb3],
2055 interpreter: vec![0xb4],
2056 authoring_meta: None,
2057 }
2058 }
2059
2060 fn deployer_json_body(
2061 meta_hash_hex: &str,
2062 meta_bytes_hex: &str,
2063 tx_hex: &str,
2064 bytecode_meta_id_hex: &str,
2065 ) -> serde_json::Value {
2066 json!({
2067 "data": {
2068 "expressionDeployers": [{
2069 "constructorMetaHash": meta_hash_hex,
2070 "constructorMeta": meta_bytes_hex,
2071 "deployTransaction": {"id": tx_hex},
2072 "bytecode": "0x01",
2073 "parser": {"parser": {"deployedBytecode": "0x02"}},
2074 "store": {"store": {"deployedBytecode": "0x03"}},
2075 "interpreter": {"interpreter": {"deployedBytecode": "0x04"}},
2076 "meta": [{"__typename": "RainMetaV1", "id": bytecode_meta_id_hex}]
2077 }]
2078 }
2079 })
2080 }
2081
2082 #[tokio::test]
2084 async fn test_search_lowercases_hash() {
2085 use httpmock::prelude::*;
2086 let (_, doc) = sample_authoring_doc();
2087 let hash_upper = format!("0x{}", "AB".repeat(32));
2088 let server = MockServer::start();
2089 let mock = server.mock(|when, then| {
2090 when.method(POST)
2091 .body_contains(hash_upper.to_ascii_lowercase());
2092 then.status(200).json_body(json!({
2093 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2094 }));
2095 });
2096 let response = search(&hash_upper, &vec![server.url("/sg")]).await.unwrap();
2097 assert_eq!(response.bytes, doc);
2098 mock.assert();
2099 }
2100
2101 #[tokio::test]
2104 async fn test_search_first_success_wins() {
2105 use httpmock::prelude::*;
2106 let (_, doc) = sample_authoring_doc();
2107 let bad = MockServer::start();
2108 let _bad_mock = bad.mock(|when, then| {
2109 when.method(POST);
2110 then.status(500).body("subgraph down");
2111 });
2112 let good = MockServer::start();
2113 let _good_mock = good.mock(|when, then| {
2114 when.method(POST);
2115 then.status(200).json_body(json!({
2116 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2117 }));
2118 });
2119 let response = search(
2120 &format!("0x{}", "11".repeat(32)),
2121 &vec![bad.url("/sg"), good.url("/sg")],
2122 )
2123 .await
2124 .unwrap();
2125 assert_eq!(response.bytes, doc);
2126 }
2127
2128 #[tokio::test]
2131 async fn test_search_deployer_lowercases_hash() {
2132 use httpmock::prelude::*;
2133 let (_, doc) = sample_authoring_doc();
2134 let meta_hash_hex = hex::encode_prefixed(keccak256(&doc).0);
2135 let hash_upper = format!("0x{}", "CD".repeat(32));
2136 let server = MockServer::start();
2137 let mock = server.mock(|when, then| {
2138 when.method(POST)
2139 .body_contains(hash_upper.to_ascii_lowercase());
2140 then.status(200).json_body(deployer_json_body(
2141 &meta_hash_hex,
2142 &hex::encode_prefixed(&doc),
2143 &format!("0x{}", "77".repeat(32)),
2144 &meta_hash_hex,
2145 ));
2146 });
2147 let response = search_deployer(&hash_upper, &vec![server.url("/sg")])
2148 .await
2149 .unwrap();
2150 assert_eq!(response.meta_bytes, doc);
2151 assert_eq!(response.bytecode, vec![0x01]);
2152 mock.assert();
2153 }
2154
2155 #[tokio::test]
2158 async fn test_search_deployer_first_success_wins() {
2159 use httpmock::prelude::*;
2160 let (_, doc) = sample_authoring_doc();
2161 let meta_hash_hex = hex::encode_prefixed(keccak256(&doc).0);
2162 let bad = MockServer::start();
2163 let _bad_mock = bad.mock(|when, then| {
2164 when.method(POST);
2165 then.status(500).body("subgraph down");
2166 });
2167 let good = MockServer::start();
2168 let _good_mock = good.mock(|when, then| {
2169 when.method(POST);
2170 then.status(200).json_body(deployer_json_body(
2171 &meta_hash_hex,
2172 &hex::encode_prefixed(&doc),
2173 &format!("0x{}", "77".repeat(32)),
2174 &meta_hash_hex,
2175 ));
2176 });
2177 let response = search_deployer(
2178 &format!("0x{}", "22".repeat(32)),
2179 &vec![bad.url("/sg"), good.url("/sg")],
2180 )
2181 .await
2182 .unwrap();
2183 assert_eq!(response.meta_bytes, doc);
2184 }
2185
2186 #[tokio::test]
2190 async fn test_search_empty_subgraphs_is_a_miss() {
2191 let hash = format!("0x{}", "33".repeat(32));
2192 assert!(matches!(
2193 search(&hash, &vec![]).await,
2194 Err(Error::NoRecordFound)
2195 ));
2196 assert!(matches!(
2197 search_deployer(&hash, &vec![]).await,
2198 Err(Error::NoRecordFound)
2199 ));
2200 }
2201
2202 #[tokio::test]
2206 async fn test_implements_erc165_gate_short_circuits() {
2207 let address = Address::random();
2208
2209 let asserter = Asserter::new();
2211 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2212 asserter
2213 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2214 asserter
2215 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2216 assert!(!implements_i_described_by_meta_v1(&provider, address).await);
2217
2218 let asserter = Asserter::new();
2220 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2221 asserter.push_failure(ErrorPayload {
2222 code: -32000,
2223 message: "connection reset".into(),
2224 data: None,
2225 });
2226 asserter
2227 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2228 assert!(!implements_i_described_by_meta_v1(&provider, address).await);
2229 }
2230
2231 #[tokio::test]
2234 async fn test_implements_undecodable_response_is_false() {
2235 let address = Address::random();
2236 let asserter = Asserter::new();
2237 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2238 asserter
2239 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2240 asserter
2241 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2242 asserter.push_success(&"0x");
2243 assert!(!implements_i_described_by_meta_v1(&provider, address).await);
2244 }
2245
2246 #[test]
2249 fn test_npe2_deployer_is_corrupt_per_field() {
2250 let full = NPE2Deployer {
2251 meta_hash: vec![1],
2252 meta_bytes: vec![2],
2253 bytecode: vec![3],
2254 parser: vec![4],
2255 store: vec![5],
2256 interpreter: vec![6],
2257 authoring_meta: None,
2258 };
2259 assert!(!full.is_corrupt());
2260 for field in 0..6usize {
2261 let mut record = full.clone();
2262 match field {
2263 0 => record.meta_hash = vec![],
2264 1 => record.meta_bytes = vec![],
2265 2 => record.bytecode = vec![],
2266 3 => record.parser = vec![],
2267 4 => record.store = vec![],
2268 5 => record.interpreter = vec![],
2269 _ => unreachable!(),
2270 }
2271 assert!(record.is_corrupt(), "empty field {} must corrupt", field);
2272 }
2273 }
2274
2275 #[tokio::test]
2279 async fn test_store_constructors_inject_no_subgraphs() {
2280 assert!(Store::new().subgraphs().is_empty());
2281 assert!(Store::default().subgraphs().is_empty());
2282 assert!(
2283 Store::create(&vec![], &HashMap::new(), &HashMap::new(), &HashMap::new())
2284 .subgraphs()
2285 .is_empty()
2286 );
2287
2288 let hash = [0u8; 32];
2289 let mut store = Store::default();
2290 assert!(store.update(&hash).await.is_none());
2291 assert!(store.search_deployer(&hash).await.is_none());
2292 }
2293
2294 #[test]
2298 fn test_store_create_validates_entries() {
2299 let (_, doc) = sample_authoring_doc();
2300 let good_hash = keccak256(&doc).0.to_vec();
2301 let bad_hash = vec![0xEEu8; 32];
2302 let mut cache = HashMap::new();
2303 cache.insert(good_hash.clone(), doc.clone());
2304 cache.insert(bad_hash.clone(), b"does not hash to bad_hash".to_vec());
2305 let mut deployer_cache = HashMap::new();
2306 let deployer = sample_deployer(&[0xAA; 32], b"dep-meta");
2307 let deployer_key = vec![0x33u8; 32];
2308 deployer_cache.insert(deployer_key.clone(), deployer.clone());
2309 let mut dotrain_cache = HashMap::new();
2310 dotrain_cache.insert("a.rain".to_string(), good_hash.clone());
2311 dotrain_cache.insert("missing.rain".to_string(), vec![0x44u8; 32]);
2312
2313 let store = Store::create(
2314 &vec!["https://example.com/custom-sg".to_string()],
2315 &cache,
2316 &deployer_cache,
2317 &dotrain_cache,
2318 );
2319
2320 assert_eq!(
2321 store.subgraphs(),
2322 &vec!["https://example.com/custom-sg".to_string()]
2323 );
2324 assert_eq!(store.get_meta(&good_hash), Some(&doc));
2325 assert_eq!(store.get_meta(&bad_hash), None);
2326 assert_eq!(store.get_deployer(&deployer_key), Some(&deployer));
2327 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&good_hash));
2328 assert_eq!(store.get_dotrain_hash("missing.rain"), None);
2329 }
2330
2331 #[test]
2333 fn test_store_add_subgraphs_dedupe() {
2334 let mut store = Store::new();
2335 store.add_subgraphs(&vec!["sg-a".to_string()]);
2336 store.add_subgraphs(&vec!["sg-a".to_string(), "sg-b".to_string()]);
2337 assert_eq!(
2338 store.subgraphs(),
2339 &vec!["sg-a".to_string(), "sg-b".to_string()]
2340 );
2341 }
2342
2343 #[test]
2346 fn test_store_get_deployer_lookup_chain() {
2347 let mut store = Store::new();
2348 let deployer = sample_deployer(&[0xAB; 32], b"dep-meta-bytes");
2349 let key = vec![0x01u8; 32];
2350 let tx = vec![0x02u8; 32];
2351 store.set_deployer(&key, &deployer, Some(&tx));
2352 assert_eq!(store.get_deployer(&key), Some(&deployer));
2353 assert_eq!(store.get_deployer(&tx), Some(&deployer));
2354 assert_eq!(store.get_deployer(&[0x03u8; 32]), None);
2355 assert_eq!(
2356 store.get_meta(&deployer.meta_hash),
2357 Some(&deployer.meta_bytes)
2358 );
2359 }
2360
2361 #[tokio::test]
2365 async fn test_store_search_deployer_populates_caches() {
2366 use httpmock::prelude::*;
2367 let (authoring_meta, doc) = sample_authoring_doc();
2368 let meta_hash = keccak256(&doc).0.to_vec();
2369 let meta_hash_hex = hex::encode_prefixed(&meta_hash);
2370 let tx = vec![0x77u8; 32];
2371 let server = MockServer::start();
2372 let _mock = server.mock(|when, then| {
2373 when.method(POST);
2374 then.status(200).json_body(deployer_json_body(
2375 &meta_hash_hex,
2376 &hex::encode_prefixed(&doc),
2377 &hex::encode_prefixed(&tx),
2378 &meta_hash_hex,
2379 ));
2380 });
2381 let mut store = Store::new();
2382 store.add_subgraphs(&vec![server.url("/sg")]);
2383
2384 let record = store.search_deployer(&meta_hash).await.cloned().unwrap();
2385 assert_eq!(record.meta_hash, meta_hash);
2386 assert_eq!(record.meta_bytes, doc);
2387 assert_eq!(record.bytecode, vec![0x01]);
2388 assert_eq!(record.parser, vec![0x02]);
2389 assert_eq!(record.store, vec![0x03]);
2390 assert_eq!(record.interpreter, vec![0x04]);
2391 assert_eq!(record.authoring_meta, Some(authoring_meta));
2392 assert_eq!(store.get_meta(&meta_hash), Some(&doc));
2393 assert_eq!(store.get_deployer(&tx), Some(&record));
2394 }
2395
2396 #[tokio::test]
2398 async fn test_store_search_deployer_error_returns_none() {
2399 use httpmock::prelude::*;
2400 let server = MockServer::start();
2401 let _mock = server.mock(|when, then| {
2402 when.method(POST);
2403 then.status(500).body("subgraph down");
2404 });
2405 let mut store = Store::new();
2406 store.add_subgraphs(&vec![server.url("/sg")]);
2407 assert!(store.search_deployer(&[0x0Du8; 32]).await.is_none());
2408 assert!(store.cache().is_empty());
2409 assert!(store.deployer_cache().is_empty());
2410 }
2411
2412 #[tokio::test]
2416 async fn test_store_search_deployer_check_branches() {
2417 use httpmock::prelude::*;
2418 let mut store = Store::new();
2420 let deployer = sample_deployer(&[0xAC; 32], b"cached-meta");
2421 let key = vec![0x11u8; 32];
2422 let tx = vec![0x22u8; 32];
2423 store.set_deployer(&key, &deployer, Some(&tx));
2424 assert_eq!(store.search_deployer_check(&key).await, Some(&deployer));
2425 assert_eq!(store.search_deployer_check(&tx).await, Some(&deployer));
2426
2427 let (_, doc) = sample_authoring_doc();
2429 let meta_hash = keccak256(&doc).0.to_vec();
2430 let meta_hash_hex = hex::encode_prefixed(&meta_hash);
2431 let server = MockServer::start();
2432 let _mock = server.mock(|when, then| {
2433 when.method(POST);
2434 then.status(200).json_body(deployer_json_body(
2435 &meta_hash_hex,
2436 &hex::encode_prefixed(&doc),
2437 &format!("0x{}", "66".repeat(32)),
2438 &meta_hash_hex,
2439 ));
2440 });
2441 let mut fresh = Store::new();
2442 fresh.add_subgraphs(&vec![server.url("/sg")]);
2443 let found = fresh
2444 .search_deployer_check(&meta_hash)
2445 .await
2446 .cloned()
2447 .unwrap();
2448 assert_eq!(found.meta_bytes, doc);
2449 }
2450
2451 #[test]
2454 fn test_store_set_deployer_from_query_response() {
2455 let (authoring_meta, doc) = sample_authoring_doc();
2456 let meta_hash = vec![0x0Au8; 32];
2457 let bytecode_meta_hash = vec![0x0Bu8; 32];
2458 let tx = vec![0x0Cu8; 32];
2459 let response = DeployerResponse {
2460 tx_hash: tx.clone(),
2461 bytecode_meta_hash: bytecode_meta_hash.clone(),
2462 meta_hash: meta_hash.clone(),
2463 meta_bytes: doc.clone(),
2464 bytecode: vec![0xE1],
2465 parser: vec![0xE2],
2466 store: vec![0xE3],
2467 interpreter: vec![0xE4],
2468 };
2469 let mut store = Store::new();
2470 let record = store.set_deployer_from_query_response(response);
2471 assert_eq!(record.meta_hash, meta_hash);
2472 assert_eq!(record.meta_bytes, doc);
2473 assert_eq!(record.bytecode, vec![0xE1]);
2474 assert_eq!(record.authoring_meta, Some(authoring_meta));
2475 assert_eq!(store.get_meta(&meta_hash), Some(&doc));
2476 assert_eq!(store.get_deployer(&bytecode_meta_hash), Some(&record));
2477 assert_eq!(store.get_deployer(&tx), Some(&record));
2478 }
2479
2480 #[test]
2484 fn test_store_dotrain_getters_and_set_fresh() {
2485 let mut store = Store::new();
2486 let text = "some dotrain content";
2487 let (hash, old) = store.set_dotrain(text, "file.rain", false).unwrap();
2488 assert!(old.is_empty());
2489 let expected_item = RainMetaDocumentV1Item {
2490 payload: serde_bytes::ByteBuf::from(text.as_bytes()),
2491 magic: KnownMagic::DotrainV1,
2492 content_type: ContentType::OctetStream,
2493 content_encoding: ContentEncoding::None,
2494 content_language: ContentLanguage::None,
2495 schema: None,
2496 };
2497 let expected_bytes = expected_item.cbor_encode().unwrap();
2498 assert_eq!(hash, keccak256(&expected_bytes).0.to_vec());
2499 assert_eq!(store.get_dotrain_hash("file.rain"), Some(&hash));
2500 assert_eq!(store.get_dotrain_uri(&hash), Some(&"file.rain".to_string()));
2501 assert_eq!(store.get_dotrain_meta("file.rain"), Some(&expected_bytes));
2502 assert_eq!(store.get_dotrain_hash("other.rain"), None);
2503 assert_eq!(store.get_dotrain_uri(&[0u8; 32]), None);
2504 assert_eq!(store.get_dotrain_meta("other.rain"), None);
2505 }
2506
2507 #[test]
2511 fn test_store_set_dotrain_branches() {
2512 let mut store = Store::new();
2513 let (hash_one, _) = store.set_dotrain("text one", "a.rain", false).unwrap();
2514
2515 let (hash_same, old_same) = store.set_dotrain("text one", "a.rain", false).unwrap();
2517 assert_eq!(hash_same, hash_one);
2518 assert!(old_same.is_empty());
2519 assert!(store.get_meta(&hash_one).is_some());
2520
2521 let (hash_two, old_two) = store.set_dotrain("text two", "a.rain", false).unwrap();
2523 assert_ne!(hash_two, hash_one);
2524 assert_eq!(old_two, hash_one);
2525 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_two));
2526 assert!(store.get_meta(&hash_one).is_none());
2527 assert!(store.get_meta(&hash_two).is_some());
2528
2529 let (hash_three, old_three) = store.set_dotrain("text three", "a.rain", true).unwrap();
2531 assert_eq!(old_three, hash_two);
2532 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_three));
2533 assert!(store.get_meta(&hash_two).is_some());
2534 assert!(store.get_meta(&hash_three).is_some());
2535 }
2536
2537 #[test]
2540 fn test_store_delete_dotrain_keep_meta() {
2541 let mut store = Store::new();
2542 let (hash, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2543 store.delete_dotrain("d.rain", false);
2544 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2545 assert!(store.get_meta(&hash).is_none());
2546
2547 let (hash_again, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2548 store.delete_dotrain("d.rain", true);
2549 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2550 assert!(store.get_meta(&hash_again).is_some());
2551 }
2552
2553 #[test]
2556 fn test_store_merge_semantics() {
2557 let shared_meta_hash = vec![0x5Au8; 32];
2558 let deployer_ours = sample_deployer(&shared_meta_hash, b"ours");
2559 let deployer_theirs = sample_deployer(&shared_meta_hash, b"theirs");
2560 let shared_tx = vec![0x0Fu8; 32];
2561 let their_tx = vec![0x1Eu8; 32];
2562
2563 let mut ours = Store::new();
2564 let mut theirs = Store::new();
2565 ours.set_deployer(&[0x01u8; 32], &deployer_ours, Some(&shared_tx));
2566 theirs.set_deployer(&[0x02u8; 32], &deployer_theirs, Some(&shared_tx));
2567 theirs.set_deployer(&[0x02u8; 32], &deployer_theirs, Some(&their_tx));
2568
2569 let contested_key = vec![0x03u8; 32];
2571 let deployer_a = sample_deployer(&[0x04; 32], b"deployer-a");
2572 let deployer_b = sample_deployer(&[0x05; 32], b"deployer-b");
2573 ours.set_deployer(&contested_key, &deployer_a, None);
2574 theirs.set_deployer(&contested_key, &deployer_b, None);
2575
2576 let (hash_ours, _) = ours.set_dotrain("content a", "x.rain", false).unwrap();
2578 let (_hash_theirs, _) = theirs.set_dotrain("content b", "x.rain", false).unwrap();
2579
2580 theirs.add_subgraphs(&vec!["sg-their".to_string()]);
2581
2582 ours.merge(&theirs);
2583
2584 assert_eq!(ours.get_meta(&shared_meta_hash), Some(&b"ours".to_vec()));
2586 assert_eq!(ours.get_deployer(&contested_key), Some(&deployer_a));
2588 assert_eq!(ours.get_deployer(&shared_tx), Some(&deployer_ours));
2590 assert_eq!(ours.get_deployer(&their_tx), Some(&deployer_theirs));
2592 assert_eq!(ours.get_dotrain_hash("x.rain"), Some(&hash_ours));
2594 assert!(ours.subgraphs().contains(&"sg-their".to_string()));
2596 }
2597
2598 #[tokio::test]
2602 async fn test_store_update_and_update_check() {
2603 use httpmock::prelude::*;
2604 let authoring_meta: AuthoringMeta = serde_json::from_str(
2605 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
2606 )
2607 .unwrap();
2608 let item_one = RainMetaDocumentV1Item {
2609 payload: serde_bytes::ByteBuf::from(authoring_meta.abi_encode_validate().unwrap()),
2610 magic: KnownMagic::AuthoringMetaV1,
2611 content_type: ContentType::Cbor,
2612 content_encoding: ContentEncoding::None,
2613 content_language: ContentLanguage::None,
2614 schema: None,
2615 };
2616 let item_two = sample_dotrain_item();
2617 let doc = RainMetaDocumentV1Item::cbor_encode_seq(
2618 &vec![item_one.clone(), item_two.clone()],
2619 KnownMagic::RainMetaDocumentV1,
2620 )
2621 .unwrap();
2622 let requested = keccak256(&doc).0.to_vec();
2623 let server = MockServer::start();
2624 let _mock = server.mock(|when, then| {
2625 when.method(POST);
2626 then.status(200).json_body(json!({
2627 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2628 }));
2629 });
2630 let mut store = Store::new();
2631 store.add_subgraphs(&vec![server.url("/sg")]);
2632 let fetched = store.update(&requested).await.cloned().unwrap();
2633 assert_eq!(fetched, doc);
2634 assert_eq!(store.get_meta(&requested), Some(&doc));
2635 let inner_one = item_one.cbor_encode().unwrap();
2636 let inner_two = item_two.cbor_encode().unwrap();
2637 assert_eq!(
2638 store.get_meta(keccak256(&inner_one).0.as_ref()),
2639 Some(&inner_one)
2640 );
2641 assert_eq!(
2642 store.get_meta(keccak256(&inner_two).0.as_ref()),
2643 Some(&inner_two)
2644 );
2645
2646 let mut cached_store = Store::new();
2648 let bytes = b"standalone meta bytes".to_vec();
2649 let hash = keccak256(&bytes).0.to_vec();
2650 assert!(cached_store.update_with(&hash, &bytes).is_some());
2651 assert_eq!(cached_store.update_check(&hash).await, Some(&bytes));
2652 }
2653
2654 #[tokio::test]
2657 async fn test_store_no_subgraphs_lookups_return_none() {
2658 let hash = [0u8; 32];
2659 let mut store = Store::new();
2660 assert!(store.update(&hash).await.is_none());
2661 assert!(store.update_check(&hash).await.is_none());
2662 assert!(store.search_deployer(&hash).await.is_none());
2663 assert!(store.search_deployer_check(&hash).await.is_none());
2664 assert!(store.cache().is_empty());
2665 assert!(store.deployer_cache().is_empty());
2666 }
2667
2668 #[test]
2672 fn test_store_update_with_validation_and_content() {
2673 let mut store = Store::new();
2675 let bytes = b"payload bytes".to_vec();
2676 let wrong_hash = vec![0x99u8; 32];
2677 assert!(store.update_with(&wrong_hash, &bytes).is_none());
2678 assert!(store.get_meta(&wrong_hash).is_none());
2679 let hash = keccak256(&bytes).0.to_vec();
2681 assert_eq!(store.update_with(&hash, &bytes), Some(&bytes));
2682
2683 let mut seeded = Store::new();
2685 let content = b"real content".to_vec();
2686 let content_hash = keccak256(&content).0.to_vec();
2687 let planted = sample_deployer(&content_hash, b"planted value");
2688 seeded.set_deployer(&[0x77u8; 32], &planted, None);
2689 assert_eq!(
2690 seeded.update_with(&content_hash, &content),
2691 Some(&b"planted value".to_vec())
2692 );
2693 assert_eq!(
2694 seeded.get_meta(&content_hash),
2695 Some(&b"planted value".to_vec())
2696 );
2697
2698 let (_, doc) = sample_authoring_doc();
2700 let doc_hash = keccak256(&doc).0.to_vec();
2701 let mut doc_store = Store::new();
2702 assert!(doc_store.update_with(&doc_hash, &doc).is_some());
2703 let inner = doc[8..].to_vec();
2704 assert_eq!(store_inner_lookup(&doc_store, &inner), Some(inner.clone()));
2705
2706 let item_a = sample_dotrain_item().cbor_encode().unwrap();
2708 let (_, doc_b) = sample_authoring_doc();
2709 let item_b = doc_b[8..].to_vec();
2710 let seq = [item_a.clone(), item_b].concat();
2711 let seq_hash = keccak256(&seq).0.to_vec();
2712 let mut seq_store = Store::new();
2713 assert!(seq_store.update_with(&seq_hash, &seq).is_some());
2714 assert_eq!(store_inner_lookup(&seq_store, &item_a), None);
2715 }
2716
2717 fn store_inner_lookup(store: &Store, inner_encoded: &[u8]) -> Option<Vec<u8>> {
2718 store.get_meta(keccak256(inner_encoded).0.as_ref()).cloned()
2719 }
2720
2721 #[test]
2724 fn test_bytes32_to_str_invalid_utf8() {
2725 let mut bytes = [0u8; 32];
2726 bytes[0] = 0xf0;
2727 bytes[1] = 0x28;
2728 bytes[2] = 0x8c;
2729 bytes[3] = 0x28;
2730 assert!(matches!(bytes32_to_str(&bytes), Err(Error::Utf8Error(_))));
2731 let no_nul = [0xffu8; 32];
2732 assert!(matches!(bytes32_to_str(&no_nul), Err(Error::Utf8Error(_))));
2733 }
2734}