From 3c37548d50aa35f963a242d474e325b9ee036b5a Mon Sep 17 00:00:00 2001 From: hal3e Date: Thu, 13 Jun 2024 16:24:33 +0200 Subject: [PATCH] fix!: `dry_run` tx with non-zero base asset and tip (#1422) BREAKING CHANGE: - Removed `dry_run_no_validation` in favor of `dry_run_opt` - Removed `dry_run_no_validation_multiple` in favor of `dry_run_opt_multiple` ### Checklist - [x] I have added tests that prove my fix is effective or that my feature works. - [x] I have added necessary labels. - [x] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [x] I have requested a review from the relevant team or maintainers. --------- Co-authored-by: MujkicA --- e2e/tests/contracts.rs | 51 +++++++++++++++++++ examples/contracts/src/lib.rs | 3 +- packages/fuels-accounts/src/provider.rs | 23 ++++++--- .../src/provider/retryable_client.rs | 3 +- .../transaction_builders/script_dry_runner.rs | 34 ++++++++----- 5 files changed, 92 insertions(+), 22 deletions(-) diff --git a/e2e/tests/contracts.rs b/e2e/tests/contracts.rs index 23bd38931f..364f3beace 100644 --- a/e2e/tests/contracts.rs +++ b/e2e/tests/contracts.rs @@ -1868,3 +1868,54 @@ async fn variable_output_estimation_is_optimized() -> Result<()> { Ok(()) } + +#[tokio::test] +async fn contract_call_with_non_zero_base_asset_id_and_tip() -> Result<()> { + use fuels::prelude::*; + use fuels::tx::ConsensusParameters; + + abigen!(Contract( + name = "MyContract", + abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json" + )); + + let asset_id = AssetId::new([1; 32]); + + let mut consensus_parameters = ConsensusParameters::default(); + consensus_parameters.set_base_asset_id(asset_id); + + let config = ChainConfig { + consensus_parameters, + ..Default::default() + }; + + let asset_base = AssetConfig { + id: asset_id, + num_coins: 1, + coin_amount: 10_000, + }; + + let wallet_config = WalletsConfig::new_multiple_assets(1, vec![asset_base]); + let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, Some(config)).await?; + let wallet = wallets.first().expect("has wallet"); + + let contract_id = Contract::load_from( + "sway/contracts/contract_test/out/release/contract_test.bin", + LoadConfiguration::default(), + )? + .deploy(wallet, TxPolicies::default()) + .await?; + + let contract_instance = MyContract::new(contract_id, wallet.clone()); + + let response = contract_instance + .methods() + .initialize_counter(42) + .with_tx_policies(TxPolicies::default().with_tip(10)) + .call() + .await?; + + assert_eq!(42, response.value); + + Ok(()) +} diff --git a/examples/contracts/src/lib.rs b/examples/contracts/src/lib.rs index 8251e6c8b5..eb7ecf02dc 100644 --- a/examples/contracts/src/lib.rs +++ b/examples/contracts/src/lib.rs @@ -240,7 +240,6 @@ mod tests { let response = contract_instance_1 .methods() .initialize_counter(42) - .with_tx_policies(TxPolicies::default().with_script_gas_limit(1_000_000)) .call() .await?; @@ -259,11 +258,11 @@ mod tests { let response = contract_instance_2 .methods() .initialize_counter(42) // Build the ABI call - .with_tx_policies(TxPolicies::default().with_script_gas_limit(1_000_000)) .call() .await?; assert_eq!(42, response.value); + Ok(()) } diff --git a/packages/fuels-accounts/src/provider.rs b/packages/fuels-accounts/src/provider.rs index dd4e8e0e8e..f1a77551ea 100644 --- a/packages/fuels-accounts/src/provider.rs +++ b/packages/fuels-accounts/src/provider.rs @@ -309,10 +309,19 @@ impl Provider { .collect()) } - pub async fn dry_run_no_validation(&self, tx: impl Transaction) -> Result { + pub async fn dry_run_opt( + &self, + tx: impl Transaction, + utxo_validation: bool, + gas_price: Option, + ) -> Result { let [tx_status] = self .client - .dry_run_opt(Transactions::new().insert(tx).as_slice(), Some(false)) + .dry_run_opt( + Transactions::new().insert(tx).as_slice(), + Some(utxo_validation), + gas_price, + ) .await? .into_iter() .map(Into::into) @@ -323,13 +332,15 @@ impl Provider { Ok(tx_status) } - pub async fn dry_run_no_validation_multiple( + pub async fn dry_run_opt_multiple( &self, transactions: Transactions, + utxo_validation: bool, + gas_price: Option, ) -> Result> { Ok(self .client - .dry_run_opt(transactions.as_slice(), Some(false)) + .dry_run_opt(transactions.as_slice(), Some(utxo_validation), gas_price) .await? .into_iter() .map(|execution_status| (execution_status.id, execution_status.into())) @@ -639,7 +650,7 @@ impl Provider { tx: T, tolerance: f64, ) -> Result { - let receipts = self.dry_run_no_validation(tx).await?.take_receipts(); + let receipts = self.dry_run_opt(tx, false, None).await?.take_receipts(); let gas_used = self.get_script_gas_used(&receipts); Ok((gas_used as f64 * (1.0 + tolerance)) as u64) @@ -707,7 +718,7 @@ impl DryRunner for Provider { async fn dry_run(&self, tx: FuelTransaction) -> Result { let [tx_execution_status] = self .client - .dry_run_opt(&vec![tx], Some(false)) + .dry_run_opt(&vec![tx], Some(false), Some(0)) .await? .try_into() .expect("should have only one element"); diff --git a/packages/fuels-accounts/src/provider/retryable_client.rs b/packages/fuels-accounts/src/provider/retryable_client.rs index 1ed207262f..7c65acd0a8 100644 --- a/packages/fuels-accounts/src/provider/retryable_client.rs +++ b/packages/fuels-accounts/src/provider/retryable_client.rs @@ -170,8 +170,9 @@ impl RetryableClient { &self, tx: &[Transaction], utxo_validation: Option, + gas_price: Option, ) -> RequestResult> { - self.wrap(|| self.client.dry_run_opt(tx, utxo_validation, None)) + self.wrap(|| self.client.dry_run_opt(tx, utxo_validation, gas_price)) .await } diff --git a/packages/fuels-core/src/types/transaction_builders/script_dry_runner.rs b/packages/fuels-core/src/types/transaction_builders/script_dry_runner.rs index 92f4ed0a4b..28a4954130 100644 --- a/packages/fuels-core/src/types/transaction_builders/script_dry_runner.rs +++ b/packages/fuels-core/src/types/transaction_builders/script_dry_runner.rs @@ -4,7 +4,7 @@ use fuel_crypto::Signature; use fuel_tx::{ field::{Inputs, Outputs, ScriptGasLimit, WitnessLimit, Witnesses}, input::coin::{CoinPredicate, CoinSigned}, - Chargeable, Input as FuelInput, TxPointer, Witness, + AssetId, Chargeable, Input as FuelInput, TxPointer, Witness, }; use itertools::Itertools; @@ -84,7 +84,11 @@ impl ScriptDryRunner { } fn add_fake_coins(&mut self, tx: &mut fuel_tx::Script) { - if let Some(fake_input) = Self::needs_fake_spendable_input(tx.inputs()) { + let consensus_params = self.dry_runner.consensus_parameters(); + + if let Some(fake_input) = + Self::needs_fake_base_input(tx.inputs(), consensus_params.base_asset_id()) + { tx.inputs_mut().push(fake_input); // Add an empty `Witness` for the `coin_signed` we just added @@ -93,18 +97,22 @@ impl ScriptDryRunner { } } - fn needs_fake_spendable_input(inputs: &[FuelInput]) -> Option { - let has_spendable_input = inputs.iter().any(|i| { - matches!( - i, - FuelInput::CoinSigned(CoinSigned { .. }) - | FuelInput::CoinPredicate(CoinPredicate { .. }) - | FuelInput::MessageCoinSigned(_) - | FuelInput::MessageCoinPredicate(_) - ) + fn needs_fake_base_input( + inputs: &[FuelInput], + base_asset_id: &AssetId, + ) -> Option { + let has_base_asset = inputs.iter().any(|i| match i { + FuelInput::CoinSigned(CoinSigned { asset_id, .. }) + | FuelInput::CoinPredicate(CoinPredicate { asset_id, .. }) + if asset_id == base_asset_id => + { + true + } + FuelInput::MessageCoinSigned(_) | FuelInput::MessageCoinPredicate(_) => true, + _ => false, }); - if has_spendable_input { + if has_base_asset { return None; } @@ -128,7 +136,7 @@ impl ScriptDryRunner { Default::default(), fake_owner, 1_000_000_000, - Default::default(), + *base_asset_id, TxPointer::default(), 0, ))