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::OaSchema,
1859 KnownMagic::OaHashList,
1860 KnownMagic::OaStructure,
1861 KnownMagic::OaTokenImage,
1862 KnownMagic::OaTokenCredentialLinks,
1863 ];
1864 for magic in unsupported {
1865 let result: Result<Vec<u8>, Error> = plain_item(magic, vec![0x61]).unpack_into();
1866 assert!(matches!(result, Err(Error::UnsupportedMeta)), "{:?}", magic);
1867 }
1868 assert_eq!(
1870 supported.len() + unsupported.len(),
1871 KnownMagic::iter().count()
1872 );
1873 }
1874
1875 #[test]
1878 fn test_try_into_string_invalid_utf8_errors() {
1879 let item = plain_item(KnownMagic::DotrainV1, vec![0xff, 0xfe]);
1880 let result: Result<String, Error> = item.try_into();
1881 assert!(matches!(result, Err(Error::FromUtf8Error(_))));
1882 }
1883
1884 #[test]
1886 fn test_try_into_vec_decodes_encoding() -> Result<(), Error> {
1887 let content = b"raw bytecode bytes \x00\x01\x02".to_vec();
1888 let packed = ContentEncoding::Deflate.encode(&content);
1889 let mut item = plain_item(KnownMagic::ExpressionDeployerV2BytecodeV1, packed.clone());
1890 item.content_encoding = ContentEncoding::Deflate;
1891 let unpacked: Vec<u8> = item.try_into()?;
1892 assert_eq!(unpacked, content);
1893 assert_ne!(unpacked, packed);
1894 Ok(())
1895 }
1896
1897 #[test]
1900 fn test_content_encoding_deflate_roundtrip() -> Result<(), Error> {
1901 let content = b"hello rain deflate fixture hello rain deflate fixture".to_vec();
1902 let encoded = ContentEncoding::Deflate.encode(&content);
1903 assert_ne!(encoded, content);
1904 assert_eq!(encoded[0], 0x78);
1905 assert_eq!(ContentEncoding::Deflate.decode(&encoded)?, content);
1906 Ok(())
1907 }
1908
1909 #[test]
1911 fn test_content_encoding_passthrough() -> Result<(), Error> {
1912 let data = vec![0x00, 0xff, 0x10];
1913 for encoding in [ContentEncoding::None, ContentEncoding::Identity] {
1914 assert_eq!(encoding.encode(&data), data, "{:?}", encoding);
1915 assert_eq!(encoding.decode(&data)?, data, "{:?}", encoding);
1916 }
1917 Ok(())
1918 }
1919
1920 #[test]
1923 fn test_content_encoding_decode_fixtures() -> Result<(), Error> {
1924 let content = b"hello rain deflate fixture".to_vec();
1925 let zlib: Vec<u8> = vec![
1926 120, 156, 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44,
1927 73, 85, 72, 203, 172, 40, 41, 45, 74, 5, 0, 132, 64, 9, 251,
1928 ];
1929 let raw: Vec<u8> = vec![
1930 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44, 73, 85, 72,
1931 203, 172, 40, 41, 45, 74, 5, 0,
1932 ];
1933 assert_eq!(ContentEncoding::Deflate.decode(&zlib)?, content);
1934 assert_eq!(ContentEncoding::Deflate.decode(&raw)?, content);
1935 Ok(())
1936 }
1937
1938 #[test]
1941 fn test_content_encoding_decode_garbage_errors() {
1942 let garbage = [0xffu8, 0xff, 0xff, 0xff];
1943 assert!(matches!(
1944 ContentEncoding::Deflate.decode(&garbage),
1945 Err(Error::InflateError(_))
1946 ));
1947 }
1948
1949 #[test]
1951 fn test_content_headers_strum_names() {
1952 use std::str::FromStr;
1953 assert_eq!(
1954 ContentEncoding::from_str("deflate").unwrap(),
1955 ContentEncoding::Deflate
1956 );
1957 assert_eq!(
1958 ContentEncoding::from_str("identity").unwrap(),
1959 ContentEncoding::Identity
1960 );
1961 assert_eq!(
1962 ContentEncoding::from_str("none").unwrap(),
1963 ContentEncoding::None
1964 );
1965 assert_eq!(ContentEncoding::Deflate.to_string(), "deflate");
1966 assert_eq!(
1967 ContentType::from_str("octet-stream").unwrap(),
1968 ContentType::OctetStream
1969 );
1970 assert_eq!(ContentType::from_str("json").unwrap(), ContentType::Json);
1971 assert_eq!(ContentType::Json.to_string(), "json");
1972 assert_eq!(
1973 ContentLanguage::from_str("en").unwrap(),
1974 ContentLanguage::En
1975 );
1976 }
1977
1978 #[test]
1981 fn test_known_meta_try_from_magic() {
1982 let cases: [(KnownMagic, KnownMeta); 13] = [
1983 (KnownMagic::OpMetaV1, KnownMeta::OpV1),
1984 (KnownMagic::DotrainV1, KnownMeta::DotrainV1),
1985 (KnownMagic::RainlangV1, KnownMeta::RainlangV1),
1986 (KnownMagic::SolidityAbiV2, KnownMeta::SolidityAbiV2),
1987 (KnownMagic::AuthoringMetaV1, KnownMeta::AuthoringMetaV1),
1988 (KnownMagic::AuthoringMetaV2, KnownMeta::AuthoringMetaV2),
1989 (KnownMagic::AddressList, KnownMeta::AddressList),
1990 (
1991 KnownMagic::InterpreterCallerMetaV1,
1992 KnownMeta::InterpreterCallerMetaV1,
1993 ),
1994 (
1995 KnownMagic::ExpressionDeployerV2BytecodeV1,
1996 KnownMeta::ExpressionDeployerV2BytecodeV1,
1997 ),
1998 (KnownMagic::RainlangSourceV1, KnownMeta::RainlangSourceV1),
1999 (KnownMagic::DotrainSourceV1, KnownMeta::DotrainSourceV1),
2000 (
2001 KnownMagic::OrderBuilderStateV1,
2002 KnownMeta::OrderBuilderStateV1,
2003 ),
2004 (
2005 KnownMagic::RaindexSignedContextOracleV1,
2006 KnownMeta::RaindexSignedContextOracleV1,
2007 ),
2008 ];
2009 for (magic, meta) in cases {
2010 assert_eq!(KnownMeta::try_from(magic).unwrap(), meta, "{:?}", magic);
2011 }
2012 for magic in [
2013 KnownMagic::RainMetaDocumentV1,
2014 KnownMagic::OaSchema,
2015 KnownMagic::OaHashList,
2016 KnownMagic::OaStructure,
2017 KnownMagic::OaTokenImage,
2018 KnownMagic::OaTokenCredentialLinks,
2019 ] {
2020 assert!(
2021 matches!(KnownMeta::try_from(magic), Err(Error::UnsupportedMeta)),
2022 "{:?}",
2023 magic
2024 );
2025 }
2026 }
2027
2028 #[test]
2031 fn test_known_meta_strum_parse_display() {
2032 use std::str::FromStr;
2033 assert_eq!(KnownMeta::from_str("op-v1").unwrap(), KnownMeta::OpV1);
2034 assert_eq!(
2035 KnownMeta::from_str("solidity-abi-v2").unwrap(),
2036 KnownMeta::SolidityAbiV2
2037 );
2038 assert_eq!(
2039 KnownMeta::from_str("interpreter-caller-meta-v1").unwrap(),
2040 KnownMeta::InterpreterCallerMetaV1
2041 );
2042 assert_eq!(KnownMeta::SolidityAbiV2.to_string(), "solidity-abi-v2");
2043 assert_eq!(KnownMeta::OpV1.to_string(), "op-v1");
2044 }
2045
2046 fn sample_deployer(meta_hash: &[u8], meta_bytes: &[u8]) -> NPE2Deployer {
2047 NPE2Deployer {
2048 meta_hash: meta_hash.to_vec(),
2049 meta_bytes: meta_bytes.to_vec(),
2050 bytecode: vec![0xb1],
2051 parser: vec![0xb2],
2052 store: vec![0xb3],
2053 interpreter: vec![0xb4],
2054 authoring_meta: None,
2055 }
2056 }
2057
2058 fn deployer_json_body(
2059 meta_hash_hex: &str,
2060 meta_bytes_hex: &str,
2061 tx_hex: &str,
2062 bytecode_meta_id_hex: &str,
2063 ) -> serde_json::Value {
2064 json!({
2065 "data": {
2066 "expressionDeployers": [{
2067 "constructorMetaHash": meta_hash_hex,
2068 "constructorMeta": meta_bytes_hex,
2069 "deployTransaction": {"id": tx_hex},
2070 "bytecode": "0x01",
2071 "parser": {"parser": {"deployedBytecode": "0x02"}},
2072 "store": {"store": {"deployedBytecode": "0x03"}},
2073 "interpreter": {"interpreter": {"deployedBytecode": "0x04"}},
2074 "meta": [{"__typename": "RainMetaV1", "id": bytecode_meta_id_hex}]
2075 }]
2076 }
2077 })
2078 }
2079
2080 #[tokio::test]
2082 async fn test_search_lowercases_hash() {
2083 use httpmock::prelude::*;
2084 let (_, doc) = sample_authoring_doc();
2085 let hash_upper = format!("0x{}", "AB".repeat(32));
2086 let server = MockServer::start();
2087 let mock = server.mock(|when, then| {
2088 when.method(POST)
2089 .body_contains(hash_upper.to_ascii_lowercase());
2090 then.status(200).json_body(json!({
2091 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2092 }));
2093 });
2094 let response = search(&hash_upper, &vec![server.url("/sg")]).await.unwrap();
2095 assert_eq!(response.bytes, doc);
2096 mock.assert();
2097 }
2098
2099 #[tokio::test]
2102 async fn test_search_first_success_wins() {
2103 use httpmock::prelude::*;
2104 let (_, doc) = sample_authoring_doc();
2105 let bad = MockServer::start();
2106 let _bad_mock = bad.mock(|when, then| {
2107 when.method(POST);
2108 then.status(500).body("subgraph down");
2109 });
2110 let good = MockServer::start();
2111 let _good_mock = good.mock(|when, then| {
2112 when.method(POST);
2113 then.status(200).json_body(json!({
2114 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2115 }));
2116 });
2117 let response = search(
2118 &format!("0x{}", "11".repeat(32)),
2119 &vec![bad.url("/sg"), good.url("/sg")],
2120 )
2121 .await
2122 .unwrap();
2123 assert_eq!(response.bytes, doc);
2124 }
2125
2126 #[tokio::test]
2129 async fn test_search_deployer_lowercases_hash() {
2130 use httpmock::prelude::*;
2131 let (_, doc) = sample_authoring_doc();
2132 let meta_hash_hex = hex::encode_prefixed(keccak256(&doc).0);
2133 let hash_upper = format!("0x{}", "CD".repeat(32));
2134 let server = MockServer::start();
2135 let mock = server.mock(|when, then| {
2136 when.method(POST)
2137 .body_contains(hash_upper.to_ascii_lowercase());
2138 then.status(200).json_body(deployer_json_body(
2139 &meta_hash_hex,
2140 &hex::encode_prefixed(&doc),
2141 &format!("0x{}", "77".repeat(32)),
2142 &meta_hash_hex,
2143 ));
2144 });
2145 let response = search_deployer(&hash_upper, &vec![server.url("/sg")])
2146 .await
2147 .unwrap();
2148 assert_eq!(response.meta_bytes, doc);
2149 assert_eq!(response.bytecode, vec![0x01]);
2150 mock.assert();
2151 }
2152
2153 #[tokio::test]
2156 async fn test_search_deployer_first_success_wins() {
2157 use httpmock::prelude::*;
2158 let (_, doc) = sample_authoring_doc();
2159 let meta_hash_hex = hex::encode_prefixed(keccak256(&doc).0);
2160 let bad = MockServer::start();
2161 let _bad_mock = bad.mock(|when, then| {
2162 when.method(POST);
2163 then.status(500).body("subgraph down");
2164 });
2165 let good = MockServer::start();
2166 let _good_mock = good.mock(|when, then| {
2167 when.method(POST);
2168 then.status(200).json_body(deployer_json_body(
2169 &meta_hash_hex,
2170 &hex::encode_prefixed(&doc),
2171 &format!("0x{}", "77".repeat(32)),
2172 &meta_hash_hex,
2173 ));
2174 });
2175 let response = search_deployer(
2176 &format!("0x{}", "22".repeat(32)),
2177 &vec![bad.url("/sg"), good.url("/sg")],
2178 )
2179 .await
2180 .unwrap();
2181 assert_eq!(response.meta_bytes, doc);
2182 }
2183
2184 #[tokio::test]
2188 async fn test_implements_erc165_gate_short_circuits() {
2189 let address = Address::random();
2190
2191 let asserter = Asserter::new();
2193 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2194 asserter
2195 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2196 asserter
2197 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2198 assert!(!implements_i_described_by_meta_v1(&provider, address).await);
2199
2200 let asserter = Asserter::new();
2202 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2203 asserter.push_failure(ErrorPayload {
2204 code: -32000,
2205 message: "connection reset".into(),
2206 data: None,
2207 });
2208 asserter
2209 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2210 assert!(!implements_i_described_by_meta_v1(&provider, address).await);
2211 }
2212
2213 #[tokio::test]
2216 async fn test_implements_undecodable_response_is_false() {
2217 let address = Address::random();
2218 let asserter = Asserter::new();
2219 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2220 asserter
2221 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2222 asserter
2223 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2224 asserter.push_success(&"0x");
2225 assert!(!implements_i_described_by_meta_v1(&provider, address).await);
2226 }
2227
2228 #[test]
2231 fn test_npe2_deployer_is_corrupt_per_field() {
2232 let full = NPE2Deployer {
2233 meta_hash: vec![1],
2234 meta_bytes: vec![2],
2235 bytecode: vec![3],
2236 parser: vec![4],
2237 store: vec![5],
2238 interpreter: vec![6],
2239 authoring_meta: None,
2240 };
2241 assert!(!full.is_corrupt());
2242 for field in 0..6usize {
2243 let mut record = full.clone();
2244 match field {
2245 0 => record.meta_hash = vec![],
2246 1 => record.meta_bytes = vec![],
2247 2 => record.bytecode = vec![],
2248 3 => record.parser = vec![],
2249 4 => record.store = vec![],
2250 5 => record.interpreter = vec![],
2251 _ => unreachable!(),
2252 }
2253 assert!(record.is_corrupt(), "empty field {} must corrupt", field);
2254 }
2255 }
2256
2257 #[tokio::test]
2261 async fn test_store_constructors_inject_no_subgraphs() {
2262 assert!(Store::new().subgraphs().is_empty());
2263 assert!(Store::default().subgraphs().is_empty());
2264 assert!(
2265 Store::create(&vec![], &HashMap::new(), &HashMap::new(), &HashMap::new())
2266 .subgraphs()
2267 .is_empty()
2268 );
2269
2270 let hash = [0u8; 32];
2271 let mut store = Store::default();
2272 assert!(store.update(&hash).await.is_none());
2273 assert!(store.search_deployer(&hash).await.is_none());
2274 }
2275
2276 #[test]
2280 fn test_store_create_validates_entries() {
2281 let (_, doc) = sample_authoring_doc();
2282 let good_hash = keccak256(&doc).0.to_vec();
2283 let bad_hash = vec![0xEEu8; 32];
2284 let mut cache = HashMap::new();
2285 cache.insert(good_hash.clone(), doc.clone());
2286 cache.insert(bad_hash.clone(), b"does not hash to bad_hash".to_vec());
2287 let mut deployer_cache = HashMap::new();
2288 let deployer = sample_deployer(&[0xAA; 32], b"dep-meta");
2289 let deployer_key = vec![0x33u8; 32];
2290 deployer_cache.insert(deployer_key.clone(), deployer.clone());
2291 let mut dotrain_cache = HashMap::new();
2292 dotrain_cache.insert("a.rain".to_string(), good_hash.clone());
2293 dotrain_cache.insert("missing.rain".to_string(), vec![0x44u8; 32]);
2294
2295 let store = Store::create(
2296 &vec!["https://example.com/custom-sg".to_string()],
2297 &cache,
2298 &deployer_cache,
2299 &dotrain_cache,
2300 );
2301
2302 assert_eq!(
2303 store.subgraphs(),
2304 &vec!["https://example.com/custom-sg".to_string()]
2305 );
2306 assert_eq!(store.get_meta(&good_hash), Some(&doc));
2307 assert_eq!(store.get_meta(&bad_hash), None);
2308 assert_eq!(store.get_deployer(&deployer_key), Some(&deployer));
2309 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&good_hash));
2310 assert_eq!(store.get_dotrain_hash("missing.rain"), None);
2311 }
2312
2313 #[test]
2315 fn test_store_add_subgraphs_dedupe() {
2316 let mut store = Store::new();
2317 store.add_subgraphs(&vec!["sg-a".to_string()]);
2318 store.add_subgraphs(&vec!["sg-a".to_string(), "sg-b".to_string()]);
2319 assert_eq!(
2320 store.subgraphs(),
2321 &vec!["sg-a".to_string(), "sg-b".to_string()]
2322 );
2323 }
2324
2325 #[test]
2328 fn test_store_get_deployer_lookup_chain() {
2329 let mut store = Store::new();
2330 let deployer = sample_deployer(&[0xAB; 32], b"dep-meta-bytes");
2331 let key = vec![0x01u8; 32];
2332 let tx = vec![0x02u8; 32];
2333 store.set_deployer(&key, &deployer, Some(&tx));
2334 assert_eq!(store.get_deployer(&key), Some(&deployer));
2335 assert_eq!(store.get_deployer(&tx), Some(&deployer));
2336 assert_eq!(store.get_deployer(&[0x03u8; 32]), None);
2337 assert_eq!(
2338 store.get_meta(&deployer.meta_hash),
2339 Some(&deployer.meta_bytes)
2340 );
2341 }
2342
2343 #[tokio::test]
2347 async fn test_store_search_deployer_populates_caches() {
2348 use httpmock::prelude::*;
2349 let (authoring_meta, doc) = sample_authoring_doc();
2350 let meta_hash = keccak256(&doc).0.to_vec();
2351 let meta_hash_hex = hex::encode_prefixed(&meta_hash);
2352 let tx = vec![0x77u8; 32];
2353 let server = MockServer::start();
2354 let _mock = server.mock(|when, then| {
2355 when.method(POST);
2356 then.status(200).json_body(deployer_json_body(
2357 &meta_hash_hex,
2358 &hex::encode_prefixed(&doc),
2359 &hex::encode_prefixed(&tx),
2360 &meta_hash_hex,
2361 ));
2362 });
2363 let mut store = Store::new();
2364 store.add_subgraphs(&vec![server.url("/sg")]);
2365
2366 let record = store.search_deployer(&meta_hash).await.cloned().unwrap();
2367 assert_eq!(record.meta_hash, meta_hash);
2368 assert_eq!(record.meta_bytes, doc);
2369 assert_eq!(record.bytecode, vec![0x01]);
2370 assert_eq!(record.parser, vec![0x02]);
2371 assert_eq!(record.store, vec![0x03]);
2372 assert_eq!(record.interpreter, vec![0x04]);
2373 assert_eq!(record.authoring_meta, Some(authoring_meta));
2374 assert_eq!(store.get_meta(&meta_hash), Some(&doc));
2375 assert_eq!(store.get_deployer(&tx), Some(&record));
2376 }
2377
2378 #[tokio::test]
2380 async fn test_store_search_deployer_error_returns_none() {
2381 use httpmock::prelude::*;
2382 let server = MockServer::start();
2383 let _mock = server.mock(|when, then| {
2384 when.method(POST);
2385 then.status(500).body("subgraph down");
2386 });
2387 let mut store = Store::new();
2388 store.add_subgraphs(&vec![server.url("/sg")]);
2389 assert!(store.search_deployer(&[0x0Du8; 32]).await.is_none());
2390 assert!(store.cache().is_empty());
2391 assert!(store.deployer_cache().is_empty());
2392 }
2393
2394 #[tokio::test]
2398 async fn test_store_search_deployer_check_branches() {
2399 use httpmock::prelude::*;
2400 let mut store = Store::new();
2402 let deployer = sample_deployer(&[0xAC; 32], b"cached-meta");
2403 let key = vec![0x11u8; 32];
2404 let tx = vec![0x22u8; 32];
2405 store.set_deployer(&key, &deployer, Some(&tx));
2406 assert_eq!(store.search_deployer_check(&key).await, Some(&deployer));
2407 assert_eq!(store.search_deployer_check(&tx).await, Some(&deployer));
2408
2409 let (_, doc) = sample_authoring_doc();
2411 let meta_hash = keccak256(&doc).0.to_vec();
2412 let meta_hash_hex = hex::encode_prefixed(&meta_hash);
2413 let server = MockServer::start();
2414 let _mock = server.mock(|when, then| {
2415 when.method(POST);
2416 then.status(200).json_body(deployer_json_body(
2417 &meta_hash_hex,
2418 &hex::encode_prefixed(&doc),
2419 &format!("0x{}", "66".repeat(32)),
2420 &meta_hash_hex,
2421 ));
2422 });
2423 let mut fresh = Store::new();
2424 fresh.add_subgraphs(&vec![server.url("/sg")]);
2425 let found = fresh
2426 .search_deployer_check(&meta_hash)
2427 .await
2428 .cloned()
2429 .unwrap();
2430 assert_eq!(found.meta_bytes, doc);
2431 }
2432
2433 #[test]
2436 fn test_store_set_deployer_from_query_response() {
2437 let (authoring_meta, doc) = sample_authoring_doc();
2438 let meta_hash = vec![0x0Au8; 32];
2439 let bytecode_meta_hash = vec![0x0Bu8; 32];
2440 let tx = vec![0x0Cu8; 32];
2441 let response = DeployerResponse {
2442 tx_hash: tx.clone(),
2443 bytecode_meta_hash: bytecode_meta_hash.clone(),
2444 meta_hash: meta_hash.clone(),
2445 meta_bytes: doc.clone(),
2446 bytecode: vec![0xE1],
2447 parser: vec![0xE2],
2448 store: vec![0xE3],
2449 interpreter: vec![0xE4],
2450 };
2451 let mut store = Store::new();
2452 let record = store.set_deployer_from_query_response(response);
2453 assert_eq!(record.meta_hash, meta_hash);
2454 assert_eq!(record.meta_bytes, doc);
2455 assert_eq!(record.bytecode, vec![0xE1]);
2456 assert_eq!(record.authoring_meta, Some(authoring_meta));
2457 assert_eq!(store.get_meta(&meta_hash), Some(&doc));
2458 assert_eq!(store.get_deployer(&bytecode_meta_hash), Some(&record));
2459 assert_eq!(store.get_deployer(&tx), Some(&record));
2460 }
2461
2462 #[test]
2466 fn test_store_dotrain_getters_and_set_fresh() {
2467 let mut store = Store::new();
2468 let text = "some dotrain content";
2469 let (hash, old) = store.set_dotrain(text, "file.rain", false).unwrap();
2470 assert!(old.is_empty());
2471 let expected_item = RainMetaDocumentV1Item {
2472 payload: serde_bytes::ByteBuf::from(text.as_bytes()),
2473 magic: KnownMagic::DotrainV1,
2474 content_type: ContentType::OctetStream,
2475 content_encoding: ContentEncoding::None,
2476 content_language: ContentLanguage::None,
2477 schema: None,
2478 };
2479 let expected_bytes = expected_item.cbor_encode().unwrap();
2480 assert_eq!(hash, keccak256(&expected_bytes).0.to_vec());
2481 assert_eq!(store.get_dotrain_hash("file.rain"), Some(&hash));
2482 assert_eq!(store.get_dotrain_uri(&hash), Some(&"file.rain".to_string()));
2483 assert_eq!(store.get_dotrain_meta("file.rain"), Some(&expected_bytes));
2484 assert_eq!(store.get_dotrain_hash("other.rain"), None);
2485 assert_eq!(store.get_dotrain_uri(&[0u8; 32]), None);
2486 assert_eq!(store.get_dotrain_meta("other.rain"), None);
2487 }
2488
2489 #[test]
2493 fn test_store_set_dotrain_branches() {
2494 let mut store = Store::new();
2495 let (hash_one, _) = store.set_dotrain("text one", "a.rain", false).unwrap();
2496
2497 let (hash_same, old_same) = store.set_dotrain("text one", "a.rain", false).unwrap();
2499 assert_eq!(hash_same, hash_one);
2500 assert!(old_same.is_empty());
2501 assert!(store.get_meta(&hash_one).is_some());
2502
2503 let (hash_two, old_two) = store.set_dotrain("text two", "a.rain", false).unwrap();
2505 assert_ne!(hash_two, hash_one);
2506 assert_eq!(old_two, hash_one);
2507 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_two));
2508 assert!(store.get_meta(&hash_one).is_none());
2509 assert!(store.get_meta(&hash_two).is_some());
2510
2511 let (hash_three, old_three) = store.set_dotrain("text three", "a.rain", true).unwrap();
2513 assert_eq!(old_three, hash_two);
2514 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_three));
2515 assert!(store.get_meta(&hash_two).is_some());
2516 assert!(store.get_meta(&hash_three).is_some());
2517 }
2518
2519 #[test]
2522 fn test_store_delete_dotrain_keep_meta() {
2523 let mut store = Store::new();
2524 let (hash, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2525 store.delete_dotrain("d.rain", false);
2526 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2527 assert!(store.get_meta(&hash).is_none());
2528
2529 let (hash_again, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2530 store.delete_dotrain("d.rain", true);
2531 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2532 assert!(store.get_meta(&hash_again).is_some());
2533 }
2534
2535 #[test]
2538 fn test_store_merge_semantics() {
2539 let shared_meta_hash = vec![0x5Au8; 32];
2540 let deployer_ours = sample_deployer(&shared_meta_hash, b"ours");
2541 let deployer_theirs = sample_deployer(&shared_meta_hash, b"theirs");
2542 let shared_tx = vec![0x0Fu8; 32];
2543 let their_tx = vec![0x1Eu8; 32];
2544
2545 let mut ours = Store::new();
2546 let mut theirs = Store::new();
2547 ours.set_deployer(&[0x01u8; 32], &deployer_ours, Some(&shared_tx));
2548 theirs.set_deployer(&[0x02u8; 32], &deployer_theirs, Some(&shared_tx));
2549 theirs.set_deployer(&[0x02u8; 32], &deployer_theirs, Some(&their_tx));
2550
2551 let contested_key = vec![0x03u8; 32];
2553 let deployer_a = sample_deployer(&[0x04; 32], b"deployer-a");
2554 let deployer_b = sample_deployer(&[0x05; 32], b"deployer-b");
2555 ours.set_deployer(&contested_key, &deployer_a, None);
2556 theirs.set_deployer(&contested_key, &deployer_b, None);
2557
2558 let (hash_ours, _) = ours.set_dotrain("content a", "x.rain", false).unwrap();
2560 let (_hash_theirs, _) = theirs.set_dotrain("content b", "x.rain", false).unwrap();
2561
2562 theirs.add_subgraphs(&vec!["sg-their".to_string()]);
2563
2564 ours.merge(&theirs);
2565
2566 assert_eq!(ours.get_meta(&shared_meta_hash), Some(&b"ours".to_vec()));
2568 assert_eq!(ours.get_deployer(&contested_key), Some(&deployer_a));
2570 assert_eq!(ours.get_deployer(&shared_tx), Some(&deployer_ours));
2572 assert_eq!(ours.get_deployer(&their_tx), Some(&deployer_theirs));
2574 assert_eq!(ours.get_dotrain_hash("x.rain"), Some(&hash_ours));
2576 assert!(ours.subgraphs().contains(&"sg-their".to_string()));
2578 }
2579
2580 #[tokio::test]
2584 async fn test_store_update_and_update_check() {
2585 use httpmock::prelude::*;
2586 let authoring_meta: AuthoringMeta = serde_json::from_str(
2587 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
2588 )
2589 .unwrap();
2590 let item_one = RainMetaDocumentV1Item {
2591 payload: serde_bytes::ByteBuf::from(authoring_meta.abi_encode_validate().unwrap()),
2592 magic: KnownMagic::AuthoringMetaV1,
2593 content_type: ContentType::Cbor,
2594 content_encoding: ContentEncoding::None,
2595 content_language: ContentLanguage::None,
2596 schema: None,
2597 };
2598 let item_two = sample_dotrain_item();
2599 let doc = RainMetaDocumentV1Item::cbor_encode_seq(
2600 &vec![item_one.clone(), item_two.clone()],
2601 KnownMagic::RainMetaDocumentV1,
2602 )
2603 .unwrap();
2604 let requested = keccak256(&doc).0.to_vec();
2605 let server = MockServer::start();
2606 let _mock = server.mock(|when, then| {
2607 when.method(POST);
2608 then.status(200).json_body(json!({
2609 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2610 }));
2611 });
2612 let mut store = Store::new();
2613 store.add_subgraphs(&vec![server.url("/sg")]);
2614 let fetched = store.update(&requested).await.cloned().unwrap();
2615 assert_eq!(fetched, doc);
2616 assert_eq!(store.get_meta(&requested), Some(&doc));
2617 let inner_one = item_one.cbor_encode().unwrap();
2618 let inner_two = item_two.cbor_encode().unwrap();
2619 assert_eq!(
2620 store.get_meta(keccak256(&inner_one).0.as_ref()),
2621 Some(&inner_one)
2622 );
2623 assert_eq!(
2624 store.get_meta(keccak256(&inner_two).0.as_ref()),
2625 Some(&inner_two)
2626 );
2627
2628 let mut cached_store = Store::new();
2630 let bytes = b"standalone meta bytes".to_vec();
2631 let hash = keccak256(&bytes).0.to_vec();
2632 assert!(cached_store.update_with(&hash, &bytes).is_some());
2633 assert_eq!(cached_store.update_check(&hash).await, Some(&bytes));
2634 }
2635
2636 #[test]
2640 fn test_store_update_with_validation_and_content() {
2641 let mut store = Store::new();
2643 let bytes = b"payload bytes".to_vec();
2644 let wrong_hash = vec![0x99u8; 32];
2645 assert!(store.update_with(&wrong_hash, &bytes).is_none());
2646 assert!(store.get_meta(&wrong_hash).is_none());
2647 let hash = keccak256(&bytes).0.to_vec();
2649 assert_eq!(store.update_with(&hash, &bytes), Some(&bytes));
2650
2651 let mut seeded = Store::new();
2653 let content = b"real content".to_vec();
2654 let content_hash = keccak256(&content).0.to_vec();
2655 let planted = sample_deployer(&content_hash, b"planted value");
2656 seeded.set_deployer(&[0x77u8; 32], &planted, None);
2657 assert_eq!(
2658 seeded.update_with(&content_hash, &content),
2659 Some(&b"planted value".to_vec())
2660 );
2661 assert_eq!(
2662 seeded.get_meta(&content_hash),
2663 Some(&b"planted value".to_vec())
2664 );
2665
2666 let (_, doc) = sample_authoring_doc();
2668 let doc_hash = keccak256(&doc).0.to_vec();
2669 let mut doc_store = Store::new();
2670 assert!(doc_store.update_with(&doc_hash, &doc).is_some());
2671 let inner = doc[8..].to_vec();
2672 assert_eq!(store_inner_lookup(&doc_store, &inner), Some(inner.clone()));
2673
2674 let item_a = sample_dotrain_item().cbor_encode().unwrap();
2676 let (_, doc_b) = sample_authoring_doc();
2677 let item_b = doc_b[8..].to_vec();
2678 let seq = [item_a.clone(), item_b].concat();
2679 let seq_hash = keccak256(&seq).0.to_vec();
2680 let mut seq_store = Store::new();
2681 assert!(seq_store.update_with(&seq_hash, &seq).is_some());
2682 assert_eq!(store_inner_lookup(&seq_store, &item_a), None);
2683 }
2684
2685 fn store_inner_lookup(store: &Store, inner_encoded: &[u8]) -> Option<Vec<u8>> {
2686 store.get_meta(keccak256(inner_encoded).0.as_ref()).cloned()
2687 }
2688
2689 #[test]
2692 fn test_bytes32_to_str_invalid_utf8() {
2693 let mut bytes = [0u8; 32];
2694 bytes[0] = 0xf0;
2695 bytes[1] = 0x28;
2696 bytes[2] = 0x8c;
2697 bytes[3] = 0x28;
2698 assert!(matches!(bytes32_to_str(&bytes), Err(Error::Utf8Error(_))));
2699 let no_nul = [0xffu8; 32];
2700 assert!(matches!(bytes32_to_str(&no_nul), Err(Error::Utf8Error(_))));
2701 }
2702}