diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6157abcefe..88d9f1b9cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ env: CARGO_TERM_COLOR: always DASEL_VERSION: https://github.com/TomWright/dasel/releases/download/v2.3.6/dasel_linux_amd64 RUSTFLAGS: "-D warnings" - FUEL_CORE_VERSION: 0.22.1 + FUEL_CORE_VERSION: 0.23.0 FUEL_CORE_PATCH_BRANCH: RUST_VERSION: 1.74.0 FORC_VERSION: 0.51.1 diff --git a/Cargo.toml b/Cargo.toml index 31302decac..42cda23257 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,25 +68,25 @@ tokio = { version = "1.34.0", default-features = false } tracing = "0.1.40" trybuild = "1.0.85" uint = { version = "0.9.5", default-features = false } -which = { version = "5.0.0", default-features = false } +which = { version = "6.0.0", default-features = false } zeroize = "1.7.0" # Dependencies from the `fuel-core` repository: -fuel-core = { version = "0.22.1", default-features = false } -fuel-core-chain-config = { version = "0.22.1", default-features = false } -fuel-core-client = { version = "0.22.1", default-features = false } -fuel-core-poa = { version = "0.22.1", default-features = false } -fuel-core-services = { version = "0.22.1", default-features = false } -fuel-core-types = { version = "0.22.1", default-features = false } +fuel-core = { version = "0.23.0", default-features = false } +fuel-core-chain-config = { version = "0.23.0", default-features = false } +fuel-core-client = { version = "0.23.0", default-features = false } +fuel-core-poa = { version = "0.23.0", default-features = false } +fuel-core-services = { version = "0.23.0", default-features = false } +fuel-core-types = { version = "0.23.0", default-features = false } # Dependencies from the `fuel-vm` repository: -fuel-asm = { version = "0.43.2" } -fuel-crypto = { version = "0.43.2" } -fuel-merkle = { version = "0.43.2" } -fuel-storage = { version = "0.43.2" } -fuel-tx = { version = "0.43.2" } -fuel-types = { version = "0.43.2" } -fuel-vm = { version = "0.43.2" } +fuel-asm = { version = "0.47.1" } +fuel-crypto = { version = "0.47.1" } +fuel-merkle = { version = "0.47.1" } +fuel-storage = { version = "0.47.1" } +fuel-tx = { version = "0.47.1" } +fuel-types = { version = "0.47.1" } +fuel-vm = { version = "0.47.1" } # Workspace projects fuels = { version = "0.55.0", path = "./packages/fuels" } diff --git a/docs/src/calling-contracts/cost-estimation.md b/docs/src/calling-contracts/cost-estimation.md index 12b30db1b0..824bbcf8d3 100644 --- a/docs/src/calling-contracts/cost-estimation.md +++ b/docs/src/calling-contracts/cost-estimation.md @@ -1,16 +1,9 @@ # Estimating contract call cost -With the function `estimate_transaction_cost(tolerance: Option)` provided by `ContractCallHandler` and `ContractMultiCallHandler`, you can get a cost estimation for a specific call. The return type, `TransactionCost`, is a struct that contains relevant information for the estimation: +With the function `estimate_transaction_cost(tolerance: Option, block_horizon: Option)` provided by `ContractCallHandler` and `ContractMultiCallHandler`, you can get a cost estimation for a specific call. The return type, `TransactionCost`, is a struct that contains relevant information for the estimation: ```rust,ignore -TransactionCost { - min_gas_price: u64, - min_byte_price: u64, - gas_price: u64, - gas_used: u64, - metered_bytes_size: u64, - total_fee: f64, // where total_fee is the sum of the gas and byte fees -} +{{#include ../../../packages/fuels-accounts/src/provider.rs:transaction_cost}} ``` Below are examples that show how to get the estimated transaction cost from single and multi call transactions. @@ -24,3 +17,5 @@ Below are examples that show how to get the estimated transaction cost from sing ``` The transaction cost estimation can be used to set the gas limit for an actual call, or to show the user the estimated cost. + +> **Note** The same estimation interface is available for scripts. diff --git a/docs/src/calling-contracts/tx-policies.md b/docs/src/calling-contracts/tx-policies.md index e83b90c2e2..c02d839642 100644 --- a/docs/src/calling-contracts/tx-policies.md +++ b/docs/src/calling-contracts/tx-policies.md @@ -10,13 +10,13 @@ Transaction policies are defined as follows: Where: -1. **Gas Price** - Maximum gas price for transaction. +1. **Tip** - amount to pay the block producer to prioritize the transaction. 2. **Witness Limit** - The maximum amount of witness data allowed for the transaction. 3. **Maturity** - Block until which the transaction cannot be included. 4. **Max Fee** - The maximum fee payable by this transaction. 5. **Script Gas Limit** - The maximum amount of gas the transaction may consume for executing its script code. -When the **Script Gas Limit** is not set, the Rust SDK will estimate the consumed gas in the background and set it as the limit. Similarly, if no **Gas Price** is defined, the Rust SDK defaults to the network's minimum gas price. +When the **Script Gas Limit** is not set, the Rust SDK will estimate the consumed gas in the background and set it as the limit. If the **Witness Limit** is not set, the SDK will set it to the size of all witnesses and signatures defined in the transaction builder. diff --git a/examples/contracts/src/lib.rs b/examples/contracts/src/lib.rs index 5b325d16f2..1e1b7d8e23 100644 --- a/examples/contracts/src/lib.rs +++ b/examples/contracts/src/lib.rs @@ -98,11 +98,12 @@ mod tests { // ANCHOR: contract_call_cost_estimation let contract_instance = MyContract::new(contract_id, wallet); - let tolerance = 0.0; + let tolerance = Some(0.0); + let block_horizon = Some(1); let transaction_cost = contract_instance .methods() .initialize_counter(42) // Build the ABI call - .estimate_transaction_cost(Some(tolerance)) // Get estimated transaction cost + .estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost .await?; // ANCHOR_END: contract_call_cost_estimation @@ -144,7 +145,7 @@ mod tests { // Optional: Configure deployment parameters let tx_policies = TxPolicies::default() - .with_gas_price(0) + .with_tip(1) .with_script_gas_limit(1_000_000) .with_maturity(0); @@ -281,7 +282,7 @@ mod tests { let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods(); let tx_policies = TxPolicies::default() - .with_gas_price(1) + .with_tip(1) .with_script_gas_limit(1_000_000) .with_maturity(0); @@ -594,9 +595,10 @@ mod tests { .add_call(call_handler_1) .add_call(call_handler_2); - let tolerance = 0.0; + let tolerance = Some(0.0); + let block_horizon = Some(1); let transaction_cost = multi_call_handler - .estimate_transaction_cost(Some(tolerance)) // Get estimated transaction cost + .estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost .await?; // ANCHOR_END: multi_call_cost_estimation diff --git a/examples/cookbook/src/lib.rs b/examples/cookbook/src/lib.rs index d514132235..94f39707ef 100644 --- a/examples/cookbook/src/lib.rs +++ b/examples/cookbook/src/lib.rs @@ -302,7 +302,7 @@ mod tests { // ANCHOR_END: custom_tx_adjust // ANCHOR: custom_tx_policies - let tx_policies = TxPolicies::default().with_gas_price(1); + let tx_policies = TxPolicies::default().with_tip(1); let tb = tb.with_tx_policies(tx_policies); // ANCHOR_END: custom_tx_policies diff --git a/packages/fuels-accounts/Cargo.toml b/packages/fuels-accounts/Cargo.toml index 5cd4bd05f2..3918be22a9 100644 --- a/packages/fuels-accounts/Cargo.toml +++ b/packages/fuels-accounts/Cargo.toml @@ -15,6 +15,7 @@ chrono = { workspace = true } elliptic-curve = { workspace = true, default-features = false } eth-keystore = { workspace = true, optional = true } fuel-core-client = { workspace = true, optional = true } +fuel-core-types = { workspace = true } fuel-crypto = { workspace = true, features = ["random"] } fuel-tx = { workspace = true } fuel-types = { workspace = true, features = ["random"] } diff --git a/packages/fuels-accounts/src/account.rs b/packages/fuels-accounts/src/account.rs index cc8b19486e..ebeb01d80f 100644 --- a/packages/fuels-accounts/src/account.rs +++ b/packages/fuels-accounts/src/account.rs @@ -331,10 +331,12 @@ mod tests { async fn dry_run_and_get_used_gas(&self, _: FuelTransaction, _: f32) -> Result { Ok(0) } + fn consensus_parameters(&self) -> &ConsensusParameters { &self.c_param } - async fn min_gas_price(&self) -> Result { + + async fn estimate_gas_price(&self, _block_header: u32) -> Result { Ok(0) } } @@ -390,7 +392,7 @@ mod tests { assert_eq!(signature, tx_signature); // Check if the signature is what we expect it to be - assert_eq!(signature, Signature::from_str("a7446cb9703d3bc9e68677715fc7ef6ed72ff4eeac0c67bdb0d9b9c8ba38048e078e38fdd85bf988cefd3737005f1be97ed8b9662f002b0480d4404ebb397fed")?); + assert_eq!(signature, Signature::from_str("37cf6bdefc9e673f99a7fdbbeff454cb5c1bdf632c072f19cf8ac68fa1ede2749c568c56f87d73fc5c97f73b76dfe637422b77c1fdc6010fb4f488444ff5df1a")?); // Recover the address that signed the transaction let recovered_address = signature.recover(&message)?; diff --git a/packages/fuels-accounts/src/provider.rs b/packages/fuels-accounts/src/provider.rs index 9cbec2e231..28bcf9da42 100644 --- a/packages/fuels-accounts/src/provider.rs +++ b/packages/fuels-accounts/src/provider.rs @@ -10,17 +10,23 @@ use std::sync::Arc; use chrono::{DateTime, Utc}; use fuel_core_client::client::{ pagination::{PageDirection, PaginatedResult, PaginationRequest}, - types::{balance::Balance, contract::ContractBalance}, + types::{ + balance::Balance, + contract::ContractBalance, + gas_price::{EstimateGasPrice, LatestGasPrice}, + }, }; +use fuel_core_types::services::executor::{TransactionExecutionResult, TransactionExecutionStatus}; use fuel_tx::{ - AssetId, ConsensusParameters, Receipt, ScriptExecutionResult, Transaction as FuelTransaction, - TxId, UtxoId, + AssetId, ConsensusParameters, Receipt, Transaction as FuelTransaction, TxId, UtxoId, }; -use fuel_types::{Address, Bytes32, ChainId, Nonce}; +use fuel_types::{Address, BlockHeight, Bytes32, ChainId, Nonce}; #[cfg(feature = "coin-cache")] use fuels_core::types::coin_type_id::CoinTypeId; use fuels_core::{ - constants::{BASE_ASSET_ID, DEFAULT_GAS_ESTIMATION_TOLERANCE}, + constants::{ + BASE_ASSET_ID, DEFAULT_GAS_ESTIMATION_BLOCK_HORIZON, DEFAULT_GAS_ESTIMATION_TOLERANCE, + }, types::{ bech32::{Bech32Address, Bech32ContractId}, block::Block, @@ -31,7 +37,7 @@ use fuels_core::{ message::Message, message_proof::MessageProof, node_info::NodeInfo, - transaction::Transaction, + transaction::{Transaction, Transactions}, transaction_builders::DryRunner, transaction_response::TransactionResponse, tx_status::TxStatus, @@ -48,13 +54,14 @@ use crate::coin_cache::CoinsCache; use crate::provider::retryable_client::RetryableClient; #[derive(Debug)] +// ANCHOR: transaction_cost pub struct TransactionCost { - pub min_gas_price: u64, pub gas_price: u64, pub gas_used: u64, pub metered_bytes_size: u64, pub total_fee: u64, } +// ANCHOR_END: transaction_cost pub(crate) struct ResourceQueries { utxos: Vec, @@ -206,6 +213,7 @@ impl Provider { } self.validate_transaction(tx.clone()).await?; + Ok(()) } @@ -220,15 +228,11 @@ impl Provider { async fn validate_transaction(&self, tx: T) -> Result<()> { let tolerance = 0.0; - let TransactionCost { - gas_used, - min_gas_price, - .. - } = self - .estimate_transaction_cost(tx.clone(), Some(tolerance)) + let TransactionCost { gas_used, .. } = self + .estimate_transaction_cost(tx.clone(), Some(tolerance), None) .await?; - tx.validate_gas(min_gas_price, gas_used)?; + tx.validate_gas(gas_used)?; Ok(()) } @@ -299,39 +303,84 @@ impl Provider { Ok(self.client.node_info().await?.into()) } - pub async fn checked_dry_run(&self, tx: T) -> Result { - let receipts = self.dry_run(tx).await?; - Ok(Self::tx_status_from_receipts(receipts)) + pub async fn latest_gas_price(&self) -> Result { + Ok(self.client.latest_gas_price().await?) } - fn tx_status_from_receipts(receipts: Vec) -> TxStatus { - let revert_reason = receipts.iter().find_map(|receipt| match receipt { - Receipt::ScriptResult { result, .. } if *result != ScriptExecutionResult::Success => { - Some(format!("{result:?}")) - } - _ => None, - }); - - match revert_reason { - Some(reason) => TxStatus::Revert { - receipts, - reason, - revert_id: 0, - }, - None => TxStatus::Success { receipts }, - } + pub async fn estimate_gas_price(&self, block_horizon: u32) -> Result { + Ok(self.client.estimate_gas_price(block_horizon).await?) + } + + pub async fn dry_run(&self, tx: impl Transaction) -> Result { + let [(_, tx_status)] = self + .client + .dry_run(Transactions::new().insert(tx).as_slice()) + .await? + .into_iter() + .map(Self::tx_status_from_execution_status) + .collect::>() + .try_into() + .expect("should have only one element"); + + Ok(tx_status) } - pub async fn dry_run(&self, tx: T) -> Result> { - let receipts = self.client.dry_run(&tx.into()).await?; + pub async fn dry_run_multiple( + &self, + transactions: Transactions, + ) -> Result> { + Ok(self + .client + .dry_run(transactions.as_slice()) + .await? + .into_iter() + .map(Self::tx_status_from_execution_status) + .collect()) + } - Ok(receipts) + fn tx_status_from_execution_status( + tx_execution_status: TransactionExecutionStatus, + ) -> (TxId, TxStatus) { + ( + tx_execution_status.id, + match tx_execution_status.result { + TransactionExecutionResult::Success { receipts, .. } => { + TxStatus::Success { receipts } + } + TransactionExecutionResult::Failed { receipts, result } => TxStatus::Revert { + reason: TransactionExecutionResult::reason(&receipts, &result), + receipts, + revert_id: 0, + }, + }, + ) } - pub async fn dry_run_no_validation(&self, tx: T) -> Result> { - let receipts = self.client.dry_run_opt(&tx.into(), Some(false)).await?; + pub async fn dry_run_no_validation(&self, tx: impl Transaction) -> Result { + let [(_, tx_status)] = self + .client + .dry_run_opt(Transactions::new().insert(tx).as_slice(), Some(false)) + .await? + .into_iter() + .map(Self::tx_status_from_execution_status) + .collect::>() + .try_into() + .expect("should have only one element"); - Ok(receipts) + Ok(tx_status) + } + + pub async fn dry_run_no_validation_multiple( + &self, + transactions: Transactions, + ) -> Result> { + Ok(self + .client + .dry_run_opt(transactions.as_slice(), Some(false)) + .await? + .into_iter() + .map(Self::tx_status_from_execution_status) + .collect()) } /// Gets all unspent coins owned by address `from`, with asset ID `asset_id`. @@ -579,11 +628,14 @@ impl Provider { .into()) } - /// Get block by id. pub async fn block(&self, block_id: &Bytes32) -> Result> { Ok(self.client.block(block_id).await?.map(Into::into)) } + pub async fn block_by_height(&self, height: BlockHeight) -> Result> { + Ok(self.client.block_by_height(height).await?.map(Into::into)) + } + // - Get block(s) pub async fn get_blocks( &self, @@ -603,22 +655,23 @@ impl Provider { &self, tx: T, tolerance: Option, + block_horizon: Option, ) -> Result { - let NodeInfo { min_gas_price, .. } = self.node_info().await?; - let gas_price = std::cmp::max(tx.gas_price(), min_gas_price); + let block_horizon = block_horizon.unwrap_or(DEFAULT_GAS_ESTIMATION_BLOCK_HORIZON); let tolerance = tolerance.unwrap_or(DEFAULT_GAS_ESTIMATION_TOLERANCE); + let EstimateGasPrice { gas_price, .. } = self.estimate_gas_price(block_horizon).await?; + let gas_used = self .get_gas_used_with_tolerance(tx.clone(), tolerance) .await?; let transaction_fee = tx .clone() - .fee_checked_from_tx(&self.consensus_parameters) + .fee_checked_from_tx(&self.consensus_parameters, gas_price) .expect("Error calculating TransactionFee"); Ok(TransactionCost { - min_gas_price, gas_price, gas_used, metered_bytes_size: tx.metered_bytes_size() as u64, @@ -632,7 +685,8 @@ impl Provider { tx: T, tolerance: f64, ) -> Result { - let gas_used = self.get_gas_used(&self.dry_run_no_validation(tx).await?); + let receipts = self.dry_run_no_validation(tx).await?.take_receipts(); + let gas_used = self.get_gas_used(&receipts); Ok((gas_used as f64 * (1.0 + tolerance)) as u64) } @@ -697,14 +751,20 @@ impl Provider { #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] impl DryRunner for Provider { async fn dry_run_and_get_used_gas(&self, tx: FuelTransaction, tolerance: f32) -> Result { - let receipts = self.client.dry_run_opt(&tx, Some(false)).await?; - let gas_used = self.get_gas_used(&receipts); + let [tx_execution_status] = self + .client + .dry_run_opt(&vec![tx], Some(false)) + .await? + .try_into() + .expect("should have only one element"); + + let gas_used = self.get_gas_used(tx_execution_status.result.receipts()); Ok((gas_used as f64 * (1.0 + tolerance as f64)) as u64) } - async fn min_gas_price(&self) -> Result { - Ok(self.node_info().await.map(|ni| ni.min_gas_price)?) + async fn estimate_gas_price(&self, block_horizon: u32) -> Result { + Ok(self.estimate_gas_price(block_horizon).await?.gas_price) } fn consensus_parameters(&self) -> &ConsensusParameters { diff --git a/packages/fuels-accounts/src/provider/retryable_client.rs b/packages/fuels-accounts/src/provider/retryable_client.rs index f71d36704a..3a1da44fc9 100644 --- a/packages/fuels-accounts/src/provider/retryable_client.rs +++ b/packages/fuels-accounts/src/provider/retryable_client.rs @@ -3,13 +3,15 @@ use std::{future::Future, io}; use fuel_core_client::client::{ pagination::{PaginatedResult, PaginationRequest}, types::{ + gas_price::{EstimateGasPrice, LatestGasPrice}, primitives::{BlockId, TransactionId}, Balance, Block, ChainInfo, Coin, CoinType, ContractBalance, Message, MessageProof, NodeInfo, TransactionResponse, TransactionStatus, }, FuelClient, }; -use fuel_tx::{Receipt, Transaction, TxId, UtxoId}; +use fuel_core_types::services::executor::TransactionExecutionStatus; +use fuel_tx::{Transaction, TxId, UtxoId}; use fuel_types::{Address, AssetId, BlockHeight, ContractId, Nonce}; use fuels_core::types::errors::{error, Error, Result}; @@ -101,15 +103,28 @@ impl RetryableClient { self.our_retry(|| self.client.node_info()).await } - pub async fn dry_run(&self, tx: &Transaction) -> RequestResult> { + pub async fn latest_gas_price(&self) -> RequestResult { + self.our_retry(|| self.client.latest_gas_price()).await + } + + pub async fn estimate_gas_price(&self, block_horizon: u32) -> RequestResult { + self.our_retry(|| self.client.estimate_gas_price(block_horizon)) + .await + .map(Into::into) + } + + pub async fn dry_run( + &self, + tx: &[Transaction], + ) -> RequestResult> { self.our_retry(|| self.client.dry_run(tx)).await } pub async fn dry_run_opt( &self, - tx: &Transaction, + tx: &[Transaction], utxo_validation: Option, - ) -> RequestResult> { + ) -> RequestResult> { self.our_retry(|| self.client.dry_run_opt(tx, utxo_validation)) .await } @@ -202,6 +217,10 @@ impl RetryableClient { self.our_retry(|| self.client.block(id)).await } + pub async fn block_by_height(&self, height: BlockHeight) -> RequestResult> { + self.our_retry(|| self.client.block_by_height(height)).await + } + pub async fn blocks( &self, request: PaginationRequest, diff --git a/packages/fuels-accounts/src/provider/supported_versions.rs b/packages/fuels-accounts/src/provider/supported_versions.rs index 3ef803575b..478b6e894d 100644 --- a/packages/fuels-accounts/src/provider/supported_versions.rs +++ b/packages/fuels-accounts/src/provider/supported_versions.rs @@ -1,7 +1,7 @@ use semver::Version; fn get_supported_fuel_core_version() -> Version { - "0.22.0".parse().expect("is valid version") + "0.23.0".parse().expect("is valid version") } #[derive(Debug, PartialEq, Eq)] diff --git a/packages/fuels-core/src/types.rs b/packages/fuels-core/src/types.rs index 40edd2f0b2..502077f2c0 100644 --- a/packages/fuels-core/src/types.rs +++ b/packages/fuels-core/src/types.rs @@ -2,8 +2,8 @@ use std::fmt; use fuel_types::bytes::padded_len; pub use fuel_types::{ - Address, AssetId, Bytes32, Bytes4, Bytes64, Bytes8, ChainId, ContractId, MessageId, Nonce, - Salt, Word, + Address, AssetId, BlockHeight, Bytes32, Bytes4, Bytes64, Bytes8, ChainId, ContractId, + MessageId, Nonce, Salt, Word, }; pub use crate::types::{core::*, wrappers::*}; diff --git a/packages/fuels-core/src/types/transaction_builders.rs b/packages/fuels-core/src/types/transaction_builders.rs index 0c1f4bf723..ed664d2abd 100644 --- a/packages/fuels-core/src/types/transaction_builders.rs +++ b/packages/fuels-core/src/types/transaction_builders.rs @@ -10,7 +10,7 @@ use async_trait::async_trait; use fuel_asm::{op, GTFArgs, RegId}; use fuel_crypto::{Message as CryptoMessage, Signature}; use fuel_tx::{ - field::{Inputs, WitnessLimit, Witnesses}, + field::{Inputs, Policies as PoliciesField, WitnessLimit, Witnesses}, policies::{Policies, PolicyType}, Buildable, Chargeable, ConsensusParameters, Create, Input as FuelInput, Output, Script, StorageSlot, Transaction as FuelTransaction, TransactionFee, TxPointer, UniqueIdentifier, @@ -42,7 +42,7 @@ use crate::{ #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait DryRunner: Send + Sync { async fn dry_run_and_get_used_gas(&self, tx: FuelTransaction, tolerance: f32) -> Result; - async fn min_gas_price(&self) -> Result; + async fn estimate_gas_price(&self, block_horizon: u32) -> Result; fn consensus_parameters(&self) -> &ConsensusParameters; } @@ -52,8 +52,8 @@ impl DryRunner for &T { (*self).dry_run_and_get_used_gas(tx, tolerance).await } - async fn min_gas_price(&self) -> Result { - (*self).min_gas_price().await + async fn estimate_gas_price(&self, block_horizon: u32) -> Result { + (*self).estimate_gas_price(block_horizon).await } fn consensus_parameters(&self) -> &ConsensusParameters { @@ -70,12 +70,12 @@ struct UnresolvedWitnessIndexes { pub trait BuildableTransaction: sealed::Sealed { type TxType: Transaction; - async fn build(self, provider: &impl DryRunner) -> Result; + async fn build(self, provider: impl DryRunner) -> Result; /// Building without signatures will set the witness indexes of signed coins in the /// order as they appear in the inputs. Multiple coins with the same owner will have /// the same witness index. Make sure you sign the built transaction in the expected order. - async fn build_without_signatures(self, provider: &impl DryRunner) -> Result; + async fn build_without_signatures(self, provider: impl DryRunner) -> Result; } impl sealed::Sealed for ScriptTransactionBuilder {} @@ -84,11 +84,11 @@ impl sealed::Sealed for ScriptTransactionBuilder {} impl BuildableTransaction for ScriptTransactionBuilder { type TxType = ScriptTransaction; - async fn build(self, provider: &impl DryRunner) -> Result { + async fn build(self, provider: impl DryRunner) -> Result { self.build(provider).await } - async fn build_without_signatures(mut self, provider: &impl DryRunner) -> Result { + async fn build_without_signatures(mut self, provider: impl DryRunner) -> Result { self.set_witness_indexes(); self.unresolved_signers = Default::default(); @@ -102,11 +102,11 @@ impl sealed::Sealed for CreateTransactionBuilder {} impl BuildableTransaction for CreateTransactionBuilder { type TxType = CreateTransaction; - async fn build(self, provider: &impl DryRunner) -> Result { + async fn build(self, provider: impl DryRunner) -> Result { self.build(provider).await } - async fn build_without_signatures(mut self, provider: &impl DryRunner) -> Result { + async fn build_without_signatures(mut self, provider: impl DryRunner) -> Result { self.set_witness_indexes(); self.unresolved_signers = Default::default(); @@ -119,10 +119,8 @@ pub trait TransactionBuilder: BuildableTransaction + Send + sealed::Sealed { type TxType: Transaction; fn add_signer(&mut self, signer: impl Signer + Send + Sync) -> Result<&mut Self>; - async fn fee_checked_from_tx( - &self, - provider: &impl DryRunner, - ) -> Result>; + async fn fee_checked_from_tx(&self, provider: impl DryRunner) + -> Result>; fn with_tx_policies(self, tx_policies: TxPolicies) -> Self; fn with_inputs(self, inputs: Vec) -> Self; fn with_outputs(self, outputs: Vec) -> Self; @@ -133,6 +131,7 @@ pub trait TransactionBuilder: BuildableTransaction + Send + sealed::Sealed { fn outputs_mut(&mut self) -> &mut Vec; fn witnesses(&self) -> &Vec; fn witnesses_mut(&mut self) -> &mut Vec; + fn with_estimation_horizon(self, block_horizon: u32) -> Self; } macro_rules! impl_tx_trait { @@ -165,7 +164,7 @@ macro_rules! impl_tx_trait { async fn fee_checked_from_tx( &self, - provider: &impl DryRunner, + provider: impl DryRunner, ) -> Result> { let mut fee_estimation_tb = self.clone_without_signers(); @@ -177,7 +176,7 @@ macro_rules! impl_tx_trait { .extend(repeat(witness).take(self.unresolved_signers.len())); let mut tx = - BuildableTransaction::build_without_signatures(fee_estimation_tb, provider) + BuildableTransaction::build_without_signatures(fee_estimation_tb, &provider) .await?; let consensus_parameters = provider.consensus_parameters(); @@ -190,6 +189,9 @@ macro_rules! impl_tx_trait { &consensus_parameters.gas_costs, &consensus_parameters.fee_params, &tx.tx, + provider + .estimate_gas_price(self.gas_price_estimation_block_horizon) + .await?, )) } @@ -237,6 +239,12 @@ macro_rules! impl_tx_trait { fn witnesses_mut(&mut self) -> &mut Vec { &mut self.witnesses } + + fn with_estimation_horizon(mut self, block_horizon: u32) -> Self { + self.gas_price_estimation_block_horizon = block_horizon; + + self + } } impl $ty { @@ -255,9 +263,11 @@ macro_rules! impl_tx_trait { .collect(); } - fn generate_fuel_policies(&self, network_min_gas_price: u64) -> Policies { + fn generate_fuel_policies(&self) -> Policies { let mut policies = Policies::default(); - policies.set(PolicyType::MaxFee, self.tx_policies.max_fee()); + // `MaxFee` set to `tip` or `0` for `dry_run` + policies.set(PolicyType::MaxFee, self.tx_policies.tip().or(Some(0))); + policies.set(PolicyType::Maturity, self.tx_policies.maturity()); let witness_limit = self @@ -266,10 +276,7 @@ macro_rules! impl_tx_trait { .or_else(|| self.calculate_witnesses_size()); policies.set(PolicyType::WitnessLimit, witness_limit); - policies.set( - PolicyType::GasPrice, - self.tx_policies.gas_price().or(Some(network_min_gas_price)), - ); + policies.set(PolicyType::Tip, self.tx_policies.tip()); policies } @@ -300,6 +307,31 @@ macro_rules! impl_tx_trait { Some(padded_len_usize(witnesses_size + signature_size) as u64) } + + async fn set_max_fee_policy( + tx: &mut T, + provider: impl DryRunner, + block_horizon: u32, + ) -> Result<()> { + let gas_price = provider.estimate_gas_price(block_horizon).await?; + let consensus_parameters = provider.consensus_parameters(); + + let tx_fee = TransactionFee::checked_from_tx( + &consensus_parameters.gas_costs, + consensus_parameters.fee_params(), + tx, + gas_price, + ) + .ok_or(error_transaction!( + Other, + "error calculating `TransactionFee` in `TransactionBuilder`" + ))?; + + tx.policies_mut() + .set(PolicyType::MaxFee, Some(tx_fee.max_fee())); + + Ok(()) + } } }; } @@ -321,6 +353,7 @@ pub struct ScriptTransactionBuilder { pub witnesses: Vec, pub tx_policies: TxPolicies, pub gas_estimation_tolerance: f32, + pub gas_price_estimation_block_horizon: u32, unresolved_witness_indexes: UnresolvedWitnessIndexes, unresolved_signers: Vec>, } @@ -335,6 +368,7 @@ pub struct CreateTransactionBuilder { pub witnesses: Vec, pub tx_policies: TxPolicies, pub salt: Salt, + pub gas_price_estimation_block_horizon: u32, unresolved_witness_indexes: UnresolvedWitnessIndexes, unresolved_signers: Vec>, } @@ -343,7 +377,7 @@ impl_tx_trait!(ScriptTransactionBuilder, ScriptTransaction); impl_tx_trait!(CreateTransactionBuilder, CreateTransaction); impl ScriptTransactionBuilder { - async fn build(self, provider: &impl DryRunner) -> Result { + async fn build(self, provider: impl DryRunner) -> Result { let is_using_predicates = self.is_using_predicates(); let base_offset = if is_using_predicates { self.base_offset(provider.consensus_parameters()) @@ -385,7 +419,7 @@ impl ScriptTransactionBuilder { async fn set_script_gas_limit_to_gas_used( tx: &mut Script, - provider: &impl DryRunner, + provider: impl DryRunner, tolerance: f32, ) -> Result<()> { let consensus_params = provider.consensus_parameters(); @@ -401,7 +435,6 @@ impl ScriptTransactionBuilder { Default::default(), TxPointer::default(), 0, - 0u32.into(), )); // Add an empty `Witness` for the `coin_signed` we just added @@ -431,13 +464,9 @@ impl ScriptTransactionBuilder { Ok(()) } - async fn resolve_fuel_tx( - self, - base_offset: usize, - provider: &impl DryRunner, - ) -> Result