1use crate::{
2 call_response::FuelCallResponse,
3 execution_script::ExecutableFuelCall,
4 logs::{decode_revert_error, LogDecoder},
5};
6use fuel_gql_client::{
7 fuel_tx::{Contract as FuelContract, Output, Receipt, StorageSlot, Transaction},
8 prelude::PanicReason,
9};
10use fuel_tx::{Address, AssetId, Checkable, Create, Salt};
11use fuels_core::{
12 abi_decoder::ABIDecoder,
13 abi_encoder::{ABIEncoder, UnresolvedBytes},
14 constants::FAILED_TRANSFER_TO_ADDRESS_SIGNAL,
15 parameters::StorageConfiguration,
16 parameters::{CallParameters, TxParameters},
17 tx::{Bytes32, ContractId},
18 Parameterize, Selector, Token, Tokenizable,
19};
20use fuels_signers::{
21 provider::{Provider, TransactionCost},
22 Signer, WalletUnlocked,
23};
24use fuels_types::{
25 bech32::Bech32ContractId,
26 errors::Error,
27 param_types::{ParamType, ReturnLocation},
28};
29use std::{
30 collections::{HashMap, HashSet},
31 fmt::Debug,
32 fs,
33 marker::PhantomData,
34 panic,
35 path::Path,
36 str::FromStr,
37};
38
39pub const DEFAULT_TX_DEP_ESTIMATION_ATTEMPTS: u64 = 10;
41
42#[derive(Debug, Clone, Default)]
44pub struct CompiledContract {
45 pub raw: Vec<u8>,
46 pub salt: Salt,
47 pub storage_slots: Vec<StorageSlot>,
48}
49
50pub struct Contract {
55 pub compiled_contract: CompiledContract,
56 pub wallet: WalletUnlocked,
57}
58
59impl Contract {
60 pub fn new(compiled_contract: CompiledContract, wallet: WalletUnlocked) -> Self {
61 Self {
62 compiled_contract,
63 wallet,
64 }
65 }
66
67 pub fn compute_contract_id_and_state_root(
68 compiled_contract: &CompiledContract,
69 ) -> (ContractId, Bytes32) {
70 let fuel_contract = FuelContract::from(compiled_contract.raw.clone());
71 let root = fuel_contract.root();
72 let state_root = FuelContract::initial_state_root(compiled_contract.storage_slots.iter());
73
74 let contract_id = fuel_contract.id(&compiled_contract.salt, &root, &state_root);
75
76 (contract_id, state_root)
77 }
78
79 pub fn method_hash<D: Tokenizable + Parameterize + Debug>(
99 provider: &Provider,
100 contract_id: Bech32ContractId,
101 wallet: &WalletUnlocked,
102 signature: Selector,
103 args: &[Token],
104 log_decoder: LogDecoder,
105 ) -> Result<ContractCallHandler<D>, Error> {
106 let encoded_selector = signature;
107
108 let tx_parameters = TxParameters::default();
109 let call_parameters = CallParameters::default();
110
111 let compute_custom_input_offset = Contract::should_compute_custom_input_offset(args);
112
113 let unresolved_bytes = ABIEncoder::encode(args)?;
114 let contract_call = ContractCall {
115 contract_id,
116 encoded_selector,
117 encoded_args: unresolved_bytes,
118 call_parameters,
119 compute_custom_input_offset,
120 variable_outputs: None,
121 message_outputs: None,
122 external_contracts: vec![],
123 output_param: D::param_type(),
124 };
125
126 Ok(ContractCallHandler {
127 contract_call,
128 tx_parameters,
129 wallet: wallet.clone(),
130 provider: provider.clone(),
131 datatype: PhantomData,
132 log_decoder,
133 })
134 }
135
136 fn should_compute_custom_input_offset(args: &[Token]) -> bool {
140 args.len() > 1
141 || args.iter().any(|t| {
142 matches!(
143 t,
144 Token::String(_)
145 | Token::Struct(_)
146 | Token::Enum(_)
147 | Token::B256(_)
148 | Token::Tuple(_)
149 | Token::Array(_)
150 | Token::Byte(_)
151 | Token::Vector(_)
152 )
153 })
154 }
155
156 pub async fn deploy(
158 binary_filepath: &str,
159 wallet: &WalletUnlocked,
160 params: TxParameters,
161 storage_configuration: StorageConfiguration,
162 ) -> Result<Bech32ContractId, Error> {
163 let mut compiled_contract =
164 Contract::load_contract(binary_filepath, &storage_configuration.storage_path)?;
165
166 Self::merge_storage_vectors(&storage_configuration, &mut compiled_contract);
167
168 Self::deploy_loaded(&(compiled_contract), wallet, params).await
169 }
170
171 pub async fn deploy_with_parameters(
173 binary_filepath: &str,
174 wallet: &WalletUnlocked,
175 params: TxParameters,
176 storage_configuration: StorageConfiguration,
177 salt: Salt,
178 ) -> Result<Bech32ContractId, Error> {
179 let mut compiled_contract = Contract::load_contract_with_parameters(
180 binary_filepath,
181 &storage_configuration.storage_path,
182 salt,
183 )?;
184
185 Self::merge_storage_vectors(&storage_configuration, &mut compiled_contract);
186
187 Self::deploy_loaded(&(compiled_contract), wallet, params).await
188 }
189
190 fn merge_storage_vectors(
191 storage_configuration: &StorageConfiguration,
192 compiled_contract: &mut CompiledContract,
193 ) {
194 match &storage_configuration.manual_storage_vec {
195 Some(storage) if !storage.is_empty() => {
196 compiled_contract.storage_slots =
197 Self::merge_storage_slots(storage, &compiled_contract.storage_slots);
198 }
199 _ => {}
200 }
201 }
202
203 pub async fn deploy_loaded(
207 compiled_contract: &CompiledContract,
208 wallet: &WalletUnlocked,
209 params: TxParameters,
210 ) -> Result<Bech32ContractId, Error> {
211 let (mut tx, contract_id) =
212 Self::contract_deployment_transaction(compiled_contract, params).await?;
213
214 wallet.add_fee_coins(&mut tx, 0, 1).await?;
218 wallet.sign_transaction(&mut tx).await?;
219
220 let provider = wallet.get_provider()?;
221 let chain_info = provider.chain_info().await?;
222
223 tx.check_without_signatures(
224 chain_info.latest_block.header.height,
225 &chain_info.consensus_parameters,
226 )?;
227 provider.send_transaction(&tx).await?;
228
229 Ok(contract_id)
230 }
231
232 pub fn load_contract(
233 binary_filepath: &str,
234 storage_path: &Option<String>,
235 ) -> Result<CompiledContract, Error> {
236 Self::load_contract_with_parameters(binary_filepath, storage_path, Salt::from([0u8; 32]))
237 }
238
239 pub fn load_contract_with_parameters(
240 binary_filepath: &str,
241 storage_path: &Option<String>,
242 salt: Salt,
243 ) -> Result<CompiledContract, Error> {
244 let extension = Path::new(binary_filepath).extension().unwrap();
245 if extension != "bin" {
246 return Err(Error::InvalidData(extension.to_str().unwrap().to_owned()));
247 }
248 let bin = std::fs::read(binary_filepath)?;
249
250 let storage = match storage_path {
251 Some(path) if Path::new(&path).exists() => Self::get_storage_vec(path),
252 Some(path) if !Path::new(&path).exists() => {
253 return Err(Error::InvalidData(path.to_owned()));
254 }
255 _ => {
256 vec![]
257 }
258 };
259
260 Ok(CompiledContract {
261 raw: bin,
262 salt,
263 storage_slots: storage,
264 })
265 }
266
267 fn merge_storage_slots(
268 manual_storage: &[StorageSlot],
269 contract_storage: &[StorageSlot],
270 ) -> Vec<StorageSlot> {
271 let mut return_storage: Vec<StorageSlot> = manual_storage.to_owned();
272 let keys: HashSet<Bytes32> = manual_storage.iter().map(|slot| *slot.key()).collect();
273
274 contract_storage.iter().for_each(|slot| {
275 if !keys.contains(slot.key()) {
276 return_storage.push(slot.clone())
277 }
278 });
279
280 return_storage
281 }
282
283 pub async fn contract_deployment_transaction(
285 compiled_contract: &CompiledContract,
286 params: TxParameters,
287 ) -> Result<(Create, Bech32ContractId), Error> {
288 let bytecode_witness_index = 0;
289 let storage_slots: Vec<StorageSlot> = compiled_contract.storage_slots.clone();
290 let witnesses = vec![compiled_contract.raw.clone().into()];
291
292 let (contract_id, state_root) = Self::compute_contract_id_and_state_root(compiled_contract);
293
294 let outputs = vec![Output::contract_created(contract_id, state_root)];
295
296 let tx = Transaction::create(
297 params.gas_price,
298 params.gas_limit,
299 params.maturity,
300 bytecode_witness_index,
301 compiled_contract.salt,
302 storage_slots,
303 vec![],
304 outputs,
305 witnesses,
306 );
307
308 Ok((tx, contract_id.into()))
309 }
310
311 fn get_storage_vec(storage_path: &str) -> Vec<StorageSlot> {
312 let mut return_storage: Vec<StorageSlot> = vec![];
313
314 let storage_json_string = fs::read_to_string(storage_path).expect("Unable to read file");
315
316 let storage: serde_json::Value = serde_json::from_str(storage_json_string.as_str())
317 .expect("JSON was not well-formatted");
318
319 for slot in storage.as_array().unwrap() {
320 return_storage.push(StorageSlot::new(
321 Bytes32::from_str(slot["key"].as_str().unwrap()).unwrap(),
322 Bytes32::from_str(slot["value"].as_str().unwrap()).unwrap(),
323 ));
324 }
325
326 return_storage
327 }
328}
329
330#[derive(Debug)]
331pub struct ContractCall {
333 pub contract_id: Bech32ContractId,
334 pub encoded_args: UnresolvedBytes,
335 pub encoded_selector: Selector,
336 pub call_parameters: CallParameters,
337 pub compute_custom_input_offset: bool,
338 pub variable_outputs: Option<Vec<Output>>,
339 pub message_outputs: Option<Vec<Output>>,
340 pub external_contracts: Vec<Bech32ContractId>,
341 pub output_param: ParamType,
342}
343
344impl ContractCall {
345 pub fn with_contract_id(self, contract_id: Bech32ContractId) -> Self {
346 ContractCall {
347 contract_id,
348 ..self
349 }
350 }
351
352 pub fn with_external_contracts(
353 self,
354 external_contracts: Vec<Bech32ContractId>,
355 ) -> ContractCall {
356 ContractCall {
357 external_contracts,
358 ..self
359 }
360 }
361
362 pub fn with_variable_outputs(self, variable_outputs: Vec<Output>) -> ContractCall {
363 ContractCall {
364 variable_outputs: Some(variable_outputs),
365 ..self
366 }
367 }
368
369 pub fn with_message_outputs(self, message_outputs: Vec<Output>) -> ContractCall {
370 ContractCall {
371 message_outputs: Some(message_outputs),
372 ..self
373 }
374 }
375
376 pub fn with_call_parameters(self, call_parameters: CallParameters) -> ContractCall {
377 ContractCall {
378 call_parameters,
379 ..self
380 }
381 }
382
383 pub fn append_variable_outputs(&mut self, num: u64) {
384 let new_variable_outputs = vec![
385 Output::Variable {
386 amount: 0,
387 to: Address::zeroed(),
388 asset_id: AssetId::default(),
389 };
390 num as usize
391 ];
392
393 match self.variable_outputs {
394 Some(ref mut outputs) => outputs.extend(new_variable_outputs),
395 None => self.variable_outputs = Some(new_variable_outputs),
396 }
397 }
398
399 pub fn append_external_contracts(&mut self, contract_id: Bech32ContractId) {
400 self.external_contracts.push(contract_id)
401 }
402
403 pub fn append_message_outputs(&mut self, num: u64) {
404 let new_message_outputs = vec![
405 Output::Message {
406 recipient: Address::zeroed(),
407 amount: 0,
408 };
409 num as usize
410 ];
411
412 match self.message_outputs {
413 Some(ref mut outputs) => outputs.extend(new_message_outputs),
414 None => self.message_outputs = Some(new_message_outputs),
415 }
416 }
417
418 fn is_missing_output_variables(receipts: &[Receipt]) -> bool {
419 receipts.iter().any(
420 |r| matches!(r, Receipt::Revert { ra, .. } if *ra == FAILED_TRANSFER_TO_ADDRESS_SIGNAL),
421 )
422 }
423
424 fn find_contract_not_in_inputs(receipts: &[Receipt]) -> Option<&Receipt> {
425 receipts.iter().find(
426 |r| matches!(r, Receipt::Panic { reason, .. } if *reason.reason() == PanicReason::ContractNotInInputs ),
427 )
428 }
429}
430
431pub fn get_decoded_output(
434 receipts: &mut Vec<Receipt>,
435 contract_id: Option<&Bech32ContractId>,
436 output_param: &ParamType,
437) -> Result<Token, Error> {
438 let contract_id: ContractId = match contract_id {
440 Some(contract_id) => contract_id.into(),
441 None => ContractId::new([0u8; 32]),
443 };
444 let (encoded_value, index) = match output_param.get_return_location() {
445 ReturnLocation::ReturnData => {
446 match receipts.iter().position(|receipt| {
447 matches!(receipt,
448 Receipt::ReturnData { id, data, .. } if *id == contract_id && !data.is_empty())
449 }) {
450 Some(idx) => (
451 receipts[idx]
452 .data()
453 .expect("ReturnData should have data")
454 .to_vec(),
455 Some(idx),
456 ),
457 None => (vec![], None),
458 }
459 }
460 ReturnLocation::Return => {
461 match receipts.iter().position(|receipt| {
462 matches!(receipt,
463 Receipt::Return { id, ..} if *id == contract_id)
464 }) {
465 Some(idx) => (
466 receipts[idx]
467 .val()
468 .expect("Return should have val")
469 .to_be_bytes()
470 .to_vec(),
471 Some(idx),
472 ),
473 None => (vec![], None),
474 }
475 }
476 };
477 if let Some(i) = index {
478 receipts.remove(i);
479 }
480
481 let decoded_value = ABIDecoder::decode_single(output_param, &encoded_value)?;
482 Ok(decoded_value)
483}
484
485#[derive(Debug)]
486#[must_use = "contract calls do nothing unless you `call` them"]
487pub struct ContractCallHandler<D> {
489 pub contract_call: ContractCall,
490 pub tx_parameters: TxParameters,
491 pub wallet: WalletUnlocked,
492 pub provider: Provider,
493 pub datatype: PhantomData<D>,
494 pub log_decoder: LogDecoder,
495}
496
497impl<D> ContractCallHandler<D>
498where
499 D: Tokenizable + Debug,
500{
501 pub fn set_contracts(mut self, contract_ids: &[Bech32ContractId]) -> Self {
513 self.contract_call.external_contracts = contract_ids.to_vec();
514 self
515 }
516
517 pub fn append_contract(mut self, contract_id: Bech32ContractId) -> Self {
530 self.contract_call.append_external_contracts(contract_id);
531 self
532 }
533
534 pub fn tx_params(mut self, params: TxParameters) -> Self {
542 self.tx_parameters = params;
543 self
544 }
545
546 pub fn call_params(mut self, params: CallParameters) -> Self {
554 self.contract_call.call_parameters = params;
555 self
556 }
557
558 pub fn append_variable_outputs(mut self, num: u64) -> Self {
567 self.contract_call.append_variable_outputs(num);
568 self
569 }
570
571 pub fn append_message_outputs(mut self, num: u64) -> Self {
580 self.contract_call.append_message_outputs(num);
581 self
582 }
583
584 async fn call_or_simulate(&self, simulate: bool) -> Result<FuelCallResponse<D>, Error> {
590 let script = self.get_executable_call().await?;
591
592 let receipts = if simulate {
593 script.simulate(&self.provider).await?
594 } else {
595 script.execute(&self.provider).await?
596 };
597
598 self.get_response(receipts)
599 }
600
601 pub async fn get_executable_call(&self) -> Result<ExecutableFuelCall, Error> {
603 ExecutableFuelCall::from_contract_calls(
604 std::slice::from_ref(&self.contract_call),
605 &self.tx_parameters,
606 &self.wallet,
607 )
608 .await
609 }
610
611 pub async fn call(self) -> Result<FuelCallResponse<D>, Error> {
613 Self::call_or_simulate(&self, false)
614 .await
615 .map_err(|err| decode_revert_error(err, &self.log_decoder))
616 }
617
618 pub async fn simulate(self) -> Result<FuelCallResponse<D>, Error> {
624 Self::call_or_simulate(&self, true)
625 .await
626 .map_err(|err| decode_revert_error(err, &self.log_decoder))
627 }
628
629 async fn simulate_without_decode(&self) -> Result<(), Error> {
631 let script = self.get_executable_call().await?;
632 let provider = self.wallet.get_provider()?;
633
634 script.simulate(provider).await?;
635
636 Ok(())
637 }
638
639 pub async fn estimate_tx_dependencies(
642 mut self,
643 max_attempts: Option<u64>,
644 ) -> Result<Self, Error> {
645 let attempts = max_attempts.unwrap_or(DEFAULT_TX_DEP_ESTIMATION_ATTEMPTS);
646
647 for _ in 0..attempts {
648 let result = self.simulate_without_decode().await;
649
650 match result {
651 Err(Error::RevertTransactionError(_, receipts))
652 if ContractCall::is_missing_output_variables(&receipts) =>
653 {
654 self = self.append_variable_outputs(1);
655 }
656
657 Err(Error::RevertTransactionError(_, ref receipts)) => {
658 if let Some(receipt) = ContractCall::find_contract_not_in_inputs(receipts) {
659 let contract_id = Bech32ContractId::from(*receipt.contract_id().unwrap());
660 self = self.append_contract(contract_id);
661 } else {
662 return Err(result.expect_err("Couldn't estimate tx dependencies because we couldn't find the missing contract input"));
663 }
664 }
665
666 Err(e) => return Err(e),
667 _ => return Ok(self),
668 }
669 }
670
671 match self.call_or_simulate(true).await {
673 Ok(_) => Ok(self),
674 Err(e) => Err(e),
675 }
676 }
677
678 pub async fn estimate_transaction_cost(
680 &self,
681 tolerance: Option<f64>,
682 ) -> Result<TransactionCost, Error> {
683 let script = self.get_executable_call().await?;
684
685 let transaction_cost = self
686 .provider
687 .estimate_transaction_cost(&script.tx, tolerance)
688 .await?;
689
690 Ok(transaction_cost)
691 }
692
693 pub fn get_response(&self, mut receipts: Vec<Receipt>) -> Result<FuelCallResponse<D>, Error> {
695 let token = get_decoded_output(
696 &mut receipts,
697 Some(&self.contract_call.contract_id),
698 &self.contract_call.output_param,
699 )?;
700 Ok(FuelCallResponse::new(
701 D::from_token(token)?,
702 receipts,
703 self.log_decoder.clone(),
704 ))
705 }
706}
707
708#[derive(Debug)]
709#[must_use = "contract calls do nothing unless you `call` them"]
710pub struct MultiContractCallHandler {
712 pub contract_calls: Vec<ContractCall>,
713 pub log_decoder: LogDecoder,
714 pub tx_parameters: TxParameters,
715 pub wallet: WalletUnlocked,
716}
717
718impl MultiContractCallHandler {
719 pub fn new(wallet: WalletUnlocked) -> Self {
720 Self {
721 contract_calls: vec![],
722 tx_parameters: TxParameters::default(),
723 wallet,
724 log_decoder: LogDecoder {
725 logs_map: HashMap::new(),
726 },
727 }
728 }
729
730 pub fn add_call<D: Tokenizable>(&mut self, call_handler: ContractCallHandler<D>) -> &mut Self {
733 self.log_decoder.merge(&call_handler.log_decoder);
734 self.contract_calls.push(call_handler.contract_call);
735 self
736 }
737
738 pub fn tx_params(&mut self, params: TxParameters) -> &mut Self {
741 self.tx_parameters = params;
742 self
743 }
744
745 pub async fn get_executable_call(&self) -> Result<ExecutableFuelCall, Error> {
747 if self.contract_calls.is_empty() {
748 panic!("No calls added. Have you used '.add_calls()'?");
749 }
750
751 ExecutableFuelCall::from_contract_calls(
752 &self.contract_calls,
753 &self.tx_parameters,
754 &self.wallet,
755 )
756 .await
757 }
758
759 pub async fn call<D: Tokenizable + Debug>(&self) -> Result<FuelCallResponse<D>, Error> {
761 Self::call_or_simulate(self, false)
762 .await
763 .map_err(|err| decode_revert_error(err, &self.log_decoder))
764 }
765
766 pub async fn simulate<D: Tokenizable + Debug>(&self) -> Result<FuelCallResponse<D>, Error> {
772 Self::call_or_simulate(self, true)
773 .await
774 .map_err(|err| decode_revert_error(err, &self.log_decoder))
775 }
776
777 async fn call_or_simulate<D: Tokenizable + Debug>(
778 &self,
779 simulate: bool,
780 ) -> Result<FuelCallResponse<D>, Error> {
781 let script = self.get_executable_call().await?;
782
783 let provider = self.wallet.get_provider()?;
784
785 let receipts = if simulate {
786 script.simulate(provider).await?
787 } else {
788 script.execute(provider).await?
789 };
790
791 self.get_response(receipts)
792 }
793
794 async fn simulate_without_decode(&self) -> Result<(), Error> {
796 let script = self.get_executable_call().await?;
797 let provider = self.wallet.get_provider()?;
798
799 script.simulate(provider).await?;
800
801 Ok(())
802 }
803
804 pub async fn estimate_tx_dependencies(
807 mut self,
808 max_attempts: Option<u64>,
809 ) -> Result<Self, Error> {
810 let attempts = max_attempts.unwrap_or(DEFAULT_TX_DEP_ESTIMATION_ATTEMPTS);
811
812 for _ in 0..attempts {
813 let result = self.simulate_without_decode().await;
814
815 match result {
816 Err(Error::RevertTransactionError(_, receipts))
817 if ContractCall::is_missing_output_variables(&receipts) =>
818 {
819 self.contract_calls
820 .iter_mut()
821 .take(1)
822 .for_each(|call| call.append_variable_outputs(1));
823 }
824
825 Err(Error::RevertTransactionError(_, ref receipts)) => {
826 if let Some(receipt) = ContractCall::find_contract_not_in_inputs(receipts) {
827 let contract_id = Bech32ContractId::from(*receipt.contract_id().unwrap());
828 self.contract_calls
829 .iter_mut()
830 .take(1)
831 .for_each(|call| call.append_external_contracts(contract_id.clone()));
832 } else {
833 return Err(result.expect_err("Couldn't estimate tx dependencies because we couldn't find the missing contract input"));
834 }
835 }
836
837 Err(e) => return Err(e),
838 _ => return Ok(self),
839 }
840 }
841
842 Ok(self)
843 }
844
845 pub async fn estimate_transaction_cost(
847 &self,
848 tolerance: Option<f64>,
849 ) -> Result<TransactionCost, Error> {
850 let script = self.get_executable_call().await?;
851
852 let transaction_cost = self
853 .wallet
854 .get_provider()?
855 .estimate_transaction_cost(&script.tx, tolerance)
856 .await?;
857
858 Ok(transaction_cost)
859 }
860
861 pub fn get_response<D: Tokenizable + Debug>(
863 &self,
864 mut receipts: Vec<Receipt>,
865 ) -> Result<FuelCallResponse<D>, Error> {
866 let mut final_tokens = vec![];
867
868 for call in self.contract_calls.iter() {
869 let decoded =
870 get_decoded_output(&mut receipts, Some(&call.contract_id), &call.output_param)?;
871
872 final_tokens.push(decoded.clone());
873 }
874
875 let tokens_as_tuple = Token::Tuple(final_tokens);
876 let response = FuelCallResponse::<D>::new(
877 D::from_token(tokens_as_tuple)?,
878 receipts,
879 self.log_decoder.clone(),
880 );
881
882 Ok(response)
883 }
884}
885
886#[cfg(test)]
887mod test {
888 use fuels_test_helpers::launch_provider_and_get_wallet;
889
890 use super::*;
891
892 #[tokio::test]
893 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidData(\"json\")")]
894 async fn deploy_panics_on_non_binary_file() {
895 let wallet = launch_provider_and_get_wallet().await;
896
897 Contract::deploy(
899 "tests/types/contract_output_test/out/debug/contract_output_test-abi.json",
900 &wallet,
901 TxParameters::default(),
902 StorageConfiguration::default(),
903 )
904 .await
905 .unwrap();
906 }
907
908 #[tokio::test]
909 #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: InvalidData(\"json\")")]
910 async fn deploy_with_salt_panics_on_non_binary_file() {
911 let wallet = launch_provider_and_get_wallet().await;
912
913 Contract::deploy_with_parameters(
915 "tests/types/contract_output_test/out/debug/contract_output_test-abi.json",
916 &wallet,
917 TxParameters::default(),
918 StorageConfiguration::default(),
919 Salt::default(),
920 )
921 .await
922 .unwrap();
923 }
924}