1use crate::{
2 fillers::{
3 BlobGasEstimator, BlobGasFiller, CachedNonceManager, ChainIdFiller, FillerControlFlow,
4 GasFiller, JoinFill, NonceFiller, NonceManager, RecommendedFillers, SimpleNonceManager,
5 TxFiller, WalletFiller,
6 },
7 layers::{BlockIdLayer, CallBatchLayer, ChainLayer},
8 provider::SendableTx,
9 utils::Eip1559Estimator,
10 Provider, RootProvider,
11};
12use alloy_chains::NamedChain;
13use alloy_network::{Ethereum, IntoWallet, Network};
14use alloy_primitives::ChainId;
15use alloy_rpc_client::{ClientBuilder, ConnectionConfig, RpcClient};
16use alloy_transport::{TransportConnect, TransportError, TransportResult};
17use std::marker::PhantomData;
18
19pub trait ProviderLayer<P: Provider<N>, N: Network = Ethereum> {
23 type Provider: Provider<N>;
25
26 fn layer(&self, inner: P) -> Self::Provider;
28}
29
30#[derive(Clone, Copy, Debug)]
32pub struct Identity;
33
34impl<N> TxFiller<N> for Identity
35where
36 N: Network,
37{
38 type Fillable = ();
39
40 fn status(&self, _tx: &<N as Network>::TransactionRequest) -> FillerControlFlow {
41 FillerControlFlow::Finished
42 }
43
44 fn fill_sync(&self, _tx: &mut SendableTx<N>) {}
45
46 async fn prepare<P>(
47 &self,
48 _provider: &P,
49 _tx: &N::TransactionRequest,
50 ) -> TransportResult<Self::Fillable> {
51 Ok(())
52 }
53
54 async fn fill(
55 &self,
56 _to_fill: Self::Fillable,
57 tx: SendableTx<N>,
58 ) -> TransportResult<SendableTx<N>> {
59 Ok(tx)
60 }
61}
62
63impl<P, N> ProviderLayer<P, N> for Identity
64where
65 N: Network,
66 P: Provider<N>,
67{
68 type Provider = P;
69
70 fn layer(&self, inner: P) -> Self::Provider {
71 inner
72 }
73}
74
75#[derive(Debug)]
77pub struct Stack<Inner, Outer> {
78 inner: Inner,
79 outer: Outer,
80}
81
82impl<Inner, Outer> Stack<Inner, Outer> {
83 pub const fn new(inner: Inner, outer: Outer) -> Self {
85 Self { inner, outer }
86 }
87}
88
89impl<P, N, Inner, Outer> ProviderLayer<P, N> for Stack<Inner, Outer>
90where
91 N: Network,
92 P: Provider<N>,
93 Inner: ProviderLayer<P, N>,
94 Outer: ProviderLayer<Inner::Provider, N>,
95{
96 type Provider = Outer::Provider;
97
98 fn layer(&self, provider: P) -> Self::Provider {
99 let inner = self.inner.layer(provider);
100
101 self.outer.layer(inner)
102 }
103}
104
105#[derive(Debug)]
119pub struct ProviderBuilder<L, F, N = Ethereum> {
120 layer: L,
121 filler: F,
122 network: PhantomData<fn() -> N>,
123}
124
125impl
126 ProviderBuilder<
127 Identity,
128 JoinFill<Identity, <Ethereum as RecommendedFillers>::RecommendedFillers>,
129 Ethereum,
130 >
131{
132 pub fn new() -> Self {
142 ProviderBuilder::default().with_recommended_fillers()
143 }
144
145 pub fn disable_recommended_fillers(self) -> ProviderBuilder<Identity, Identity, Ethereum> {
150 ProviderBuilder { layer: self.layer, filler: Identity, network: self.network }
151 }
152}
153
154impl<N> Default for ProviderBuilder<Identity, Identity, N> {
155 fn default() -> Self {
156 Self { layer: Identity, filler: Identity, network: PhantomData }
157 }
158}
159
160impl ProviderBuilder<Identity, Identity, Ethereum> {
161 pub fn new_with_network<Net: RecommendedFillers>(
164 ) -> ProviderBuilder<Identity, JoinFill<Identity, Net::RecommendedFillers>, Net> {
165 ProviderBuilder {
166 layer: Identity,
167 filler: JoinFill::new(Identity, Net::recommended_fillers()),
168 network: PhantomData,
169 }
170 }
171}
172
173impl<L, N: Network> ProviderBuilder<L, Identity, N> {
174 pub fn with_recommended_fillers(
177 self,
178 ) -> ProviderBuilder<L, JoinFill<Identity, N::RecommendedFillers>, N>
179 where
180 N: RecommendedFillers,
181 {
182 self.filler(N::recommended_fillers())
183 }
184}
185
186impl<L, F, N> ProviderBuilder<L, F, N> {
187 pub fn apply<T>(self, f: impl FnOnce(Self) -> T) -> T {
192 f(self)
193 }
194
195 pub fn map_layer<L2>(self, f: impl FnOnce(L) -> L2) -> ProviderBuilder<L2, F, N> {
199 ProviderBuilder { layer: f(self.layer), filler: self.filler, network: PhantomData }
200 }
201
202 pub fn map_filler<F2>(self, f: impl FnOnce(F) -> F2) -> ProviderBuilder<L, F2, N> {
206 ProviderBuilder { layer: self.layer, filler: f(self.filler), network: PhantomData }
207 }
208
209 pub fn layer<Inner>(self, layer: Inner) -> ProviderBuilder<Stack<Inner, L>, F, N> {
221 self.map_layer(|current| Stack::new(layer, current))
222 }
223
224 pub fn filler<F2>(self, filler: F2) -> ProviderBuilder<L, JoinFill<F, F2>, N> {
228 self.map_filler(|current| JoinFill::new(current, filler))
229 }
230
231 pub fn network<Net: RecommendedFillers>(
243 self,
244 ) -> ProviderBuilder<L, JoinFill<Identity, Net::RecommendedFillers>, Net> {
245 ProviderBuilder {
246 layer: self.layer,
247 filler: JoinFill::new(Identity, Net::recommended_fillers()),
248 network: PhantomData,
249 }
250 }
251
252 pub fn with_chain(self, chain: NamedChain) -> ProviderBuilder<Stack<ChainLayer, L>, F, N> {
257 self.layer(ChainLayer::new(chain))
258 }
259
260 pub fn with_blob_gas_estimation(self) -> ProviderBuilder<L, JoinFill<F, BlobGasFiller>, N> {
266 self.filler(BlobGasFiller::default())
267 }
268
269 pub fn with_blob_gas_estimator(
273 self,
274 estimator: BlobGasEstimator,
275 ) -> ProviderBuilder<L, JoinFill<F, BlobGasFiller>, N> {
276 self.filler(BlobGasFiller { estimator })
277 }
278
279 pub fn with_gas_estimation(self) -> ProviderBuilder<L, JoinFill<F, GasFiller>, N> {
283 self.filler(GasFiller::default())
284 }
285
286 pub fn with_eip1559_estimator(
290 self,
291 estimator: Eip1559Estimator,
292 ) -> ProviderBuilder<L, JoinFill<F, GasFiller>, N> {
293 self.filler(GasFiller { estimator })
294 }
295
296 pub fn with_nonce_management<M: NonceManager>(
300 self,
301 nonce_manager: M,
302 ) -> ProviderBuilder<L, JoinFill<F, NonceFiller<M>>, N> {
303 self.filler(NonceFiller::new(nonce_manager))
304 }
305
306 pub fn with_simple_nonce_management(
310 self,
311 ) -> ProviderBuilder<L, JoinFill<F, NonceFiller<SimpleNonceManager>>, N> {
312 self.with_nonce_management(SimpleNonceManager::default())
313 }
314
315 pub fn with_cached_nonce_management(
319 self,
320 ) -> ProviderBuilder<L, JoinFill<F, NonceFiller<CachedNonceManager>>, N> {
321 self.with_nonce_management(CachedNonceManager::default())
322 }
323
324 pub fn fetch_chain_id(self) -> ProviderBuilder<L, JoinFill<F, ChainIdFiller>, N> {
329 self.filler(ChainIdFiller::default())
330 }
331
332 pub fn with_chain_id(
336 self,
337 chain_id: ChainId,
338 ) -> ProviderBuilder<L, JoinFill<F, ChainIdFiller>, N> {
339 self.filler(ChainIdFiller::new(Some(chain_id)))
340 }
341
342 pub fn wallet<W: IntoWallet<N>>(
346 self,
347 wallet: W,
348 ) -> ProviderBuilder<L, JoinFill<F, WalletFiller<W::NetworkWallet>>, N>
349 where
350 N: Network,
351 {
352 self.filler(WalletFiller::new(wallet.into_wallet()))
353 }
354
355 pub fn with_call_batching(self) -> ProviderBuilder<Stack<CallBatchLayer, L>, F, N> {
361 self.layer(CallBatchLayer::new())
362 }
363
364 pub fn with_arbitrum_call_batching(self) -> ProviderBuilder<Stack<CallBatchLayer, L>, F, N> {
369 self.layer(CallBatchLayer::new().arbitrum_compat())
370 }
371
372 #[cfg(not(target_family = "wasm"))]
376 pub fn with_caching(
377 self,
378 max_items: u32,
379 ) -> ProviderBuilder<Stack<crate::layers::CacheLayer, L>, F, N> {
380 self.layer(crate::layers::CacheLayer::new(max_items))
381 }
382
383 #[cfg(not(target_family = "wasm"))]
387 pub fn with_default_caching(
388 self,
389 ) -> ProviderBuilder<Stack<crate::layers::CacheLayer, L>, F, N> {
390 self.with_caching(100)
391 }
392
393 pub fn with_default_block(
397 self,
398 block_id: alloy_eips::BlockId,
399 ) -> ProviderBuilder<Stack<BlockIdLayer, L>, F, N> {
400 self.layer(BlockIdLayer::new(block_id))
401 }
402
403 pub fn connect_provider<P>(self, provider: P) -> F::Provider
408 where
409 L: ProviderLayer<P, N>,
410 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
411 P: Provider<N>,
412 N: Network,
413 {
414 let Self { layer, filler, network: PhantomData } = self;
415 let stack = Stack::new(layer, filler);
416 stack.layer(provider)
417 }
418
419 pub fn connect_client(self, client: RpcClient) -> F::Provider
425 where
426 L: ProviderLayer<RootProvider<N>, N>,
427 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
428 N: Network,
429 {
430 self.connect_provider(RootProvider::new(client))
431 }
432
433 pub fn connect_mocked_client(self, asserter: alloy_transport::mock::Asserter) -> F::Provider
439 where
440 L: ProviderLayer<RootProvider<N>, N>,
441 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
442 N: Network,
443 {
444 self.connect_client(RpcClient::mocked(asserter))
445 }
446
447 #[doc(alias = "on_builtin")]
451 pub async fn connect(self, s: &str) -> Result<F::Provider, TransportError>
452 where
453 L: ProviderLayer<RootProvider<N>, N>,
454 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
455 N: Network,
456 {
457 let client = ClientBuilder::default().connect(s).await?;
458 Ok(self.connect_client(client))
459 }
460
461 pub async fn connect_with_config(
487 self,
488 s: &str,
489 config: ConnectionConfig,
490 ) -> Result<F::Provider, TransportError>
491 where
492 L: ProviderLayer<RootProvider<N>, N>,
493 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
494 N: Network,
495 {
496 let client = ClientBuilder::default().connect_with_config(s, config).await?;
497 Ok(self.connect_client(client))
498 }
499
500 pub async fn connect_with<C>(self, connect: &C) -> Result<F::Provider, TransportError>
502 where
503 L: ProviderLayer<RootProvider<N>, N>,
504 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
505 N: Network,
506 C: TransportConnect,
507 {
508 connect
509 .get_transport()
510 .await
511 .map(|t| RpcClient::new(t, connect.is_local()))
512 .map(|client| self.connect_client(client))
513 }
514
515 #[cfg(feature = "pubsub")]
520 pub async fn connect_pubsub_with<C>(self, connect: C) -> Result<F::Provider, TransportError>
521 where
522 L: ProviderLayer<RootProvider<N>, N>,
523 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
524 N: Network,
525 C: alloy_pubsub::PubSubConnect,
526 {
527 ClientBuilder::default().pubsub(connect).await.map(|client| self.connect_client(client))
528 }
529
530 #[cfg(feature = "ws")]
532 pub async fn connect_ws(
533 self,
534 connect: alloy_transport_ws::WsConnect,
535 ) -> Result<F::Provider, TransportError>
536 where
537 L: ProviderLayer<RootProvider<N>, N>,
538 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
539 N: Network,
540 {
541 let client = ClientBuilder::default().ws(connect).await?;
542 Ok(self.connect_client(client))
543 }
544
545 #[cfg(feature = "ipc")]
547 pub async fn connect_ipc<T>(
548 self,
549 connect: alloy_transport_ipc::IpcConnect<T>,
550 ) -> Result<F::Provider, TransportError>
551 where
552 alloy_transport_ipc::IpcConnect<T>: alloy_pubsub::PubSubConnect,
553 L: ProviderLayer<RootProvider<N>, N>,
554 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
555 N: Network,
556 {
557 let client = ClientBuilder::default().ipc(connect).await?;
558 Ok(self.connect_client(client))
559 }
560
561 #[cfg(any(test, feature = "reqwest"))]
563 pub fn connect_http(self, url: reqwest::Url) -> F::Provider
564 where
565 L: ProviderLayer<crate::RootProvider<N>, N>,
566 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
567 N: Network,
568 {
569 let client = ClientBuilder::default().http(url);
570 self.connect_client(client)
571 }
572
573 #[cfg(any(test, feature = "reqwest"))]
575 pub fn connect_reqwest<C>(self, client: C, url: reqwest::Url) -> F::Provider
576 where
577 L: ProviderLayer<crate::RootProvider<N>, N>,
578 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
579 N: Network,
580 C: Into<reqwest::Client>,
581 {
582 let client = ClientBuilder::default().http_with_client(client.into(), url);
583 self.connect_client(client)
584 }
585
586 #[cfg(any(test, feature = "reqwest"))]
588 pub fn with_reqwest<B>(self, url: reqwest::Url, builder: B) -> F::Provider
589 where
590 L: ProviderLayer<crate::RootProvider<N>, N>,
591 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
592 N: Network,
593 B: FnOnce(reqwest::ClientBuilder) -> reqwest::Client,
594 {
595 self.connect_reqwest(builder(reqwest::ClientBuilder::default()), url)
596 }
597
598 #[cfg(feature = "hyper")]
600 pub fn connect_hyper_http(self, url: url::Url) -> F::Provider
601 where
602 L: ProviderLayer<crate::RootProvider<N>, N>,
603 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
604 N: Network,
605 {
606 let client = ClientBuilder::default().hyper_http(url);
607 self.connect_client(client)
608 }
609}
610
611#[cfg(any(test, feature = "anvil-node"))]
612type JoinedEthereumWalletFiller<F> = JoinFill<F, WalletFiller<alloy_network::EthereumWallet>>;
613
614#[cfg(any(test, feature = "anvil-node"))]
615type AnvilProviderResult<T> = Result<T, alloy_node_bindings::NodeError>;
616
617#[cfg(any(test, feature = "anvil-node"))]
618impl<L, F, N: Network> ProviderBuilder<L, F, N> {
619 #[cfg_attr(docsrs, doc(cfg(feature = "anvil-node")))]
625 pub fn connect_anvil(self) -> F::Provider
626 where
627 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
628 L: crate::builder::ProviderLayer<
629 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
630 N,
631 >,
632 {
633 self.connect_anvil_with_config(std::convert::identity)
634 }
635
636 #[cfg_attr(docsrs, doc(cfg(feature = "anvil-node")))]
644 pub fn connect_anvil_with_wallet(
645 self,
646 ) -> <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider
647 where
648 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
649 L: crate::builder::ProviderLayer<
650 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
651 N,
652 >,
653 alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
654 {
655 self.connect_anvil_with_wallet_and_config(std::convert::identity)
656 .expect("failed to build provider")
657 }
658
659 #[cfg_attr(docsrs, doc(cfg(feature = "anvil-node")))]
666 pub fn connect_anvil_with_config(
667 self,
668 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
669 ) -> F::Provider
670 where
671 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
672 L: crate::builder::ProviderLayer<
673 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
674 N,
675 >,
676 {
677 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
678 let url = anvil_layer.endpoint_url();
679
680 let rpc_client = ClientBuilder::default().http(url);
681
682 self.layer(anvil_layer).connect_client(rpc_client)
683 }
684
685 #[cfg_attr(docsrs, doc(cfg(feature = "anvil-node")))]
692 #[deprecated(since = "0.12.6", note = "use `connect_anvil_with_config` instead")]
693 pub fn on_anvil_with_config(
694 self,
695 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
696 ) -> F::Provider
697 where
698 L: ProviderLayer<crate::layers::AnvilProvider<RootProvider<N>, N>, N>,
699 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
700 {
701 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
702 let url = anvil_layer.endpoint_url();
703
704 let rpc_client = ClientBuilder::default().http(url);
705
706 self.layer(anvil_layer).connect_client(rpc_client)
707 }
708
709 #[cfg_attr(docsrs, doc(cfg(feature = "anvil-node")))]
716 pub fn connect_anvil_with_wallet_and_config(
717 self,
718 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
719 ) -> AnvilProviderResult<
720 <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider,
721 >
722 where
723 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
724 L: crate::builder::ProviderLayer<
725 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
726 N,
727 >,
728 alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
729 {
730 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
731 let url = anvil_layer.endpoint_url();
732
733 let wallet = anvil_layer
734 .instance()
735 .wallet()
736 .ok_or(alloy_node_bindings::NodeError::NoKeysAvailable)?;
737
738 let rpc_client = ClientBuilder::default().http(url);
739
740 Ok(self.wallet(wallet).layer(anvil_layer).connect_client(rpc_client))
741 }
742
743 #[cfg_attr(docsrs, doc(cfg(feature = "anvil-node")))]
750 #[deprecated(since = "0.12.6", note = "use `connect_anvil_with_wallet_and_config` instead")]
751 pub fn on_anvil_with_wallet_and_config(
752 self,
753 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
754 ) -> AnvilProviderResult<
755 <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider,
756 >
757 where
758 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
759 L: crate::builder::ProviderLayer<
760 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
761 N,
762 >,
763 alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
764 {
765 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
766 let url = anvil_layer.endpoint_url();
767
768 let wallet = anvil_layer
769 .instance()
770 .wallet()
771 .ok_or(alloy_node_bindings::NodeError::NoKeysAvailable)?;
772
773 let rpc_client = ClientBuilder::default().http(url);
774
775 Ok(self.wallet(wallet).layer(anvil_layer).connect_client(rpc_client))
776 }
777}
778
779#[cfg(test)]
780mod tests {
781 use super::*;
782 use crate::Provider;
783 use alloy_network::AnyNetwork;
784
785 #[tokio::test]
786 async fn basic() {
787 let provider = ProviderBuilder::new()
788 .with_cached_nonce_management()
789 .with_call_batching()
790 .connect_http("http://localhost:8545".parse().unwrap());
791 let _ = provider.get_account(Default::default());
792 let provider = provider.erased();
793 let _ = provider.get_account(Default::default());
794 }
795
796 #[tokio::test]
797 #[cfg(feature = "reqwest")]
798 async fn test_connect_reqwest() {
799 let provider = ProviderBuilder::new()
800 .with_cached_nonce_management()
801 .with_call_batching()
802 .connect_reqwest(
803 reqwest::Client::new(),
804 reqwest::Url::parse("http://localhost:8545").unwrap(),
805 );
806 let _ = provider.get_account(Default::default());
807 let provider = provider.erased();
808 let _ = provider.get_account(Default::default());
809 }
810
811 #[tokio::test]
812 #[cfg(feature = "reqwest")]
813 async fn test_with_reqwest() {
814 let provider = ProviderBuilder::new()
815 .with_cached_nonce_management()
816 .with_call_batching()
817 .with_reqwest(reqwest::Url::parse("http://localhost:8545").unwrap(), |builder| {
818 builder
819 .user_agent("alloy/test")
820 .timeout(std::time::Duration::from_secs(10))
821 .build()
822 .expect("failed to build reqwest client")
823 });
824 let _ = provider.get_account(Default::default());
825 let provider = provider.erased();
826 let _ = provider.get_account(Default::default());
827 }
828
829 #[tokio::test]
830 async fn compile_with_network() {
831 let p = ProviderBuilder::new_with_network::<AnyNetwork>().connect_anvil();
832 let num = p.get_block_number().await.unwrap();
833 assert_eq!(num, 0);
834 }
835
836 #[test]
838 fn network_replaces_fillers() {
839 let builder = ProviderBuilder::new().filler(GasFiller::default()).network::<AnyNetwork>();
841
842 let _: ProviderBuilder<
843 Identity,
844 JoinFill<Identity, <AnyNetwork as RecommendedFillers>::RecommendedFillers>,
845 AnyNetwork,
846 > = builder;
847 }
848
849 #[test]
850 fn apply_transforms_builder() {
851 let builder = ProviderBuilder::new()
852 .apply(|builder| builder.disable_recommended_fillers().with_gas_estimation());
853
854 let _: ProviderBuilder<Identity, JoinFill<Identity, GasFiller>, Ethereum> = builder;
855 }
856
857 #[test]
858 fn map_filler_replaces_fillers() {
859 let builder = ProviderBuilder::new().map_filler(|_| GasFiller::default());
860
861 let _: ProviderBuilder<Identity, GasFiller, Ethereum> = builder;
862 }
863
864 #[test]
865 fn map_layer_replaces_layers() {
866 let builder = ProviderBuilder::<Identity, Identity>::default()
867 .map_layer(|_| ChainLayer::new(NamedChain::Mainnet));
868
869 let _: ProviderBuilder<ChainLayer, Identity, Ethereum> = builder;
870 }
871
872 #[tokio::test]
873 async fn network_swap_works_at_runtime() {
874 let p = ProviderBuilder::new().network::<AnyNetwork>().connect_anvil();
876 let num = p.get_block_number().await.unwrap();
877 assert_eq!(num, 0);
878 }
879}