diff --git a/.changeset/mean-sheep-occur.md b/.changeset/mean-sheep-occur.md new file mode 100644 index 00000000000..014a178950c --- /dev/null +++ b/.changeset/mean-sheep-occur.md @@ -0,0 +1,5 @@ +--- +"@fuel-ts/account": patch +--- + +chore: avoid re-estimate `gasPrice` at `estimateTxDependencies` diff --git a/packages/account/src/account.ts b/packages/account/src/account.ts index 112aaaaa318..60484577999 100644 --- a/packages/account/src/account.ts +++ b/packages/account/src/account.ts @@ -323,6 +323,7 @@ export class Account extends AbstractAccount { const { maxFee } = await this.provider.estimateTxGasAndFee({ transactionRequest: requestToReestimate, + gasPrice, }); request.maxFee = maxFee; diff --git a/packages/account/src/providers/provider.ts b/packages/account/src/providers/provider.ts index b07bcfd1d67..5f9ffe88f42 100644 --- a/packages/account/src/providers/provider.ts +++ b/packages/account/src/providers/provider.ts @@ -1011,6 +1011,7 @@ Supported fuel-core version: ${supportedVersion}.` } = await this.operations.dryRun({ encodedTransactions: [hexlify(transactionRequest.toTransactionBytes())], utxoValidation: false, + gasPrice: '0', }); receipts = rawReceipts.map(processGqlReceipt); @@ -1032,6 +1033,7 @@ Supported fuel-core version: ${supportedVersion}.` const { maxFee } = await this.estimateTxGasAndFee({ transactionRequest, + gasPrice: bn(0), }); // eslint-disable-next-line no-param-reassign @@ -1204,7 +1206,7 @@ Supported fuel-core version: ${supportedVersion}.` const { gasPriceFactor, maxGasPerTx } = this.getGasConfig(); const minGas = transactionRequest.calculateMinGas(chainInfo); - if (!gasPrice) { + if (!isDefined(gasPrice)) { gasPrice = await this.estimateGasPrice(10); } diff --git a/packages/fuel-gauge/src/fee.test.ts b/packages/fuel-gauge/src/fee.test.ts index 17b87f5d055..f5c5a53440e 100644 --- a/packages/fuel-gauge/src/fee.test.ts +++ b/packages/fuel-gauge/src/fee.test.ts @@ -3,6 +3,7 @@ import { InputMessageCoder, ScriptTransactionRequest, Wallet, + getMintedAssetId, getRandomB256, hexlify, isCoin, @@ -40,6 +41,8 @@ describe('Fee', () => { } }; + const SUB_ID = '0x4a778acfad1abc155a009dc976d2cf0db6197d3d360194d74b1fb92b96986b00'; + it('should ensure fee is properly calculated when minting and burning coins', async () => { using launched = await launchTestNode({ contractsConfigs: [ @@ -431,5 +434,81 @@ describe('Fee', () => { expect(cost.dryRunStatus?.type).toBe('DryRunSuccessStatus'); }); + + it('should not run estimateGasPrice in between estimateTxDependencies dry run attempts', async () => { + using launched = await launchTestNode({ + contractsConfigs: [ + { + factory: MultiTokenContractFactory, + }, + ], + }); + + const { + contracts: [contract], + wallets: [wallet], + provider, + } = launched; + + const assetId = getMintedAssetId(contract.id.toB256(), SUB_ID); + + // Minting coins first + const mintCall = await contract.functions.mint_coins(SUB_ID, 10_000).call(); + await mintCall.waitForResult(); + + const estimateGasPrice = vi.spyOn(provider, 'estimateGasPrice'); + const dryRun = vi.spyOn(provider.operations, 'dryRun'); + + /** + * Sway transfer without adding `OutputVariable` which will result in + * 2 dry runs at the `Provider.estimateTxDependencies` method: + * - 1st dry run will fail due to missing `OutputVariable` + * - 2nd dry run will succeed + */ + const transferCall = await contract.functions + .transfer_to_address({ bits: wallet.address.toB256() }, { bits: assetId }, 10_000) + .call(); + + await transferCall.waitForResult(); + + expect(estimateGasPrice).toHaveBeenCalledOnce(); + expect(dryRun).toHaveBeenCalledTimes(2); + }); + + it('should ensure estimateGasPrice runs only once when funding a transaction.', async () => { + const amountPerCoin = 100; + + using launched = await launchTestNode({ + walletsConfig: { + amountPerCoin, // Funding with multiple UTXOs so the fee will change after funding the TX. + coinsPerAsset: 250, + }, + contractsConfigs: [ + { + factory: MultiTokenContractFactory, + }, + ], + }); + + const { + wallets: [wallet], + provider, + } = launched; + + const fund = vi.spyOn(wallet, 'fund'); + const estimateGasPrice = vi.spyOn(provider, 'estimateGasPrice'); + + const tx = await wallet.transfer( + wallet.address, + amountPerCoin * 20, + provider.getBaseAssetId() + ); + const { isStatusSuccess } = await tx.waitForResult(); + + expect(fund).toHaveBeenCalledOnce(); + expect(estimateGasPrice).toHaveBeenCalledOnce(); + + expect(isStatusSuccess).toBeTruthy(); + }); }); });