1use super::error::Error;
2
3pub mod cache;
4use cache::{DeployerCache, MetaCache};
5use alloy::primitives::{hex, keccak256};
6use futures::future;
7use graphql_client::GraphQLQuery;
8use rain_metadata_bindings::IDescribedByMetaV1;
9use reqwest::Client;
10use serde::de::{Deserialize, Deserializer, Visitor};
11use serde::ser::{Serialize, SerializeMap, Serializer};
12use std::{collections::HashMap, convert::TryFrom, fmt::Debug, sync::Arc};
13use strum::{EnumIter, EnumString};
14use types::authoring::v1::AuthoringMeta;
15use alloy::sol_types::private::Address;
16use alloy::providers::Provider;
17use alloy::rpc::types::TransactionRequest;
18use alloy::sol_types::SolCall;
19use rain_erc::erc165::{IERC165, XorSelectors, supports_erc165};
20
21pub mod magic;
22pub(crate) mod normalize;
23pub(crate) mod query;
24pub mod types;
25
26pub use magic::*;
27pub use query::*;
28
29#[derive(Copy, Clone, EnumString, EnumIter, strum::Display, Debug, PartialEq)]
31#[strum(serialize_all = "kebab-case")]
32pub enum KnownMeta {
33 OpV1,
40 DotrainV1,
41 RainlangV1,
42 SolidityAbiV2,
43 AuthoringMetaV1,
44 AuthoringMetaV2,
45 InterpreterCallerMetaV1,
46 ExpressionDeployerV2BytecodeV1,
47 RainlangSourceV1,
48 AddressList,
49 DotrainSourceV1,
50 OrderBuilderStateV1,
51 RaindexSignedContextOracleV1,
52}
53
54impl TryFrom<KnownMagic> for KnownMeta {
55 type Error = Error;
56 fn try_from(value: KnownMagic) -> Result<Self, Self::Error> {
57 match value {
58 KnownMagic::DotrainV1 => Ok(KnownMeta::DotrainV1),
59 KnownMagic::RainlangV1 => Ok(KnownMeta::RainlangV1),
60 KnownMagic::SolidityAbiV2 => Ok(KnownMeta::SolidityAbiV2),
61 KnownMagic::OpMetaV1 => Ok(KnownMeta::OpV1),
62 KnownMagic::AuthoringMetaV1 => Ok(KnownMeta::AuthoringMetaV1),
63 KnownMagic::AuthoringMetaV2 => Ok(KnownMeta::AuthoringMetaV2),
64 KnownMagic::AddressList => Ok(KnownMeta::AddressList),
65 KnownMagic::InterpreterCallerMetaV1 => Ok(KnownMeta::InterpreterCallerMetaV1),
66 KnownMagic::DotrainSourceV1 => Ok(KnownMeta::DotrainSourceV1),
67 KnownMagic::OrderBuilderStateV1 => Ok(KnownMeta::OrderBuilderStateV1),
68 KnownMagic::ExpressionDeployerV2BytecodeV1 => {
69 Ok(KnownMeta::ExpressionDeployerV2BytecodeV1)
70 }
71 KnownMagic::RainlangSourceV1 => Ok(KnownMeta::RainlangSourceV1),
72 KnownMagic::RaindexSignedContextOracleV1 => Ok(KnownMeta::RaindexSignedContextOracleV1),
73 _ => Err(Error::UnsupportedMeta),
74 }
75 }
76}
77
78#[derive(
80 Copy,
81 Clone,
82 Debug,
83 EnumIter,
84 PartialEq,
85 EnumString,
86 strum::Display,
87 serde::Serialize,
88 serde::Deserialize,
89)]
90#[strum(serialize_all = "kebab-case")]
91pub enum ContentType {
92 None,
93 #[serde(rename = "application/json")]
94 Json,
95 #[serde(rename = "application/cbor")]
96 Cbor,
97 #[serde(rename = "application/octet-stream")]
98 OctetStream,
99}
100
101#[derive(
103 Copy,
104 Clone,
105 Debug,
106 EnumIter,
107 PartialEq,
108 EnumString,
109 strum::Display,
110 serde::Serialize,
111 serde::Deserialize,
112)]
113#[serde(rename_all = "kebab-case")]
114#[strum(serialize_all = "kebab-case")]
115pub enum ContentEncoding {
116 None,
117 Identity,
118 Deflate,
119}
120
121impl ContentEncoding {
122 pub fn encode(&self, data: &[u8]) -> Vec<u8> {
124 match self {
125 ContentEncoding::None | ContentEncoding::Identity => data.to_vec(),
126 ContentEncoding::Deflate => deflate::deflate_bytes_zlib(data),
127 }
128 }
129
130 pub fn decode(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
132 Ok(match self {
133 ContentEncoding::None | ContentEncoding::Identity => data.to_vec(),
134 ContentEncoding::Deflate => match inflate::inflate_bytes_zlib(data) {
135 Ok(v) => v,
136 Err(error) => match inflate::inflate_bytes(data) {
137 Ok(v) => v,
138 Err(_) => Err(Error::InflateError(error))?,
139 },
140 },
141 })
142 }
143}
144
145#[derive(
147 Copy,
148 Clone,
149 Debug,
150 EnumIter,
151 PartialEq,
152 EnumString,
153 strum::Display,
154 serde::Serialize,
155 serde::Deserialize,
156)]
157#[serde(rename_all = "kebab-case")]
158#[strum(serialize_all = "kebab-case")]
159pub enum ContentLanguage {
160 None,
161 En,
162}
163
164#[derive(PartialEq, Debug, Clone)]
168pub struct RainMetaDocumentV1Item {
169 pub payload: serde_bytes::ByteBuf,
170 pub magic: KnownMagic,
171 pub content_type: ContentType,
172 pub content_encoding: ContentEncoding,
173 pub content_language: ContentLanguage,
174 pub schema: Option<String>,
178}
179
180impl TryFrom<RainMetaDocumentV1Item> for String {
182 type Error = Error;
183 fn try_from(value: RainMetaDocumentV1Item) -> Result<Self, Self::Error> {
184 Ok(String::from_utf8(value.unpack()?)?)
185 }
186}
187
188impl TryFrom<RainMetaDocumentV1Item> for Vec<u8> {
190 type Error = Error;
191 fn try_from(value: RainMetaDocumentV1Item) -> Result<Self, Self::Error> {
192 value.unpack()
193 }
194}
195
196impl RainMetaDocumentV1Item {
197 fn len(&self) -> usize {
198 let mut l = 2;
199 if !matches!(self.content_type, ContentType::None) {
200 l += 1;
201 }
202 if !matches!(self.content_encoding, ContentEncoding::None) {
203 l += 1;
204 }
205 if !matches!(self.content_language, ContentLanguage::None) {
206 l += 1;
207 }
208 if self.schema.is_some() {
209 l += 1;
210 }
211 l
212 }
213
214 pub fn hash(&self, as_rain_meta_document: bool) -> Result<[u8; 32], Error> {
216 if as_rain_meta_document {
217 Ok(keccak256(Self::cbor_encode_seq(
218 &vec![self.clone()],
219 KnownMagic::RainMetaDocumentV1,
220 )?)
221 .0)
222 } else {
223 Ok(keccak256(self.cbor_encode()?).0)
224 }
225 }
226
227 pub fn cbor_encode(&self) -> Result<Vec<u8>, Error> {
229 let mut bytes: Vec<u8> = vec![];
230 Ok(serde_cbor::to_writer(&mut bytes, &self).map(|_| bytes)?)
231 }
232
233 pub fn cbor_encode_seq(
235 seq: &Vec<RainMetaDocumentV1Item>,
236 magic: KnownMagic,
237 ) -> Result<Vec<u8>, Error> {
238 let mut bytes: Vec<u8> = magic.to_prefix_bytes().to_vec();
239 for item in seq {
240 serde_cbor::to_writer(&mut bytes, &item)?;
241 }
242 Ok(bytes)
243 }
244
245 pub fn cbor_decode(data: &[u8]) -> Result<Vec<RainMetaDocumentV1Item>, Error> {
247 let mut track: Vec<usize> = vec![];
248 let mut metas: Vec<RainMetaDocumentV1Item> = vec![];
249 let mut is_rain_document_meta = false;
250 let mut len = data.len();
251 if data.starts_with(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes()) {
252 is_rain_document_meta = true;
253 len -= 8;
254 }
255 let mut deserializer = match is_rain_document_meta {
256 true => serde_cbor::Deserializer::from_slice(&data[8..]),
257 false => serde_cbor::Deserializer::from_slice(data),
258 };
259 while match serde_cbor::Value::deserialize(&mut deserializer) {
260 Ok(cbor_map) => {
261 track.push(deserializer.byte_offset());
262 match serde_cbor::value::from_value(cbor_map) {
263 Ok(meta) => metas.push(meta),
264 Err(error) => Err(Error::SerdeCborError(error))?,
265 };
266 true
267 }
268 Err(error) => {
269 if error.is_eof() {
270 if error.offset() == len as u64 {
271 false
272 } else {
273 Err(Error::SerdeCborError(error))?
274 }
275 } else {
276 Err(Error::SerdeCborError(error))?
277 }
278 }
279 } {}
280
281 if metas.is_empty()
282 || track.is_empty()
283 || track.len() != metas.len()
284 || len != track[track.len() - 1]
285 {
286 Err(Error::CorruptMeta)?
287 }
288 Ok(metas)
289 }
290
291 pub fn unpack(&self) -> Result<Vec<u8>, Error> {
293 ContentEncoding::decode(&self.content_encoding, self.payload.as_ref())
294 }
295
296 pub fn unpack_into<T: TryFrom<Self, Error = Error>>(self) -> Result<T, Error> {
298 match self.magic {
299 KnownMagic::OpMetaV1
300 | KnownMagic::DotrainV1
301 | KnownMagic::RainlangV1
302 | KnownMagic::SolidityAbiV2
303 | KnownMagic::AuthoringMetaV1
304 | KnownMagic::AuthoringMetaV2
305 | KnownMagic::AddressList
306 | KnownMagic::InterpreterCallerMetaV1
307 | KnownMagic::ExpressionDeployerV2BytecodeV1
308 | KnownMagic::DotrainSourceV1
309 | KnownMagic::OrderBuilderStateV1
310 | KnownMagic::RainlangSourceV1
311 | KnownMagic::RaindexSignedContextOracleV1 => T::try_from(self),
312 _ => Err(Error::UnsupportedMeta)?,
313 }
314 }
315}
316
317impl Serialize for RainMetaDocumentV1Item {
318 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
319 let mut map = serializer.serialize_map(Some(self.len()))?;
320 map.serialize_entry(&0, &self.payload)?;
321 map.serialize_entry(&1, &(self.magic as u64))?;
322 match self.content_type {
323 ContentType::None => {}
324 content_type => map.serialize_entry(&2, &content_type)?,
325 }
326 match self.content_encoding {
327 ContentEncoding::None => {}
328 content_encoding => map.serialize_entry(&3, &content_encoding)?,
329 }
330 match self.content_language {
331 ContentLanguage::None => {}
332 content_language => map.serialize_entry(&4, &content_language)?,
333 }
334 if let Some(schema) = &self.schema {
335 map.serialize_entry(&(KnownMagic::OaSchema as u64), schema)?;
336 }
337 map.end()
338 }
339}
340
341impl<'de> Deserialize<'de> for RainMetaDocumentV1Item {
342 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
343 struct EncodedMap;
344 impl<'de> Visitor<'de> for EncodedMap {
345 type Value = RainMetaDocumentV1Item;
346
347 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
348 formatter.write_str("rain meta cbor encoded bytes")
349 }
350
351 fn visit_map<T: serde::de::MapAccess<'de>>(
352 self,
353 mut map: T,
354 ) -> Result<Self::Value, T::Error> {
355 const OA_SCHEMA_KEY: u64 = KnownMagic::OaSchema as u64;
356 let mut payload = None;
357 let mut magic: Option<u64> = None;
358 let mut content_type = None;
359 let mut content_encoding = None;
360 let mut content_language = None;
361 let mut schema = None;
362 while match map.next_key::<u64>() {
363 Ok(Some(key)) => {
364 match key {
365 0 => payload = Some(map.next_value()?),
366 1 => magic = Some(map.next_value()?),
367 2 => content_type = Some(map.next_value()?),
368 3 => content_encoding = Some(map.next_value()?),
369 4 => content_language = Some(map.next_value()?),
370 OA_SCHEMA_KEY => schema = Some(map.next_value()?),
371 _ => {
376 map.next_value::<serde::de::IgnoredAny>()?;
377 }
378 };
379 true
380 }
381 Ok(None) => false,
382 Err(error) => Err(error)?,
383 } {}
384 let payload = payload.ok_or_else(|| serde::de::Error::missing_field("payload"))?;
385 let magic = match magic
386 .ok_or_else(|| serde::de::Error::missing_field("magic number"))?
387 .try_into()
388 {
389 Ok(m) => m,
390 _ => Err(serde::de::Error::custom("unknown magic number"))?,
391 };
392 let content_type = content_type.unwrap_or(ContentType::None);
393 let content_encoding = content_encoding.unwrap_or(ContentEncoding::None);
394 let content_language = content_language.unwrap_or(ContentLanguage::None);
395
396 Ok(RainMetaDocumentV1Item {
397 payload,
398 magic,
399 content_type,
400 content_encoding,
401 content_language,
402 schema,
403 })
404 }
405 }
406 deserializer.deserialize_map(EncodedMap)
407 }
408}
409
410pub async fn search(hash: &str, subgraphs: &Vec<String>) -> Result<query::MetaResponse, Error> {
412 if subgraphs.is_empty() {
414 return Err(Error::NoRecordFound);
415 }
416 let request_body = query::MetaQuery::build_query(query::meta_query::Variables {
417 hash: Some(hash.to_ascii_lowercase()),
418 });
419 let mut promises = vec![];
420
421 let client = Arc::new(Client::builder().build().map_err(Error::ReqwestError)?);
422 for url in subgraphs {
423 promises.push(Box::pin(query::process_meta_query(
424 client.clone(),
425 &request_body,
426 url,
427 )));
428 }
429 let response_value = future::select_ok(promises.drain(..)).await?.0;
430 Ok(response_value)
431}
432
433pub async fn search_deployer(
435 hash: &str,
436 subgraphs: &Vec<String>,
437) -> Result<DeployerResponse, Error> {
438 if subgraphs.is_empty() {
440 return Err(Error::NoRecordFound);
441 }
442 let request_body = query::DeployerQuery::build_query(query::deployer_query::Variables {
443 hash: Some(hash.to_ascii_lowercase()),
444 });
445 let mut promises = vec![];
446
447 let client = Arc::new(Client::builder().build().map_err(Error::ReqwestError)?);
448 for url in subgraphs {
449 promises.push(Box::pin(query::process_deployer_query(
450 client.clone(),
451 &request_body,
452 url,
453 )));
454 }
455 let response_value = future::select_ok(promises.drain(..)).await?.0;
456 Ok(response_value)
457}
458
459pub async fn implements_i_described_by_meta_v1<P: Provider>(
461 provider: &P,
462 contract_address: Address,
463) -> bool {
464 if !supports_erc165(provider, contract_address)
465 .await
466 .unwrap_or(false)
467 {
468 return false;
469 }
470
471 let interface_id_res = IDescribedByMetaV1::IDescribedByMetaV1Calls::xor_selectors();
472 if interface_id_res.is_err() {
473 return false;
474 }
475
476 let call = IERC165::supportsInterfaceCall {
477 interfaceID: interface_id_res.unwrap().into(),
478 };
479 let tx = TransactionRequest::default()
480 .to(contract_address)
481 .input(call.abi_encode().into());
482 match provider.call(tx).await {
483 Ok(bytes) => IERC165::supportsInterfaceCall::abi_decode_returns(&bytes).unwrap_or(false),
484 Err(_) => false,
485 }
486}
487
488#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Default)]
490#[serde(rename_all = "camelCase")]
491pub struct NPE2Deployer {
492 #[serde(with = "serde_bytes")]
494 pub meta_hash: Vec<u8>,
495 #[serde(with = "serde_bytes")]
497 pub meta_bytes: Vec<u8>,
498 #[serde(with = "serde_bytes")]
500 pub bytecode: Vec<u8>,
501 #[serde(with = "serde_bytes")]
503 pub parser: Vec<u8>,
504 #[serde(with = "serde_bytes")]
506 pub store: Vec<u8>,
507 #[serde(with = "serde_bytes")]
509 pub interpreter: Vec<u8>,
510 pub authoring_meta: Option<AuthoringMeta>,
512}
513
514impl NPE2Deployer {
515 pub fn is_corrupt(&self) -> bool {
516 if self.meta_hash.is_empty() {
517 return true;
518 }
519 if self.meta_bytes.is_empty() {
520 return true;
521 }
522 if self.bytecode.is_empty() {
523 return true;
524 }
525 if self.parser.is_empty() {
526 return true;
527 }
528 if self.store.is_empty() {
529 return true;
530 }
531 if self.interpreter.is_empty() {
532 return true;
533 }
534 false
535 }
536}
537
538#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
599pub struct Store {
600 subgraphs: Vec<String>,
601 cache: MetaCache,
602 dotrain_cache: HashMap<String, Vec<u8>>,
603 deployer_cache: DeployerCache,
604 deployer_hash_map: HashMap<Vec<u8>, Vec<u8>>,
605}
606
607impl Default for Store {
608 fn default() -> Self {
609 Store::new()
610 }
611}
612
613impl Store {
614 pub fn new() -> Store {
617 Store {
618 subgraphs: vec![],
619 cache: MetaCache::default(),
620 dotrain_cache: HashMap::new(),
621 deployer_cache: DeployerCache::default(),
622 deployer_hash_map: HashMap::new(),
623 }
624 }
625
626 pub fn create(
629 subgraphs: &Vec<String>,
630 cache: &MetaCache,
631 deployer_cache: &DeployerCache,
632 dotrain_cache: &HashMap<String, Vec<u8>>,
633 ) -> Store {
634 let mut store = Store::new();
635 store.add_subgraphs(subgraphs);
636 for (hash, bytes) in cache.iter() {
637 let _ = store.update_with(hash, bytes);
638 }
639 for (hash, deployer) in deployer_cache.iter() {
640 let _ = store.set_deployer(hash, deployer, None);
641 }
642 for (uri, hash) in dotrain_cache {
643 if !store.dotrain_cache.contains_key(uri) && store.cache.contains_key(hash) {
644 store.dotrain_cache.insert(uri.clone(), hash.clone());
645 }
646 }
647 store
648 }
649
650 pub fn subgraphs(&self) -> &Vec<String> {
652 &self.subgraphs
653 }
654
655 pub fn add_subgraphs(&mut self, subgraphs: &Vec<String>) {
657 for sg in subgraphs {
658 if !self.subgraphs.contains(sg) {
659 self.subgraphs.push(sg.to_string());
660 }
661 }
662 }
663
664 pub fn cache(&self) -> &MetaCache {
666 &self.cache
667 }
668
669 pub fn get_meta(&self, hash: &[u8]) -> Option<&Vec<u8>> {
671 self.cache.get(hash)
672 }
673
674 pub fn deployer_cache(&self) -> &DeployerCache {
676 &self.deployer_cache
677 }
678
679 pub fn get_deployer(&self, hash: &[u8]) -> Option<&NPE2Deployer> {
681 if self.deployer_cache.contains_key(hash) {
682 self.deployer_cache.get(hash)
683 } else if let Some(h) = self.deployer_hash_map.get(hash) {
684 self.deployer_cache.get(h)
685 } else {
686 None
687 }
688 }
689
690 pub async fn search_deployer(&mut self, hash: &[u8]) -> Result<&NPE2Deployer, Error> {
695 match search_deployer(&hex::encode_prefixed(hash), &self.subgraphs).await {
696 Ok(res) => {
697 self.insert_verified(&res.meta_hash.clone(), res.meta_bytes.clone())?;
698 let authoring_meta = res.get_authoring_meta();
699 self.deployer_cache.insert_verified(
700 &res.bytecode_meta_hash.clone(),
701 NPE2Deployer {
702 meta_hash: res.meta_hash.clone(),
703 meta_bytes: res.meta_bytes,
704 bytecode: res.bytecode,
705 parser: res.parser,
706 store: res.store,
707 interpreter: res.interpreter,
708 authoring_meta,
709 },
710 )?;
711 self.deployer_hash_map.insert(res.tx_hash, res.meta_hash);
712 self.deployer_cache.get(hash).ok_or(Error::NoRecordFound)
713 }
714 Err(e) => Err(e),
715 }
716 }
717
718 pub async fn search_deployer_check(&mut self, hash: &[u8]) -> Result<&NPE2Deployer, Error> {
721 if self.deployer_cache.contains_key(hash) {
722 self.get_deployer(hash).ok_or(Error::NoRecordFound)
723 } else if self.deployer_hash_map.contains_key(hash) {
724 let b_hash = self.deployer_hash_map.get(hash).unwrap().clone();
725 self.get_deployer(&b_hash).ok_or(Error::NoRecordFound)
726 } else {
727 self.search_deployer(hash).await
728 }
729 }
730
731 pub fn set_deployer_from_query_response(
733 &mut self,
734 deployer_query_response: DeployerResponse,
735 ) -> Result<NPE2Deployer, Error> {
736 let authoring_meta = deployer_query_response.get_authoring_meta();
737 let tx_hash = deployer_query_response.tx_hash;
738 let bytecode_meta_hash = deployer_query_response.bytecode_meta_hash;
739 let result = NPE2Deployer {
740 meta_hash: deployer_query_response.meta_hash.clone(),
741 meta_bytes: deployer_query_response.meta_bytes,
742 bytecode: deployer_query_response.bytecode,
743 parser: deployer_query_response.parser,
744 store: deployer_query_response.store,
745 interpreter: deployer_query_response.interpreter,
746 authoring_meta,
747 };
748 self.cache.insert_verified(
749 &deployer_query_response.meta_hash,
750 result.meta_bytes.clone(),
751 )?;
752 self.deployer_hash_map
753 .insert(tx_hash, bytecode_meta_hash.clone());
754 self.deployer_cache
755 .insert_verified(&bytecode_meta_hash, result.clone())?;
756 Ok(result)
757 }
758
759 pub fn set_deployer(
762 &mut self,
763 hash: &[u8],
764 npe2_deployer: &NPE2Deployer,
765 tx_hash: Option<&[u8]>,
766 ) -> Result<(), Error> {
767 self.cache
768 .insert_verified(&npe2_deployer.meta_hash, npe2_deployer.meta_bytes.clone())?;
769 self.deployer_cache
770 .insert_verified(hash, npe2_deployer.clone())?;
771 if let Some(v) = tx_hash {
772 self.deployer_hash_map.insert(v.to_vec(), hash.to_vec());
773 }
774 Ok(())
775 }
776
777 pub fn dotrain_cache(&self) -> &HashMap<String, Vec<u8>> {
779 &self.dotrain_cache
780 }
781
782 pub fn get_dotrain_hash(&self, uri: &str) -> Option<&Vec<u8>> {
784 self.dotrain_cache.get(uri)
785 }
786
787 pub fn get_dotrain_uri(&self, hash: &[u8]) -> Option<&String> {
789 for (uri, h) in &self.dotrain_cache {
790 if h == hash {
791 return Some(uri);
792 }
793 }
794 None
795 }
796
797 pub fn get_dotrain_meta(&self, uri: &str) -> Option<&Vec<u8>> {
799 self.get_meta(self.dotrain_cache.get(uri)?)
800 }
801
802 pub fn delete_dotrain(&mut self, uri: &str, keep_meta: bool) {
804 if let Some(kv) = self.dotrain_cache.remove_entry(uri) {
805 if !keep_meta {
806 self.cache.remove(&kv.1);
807 }
808 };
809 }
810
811 pub fn merge(&mut self, other: &Store) {
814 self.add_subgraphs(&other.subgraphs);
815 for (hash, bytes) in other.cache.iter() {
816 if !self.cache.contains_key(hash) {
817 let _ = self.cache.insert_verified(hash, bytes.clone());
820 }
821 }
822 for (hash, deployer) in other.deployer_cache.iter() {
823 if !self.deployer_cache.contains_key(hash) {
824 let _ = self.deployer_cache.insert_verified(hash, deployer.clone());
826 }
827 }
828 for (tx_hash, hash) in &other.deployer_hash_map {
829 if !self.deployer_hash_map.contains_key(tx_hash) {
830 self.deployer_hash_map.insert(tx_hash.clone(), hash.clone());
831 }
832 }
833 for (uri, hash) in &other.dotrain_cache {
834 if !self.dotrain_cache.contains_key(uri) {
835 self.dotrain_cache.insert(uri.clone(), hash.clone());
836 }
837 }
838 }
839
840 fn insert_verified(&mut self, hash: &[u8], bytes: Vec<u8>) -> Result<&Vec<u8>, Error> {
844 self.cache.insert_verified(hash, bytes.clone())?;
845 self.store_content(&bytes);
846 self.get_meta(hash).ok_or(Error::NoRecordFound)
847 }
848
849 pub async fn update(&mut self, hash: &[u8]) -> Result<&Vec<u8>, Error> {
854 let meta = search(&hex::encode_prefixed(hash), &self.subgraphs).await?;
855 self.insert_verified(hash, meta.bytes)
856 }
857
858 pub async fn update_check(&mut self, hash: &[u8]) -> Result<&Vec<u8>, Error> {
860 if self.cache.contains_key(hash) {
866 return self.get_meta(hash).ok_or(Error::NoRecordFound);
867 }
868 self.update(hash).await
869 }
870
871 pub fn update_with(&mut self, hash: &[u8], bytes: &[u8]) -> Result<&Vec<u8>, Error> {
875 if self.cache.contains_key(hash) {
881 return self.get_meta(hash).ok_or(Error::NoRecordFound);
882 }
883 self.insert_verified(hash, bytes.to_vec())
884 }
885
886 pub fn set_dotrain(
891 &mut self,
892 text: &str,
893 uri: &str,
894 keep_old: bool,
895 ) -> Result<(Vec<u8>, Vec<u8>), Error> {
896 let bytes = RainMetaDocumentV1Item {
897 payload: serde_bytes::ByteBuf::from(text.as_bytes()),
898 magic: KnownMagic::DotrainV1,
899 content_type: ContentType::OctetStream,
900 content_encoding: ContentEncoding::None,
901 content_language: ContentLanguage::None,
902 schema: None,
903 }
904 .cbor_encode()?;
905 let new_hash = keccak256(&bytes).0.to_vec();
906 if let Some(h) = self.dotrain_cache.get(uri) {
907 let old_hash = h.clone();
908 if new_hash == old_hash {
909 self.cache.insert_verified(&new_hash, bytes)?;
910 Ok((new_hash, vec![]))
911 } else {
912 self.cache.insert_verified(&new_hash, bytes)?;
913 self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
914 if !keep_old {
915 self.cache.remove(&old_hash);
916 }
917 Ok((new_hash, old_hash))
918 }
919 } else {
920 self.dotrain_cache.insert(uri.to_string(), new_hash.clone());
921 self.cache.insert_verified(&new_hash, bytes)?;
922 Ok((new_hash, vec![]))
923 }
924 }
925
926 fn store_content(&mut self, bytes: &[u8]) {
930 if let Ok(meta_maps) = RainMetaDocumentV1Item::cbor_decode(bytes) {
931 if bytes.starts_with(&KnownMagic::RainMetaDocumentV1.to_prefix_bytes()) {
932 for meta_map in &meta_maps {
933 if let Ok(encoded_bytes) = meta_map.cbor_encode() {
934 let _ = self
938 .cache
939 .insert_verified(&keccak256(&encoded_bytes).0, encoded_bytes);
940 }
941 }
942 }
943 }
944 }
945}
946
947pub fn str_to_bytes32(text: &str) -> Result<[u8; 32], Error> {
953 let bytes: &[u8] = text.as_bytes();
954 if bytes.len() > 32 {
955 return Err(Error::BiggerThan32Bytes);
956 }
957 if bytes.contains(&0u8) {
958 return Err(Error::NulByteInInput);
959 }
960 let mut b32 = [0u8; 32];
961 b32[..bytes.len()].copy_from_slice(bytes);
962 Ok(b32)
963}
964
965pub fn bytes32_to_str(bytes: &[u8; 32]) -> Result<&str, Error> {
967 let mut len = 32;
968 if let Some((pos, _)) = itertools::Itertools::find_position(&mut bytes.iter(), |b| **b == 0u8) {
969 len = pos;
970 };
971 Ok(std::str::from_utf8(&bytes[..len])?)
972}
973
974#[cfg(all(test, not(target_family = "wasm")))]
975mod tests {
976 use super::{
977 *, bytes32_to_str, magic::KnownMagic, str_to_bytes32, types::authoring::v1::AuthoringMeta,
978 ContentEncoding, ContentLanguage, ContentType, Error, RainMetaDocumentV1Item,
979 };
980 use alloy::providers::ProviderBuilder;
981 use alloy::{providers::mock::Asserter, rpc::json_rpc::ErrorPayload, sol_types::SolType};
982 use serde_json::json;
983
984 #[test]
987 fn authoring_meta_roundtrip() -> Result<(), Error> {
988 let authoring_meta_content = r#"[
989 {
990 "word": "stack",
991 "description": "Copies an existing value from the stack.",
992 "operandParserOffset": 16
993 },
994 {
995 "word": "constant",
996 "description": "Copies a constant value onto the stack.",
997 "operandParserOffset": 16
998 }
999 ]"#;
1000 let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
1001
1002 let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
1004 let expected_abi_encoded = <alloy::sol!((bytes32, uint8, string)[])>::abi_encode(&vec![
1005 (
1006 str_to_bytes32("stack")?,
1007 16u8,
1008 "Copies an existing value from the stack.".to_string(),
1009 ),
1010 (
1011 str_to_bytes32("constant")?,
1012 16u8,
1013 "Copies a constant value onto the stack.".to_string(),
1014 ),
1015 ]);
1016 assert_eq!(authoring_meta_abi_encoded, expected_abi_encoded);
1018
1019 let meta_map = RainMetaDocumentV1Item {
1020 payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
1021 magic: KnownMagic::AuthoringMetaV1,
1022 content_type: ContentType::Cbor,
1023 content_encoding: ContentEncoding::None,
1024 content_language: ContentLanguage::None,
1025 schema: None,
1026 };
1027 let cbor_encoded = meta_map.cbor_encode()?;
1028
1029 assert_eq!(cbor_encoded[0], 0xa3);
1031 assert_eq!(cbor_encoded[1], 0x00);
1033 assert_eq!(cbor_encoded[2], 0b010_11001);
1035 assert_eq!(cbor_encoded[3], 0b000_00010);
1036 assert_eq!(cbor_encoded[4], 0b000_00000);
1037 assert_eq!(cbor_encoded[5..517], authoring_meta_abi_encoded);
1039 assert_eq!(cbor_encoded[517], 0x01);
1041 assert_eq!(cbor_encoded[518], 0b000_11011);
1043 assert_eq!(
1045 &cbor_encoded[519..527],
1046 KnownMagic::AuthoringMetaV1.to_prefix_bytes()
1047 );
1048 assert_eq!(cbor_encoded[527], 0x02);
1050 assert_eq!(cbor_encoded[528], 0b011_10000);
1052 assert_eq!(&cbor_encoded[529..], "application/cbor".as_bytes());
1054
1055 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1057 assert_eq!(cbor_decoded.len(), 1);
1059 assert_eq!(cbor_decoded[0], meta_map);
1061
1062 Ok(())
1063 }
1064
1065 #[test]
1068 fn dotrain_meta_roundtrip() -> Result<(), Error> {
1069 let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
1070 let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
1071
1072 let content_encoding = ContentEncoding::Deflate;
1073 let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
1074
1075 let meta_map = RainMetaDocumentV1Item {
1076 payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
1077 magic: KnownMagic::DotrainV1,
1078 content_type: ContentType::OctetStream,
1079 content_encoding,
1080 content_language: ContentLanguage::En,
1081 schema: None,
1082 };
1083 let cbor_encoded = meta_map.cbor_encode()?;
1084
1085 assert_eq!(cbor_encoded[0], 0xa5);
1087 assert_eq!(cbor_encoded[1], 0x00);
1089 assert_eq!(cbor_encoded[2], 0b010_11000);
1091 assert_eq!(cbor_encoded[3], 0b001_00100);
1092 assert_eq!(cbor_encoded[4..40], deflated_payload);
1095 assert_eq!(cbor_encoded[40], 0x01);
1097 assert_eq!(cbor_encoded[41], 0b000_11011);
1099 assert_eq!(
1101 &cbor_encoded[42..50],
1102 KnownMagic::DotrainV1.to_prefix_bytes()
1103 );
1104 assert_eq!(cbor_encoded[50], 0x02);
1106 assert_eq!(cbor_encoded[51], 0b011_11000);
1108 assert_eq!(cbor_encoded[52], 0b000_11000);
1109 assert_eq!(&cbor_encoded[53..77], "application/octet-stream".as_bytes());
1111 assert_eq!(cbor_encoded[77], 0x03);
1113 assert_eq!(cbor_encoded[78], 0b011_00111);
1115 assert_eq!(&cbor_encoded[79..86], "deflate".as_bytes());
1117 assert_eq!(cbor_encoded[86], 0x04);
1119 assert_eq!(cbor_encoded[87], 0b011_00010);
1121 assert_eq!(&cbor_encoded[88..], "en".as_bytes());
1123
1124 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1126 assert_eq!(cbor_decoded.len(), 1);
1128 assert_eq!(cbor_decoded[0], meta_map);
1130
1131 Ok(())
1132 }
1133
1134 #[test]
1137 fn meta_seq_roundtrip() -> Result<(), Error> {
1138 let authoring_meta_content = r#"[
1139 {
1140 "word": "stack",
1141 "description": "Copies an existing value from the stack.",
1142 "operandParserOffset": 16
1143 },
1144 {
1145 "word": "constant",
1146 "description": "Copies a constant value onto the stack.",
1147 "operandParserOffset": 16
1148 }
1149 ]"#;
1150 let authoring_meta: AuthoringMeta = serde_json::from_str(authoring_meta_content)?;
1151 let authoring_meta_abi_encoded = authoring_meta.abi_encode_validate()?;
1152 let meta_map_1 = RainMetaDocumentV1Item {
1153 payload: serde_bytes::ByteBuf::from(authoring_meta_abi_encoded.clone()),
1154 magic: KnownMagic::AuthoringMetaV1,
1155 content_type: ContentType::Cbor,
1156 content_encoding: ContentEncoding::None,
1157 content_language: ContentLanguage::None,
1158 schema: None,
1159 };
1160
1161 let dotrain_content = "#main _ _: int-add(1 2) int-add(2 3)";
1162 let dotrain_content_bytes = dotrain_content.as_bytes().to_vec();
1163 let content_encoding = ContentEncoding::Deflate;
1164 let deflated_payload = content_encoding.encode(&dotrain_content_bytes);
1165 let meta_map_2 = RainMetaDocumentV1Item {
1166 payload: serde_bytes::ByteBuf::from(deflated_payload.clone()),
1167 magic: KnownMagic::DotrainV1,
1168 content_type: ContentType::OctetStream,
1169 content_encoding,
1170 content_language: ContentLanguage::En,
1171 schema: None,
1172 };
1173
1174 let cbor_encoded = RainMetaDocumentV1Item::cbor_encode_seq(
1176 &vec![meta_map_1.clone(), meta_map_2.clone()],
1177 KnownMagic::RainMetaDocumentV1,
1178 )?;
1179
1180 assert_eq!(
1182 &cbor_encoded[0..8],
1183 KnownMagic::RainMetaDocumentV1.to_prefix_bytes()
1184 );
1185
1186 assert_eq!(cbor_encoded[8], 0xa3);
1189 assert_eq!(cbor_encoded[9], 0x00);
1191 assert_eq!(cbor_encoded[10], 0b010_11001);
1193 assert_eq!(cbor_encoded[11], 0b000_00010);
1194 assert_eq!(cbor_encoded[12], 0b000_00000);
1195 assert_eq!(cbor_encoded[13..525], authoring_meta_abi_encoded);
1197 assert_eq!(cbor_encoded[525], 0x01);
1199 assert_eq!(cbor_encoded[526], 0b000_11011);
1201 assert_eq!(
1203 &cbor_encoded[527..535],
1204 KnownMagic::AuthoringMetaV1.to_prefix_bytes()
1205 );
1206 assert_eq!(cbor_encoded[535], 0x02);
1208 assert_eq!(cbor_encoded[536], 0b011_10000);
1210 assert_eq!(&cbor_encoded[537..553], "application/cbor".as_bytes());
1212
1213 assert_eq!(cbor_encoded[553], 0xa5);
1216 assert_eq!(cbor_encoded[554], 0x00);
1218 assert_eq!(cbor_encoded[555], 0b010_11000);
1220 assert_eq!(cbor_encoded[556], 0b001_00100);
1221 assert_eq!(cbor_encoded[557..593], deflated_payload);
1224 assert_eq!(cbor_encoded[593], 0x01);
1226 assert_eq!(cbor_encoded[594], 0b000_11011);
1228 assert_eq!(
1230 &cbor_encoded[595..603],
1231 KnownMagic::DotrainV1.to_prefix_bytes()
1232 );
1233 assert_eq!(cbor_encoded[603], 0x02);
1235 assert_eq!(cbor_encoded[604], 0b011_11000);
1237 assert_eq!(cbor_encoded[605], 0b000_11000);
1238 assert_eq!(
1240 &cbor_encoded[606..630],
1241 "application/octet-stream".as_bytes()
1242 );
1243 assert_eq!(cbor_encoded[630], 0x03);
1245 assert_eq!(cbor_encoded[631], 0b011_00111);
1247 assert_eq!(&cbor_encoded[632..639], "deflate".as_bytes());
1249 assert_eq!(cbor_encoded[639], 0x04);
1251 assert_eq!(cbor_encoded[640], 0b011_00010);
1253 assert_eq!(&cbor_encoded[641..], "en".as_bytes());
1255
1256 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1258 assert_eq!(cbor_decoded.len(), 2);
1260
1261 assert_eq!(cbor_decoded[0], meta_map_1);
1263 assert_eq!(cbor_decoded[1], meta_map_2);
1265
1266 Ok(())
1267 }
1268
1269 #[test]
1270 fn test_bytes32_to_str() {
1271 let text_bytes_list = vec![
1272 (
1273 "",
1274 hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1275 ),
1276 (
1277 "A",
1278 hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1279 ),
1280 (
1281 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1282 hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1283 ),
1284 (
1285 "!@#$%^&*(),./;'[]",
1286 hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1287 ),
1288 ];
1289
1290 for (text, bytes) in text_bytes_list {
1291 assert_eq!(text, bytes32_to_str(&bytes).unwrap());
1292 }
1293 }
1294
1295 #[test]
1296 fn test_str_to_bytes32() {
1297 let text_bytes_list = vec![
1298 (
1299 "",
1300 hex!("0000000000000000000000000000000000000000000000000000000000000000"),
1301 ),
1302 (
1303 "A",
1304 hex!("4100000000000000000000000000000000000000000000000000000000000000"),
1305 ),
1306 (
1307 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1308 hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"),
1309 ),
1310 (
1311 "!@#$%^&*(),./;'[]",
1312 hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"),
1313 ),
1314 ];
1315
1316 for (text, bytes) in text_bytes_list {
1317 assert_eq!(bytes, str_to_bytes32(text).unwrap());
1318 }
1319 }
1320
1321 #[test]
1322 fn test_str_to_bytes32_long() {
1323 assert!(matches!(
1324 str_to_bytes32("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456").unwrap_err(),
1325 Error::BiggerThan32Bytes
1326 ));
1327 }
1328
1329 #[test]
1333 fn test_str_to_bytes32_rejects_nul() {
1334 for text in [
1335 "\0",
1336 "\0a",
1337 "a\0",
1338 "a\0b",
1339 "abcdefghijklmnopqrstuvwxyz01234\0",
1340 ] {
1341 assert!(
1342 matches!(str_to_bytes32(text), Err(Error::NulByteInInput)),
1343 "nul bearing input {:?} accepted",
1344 text
1345 );
1346 }
1347 }
1348
1349 #[test]
1352 fn test_str_to_bytes32_round_trip() -> Result<(), Error> {
1353 let mut seen: Vec<[u8; 32]> = vec![];
1354 for text in [
1355 "",
1356 "a",
1357 "stack",
1358 "!@#$%^&*(),./;'[]",
1359 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345",
1360 ] {
1361 let bytes = str_to_bytes32(text)?;
1362 assert_eq!(bytes32_to_str(&bytes)?, text);
1363 assert!(!seen.contains(&bytes), "input {:?} collided", text);
1364 seen.push(bytes);
1365 }
1366 Ok(())
1367 }
1368
1369 #[tokio::test]
1370 async fn test_implements_i_describe_by_meta_v1() {
1371 async fn new_server_client() -> (Asserter, impl Provider) {
1373 let asserter = Asserter::new();
1374 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
1375
1376 asserter.push_success(
1378 &"0x0000000000000000000000000000000000000000000000000000000000000001",
1379 );
1380 asserter.push_success(
1381 &"0x0000000000000000000000000000000000000000000000000000000000000000",
1382 );
1383
1384 (asserter, provider)
1385 }
1386
1387 let address = Address::random();
1388
1389 let (asserter, provider) = new_server_client().await;
1391 asserter
1392 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
1393 let result = implements_i_described_by_meta_v1(&provider, address).await;
1394 assert!(result);
1395
1396 let (asserter, provider) = new_server_client().await;
1398 asserter
1399 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
1400 let result = implements_i_described_by_meta_v1(&provider, address).await;
1401 assert!(!result);
1402
1403 let (asserter, provider) = new_server_client().await;
1405 asserter.push_failure(ErrorPayload {
1406 code: -32003,
1407 message: "execution reverted".into(),
1408 data: Some(serde_json::value::to_raw_value(&json!("0x00")).unwrap()),
1409 });
1410 let result = implements_i_described_by_meta_v1(&provider, address).await;
1411 assert!(!result);
1412 }
1413
1414 #[test]
1418 fn oa_schema_map_key_roundtrip() -> Result<(), Error> {
1419 let payload = vec![0x01, 0x02, 0x03];
1420 let schema = "QmSchemaHash1234567890abcdefghijklmnopqrstuvwx".to_string();
1423 assert_eq!(schema.len(), 46);
1424
1425 let meta_map = RainMetaDocumentV1Item {
1426 payload: serde_bytes::ByteBuf::from(payload.clone()),
1427 magic: KnownMagic::OaStructure,
1428 content_type: ContentType::Json,
1429 content_encoding: ContentEncoding::Deflate,
1430 content_language: ContentLanguage::None,
1431 schema: Some(schema.clone()),
1432 };
1433 let cbor_encoded = meta_map.cbor_encode()?;
1434
1435 assert_eq!(cbor_encoded[0], 0xa5);
1437 assert_eq!(cbor_encoded[1], 0x00);
1439 assert_eq!(cbor_encoded[2], 0b010_00011);
1441 assert_eq!(cbor_encoded[3..6], payload);
1443 assert_eq!(cbor_encoded[6], 0x01);
1445 assert_eq!(cbor_encoded[7], 0b000_11011);
1447 assert_eq!(
1449 &cbor_encoded[8..16],
1450 KnownMagic::OaStructure.to_prefix_bytes()
1451 );
1452 assert_eq!(cbor_encoded[16], 0x02);
1454 assert_eq!(cbor_encoded[17], 0b011_10000);
1456 assert_eq!(&cbor_encoded[18..34], "application/json".as_bytes());
1457 assert_eq!(cbor_encoded[34], 0x03);
1459 assert_eq!(cbor_encoded[35], 0b011_00111);
1461 assert_eq!(&cbor_encoded[36..43], "deflate".as_bytes());
1462 assert_eq!(cbor_encoded[43], 0b000_11011);
1464 assert_eq!(
1465 &cbor_encoded[44..52],
1466 KnownMagic::OaSchema.to_prefix_bytes()
1467 );
1468 assert_eq!(cbor_encoded[52], 0b011_11000);
1470 assert_eq!(cbor_encoded[53], 46);
1471 assert_eq!(&cbor_encoded[54..], schema.as_bytes());
1473
1474 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1476 assert_eq!(cbor_decoded.len(), 1);
1478 assert_eq!(cbor_decoded[0], meta_map);
1480
1481 Ok(())
1482 }
1483
1484 #[test]
1487 fn no_schema_key_encodes_as_before_roundtrip() -> Result<(), Error> {
1488 let payload = vec![0x0a, 0x0b];
1489 let meta_map = RainMetaDocumentV1Item {
1490 payload: serde_bytes::ByteBuf::from(payload.clone()),
1491 magic: KnownMagic::OaStructure,
1492 content_type: ContentType::None,
1493 content_encoding: ContentEncoding::None,
1494 content_language: ContentLanguage::None,
1495 schema: None,
1496 };
1497 let cbor_encoded = meta_map.cbor_encode()?;
1498
1499 assert_eq!(cbor_encoded[0], 0xa2);
1501 assert_eq!(cbor_encoded[1], 0x00);
1503 assert_eq!(cbor_encoded[2], 0b010_00010);
1505 assert_eq!(cbor_encoded[3..5], payload);
1507 assert_eq!(cbor_encoded[5], 0x01);
1509 assert_eq!(cbor_encoded[6], 0b000_11011);
1511 assert_eq!(
1513 &cbor_encoded[7..],
1514 KnownMagic::OaStructure.to_prefix_bytes()
1515 );
1516
1517 let cbor_decoded = RainMetaDocumentV1Item::cbor_decode(&cbor_encoded)?;
1518 assert_eq!(cbor_decoded.len(), 1);
1519 assert_eq!(cbor_decoded[0], meta_map);
1520
1521 Ok(())
1522 }
1523
1524 #[test]
1527 fn unknown_map_key_index_is_ignored() -> Result<(), Error> {
1528 let mut bytes: Vec<u8> = vec![
1529 0xa3, 0x00, 0x40, 0x01, 0x1b,
1533 ];
1534 bytes.extend_from_slice(&KnownMagic::DotrainSourceV1.to_prefix_bytes());
1535 bytes.extend_from_slice(&[0x05, 0x07]);
1537
1538 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1539 assert_eq!(decoded.len(), 1);
1540 assert_eq!(decoded[0], plain_item(KnownMagic::DotrainSourceV1, vec![]));
1541
1542 Ok(())
1543 }
1544
1545 #[test]
1548 fn non_oa_schema_extra_map_key_is_ignored() -> Result<(), Error> {
1549 let mut bytes: Vec<u8> = vec![
1552 0xa3, 0x00, 0x41, 0xff, 0x01, 0x1b,
1556 ];
1557 bytes.extend_from_slice(&KnownMagic::OaStructure.to_prefix_bytes());
1558 bytes.push(0x1b);
1560 bytes.extend_from_slice(&KnownMagic::OaHashList.to_prefix_bytes());
1561 bytes.extend_from_slice(&[0x62, 0x68, 0x69]);
1563
1564 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1565 assert_eq!(decoded.len(), 1);
1566 let expected = plain_item(KnownMagic::OaStructure, vec![0xff]);
1567 assert_eq!(decoded[0], expected);
1568 assert_eq!(decoded[0].schema, None);
1569
1570 Ok(())
1571 }
1572
1573 #[test]
1576 fn unknown_map_key_consumes_its_whole_value() -> Result<(), Error> {
1577 let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1578 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1579 bytes.extend_from_slice(&[0x05, 0xa1, 0x18, 0x2a, 0x82, 0x01, 0x02]);
1581 bytes.extend_from_slice(&handwritten_map());
1582
1583 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1584 assert_eq!(decoded.len(), 2);
1585 let expected = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1586 assert_eq!(decoded[0], expected);
1587 assert_eq!(decoded[1], expected);
1588
1589 Ok(())
1590 }
1591
1592 #[test]
1595 fn ignored_map_key_is_absent_from_the_reencoding() -> Result<(), Error> {
1596 let mut bytes: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1597 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1598 bytes.extend_from_slice(&[0x05, 0x07]);
1599
1600 let decoded = RainMetaDocumentV1Item::cbor_decode(&bytes)?;
1601 assert_eq!(decoded[0].cbor_encode()?, handwritten_map());
1602 assert_eq!(decoded[0].hash(false)?, keccak256(handwritten_map()).0);
1603 assert_ne!(decoded[0].hash(false)?, keccak256(&bytes).0);
1604
1605 Ok(())
1606 }
1607
1608 #[test]
1612 fn non_integer_map_key_errors() {
1613 let mut text_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1614 text_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1615 text_key.extend_from_slice(&[0x61, 0x35, 0x07]);
1617 assert!(matches!(
1618 RainMetaDocumentV1Item::cbor_decode(&text_key),
1619 Err(Error::SerdeCborError(_))
1620 ));
1621
1622 let mut negative_key: Vec<u8> = vec![0xa3, 0x00, 0x41, 0x01, 0x01, 0x1b];
1623 negative_key.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1624 negative_key.extend_from_slice(&[0x20, 0x07]);
1626 assert!(matches!(
1627 RainMetaDocumentV1Item::cbor_decode(&negative_key),
1628 Err(Error::SerdeCborError(_))
1629 ));
1630 }
1631
1632 #[test]
1634 fn unknown_map_key_does_not_stand_in_for_a_mandatory_key() {
1635 let mut bytes: Vec<u8> = vec![0xa2, 0x05, 0x07, 0x01, 0x1b];
1636 bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1637 assert!(matches!(
1638 RainMetaDocumentV1Item::cbor_decode(&bytes),
1639 Err(Error::SerdeCborError(_))
1640 ));
1641 }
1642
1643 fn plain_item(magic: KnownMagic, payload: Vec<u8>) -> RainMetaDocumentV1Item {
1644 RainMetaDocumentV1Item {
1645 payload: serde_bytes::ByteBuf::from(payload),
1646 magic,
1647 content_type: ContentType::None,
1648 content_encoding: ContentEncoding::None,
1649 content_language: ContentLanguage::None,
1650 schema: None,
1651 }
1652 }
1653
1654 fn sample_authoring_doc() -> (AuthoringMeta, Vec<u8>) {
1657 let authoring_meta: AuthoringMeta = serde_json::from_str(
1658 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
1659 )
1660 .unwrap();
1661 let abi = authoring_meta.abi_encode_validate().unwrap();
1662 let item = RainMetaDocumentV1Item {
1663 payload: serde_bytes::ByteBuf::from(abi),
1664 magic: KnownMagic::AuthoringMetaV1,
1665 content_type: ContentType::Cbor,
1666 content_encoding: ContentEncoding::None,
1667 content_language: ContentLanguage::None,
1668 schema: None,
1669 };
1670 let doc =
1671 RainMetaDocumentV1Item::cbor_encode_seq(&vec![item], KnownMagic::RainMetaDocumentV1)
1672 .unwrap();
1673 (authoring_meta, doc)
1674 }
1675
1676 fn sample_dotrain_item() -> RainMetaDocumentV1Item {
1677 RainMetaDocumentV1Item {
1678 payload: serde_bytes::ByteBuf::from("some dotrain body".as_bytes()),
1679 magic: KnownMagic::DotrainV1,
1680 content_type: ContentType::OctetStream,
1681 content_encoding: ContentEncoding::None,
1682 content_language: ContentLanguage::None,
1683 schema: None,
1684 }
1685 }
1686
1687 fn handwritten_map() -> Vec<u8> {
1690 vec![
1691 0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b, 0xff, 0xda, 0xc2, 0xf2, 0xf3, 0x7b, 0xe8, 0x94, ]
1697 }
1698
1699 #[test]
1703 fn test_hash_bare_vs_document() -> Result<(), Error> {
1704 let map_bytes = handwritten_map();
1705 let mut doc_bytes: Vec<u8> = vec![0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74];
1706 doc_bytes.extend_from_slice(&map_bytes);
1707
1708 let item = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1709 assert_eq!(item.hash(false)?, keccak256(&map_bytes).0);
1710 assert_eq!(item.hash(true)?, keccak256(&doc_bytes).0);
1711 assert_ne!(item.hash(false)?, item.hash(true)?);
1712 Ok(())
1713 }
1714
1715 #[test]
1717 fn test_cbor_decode_empty_is_corrupt() {
1718 assert!(matches!(
1719 RainMetaDocumentV1Item::cbor_decode(&[]),
1720 Err(Error::CorruptMeta)
1721 ));
1722 let prefix = KnownMagic::RainMetaDocumentV1.to_prefix_bytes();
1723 assert!(matches!(
1724 RainMetaDocumentV1Item::cbor_decode(&prefix),
1725 Err(Error::CorruptMeta)
1726 ));
1727 }
1728
1729 #[test]
1732 fn test_cbor_decode_trailing_truncated_is_corrupt() {
1733 let mut bytes = handwritten_map();
1734 bytes.push(0x1b); assert!(matches!(
1736 RainMetaDocumentV1Item::cbor_decode(&bytes),
1737 Err(Error::CorruptMeta)
1738 ));
1739 }
1740
1741 #[test]
1744 fn test_cbor_decode_trailing_garbage_errors() {
1745 let mut bytes = handwritten_map();
1746 bytes.push(0xff); assert!(matches!(
1748 RainMetaDocumentV1Item::cbor_decode(&bytes),
1749 Err(Error::SerdeCborError(_))
1750 ));
1751 }
1752
1753 #[test]
1755 fn test_cbor_decode_missing_payload_errors() {
1756 let mut bytes: Vec<u8> = vec![0xa1, 0x01, 0x1b]; bytes.extend_from_slice(&KnownMagic::DotrainV1.to_prefix_bytes());
1758 assert!(matches!(
1759 RainMetaDocumentV1Item::cbor_decode(&bytes),
1760 Err(Error::SerdeCborError(_))
1761 ));
1762 }
1763
1764 #[test]
1766 fn test_cbor_decode_missing_magic_errors() {
1767 let bytes: Vec<u8> = vec![0xa1, 0x00, 0x41, 0x01]; assert!(matches!(
1769 RainMetaDocumentV1Item::cbor_decode(&bytes),
1770 Err(Error::SerdeCborError(_))
1771 ));
1772 }
1773
1774 #[test]
1776 fn test_cbor_decode_unknown_magic_errors() {
1777 let mut bytes: Vec<u8> = vec![0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b];
1778 bytes.extend_from_slice(&0xdeadbeefdeadbeefu64.to_be_bytes());
1779 assert!(matches!(
1780 RainMetaDocumentV1Item::cbor_decode(&bytes),
1781 Err(Error::SerdeCborError(_))
1782 ));
1783 }
1784
1785 #[test]
1789 fn test_cbor_decode_handwritten_document_magic_item() {
1790 let bytes: Vec<u8> = vec![
1791 0xa2, 0x00, 0x41, 0x01, 0x01, 0x1b, 0xff, 0x0a, 0x89, 0xc6, 0x74, 0xee, 0x78, 0x74, ];
1797 assert!(RainMetaDocumentV1Item::cbor_decode(&bytes).is_err());
1801 }
1802
1803 #[test]
1809 fn test_document_magic_item_carries_a_nested_document() -> Result<(), Error> {
1810 let inner = plain_item(KnownMagic::DotrainV1, vec![0x01]);
1811 let inner_doc = RainMetaDocumentV1Item::cbor_encode_seq(
1812 &vec![inner.clone()],
1813 KnownMagic::RainMetaDocumentV1,
1814 )?;
1815 let outer = plain_item(KnownMagic::RainMetaDocumentV1, inner_doc.clone());
1816 let outer_doc = RainMetaDocumentV1Item::cbor_encode_seq(
1817 &vec![outer.clone()],
1818 KnownMagic::RainMetaDocumentV1,
1819 )?;
1820
1821 assert!(RainMetaDocumentV1Item::cbor_decode(&outer_doc).is_err());
1827 assert_eq!(
1828 RainMetaDocumentV1Item::cbor_decode(&inner_doc)?,
1829 vec![inner]
1830 );
1831 Ok(())
1832 }
1833
1834 #[test]
1837 fn test_document_magic_item_is_not_unpackable() {
1838 assert!(matches!(
1839 KnownMeta::try_from(KnownMagic::RainMetaDocumentV1),
1840 Err(Error::UnsupportedMeta)
1841 ));
1842 assert!(matches!(
1843 plain_item(KnownMagic::RainMetaDocumentV1, vec![0x01]).unpack_into::<Vec<u8>>(),
1844 Err(Error::UnsupportedMeta)
1845 ));
1846 }
1847
1848 #[test]
1850 fn test_unpack_decodes_content_encoding() -> Result<(), Error> {
1851 let content = b"unpack me via deflate".to_vec();
1852 let packed = ContentEncoding::Deflate.encode(&content);
1853 assert_ne!(packed, content);
1854 let mut item = plain_item(KnownMagic::DotrainV1, packed);
1855 item.content_encoding = ContentEncoding::Deflate;
1856 assert_eq!(item.unpack()?, content);
1857
1858 let item = plain_item(KnownMagic::DotrainV1, content.clone());
1859 assert_eq!(item.unpack()?, content);
1860 Ok(())
1861 }
1862
1863 #[test]
1866 fn test_unpack_into_whitelist() {
1867 use strum::IntoEnumIterator;
1868 let supported = [
1869 KnownMagic::OpMetaV1,
1870 KnownMagic::DotrainV1,
1871 KnownMagic::RainlangV1,
1872 KnownMagic::SolidityAbiV2,
1873 KnownMagic::AuthoringMetaV1,
1874 KnownMagic::AuthoringMetaV2,
1875 KnownMagic::AddressList,
1876 KnownMagic::InterpreterCallerMetaV1,
1877 KnownMagic::ExpressionDeployerV2BytecodeV1,
1878 KnownMagic::DotrainSourceV1,
1879 KnownMagic::OrderBuilderStateV1,
1880 KnownMagic::RainlangSourceV1,
1881 KnownMagic::RaindexSignedContextOracleV1,
1882 ];
1883 for magic in supported {
1884 let unpacked: Vec<u8> = plain_item(magic, vec![0x61]).unpack_into().unwrap();
1885 assert_eq!(unpacked, vec![0x61], "{:?}", magic);
1886 }
1887 let unsupported = [
1888 KnownMagic::RainMetaDocumentV1,
1889 KnownMagic::WebDataV1,
1890 KnownMagic::OaSchema,
1891 KnownMagic::OaHashList,
1892 KnownMagic::OaStructure,
1893 KnownMagic::OaTokenImage,
1894 KnownMagic::OaTokenCredentialLinks,
1895 ];
1896 for magic in unsupported {
1897 let result: Result<Vec<u8>, Error> = plain_item(magic, vec![0x61]).unpack_into();
1898 assert!(matches!(result, Err(Error::UnsupportedMeta)), "{:?}", magic);
1899 }
1900 assert_eq!(
1902 supported.len() + unsupported.len(),
1903 KnownMagic::iter().count()
1904 );
1905 }
1906
1907 #[test]
1910 fn test_try_into_string_invalid_utf8_errors() {
1911 let item = plain_item(KnownMagic::DotrainV1, vec![0xff, 0xfe]);
1912 let result: Result<String, Error> = item.try_into();
1913 assert!(matches!(result, Err(Error::FromUtf8Error(_))));
1914 }
1915
1916 #[test]
1918 fn test_try_into_vec_decodes_encoding() -> Result<(), Error> {
1919 let content = b"raw bytecode bytes \x00\x01\x02".to_vec();
1920 let packed = ContentEncoding::Deflate.encode(&content);
1921 let mut item = plain_item(KnownMagic::ExpressionDeployerV2BytecodeV1, packed.clone());
1922 item.content_encoding = ContentEncoding::Deflate;
1923 let unpacked: Vec<u8> = item.try_into()?;
1924 assert_eq!(unpacked, content);
1925 assert_ne!(unpacked, packed);
1926 Ok(())
1927 }
1928
1929 #[test]
1932 fn test_content_encoding_deflate_roundtrip() -> Result<(), Error> {
1933 let content = b"hello rain deflate fixture hello rain deflate fixture".to_vec();
1934 let encoded = ContentEncoding::Deflate.encode(&content);
1935 assert_ne!(encoded, content);
1936 assert_eq!(encoded[0], 0x78);
1937 assert_eq!(ContentEncoding::Deflate.decode(&encoded)?, content);
1938 Ok(())
1939 }
1940
1941 #[test]
1943 fn test_content_encoding_passthrough() -> Result<(), Error> {
1944 let data = vec![0x00, 0xff, 0x10];
1945 for encoding in [ContentEncoding::None, ContentEncoding::Identity] {
1946 assert_eq!(encoding.encode(&data), data, "{:?}", encoding);
1947 assert_eq!(encoding.decode(&data)?, data, "{:?}", encoding);
1948 }
1949 Ok(())
1950 }
1951
1952 #[test]
1955 fn test_content_encoding_decode_fixtures() -> Result<(), Error> {
1956 let content = b"hello rain deflate fixture".to_vec();
1957 let zlib: Vec<u8> = vec![
1958 120, 156, 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44,
1959 73, 85, 72, 203, 172, 40, 41, 45, 74, 5, 0, 132, 64, 9, 251,
1960 ];
1961 let raw: Vec<u8> = vec![
1962 203, 72, 205, 201, 201, 87, 40, 74, 204, 204, 83, 72, 73, 77, 203, 73, 44, 73, 85, 72,
1963 203, 172, 40, 41, 45, 74, 5, 0,
1964 ];
1965 assert_eq!(ContentEncoding::Deflate.decode(&zlib)?, content);
1966 assert_eq!(ContentEncoding::Deflate.decode(&raw)?, content);
1967 Ok(())
1968 }
1969
1970 #[test]
1973 fn test_content_encoding_decode_garbage_errors() {
1974 let garbage = [0xffu8, 0xff, 0xff, 0xff];
1975 assert!(matches!(
1976 ContentEncoding::Deflate.decode(&garbage),
1977 Err(Error::InflateError(_))
1978 ));
1979 }
1980
1981 #[test]
1983 fn test_content_headers_strum_names() {
1984 use std::str::FromStr;
1985 assert_eq!(
1986 ContentEncoding::from_str("deflate").unwrap(),
1987 ContentEncoding::Deflate
1988 );
1989 assert_eq!(
1990 ContentEncoding::from_str("identity").unwrap(),
1991 ContentEncoding::Identity
1992 );
1993 assert_eq!(
1994 ContentEncoding::from_str("none").unwrap(),
1995 ContentEncoding::None
1996 );
1997 assert_eq!(ContentEncoding::Deflate.to_string(), "deflate");
1998 assert_eq!(
1999 ContentType::from_str("octet-stream").unwrap(),
2000 ContentType::OctetStream
2001 );
2002 assert_eq!(ContentType::from_str("json").unwrap(), ContentType::Json);
2003 assert_eq!(ContentType::Json.to_string(), "json");
2004 assert_eq!(
2005 ContentLanguage::from_str("en").unwrap(),
2006 ContentLanguage::En
2007 );
2008 }
2009
2010 #[test]
2013 fn test_known_meta_try_from_magic() {
2014 let cases: [(KnownMagic, KnownMeta); 13] = [
2015 (KnownMagic::OpMetaV1, KnownMeta::OpV1),
2016 (KnownMagic::DotrainV1, KnownMeta::DotrainV1),
2017 (KnownMagic::RainlangV1, KnownMeta::RainlangV1),
2018 (KnownMagic::SolidityAbiV2, KnownMeta::SolidityAbiV2),
2019 (KnownMagic::AuthoringMetaV1, KnownMeta::AuthoringMetaV1),
2020 (KnownMagic::AuthoringMetaV2, KnownMeta::AuthoringMetaV2),
2021 (KnownMagic::AddressList, KnownMeta::AddressList),
2022 (
2023 KnownMagic::InterpreterCallerMetaV1,
2024 KnownMeta::InterpreterCallerMetaV1,
2025 ),
2026 (
2027 KnownMagic::ExpressionDeployerV2BytecodeV1,
2028 KnownMeta::ExpressionDeployerV2BytecodeV1,
2029 ),
2030 (KnownMagic::RainlangSourceV1, KnownMeta::RainlangSourceV1),
2031 (KnownMagic::DotrainSourceV1, KnownMeta::DotrainSourceV1),
2032 (
2033 KnownMagic::OrderBuilderStateV1,
2034 KnownMeta::OrderBuilderStateV1,
2035 ),
2036 (
2037 KnownMagic::RaindexSignedContextOracleV1,
2038 KnownMeta::RaindexSignedContextOracleV1,
2039 ),
2040 ];
2041 for (magic, meta) in cases {
2042 assert_eq!(KnownMeta::try_from(magic).unwrap(), meta, "{:?}", magic);
2043 }
2044 for magic in [
2045 KnownMagic::RainMetaDocumentV1,
2046 KnownMagic::WebDataV1,
2047 KnownMagic::OaSchema,
2048 KnownMagic::OaHashList,
2049 KnownMagic::OaStructure,
2050 KnownMagic::OaTokenImage,
2051 KnownMagic::OaTokenCredentialLinks,
2052 ] {
2053 assert!(
2054 matches!(KnownMeta::try_from(magic), Err(Error::UnsupportedMeta)),
2055 "{:?}",
2056 magic
2057 );
2058 }
2059 }
2060
2061 #[test]
2064 fn test_known_meta_strum_parse_display() {
2065 use std::str::FromStr;
2066 assert_eq!(
2067 KnownMeta::from_str("solidity-abi-v2").unwrap(),
2068 KnownMeta::SolidityAbiV2
2069 );
2070 assert_eq!(
2071 KnownMeta::from_str("interpreter-caller-meta-v1").unwrap(),
2072 KnownMeta::InterpreterCallerMetaV1
2073 );
2074 assert_eq!(KnownMeta::SolidityAbiV2.to_string(), "solidity-abi-v2");
2075 }
2076
2077 fn sample_deployer(meta_bytes: &[u8]) -> NPE2Deployer {
2081 NPE2Deployer {
2082 meta_hash: keccak256(meta_bytes).0.to_vec(),
2083 meta_bytes: meta_bytes.to_vec(),
2084 bytecode: vec![0xb1],
2085 parser: vec![0xb2],
2086 store: vec![0xb3],
2087 interpreter: vec![0xb4],
2088 authoring_meta: None,
2089 }
2090 }
2091
2092 fn deployer_json_body(
2093 meta_hash_hex: &str,
2094 meta_bytes_hex: &str,
2095 tx_hex: &str,
2096 bytecode_meta_id_hex: &str,
2097 ) -> serde_json::Value {
2098 json!({
2099 "data": {
2100 "expressionDeployers": [{
2101 "constructorMetaHash": meta_hash_hex,
2102 "constructorMeta": meta_bytes_hex,
2103 "deployTransaction": {"id": tx_hex},
2104 "bytecode": "0x01",
2105 "parser": {"parser": {"deployedBytecode": "0x02"}},
2106 "store": {"store": {"deployedBytecode": "0x03"}},
2107 "interpreter": {"interpreter": {"deployedBytecode": "0x04"}},
2108 "meta": [{"__typename": "RainMetaV1", "id": bytecode_meta_id_hex}]
2109 }]
2110 }
2111 })
2112 }
2113
2114 #[tokio::test]
2116 async fn test_search_lowercases_hash() {
2117 use httpmock::prelude::*;
2118 let (_, doc) = sample_authoring_doc();
2119 let hash_upper = format!("0x{}", "AB".repeat(32));
2120 let server = MockServer::start();
2121 let mock = server.mock(|when, then| {
2122 when.method(POST)
2123 .body_contains(hash_upper.to_ascii_lowercase());
2124 then.status(200).json_body(json!({
2125 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2126 }));
2127 });
2128 let response = search(&hash_upper, &vec![server.url("/sg")]).await.unwrap();
2129 assert_eq!(response.bytes, doc);
2130 mock.assert();
2131 }
2132
2133 #[tokio::test]
2136 async fn test_search_first_success_wins() {
2137 use httpmock::prelude::*;
2138 let (_, doc) = sample_authoring_doc();
2139 let bad = MockServer::start();
2140 let _bad_mock = bad.mock(|when, then| {
2141 when.method(POST);
2142 then.status(500).body("subgraph down");
2143 });
2144 let good = MockServer::start();
2145 let _good_mock = good.mock(|when, then| {
2146 when.method(POST);
2147 then.status(200).json_body(json!({
2148 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2149 }));
2150 });
2151 let response = search(
2152 &format!("0x{}", "11".repeat(32)),
2153 &vec![bad.url("/sg"), good.url("/sg")],
2154 )
2155 .await
2156 .unwrap();
2157 assert_eq!(response.bytes, doc);
2158 }
2159
2160 #[tokio::test]
2163 async fn test_search_deployer_lowercases_hash() {
2164 use httpmock::prelude::*;
2165 let (_, doc) = sample_authoring_doc();
2166 let meta_hash_hex = hex::encode_prefixed(keccak256(&doc).0);
2167 let hash_upper = format!("0x{}", "CD".repeat(32));
2168 let server = MockServer::start();
2169 let mock = server.mock(|when, then| {
2170 when.method(POST)
2171 .body_contains(hash_upper.to_ascii_lowercase());
2172 then.status(200).json_body(deployer_json_body(
2173 &meta_hash_hex,
2174 &hex::encode_prefixed(&doc),
2175 &format!("0x{}", "77".repeat(32)),
2176 &meta_hash_hex,
2177 ));
2178 });
2179 let response = search_deployer(&hash_upper, &vec![server.url("/sg")])
2180 .await
2181 .unwrap();
2182 assert_eq!(response.meta_bytes, doc);
2183 assert_eq!(response.bytecode, vec![0x01]);
2184 mock.assert();
2185 }
2186
2187 #[tokio::test]
2190 async fn test_search_deployer_first_success_wins() {
2191 use httpmock::prelude::*;
2192 let (_, doc) = sample_authoring_doc();
2193 let meta_hash_hex = hex::encode_prefixed(keccak256(&doc).0);
2194 let bad = MockServer::start();
2195 let _bad_mock = bad.mock(|when, then| {
2196 when.method(POST);
2197 then.status(500).body("subgraph down");
2198 });
2199 let good = MockServer::start();
2200 let _good_mock = good.mock(|when, then| {
2201 when.method(POST);
2202 then.status(200).json_body(deployer_json_body(
2203 &meta_hash_hex,
2204 &hex::encode_prefixed(&doc),
2205 &format!("0x{}", "77".repeat(32)),
2206 &meta_hash_hex,
2207 ));
2208 });
2209 let response = search_deployer(
2210 &format!("0x{}", "22".repeat(32)),
2211 &vec![bad.url("/sg"), good.url("/sg")],
2212 )
2213 .await
2214 .unwrap();
2215 assert_eq!(response.meta_bytes, doc);
2216 }
2217
2218 #[tokio::test]
2222 async fn test_search_empty_subgraphs_is_a_miss() {
2223 let hash = format!("0x{}", "33".repeat(32));
2224 assert!(matches!(
2225 search(&hash, &vec![]).await,
2226 Err(Error::NoRecordFound)
2227 ));
2228 assert!(matches!(
2229 search_deployer(&hash, &vec![]).await,
2230 Err(Error::NoRecordFound)
2231 ));
2232 }
2233
2234 #[tokio::test]
2238 async fn test_implements_erc165_gate_short_circuits() {
2239 let address = Address::random();
2240
2241 let asserter = Asserter::new();
2243 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2244 asserter
2245 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2246 asserter
2247 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2248 assert!(!implements_i_described_by_meta_v1(&provider, address).await);
2249
2250 let asserter = Asserter::new();
2252 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2253 asserter.push_failure(ErrorPayload {
2254 code: -32000,
2255 message: "connection reset".into(),
2256 data: None,
2257 });
2258 asserter
2259 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2260 assert!(!implements_i_described_by_meta_v1(&provider, address).await);
2261 }
2262
2263 #[tokio::test]
2266 async fn test_implements_undecodable_response_is_false() {
2267 let address = Address::random();
2268 let asserter = Asserter::new();
2269 let provider = ProviderBuilder::new().connect_mocked_client(asserter.clone());
2270 asserter
2271 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000001");
2272 asserter
2273 .push_success(&"0x0000000000000000000000000000000000000000000000000000000000000000");
2274 asserter.push_success(&"0x");
2275 assert!(!implements_i_described_by_meta_v1(&provider, address).await);
2276 }
2277
2278 #[test]
2281 fn test_npe2_deployer_is_corrupt_per_field() {
2282 let full = NPE2Deployer {
2283 meta_hash: vec![1],
2284 meta_bytes: vec![2],
2285 bytecode: vec![3],
2286 parser: vec![4],
2287 store: vec![5],
2288 interpreter: vec![6],
2289 authoring_meta: None,
2290 };
2291 assert!(!full.is_corrupt());
2292 for field in 0..6usize {
2293 let mut record = full.clone();
2294 match field {
2295 0 => record.meta_hash = vec![],
2296 1 => record.meta_bytes = vec![],
2297 2 => record.bytecode = vec![],
2298 3 => record.parser = vec![],
2299 4 => record.store = vec![],
2300 5 => record.interpreter = vec![],
2301 _ => unreachable!(),
2302 }
2303 assert!(record.is_corrupt(), "empty field {} must corrupt", field);
2304 }
2305 }
2306
2307 #[tokio::test]
2311 async fn test_store_constructors_inject_no_subgraphs() {
2312 assert!(Store::new().subgraphs().is_empty());
2313 assert!(Store::default().subgraphs().is_empty());
2314 assert!(Store::create(
2315 &vec![],
2316 &MetaCache::default(),
2317 &DeployerCache::default(),
2318 &HashMap::new()
2319 )
2320 .subgraphs()
2321 .is_empty());
2322
2323 let hash = [0u8; 32];
2324 let mut store = Store::default();
2325 assert!(store.update(&hash).await.is_err());
2326 assert!(store.search_deployer(&hash).await.is_err());
2327 }
2328
2329 #[test]
2337 fn test_store_create_validates_entries() {
2338 let (_, doc) = sample_authoring_doc();
2339 let good_hash = keccak256(&doc).0.to_vec();
2340 let mut cache = MetaCache::default();
2341 cache.insert_verified(&good_hash, doc.clone()).unwrap();
2342 let mut deployer_cache = DeployerCache::default();
2343 let deployer = sample_deployer(b"dep-meta");
2344 let deployer_key = vec![0x33u8; 32];
2345 deployer_cache
2346 .insert_verified(&deployer_key, deployer.clone())
2347 .unwrap();
2348 let mut dotrain_cache = HashMap::new();
2349 dotrain_cache.insert("a.rain".to_string(), good_hash.clone());
2350 dotrain_cache.insert("missing.rain".to_string(), vec![0x44u8; 32]);
2351
2352 let store = Store::create(
2353 &vec!["https://example.com/custom-sg".to_string()],
2354 &cache,
2355 &deployer_cache,
2356 &dotrain_cache,
2357 );
2358
2359 assert_eq!(
2360 store.subgraphs(),
2361 &vec!["https://example.com/custom-sg".to_string()]
2362 );
2363 assert_eq!(store.get_meta(&good_hash), Some(&doc));
2364 assert_eq!(store.get_deployer(&deployer_key), Some(&deployer));
2365 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&good_hash));
2366 assert_eq!(store.get_dotrain_hash("missing.rain"), None);
2367 }
2368
2369 #[test]
2371 fn test_store_add_subgraphs_dedupe() {
2372 let mut store = Store::new();
2373 store.add_subgraphs(&vec!["sg-a".to_string()]);
2374 store.add_subgraphs(&vec!["sg-a".to_string(), "sg-b".to_string()]);
2375 assert_eq!(
2376 store.subgraphs(),
2377 &vec!["sg-a".to_string(), "sg-b".to_string()]
2378 );
2379 }
2380
2381 #[test]
2384 fn test_store_get_deployer_lookup_chain() {
2385 let mut store = Store::new();
2386 let deployer = sample_deployer(b"dep-meta-bytes");
2387 let key = vec![0x01u8; 32];
2388 let tx = vec![0x02u8; 32];
2389 store.set_deployer(&key, &deployer, Some(&tx)).unwrap();
2390 assert_eq!(store.get_deployer(&key), Some(&deployer));
2391 assert_eq!(store.get_deployer(&tx), Some(&deployer));
2392 assert_eq!(store.get_deployer(&[0x03u8; 32]), None);
2393 assert_eq!(
2394 store.get_meta(&deployer.meta_hash),
2395 Some(&deployer.meta_bytes)
2396 );
2397 }
2398
2399 #[tokio::test]
2403 async fn test_store_search_deployer_populates_caches() {
2404 use httpmock::prelude::*;
2405 let (authoring_meta, doc) = sample_authoring_doc();
2406 let meta_hash = keccak256(&doc).0.to_vec();
2407 let meta_hash_hex = hex::encode_prefixed(&meta_hash);
2408 let tx = vec![0x77u8; 32];
2409 let server = MockServer::start();
2410 let _mock = server.mock(|when, then| {
2411 when.method(POST);
2412 then.status(200).json_body(deployer_json_body(
2413 &meta_hash_hex,
2414 &hex::encode_prefixed(&doc),
2415 &hex::encode_prefixed(&tx),
2416 &meta_hash_hex,
2417 ));
2418 });
2419 let mut store = Store::new();
2420 store.add_subgraphs(&vec![server.url("/sg")]);
2421
2422 let record = store.search_deployer(&meta_hash).await.cloned().unwrap();
2423 assert_eq!(record.meta_hash, meta_hash);
2424 assert_eq!(record.meta_bytes, doc);
2425 assert_eq!(record.bytecode, vec![0x01]);
2426 assert_eq!(record.parser, vec![0x02]);
2427 assert_eq!(record.store, vec![0x03]);
2428 assert_eq!(record.interpreter, vec![0x04]);
2429 assert_eq!(record.authoring_meta, Some(authoring_meta));
2430 assert_eq!(store.get_meta(&meta_hash), Some(&doc));
2431 assert_eq!(store.get_deployer(&tx), Some(&record));
2432 }
2433
2434 #[tokio::test]
2436 async fn test_store_search_deployer_error_returns_none() {
2437 use httpmock::prelude::*;
2438 let server = MockServer::start();
2439 let _mock = server.mock(|when, then| {
2440 when.method(POST);
2441 then.status(500).body("subgraph down");
2442 });
2443 let mut store = Store::new();
2444 store.add_subgraphs(&vec![server.url("/sg")]);
2445 assert!(store.search_deployer(&[0x0Du8; 32]).await.is_err());
2446 assert!(store.cache().is_empty());
2447 assert!(store.deployer_cache().is_empty());
2448 }
2449
2450 #[tokio::test]
2454 async fn test_store_search_deployer_check_branches() {
2455 use httpmock::prelude::*;
2456 let mut store = Store::new();
2458 let deployer = sample_deployer(b"cached-meta");
2459 let key = vec![0x11u8; 32];
2460 let tx = vec![0x22u8; 32];
2461 store.set_deployer(&key, &deployer, Some(&tx)).unwrap();
2462 assert_eq!(store.search_deployer_check(&key).await.unwrap(), &deployer);
2463 assert_eq!(store.search_deployer_check(&tx).await.unwrap(), &deployer);
2464
2465 let (_, doc) = sample_authoring_doc();
2467 let meta_hash = keccak256(&doc).0.to_vec();
2468 let meta_hash_hex = hex::encode_prefixed(&meta_hash);
2469 let server = MockServer::start();
2470 let _mock = server.mock(|when, then| {
2471 when.method(POST);
2472 then.status(200).json_body(deployer_json_body(
2473 &meta_hash_hex,
2474 &hex::encode_prefixed(&doc),
2475 &format!("0x{}", "66".repeat(32)),
2476 &meta_hash_hex,
2477 ));
2478 });
2479 let mut fresh = Store::new();
2480 fresh.add_subgraphs(&vec![server.url("/sg")]);
2481 let found = fresh
2482 .search_deployer_check(&meta_hash)
2483 .await
2484 .cloned()
2485 .unwrap();
2486 assert_eq!(found.meta_bytes, doc);
2487 }
2488
2489 #[test]
2492 fn test_store_set_deployer_from_query_response() {
2493 let (authoring_meta, doc) = sample_authoring_doc();
2494 let meta_hash = keccak256(&doc).0.to_vec();
2496 let bytecode_meta_hash = vec![0x0Bu8; 32];
2497 let tx = vec![0x0Cu8; 32];
2498 let response = DeployerResponse {
2499 tx_hash: tx.clone(),
2500 bytecode_meta_hash: bytecode_meta_hash.clone(),
2501 meta_hash: meta_hash.clone(),
2502 meta_bytes: doc.clone(),
2503 bytecode: vec![0xE1],
2504 parser: vec![0xE2],
2505 store: vec![0xE3],
2506 interpreter: vec![0xE4],
2507 };
2508 let mut store = Store::new();
2509 let record = store.set_deployer_from_query_response(response).unwrap();
2510 assert_eq!(record.meta_hash, meta_hash);
2511 assert_eq!(record.meta_bytes, doc);
2512 assert_eq!(record.bytecode, vec![0xE1]);
2513 assert_eq!(record.authoring_meta, Some(authoring_meta));
2514 assert_eq!(store.get_meta(&meta_hash), Some(&doc));
2515 assert_eq!(store.get_deployer(&bytecode_meta_hash), Some(&record));
2516 assert_eq!(store.get_deployer(&tx), Some(&record));
2517 }
2518
2519 #[test]
2523 fn test_store_dotrain_getters_and_set_fresh() {
2524 let mut store = Store::new();
2525 let text = "some dotrain content";
2526 let (hash, old) = store.set_dotrain(text, "file.rain", false).unwrap();
2527 assert!(old.is_empty());
2528 let expected_item = RainMetaDocumentV1Item {
2529 payload: serde_bytes::ByteBuf::from(text.as_bytes()),
2530 magic: KnownMagic::DotrainV1,
2531 content_type: ContentType::OctetStream,
2532 content_encoding: ContentEncoding::None,
2533 content_language: ContentLanguage::None,
2534 schema: None,
2535 };
2536 let expected_bytes = expected_item.cbor_encode().unwrap();
2537 assert_eq!(hash, keccak256(&expected_bytes).0.to_vec());
2538 assert_eq!(store.get_dotrain_hash("file.rain"), Some(&hash));
2539 assert_eq!(store.get_dotrain_uri(&hash), Some(&"file.rain".to_string()));
2540 assert_eq!(store.get_dotrain_meta("file.rain"), Some(&expected_bytes));
2541 assert_eq!(store.get_dotrain_hash("other.rain"), None);
2542 assert_eq!(store.get_dotrain_uri(&[0u8; 32]), None);
2543 assert_eq!(store.get_dotrain_meta("other.rain"), None);
2544 }
2545
2546 #[test]
2550 fn test_store_set_dotrain_branches() {
2551 let mut store = Store::new();
2552 let (hash_one, _) = store.set_dotrain("text one", "a.rain", false).unwrap();
2553
2554 let (hash_same, old_same) = store.set_dotrain("text one", "a.rain", false).unwrap();
2556 assert_eq!(hash_same, hash_one);
2557 assert!(old_same.is_empty());
2558 assert!(store.get_meta(&hash_one).is_some());
2559
2560 let (hash_two, old_two) = store.set_dotrain("text two", "a.rain", false).unwrap();
2562 assert_ne!(hash_two, hash_one);
2563 assert_eq!(old_two, hash_one);
2564 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_two));
2565 assert!(store.get_meta(&hash_one).is_none());
2566 assert!(store.get_meta(&hash_two).is_some());
2567
2568 let (hash_three, old_three) = store.set_dotrain("text three", "a.rain", true).unwrap();
2570 assert_eq!(old_three, hash_two);
2571 assert_eq!(store.get_dotrain_hash("a.rain"), Some(&hash_three));
2572 assert!(store.get_meta(&hash_two).is_some());
2573 assert!(store.get_meta(&hash_three).is_some());
2574 }
2575
2576 #[test]
2579 fn test_store_delete_dotrain_keep_meta() {
2580 let mut store = Store::new();
2581 let (hash, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2582 store.delete_dotrain("d.rain", false);
2583 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2584 assert!(store.get_meta(&hash).is_none());
2585
2586 let (hash_again, _) = store.set_dotrain("dotrain body", "d.rain", false).unwrap();
2587 store.delete_dotrain("d.rain", true);
2588 assert_eq!(store.get_dotrain_hash("d.rain"), None);
2589 assert!(store.get_meta(&hash_again).is_some());
2590 }
2591
2592 #[test]
2595 fn test_store_merge_semantics() {
2596 let deployer_ours = sample_deployer(b"ours");
2597 let deployer_theirs = sample_deployer(b"theirs");
2598 let shared_tx = vec![0x0Fu8; 32];
2599 let their_tx = vec![0x1Eu8; 32];
2600
2601 let mut ours = Store::new();
2602 let mut theirs = Store::new();
2603 ours.set_deployer(&[0x01u8; 32], &deployer_ours, Some(&shared_tx))
2604 .unwrap();
2605 theirs
2606 .set_deployer(&[0x02u8; 32], &deployer_theirs, Some(&shared_tx))
2607 .unwrap();
2608 theirs
2609 .set_deployer(&[0x02u8; 32], &deployer_theirs, Some(&their_tx))
2610 .unwrap();
2611
2612 let contested_key = vec![0x03u8; 32];
2614 let deployer_a = sample_deployer(b"deployer-a");
2615 let deployer_b = sample_deployer(b"deployer-b");
2616 ours.set_deployer(&contested_key, &deployer_a, None)
2617 .unwrap();
2618 theirs
2619 .set_deployer(&contested_key, &deployer_b, None)
2620 .unwrap();
2621
2622 let (hash_ours, _) = ours.set_dotrain("content a", "x.rain", false).unwrap();
2624 let (_hash_theirs, _) = theirs.set_dotrain("content b", "x.rain", false).unwrap();
2625
2626 theirs.add_subgraphs(&vec!["sg-their".to_string()]);
2627
2628 ours.merge(&theirs);
2629
2630 assert_eq!(
2634 ours.get_meta(&deployer_ours.meta_hash),
2635 Some(&b"ours".to_vec())
2636 );
2637 assert_eq!(
2638 ours.get_meta(&deployer_theirs.meta_hash),
2639 Some(&b"theirs".to_vec())
2640 );
2641 assert_eq!(ours.get_deployer(&contested_key), Some(&deployer_a));
2643 assert_eq!(ours.get_deployer(&shared_tx), Some(&deployer_ours));
2645 assert_eq!(ours.get_deployer(&their_tx), Some(&deployer_theirs));
2647 assert_eq!(ours.get_dotrain_hash("x.rain"), Some(&hash_ours));
2649 assert!(ours.subgraphs().contains(&"sg-their".to_string()));
2651 }
2652
2653 #[tokio::test]
2657 async fn test_store_update_and_update_check() {
2658 use httpmock::prelude::*;
2659 let authoring_meta: AuthoringMeta = serde_json::from_str(
2660 r#"[{"word":"stack","description":"Copies an existing value from the stack.","operandParserOffset":16}]"#,
2661 )
2662 .unwrap();
2663 let item_one = RainMetaDocumentV1Item {
2664 payload: serde_bytes::ByteBuf::from(authoring_meta.abi_encode_validate().unwrap()),
2665 magic: KnownMagic::AuthoringMetaV1,
2666 content_type: ContentType::Cbor,
2667 content_encoding: ContentEncoding::None,
2668 content_language: ContentLanguage::None,
2669 schema: None,
2670 };
2671 let item_two = sample_dotrain_item();
2672 let doc = RainMetaDocumentV1Item::cbor_encode_seq(
2673 &vec![item_one.clone(), item_two.clone()],
2674 KnownMagic::RainMetaDocumentV1,
2675 )
2676 .unwrap();
2677 let requested = keccak256(&doc).0.to_vec();
2678 let server = MockServer::start();
2679 let _mock = server.mock(|when, then| {
2680 when.method(POST);
2681 then.status(200).json_body(json!({
2682 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2683 }));
2684 });
2685 let mut store = Store::new();
2686 store.add_subgraphs(&vec![server.url("/sg")]);
2687 let fetched = store.update(&requested).await.cloned().unwrap();
2688 assert_eq!(fetched, doc);
2689 assert_eq!(store.get_meta(&requested), Some(&doc));
2690 let inner_one = item_one.cbor_encode().unwrap();
2691 let inner_two = item_two.cbor_encode().unwrap();
2692 assert_eq!(
2693 store.get_meta(keccak256(&inner_one).0.as_ref()),
2694 Some(&inner_one)
2695 );
2696 assert_eq!(
2697 store.get_meta(keccak256(&inner_two).0.as_ref()),
2698 Some(&inner_two)
2699 );
2700
2701 let mut cached_store = Store::new();
2703 let bytes = b"standalone meta bytes".to_vec();
2704 let hash = keccak256(&bytes).0.to_vec();
2705 assert!(cached_store.update_with(&hash, &bytes).is_ok());
2706 assert_eq!(cached_store.update_check(&hash).await.unwrap(), &bytes);
2707 }
2708
2709 #[tokio::test]
2713 async fn test_store_update_rejects_hash_mismatch() {
2714 use httpmock::prelude::*;
2715 let (_, doc) = sample_authoring_doc();
2716 let requested = keccak256(b"the real content").0.to_vec();
2717 let server = MockServer::start();
2718 let _mock = server.mock(|when, then| {
2719 when.method(POST);
2720 then.status(200).json_body(json!({
2721 "data": {"meta": {"__typename": "RainMetaV1", "rawBytes": hex::encode_prefixed(&doc)}}
2722 }));
2723 });
2724 let mut store = Store::new();
2725 store.add_subgraphs(&vec![server.url("/sg")]);
2726 assert!(store.update(&requested).await.is_err());
2727 assert!(store.get_meta(&requested).is_none());
2728 assert!(store.cache().is_empty());
2729 assert!(store.update_check(&requested).await.is_err());
2731 }
2732
2733 #[tokio::test]
2736 async fn test_store_no_subgraphs_lookups_return_none() {
2737 let hash = [0u8; 32];
2738 let mut store = Store::new();
2739 assert!(store.update(&hash).await.is_err());
2740 assert!(store.update_check(&hash).await.is_err());
2741 assert!(store.search_deployer(&hash).await.is_err());
2742 assert!(store.search_deployer_check(&hash).await.is_err());
2743 assert!(store.cache().is_empty());
2744 assert!(store.deployer_cache().is_empty());
2745 }
2746
2747 #[test]
2751 fn test_store_update_with_validation_and_content() {
2752 let mut store = Store::new();
2754 let bytes = b"payload bytes".to_vec();
2755 let wrong_hash = vec![0x99u8; 32];
2756 match store.update_with(&wrong_hash, &bytes).unwrap_err() {
2761 Error::CorruptRecord(message) => {
2762 assert!(
2763 message.contains(&hex::encode_prefixed(&wrong_hash)),
2764 "{}",
2765 message
2766 )
2767 }
2768 other => panic!("expected CorruptRecord, got {:?}", other),
2769 }
2770 assert!(store.get_meta(&wrong_hash).is_none());
2771 let hash = keccak256(&bytes).0.to_vec();
2773 assert_eq!(store.update_with(&hash, &bytes).unwrap(), &bytes);
2774
2775 let mut seeded = Store::new();
2782 let planted = b"planted value".to_vec();
2783 let planted_hash = keccak256(&planted).0.to_vec();
2784 seeded.update_with(&planted_hash, &planted).unwrap();
2785 assert_eq!(seeded.cache().len(), 1);
2786 assert_eq!(
2787 seeded.update_with(&planted_hash, &planted).unwrap(),
2788 &planted
2789 );
2790 assert_eq!(seeded.cache().len(), 1);
2791
2792 let (_, doc) = sample_authoring_doc();
2794 let doc_hash = keccak256(&doc).0.to_vec();
2795 let mut doc_store = Store::new();
2796 assert!(doc_store.update_with(&doc_hash, &doc).is_ok());
2797 let inner = doc[8..].to_vec();
2798 assert_eq!(store_inner_lookup(&doc_store, &inner), Some(inner.clone()));
2799
2800 let item_a = sample_dotrain_item().cbor_encode().unwrap();
2802 let (_, doc_b) = sample_authoring_doc();
2803 let item_b = doc_b[8..].to_vec();
2804 let seq = [item_a.clone(), item_b].concat();
2805 let seq_hash = keccak256(&seq).0.to_vec();
2806 let mut seq_store = Store::new();
2807 assert!(seq_store.update_with(&seq_hash, &seq).is_ok());
2808 assert_eq!(store_inner_lookup(&seq_store, &item_a), None);
2809 }
2810
2811 fn store_inner_lookup(store: &Store, inner_encoded: &[u8]) -> Option<Vec<u8>> {
2812 store.get_meta(keccak256(inner_encoded).0.as_ref()).cloned()
2813 }
2814
2815 #[test]
2818 fn test_bytes32_to_str_invalid_utf8() {
2819 let mut bytes = [0u8; 32];
2820 bytes[0] = 0xf0;
2821 bytes[1] = 0x28;
2822 bytes[2] = 0x8c;
2823 bytes[3] = 0x28;
2824 assert!(matches!(bytes32_to_str(&bytes), Err(Error::Utf8Error(_))));
2825 let no_nul = [0xffu8; 32];
2826 assert!(matches!(bytes32_to_str(&no_nul), Err(Error::Utf8Error(_))));
2827 }
2828}