1use core::{fmt::Debug, marker::PhantomData};
2use std::sync::Arc;
3
4use fuel_tx::{AssetId, Bytes32};
5use fuels_accounts::{Account, provider::TransactionCost};
6use fuels_core::{
7 codec::{ABIEncoder, DecoderConfig, EncoderConfig, LogDecoder},
8 traits::{Parameterize, Signer, Tokenizable},
9 types::{
10 Selector, Token,
11 bech32::{Bech32Address, Bech32ContractId},
12 errors::{Error, Result, error, transaction::Reason},
13 input::Input,
14 output::Output,
15 transaction::{ScriptTransaction, Transaction, TxPolicies},
16 transaction_builders::{
17 BuildableTransaction, ScriptBuildStrategy, ScriptTransactionBuilder,
18 TransactionBuilder, VariableOutputPolicy,
19 },
20 tx_status::TxStatus,
21 },
22};
23
24use crate::{
25 calls::{
26 CallParameters, ContractCall, Execution, ExecutionType, ScriptCall,
27 receipt_parser::ReceiptParser,
28 traits::{ContractDependencyConfigurator, ResponseParser, TransactionTuner},
29 utils::find_ids_of_missing_contracts,
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(
203 &mut self,
204 Execution {
205 execution_type,
206 at_height,
207 }: Execution,
208 ) -> Result<CallResponse<T>> {
209 let provider = self.account.try_provider()?;
210
211 let tx_status = if let ExecutionType::StateReadOnly = execution_type {
212 let tx = self
213 .transaction_builder()
214 .await?
215 .with_build_strategy(ScriptBuildStrategy::StateReadOnly)
216 .build(provider)
217 .await?;
218
219 provider.dry_run_opt(tx, false, Some(0), at_height).await?
220 } else {
221 let tx = self.build_tx().await?;
222 provider.dry_run_opt(tx, true, None, at_height).await?
223 };
224
225 self.get_response(tx_status)
226 }
227
228 pub fn get_response(&self, tx_status: TxStatus) -> Result<CallResponse<T>> {
230 let success = tx_status.take_success_checked(Some(&self.log_decoder))?;
231
232 let token =
233 self.call
234 .parse_call(&success.receipts, self.decoder_config, &T::param_type())?;
235
236 Ok(CallResponse {
237 value: T::from_token(token)?,
238 log_decoder: self.log_decoder.clone(),
239 tx_id: self.cached_tx_id,
240 tx_status: success,
241 })
242 }
243
244 pub async fn determine_missing_contracts(mut self) -> Result<Self> {
245 match self.simulate(Execution::realistic()).await {
246 Ok(_) => Ok(self),
247
248 Err(Error::Transaction(Reason::Failure { ref receipts, .. })) => {
249 for contract_id in find_ids_of_missing_contracts(receipts) {
250 self.call.append_external_contract(contract_id);
251 }
252
253 Ok(self)
254 }
255
256 Err(other_error) => Err(other_error),
257 }
258 }
259}
260
261impl<A, T> CallHandler<A, ContractCall, T>
262where
263 A: Account,
264 T: Tokenizable + Parameterize + Debug,
265{
266 pub fn new_contract_call(
267 contract_id: Bech32ContractId,
268 account: A,
269 encoded_selector: Selector,
270 args: &[Token],
271 log_decoder: LogDecoder,
272 is_payable: bool,
273 encoder_config: EncoderConfig,
274 ) -> Self {
275 let call = ContractCall {
276 contract_id,
277 encoded_selector,
278 encoded_args: ABIEncoder::new(encoder_config).encode(args),
279 call_parameters: CallParameters::default(),
280 external_contracts: vec![],
281 output_param: T::param_type(),
282 is_payable,
283 custom_assets: Default::default(),
284 inputs: vec![],
285 outputs: vec![],
286 };
287 CallHandler {
288 account,
289 call,
290 tx_policies: TxPolicies::default(),
291 log_decoder,
292 datatype: PhantomData,
293 decoder_config: DecoderConfig::default(),
294 cached_tx_id: None,
295 variable_output_policy: VariableOutputPolicy::default(),
296 unresolved_signers: vec![],
297 }
298 }
299
300 pub fn add_custom_asset(
317 mut self,
318 asset_id: AssetId,
319 amount: u64,
320 to: Option<Bech32Address>,
321 ) -> Self {
322 self.call.add_custom_asset(asset_id, amount, to);
323 self
324 }
325
326 pub fn is_payable(&self) -> bool {
327 self.call.is_payable
328 }
329
330 pub fn call_params(mut self, params: CallParameters) -> Result<Self> {
338 if !self.is_payable() && params.amount() > 0 {
339 return Err(error!(Other, "assets forwarded to non-payable method"));
340 }
341 self.call.call_parameters = params;
342
343 Ok(self)
344 }
345
346 pub fn with_outputs(mut self, outputs: Vec<Output>) -> Self {
349 self.call = self.call.with_outputs(outputs);
350 self
351 }
352
353 pub fn with_inputs(mut self, inputs: Vec<Input>) -> Self {
356 self.call = self.call.with_inputs(inputs);
357 self
358 }
359}
360
361impl<A, T> CallHandler<A, ScriptCall, T>
362where
363 A: Account,
364 T: Parameterize + Tokenizable + Debug,
365{
366 pub fn new_script_call(
367 script_binary: Vec<u8>,
368 encoded_args: Result<Vec<u8>>,
369 account: A,
370 log_decoder: LogDecoder,
371 ) -> Self {
372 let call = ScriptCall {
373 script_binary,
374 encoded_args,
375 inputs: vec![],
376 outputs: vec![],
377 external_contracts: vec![],
378 };
379
380 Self {
381 account,
382 call,
383 tx_policies: TxPolicies::default(),
384 log_decoder,
385 datatype: PhantomData,
386 decoder_config: DecoderConfig::default(),
387 cached_tx_id: None,
388 variable_output_policy: VariableOutputPolicy::default(),
389 unresolved_signers: vec![],
390 }
391 }
392
393 pub fn with_outputs(mut self, outputs: Vec<Output>) -> Self {
396 self.call = self.call.with_outputs(outputs);
397 self
398 }
399
400 pub fn with_inputs(mut self, inputs: Vec<Input>) -> Self {
403 self.call = self.call.with_inputs(inputs);
404 self
405 }
406}
407
408impl<A> CallHandler<A, Vec<ContractCall>, ()>
409where
410 A: Account,
411{
412 pub fn new_multi_call(account: A) -> Self {
413 Self {
414 account,
415 call: vec![],
416 tx_policies: TxPolicies::default(),
417 log_decoder: LogDecoder::new(Default::default(), Default::default()),
418 datatype: PhantomData,
419 decoder_config: DecoderConfig::default(),
420 cached_tx_id: None,
421 variable_output_policy: VariableOutputPolicy::default(),
422 unresolved_signers: vec![],
423 }
424 }
425
426 fn append_external_contract(mut self, contract_id: Bech32ContractId) -> Result<Self> {
427 if self.call.is_empty() {
428 return Err(error!(
429 Other,
430 "no calls added. Have you used '.add_calls()'?"
431 ));
432 }
433
434 self.call
435 .iter_mut()
436 .take(1)
437 .for_each(|call| call.append_external_contract(contract_id.clone()));
438
439 Ok(self)
440 }
441
442 pub fn add_call(
446 mut self,
447 call_handler: CallHandler<impl Account, ContractCall, impl Tokenizable>,
448 ) -> Self {
449 self.log_decoder.merge(call_handler.log_decoder);
450 self.call.push(call_handler.call);
451 self.unresolved_signers
452 .extend(call_handler.unresolved_signers);
453
454 self
455 }
456
457 pub async fn call<T: Tokenizable + Debug>(mut self) -> Result<CallResponse<T>> {
459 let tx = self.build_tx().await?;
460
461 let provider = self.account.try_provider()?;
462 let consensus_parameters = provider.consensus_parameters().await?;
463 let chain_id = consensus_parameters.chain_id();
464
465 self.cached_tx_id = Some(tx.id(chain_id));
466
467 let tx_status = provider.send_transaction_and_await_commit(tx).await?;
468
469 self.get_response(tx_status)
470 }
471
472 pub async fn submit(mut self) -> Result<SubmitResponse<A, Vec<ContractCall>, ()>> {
473 let tx = self.build_tx().await?;
474 let provider = self.account.try_provider()?;
475
476 let tx_id = provider.send_transaction(tx).await?;
477 self.cached_tx_id = Some(tx_id);
478
479 Ok(SubmitResponse::<A, Vec<ContractCall>, ()>::new(tx_id, self))
480 }
481
482 pub async fn simulate<T: Tokenizable + Debug>(
488 &mut self,
489 Execution {
490 execution_type,
491 at_height,
492 }: Execution,
493 ) -> Result<CallResponse<T>> {
494 let provider = self.account.try_provider()?;
495
496 let tx_status = if let ExecutionType::StateReadOnly = execution_type {
497 let tx = self
498 .transaction_builder()
499 .await?
500 .with_build_strategy(ScriptBuildStrategy::StateReadOnly)
501 .build(provider)
502 .await?;
503
504 provider.dry_run_opt(tx, false, Some(0), at_height).await?
505 } else {
506 let tx = self.build_tx().await?;
507 provider.dry_run_opt(tx, true, None, at_height).await?
508 };
509
510 self.get_response(tx_status)
511 }
512
513 async fn simulate_without_decode(&self) -> Result<()> {
515 let provider = self.account.try_provider()?;
516 let tx = self.build_tx().await?;
517
518 provider.dry_run(tx).await?.check(None)?;
519
520 Ok(())
521 }
522
523 pub fn get_response<T: Tokenizable + Debug>(
525 &self,
526 tx_status: TxStatus,
527 ) -> Result<CallResponse<T>> {
528 let success = tx_status.take_success_checked(Some(&self.log_decoder))?;
529 let mut receipt_parser = ReceiptParser::new(&success.receipts, self.decoder_config);
530
531 let final_tokens = self
532 .call
533 .iter()
534 .map(|call| receipt_parser.parse_call(&call.contract_id, &call.output_param))
535 .collect::<Result<Vec<_>>>()?;
536
537 let tokens_as_tuple = Token::Tuple(final_tokens);
538
539 Ok(CallResponse {
540 value: T::from_token(tokens_as_tuple)?,
541 log_decoder: self.log_decoder.clone(),
542 tx_id: self.cached_tx_id,
543 tx_status: success,
544 })
545 }
546
547 pub async fn determine_missing_contracts(mut self) -> Result<Self> {
550 match self.simulate_without_decode().await {
551 Ok(_) => Ok(self),
552
553 Err(Error::Transaction(Reason::Failure { ref receipts, .. })) => {
554 for contract_id in find_ids_of_missing_contracts(receipts) {
555 self = self.append_external_contract(contract_id)?;
556 }
557
558 Ok(self)
559 }
560
561 Err(other_error) => Err(other_error),
562 }
563 }
564}