From e0c5d84738db573aaf4a544a5f79e97a35adb524 Mon Sep 17 00:00:00 2001 From: Dmytro Medynskyi Date: Fri, 31 Oct 2025 14:21:40 +0100 Subject: [PATCH] feat: make max_fee_estimation_tolerance configurable in call handlers and Executable APIs --- e2e/tests/contracts.rs | 42 +++++++++++++++++++ .../fuels-programs/src/calls/call_handler.rs | 16 +++++++ .../src/calls/traits/transaction_tuner.rs | 8 +++- packages/fuels-programs/src/calls/utils.rs | 3 +- packages/fuels-programs/src/executable.rs | 12 +++++- 5 files changed, 78 insertions(+), 3 deletions(-) diff --git a/e2e/tests/contracts.rs b/e2e/tests/contracts.rs index e01f3a75c..2da2dd08b 100644 --- a/e2e/tests/contracts.rs +++ b/e2e/tests/contracts.rs @@ -2880,3 +2880,45 @@ async fn test_returned_method_descriptors_are_valid() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn test_max_fee_estimation_tolerance() -> Result<()> { + setup_program_test!( + Wallets("wallet"), + Abigen(Contract( + name = "TestContract", + project = "e2e/sway/contracts/contract_test" + )), + Deploy( + name = "contract_instance", + contract = "TestContract", + wallet = "wallet", + random_salt = false, + ), + ); + + // Test with default tolerance + let contract_methods = contract_instance.methods(); + let response_default = contract_methods.get_single(5).call().await?; + assert_eq!(response_default.value, 5); + + // Test with custom tolerance (lower than default) + let custom_tolerance = 0.1; // 10% tolerance + let response_custom = contract_methods + .get_single(5) + .with_max_fee_estimation_tolerance(custom_tolerance) + .call() + .await?; + assert_eq!(response_custom.value, 5); + + // Test with custom tolerance (higher than default) + let high_tolerance = 1.0; // 100% tolerance + let response_high = contract_methods + .get_single(5) + .with_max_fee_estimation_tolerance(high_tolerance) + .call() + .await?; + assert_eq!(response_high.value, 5); + + Ok(()) +} diff --git a/packages/fuels-programs/src/calls/call_handler.rs b/packages/fuels-programs/src/calls/call_handler.rs index 96f696587..a60280cf5 100644 --- a/packages/fuels-programs/src/calls/call_handler.rs +++ b/packages/fuels-programs/src/calls/call_handler.rs @@ -49,6 +49,7 @@ pub struct CallHandler { cached_tx_id: Option, variable_output_policy: VariableOutputPolicy, unresolved_signers: Vec>, + max_fee_estimation_tolerance: f32, } impl CallHandler { @@ -85,6 +86,17 @@ impl CallHandler { self.unresolved_signers.push(Arc::new(signer)); self } + + /// Sets the max fee estimation tolerance for a given transaction. + /// This tolerance is used as a buffer when estimating the maximum fee. + /// Note that this is a builder method, i.e. use it as a chain: + /// ```ignore + /// my_contract_instance.my_method(...).with_max_fee_estimation_tolerance(0.25).call() + /// ``` + pub fn with_max_fee_estimation_tolerance(mut self, max_fee_estimation_tolerance: f32) -> Self { + self.max_fee_estimation_tolerance = max_fee_estimation_tolerance; + self + } } impl CallHandler @@ -123,6 +135,7 @@ where consensus_parameters, asset_inputs, &self.account, + self.max_fee_estimation_tolerance, )?; tb.add_signers(&self.unresolved_signers)?; @@ -317,6 +330,7 @@ where cached_tx_id: None, variable_output_policy: VariableOutputPolicy::default(), unresolved_signers: vec![], + max_fee_estimation_tolerance: crate::DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE, } } @@ -405,6 +419,7 @@ where cached_tx_id: None, variable_output_policy: VariableOutputPolicy::default(), unresolved_signers: vec![], + max_fee_estimation_tolerance: crate::DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE, } } @@ -438,6 +453,7 @@ where cached_tx_id: None, variable_output_policy: VariableOutputPolicy::default(), unresolved_signers: vec![], + max_fee_estimation_tolerance: crate::DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE, } } diff --git a/packages/fuels-programs/src/calls/traits/transaction_tuner.rs b/packages/fuels-programs/src/calls/traits/transaction_tuner.rs index abb0aff69..442bc43a4 100644 --- a/packages/fuels-programs/src/calls/traits/transaction_tuner.rs +++ b/packages/fuels-programs/src/calls/traits/transaction_tuner.rs @@ -29,6 +29,7 @@ pub trait TransactionTuner: sealed::Sealed { consensus_parameters: &ConsensusParameters, asset_input: Vec, account: &T, + max_fee_estimation_tolerance: f32, ) -> Result; async fn build_tx( @@ -51,6 +52,7 @@ impl TransactionTuner for ContractCall { consensus_parameters: &ConsensusParameters, asset_input: Vec, account: &T, + max_fee_estimation_tolerance: f32, ) -> Result { transaction_builder_from_contract_calls( std::slice::from_ref(self), @@ -59,6 +61,7 @@ impl TransactionTuner for ContractCall { consensus_parameters, asset_input, account, + max_fee_estimation_tolerance, ) } @@ -84,6 +87,7 @@ impl TransactionTuner for ScriptCall { _: &ConsensusParameters, _: Vec, _account: &T, + max_fee_estimation_tolerance: f32, ) -> Result { let (inputs, outputs) = self.prepare_inputs_outputs()?; @@ -95,7 +99,7 @@ impl TransactionTuner for ScriptCall { .with_inputs(inputs) .with_outputs(outputs) .with_gas_estimation_tolerance(DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE) - .with_max_fee_estimation_tolerance(DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE)) + .with_max_fee_estimation_tolerance(max_fee_estimation_tolerance)) } async fn build_tx( @@ -128,6 +132,7 @@ impl TransactionTuner for Vec { consensus_parameters: &ConsensusParameters, asset_input: Vec, account: &T, + max_fee_estimation_tolerance: f32, ) -> Result { validate_contract_calls(self)?; @@ -138,6 +143,7 @@ impl TransactionTuner for Vec { consensus_parameters, asset_input, account, + max_fee_estimation_tolerance, ) } diff --git a/packages/fuels-programs/src/calls/utils.rs b/packages/fuels-programs/src/calls/utils.rs index 3e30b39f0..8aa58522d 100644 --- a/packages/fuels-programs/src/calls/utils.rs +++ b/packages/fuels-programs/src/calls/utils.rs @@ -37,6 +37,7 @@ pub(crate) fn transaction_builder_from_contract_calls( consensus_parameters: &ConsensusParameters, asset_inputs: Vec, account: &impl Account, + max_fee_estimation_tolerance: f32, ) -> Result { let calls_instructions_len = compute_calls_instructions_len(calls); let data_offset = call_script_data_offset(consensus_parameters, calls_instructions_len)?; @@ -63,7 +64,7 @@ pub(crate) fn transaction_builder_from_contract_calls( .with_inputs(inputs) .with_outputs(outputs) .with_gas_estimation_tolerance(DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE) - .with_max_fee_estimation_tolerance(DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE)) + .with_max_fee_estimation_tolerance(max_fee_estimation_tolerance)) } /// Creates a [`ScriptTransaction`] from contract calls. The internal [Transaction] is diff --git a/packages/fuels-programs/src/executable.rs b/packages/fuels-programs/src/executable.rs index 04f69b186..d137940fe 100644 --- a/packages/fuels-programs/src/executable.rs +++ b/packages/fuels-programs/src/executable.rs @@ -167,6 +167,16 @@ impl Executable { pub async fn upload_blob( &self, account: impl fuels_accounts::Account, + ) -> Result> { + self.upload_blob_with_tolerance(account, DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE).await + } + + /// If not previously uploaded, uploads a blob containing the original executable code minus the data section, + /// using the specified max fee estimation tolerance. + pub async fn upload_blob_with_tolerance( + &self, + account: impl fuels_accounts::Account, + max_fee_estimation_tolerance: f32, ) -> Result> { let blob = self.blob(); let provider = account.try_provider()?; @@ -178,7 +188,7 @@ impl Executable { let mut tb = BlobTransactionBuilder::default() .with_blob(self.blob()) - .with_max_fee_estimation_tolerance(DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE); + .with_max_fee_estimation_tolerance(max_fee_estimation_tolerance); account .adjust_for_fee(&mut tb, 0)