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