1use core::{fmt::Debug, marker::PhantomData};
2use std::sync::Arc;
3
4use fuel_tx::{AssetId, Bytes32};
5use fuels_accounts::{provider::TransactionCost, Account};
6use fuels_core::{
7 codec::{ABIEncoder, DecoderConfig, EncoderConfig, LogDecoder},
8 traits::{Parameterize, Signer, Tokenizable},
9 types::{
10 bech32::{Bech32Address, Bech32ContractId},
11 errors::{error, transaction::Reason, Error, Result},
12 input::Input,
13 output::Output,
14 transaction::{ScriptTransaction, Transaction, TxPolicies},
15 transaction_builders::{
16 BuildableTransaction, ScriptBuildStrategy, ScriptTransactionBuilder,
17 TransactionBuilder, VariableOutputPolicy,
18 },
19 tx_status::TxStatus,
20 Selector, Token,
21 },
22};
23
24use crate::{
25 calls::{
26 receipt_parser::ReceiptParser,
27 traits::{ContractDependencyConfigurator, ResponseParser, TransactionTuner},
28 utils::find_id_of_missing_contract,
29 CallParameters, ContractCall, Execution, ScriptCall,
30 },
31 responses::{CallResponse, SubmitResponse},
32};
33
34pub trait ContractDependency {
37 fn id(&self) -> Bech32ContractId;
38 fn log_decoder(&self) -> LogDecoder;
39}
40
41#[derive(Debug, Clone)]
42#[must_use = "contract calls do nothing unless you `call` them"]
43pub struct CallHandler<A, C, T> {
45 pub account: A,
46 pub call: C,
47 pub tx_policies: TxPolicies,
48 pub log_decoder: LogDecoder,
49 pub datatype: PhantomData<T>,
50 decoder_config: DecoderConfig,
51 cached_tx_id: Option<Bytes32>,
53 variable_output_policy: VariableOutputPolicy,
54 unresolved_signers: Vec<Arc<dyn Signer + Send + Sync>>,
55}
56
57impl<A, C, T> CallHandler<A, C, T> {
58 pub fn with_tx_policies(mut self, tx_policies: TxPolicies) -> Self {
65 self.tx_policies = tx_policies;
66 self
67 }
68
69 pub fn with_decoder_config(mut self, decoder_config: DecoderConfig) -> Self {
70 self.decoder_config = decoder_config;
71 self.log_decoder.set_decoder_config(decoder_config);
72 self
73 }
74
75 pub fn with_variable_output_policy(mut self, variable_outputs: VariableOutputPolicy) -> Self {
83 self.variable_output_policy = variable_outputs;
84 self
85 }
86
87 pub fn add_signer(mut self, signer: impl Signer + Send + Sync + 'static) -> Self {
88 self.unresolved_signers.push(Arc::new(signer));
89 self
90 }
91}
92
93impl<A, C, T> CallHandler<A, C, T>
94where
95 A: Account,
96 C: TransactionTuner,
97 T: Tokenizable + Parameterize + Debug,
98{
99 pub async fn transaction_builder(&self) -> Result<ScriptTransactionBuilder> {
100 let mut tb = self
101 .call
102 .transaction_builder(self.tx_policies, self.variable_output_policy, &self.account)
103 .await?;
104
105 tb.add_signers(&self.unresolved_signers)?;
106
107 Ok(tb)
108 }
109
110 pub async fn build_tx(&self) -> Result<ScriptTransaction> {
112 let tb = self.transaction_builder().await?;
113
114 self.call.build_tx(tb, &self.account).await
115 }
116
117 pub async fn estimate_transaction_cost(
119 &self,
120 tolerance: Option<f64>,
121 block_horizon: Option<u32>,
122 ) -> Result<TransactionCost> {
123 let tx = self.build_tx().await?;
124 let provider = self.account.try_provider()?;
125
126 let transaction_cost = provider
127 .estimate_transaction_cost(tx, tolerance, block_horizon)
128 .await?;
129
130 Ok(transaction_cost)
131 }
132}
133
134impl<A, C, T> CallHandler<A, C, T>
135where
136 A: Account,
137 C: ContractDependencyConfigurator + TransactionTuner + ResponseParser,
138 T: Tokenizable + Parameterize + Debug,
139{
140 pub fn with_contract_ids(mut self, contract_ids: &[Bech32ContractId]) -> Self {
152 self.call = self.call.with_external_contracts(contract_ids.to_vec());
153
154 self
155 }
156
157 pub fn with_contracts(mut self, contracts: &[&dyn ContractDependency]) -> Self {
166 self.call = self
167 .call
168 .with_external_contracts(contracts.iter().map(|c| c.id()).collect());
169 for c in contracts {
170 self.log_decoder.merge(c.log_decoder());
171 }
172
173 self
174 }
175
176 pub async fn call(mut self) -> Result<CallResponse<T>> {
178 let tx = self.build_tx().await?;
179 let provider = self.account.try_provider()?;
180
181 let consensus_parameters = provider.consensus_parameters().await?;
182 let chain_id = consensus_parameters.chain_id();
183 self.cached_tx_id = Some(tx.id(chain_id));
184
185 let tx_status = provider.send_transaction_and_await_commit(tx).await?;
186
187 self.get_response(tx_status)
188 }
189
190 pub async fn submit(mut self) -> Result<SubmitResponse<A, C, T>> {
191 let tx = self.build_tx().await?;
192 let provider = self.account.try_provider()?;
193
194 let tx_id = provider.send_transaction(tx.clone()).await?;
195 self.cached_tx_id = Some(tx_id);
196
197 Ok(SubmitResponse::<A, C, T>::new(tx_id, self))
198 }
199
200 pub async fn simulate(&mut self, execution: Execution) -> Result<CallResponse<T>> {
203 let provider = self.account.try_provider()?;
204
205 let tx_status = if let Execution::StateReadOnly = execution {
206 let tx = self
207 .transaction_builder()
208 .await?
209 .with_build_strategy(ScriptBuildStrategy::StateReadOnly)
210 .build(provider)
211 .await?;
212
213 provider.dry_run_opt(tx, false, Some(0)).await?
214 } else {
215 let tx = self.build_tx().await?;
216 provider.dry_run(tx).await?
217 };
218
219 self.get_response(tx_status)
220 }
221
222 pub fn get_response(&self, tx_status: TxStatus) -> Result<CallResponse<T>> {
224 let success = tx_status.take_success_checked(Some(&self.log_decoder))?;
225
226 let token =
227 self.call
228 .parse_call(&success.receipts, self.decoder_config, &T::param_type())?;
229
230 Ok(CallResponse {
231 value: T::from_token(token)?,
232 log_decoder: self.log_decoder.clone(),
233 tx_id: self.cached_tx_id,
234 tx_status: success,
235 })
236 }
237
238 pub async fn determine_missing_contracts(mut self, max_attempts: Option<u64>) -> Result<Self> {
239 let attempts = max_attempts.unwrap_or(10);
240
241 for _ in 0..attempts {
242 match self.simulate(Execution::Realistic).await {
243 Ok(_) => return Ok(self),
244
245 Err(Error::Transaction(Reason::Reverted { ref receipts, .. })) => {
246 if let Some(contract_id) = find_id_of_missing_contract(receipts) {
247 self.call.append_external_contract(contract_id);
248 }
249 }
250
251 Err(other_error) => return Err(other_error),
252 }
253 }
254
255 self.simulate(Execution::Realistic).await.map(|_| self)
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: Bech32ContractId,
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(
315 mut self,
316 asset_id: AssetId,
317 amount: u64,
318 to: Option<Bech32Address>,
319 ) -> Self {
320 self.call.add_custom_asset(asset_id, amount, to);
321 self
322 }
323
324 pub fn is_payable(&self) -> bool {
325 self.call.is_payable
326 }
327
328 pub fn call_params(mut self, params: CallParameters) -> Result<Self> {
336 if !self.is_payable() && params.amount() > 0 {
337 return Err(error!(Other, "assets forwarded to non-payable method"));
338 }
339 self.call.call_parameters = params;
340
341 Ok(self)
342 }
343
344 pub fn with_outputs(mut self, outputs: Vec<Output>) -> Self {
347 self.call = self.call.with_outputs(outputs);
348 self
349 }
350
351 pub fn with_inputs(mut self, inputs: Vec<Input>) -> Self {
354 self.call = self.call.with_inputs(inputs);
355 self
356 }
357}
358
359impl<A, T> CallHandler<A, ScriptCall, T>
360where
361 A: Account,
362 T: Parameterize + Tokenizable + Debug,
363{
364 pub fn new_script_call(
365 script_binary: Vec<u8>,
366 encoded_args: Result<Vec<u8>>,
367 account: A,
368 log_decoder: LogDecoder,
369 ) -> Self {
370 let call = ScriptCall {
371 script_binary,
372 encoded_args,
373 inputs: vec![],
374 outputs: vec![],
375 external_contracts: vec![],
376 };
377
378 Self {
379 account,
380 call,
381 tx_policies: TxPolicies::default(),
382 log_decoder,
383 datatype: PhantomData,
384 decoder_config: DecoderConfig::default(),
385 cached_tx_id: None,
386 variable_output_policy: VariableOutputPolicy::default(),
387 unresolved_signers: vec![],
388 }
389 }
390
391 pub fn with_outputs(mut self, outputs: Vec<Output>) -> Self {
394 self.call = self.call.with_outputs(outputs);
395 self
396 }
397
398 pub fn with_inputs(mut self, inputs: Vec<Input>) -> Self {
401 self.call = self.call.with_inputs(inputs);
402 self
403 }
404}
405
406impl<A> CallHandler<A, Vec<ContractCall>, ()>
407where
408 A: Account,
409{
410 pub fn new_multi_call(account: A) -> Self {
411 Self {
412 account,
413 call: vec![],
414 tx_policies: TxPolicies::default(),
415 log_decoder: LogDecoder::new(Default::default()),
416 datatype: PhantomData,
417 decoder_config: DecoderConfig::default(),
418 cached_tx_id: None,
419 variable_output_policy: VariableOutputPolicy::default(),
420 unresolved_signers: vec![],
421 }
422 }
423
424 fn append_external_contract(mut self, contract_id: Bech32ContractId) -> Result<Self> {
425 if self.call.is_empty() {
426 return Err(error!(
427 Other,
428 "no calls added. Have you used '.add_calls()'?"
429 ));
430 }
431
432 self.call
433 .iter_mut()
434 .take(1)
435 .for_each(|call| call.append_external_contract(contract_id.clone()));
436
437 Ok(self)
438 }
439
440 pub fn add_call(
444 mut self,
445 call_handler: CallHandler<impl Account, ContractCall, impl Tokenizable>,
446 ) -> Self {
447 self.log_decoder.merge(call_handler.log_decoder);
448 self.call.push(call_handler.call);
449 self.unresolved_signers
450 .extend(call_handler.unresolved_signers);
451
452 self
453 }
454
455 pub async fn call<T: Tokenizable + Debug>(mut self) -> Result<CallResponse<T>> {
457 let tx = self.build_tx().await?;
458
459 let provider = self.account.try_provider()?;
460 let consensus_parameters = provider.consensus_parameters().await?;
461 let chain_id = consensus_parameters.chain_id();
462
463 self.cached_tx_id = Some(tx.id(chain_id));
464
465 let tx_status = provider.send_transaction_and_await_commit(tx).await?;
466
467 self.get_response(tx_status)
468 }
469
470 pub async fn submit(mut self) -> Result<SubmitResponse<A, Vec<ContractCall>, ()>> {
471 let tx = self.build_tx().await?;
472 let provider = self.account.try_provider()?;
473
474 let tx_id = provider.send_transaction(tx).await?;
475 self.cached_tx_id = Some(tx_id);
476
477 Ok(SubmitResponse::<A, Vec<ContractCall>, ()>::new(tx_id, self))
478 }
479
480 pub async fn simulate<T: Tokenizable + Debug>(
486 &mut self,
487 execution: Execution,
488 ) -> Result<CallResponse<T>> {
489 let provider = self.account.try_provider()?;
490
491 let tx_status = if let Execution::StateReadOnly = execution {
492 let tx = self
493 .transaction_builder()
494 .await?
495 .with_build_strategy(ScriptBuildStrategy::StateReadOnly)
496 .build(provider)
497 .await?;
498
499 provider.dry_run_opt(tx, false, Some(0)).await?
500 } else {
501 let tx = self.build_tx().await?;
502 provider.dry_run(tx).await?
503 };
504
505 self.get_response(tx_status)
506 }
507
508 async fn simulate_without_decode(&self) -> Result<()> {
510 let provider = self.account.try_provider()?;
511 let tx = self.build_tx().await?;
512
513 provider.dry_run(tx).await?.check(None)?;
514
515 Ok(())
516 }
517
518 pub fn get_response<T: Tokenizable + Debug>(
520 &self,
521 tx_status: TxStatus,
522 ) -> Result<CallResponse<T>> {
523 let success = tx_status.take_success_checked(Some(&self.log_decoder))?;
524 let mut receipt_parser = ReceiptParser::new(&success.receipts, self.decoder_config);
525
526 let final_tokens = self
527 .call
528 .iter()
529 .map(|call| receipt_parser.parse_call(&call.contract_id, &call.output_param))
530 .collect::<Result<Vec<_>>>()?;
531
532 let tokens_as_tuple = Token::Tuple(final_tokens);
533
534 Ok(CallResponse {
535 value: T::from_token(tokens_as_tuple)?,
536 log_decoder: self.log_decoder.clone(),
537 tx_id: self.cached_tx_id,
538 tx_status: success,
539 })
540 }
541
542 pub async fn determine_missing_contracts(mut self, max_attempts: Option<u64>) -> Result<Self> {
545 let attempts = max_attempts.unwrap_or(10);
546
547 for _ in 0..attempts {
548 match self.simulate_without_decode().await {
549 Ok(_) => return Ok(self),
550
551 Err(Error::Transaction(Reason::Reverted { ref receipts, .. })) => {
552 if let Some(contract_id) = find_id_of_missing_contract(receipts) {
553 self = self.append_external_contract(contract_id)?;
554 }
555 }
556
557 Err(other_error) => return Err(other_error),
558 }
559 }
560
561 self.simulate_without_decode().await.map(|_| self)
562 }
563}