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;
551 requirements.selector = match risc0_zkvm::is_dev_mode() {
552 true => Some((Selector::FakeReceipt as u32).into()),
553 false => Some((Selector::Groth16V2_1 as u32).into()),
554 };
555 Self { requirements, ..self }
556 }
557}
558
559impl Debug for RequestParams {
560 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
562 f.debug_struct("ExampleRequestParams")
563 .field("program", &self.program.as_ref().map(|x| format!("[{} bytes]", x.len())))
564 .field("env", &self.env)
565 .field("program_url", &self.program_url)
566 .field("input", &self.request_input)
567 .field("cycles", &self.cycles)
568 .field("journal", &self.journal)
569 .field("request_id", &self.request_id)
570 .field("offer", &self.offer)
571 .field("requirements", &self.requirements)
572 .finish()
573 }
574}
575
576impl<Program, Env> From<(Program, Env)> for RequestParams
577where
578 Program: Into<Cow<'static, [u8]>>,
579 Env: Into<GuestEnv>,
580{
581 fn from(value: (Program, Env)) -> Self {
582 Self::default().with_program(value.0).with_env(value.1)
583 }
584}
585
586#[derive(Debug)]
591pub struct MissingFieldError {
592 pub label: Cow<'static, str>,
594 pub hint: Option<Cow<'static, str>>,
596}
597
598impl Display for MissingFieldError {
599 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
600 match self.hint {
601 None => write!(f, "field `{}` is required but is uninitialized", self.label),
602 Some(ref hint) => {
603 write!(f, "field `{}` is required but is uninitialized; {hint}", self.label)
604 }
605 }
606 }
607}
608
609impl std::error::Error for MissingFieldError {}
610
611impl MissingFieldError {
612 pub fn new(label: impl Into<Cow<'static, str>>) -> Self {
614 Self { label: label.into(), hint: None }
615 }
616
617 pub fn with_hint(
619 label: impl Into<Cow<'static, str>>,
620 hint: impl Into<Cow<'static, str>>,
621 ) -> Self {
622 Self { label: label.into(), hint: Some(hint.into()) }
623 }
624}
625
626#[cfg(test)]
627mod tests {
628 use std::sync::Arc;
629
630 use alloy::{
631 network::TransactionBuilder,
632 node_bindings::Anvil,
633 primitives::Address,
634 providers::{DynProvider, Provider},
635 rpc::types::TransactionRequest,
636 };
637 use boundless_market_test_utils::{create_test_ctx, ECHO_ELF};
638 use tracing_test::traced_test;
639 use url::Url;
640
641 use super::{
642 Layer, OfferLayer, OfferLayerConfig, OfferParams, PreflightLayer, RequestBuilder,
643 RequestId, RequestIdLayer, RequestIdLayerConfig, RequestIdLayerMode, RequestParams,
644 RequirementsLayer, StandardRequestBuilder, StorageLayer, StorageLayerConfig,
645 };
646
647 use crate::{
648 contracts::{
649 boundless_market::BoundlessMarketService, Predicate, RequestInput, RequestInputType,
650 Requirements,
651 },
652 input::GuestEnv,
653 storage::{fetch_url, MockStorageProvider, StorageProvider},
654 util::NotProvided,
655 StandardStorageProvider,
656 };
657 use alloy_primitives::U256;
658 use risc0_zkvm::{compute_image_id, sha::Digestible, Journal};
659
660 #[tokio::test]
661 #[traced_test]
662 async fn basic() -> anyhow::Result<()> {
663 let anvil = Anvil::new().spawn();
664 let test_ctx = create_test_ctx(&anvil).await.unwrap();
665 let storage = Arc::new(MockStorageProvider::start());
666 let market = BoundlessMarketService::new(
667 test_ctx.deployment.boundless_market_address,
668 test_ctx.customer_provider.clone(),
669 test_ctx.customer_signer.address(),
670 );
671
672 let request_builder = StandardRequestBuilder::builder()
673 .storage_layer(Some(storage))
674 .offer_layer(test_ctx.customer_provider.clone())
675 .request_id_layer(market)
676 .build()?;
677
678 let params = request_builder.params().with_program(ECHO_ELF).with_stdin(b"hello!");
679 let request = request_builder.build(params).await?;
680 println!("built request {request:#?}");
681 Ok(())
682 }
683
684 #[tokio::test]
685 #[traced_test]
686 async fn with_offer_layer_settings() -> anyhow::Result<()> {
687 let anvil = Anvil::new().spawn();
688 let test_ctx = create_test_ctx(&anvil).await.unwrap();
689 let storage = Arc::new(MockStorageProvider::start());
690 let market = BoundlessMarketService::new(
691 test_ctx.deployment.boundless_market_address,
692 test_ctx.customer_provider.clone(),
693 test_ctx.customer_signer.address(),
694 );
695
696 let request_builder = StandardRequestBuilder::builder()
697 .storage_layer(Some(storage))
698 .offer_layer(OfferLayer::new(
699 test_ctx.customer_provider.clone(),
700 OfferLayerConfig::builder().ramp_up_period(27).build()?,
701 ))
702 .request_id_layer(market)
703 .build()?;
704
705 let params = request_builder.params().with_program(ECHO_ELF).with_stdin(b"hello!");
706 let request = request_builder.build(params).await?;
707 assert_eq!(request.offer.rampUpPeriod, 27);
708 Ok(())
709 }
710
711 #[tokio::test]
712 #[traced_test]
713 async fn without_storage_provider() -> anyhow::Result<()> {
714 let anvil = Anvil::new().spawn();
715 let test_ctx = create_test_ctx(&anvil).await.unwrap();
716 let market = BoundlessMarketService::new(
717 test_ctx.deployment.boundless_market_address,
718 test_ctx.customer_provider.clone(),
719 test_ctx.customer_signer.address(),
720 );
721
722 let request_builder = StandardRequestBuilder::builder()
723 .storage_layer(None::<NotProvided>)
724 .offer_layer(test_ctx.customer_provider.clone())
725 .request_id_layer(market)
726 .build()?;
727
728 let params = request_builder.params().with_program(ECHO_ELF).with_stdin(b"hello!");
730 let err = request_builder.build(params).await.unwrap_err();
731 tracing::debug!("err: {err}");
732
733 let storage = Arc::new(MockStorageProvider::start());
735 let program_url = storage.upload_program(ECHO_ELF).await?;
736 let params = request_builder.params().with_program_url(program_url)?.with_stdin(b"hello!");
737 let request = request_builder.build(params).await?;
738 assert_eq!(
739 request.requirements.imageId,
740 risc0_zkvm::compute_image_id(ECHO_ELF)?.as_bytes()
741 );
742 Ok(())
743 }
744
745 #[tokio::test]
746 #[traced_test]
747 async fn test_storage_layer() -> anyhow::Result<()> {
748 let storage = Arc::new(MockStorageProvider::start());
749 let layer = StorageLayer::new(
750 Some(storage.clone()),
751 StorageLayerConfig::builder().inline_input_max_bytes(Some(1024)).build()?,
752 );
753 let env = GuestEnv::from_stdin(b"inline_data");
754 let (program_url, request_input) = layer.process((ECHO_ELF, &env)).await?;
755
756 assert_eq!(fetch_url(&program_url).await?, ECHO_ELF);
758 assert_eq!(request_input.inputType, RequestInputType::Inline);
759 assert_eq!(request_input.data, env.encode()?);
760 Ok(())
761 }
762
763 #[tokio::test]
764 #[traced_test]
765 async fn test_storage_layer_no_provider() -> anyhow::Result<()> {
766 let layer = StorageLayer::<NotProvided>::from(
767 StorageLayerConfig::builder().inline_input_max_bytes(Some(1024)).build()?,
768 );
769
770 let env = GuestEnv::from_stdin(b"inline_data");
771 let request_input = layer.process(&env).await?;
772
773 assert_eq!(request_input.inputType, RequestInputType::Inline);
775 assert_eq!(request_input.data, env.encode()?);
776 Ok(())
777 }
778
779 #[tokio::test]
780 #[traced_test]
781 async fn test_storage_layer_large_input() -> anyhow::Result<()> {
782 let storage = Arc::new(MockStorageProvider::start());
783 let layer = StorageLayer::new(
784 Some(storage.clone()),
785 StorageLayerConfig::builder().inline_input_max_bytes(Some(1024)).build()?,
786 );
787 let env = GuestEnv::from_stdin(rand::random_iter().take(2048).collect::<Vec<u8>>());
788 let (program_url, request_input) = layer.process((ECHO_ELF, &env)).await?;
789
790 assert_eq!(fetch_url(&program_url).await?, ECHO_ELF);
792 assert_eq!(request_input.inputType, RequestInputType::Url);
793 let fetched_input = fetch_url(String::from_utf8(request_input.data.to_vec())?).await?;
794 assert_eq!(fetched_input, env.encode()?);
795 Ok(())
796 }
797
798 #[tokio::test]
799 #[traced_test]
800 async fn test_storage_layer_large_input_no_provider() -> anyhow::Result<()> {
801 let layer = StorageLayer::from(
802 StorageLayerConfig::builder().inline_input_max_bytes(Some(1024)).build()?,
803 );
804
805 let env = GuestEnv::from_stdin(rand::random_iter().take(2048).collect::<Vec<u8>>());
806 let err = layer.process(&env).await.unwrap_err();
807
808 assert!(err
809 .to_string()
810 .contains("cannot upload input using StorageLayer with no storage_provider"));
811 Ok(())
812 }
813
814 #[tokio::test]
815 #[traced_test]
816 async fn test_preflight_layer() -> anyhow::Result<()> {
817 let storage = MockStorageProvider::start();
818 let program_url = storage.upload_program(ECHO_ELF).await?;
819 let layer = PreflightLayer::default();
820 let data = b"hello_zkvm".to_vec();
821 let env = GuestEnv::from_stdin(data.clone());
822 let input = RequestInput::inline(env.encode()?);
823 let session = layer.process((&program_url, &input)).await?;
824
825 assert_eq!(session.journal.as_ref(), data.as_slice());
826 let cycles: u64 = session.segments.iter().map(|s| 1 << s.po2).sum();
828 assert!(cycles > 0);
829 assert!(session.exit_code.is_ok());
830 Ok(())
831 }
832
833 #[tokio::test]
834 #[traced_test]
835 async fn test_requirements_layer() -> anyhow::Result<()> {
836 let layer = RequirementsLayer::default();
837 let program = ECHO_ELF;
838 let bytes = b"journal_data".to_vec();
839 let journal = Journal::new(bytes.clone());
840 let req = layer.process((program, &journal, &Default::default())).await?;
841
842 assert!(req.predicate.eval(&journal));
844 let other = Journal::new(b"other_data".to_vec());
846 assert!(!req.predicate.eval(&other));
847 Ok(())
848 }
849
850 #[tokio::test]
851 #[traced_test]
852 async fn test_request_id_layer_rand() -> anyhow::Result<()> {
853 let anvil = Anvil::new().spawn();
854 let test_ctx = create_test_ctx(&anvil).await?;
855 let market = BoundlessMarketService::new(
856 test_ctx.deployment.boundless_market_address,
857 test_ctx.customer_provider.clone(),
858 test_ctx.customer_signer.address(),
859 );
860 let layer = RequestIdLayer::from(market.clone());
861 assert_eq!(layer.config.mode, RequestIdLayerMode::Rand);
862 let id = layer.process(()).await?;
863 assert_eq!(id.addr, test_ctx.customer_signer.address());
864 assert!(!id.smart_contract_signed);
865 Ok(())
866 }
867
868 #[tokio::test]
869 #[traced_test]
870 async fn test_request_id_layer_nonce() -> anyhow::Result<()> {
871 let anvil = Anvil::new().spawn();
872 let test_ctx = create_test_ctx(&anvil).await?;
873 let market = BoundlessMarketService::new(
874 test_ctx.deployment.boundless_market_address,
875 test_ctx.customer_provider.clone(),
876 test_ctx.customer_signer.address(),
877 );
878 let layer = RequestIdLayer::new(
879 market.clone(),
880 RequestIdLayerConfig::builder().mode(RequestIdLayerMode::Nonce).build()?,
881 );
882
883 let id = layer.process(()).await?;
884 assert_eq!(id.addr, test_ctx.customer_signer.address());
885 assert_eq!(id.index, 0);
887 assert!(!id.smart_contract_signed);
888
889 let tx = TransactionRequest::default()
891 .with_from(test_ctx.customer_signer.address())
892 .with_to(Address::ZERO)
893 .with_value(U256::from(1));
894 test_ctx.customer_provider.send_transaction(tx).await?.watch().await?;
895
896 let id = layer.process(()).await?;
897 assert_eq!(id.addr, test_ctx.customer_signer.address());
898 assert_eq!(id.index, 1);
900 assert!(!id.smart_contract_signed);
901
902 Ok(())
903 }
904
905 #[tokio::test]
906 #[traced_test]
907 async fn test_offer_layer_estimates() -> anyhow::Result<()> {
908 let anvil = Anvil::new().spawn();
910 let test_ctx = create_test_ctx(&anvil).await?;
911 let provider = test_ctx.customer_provider.clone();
912 let layer = OfferLayer::from(provider.clone());
913 let image_id = compute_image_id(ECHO_ELF).unwrap();
915 let predicate = Predicate::digest_match(Journal::new(b"hello".to_vec()).digest());
916 let requirements = Requirements::new(image_id, predicate);
917 let request_id = RequestId::new(test_ctx.customer_signer.address(), 0);
918
919 let offer_params = OfferParams::default();
921 let offer_zero_mcycles =
922 layer.process((&requirements, &request_id, Some(0u64), &offer_params)).await?;
923 assert_eq!(offer_zero_mcycles.minPrice, U256::ZERO);
924 assert_eq!(offer_zero_mcycles.rampUpPeriod, 60);
926 assert_eq!(offer_zero_mcycles.lockTimeout, 600);
927 assert_eq!(offer_zero_mcycles.timeout, 1200);
928 assert!(offer_zero_mcycles.maxPrice > U256::ZERO);
930
931 let offer_more_mcycles =
933 layer.process((&requirements, &request_id, Some(100u64 << 20), &offer_params)).await?;
934 assert!(offer_more_mcycles.maxPrice > offer_zero_mcycles.maxPrice);
935
936 let min_price = U256::from(1u64);
938 let max_price = U256::from(5u64);
939 let offer_params = OfferParams::builder().max_price(max_price).min_price(min_price).into();
940 let offer_zero_mcycles =
941 layer.process((&requirements, &request_id, Some(0u64), &offer_params)).await?;
942 assert_eq!(offer_zero_mcycles.maxPrice, max_price);
943 assert_eq!(offer_zero_mcycles.minPrice, min_price);
944 assert_eq!(offer_zero_mcycles.rampUpPeriod, 60);
945 assert_eq!(offer_zero_mcycles.lockTimeout, 600);
946 assert_eq!(offer_zero_mcycles.timeout, 1200);
947 Ok(())
948 }
949
950 #[test]
951 fn request_params_with_program_url_infallible() {
952 let url = Url::parse("https://fileserver.example/guest.bin").unwrap();
956 RequestParams::new().with_program_url(url).inspect_err(|e| match *e {}).unwrap();
957 }
958
959 #[test]
960 fn request_params_with_input_url_infallible() {
961 let url = Url::parse("https://fileserver.example/input.bin").unwrap();
965 RequestParams::new().with_input_url(url).inspect_err(|e| match *e {}).unwrap();
966 }
967
968 #[test]
969 fn test_with_input_url() {
970 let params =
972 RequestParams::new().with_input_url("https://fileserver.example/input.bin").unwrap();
973
974 let input = params.request_input.unwrap();
975 assert_eq!(input.inputType, RequestInputType::Url);
976 assert_eq!(input.data.as_ref(), "https://fileserver.example/input.bin".as_bytes());
977
978 let url = Url::parse("https://fileserver.example/input2.bin").unwrap();
980 let params = RequestParams::new().with_input_url(url).unwrap();
981
982 let input = params.request_input.unwrap();
983 assert_eq!(input.inputType, RequestInputType::Url);
984 assert_eq!(input.data.as_ref(), "https://fileserver.example/input2.bin".as_bytes());
985 }
986
987 #[allow(dead_code)]
988 trait AssertSend: Send {}
989
990 impl AssertSend for StandardRequestBuilder<DynProvider, StandardStorageProvider> {}
992}