1use std::{
16 borrow::Cow,
17 fmt,
18 fmt::{Debug, Display},
19 future::Future,
20};
21
22use alloy::{
23 network::Ethereum,
24 providers::{DynProvider, Provider},
25};
26use derive_builder::Builder;
27use risc0_ethereum_contracts::selector::Selector;
28use risc0_zkvm::{Digest, Journal};
29use url::Url;
30
31use crate::{
32 contracts::{ProofRequest, RequestId, RequestInput},
33 input::GuestEnv,
34 storage::{StandardStorageProvider, StorageProvider},
35 util::NotProvided,
36};
37mod preflight_layer;
38mod storage_layer;
39
40pub use preflight_layer::PreflightLayer;
41pub use storage_layer::{StorageLayer, StorageLayerConfig, StorageLayerConfigBuilder};
42mod requirements_layer;
43pub use requirements_layer::{RequirementParams, RequirementsLayer};
44mod request_id_layer;
45pub use request_id_layer::{
46 RequestIdLayer, RequestIdLayerConfig, RequestIdLayerConfigBuilder, RequestIdLayerMode,
47};
48mod offer_layer;
49pub use offer_layer::{
50 OfferLayer, OfferLayerConfig, OfferLayerConfigBuilder, OfferParams, OfferParamsBuilder,
51};
52mod finalizer;
53pub use finalizer::{Finalizer, FinalizerConfig, FinalizerConfigBuilder};
54
55pub trait RequestBuilder<Params> {
59 type Error;
61
62 fn params(&self) -> Params
67 where
68 Params: Default,
69 {
70 Default::default()
71 }
72
73 fn build(
75 &self,
76 params: impl Into<Params>,
77 ) -> impl Future<Output = Result<ProofRequest, Self::Error>>;
78}
79
80impl<L, Params> RequestBuilder<Params> for L
85where
86 L: Layer<Params, Output = ProofRequest>,
87{
88 type Error = L::Error;
89
90 async fn build(&self, params: impl Into<Params>) -> Result<ProofRequest, Self::Error> {
91 self.process(params.into()).await
92 }
93}
94
95pub trait Layer<Input> {
101 type Output;
103
104 type Error;
106
107 fn process(&self, input: Input) -> impl Future<Output = Result<Self::Output, Self::Error>>;
109}
110
111pub trait Adapt<L> {
120 type Output;
122
123 type Error;
125
126 fn process_with(self, layer: &L) -> impl Future<Output = Result<Self::Output, Self::Error>>;
128}
129
130impl<L, I> Adapt<L> for I
131where
132 L: Layer<I>,
133{
134 type Output = L::Output;
135 type Error = L::Error;
136
137 async fn process_with(self, layer: &L) -> Result<Self::Output, Self::Error> {
138 layer.process(self).await
139 }
140}
141
142impl<A, B, Input> Layer<Input> for (A, B)
144where
145 Input: Adapt<A>,
146 <Input as Adapt<A>>::Output: Adapt<B>,
147 <Input as Adapt<A>>::Error: Into<<<Input as Adapt<A>>::Output as Adapt<B>>::Error>,
148{
149 type Output = <<Input as Adapt<A>>::Output as Adapt<B>>::Output;
150 type Error = <<Input as Adapt<A>>::Output as Adapt<B>>::Error;
151
152 async fn process(&self, input: Input) -> Result<Self::Output, Self::Error> {
153 input.process_with(&self.0).await.map_err(Into::into)?.process_with(&self.1).await
154 }
155}
156
157#[derive(Clone, Builder)]
170#[non_exhaustive]
171pub struct StandardRequestBuilder<P = DynProvider, S = StandardStorageProvider> {
172 #[builder(setter(into))]
174 pub storage_layer: StorageLayer<S>,
175
176 #[builder(setter(into), default)]
178 pub preflight_layer: PreflightLayer,
179
180 #[builder(setter(into), default)]
182 pub requirements_layer: RequirementsLayer,
183
184 #[builder(setter(into))]
186 pub request_id_layer: RequestIdLayer<P>,
187
188 #[builder(setter(into))]
190 pub offer_layer: OfferLayer<P>,
191
192 #[builder(setter(into), default)]
194 pub finalizer: Finalizer,
195}
196
197impl StandardRequestBuilder<NotProvided, NotProvided> {
198 pub fn builder<P: Clone, S: Clone>() -> StandardRequestBuilderBuilder<P, S> {
207 Default::default()
208 }
209}
210
211impl<P, S> Layer<RequestParams> for StandardRequestBuilder<P, S>
212where
213 S: StorageProvider,
214 S::Error: std::error::Error + Send + Sync + 'static,
215 P: Provider<Ethereum> + 'static + Clone,
216{
217 type Output = ProofRequest;
218 type Error = anyhow::Error;
219
220 async fn process(&self, input: RequestParams) -> Result<ProofRequest, Self::Error> {
221 input
222 .process_with(&self.storage_layer)
223 .await?
224 .process_with(&self.preflight_layer)
225 .await?
226 .process_with(&self.requirements_layer)
227 .await?
228 .process_with(&self.request_id_layer)
229 .await?
230 .process_with(&self.offer_layer)
231 .await?
232 .process_with(&self.finalizer)
233 .await
234 }
235}
236
237impl<P> Layer<RequestParams> for StandardRequestBuilder<P, NotProvided>
238where
239 P: Provider<Ethereum> + 'static + Clone,
240{
241 type Output = ProofRequest;
242 type Error = anyhow::Error;
243
244 async fn process(&self, input: RequestParams) -> Result<ProofRequest, Self::Error> {
245 input
246 .process_with(&self.storage_layer)
247 .await?
248 .process_with(&self.preflight_layer)
249 .await?
250 .process_with(&self.requirements_layer)
251 .await?
252 .process_with(&self.request_id_layer)
253 .await?
254 .process_with(&self.offer_layer)
255 .await?
256 .process_with(&self.finalizer)
257 .await
258 }
259}
260
261#[non_exhaustive]
273#[derive(Clone, Default)]
274pub struct RequestParams {
275 pub program: Option<Cow<'static, [u8]>>,
277
278 pub env: Option<GuestEnv>,
281
282 pub program_url: Option<Url>,
284
285 pub request_input: Option<RequestInput>,
288
289 pub cycles: Option<u64>,
291
292 pub image_id: Option<Digest>,
294
295 pub journal: Option<Journal>,
297
298 pub request_id: Option<RequestId>,
300
301 pub offer: OfferParams,
303
304 pub requirements: RequirementParams,
306}
307
308impl RequestParams {
309 pub fn new() -> Self {
314 Self::default()
315 }
316
317 pub fn require_program(&self) -> Result<&[u8], MissingFieldError> {
322 self.program
323 .as_deref()
324 .ok_or(MissingFieldError::with_hint("program", "can be set using .with_program(...)"))
325 }
326
327 pub fn with_program(self, value: impl Into<Cow<'static, [u8]>>) -> Self {
329 Self { program: Some(value.into()), ..self }
330 }
331
332 pub fn require_env(&self) -> Result<&GuestEnv, MissingFieldError> {
336 self.env.as_ref().ok_or(MissingFieldError::with_hint(
337 "env",
338 "can be set using .with_env(...) or .with_stdin",
339 ))
340 }
341
342 pub fn with_env(self, value: impl Into<GuestEnv>) -> Self {
360 Self { env: Some(value.into()), ..self }
361 }
362
363 pub fn with_stdin(self, value: impl Into<Vec<u8>>) -> Self {
381 Self { env: Some(GuestEnv::from_stdin(value)), ..self }
382 }
383
384 pub fn require_program_url(&self) -> Result<&Url, MissingFieldError> {
388 self.program_url.as_ref().ok_or(MissingFieldError::with_hint(
389 "program_url",
390 "can be set using .with_program_url(...)",
391 ))
392 }
393
394 pub fn with_program_url<T: TryInto<Url>>(self, value: T) -> Result<Self, T::Error> {
405 Ok(Self { program_url: Some(value.try_into()?), ..self })
406 }
407
408 pub fn require_request_input(&self) -> Result<&RequestInput, MissingFieldError> {
412 self.request_input.as_ref().ok_or(MissingFieldError::with_hint(
413 "request_input",
414 "can be set using .with_request_input(...)",
415 ))
416 }
417
418 pub fn with_request_input(self, value: impl Into<RequestInput>) -> Self {
425 Self { request_input: Some(value.into()), ..self }
426 }
427
428 pub fn with_input_url<T: TryInto<Url>>(self, value: T) -> Result<Self, T::Error> {
441 Ok(Self { request_input: Some(RequestInput::url(value.try_into()?)), ..self })
442 }
443
444 pub fn require_cycles(&self) -> Result<u64, MissingFieldError> {
448 self.cycles
449 .ok_or(MissingFieldError::with_hint("cycles", "can be set using .with_cycles(...)"))
450 }
451
452 pub fn with_cycles(self, value: u64) -> Self {
456 Self { cycles: Some(value), ..self }
457 }
458
459 pub fn require_journal(&self) -> Result<&Journal, MissingFieldError> {
463 self.journal
464 .as_ref()
465 .ok_or(MissingFieldError::with_hint("journal", "can be set using .with_journal(...)"))
466 }
467
468 pub fn with_journal(self, value: impl Into<Journal>) -> Self {
473 Self { journal: Some(value.into()), ..self }
474 }
475
476 pub fn require_image_id(&self) -> Result<Digest, MissingFieldError> {
480 self.image_id.ok_or(MissingFieldError::with_hint(
481 "image_id",
482 "can be set using .with_image_id(...), and is calculated from the program",
483 ))
484 }
485
486 pub fn with_image_id(self, value: impl Into<Digest>) -> Self {
491 Self { image_id: Some(value.into()), ..self }
492 }
493
494 pub fn require_request_id(&self) -> Result<&RequestId, MissingFieldError> {
499 self.request_id.as_ref().ok_or(MissingFieldError::with_hint("request_id", "can be set using .with_request_id(...), and can be generated by boundless_market::Client"))
500 }
501
502 pub fn with_request_id(self, value: impl Into<RequestId>) -> Self {
507 Self { request_id: Some(value.into()), ..self }
508 }
509
510 pub fn with_offer(self, value: impl Into<OfferParams>) -> Self {
525 Self { offer: value.into(), ..self }
526 }
527
528 pub fn with_requirements(self, value: impl Into<RequirementParams>) -> Self {
540 Self { requirements: value.into(), ..self }
541 }
542
543 pub fn with_groth16_proof(self) -> Self {
548 let mut requirements = self.requirements;
549 requirements.selector = match crate::util::is_dev_mode() {
550 true => Some((Selector::FakeReceipt as u32).into()),
551 false => Some((Selector::groth16_latest() as u32).into()),
552 };
553 Self { requirements, ..self }
554 }
555}
556
557impl Debug for RequestParams {
558 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
560 f.debug_struct("ExampleRequestParams")
561 .field("program", &self.program.as_ref().map(|x| format!("[{} bytes]", x.len())))
562 .field("env", &self.env)
563 .field("program_url", &self.program_url)
564 .field("input", &self.request_input)
565 .field("cycles", &self.cycles)
566 .field("journal", &self.journal)
567 .field("request_id", &self.request_id)
568 .field("offer", &self.offer)
569 .field("requirements", &self.requirements)
570 .finish()
571 }
572}
573
574impl<Program, Env> From<(Program, Env)> for RequestParams
575where
576 Program: Into<Cow<'static, [u8]>>,
577 Env: Into<GuestEnv>,
578{
579 fn from(value: (Program, Env)) -> Self {
580 Self::default().with_program(value.0).with_env(value.1)
581 }
582}
583
584#[derive(Debug)]
589pub struct MissingFieldError {
590 pub label: Cow<'static, str>,
592 pub hint: Option<Cow<'static, str>>,
594}
595
596impl Display for MissingFieldError {
597 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
598 match self.hint {
599 None => write!(f, "field `{}` is required but is uninitialized", self.label),
600 Some(ref hint) => {
601 write!(f, "field `{}` is required but is uninitialized; {hint}", self.label)
602 }
603 }
604 }
605}
606
607impl std::error::Error for MissingFieldError {}
608
609impl MissingFieldError {
610 pub fn new(label: impl Into<Cow<'static, str>>) -> Self {
612 Self { label: label.into(), hint: None }
613 }
614
615 pub fn with_hint(
617 label: impl Into<Cow<'static, str>>,
618 hint: impl Into<Cow<'static, str>>,
619 ) -> Self {
620 Self { label: label.into(), hint: Some(hint.into()) }
621 }
622}
623
624#[cfg(test)]
625mod tests {
626 use std::sync::Arc;
627
628 use alloy::{
629 network::TransactionBuilder,
630 node_bindings::Anvil,
631 primitives::Address,
632 providers::{DynProvider, Provider},
633 rpc::types::TransactionRequest,
634 };
635 use boundless_market_test_utils::{create_test_ctx, ECHO_ELF};
636 use tracing_test::traced_test;
637 use url::Url;
638
639 use super::{
640 Layer, OfferLayer, OfferLayerConfig, OfferParams, PreflightLayer, RequestBuilder,
641 RequestId, RequestIdLayer, RequestIdLayerConfig, RequestIdLayerMode, RequestParams,
642 RequirementsLayer, StandardRequestBuilder, StorageLayer, StorageLayerConfig,
643 };
644
645 use crate::{
646 contracts::{
647 boundless_market::BoundlessMarketService, Predicate, RequestInput, RequestInputType,
648 Requirements,
649 },
650 input::GuestEnv,
651 storage::{fetch_url, MockStorageProvider, StorageProvider},
652 util::NotProvided,
653 StandardStorageProvider,
654 };
655 use alloy_primitives::U256;
656 use risc0_zkvm::{compute_image_id, sha::Digestible, Journal};
657
658 #[tokio::test]
659 #[traced_test]
660 async fn basic() -> anyhow::Result<()> {
661 let anvil = Anvil::new().spawn();
662 let test_ctx = create_test_ctx(&anvil).await.unwrap();
663 let storage = Arc::new(MockStorageProvider::start());
664 let market = BoundlessMarketService::new(
665 test_ctx.deployment.boundless_market_address,
666 test_ctx.customer_provider.clone(),
667 test_ctx.customer_signer.address(),
668 );
669
670 let request_builder = StandardRequestBuilder::builder()
671 .storage_layer(Some(storage))
672 .offer_layer(test_ctx.customer_provider.clone())
673 .request_id_layer(market)
674 .build()?;
675
676 let params = request_builder.params().with_program(ECHO_ELF).with_stdin(b"hello!");
677 let request = request_builder.build(params).await?;
678 println!("built request {request:#?}");
679 Ok(())
680 }
681
682 #[tokio::test]
683 #[traced_test]
684 async fn with_offer_layer_settings() -> anyhow::Result<()> {
685 let anvil = Anvil::new().spawn();
686 let test_ctx = create_test_ctx(&anvil).await.unwrap();
687 let storage = Arc::new(MockStorageProvider::start());
688 let market = BoundlessMarketService::new(
689 test_ctx.deployment.boundless_market_address,
690 test_ctx.customer_provider.clone(),
691 test_ctx.customer_signer.address(),
692 );
693
694 let request_builder = StandardRequestBuilder::builder()
695 .storage_layer(Some(storage))
696 .offer_layer(OfferLayer::new(
697 test_ctx.customer_provider.clone(),
698 OfferLayerConfig::builder().ramp_up_period(27).build()?,
699 ))
700 .request_id_layer(market)
701 .build()?;
702
703 let params = request_builder.params().with_program(ECHO_ELF).with_stdin(b"hello!");
704 let request = request_builder.build(params).await?;
705 assert_eq!(request.offer.rampUpPeriod, 27);
706 Ok(())
707 }
708
709 #[tokio::test]
710 #[traced_test]
711 async fn without_storage_provider() -> anyhow::Result<()> {
712 let anvil = Anvil::new().spawn();
713 let test_ctx = create_test_ctx(&anvil).await.unwrap();
714 let market = BoundlessMarketService::new(
715 test_ctx.deployment.boundless_market_address,
716 test_ctx.customer_provider.clone(),
717 test_ctx.customer_signer.address(),
718 );
719
720 let request_builder = StandardRequestBuilder::builder()
721 .storage_layer(None::<NotProvided>)
722 .offer_layer(test_ctx.customer_provider.clone())
723 .request_id_layer(market)
724 .build()?;
725
726 let params = request_builder.params().with_program(ECHO_ELF).with_stdin(b"hello!");
728 let err = request_builder.build(params).await.unwrap_err();
729 tracing::debug!("err: {err}");
730
731 let storage = Arc::new(MockStorageProvider::start());
733 let program_url = storage.upload_program(ECHO_ELF).await?;
734 let params = request_builder.params().with_program_url(program_url)?.with_stdin(b"hello!");
735 let request = request_builder.build(params).await?;
736 assert_eq!(
737 request.requirements.imageId,
738 risc0_zkvm::compute_image_id(ECHO_ELF)?.as_bytes()
739 );
740 Ok(())
741 }
742
743 #[tokio::test]
744 #[traced_test]
745 async fn test_storage_layer() -> anyhow::Result<()> {
746 let storage = Arc::new(MockStorageProvider::start());
747 let layer = StorageLayer::new(
748 Some(storage.clone()),
749 StorageLayerConfig::builder().inline_input_max_bytes(Some(1024)).build()?,
750 );
751 let env = GuestEnv::from_stdin(b"inline_data");
752 let (program_url, request_input) = layer.process((ECHO_ELF, &env)).await?;
753
754 assert_eq!(fetch_url(&program_url).await?, ECHO_ELF);
756 assert_eq!(request_input.inputType, RequestInputType::Inline);
757 assert_eq!(request_input.data, env.encode()?);
758 Ok(())
759 }
760
761 #[tokio::test]
762 #[traced_test]
763 async fn test_storage_layer_no_provider() -> anyhow::Result<()> {
764 let layer = StorageLayer::<NotProvided>::from(
765 StorageLayerConfig::builder().inline_input_max_bytes(Some(1024)).build()?,
766 );
767
768 let env = GuestEnv::from_stdin(b"inline_data");
769 let request_input = layer.process(&env).await?;
770
771 assert_eq!(request_input.inputType, RequestInputType::Inline);
773 assert_eq!(request_input.data, env.encode()?);
774 Ok(())
775 }
776
777 #[tokio::test]
778 #[traced_test]
779 async fn test_storage_layer_large_input() -> anyhow::Result<()> {
780 let storage = Arc::new(MockStorageProvider::start());
781 let layer = StorageLayer::new(
782 Some(storage.clone()),
783 StorageLayerConfig::builder().inline_input_max_bytes(Some(1024)).build()?,
784 );
785 let env = GuestEnv::from_stdin(rand::random_iter().take(2048).collect::<Vec<u8>>());
786 let (program_url, request_input) = layer.process((ECHO_ELF, &env)).await?;
787
788 assert_eq!(fetch_url(&program_url).await?, ECHO_ELF);
790 assert_eq!(request_input.inputType, RequestInputType::Url);
791 let fetched_input = fetch_url(String::from_utf8(request_input.data.to_vec())?).await?;
792 assert_eq!(fetched_input, env.encode()?);
793 Ok(())
794 }
795
796 #[tokio::test]
797 #[traced_test]
798 async fn test_storage_layer_large_input_no_provider() -> anyhow::Result<()> {
799 let layer = StorageLayer::from(
800 StorageLayerConfig::builder().inline_input_max_bytes(Some(1024)).build()?,
801 );
802
803 let env = GuestEnv::from_stdin(rand::random_iter().take(2048).collect::<Vec<u8>>());
804 let err = layer.process(&env).await.unwrap_err();
805
806 assert!(err
807 .to_string()
808 .contains("cannot upload input using StorageLayer with no storage_provider"));
809 Ok(())
810 }
811
812 #[tokio::test]
813 #[traced_test]
814 async fn test_preflight_layer() -> anyhow::Result<()> {
815 let storage = MockStorageProvider::start();
816 let program_url = storage.upload_program(ECHO_ELF).await?;
817 let layer = PreflightLayer::default();
818 let data = b"hello_zkvm".to_vec();
819 let env = GuestEnv::from_stdin(data.clone());
820 let input = RequestInput::inline(env.encode()?);
821 let session = layer.process((&program_url, &input)).await?;
822
823 assert_eq!(session.journal.as_ref(), data.as_slice());
824 let cycles: u64 = session.segments.iter().map(|s| 1 << s.po2).sum();
826 assert!(cycles > 0);
827 assert!(session.exit_code.is_ok());
828 Ok(())
829 }
830
831 #[tokio::test]
832 #[traced_test]
833 async fn test_requirements_layer() -> anyhow::Result<()> {
834 let layer = RequirementsLayer::default();
835 let program = ECHO_ELF;
836 let bytes = b"journal_data".to_vec();
837 let journal = Journal::new(bytes.clone());
838 let req = layer.process((program, &journal, &Default::default())).await?;
839
840 assert!(req.predicate.eval(&journal));
842 let other = Journal::new(b"other_data".to_vec());
844 assert!(!req.predicate.eval(&other));
845 Ok(())
846 }
847
848 #[tokio::test]
849 #[traced_test]
850 async fn test_request_id_layer_rand() -> anyhow::Result<()> {
851 let anvil = Anvil::new().spawn();
852 let test_ctx = create_test_ctx(&anvil).await?;
853 let market = BoundlessMarketService::new(
854 test_ctx.deployment.boundless_market_address,
855 test_ctx.customer_provider.clone(),
856 test_ctx.customer_signer.address(),
857 );
858 let layer = RequestIdLayer::from(market.clone());
859 assert_eq!(layer.config.mode, RequestIdLayerMode::Rand);
860 let id = layer.process(()).await?;
861 assert_eq!(id.addr, test_ctx.customer_signer.address());
862 assert!(!id.smart_contract_signed);
863 Ok(())
864 }
865
866 #[tokio::test]
867 #[traced_test]
868 async fn test_request_id_layer_nonce() -> anyhow::Result<()> {
869 let anvil = Anvil::new().spawn();
870 let test_ctx = create_test_ctx(&anvil).await?;
871 let market = BoundlessMarketService::new(
872 test_ctx.deployment.boundless_market_address,
873 test_ctx.customer_provider.clone(),
874 test_ctx.customer_signer.address(),
875 );
876 let layer = RequestIdLayer::new(
877 market.clone(),
878 RequestIdLayerConfig::builder().mode(RequestIdLayerMode::Nonce).build()?,
879 );
880
881 let id = layer.process(()).await?;
882 assert_eq!(id.addr, test_ctx.customer_signer.address());
883 assert_eq!(id.index, 0);
885 assert!(!id.smart_contract_signed);
886
887 let tx = TransactionRequest::default()
889 .with_from(test_ctx.customer_signer.address())
890 .with_to(Address::ZERO)
891 .with_value(U256::from(1));
892 test_ctx.customer_provider.send_transaction(tx).await?.watch().await?;
893
894 let id = layer.process(()).await?;
895 assert_eq!(id.addr, test_ctx.customer_signer.address());
896 assert_eq!(id.index, 1);
898 assert!(!id.smart_contract_signed);
899
900 Ok(())
901 }
902
903 #[tokio::test]
904 #[traced_test]
905 async fn test_offer_layer_estimates() -> anyhow::Result<()> {
906 let anvil = Anvil::new().spawn();
908 let test_ctx = create_test_ctx(&anvil).await?;
909 let provider = test_ctx.customer_provider.clone();
910 let layer = OfferLayer::from(provider.clone());
911 let image_id = compute_image_id(ECHO_ELF).unwrap();
913 let predicate = Predicate::digest_match(Journal::new(b"hello".to_vec()).digest());
914 let requirements = Requirements::new(image_id, predicate);
915 let request_id = RequestId::new(test_ctx.customer_signer.address(), 0);
916
917 let offer_params = OfferParams::default();
919 let offer_zero_mcycles =
920 layer.process((&requirements, &request_id, Some(0u64), &offer_params)).await?;
921 assert_eq!(offer_zero_mcycles.minPrice, U256::ZERO);
922 assert_eq!(offer_zero_mcycles.rampUpPeriod, 60);
924 assert_eq!(offer_zero_mcycles.lockTimeout, 600);
925 assert_eq!(offer_zero_mcycles.timeout, 1200);
926 assert!(offer_zero_mcycles.maxPrice > U256::ZERO);
928
929 let offer_more_mcycles =
931 layer.process((&requirements, &request_id, Some(100u64 << 20), &offer_params)).await?;
932 assert!(offer_more_mcycles.maxPrice > offer_zero_mcycles.maxPrice);
933
934 let min_price = U256::from(1u64);
936 let max_price = U256::from(5u64);
937 let offer_params = OfferParams::builder().max_price(max_price).min_price(min_price).into();
938 let offer_zero_mcycles =
939 layer.process((&requirements, &request_id, Some(0u64), &offer_params)).await?;
940 assert_eq!(offer_zero_mcycles.maxPrice, max_price);
941 assert_eq!(offer_zero_mcycles.minPrice, min_price);
942 assert_eq!(offer_zero_mcycles.rampUpPeriod, 60);
943 assert_eq!(offer_zero_mcycles.lockTimeout, 600);
944 assert_eq!(offer_zero_mcycles.timeout, 1200);
945 Ok(())
946 }
947
948 #[test]
949 fn request_params_with_program_url_infallible() {
950 let url = Url::parse("https://fileserver.example/guest.bin").unwrap();
954 RequestParams::new().with_program_url(url).inspect_err(|e| match *e {}).unwrap();
955 }
956
957 #[test]
958 fn request_params_with_input_url_infallible() {
959 let url = Url::parse("https://fileserver.example/input.bin").unwrap();
963 RequestParams::new().with_input_url(url).inspect_err(|e| match *e {}).unwrap();
964 }
965
966 #[test]
967 fn test_with_input_url() {
968 let params =
970 RequestParams::new().with_input_url("https://fileserver.example/input.bin").unwrap();
971
972 let input = params.request_input.unwrap();
973 assert_eq!(input.inputType, RequestInputType::Url);
974 assert_eq!(input.data.as_ref(), "https://fileserver.example/input.bin".as_bytes());
975
976 let url = Url::parse("https://fileserver.example/input2.bin").unwrap();
978 let params = RequestParams::new().with_input_url(url).unwrap();
979
980 let input = params.request_input.unwrap();
981 assert_eq!(input.inputType, RequestInputType::Url);
982 assert_eq!(input.data.as_ref(), "https://fileserver.example/input2.bin".as_bytes());
983 }
984
985 #[allow(dead_code)]
986 trait AssertSend: Send {}
987
988 impl AssertSend for StandardRequestBuilder<DynProvider, StandardStorageProvider> {}
990}