1use crate::{CallDecoder, Error, EthCall, Result};
2use alloy_consensus::SignableTransaction;
3use alloy_dyn_abi::{DynSolValue, JsonAbiExt};
4use alloy_json_abi::Function;
5use alloy_network::{
6 eip2718::Encodable2718, Ethereum, IntoWallet, Network, TransactionBuilder,
7 TransactionBuilder4844, TransactionBuilder7594, TransactionBuilder7702,
8 TransactionBuilderError, TxSigner,
9};
10use alloy_network_primitives::ReceiptResponse;
11use alloy_primitives::{Address, Bytes, ChainId, Signature, TxKind, U256};
12use alloy_provider::{PendingTransactionBuilder, Provider};
13use alloy_rpc_types_eth::{
14 state::StateOverride, AccessList, BlobTransactionSidecar, BlobTransactionSidecarEip7594,
15 BlockId, SignedAuthorization,
16};
17use alloy_sol_types::SolCall;
18use std::marker::PhantomData;
19
20pub type SolCallBuilder<P, C, N = Ethereum> = CallBuilder<P, PhantomData<C>, N>;
26
27pub type DynCallBuilder<P, N = Ethereum> = CallBuilder<P, Function, N>;
29
30pub type RawCallBuilder<P, N = Ethereum> = CallBuilder<P, (), N>;
32
33#[derive(Clone)]
129#[must_use = "call builders do nothing unless you `.call`, `.send`, or `.await` them"]
130pub struct CallBuilder<P, D, N: Network = Ethereum> {
131 pub(crate) request: N::TransactionRequest,
132 block: BlockId,
133 state: Option<StateOverride>,
134 pub provider: P,
137 decoder: D,
138}
139
140impl<P, D, N: Network> CallBuilder<P, D, N> {
141 pub fn into_transaction_request(self) -> N::TransactionRequest {
143 self.request
144 }
145
146 pub fn build_unsigned_raw_transaction(self) -> Result<Vec<u8>, TransactionBuilderError<N>>
179 where
180 N::UnsignedTx: SignableTransaction<Signature>,
181 {
182 let tx = self.request.build_unsigned().map_err(|e| e.error)?;
183 Ok(tx.encoded_for_signing())
184 }
185
186 pub async fn build_raw_transaction<S>(
222 self,
223 signer: S,
224 ) -> Result<Vec<u8>, TransactionBuilderError<N>>
225 where
226 S: TxSigner<Signature> + IntoWallet<N>,
227 {
228 let tx = self.request.build(&signer.into_wallet()).await?;
229 Ok(tx.encoded_2718())
230 }
231}
232
233impl<P, D, N: Network> AsRef<N::TransactionRequest> for CallBuilder<P, D, N> {
234 fn as_ref(&self) -> &N::TransactionRequest {
235 &self.request
236 }
237}
238
239impl<P: Provider<N>, N: Network> DynCallBuilder<P, N> {
241 pub(crate) fn new_dyn(
242 provider: P,
243 address: &Address,
244 function: &Function,
245 args: &[DynSolValue],
246 ) -> Result<Self> {
247 Ok(Self::new_inner_call(
248 provider,
249 function.abi_encode_input(args)?.into(),
250 function.clone(),
251 )
252 .to(*address))
253 }
254
255 #[inline]
257 pub fn clear_decoder(self) -> RawCallBuilder<P, N> {
258 RawCallBuilder {
259 request: self.request,
260 block: self.block,
261 state: self.state,
262 provider: self.provider,
263 decoder: (),
264 }
265 }
266}
267
268#[doc(hidden)]
269impl<'a, P: Provider<N>, C: SolCall, N: Network> SolCallBuilder<&'a P, C, N> {
270 pub fn new_sol(provider: &'a P, address: &Address, call: &C) -> Self {
273 Self::new_inner_call(provider, call.abi_encode().into(), PhantomData::<C>).to(*address)
274 }
275}
276
277impl<P: Provider<N>, C: SolCall, N: Network> SolCallBuilder<P, C, N> {
278 #[inline]
280 pub fn clear_decoder(self) -> RawCallBuilder<P, N> {
281 RawCallBuilder {
282 request: self.request,
283 block: self.block,
284 state: self.state,
285 provider: self.provider,
286 decoder: (),
287 }
288 }
289}
290
291impl<P: Provider<N>, N: Network> RawCallBuilder<P, N> {
292 #[inline]
344 pub fn with_sol_decoder<C: SolCall>(self) -> SolCallBuilder<P, C, N> {
345 SolCallBuilder {
346 request: self.request,
347 block: self.block,
348 state: self.state,
349 provider: self.provider,
350 decoder: PhantomData::<C>,
351 }
352 }
353}
354
355impl<P: Provider<N>, N: Network> RawCallBuilder<P, N> {
356 #[inline]
361 pub fn new_raw(provider: P, input: Bytes) -> Self {
362 Self::new_inner_call(provider, input, ())
363 }
364
365 pub fn new_raw_deploy(provider: P, input: Bytes) -> Self {
371 Self::new_inner_deploy(provider, input, ())
372 }
373}
374
375impl<P: Provider<N>, D: CallDecoder, N: Network> CallBuilder<P, D, N> {
376 fn new_inner_deploy(provider: P, input: Bytes, decoder: D) -> Self {
377 Self {
378 request: <N::TransactionRequest>::default().with_deploy_code(input),
379 decoder,
380 provider,
381 block: BlockId::default(),
382 state: None,
383 }
384 }
385
386 fn new_inner_call(provider: P, input: Bytes, decoder: D) -> Self {
387 Self {
388 request: <N::TransactionRequest>::default().with_input(input),
389 decoder,
390 provider,
391 block: BlockId::default(),
392 state: None,
393 }
394 }
395
396 pub fn chain_id(mut self, chain_id: ChainId) -> Self {
398 self.request.set_chain_id(chain_id);
399 self
400 }
401
402 pub fn from(mut self, from: Address) -> Self {
404 self.request.set_from(from);
405 self
406 }
407
408 pub fn kind(mut self, to: TxKind) -> Self {
410 self.request.set_kind(to);
411 self
412 }
413
414 pub fn to(mut self, to: Address) -> Self {
416 self.request.set_to(to);
417 self
418 }
419
420 pub fn sidecar(mut self, blob_sidecar: BlobTransactionSidecar) -> Self
422 where
423 N::TransactionRequest: TransactionBuilder4844,
424 {
425 self.request.set_blob_sidecar(blob_sidecar);
426 self
427 }
428
429 pub fn sidecar_7594(mut self, sidecar: BlobTransactionSidecarEip7594) -> Self
431 where
432 N::TransactionRequest: TransactionBuilder7594,
433 {
434 self.request.set_blob_sidecar_7594(sidecar);
435 self
436 }
437
438 pub fn gas(mut self, gas: u64) -> Self {
440 self.request.set_gas_limit(gas);
441 self
442 }
443
444 pub fn gas_price(mut self, gas_price: u128) -> Self {
448 self.request.set_gas_price(gas_price);
449 self
450 }
451
452 pub fn max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self {
454 self.request.set_max_fee_per_gas(max_fee_per_gas);
455 self
456 }
457
458 pub fn max_priority_fee_per_gas(mut self, max_priority_fee_per_gas: u128) -> Self {
460 self.request.set_max_priority_fee_per_gas(max_priority_fee_per_gas);
461 self
462 }
463
464 pub fn max_fee_per_blob_gas(mut self, max_fee_per_blob_gas: u128) -> Self
466 where
467 N::TransactionRequest: TransactionBuilder4844,
468 {
469 self.request.set_max_fee_per_blob_gas(max_fee_per_blob_gas);
470 self
471 }
472
473 pub fn access_list(mut self, access_list: AccessList) -> Self {
475 self.request.set_access_list(access_list);
476 self
477 }
478
479 pub fn authorization_list(mut self, authorization_list: Vec<SignedAuthorization>) -> Self
481 where
482 N::TransactionRequest: TransactionBuilder7702,
483 {
484 self.request.set_authorization_list(authorization_list);
485 self
486 }
487
488 pub fn value(mut self, value: U256) -> Self {
490 self.request.set_value(value);
491 self
492 }
493
494 pub fn nonce(mut self, nonce: u64) -> Self {
496 self.request.set_nonce(nonce);
497 self
498 }
499
500 pub fn map<F>(mut self, f: F) -> Self
502 where
503 F: FnOnce(N::TransactionRequest) -> N::TransactionRequest,
504 {
505 self.request = f(self.request);
506 self
507 }
508
509 pub const fn block(mut self, block: BlockId) -> Self {
511 self.block = block;
512 self
513 }
514
515 pub fn state(mut self, state: impl Into<StateOverride>) -> Self {
521 self.state = Some(state.into());
522 self
523 }
524
525 pub fn calldata(&self) -> &Bytes {
527 self.request.input().expect("set in the constructor")
528 }
529
530 pub async fn estimate_gas(&self) -> Result<u64> {
533 let mut estimate = self.provider.estimate_gas(self.request.clone());
534 if let Some(state) = self.state.clone() {
535 estimate = estimate.overrides(state);
536 }
537 estimate.block(self.block).await.map_err(Into::into)
538 }
539
540 #[doc(alias = "eth_call")]
546 #[doc(alias = "call_with_overrides")]
547 pub fn call(&self) -> EthCall<'_, D, N> {
548 self.call_raw().with_decoder(&self.decoder)
549 }
550
551 pub fn call_raw(&self) -> EthCall<'_, (), N> {
558 let call = self.provider.call(self.request.clone()).block(self.block);
559 let call = match self.state.clone() {
560 Some(state) => call.overrides(state),
561 None => call,
562 };
563 call.into()
564 }
565
566 #[inline]
568 pub fn decode_output(&self, data: Bytes) -> Result<D::CallOutput> {
569 self.decoder.abi_decode_output(data)
570 }
571
572 pub async fn deploy(&self) -> Result<Address> {
583 if !self.request.kind().is_some_and(|to| to.is_create()) {
584 return Err(Error::NotADeploymentTransaction);
585 }
586 let pending_tx = self.send().await?;
587 let receipt = pending_tx.get_receipt().await?;
588 receipt.contract_address().ok_or(Error::ContractNotDeployed)
589 }
590
591 pub async fn deploy_sync(&self) -> Result<Address> {
611 if !self.request.kind().is_some_and(|to| to.is_create()) {
612 return Err(Error::NotADeploymentTransaction);
613 }
614 let receipt = self.send_sync().await?;
615 receipt.contract_address().ok_or(Error::ContractNotDeployed)
616 }
617
618 pub async fn send(&self) -> Result<PendingTransactionBuilder<N>> {
623 Ok(self.provider.send_transaction(self.request.clone()).await?)
624 }
625
626 pub async fn send_sync(&self) -> Result<N::ReceiptResponse> {
640 Ok(self.provider.send_transaction_sync(self.request.clone()).await?)
641 }
642
643 pub fn calculate_create_address(&self) -> Option<Address> {
648 self.request.calculate_create_address()
649 }
650}
651
652impl<P: Clone, D, N: Network> CallBuilder<&P, D, N> {
653 pub fn with_cloned_provider(self) -> CallBuilder<P, D, N> {
655 CallBuilder {
656 request: self.request,
657 block: self.block,
658 state: self.state,
659 provider: self.provider.clone(),
660 decoder: self.decoder,
661 }
662 }
663}
664
665impl<P, D: CallDecoder, N: Network> std::fmt::Debug for CallBuilder<P, D, N> {
666 #[inline]
667 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
668 f.debug_struct("CallBuilder")
669 .field("request", &self.request)
670 .field("block", &self.block)
671 .field("state", &self.state)
672 .field("decoder", &self.decoder.as_debug_field())
673 .finish()
674 }
675}
676
677#[cfg(test)]
678mod tests {
679 use super::*;
680 use alloy_consensus::Transaction;
681 use alloy_network::EthereumWallet;
682 use alloy_node_bindings::Anvil;
683 use alloy_primitives::{address, b256, bytes, hex, utils::parse_units, B256};
684 use alloy_provider::{Provider, ProviderBuilder, WalletProvider};
685 use alloy_rpc_types_eth::{AccessListItem, Authorization};
686 use alloy_signer_local::PrivateKeySigner;
687 use alloy_sol_types::sol;
688 use futures::Future;
689
690 #[test]
691 fn empty_constructor() {
692 sol! {
693 #[sol(rpc, bytecode = "6942")]
694 contract EmptyConstructor {
695 constructor();
696 }
697 }
698
699 let provider = ProviderBuilder::new().connect_anvil();
700 let call_builder = EmptyConstructor::deploy_builder(&provider);
701 assert_eq!(*call_builder.calldata(), bytes!("6942"));
702 }
703
704 sol! {
705 #[sol(rpc, bytecode = "60803461006357601f61014838819003918201601f19168301916001600160401b038311848410176100675780849260209460405283398101031261006357518015158091036100635760ff80195f54169116175f5560405160cc908161007c8239f35b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60808060405260043610156011575f80fd5b5f3560e01c9081638bf1799f14607a575063b09a261614602f575f80fd5b346076576040366003190112607657602435801515810360765715606f57604060015b81516004356001600160a01b0316815260ff919091166020820152f35b60405f6052565b5f80fd5b346076575f36600319011260765760209060ff5f541615158152f3fea264697066735822122043709781c9bdc30c530978abf5db25a4b4ccfebf989baafd2ba404519a7f7e8264736f6c63430008180033")]
708 contract MyContract {
709 bool public myState;
710
711 constructor(bool myState_) {
712 myState = myState_;
713 }
714
715 function doStuff(uint a, bool b) external pure returns(address c, bytes32 d) {
716 return (address(uint160(a)), bytes32(uint256(b ? 1 : 0)));
717 }
718 }
719 }
720
721 sol! {
722 #[sol(rpc, bytecode = "608080604052346100155760d4908161001a8239f35b5f80fdfe60808060405260043610156011575f80fd5b5f3560e01c90816361bc221a14607e575063d09de08a14602f575f80fd5b34607a575f366003190112607a575f546001600160801b038082166001018181116066576001600160801b03199092169116175f55005b634e487b7160e01b5f52601160045260245ffd5b5f80fd5b34607a575f366003190112607a575f546001600160801b03168152602090f3fea26469706673582212208b360e442c4bb2a4bbdec007ee24588c7a88e0aa52ac39efac748e5e23eff69064736f6c63430008180033")]
725 contract Counter {
726 uint128 public counter;
727
728 function increment() external {
729 counter += 1;
730 }
731 }
732 }
733
734 fn build_call_builder() -> CallBuilder<impl Provider, PhantomData<MyContract::doStuffCall>> {
736 let provider = ProviderBuilder::new().connect_anvil();
737 let contract = MyContract::new(Address::ZERO, provider);
738 let call_builder = contract.doStuff(U256::ZERO, true).with_cloned_provider();
739 call_builder
740 }
741
742 #[test]
743 fn change_chain_id() {
744 let call_builder = build_call_builder().chain_id(1337);
745 assert_eq!(
746 call_builder.request.chain_id.expect("chain_id should be set"),
747 1337,
748 "chain_id of request should be '1337'"
749 );
750 }
751
752 #[test]
753 fn change_max_fee_per_gas() {
754 let call_builder = build_call_builder().max_fee_per_gas(42);
755 assert_eq!(
756 call_builder.request.max_fee_per_gas.expect("max_fee_per_gas should be set"),
757 42,
758 "max_fee_per_gas of request should be '42'"
759 );
760 }
761
762 #[test]
763 fn change_max_priority_fee_per_gas() {
764 let call_builder = build_call_builder().max_priority_fee_per_gas(45);
765 assert_eq!(
766 call_builder
767 .request
768 .max_priority_fee_per_gas
769 .expect("max_priority_fee_per_gas should be set"),
770 45,
771 "max_priority_fee_per_gas of request should be '45'"
772 );
773 }
774
775 #[test]
776 fn change_max_fee_per_blob_gas() {
777 let call_builder = build_call_builder().max_fee_per_blob_gas(50);
778 assert_eq!(
779 call_builder.request.max_fee_per_blob_gas.expect("max_fee_per_blob_gas should be set"),
780 50,
781 "max_fee_per_blob_gas of request should be '50'"
782 );
783 }
784
785 #[test]
786 fn change_authorization_list() {
787 let authorization_list = vec![SignedAuthorization::new_unchecked(
788 Authorization { chain_id: U256::from(1337), address: Address::ZERO, nonce: 0 },
789 0,
790 U256::ZERO,
791 U256::ZERO,
792 )];
793 let call_builder = build_call_builder().authorization_list(authorization_list.clone());
794 assert_eq!(
795 call_builder.request.authorization_list.expect("authorization_list should be set"),
796 authorization_list,
797 "Authorization list of the transaction should have been set to our authorization list"
798 );
799 }
800
801 #[test]
802 fn change_access_list() {
803 let access_list = AccessList::from(vec![AccessListItem {
804 address: Address::ZERO,
805 storage_keys: vec![B256::ZERO],
806 }]);
807 let call_builder = build_call_builder().access_list(access_list.clone());
808 assert_eq!(
809 call_builder.request.access_list.expect("access_list should be set"),
810 access_list,
811 "Access list of the transaction should have been set to our access list"
812 )
813 }
814
815 #[test]
816 fn call_encoding() {
817 let provider = ProviderBuilder::new().connect_anvil();
818 let contract = MyContract::new(Address::ZERO, &&provider).with_cloned_provider();
819 let call_builder = contract.doStuff(U256::ZERO, true).with_cloned_provider();
820 assert_eq!(
821 *call_builder.calldata(),
822 bytes!(
823 "b09a2616"
824 "0000000000000000000000000000000000000000000000000000000000000000"
825 "0000000000000000000000000000000000000000000000000000000000000001"
826 ),
827 );
828 let _future: Box<dyn Future<Output = Result<MyContract::doStuffReturn>> + Send> =
830 Box::new(async move { call_builder.call().await });
831 }
832
833 #[test]
834 fn deploy_encoding() {
835 let provider = ProviderBuilder::new().connect_anvil();
836 let bytecode = &MyContract::BYTECODE[..];
837 let call_builder = MyContract::deploy_builder(&provider, false);
838 assert_eq!(
839 call_builder.calldata()[..],
840 [
841 bytecode,
842 &hex!("0000000000000000000000000000000000000000000000000000000000000000")[..]
843 ]
844 .concat(),
845 );
846 let call_builder = MyContract::deploy_builder(&provider, true);
847 assert_eq!(
848 call_builder.calldata()[..],
849 [
850 bytecode,
851 &hex!("0000000000000000000000000000000000000000000000000000000000000001")[..]
852 ]
853 .concat(),
854 );
855 }
856
857 #[tokio::test(flavor = "multi_thread")]
858 async fn deploy_and_call() {
859 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
860
861 let expected_address = provider.default_signer_address().create(0);
862 let my_contract = MyContract::deploy(provider, true).await.unwrap();
863 assert_eq!(*my_contract.address(), expected_address);
864
865 let my_state_builder = my_contract.myState();
866 assert_eq!(my_state_builder.calldata()[..], MyContract::myStateCall {}.abi_encode(),);
867 let my_state = my_state_builder.call().await.unwrap();
868 assert!(my_state);
869
870 let do_stuff_builder = my_contract.doStuff(U256::from(0x69), true);
871 assert_eq!(
872 do_stuff_builder.calldata()[..],
873 MyContract::doStuffCall { a: U256::from(0x69), b: true }.abi_encode(),
874 );
875 let result: MyContract::doStuffReturn = do_stuff_builder.call().await.unwrap();
876 assert_eq!(result.c, address!("0000000000000000000000000000000000000069"));
877 assert_eq!(
878 result.d,
879 b256!("0000000000000000000000000000000000000000000000000000000000000001"),
880 );
881 }
882
883 #[tokio::test(flavor = "multi_thread")]
884 async fn deploy_and_call_with_priority() {
885 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
886 let counter_contract = Counter::deploy(provider.clone()).await.unwrap();
887 let max_fee_per_gas: U256 = parse_units("50", "gwei").unwrap().into();
888 let max_priority_fee_per_gas: U256 = parse_units("0.1", "gwei").unwrap().into();
889 let receipt = counter_contract
890 .increment()
891 .max_fee_per_gas(max_fee_per_gas.to())
892 .max_priority_fee_per_gas(max_priority_fee_per_gas.to())
893 .send()
894 .await
895 .expect("Could not send transaction")
896 .get_receipt()
897 .await
898 .expect("Could not get the receipt");
899 let transaction_hash = receipt.transaction_hash;
900 let transaction = provider
901 .get_transaction_by_hash(transaction_hash)
902 .await
903 .expect("failed to fetch tx")
904 .expect("tx not included");
905 assert_eq!(
906 transaction.max_fee_per_gas(),
907 max_fee_per_gas.to::<u128>(),
908 "max_fee_per_gas of the transaction should be set to the right value"
909 );
910 assert_eq!(
911 transaction
912 .max_priority_fee_per_gas()
913 .expect("max_priority_fee_per_gas of the transaction should be set"),
914 max_priority_fee_per_gas.to::<u128>(),
915 "max_priority_fee_per_gas of the transaction should be set to the right value"
916 )
917 }
918
919 #[tokio::test(flavor = "multi_thread")]
920 async fn deploy_and_call_with_priority_sync() {
921 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
922 let counter_address =
923 Counter::deploy_builder(provider.clone()).deploy_sync().await.unwrap();
924 let counter_contract = Counter::new(counter_address, provider.clone());
925 let max_fee_per_gas: U256 = parse_units("50", "gwei").unwrap().into();
926 let max_priority_fee_per_gas: U256 = parse_units("0.1", "gwei").unwrap().into();
927 let receipt = counter_contract
928 .increment()
929 .max_fee_per_gas(max_fee_per_gas.to())
930 .max_priority_fee_per_gas(max_priority_fee_per_gas.to())
931 .send_sync()
932 .await
933 .expect("Could not send transaction");
934 let transaction_hash = receipt.transaction_hash;
935 let transaction = provider
936 .get_transaction_by_hash(transaction_hash)
937 .await
938 .expect("failed to fetch tx")
939 .expect("tx not included");
940 assert_eq!(
941 transaction.max_fee_per_gas(),
942 max_fee_per_gas.to::<u128>(),
943 "max_fee_per_gas of the transaction should be set to the right value"
944 );
945 assert_eq!(
946 transaction
947 .max_priority_fee_per_gas()
948 .expect("max_priority_fee_per_gas of the transaction should be set"),
949 max_priority_fee_per_gas.to::<u128>(),
950 "max_priority_fee_per_gas of the transaction should be set to the right value"
951 )
952 }
953
954 sol! {
955 #[sol(rpc, bytecode = "6080604052348015600e575f80fd5b506101448061001c5f395ff3fe60806040526004361061001d575f3560e01c8063785d04f514610021575b5f80fd5b61003461002f3660046100d5565b610036565b005b5f816001600160a01b0316836040515f6040518083038185875af1925050503d805f811461007f576040519150601f19603f3d011682016040523d82523d5f602084013e610084565b606091505b50509050806100d05760405162461bcd60e51b81526020600482015260146024820152734661696c656420746f2073656e64206d6f6e657960601b604482015260640160405180910390fd5b505050565b5f80604083850312156100e6575f80fd5b8235915060208301356001600160a01b0381168114610103575f80fd5b80915050925092905056fea2646970667358221220188e65dcedbc4bd68fdebc795292d5a9bf643385f138383969a28f796ff8858664736f6c63430008190033")]
956 contract SendMoney {
957 function send(uint256 amount, address target) external payable {
958 (bool success, ) = target.call{value: amount}("");
959 require(success, "Failed to send money");
960 }
961 }
962 }
963
964 #[tokio::test]
966 async fn fill_eth_call() {
967 let anvil = Anvil::new().spawn();
968 let pk: PrivateKeySigner =
969 "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".parse().unwrap();
970
971 let wallet = EthereumWallet::new(pk);
972
973 let wallet_provider =
974 ProviderBuilder::new().wallet(wallet).connect_http(anvil.endpoint_url());
975
976 let contract = SendMoney::deploy(wallet_provider.clone()).await.unwrap();
977
978 let tx = contract
979 .send(U256::from(1000000), Address::with_last_byte(1))
980 .into_transaction_request()
981 .value(U256::from(1000000));
982
983 assert!(tx.from.is_none());
984
985 let std_provider = ProviderBuilder::new().connect_http(anvil.endpoint_url());
986 let should_fail = std_provider.estimate_gas(tx.clone()).await.is_err();
987
988 assert!(should_fail);
989
990 let gas = wallet_provider.estimate_gas(tx).await.unwrap();
991
992 assert_eq!(gas, 56555);
993 }
994
995 #[test]
996 fn change_sidecar_7594() {
997 use alloy_consensus::Blob;
998
999 let sidecar =
1000 BlobTransactionSidecarEip7594::new(vec![Blob::repeat_byte(0xAB)], vec![], vec![]);
1001 let call_builder = build_call_builder().sidecar_7594(sidecar.clone());
1002
1003 let set_sidecar = call_builder
1004 .request
1005 .sidecar
1006 .expect("sidecar should be set")
1007 .into_eip7594()
1008 .expect("sidecar should be EIP-7594 variant");
1009
1010 assert_eq!(set_sidecar, sidecar, "EIP-7594 sidecar should match the one we set");
1011 }
1012
1013 #[tokio::test]
1014 async fn decode_eth_call_ret_bytes() {
1015 sol! {
1016 #[derive(Debug, PartialEq)]
1017 #[sol(rpc, bytecode = "0x6080604052348015600e575f5ffd5b506101578061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610029575f3560e01c80630d1d2c641461002d575b5f5ffd5b61003561004b565b6040516100429190610108565b60405180910390f35b61005361007b565b6040518060400160405280602a67ffffffffffffffff16815260200160011515815250905090565b60405180604001604052805f67ffffffffffffffff1681526020015f151581525090565b5f67ffffffffffffffff82169050919050565b6100bb8161009f565b82525050565b5f8115159050919050565b6100d5816100c1565b82525050565b604082015f8201516100ef5f8501826100b2565b50602082015161010260208501826100cc565b50505050565b5f60408201905061011b5f8301846100db565b9291505056fea264697066735822122039acc87c027f3bddf6806ff9914411d4245bdc708bca36a07138a37b1b98573464736f6c634300081c0033")]
1018 contract RetStruct {
1019 struct MyStruct {
1020 uint64 a;
1021 bool b;
1022 }
1023
1024 function retStruct() external pure returns (MyStruct memory) {
1025 return MyStruct(42, true);
1026 }
1027 }
1028 }
1029
1030 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
1031
1032 let contract = RetStruct::deploy(provider.clone()).await.unwrap();
1033
1034 let tx = contract.retStruct().into_transaction_request();
1035
1036 let result =
1037 provider.call(tx).decode_resp::<RetStruct::retStructCall>().await.unwrap().unwrap();
1038
1039 assert_eq!(result, RetStruct::MyStruct { a: 42, b: true });
1040 }
1041}