1use core::{fmt::Debug, marker::PhantomData};
2use std::sync::Arc;
3
4use fuels_accounts::{Account, provider::TransactionCost};
5use fuels_core::{
6 codec::{ABIEncoder, DecoderConfig, EncoderConfig, LogDecoder},
7 traits::{Parameterize, Signer, Tokenizable},
8 types::{
9 Address, AssetId, Bytes32, ContractId, Selector, Token,
10 errors::{Error, Result, error, transaction::Reason},
11 input::Input,
12 output::Output,
13 transaction::{ScriptTransaction, Transaction, TxPolicies},
14 transaction_builders::{
15 BuildableTransaction, ScriptBuildStrategy, ScriptTransactionBuilder,
16 TransactionBuilder, VariableOutputPolicy,
17 },
18 tx_status::TxStatus,
19 },
20};
21
22use crate::{
23 calls::{
24 CallParameters, ContractCall, Execution, ExecutionType, ScriptCall,
25 receipt_parser::ReceiptParser,
26 traits::{ContractDependencyConfigurator, ResponseParser, TransactionTuner},
27 utils::find_ids_of_missing_contracts,
28 },
29 responses::{CallResponse, SubmitResponse},
30};
31
32pub trait ContractDependency {
35 fn id(&self) -> ContractId;
36 fn log_decoder(&self) -> LogDecoder;
37}
38
39#[derive(Debug, Clone)]
40#[must_use = "contract calls do nothing unless you `call` them"]
41pub struct CallHandler<A, C, T> {
43 pub account: A,
44 pub call: C,
45 pub tx_policies: TxPolicies,
46 pub log_decoder: LogDecoder,
47 pub datatype: PhantomData<T>,
48 decoder_config: DecoderConfig,
49 cached_tx_id: Option<Bytes32>,
51 variable_output_policy: VariableOutputPolicy,
52 unresolved_signers: Vec<Arc<dyn Signer + Send + Sync>>,
53}
54
55impl<A, C, T> CallHandler<A, C, T> {
56 pub fn with_tx_policies(mut self, tx_policies: TxPolicies) -> Self {
63 self.tx_policies = tx_policies;
64 self
65 }
66
67 pub fn with_decoder_config(mut self, decoder_config: DecoderConfig) -> Self {
68 self.decoder_config = decoder_config;
69 self.log_decoder.set_decoder_config(decoder_config);
70 self
71 }
72
73 pub fn with_variable_output_policy(mut self, variable_outputs: VariableOutputPolicy) -> Self {
81 self.variable_output_policy = variable_outputs;
82 self
83 }
84
85 pub fn add_signer(mut self, signer: impl Signer + Send + Sync + 'static) -> Self {
86 self.unresolved_signers.push(Arc::new(signer));
87 self
88 }
89}
90
91impl<A, C, T> CallHandler<A, C, T>
92where
93 A: Account,
94 C: TransactionTuner,
95 T: Tokenizable + Parameterize + Debug,
96{
97 pub async fn transaction_builder(&self) -> Result<ScriptTransactionBuilder> {
98 let mut tb = self
99 .call
100 .transaction_builder(self.tx_policies, self.variable_output_policy, &self.account)
101 .await?;
102
103 tb.add_signers(&self.unresolved_signers)?;
104
105 Ok(tb)
106 }
107
108 pub async fn build_tx(&self) -> Result<ScriptTransaction> {
110 let tb = self.transaction_builder().await?;
111
112 self.call.build_tx(tb, &self.account).await
113 }
114
115 pub async fn estimate_transaction_cost(
117 &self,
118 tolerance: Option<f64>,
119 block_horizon: Option<u32>,
120 ) -> Result<TransactionCost> {
121 let tx = self.build_tx().await?;
122 let provider = self.account.try_provider()?;
123
124 let transaction_cost = provider
125 .estimate_transaction_cost(tx, tolerance, block_horizon)
126 .await?;
127
128 Ok(transaction_cost)
129 }
130}
131
132impl<A, C, T> CallHandler<A, C, T>
133where
134 A: Account,
135 C: ContractDependencyConfigurator + TransactionTuner + ResponseParser,
136 T: Tokenizable + Parameterize + Debug,
137{
138 pub fn with_contract_ids(mut self, contract_ids: &[ContractId]) -> Self {
150 self.call = self.call.with_external_contracts(contract_ids.to_vec());
151
152 self
153 }
154
155 pub fn with_contracts(mut self, contracts: &[&dyn ContractDependency]) -> Self {
164 self.call = self
165 .call
166 .with_external_contracts(contracts.iter().map(|c| c.id()).collect());
167 for c in contracts {
168 self.log_decoder.merge(c.log_decoder());
169 }
170
171 self
172 }
173
174 pub async fn call(mut self) -> Result<CallResponse<T>> {
176 let tx = self.build_tx().await?;
177 let provider = self.account.try_provider()?;
178
179 let consensus_parameters = provider.consensus_parameters().await?;
180 let chain_id = consensus_parameters.chain_id();
181 self.cached_tx_id = Some(tx.id(chain_id));
182
183 let tx_status = provider.send_transaction_and_await_commit(tx).await?;
184
185 self.get_response(tx_status)
186 }
187
188 pub async fn submit(mut self) -> Result<SubmitResponse<A, C, T>> {
189 let tx = self.build_tx().await?;
190 let provider = self.account.try_provider()?;
191
192 let tx_id = provider.send_transaction(tx.clone()).await?;
193 self.cached_tx_id = Some(tx_id);
194
195 Ok(SubmitResponse::<A, C, T>::new(tx_id, self))
196 }
197
198 pub async fn simulate(
201 &mut self,
202 Execution {
203 execution_type,
204 at_height,
205 }: Execution,
206 ) -> Result<CallResponse<T>> {
207 let provider = self.account.try_provider()?;
208
209 let tx_status = if let ExecutionType::StateReadOnly = execution_type {
210 let tx = self
211 .transaction_builder()
212 .await?
213 .with_build_strategy(ScriptBuildStrategy::StateReadOnly)
214 .build(provider)
215 .await?;
216
217 provider.dry_run_opt(tx, false, Some(0), at_height).await?
218 } else {
219 let tx = self.build_tx().await?;
220 provider.dry_run_opt(tx, true, None, at_height).await?
221 };
222
223 self.get_response(tx_status)
224 }
225
226 pub fn get_response(&self, tx_status: TxStatus) -> Result<CallResponse<T>> {
228 let success = tx_status.take_success_checked(Some(&self.log_decoder))?;
229
230 let token =
231 self.call
232 .parse_call(&success.receipts, self.decoder_config, &T::param_type())?;
233
234 Ok(CallResponse {
235 value: T::from_token(token)?,
236 log_decoder: self.log_decoder.clone(),
237 tx_id: self.cached_tx_id,
238 tx_status: success,
239 })
240 }
241
242 pub async fn determine_missing_contracts(mut self) -> Result<Self> {
243 match self.simulate(Execution::realistic()).await {
244 Ok(_) => Ok(self),
245
246 Err(Error::Transaction(Reason::Failure { ref receipts, .. })) => {
247 for contract_id in find_ids_of_missing_contracts(receipts) {
248 self.call.append_external_contract(contract_id);
249 }
250
251 Ok(self)
252 }
253
254 Err(other_error) => Err(other_error),
255 }
256 }
257}
258
259impl<A, T> CallHandler<A, ContractCall, T>
260where
261 A: Account,
262 T: Tokenizable + Parameterize + Debug,
263{
264 pub fn new_contract_call(
265 contract_id: ContractId,
266 account: A,
267 encoded_selector: Selector,
268 args: &[Token],
269 log_decoder: LogDecoder,
270 is_payable: bool,
271 encoder_config: EncoderConfig,
272 ) -> Self {
273 let call = ContractCall {
274 contract_id,
275 encoded_selector,
276 encoded_args: ABIEncoder::new(encoder_config).encode(args),
277 call_parameters: CallParameters::default(),
278 external_contracts: vec![],
279 output_param: T::param_type(),
280 is_payable,
281 custom_assets: Default::default(),
282 inputs: vec![],
283 outputs: vec![],
284 };
285 CallHandler {
286 account,
287 call,
288 tx_policies: TxPolicies::default(),
289 log_decoder,
290 datatype: PhantomData,
291 decoder_config: DecoderConfig::default(),
292 cached_tx_id: None,
293 variable_output_policy: VariableOutputPolicy::default(),
294 unresolved_signers: vec![],
295 }
296 }
297
298 pub fn add_custom_asset(mut self, asset_id: AssetId, amount: u64, to: Option<Address>) -> Self {
315 self.call.add_custom_asset(asset_id, amount, to);
316 self
317 }
318
319 pub fn is_payable(&self) -> bool {
320 self.call.is_payable
321 }
322
323 pub fn call_params(mut self, params: CallParameters) -> Result<Self> {
331 if !self.is_payable() && params.amount() > 0 {
332 return Err(error!(Other, "assets forwarded to non-payable method"));
333 }
334 self.call.call_parameters = params;
335
336 Ok(self)
337 }
338
339 pub fn with_outputs(mut self, outputs: Vec<Output>) -> Self {
342 self.call = self.call.with_outputs(outputs);
343 self
344 }
345
346 pub fn with_inputs(mut self, inputs: Vec<Input>) -> Self {
349 self.call = self.call.with_inputs(inputs);
350 self
351 }
352}
353
354impl<A, T> CallHandler<A, ScriptCall, T>
355where
356 A: Account,
357 T: Parameterize + Tokenizable + Debug,
358{
359 pub fn new_script_call(
360 script_binary: Vec<u8>,
361 encoded_args: Result<Vec<u8>>,
362 account: A,
363 log_decoder: LogDecoder,
364 ) -> Self {
365 let call = ScriptCall {
366 script_binary,
367 encoded_args,
368 inputs: vec![],
369 outputs: vec![],
370 external_contracts: vec![],
371 };
372
373 Self {
374 account,
375 call,
376 tx_policies: TxPolicies::default(),
377 log_decoder,
378 datatype: PhantomData,
379 decoder_config: DecoderConfig::default(),
380 cached_tx_id: None,
381 variable_output_policy: VariableOutputPolicy::default(),
382 unresolved_signers: vec![],
383 }
384 }
385
386 pub fn with_outputs(mut self, outputs: Vec<Output>) -> Self {
389 self.call = self.call.with_outputs(outputs);
390 self
391 }
392
393 pub fn with_inputs(mut self, inputs: Vec<Input>) -> Self {
396 self.call = self.call.with_inputs(inputs);
397 self
398 }
399}
400
401impl<A> CallHandler<A, Vec<ContractCall>, ()>
402where
403 A: Account,
404{
405 pub fn new_multi_call(account: A) -> Self {
406 Self {
407 account,
408 call: vec![],
409 tx_policies: TxPolicies::default(),
410 log_decoder: LogDecoder::new(Default::default(), Default::default()),
411 datatype: PhantomData,
412 decoder_config: DecoderConfig::default(),
413 cached_tx_id: None,
414 variable_output_policy: VariableOutputPolicy::default(),
415 unresolved_signers: vec![],
416 }
417 }
418
419 fn append_external_contract(mut self, contract_id: ContractId) -> Result<Self> {
420 if self.call.is_empty() {
421 return Err(error!(
422 Other,
423 "no calls added. Have you used '.add_calls()'?"
424 ));
425 }
426
427 self.call
428 .iter_mut()
429 .take(1)
430 .for_each(|call| call.append_external_contract(contract_id));
431
432 Ok(self)
433 }
434
435 pub fn add_call(
439 mut self,
440 call_handler: CallHandler<impl Account, ContractCall, impl Tokenizable>,
441 ) -> Self {
442 self.log_decoder.merge(call_handler.log_decoder);
443 self.call.push(call_handler.call);
444 self.unresolved_signers
445 .extend(call_handler.unresolved_signers);
446
447 self
448 }
449
450 pub async fn call<T: Tokenizable + Debug>(mut self) -> Result<CallResponse<T>> {
452 let tx = self.build_tx().await?;
453
454 let provider = self.account.try_provider()?;
455 let consensus_parameters = provider.consensus_parameters().await?;
456 let chain_id = consensus_parameters.chain_id();
457
458 self.cached_tx_id = Some(tx.id(chain_id));
459
460 let tx_status = provider.send_transaction_and_await_commit(tx).await?;
461
462 self.get_response(tx_status)
463 }
464
465 pub async fn submit(mut self) -> Result<SubmitResponse<A, Vec<ContractCall>, ()>> {
466 let tx = self.build_tx().await?;
467 let provider = self.account.try_provider()?;
468
469 let tx_id = provider.send_transaction(tx).await?;
470 self.cached_tx_id = Some(tx_id);
471
472 Ok(SubmitResponse::<A, Vec<ContractCall>, ()>::new(tx_id, self))
473 }
474
475 pub async fn simulate<T: Tokenizable + Debug>(
481 &mut self,
482 Execution {
483 execution_type,
484 at_height,
485 }: Execution,
486 ) -> Result<CallResponse<T>> {
487 let provider = self.account.try_provider()?;
488
489 let tx_status = if let ExecutionType::StateReadOnly = execution_type {
490 let tx = self
491 .transaction_builder()
492 .await?
493 .with_build_strategy(ScriptBuildStrategy::StateReadOnly)
494 .build(provider)
495 .await?;
496
497 provider.dry_run_opt(tx, false, Some(0), at_height).await?
498 } else {
499 let tx = self.build_tx().await?;
500 provider.dry_run_opt(tx, true, None, at_height).await?
501 };
502
503 self.get_response(tx_status)
504 }
505
506 async fn simulate_without_decode(&self) -> Result<()> {
508 let provider = self.account.try_provider()?;
509 let tx = self.build_tx().await?;
510
511 provider.dry_run(tx).await?.check(None)?;
512
513 Ok(())
514 }
515
516 pub fn get_response<T: Tokenizable + Debug>(
518 &self,
519 tx_status: TxStatus,
520 ) -> Result<CallResponse<T>> {
521 let success = tx_status.take_success_checked(Some(&self.log_decoder))?;
522 let mut receipt_parser = ReceiptParser::new(&success.receipts, self.decoder_config);
523
524 let final_tokens = self
525 .call
526 .iter()
527 .map(|call| receipt_parser.parse_call(call.contract_id, &call.output_param))
528 .collect::<Result<Vec<_>>>()?;
529
530 let tokens_as_tuple = Token::Tuple(final_tokens);
531
532 Ok(CallResponse {
533 value: T::from_token(tokens_as_tuple)?,
534 log_decoder: self.log_decoder.clone(),
535 tx_id: self.cached_tx_id,
536 tx_status: success,
537 })
538 }
539
540 pub async fn determine_missing_contracts(mut self) -> Result<Self> {
543 match self.simulate_without_decode().await {
544 Ok(_) => Ok(self),
545
546 Err(Error::Transaction(Reason::Failure { ref receipts, .. })) => {
547 for contract_id in find_ids_of_missing_contracts(receipts) {
548 self = self.append_external_contract(contract_id)?;
549 }
550
551 Ok(self)
552 }
553
554 Err(other_error) => Err(other_error),
555 }
556 }
557}