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