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