1use std::{
16 borrow::Cow,
17 fmt,
18 fmt::{Debug, Display},
19 future::Future,
20};
21
22use alloy::{network::Ethereum, providers::Provider};
23use derive_builder::Builder;
24use risc0_ethereum_contracts::selector::Selector;
25use risc0_zkvm::{Digest, Journal};
26use url::Url;
27
28use crate::{
29 contracts::{ProofRequest, RequestId, RequestInput},
30 input::GuestEnv,
31 storage::{StandardStorageProvider, StorageProvider},
32 util::{NotProvided, StandardRpcProvider},
33};
34mod preflight_layer;
35mod storage_layer;
36
37pub use preflight_layer::PreflightLayer;
38pub use storage_layer::{StorageLayer, StorageLayerConfig, StorageLayerConfigBuilder};
39mod requirements_layer;
40pub use requirements_layer::{RequirementParams, RequirementsLayer};
41mod request_id_layer;
42pub use request_id_layer::{
43 RequestIdLayer, RequestIdLayerConfig, RequestIdLayerConfigBuilder, RequestIdLayerMode,
44};
45mod offer_layer;
46pub use offer_layer::{
47 OfferLayer, OfferLayerConfig, OfferLayerConfigBuilder, OfferParams, OfferParamsBuilder,
48};
49mod finalizer;
50pub use finalizer::{Finalizer, FinalizerConfig, FinalizerConfigBuilder};
51
52pub trait RequestBuilder<Params> {
56 type Error;
58
59 fn params(&self) -> Params
64 where
65 Params: Default,
66 {
67 Default::default()
68 }
69
70 fn build(
72 &self,
73 params: impl Into<Params>,
74 ) -> impl Future<Output = Result<ProofRequest, Self::Error>>;
75}
76
77impl<L, Params> RequestBuilder<Params> for L
82where
83 L: Layer<Params, Output = ProofRequest>,
84{
85 type Error = L::Error;
86
87 async fn build(&self, params: impl Into<Params>) -> Result<ProofRequest, Self::Error> {
88 self.process(params.into()).await
89 }
90}
91
92pub trait Layer<Input> {
98 type Output;
100
101 type Error;
103
104 fn process(&self, input: Input) -> impl Future<Output = Result<Self::Output, Self::Error>>;
106}
107
108pub trait Adapt<L> {
117 type Output;
119
120 type Error;
122
123 fn process_with(self, layer: &L) -> impl Future<Output = Result<Self::Output, Self::Error>>;
125}
126
127impl<L, I> Adapt<L> for I
128where
129 L: Layer<I>,
130{
131 type Output = L::Output;
132 type Error = L::Error;
133
134 async fn process_with(self, layer: &L) -> Result<Self::Output, Self::Error> {
135 layer.process(self).await
136 }
137}
138
139impl<A, B, Input> Layer<Input> for (A, B)
141where
142 Input: Adapt<A>,
143 <Input as Adapt<A>>::Output: Adapt<B>,
144 <Input as Adapt<A>>::Error: Into<<<Input as Adapt<A>>::Output as Adapt<B>>::Error>,
145{
146 type Output = <<Input as Adapt<A>>::Output as Adapt<B>>::Output;
147 type Error = <<Input as Adapt<A>>::Output as Adapt<B>>::Error;
148
149 async fn process(&self, input: Input) -> Result<Self::Output, Self::Error> {
150 input.process_with(&self.0).await.map_err(Into::into)?.process_with(&self.1).await
151 }
152}
153
154#[derive(Clone, Builder)]
167#[non_exhaustive]
168pub struct StandardRequestBuilder<P = StandardRpcProvider, S = StandardStorageProvider> {
169 #[builder(setter(into))]
171 pub storage_layer: StorageLayer<S>,
172
173 #[builder(setter(into), default)]
175 pub preflight_layer: PreflightLayer,
176
177 #[builder(setter(into), default)]
179 pub requirements_layer: RequirementsLayer,
180
181 #[builder(setter(into))]
183 pub request_id_layer: RequestIdLayer<P>,
184
185 #[builder(setter(into))]
187 pub offer_layer: OfferLayer<P>,
188
189 #[builder(setter(into), default)]
191 pub finalizer: Finalizer,
192}
193
194impl StandardRequestBuilder<NotProvided, NotProvided> {
195 pub fn builder<P: Clone, S: Clone>() -> StandardRequestBuilderBuilder<P, S> {
204 Default::default()
205 }
206}
207
208impl<P, S> Layer<RequestParams> for StandardRequestBuilder<P, S>
209where
210 S: StorageProvider,
211 S::Error: std::error::Error + Send + Sync + 'static,
212 P: Provider<Ethereum> + 'static + Clone,
213{
214 type Output = ProofRequest;
215 type Error = anyhow::Error;
216
217 async fn process(&self, input: RequestParams) -> Result<ProofRequest, Self::Error> {
218 input
219 .process_with(&self.storage_layer)
220 .await?
221 .process_with(&self.preflight_layer)
222 .await?
223 .process_with(&self.requirements_layer)
224 .await?
225 .process_with(&self.request_id_layer)
226 .await?
227 .process_with(&self.offer_layer)
228 .await?
229 .process_with(&self.finalizer)
230 .await
231 }
232}
233
234impl<P> Layer<RequestParams> for StandardRequestBuilder<P, NotProvided>
235where
236 P: Provider<Ethereum> + 'static + Clone,
237{
238 type Output = ProofRequest;
239 type Error = anyhow::Error;
240
241 async fn process(&self, input: RequestParams) -> Result<ProofRequest, Self::Error> {
242 input
243 .process_with(&self.storage_layer)
244 .await?
245 .process_with(&self.preflight_layer)
246 .await?
247 .process_with(&self.requirements_layer)
248 .await?
249 .process_with(&self.request_id_layer)
250 .await?
251 .process_with(&self.offer_layer)
252 .await?
253 .process_with(&self.finalizer)
254 .await
255 }
256}
257
258#[non_exhaustive]
270#[derive(Clone, Default)]
271pub struct RequestParams {
272 pub program: Option<Cow<'static, [u8]>>,
274
275 pub env: Option<GuestEnv>,
278
279 pub program_url: Option<Url>,
281
282 pub request_input: Option<RequestInput>,
285
286 pub cycles: Option<u64>,
288
289 pub image_id: Option<Digest>,
291
292 pub journal: Option<Journal>,
294
295 pub request_id: Option<RequestId>,
297
298 pub offer: OfferParams,
300
301 pub requirements: RequirementParams,
303}
304
305impl RequestParams {
306 pub fn new() -> Self {
311 Self::default()
312 }
313
314 pub fn require_program(&self) -> Result<&[u8], MissingFieldError> {
319 self.program
320 .as_deref()
321 .ok_or(MissingFieldError::with_hint("program", "can be set using .with_program(...)"))
322 }
323
324 pub fn with_program(self, value: impl Into<Cow<'static, [u8]>>) -> Self {
326 Self { program: Some(value.into()), ..self }
327 }
328
329 pub fn require_env(&self) -> Result<&GuestEnv, MissingFieldError> {
333 self.env.as_ref().ok_or(MissingFieldError::with_hint(
334 "env",
335 "can be set using .with_env(...) or .with_stdin",
336 ))
337 }
338
339 pub fn with_env(self, value: impl Into<GuestEnv>) -> Self {
357 Self { env: Some(value.into()), ..self }
358 }
359
360 pub fn with_stdin(self, value: impl Into<Vec<u8>>) -> Self {
378 Self { env: Some(GuestEnv::from_stdin(value)), ..self }
379 }
380
381 pub fn require_program_url(&self) -> Result<&Url, MissingFieldError> {
385 self.program_url.as_ref().ok_or(MissingFieldError::with_hint(
386 "program_url",
387 "can be set using .with_program_url(...)",
388 ))
389 }
390
391 pub fn with_program_url<T: TryInto<Url>>(self, value: T) -> Result<Self, T::Error> {
402 Ok(Self { program_url: Some(value.try_into()?), ..self })
403 }
404
405 pub fn require_request_input(&self) -> Result<&RequestInput, MissingFieldError> {
409 self.request_input.as_ref().ok_or(MissingFieldError::with_hint(
410 "request_input",
411 "can be set using .with_request_input(...)",
412 ))
413 }
414
415 pub fn with_request_input(self, value: impl Into<RequestInput>) -> Self {
422 Self { request_input: Some(value.into()), ..self }
423 }
424
425 pub fn with_input_url<T: TryInto<Url>>(self, value: T) -> Result<Self, T::Error> {
438 Ok(Self { request_input: Some(RequestInput::url(value.try_into()?)), ..self })
439 }
440
441 pub fn require_cycles(&self) -> Result<u64, MissingFieldError> {
445 self.cycles
446 .ok_or(MissingFieldError::with_hint("cycles", "can be set using .with_cycles(...)"))
447 }
448
449 pub fn with_cycles(self, value: u64) -> Self {
453 Self { cycles: Some(value), ..self }
454 }
455
456 pub fn require_journal(&self) -> Result<&Journal, MissingFieldError> {
460 self.journal
461 .as_ref()
462 .ok_or(MissingFieldError::with_hint("journal", "can be set using .with_journal(...)"))
463 }
464
465 pub fn with_journal(self, value: impl Into<Journal>) -> Self {
470 Self { journal: Some(value.into()), ..self }
471 }
472
473 pub fn require_image_id(&self) -> Result<Digest, MissingFieldError> {
477 self.image_id.ok_or(MissingFieldError::with_hint(
478 "image_id",
479 "can be set using .with_image_id(...), and is calculated from the program",
480 ))
481 }
482
483 pub fn with_image_id(self, value: impl Into<Digest>) -> Self {
488 Self { image_id: Some(value.into()), ..self }
489 }
490
491 pub fn require_request_id(&self) -> Result<&RequestId, MissingFieldError> {
496 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"))
497 }
498
499 pub fn with_request_id(self, value: impl Into<RequestId>) -> Self {
504 Self { request_id: Some(value.into()), ..self }
505 }
506
507 pub fn with_offer(self, value: impl Into<OfferParams>) -> Self {
522 Self { offer: value.into(), ..self }
523 }
524
525 pub fn with_requirements(self, value: impl Into<RequirementParams>) -> Self {
537 Self { requirements: value.into(), ..self }
538 }
539
540 pub fn with_groth16_proof(self) -> Self {
545 let mut requirements = self.requirements;
548 requirements.selector = match risc0_zkvm::is_dev_mode() {
549 true => Some((Selector::FakeReceipt as u32).into()),
550 false => Some((Selector::Groth16V2_0 as u32).into()),
551 };
552 Self { requirements, ..self }
553 }
554}
555
556impl Debug for RequestParams {
557 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
559 f.debug_struct("ExampleRequestParams")
560 .field("program", &self.program.as_ref().map(|x| format!("[{} bytes]", x.len())))
561 .field("env", &self.env)
562 .field("program_url", &self.program_url)
563 .field("input", &self.request_input)
564 .field("cycles", &self.cycles)
565 .field("journal", &self.journal)
566 .field("request_id", &self.request_id)
567 .field("offer", &self.offer)
568 .field("requirements", &self.requirements)
569 .finish()
570 }
571}
572
573impl<Program, Env> From<(Program, Env)> for RequestParams
574where
575 Program: Into<Cow<'static, [u8]>>,
576 Env: Into<GuestEnv>,
577{
578 fn from(value: (Program, Env)) -> Self {
579 Self::default().with_program(value.0).with_env(value.1)
580 }
581}
582
583#[derive(Debug)]
588pub struct MissingFieldError {
589 pub label: Cow<'static, str>,
591 pub hint: Option<Cow<'static, str>>,
593}
594
595impl Display for MissingFieldError {
596 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
597 match self.hint {
598 None => write!(f, "field `{}` is required but is uninitialized", self.label),
599 Some(ref hint) => {
600 write!(f, "field `{}` is required but is uninitialized; {hint}", self.label)
601 }
602 }
603 }
604}
605
606impl std::error::Error for MissingFieldError {}
607
608impl MissingFieldError {
609 pub fn new(label: impl Into<Cow<'static, str>>) -> Self {
611 Self { label: label.into(), hint: None }
612 }
613
614 pub fn with_hint(
616 label: impl Into<Cow<'static, str>>,
617 hint: impl Into<Cow<'static, str>>,
618 ) -> Self {
619 Self { label: label.into(), hint: Some(hint.into()) }
620 }
621}
622
623#[cfg(test)]
624mod tests {
625 use std::sync::Arc;
626
627 use alloy::{
628 network::TransactionBuilder, node_bindings::Anvil, primitives::Address,
629 providers::Provider, rpc::types::TransactionRequest,
630 };
631 use boundless_market_test_utils::{create_test_ctx, ECHO_ELF};
632 use tracing_test::traced_test;
633 use url::Url;
634
635 use super::{
636 Layer, OfferLayer, OfferLayerConfig, OfferParams, PreflightLayer, RequestBuilder,
637 RequestId, RequestIdLayer, RequestIdLayerConfig, RequestIdLayerMode, RequestParams,
638 RequirementsLayer, StandardRequestBuilder, StorageLayer, StorageLayerConfig,
639 };
640
641 use crate::{
642 contracts::{
643 boundless_market::BoundlessMarketService, Predicate, RequestInput, RequestInputType,
644 Requirements,
645 },
646 input::GuestEnv,
647 storage::{fetch_url, MockStorageProvider, StorageProvider},
648 util::NotProvided,
649 StandardRpcProvider, StandardStorageProvider,
650 };
651 use alloy_primitives::U256;
652 use risc0_zkvm::{compute_image_id, sha::Digestible, Journal};
653
654 #[tokio::test]
655 #[traced_test]
656 async fn basic() -> anyhow::Result<()> {
657 let anvil = Anvil::new().spawn();
658 let test_ctx = create_test_ctx(&anvil).await.unwrap();
659 let storage = Arc::new(MockStorageProvider::start());
660 let market = BoundlessMarketService::new(
661 test_ctx.deployment.boundless_market_address,
662 test_ctx.customer_provider.clone(),
663 test_ctx.customer_signer.address(),
664 );
665
666 let request_builder = StandardRequestBuilder::builder()
667 .storage_layer(Some(storage))
668 .offer_layer(test_ctx.customer_provider.clone())
669 .request_id_layer(market)
670 .build()?;
671
672 let params = request_builder.params().with_program(ECHO_ELF).with_stdin(b"hello!");
673 let request = request_builder.build(params).await?;
674 println!("built request {request:#?}");
675 Ok(())
676 }
677
678 #[tokio::test]
679 #[traced_test]
680 async fn with_offer_layer_settings() -> anyhow::Result<()> {
681 let anvil = Anvil::new().spawn();
682 let test_ctx = create_test_ctx(&anvil).await.unwrap();
683 let storage = Arc::new(MockStorageProvider::start());
684 let market = BoundlessMarketService::new(
685 test_ctx.deployment.boundless_market_address,
686 test_ctx.customer_provider.clone(),
687 test_ctx.customer_signer.address(),
688 );
689
690 let request_builder = StandardRequestBuilder::builder()
691 .storage_layer(Some(storage))
692 .offer_layer(OfferLayer::new(
693 test_ctx.customer_provider.clone(),
694 OfferLayerConfig::builder().ramp_up_period(27).build()?,
695 ))
696 .request_id_layer(market)
697 .build()?;
698
699 let params = request_builder.params().with_program(ECHO_ELF).with_stdin(b"hello!");
700 let request = request_builder.build(params).await?;
701 assert_eq!(request.offer.rampUpPeriod, 27);
702 Ok(())
703 }
704
705 #[tokio::test]
706 #[traced_test]
707 async fn without_storage_provider() -> anyhow::Result<()> {
708 let anvil = Anvil::new().spawn();
709 let test_ctx = create_test_ctx(&anvil).await.unwrap();
710 let market = BoundlessMarketService::new(
711 test_ctx.deployment.boundless_market_address,
712 test_ctx.customer_provider.clone(),
713 test_ctx.customer_signer.address(),
714 );
715
716 let request_builder = StandardRequestBuilder::builder()
717 .storage_layer(None::<NotProvided>)
718 .offer_layer(test_ctx.customer_provider.clone())
719 .request_id_layer(market)
720 .build()?;
721
722 let params = request_builder.params().with_program(ECHO_ELF).with_stdin(b"hello!");
724 let err = request_builder.build(params).await.unwrap_err();
725 tracing::debug!("err: {err}");
726
727 let storage = Arc::new(MockStorageProvider::start());
729 let program_url = storage.upload_program(ECHO_ELF).await?;
730 let params = request_builder.params().with_program_url(program_url)?.with_stdin(b"hello!");
731 let request = request_builder.build(params).await?;
732 assert_eq!(
733 request.requirements.imageId,
734 risc0_zkvm::compute_image_id(ECHO_ELF)?.as_bytes()
735 );
736 Ok(())
737 }
738
739 #[tokio::test]
740 #[traced_test]
741 async fn test_storage_layer() -> anyhow::Result<()> {
742 let storage = Arc::new(MockStorageProvider::start());
743 let layer = StorageLayer::new(
744 Some(storage.clone()),
745 StorageLayerConfig::builder().inline_input_max_bytes(Some(1024)).build()?,
746 );
747 let env = GuestEnv::from_stdin(b"inline_data");
748 let (program_url, request_input) = layer.process((ECHO_ELF, &env)).await?;
749
750 assert_eq!(fetch_url(&program_url).await?, ECHO_ELF);
752 assert_eq!(request_input.inputType, RequestInputType::Inline);
753 assert_eq!(request_input.data, env.encode()?);
754 Ok(())
755 }
756
757 #[tokio::test]
758 #[traced_test]
759 async fn test_storage_layer_no_provider() -> anyhow::Result<()> {
760 let layer = StorageLayer::<NotProvided>::from(
761 StorageLayerConfig::builder().inline_input_max_bytes(Some(1024)).build()?,
762 );
763
764 let env = GuestEnv::from_stdin(b"inline_data");
765 let request_input = layer.process(&env).await?;
766
767 assert_eq!(request_input.inputType, RequestInputType::Inline);
769 assert_eq!(request_input.data, env.encode()?);
770 Ok(())
771 }
772
773 #[tokio::test]
774 #[traced_test]
775 async fn test_storage_layer_large_input() -> anyhow::Result<()> {
776 let storage = Arc::new(MockStorageProvider::start());
777 let layer = StorageLayer::new(
778 Some(storage.clone()),
779 StorageLayerConfig::builder().inline_input_max_bytes(Some(1024)).build()?,
780 );
781 let env = GuestEnv::from_stdin(rand::random_iter().take(2048).collect::<Vec<u8>>());
782 let (program_url, request_input) = layer.process((ECHO_ELF, &env)).await?;
783
784 assert_eq!(fetch_url(&program_url).await?, ECHO_ELF);
786 assert_eq!(request_input.inputType, RequestInputType::Url);
787 let fetched_input = fetch_url(String::from_utf8(request_input.data.to_vec())?).await?;
788 assert_eq!(fetched_input, env.encode()?);
789 Ok(())
790 }
791
792 #[tokio::test]
793 #[traced_test]
794 async fn test_storage_layer_large_input_no_provider() -> anyhow::Result<()> {
795 let layer = StorageLayer::from(
796 StorageLayerConfig::builder().inline_input_max_bytes(Some(1024)).build()?,
797 );
798
799 let env = GuestEnv::from_stdin(rand::random_iter().take(2048).collect::<Vec<u8>>());
800 let err = layer.process(&env).await.unwrap_err();
801
802 assert!(err
803 .to_string()
804 .contains("cannot upload input using StorageLayer with no storage_provider"));
805 Ok(())
806 }
807
808 #[tokio::test]
809 #[traced_test]
810 async fn test_preflight_layer() -> anyhow::Result<()> {
811 let storage = MockStorageProvider::start();
812 let program_url = storage.upload_program(ECHO_ELF).await?;
813 let layer = PreflightLayer::default();
814 let data = b"hello_zkvm".to_vec();
815 let env = GuestEnv::from_stdin(data.clone());
816 let input = RequestInput::inline(env.encode()?);
817 let session = layer.process((&program_url, &input)).await?;
818
819 assert_eq!(session.journal.as_ref(), data.as_slice());
820 let cycles: u64 = session.segments.iter().map(|s| 1 << s.po2).sum();
822 assert!(cycles > 0);
823 assert!(session.exit_code.is_ok());
824 Ok(())
825 }
826
827 #[tokio::test]
828 #[traced_test]
829 async fn test_requirements_layer() -> anyhow::Result<()> {
830 let layer = RequirementsLayer::default();
831 let program = ECHO_ELF;
832 let bytes = b"journal_data".to_vec();
833 let journal = Journal::new(bytes.clone());
834 let req = layer.process((program, &journal, &Default::default())).await?;
835
836 assert!(req.predicate.eval(&journal));
838 let other = Journal::new(b"other_data".to_vec());
840 assert!(!req.predicate.eval(&other));
841 Ok(())
842 }
843
844 #[tokio::test]
845 #[traced_test]
846 async fn test_request_id_layer_rand() -> anyhow::Result<()> {
847 let anvil = Anvil::new().spawn();
848 let test_ctx = create_test_ctx(&anvil).await?;
849 let market = BoundlessMarketService::new(
850 test_ctx.deployment.boundless_market_address,
851 test_ctx.customer_provider.clone(),
852 test_ctx.customer_signer.address(),
853 );
854 let layer = RequestIdLayer::from(market.clone());
855 assert_eq!(layer.config.mode, RequestIdLayerMode::Rand);
856 let id = layer.process(()).await?;
857 assert_eq!(id.addr, test_ctx.customer_signer.address());
858 assert!(!id.smart_contract_signed);
859 Ok(())
860 }
861
862 #[tokio::test]
863 #[traced_test]
864 async fn test_request_id_layer_nonce() -> anyhow::Result<()> {
865 let anvil = Anvil::new().spawn();
866 let test_ctx = create_test_ctx(&anvil).await?;
867 let market = BoundlessMarketService::new(
868 test_ctx.deployment.boundless_market_address,
869 test_ctx.customer_provider.clone(),
870 test_ctx.customer_signer.address(),
871 );
872 let layer = RequestIdLayer::new(
873 market.clone(),
874 RequestIdLayerConfig::builder().mode(RequestIdLayerMode::Nonce).build()?,
875 );
876
877 let id = layer.process(()).await?;
878 assert_eq!(id.addr, test_ctx.customer_signer.address());
879 assert_eq!(id.index, 0);
881 assert!(!id.smart_contract_signed);
882
883 let tx = TransactionRequest::default()
885 .with_from(test_ctx.customer_signer.address())
886 .with_to(Address::ZERO)
887 .with_value(U256::from(1));
888 test_ctx.customer_provider.send_transaction(tx).await?.watch().await?;
889
890 let id = layer.process(()).await?;
891 assert_eq!(id.addr, test_ctx.customer_signer.address());
892 assert_eq!(id.index, 1);
894 assert!(!id.smart_contract_signed);
895
896 Ok(())
897 }
898
899 #[tokio::test]
900 #[traced_test]
901 async fn test_offer_layer_estimates() -> anyhow::Result<()> {
902 let anvil = Anvil::new().spawn();
904 let test_ctx = create_test_ctx(&anvil).await?;
905 let provider = test_ctx.customer_provider.clone();
906 let layer = OfferLayer::from(provider.clone());
907 let image_id = compute_image_id(ECHO_ELF).unwrap();
909 let predicate = Predicate::digest_match(Journal::new(b"hello".to_vec()).digest());
910 let requirements = Requirements::new(image_id, predicate);
911 let request_id = RequestId::new(test_ctx.customer_signer.address(), 0);
912
913 let offer_params = OfferParams::default();
915 let offer_zero_mcycles =
916 layer.process((&requirements, &request_id, Some(0u64), &offer_params)).await?;
917 assert_eq!(offer_zero_mcycles.minPrice, U256::ZERO);
918 assert_eq!(offer_zero_mcycles.rampUpPeriod, 60);
920 assert_eq!(offer_zero_mcycles.lockTimeout, 600);
921 assert_eq!(offer_zero_mcycles.timeout, 1200);
922 assert!(offer_zero_mcycles.maxPrice > U256::ZERO);
924
925 let offer_more_mcycles =
927 layer.process((&requirements, &request_id, Some(100u64 << 20), &offer_params)).await?;
928 assert!(offer_more_mcycles.maxPrice > offer_zero_mcycles.maxPrice);
929
930 let min_price = U256::from(1u64);
932 let max_price = U256::from(5u64);
933 let offer_params = OfferParams::builder().max_price(max_price).min_price(min_price).into();
934 let offer_zero_mcycles =
935 layer.process((&requirements, &request_id, Some(0u64), &offer_params)).await?;
936 assert_eq!(offer_zero_mcycles.maxPrice, max_price);
937 assert_eq!(offer_zero_mcycles.minPrice, min_price);
938 assert_eq!(offer_zero_mcycles.rampUpPeriod, 60);
939 assert_eq!(offer_zero_mcycles.lockTimeout, 600);
940 assert_eq!(offer_zero_mcycles.timeout, 1200);
941 Ok(())
942 }
943
944 #[test]
945 fn request_params_with_program_url_infallible() {
946 let url = Url::parse("https://fileserver.example/guest.bin").unwrap();
950 RequestParams::new().with_program_url(url).inspect_err(|e| match *e {}).unwrap();
951 }
952
953 #[test]
954 fn request_params_with_input_url_infallible() {
955 let url = Url::parse("https://fileserver.example/input.bin").unwrap();
959 RequestParams::new().with_input_url(url).inspect_err(|e| match *e {}).unwrap();
960 }
961
962 #[test]
963 fn test_with_input_url() {
964 let params =
966 RequestParams::new().with_input_url("https://fileserver.example/input.bin").unwrap();
967
968 let input = params.request_input.unwrap();
969 assert_eq!(input.inputType, RequestInputType::Url);
970 assert_eq!(input.data.as_ref(), "https://fileserver.example/input.bin".as_bytes());
971
972 let url = Url::parse("https://fileserver.example/input2.bin").unwrap();
974 let params = RequestParams::new().with_input_url(url).unwrap();
975
976 let input = params.request_input.unwrap();
977 assert_eq!(input.inputType, RequestInputType::Url);
978 assert_eq!(input.data.as_ref(), "https://fileserver.example/input2.bin".as_bytes());
979 }
980
981 #[allow(dead_code)]
982 trait AssertSend: Send {}
983
984 impl AssertSend for StandardRequestBuilder<StandardRpcProvider, StandardStorageProvider> {}
986}