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_implements_erc165_gate_short_circuits() {
2191 let address = Address::random();
2192
2193 let asserter = Asserter::new();
2195 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2196 asserter
2197 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2198 asserter
2199 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2200 assert!(!implements_i_described_by_meta_v1(&provider, address).await);
2201
2202 let asserter = Asserter::new();
2204 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2205 asserter.push_failure(ErrorPayload {
2206 code: -32000,
2207 message: "connection reset".into(),
2208 data: None,
2209 });
2210 asserter
2211 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2212 assert!(!implements_i_described_by_meta_v1(&provider, address).await);
2213 }
2214
2215 #[tokio::test]
2218 async fn test_implements_undecodable_response_is_false() {
2219 let address = Address::random();
2220 let asserter = Asserter::new();
2221 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2222 asserter
2223 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2224 asserter
2225 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2226 asserter.push_success(&"0x");
2227 assert!(!implements_i_described_by_meta_v1(&provider, address).await);
2228 }
2229
2230 #[test]
2233 fn test_npe2_deployer_is_corrupt_per_field() {
2234 let full = NPE2Deployer {
2235 meta_hash: vec![1],
2236 meta_bytes: vec![2],
2237 bytecode: vec![3],
2238 parser: vec![4],
2239 store: vec![5],
2240 interpreter: vec![6],
2241 authoring_meta: None,
2242 };
2243 assert!(!full.is_corrupt());
2244 for field in 0..6usize {
2245 let mut record = full.clone();
2246 match field {
2247 0 => record.meta_hash = vec![],
2248 1 => record.meta_bytes = vec![],
2249 2 => record.bytecode = vec![],
2250 3 => record.parser = vec![],
2251 4 => record.store = vec![],
2252 5 => record.interpreter = vec![],
2253 _ => unreachable!(),
2254 }
2255 assert!(record.is_corrupt(), "empty field {} must corrupt", field);
2256 }
2257 }
2258
2259 #[tokio::test]
2263 async fn test_store_constructors_inject_no_subgraphs() {
2264 assert!(Store::new().subgraphs().is_empty());
2265 assert!(Store::default().subgraphs().is_empty());
2266 assert!(
2267 Store::create(&vec![], &HashMap::new(), &HashMap::new(), &HashMap::new())
2268 .subgraphs()
2269 .is_empty()
2270 );
2271
2272 let hash = [0u8; 32];
2273 let mut store = Store::default();
2274 assert!(store.update(&hash).await.is_none());
2275 assert!(store.search_deployer(&hash).await.is_none());
2276 }
2277
2278 #[test]
2282 fn test_store_create_validates_entries() {
2283 let (_, doc) = sample_authoring_doc();
2284 let good_hash = keccak256(&doc).0.to_vec();
2285 let bad_hash = vec![0xEEu8; 32];
2286 let mut cache = HashMap::new();
2287 cache.insert(good_hash.clone(), doc.clone());
2288 cache.insert(bad_hash.clone(), b"does not hash to bad_hash".to_vec());
2289 let mut deployer_cache = HashMap::new();
2290 let deployer = sample_deployer(&[0xAA; 32], b"dep-meta");
2291 let deployer_key = vec![0x33u8; 32];
2292 deployer_cache.insert(deployer_key.clone(), deployer.clone());
2293 let mut dotrain_cache = HashMap::new();
2294 dotrain_cache.insert("a.rain".to_string(), good_hash.clone());
2295 dotrain_cache.insert("missing.rain".to_string(), vec![0x44u8; 32]);
2296
2297 let store = Store::create(
2298 &vec!["https://example.com/custom-sg".to_string()],
2299 &cache,
2300 &deployer_cache,
2301 &dotrain_cache,
2302 );
2303
2304 assert_eq!(
2305 store.subgraphs(),
2306 &vec!["https://example.com/custom-sg".to_string()]
2307 );
2308 assert_eq!(store.get_meta(&good_hash), Some(&doc));
2309 assert_eq!(store.get_meta(&bad_hash), None);
2310 assert_eq!(store.get_deployer(&deployer_key), Some(&deployer));
2311 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&good_hash));
2312 assert_eq!(store.get_dotrain_hash("missing.rain"), None);
2313 }
2314
2315 #[test]
2317 fn test_store_add_subgraphs_dedupe() {
2318 let mut store = Store::new();
2319 store.add_subgraphs(&vec!["sg-a".to_string()]);
2320 store.add_subgraphs(&vec!["sg-a".to_string(), "sg-b".to_string()]);
2321 assert_eq!(
2322 store.subgraphs(),
2323 &vec!["sg-a".to_string(), "sg-b".to_string()]
2324 );
2325 }
2326
2327 #[test]
2330 fn test_store_get_deployer_lookup_chain() {
2331 let mut store = Store::new();
2332 let deployer = sample_deployer(&[0xAB; 32], b"dep-meta-bytes");
2333 let key = vec![0x01u8; 32];
2334 let tx = vec![0x02u8; 32];
2335 store.set_deployer(&key, &deployer, Some(&tx));
2336 assert_eq!(store.get_deployer(&key), Some(&deployer));
2337 assert_eq!(store.get_deployer(&tx), Some(&deployer));
2338 assert_eq!(store.get_deployer(&[0x03u8; 32]), None);
2339 assert_eq!(
2340 store.get_meta(&deployer.meta_hash),
2341 Some(&deployer.meta_bytes)
2342 );
2343 }
2344
2345 #[tokio::test]
2349 async fn test_store_search_deployer_populates_caches() {
2350 use httpmock::prelude::*;
2351 let (authoring_meta, doc) = sample_authoring_doc();
2352 let meta_hash = keccak256(&doc).0.to_vec();
2353 let meta_hash_hex = hex::encode_prefixed(&meta_hash);
2354 let tx = vec![0x77u8; 32];
2355 let server = MockServer::start();
2356 let _mock = server.mock(|when, then| {
2357 when.method(POST);
2358 then.status(200).json_body(deployer_json_body(
2359 &meta_hash_hex,
2360 &hex::encode_prefixed(&doc),
2361 &hex::encode_prefixed(&tx),
2362 &meta_hash_hex,
2363 ));
2364 });
2365 let mut store = Store::new();
2366 store.add_subgraphs(&vec![server.url("/sg")]);
2367
2368 let record = store.search_deployer(&meta_hash).await.cloned().unwrap();
2369 assert_eq!(record.meta_hash, meta_hash);
2370 assert_eq!(record.meta_bytes, doc);
2371 assert_eq!(record.bytecode, vec![0x01]);
2372 assert_eq!(record.parser, vec![0x02]);
2373 assert_eq!(record.store, vec![0x03]);
2374 assert_eq!(record.interpreter, vec![0x04]);
2375 assert_eq!(record.authoring_meta, Some(authoring_meta));
2376 assert_eq!(store.get_meta(&meta_hash), Some(&doc));
2377 assert_eq!(store.get_deployer(&tx), Some(&record));
2378 }
2379
2380 #[tokio::test]
2382 async fn test_store_search_deployer_error_returns_none() {
2383 use httpmock::prelude::*;
2384 let server = MockServer::start();
2385 let _mock = server.mock(|when, then| {
2386 when.method(POST);
2387 then.status(500).body("subgraph down");
2388 });
2389 let mut store = Store::new();
2390 store.add_subgraphs(&vec![server.url("/sg")]);
2391 assert!(store.search_deployer(&[0x0Du8; 32]).await.is_none());
2392 assert!(store.cache().is_empty());
2393 assert!(store.deployer_cache().is_empty());
2394 }
2395
2396 #[tokio::test]
2400 async fn test_store_search_deployer_check_branches() {
2401 use httpmock::prelude::*;
2402 let mut store = Store::new();
2404 let deployer = sample_deployer(&[0xAC; 32], b"cached-meta");
2405 let key = vec![0x11u8; 32];
2406 let tx = vec![0x22u8; 32];
2407 store.set_deployer(&key, &deployer, Some(&tx));
2408 assert_eq!(store.search_deployer_check(&key).await, Some(&deployer));
2409 assert_eq!(store.search_deployer_check(&tx).await, Some(&deployer));
2410
2411 let (_, doc) = sample_authoring_doc();
2413 let meta_hash = keccak256(&doc).0.to_vec();
2414 let meta_hash_hex = hex::encode_prefixed(&meta_hash);
2415 let server = MockServer::start();
2416 let _mock = server.mock(|when, then| {
2417 when.method(POST);
2418 then.status(200).json_body(deployer_json_body(
2419 &meta_hash_hex,
2420 &hex::encode_prefixed(&doc),
2421 &format!("0x{}", "66".repeat(32)),
2422 &meta_hash_hex,
2423 ));
2424 });
2425 let mut fresh = Store::new();
2426 fresh.add_subgraphs(&vec![server.url("/sg")]);
2427 let found = fresh
2428 .search_deployer_check(&meta_hash)
2429 .await
2430 .cloned()
2431 .unwrap();
2432 assert_eq!(found.meta_bytes, doc);
2433 }
2434
2435 #[test]
2438 fn test_store_set_deployer_from_query_response() {
2439 let (authoring_meta, doc) = sample_authoring_doc();
2440 let meta_hash = vec![0x0Au8; 32];
2441 let bytecode_meta_hash = vec![0x0Bu8; 32];
2442 let tx = vec![0x0Cu8; 32];
2443 let response = DeployerResponse {
2444 tx_hash: tx.clone(),
2445 bytecode_meta_hash: bytecode_meta_hash.clone(),
2446 meta_hash: meta_hash.clone(),
2447 meta_bytes: doc.clone(),
2448 bytecode: vec![0xE1],
2449 parser: vec![0xE2],
2450 store: vec![0xE3],
2451 interpreter: vec![0xE4],
2452 };
2453 let mut store = Store::new();
2454 let record = store.set_deployer_from_query_response(response);
2455 assert_eq!(record.meta_hash, meta_hash);
2456 assert_eq!(record.meta_bytes, doc);
2457 assert_eq!(record.bytecode, vec![0xE1]);
2458 assert_eq!(record.authoring_meta, Some(authoring_meta));
2459 assert_eq!(store.get_meta(&meta_hash), Some(&doc));
2460 assert_eq!(store.get_deployer(&bytecode_meta_hash), Some(&record));
2461 assert_eq!(store.get_deployer(&tx), Some(&record));
2462 }
2463
2464 #[test]
2468 fn test_store_dotrain_getters_and_set_fresh() {
2469 let mut store = Store::new();
2470 let text = "some dotrain content";
2471 let (hash, old) = store.set_dotrain(text, "file.rain", false).unwrap();
2472 assert!(old.is_empty());
2473 let expected_item = RainMetaDocumentV1Item {
2474 payload: serde_bytes::ByteBuf::from(text.as_bytes()),
2475 magic: KnownMagic::DotrainV1,
2476 content_type: ContentType::OctetStream,
2477 content_encoding: ContentEncoding::None,
2478 content_language: ContentLanguage::None,
2479 schema: None,
2480 };
2481 let expected_bytes = expected_item.cbor_encode().unwrap();
2482 assert_eq!(hash, keccak256(&expected_bytes).0.to_vec());
2483 assert_eq!(store.get_dotrain_hash("file.rain"), Some(&hash));
2484 assert_eq!(store.get_dotrain_uri(&hash), Some(&"file.rain".to_string()));
2485 assert_eq!(store.get_dotrain_meta("file.rain"), Some(&expected_bytes));
2486 assert_eq!(store.get_dotrain_hash("other.rain"), None);
2487 assert_eq!(store.get_dotrain_uri(&[0u8; 32]), None);
2488 assert_eq!(store.get_dotrain_meta("other.rain"), None);
2489 }
2490
2491 #[test]
2495 fn test_store_set_dotrain_branches() {
2496 let mut store = Store::new();
2497 let (hash_one, _) = store.set_dotrain("text one", "a.rain", false).unwrap();
2498
2499 let (hash_same, old_same) = store.set_dotrain("text one", "a.rain", false).unwrap();
2501 assert_eq!(hash_same, hash_one);
2502 assert!(old_same.is_empty());
2503 assert!(store.get_meta(&hash_one).is_some());
2504
2505 let (hash_two, old_two) = store.set_dotrain("text two", "a.rain", false).unwrap();
2507 assert_ne!(hash_two, hash_one);
2508 assert_eq!(old_two, hash_one);
2509 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_two));
2510 assert!(store.get_meta(&hash_one).is_none());
2511 assert!(store.get_meta(&hash_two).is_some());
2512
2513 let (hash_three, old_three) = store.set_dotrain("text three", "a.rain", true).unwrap();
2515 assert_eq!(old_three, hash_two);
2516 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_three));
2517 assert!(store.get_meta(&hash_two).is_some());
2518 assert!(store.get_meta(&hash_three).is_some());
2519 }
2520
2521 #[test]
2524 fn test_store_delete_dotrain_keep_meta() {
2525 let mut store = Store::new();
2526 let (hash, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2527 store.delete_dotrain("d.rain", false);
2528 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2529 assert!(store.get_meta(&hash).is_none());
2530
2531 let (hash_again, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2532 store.delete_dotrain("d.rain", true);
2533 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2534 assert!(store.get_meta(&hash_again).is_some());
2535 }
2536
2537 #[test]
2540 fn test_store_merge_semantics() {
2541 let shared_meta_hash = vec![0x5Au8; 32];
2542 let deployer_ours = sample_deployer(&shared_meta_hash, b"ours");
2543 let deployer_theirs = sample_deployer(&shared_meta_hash, b"theirs");
2544 let shared_tx = vec![0x0Fu8; 32];
2545 let their_tx = vec![0x1Eu8; 32];
2546
2547 let mut ours = Store::new();
2548 let mut theirs = Store::new();
2549 ours.set_deployer(&[0x01u8; 32], &deployer_ours, Some(&shared_tx));
2550 theirs.set_deployer(&[0x02u8; 32], &deployer_theirs, Some(&shared_tx));
2551 theirs.set_deployer(&[0x02u8; 32], &deployer_theirs, Some(&their_tx));
2552
2553 let contested_key = vec![0x03u8; 32];
2555 let deployer_a = sample_deployer(&[0x04; 32], b"deployer-a");
2556 let deployer_b = sample_deployer(&[0x05; 32], b"deployer-b");
2557 ours.set_deployer(&contested_key, &deployer_a, None);
2558 theirs.set_deployer(&contested_key, &deployer_b, None);
2559
2560 let (hash_ours, _) = ours.set_dotrain("content a", "x.rain", false).unwrap();
2562 let (_hash_theirs, _) = theirs.set_dotrain("content b", "x.rain", false).unwrap();
2563
2564 theirs.add_subgraphs(&vec!["sg-their".to_string()]);
2565
2566 ours.merge(&theirs);
2567
2568 assert_eq!(ours.get_meta(&shared_meta_hash), Some(&b"ours".to_vec()));
2570 assert_eq!(ours.get_deployer(&contested_key), Some(&deployer_a));
2572 assert_eq!(ours.get_deployer(&shared_tx), Some(&deployer_ours));
2574 assert_eq!(ours.get_deployer(&their_tx), Some(&deployer_theirs));
2576 assert_eq!(ours.get_dotrain_hash("x.rain"), Some(&hash_ours));
2578 assert!(ours.subgraphs().contains(&"sg-their".to_string()));
2580 }
2581
2582 #[tokio::test]
2586 async fn test_store_update_and_update_check() {
2587 use httpmock::prelude::*;
2588 let authoring_meta: AuthoringMeta = serde_json::from_str(
2589 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
2590 )
2591 .unwrap();
2592 let item_one = RainMetaDocumentV1Item {
2593 payload: serde_bytes::ByteBuf::from(authoring_meta.abi_encode_validate().unwrap()),
2594 magic: KnownMagic::AuthoringMetaV1,
2595 content_type: ContentType::Cbor,
2596 content_encoding: ContentEncoding::None,
2597 content_language: ContentLanguage::None,
2598 schema: None,
2599 };
2600 let item_two = sample_dotrain_item();
2601 let doc = RainMetaDocumentV1Item::cbor_encode_seq(
2602 &vec![item_one.clone(), item_two.clone()],
2603 KnownMagic::RainMetaDocumentV1,
2604 )
2605 .unwrap();
2606 let requested = keccak256(&doc).0.to_vec();
2607 let server = MockServer::start();
2608 let _mock = server.mock(|when, then| {
2609 when.method(POST);
2610 then.status(200).json_body(json!({
2611 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2612 }));
2613 });
2614 let mut store = Store::new();
2615 store.add_subgraphs(&vec![server.url("/sg")]);
2616 let fetched = store.update(&requested).await.cloned().unwrap();
2617 assert_eq!(fetched, doc);
2618 assert_eq!(store.get_meta(&requested), Some(&doc));
2619 let inner_one = item_one.cbor_encode().unwrap();
2620 let inner_two = item_two.cbor_encode().unwrap();
2621 assert_eq!(
2622 store.get_meta(keccak256(&inner_one).0.as_ref()),
2623 Some(&inner_one)
2624 );
2625 assert_eq!(
2626 store.get_meta(keccak256(&inner_two).0.as_ref()),
2627 Some(&inner_two)
2628 );
2629
2630 let mut cached_store = Store::new();
2632 let bytes = b"standalone meta bytes".to_vec();
2633 let hash = keccak256(&bytes).0.to_vec();
2634 assert!(cached_store.update_with(&hash, &bytes).is_some());
2635 assert_eq!(cached_store.update_check(&hash).await, Some(&bytes));
2636 }
2637
2638 #[test]
2642 fn test_store_update_with_validation_and_content() {
2643 let mut store = Store::new();
2645 let bytes = b"payload bytes".to_vec();
2646 let wrong_hash = vec![0x99u8; 32];
2647 assert!(store.update_with(&wrong_hash, &bytes).is_none());
2648 assert!(store.get_meta(&wrong_hash).is_none());
2649 let hash = keccak256(&bytes).0.to_vec();
2651 assert_eq!(store.update_with(&hash, &bytes), Some(&bytes));
2652
2653 let mut seeded = Store::new();
2655 let content = b"real content".to_vec();
2656 let content_hash = keccak256(&content).0.to_vec();
2657 let planted = sample_deployer(&content_hash, b"planted value");
2658 seeded.set_deployer(&[0x77u8; 32], &planted, None);
2659 assert_eq!(
2660 seeded.update_with(&content_hash, &content),
2661 Some(&b"planted value".to_vec())
2662 );
2663 assert_eq!(
2664 seeded.get_meta(&content_hash),
2665 Some(&b"planted value".to_vec())
2666 );
2667
2668 let (_, doc) = sample_authoring_doc();
2670 let doc_hash = keccak256(&doc).0.to_vec();
2671 let mut doc_store = Store::new();
2672 assert!(doc_store.update_with(&doc_hash, &doc).is_some());
2673 let inner = doc[8..].to_vec();
2674 assert_eq!(store_inner_lookup(&doc_store, &inner), Some(inner.clone()));
2675
2676 let item_a = sample_dotrain_item().cbor_encode().unwrap();
2678 let (_, doc_b) = sample_authoring_doc();
2679 let item_b = doc_b[8..].to_vec();
2680 let seq = [item_a.clone(), item_b].concat();
2681 let seq_hash = keccak256(&seq).0.to_vec();
2682 let mut seq_store = Store::new();
2683 assert!(seq_store.update_with(&seq_hash, &seq).is_some());
2684 assert_eq!(store_inner_lookup(&seq_store, &item_a), None);
2685 }
2686
2687 fn store_inner_lookup(store: &Store, inner_encoded: &[u8]) -> Option<Vec<u8>> {
2688 store.get_meta(keccak256(inner_encoded).0.as_ref()).cloned()
2689 }
2690
2691 #[test]
2694 fn test_bytes32_to_str_invalid_utf8() {
2695 let mut bytes = [0u8; 32];
2696 bytes[0] = 0xf0;
2697 bytes[1] = 0x28;
2698 bytes[2] = 0x8c;
2699 bytes[3] = 0x28;
2700 assert!(matches!(bytes32_to_str(&bytes), Err(Error::Utf8Error(_))));
2701 let no_nul = [0xffu8; 32];
2702 assert!(matches!(bytes32_to_str(&no_nul), Err(Error::Utf8Error(_))));
2703 }
2704}